1 #!/usr/bin/env python 2 3 """ 4 A setup and configuration script for MoinMoin. 5 6 Copyright (C) 2010, 2011, 2012 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from os.path import abspath, exists, extsep, isdir, islink, join, normpath, split 23 from os import chdir, chmod, listdir, mkdir, makedirs, remove, rename, rmdir 24 from getpass import getpass 25 from glob import glob 26 from zipfile import ZipFile 27 import os 28 import sys 29 import shutil 30 import re 31 import tempfile 32 33 __version__ = "0.4" 34 35 # Regular expressions for editing MoinMoin scripts and configuration files. 36 37 def compile_definition(name): 38 return re.compile(r"^(\s*)#*\s*(%s =)\s*(.*)$" % name, re.MULTILINE) 39 40 wikiconfig_import = re.compile(r"^(\s*)#*\s*" 41 r"(from\s+)(?P<module>\S+)" 42 r"(\s+import\s+)(?P<names>(?:\S|[^,\s])+(?:\s*,\s*(?:\S|[^,\s])+)*)" 43 r"(\s*)$", re.MULTILINE) 44 45 moin_cgi_prefix = re.compile(r"^#sys\.path\.insert\(0, 'PREFIX.*$", re.MULTILINE) 46 moin_cgi_wikiconfig = re.compile(r"^#sys\.path\.insert\(0, '/path/to/wikiconfigdir.*$", re.MULTILINE) 47 moin_cgi_properties = compile_definition("properties") 48 moin_cgi_fix_script_name = compile_definition("fix_script_name") 49 moin_cgi_force_cgi = re.compile(r"^#(os.environ\['FCGI_FORCE_CGI'\].*)$", re.MULTILINE) 50 51 css_import_stylesheet = re.compile(r"(\s*@import\s+[\"'])(.*?)([\"']\s*;)") 52 53 # Templates for Apache site definitions. 54 55 apache_site = """\ 56 ScriptAlias %(url_path)s "%(web_app_dir)s/moin.cgi" 57 """ 58 59 apache_site_extra = """\ 60 Alias %(static_url_resources_path)s "%(htdocs_dir)s/" 61 """ 62 63 # Limited hosting .htaccess definitions require the following settings to be 64 # configured in the main Apache configuration files for the directory where 65 # .htaccess is to be deployed: 66 # 67 # AllowOverride FileInfo Indexes 68 # 69 # The following settings are required for the directory where the moin.cgi 70 # script is to be deployed: 71 # 72 # Options ExecCGI FollowSymLinks SymLinksIfOwnerMatch 73 # AddHandler cgi-script .cgi 74 # 75 # If a DirectoryIndex directive is also desired, the Indexes option must be set. 76 # Such a directive is not desirable where the static and dynamic resources are 77 # in different places, however. 78 79 apache_htaccess_combined_mod_rewrite = """\ 80 DirectoryIndex %(url_path)s/moin.cgi/ 81 RewriteEngine On 82 RewriteBase %(final_url_path)s 83 RewriteCond %%{REQUEST_FILENAME} !-f 84 RewriteCond %%{REQUEST_FILENAME} !-d 85 RewriteRule ^(.*) %(url_path)s/moin.cgi/$1 [PT,L,QSA] 86 """ 87 88 # Post-setup templates. 89 90 postsetup_setfacl = """\ 91 #!/bin/sh 92 93 find '%(common_dir)s/data' -type f | xargs setfacl -m u:%(web_user)s:rw 94 find '%(common_dir)s/data' -type d | xargs setfacl -m u:%(web_user)s:rwx 95 find '%(common_dir)s/underlay' -type f | xargs setfacl -m u:%(web_user)s:rw 96 find '%(common_dir)s/underlay' -type d | xargs setfacl -m u:%(web_user)s:rwx 97 if [ -e "%(common_dir)s/wikiconfig.py" ]; then 98 setfacl -m u:%(web_user)s:r '%(common_dir)s/wikiconfig.py' 99 fi 100 """ 101 102 postsetup_setfacl_extra = """ 103 find '%(htdocs_dir)s' -type f | xargs setfacl -m u:%(web_user)s:r 104 find '%(htdocs_dir)s' -type d | xargs setfacl -m u:%(web_user)s:rx 105 """ 106 107 postsetup_setfacl_logs = """ 108 if [ -e "%(common_dir)s/data/*-log" ]; then 109 setfacl -m g:%(web_group)s:rw %(common_dir)s/data/*-log 110 fi 111 """ 112 113 postsetup_chown_chmod = """\ 114 #!/bin/sh 115 116 chown -R %(this_user)s.%(web_group)s '%(common_dir)s/data' 117 chown -R %(this_user)s.%(web_group)s '%(common_dir)s/underlay' 118 chmod -R g+w '%(common_dir)s/data' 119 chmod -R g+w '%(common_dir)s/underlay' 120 if [ -e "%(common_dir)s/wikiconfig.py" ]; then 121 chown %(this_user)s.%(web_group)s '%(common_dir)s/wikiconfig.py' 122 chmod g+r '%(common_dir)s/wikiconfig.py' 123 fi 124 """ 125 126 postsetup_chown_extra = """ 127 chown -R %(this_user)s.%(web_group)s '%(htdocs_dir)s' 128 """ 129 130 postsetup_chown_logs = """ 131 if [ -e "%(common_dir)s/data/*-log" ]; then 132 chown %(this_user)s.%(web_group)s %(common_dir)s/data/*-log 133 fi 134 """ 135 136 # Utility functions. 137 138 def readfile(filename): 139 f = open(filename) 140 try: 141 return f.read() 142 finally: 143 f.close() 144 145 def writefile(filename, s): 146 f = open(filename, "w") 147 try: 148 f.write(s) 149 finally: 150 f.close() 151 152 def status(message): 153 print message 154 155 note = status 156 error = status 157 158 def format(s, indent): 159 return re.sub("\n\s+", "\n" + " " * indent, s) 160 161 # Classes. 162 163 class SetupException(Exception): 164 165 "An exception indicating a problem with a setup action." 166 167 pass 168 169 class Configuration: 170 171 "A class representing the configuration." 172 173 special_names = ["site_name"] 174 175 def __init__(self, filename): 176 self.content = readfile(filename) 177 self.filename = filename 178 179 def get_pattern(self, name): 180 181 # Make underscores optional for certain names. 182 183 if name in self.special_names: 184 name = name.replace("_", "_?") 185 186 return compile_definition(name) 187 188 def get(self, name): 189 190 """ 191 Return the raw value of the last definition having the given 'name'. 192 """ 193 194 pattern = self.get_pattern(name) 195 results = [match.group(3) for match in pattern.finditer(self.content)] 196 if results: 197 return results[-1] 198 else: 199 return None 200 201 def set(self, name, value, count=None, raw=0): 202 203 """ 204 Set the configuration parameter having the given 'name' with the given 205 'value', limiting the number of appropriately named parameters changed 206 to 'count', if specified. 207 208 If the configuration parameter of the given 'name' does not exist, 209 insert such a parameter at the end of the file. 210 211 If the optional 'raw' parameter is specified and set to a true value, 212 the provided 'value' is inserted directly into the configuration file. 213 """ 214 215 if not self.replace(name, value, count, raw): 216 self.insert(name, value, raw) 217 218 def replace(self, name, value, count=None, raw=0): 219 220 """ 221 Replace configuration parameters having the given 'name' with the given 222 'value', limiting the number of appropriately named parameters changed 223 to 'count', if specified. 224 225 If the optional 'raw' parameter is specified and set to a true value, 226 the provided 'value' is inserted directly into the configuration file. 227 228 Return the number of substitutions made. 229 """ 230 231 if raw: 232 substitution = r"\1\2 %s" % value 233 else: 234 substitution = r"\1\2 %r" % value 235 236 pattern = self.get_pattern(name) 237 238 if count is None: 239 self.content, n = pattern.subn(substitution, self.content) 240 else: 241 self.content, n = pattern.subn(substitution, self.content, count=count) 242 243 return n 244 245 def insert(self, name, value, raw=0): 246 247 """ 248 Insert the configuration parameter having the given 'name' and 'value'. 249 250 If the optional 'raw' parameter is specified and set to a true value, 251 the provided 'value' is inserted directly into the configuration file. 252 """ 253 254 if raw: 255 insertion = "%s = %s" 256 else: 257 insertion = "%s = %r" 258 259 self.insert_text(insertion % (name, value)) 260 261 def insert_text(self, text): 262 263 "Insert the given 'text' at the end of the configuration." 264 265 if not self.content.endswith("\n"): 266 self.content += "\n" 267 self.content += " %s\n" % text 268 269 def set_import(self, imported_module, imported_names): 270 271 """ 272 Set up an import of the given 'imported_module' exposing the given 273 'imported_names'. 274 """ 275 276 s = self.content 277 after_point = 0 278 first_point = None 279 280 for module_import in wikiconfig_import.finditer(s): 281 before, from_keyword, module, import_keyword, names, after = module_import.groups() 282 before_point, after_point = module_import.span() 283 284 if first_point is None: 285 first_point = after_point 286 287 names = [name.strip() for name in names.split(",")] 288 289 # Test the import for a reference to the requested imported module. 290 291 if imported_module == module: 292 for name in imported_names: 293 if name not in names: 294 names.append(name) 295 296 self.content = s[:before_point] + ( 297 "%s%s%s%s%s%s" % (before, from_keyword, module, import_keyword, ", ".join(names), after) 298 ) + s[after_point:] 299 break 300 301 # Where no import references the imported module, insert a reference 302 # into the configuration. 303 304 else: 305 # Add the import after the first one. 306 307 if first_point is not None: 308 self.content = s[:first_point] + ("\nfrom %s import %s" % (imported_module, ", ".join(imported_names))) + s[first_point:] 309 310 def close(self): 311 312 "Close the file, writing the content." 313 314 writefile(self.filename, self.content) 315 316 class Installation: 317 318 "A class for installing and initialising MoinMoin." 319 320 method_names = ( 321 "show_config", 322 "setup", 323 "setup_wiki", 324 "install_moin", 325 "install_data", 326 "install_static_data", 327 "configure_moin", 328 "edit_moin_script", 329 "edit_moin_web_script", 330 "add_superuser", 331 "make_site_files", 332 "make_post_install_script", 333 334 # Post-installation activities. 335 336 "reconfigure_moin", 337 "set_auth_method", 338 "migrate_instance", 339 "install_theme", 340 "install_extension_package", 341 "install_plugins", 342 "install_actions", 343 "install_macros", 344 "install_parsers", 345 "install_event_handlers", 346 "install_theme_resources", 347 "edit_theme_stylesheet", 348 349 # Other activities. 350 351 "make_page_package", 352 "install_page_package", 353 ) 354 355 source_config_names = ( 356 "moin_distribution", "prefix", "site_packages", "prefix_site_packages", 357 "htdocs_dir_source" 358 ) 359 360 instance_config_names = ( 361 "common_dir", "farm_config", "site_config", 362 "site_name", "site_identifier", 363 "front_page_name", "superuser", "theme_default", "htdocs_dir" 364 ) 365 366 site_config_names = ( 367 "web_app_dir", "web_site_dir", "web_static_dir", 368 "url_path", "static_url_path" 369 ) 370 371 # NOTE: Need to detect Web server user. 372 373 web_user = "www-data" 374 web_group = "www-data" 375 376 # MoinMoin resources. 377 378 theme_master = "modernized" 379 extra_theme_css_files = ["SlideShow.css"] 380 381 def __init__(self, 382 moin_distribution=None, 383 prefix=None, site_packages=None, 384 web_app_dir=None, web_static_dir=None, web_site_dir=None, 385 common_dir=None, farm_config=None, site_config=None, 386 url_path=None, static_url_path=None, 387 superuser=None, site_name=None, site_identifier=None, front_page_name=None, 388 theme_default=None): 389 390 """ 391 Initialise a Wiki installation using the following installation 392 settings: 393 394 * moin_distribution - the directory containing a MoinMoin source 395 distribution (can be omitted) 396 * prefix - the installation prefix (equivalent to /usr) 397 * site_packages - optional: the location of the Python 398 site-packages directory if outside the 'prefix' 399 (overrides the path calculated using 'prefix') 400 * web_app_dir - the directory where Web applications and scripts 401 reside (such as /home/www-user/cgi-bin) 402 * web_static_dir - optional: the directory where static Web 403 resources reside (such as /home/www-user/htdocs) 404 * web_site_dir - optional: the directory where Web site 405 definitions reside (such as 406 /etc/apache2/sites-available) 407 408 The following site settings are also applicable: 409 410 * common_dir - the directory where the Wiki configuration, 411 resources and instance will reside (such as 412 /home/www-user/mywiki) 413 * farm_config - optional: any Wiki farm configuration file for 414 multiple Wiki deployments (overrides the 415 'common_dir' setting) 416 * site_config - optional: a specific configuration file location 417 (overrides the 'common_dir' setting) 418 * url_path - the URL path at which the Wiki will be made 419 available (such as / or /mywiki) 420 * static_url_path - optional: the URL path at which static resources 421 will be made available (such as / or /mywiki) 422 * superuser - the name of the site's superuser (such as 423 "AdminUser", can be omitted) 424 * site_name - the name of the site (such as "My Wiki") 425 * site_identifier - optional: an identifier used to refer to the 426 site, typically derived from 'site_name' 427 * front_page_name - the front page name for the site (such as 428 "FrontPage" or a specific name for the site) 429 * theme_default - optional: the default theme (such as modern) 430 """ 431 432 self.moin_distribution = moin_distribution 433 self.superuser = superuser 434 self.site_name = site_name 435 self.site_identifier = site_identifier or site_name.replace(" ", "").lower() 436 self.front_page_name = front_page_name 437 self.farm_config = farm_config 438 self.site_config = site_config 439 self.theme_default = theme_default 440 441 # NOTE: Support the detection of the Apache sites directory. 442 443 self.prefix, self.site_packages, self.web_app_dir, self.web_site_dir, self.web_static_dir, self.common_dir = \ 444 map(self._get_abspath, (prefix, site_packages, web_app_dir, web_site_dir, web_static_dir, common_dir)) 445 446 if not self.web_app_dir: 447 raise TypeError, "The 'web_app_dir' setting must be specified." 448 449 # Strip any trailing "/" from the URL path. 450 451 if not url_path: 452 raise TypeError, "The 'url_path' setting must be specified." 453 454 self.url_path = self._tidy_url_path(url_path) 455 self.static_url_path = static_url_path and self._tidy_url_path(static_url_path) 456 457 # Define and create specific directories. 458 # Here are the configuration and actual Wiki data directories. 459 460 if not self.common_dir: 461 raise TypeError, "The 'common_dir' setting must be specified." 462 463 # Define the place where the MoinMoin package will actually reside. 464 465 if not self.prefix and not self.site_packages: 466 raise TypeError, "Either the 'prefix' or the 'site_packages' setting must be specified." 467 468 self.prefix_site_packages = self.site_packages or \ 469 join(self.prefix, "lib", "python%s.%s" % sys.version_info[:2], "site-packages") 470 471 # Find the version. 472 473 self.moin_version = self.get_moin_version() 474 475 # The static resources reside in different locations depending on the 476 # version of MoinMoin, but the Web server is used to serve static 477 # resources in both cases, even though MoinMoin 1.9 can serve static 478 # files itself. 479 480 # A shared data directory may be in use. 481 482 self.htdocs_dir_source = join(self.get_moin_data(), "htdocs") 483 484 if self.htdocs_dir_source is None or not exists(self.htdocs_dir_source): 485 486 # 1.9: moin/lib/python2.x/site-packages/MoinMoin/web/static/htdocs 487 488 if self.moin_version.startswith("1.9"): 489 self.htdocs_dir_source = join(self.prefix_site_packages, "MoinMoin", "web", "static", "htdocs") 490 else: 491 raise SetupException, "The static resources could not be found." 492 493 # Add the static identifier to the URL path. For example: 494 # 495 # / -> /moin_static187 496 # /hgwiki -> /hgwiki-moin_static187 497 # 498 # This allows multiple Wiki instances to have their own static resources 499 # in the same hosting area. 500 # 501 # Where a separate static URL path has been given, the resources are 502 # located under that path: 503 # 504 # / -> /moin_static187 505 # /hgwiki -> /hgwiki/moin_static187 506 507 # The final URL path is the principal location of the Wiki. 508 509 self.final_url_path = self.static_url_path or self.url_path 510 511 # The static URL resources path is the specific location of static 512 # resources. 513 514 self.static_url_resources_path = \ 515 self.final_url_path + \ 516 (self.static_url_path 517 and (self.static_url_path != "/" 518 and "/" 519 or "") 520 or (self.final_url_path != "/" 521 and "-" 522 or "")) + \ 523 self.get_static_identifier() 524 525 self.static_dir_name = self.static_url_resources_path.split("/")[-1] 526 527 # In limited hosting, the static resources directory is related to 528 # the URL path, either a specified static URL path or the general path. 529 530 if self.limited_hosting(): 531 self.htdocs_dir = join(self.web_static_dir or self.web_app_dir, self.static_dir_name) 532 533 # Otherwise, a mapping is made to the directory. 534 # This may be placed in a special static directory if desired. 535 536 else: 537 self.htdocs_dir = join(self.web_static_dir or self.common_dir, "htdocs") 538 539 def show_config(self): 540 541 "Show the configuration." 542 543 print 544 for section in ("source", "instance", "site"): 545 print section.title() 546 print "-" * len(section) 547 print 548 for setting in getattr(self, "%s_config_names" % section): 549 print "%-24s%s" % (setting, getattr(self, setting)) 550 print 551 552 print "Configuration locations" 553 print "-----------------------" 554 print 555 print "%-24s%s" % ("site-level", self.get_site_config()) 556 print "%-24s%s" % ("global", self.get_global_config()) 557 print 558 print "Theme directories" 559 print "-----------------" 560 print 561 562 try: 563 for theme in self.get_theme_directories(): 564 print "%-24s%s" % theme 565 except OSError, exc: 566 print "Not shown:", str(exc) 567 568 def _get_abspath(self, d): 569 return d and abspath(d) or None 570 571 def _tidy_url_path(self, url_path): 572 if url_path != "/" and url_path.endswith("/"): 573 return url_path[:-1] 574 else: 575 return url_path 576 577 def get_moin_version(self): 578 579 "Return the MoinMoin version." 580 581 this_dir = os.getcwd() 582 583 for dir in [self.moin_distribution, self.prefix_site_packages]: 584 if dir: 585 try: 586 chdir(dir) 587 version = self.get_moin_version_from_package_info() or \ 588 self.get_moin_version_from_import() 589 if version: 590 return version 591 592 finally: 593 chdir(this_dir) 594 else: 595 return self.get_moin_version_from_import() 596 597 def get_moin_version_from_package_info(self): 598 599 "Inspect the MoinMoin package information, returning the version." 600 601 try: 602 f = open("PKG-INFO") 603 try: 604 for line in f.xreadlines(): 605 columns = line.split() 606 if columns[0] == "Version:": 607 return columns[1] 608 finally: 609 f.close() 610 611 except IOError: 612 pass 613 614 return None 615 616 def get_moin_version_from_import(self): 617 618 "Return the MoinMoin version from an import of the package itself." 619 620 # Where no distribution information can be read, try and import an 621 # installed version module. 622 623 f = os.popen("%s -c 'from MoinMoin.version import release; print release'" % sys.executable) 624 try: 625 return f.read().strip() 626 finally: 627 f.close() 628 629 def get_moin_data(self): 630 631 "Return the exact location of MoinMoin data." 632 633 return self.moin_distribution and join(self.moin_distribution, "wiki") or \ 634 self.prefix and join(self.prefix, "share", "moin") or None 635 636 def get_moin_script(self): 637 638 "Return the location of the general-purpose moin script." 639 640 return join(self.prefix, "bin", "moin") 641 642 def get_wikiconfig_directory(self): 643 644 "Return the location of the Wiki configuration." 645 646 if self.site_config: 647 return split(self.site_config)[0] 648 else: 649 return self.common_dir 650 651 def get_site_config(self): 652 653 "Return the file providing the site-level configuration." 654 655 if self.site_config: 656 return self.site_config 657 else: 658 return join(self.common_dir, "wikiconfig.py") 659 660 def get_global_config(self): 661 662 "Return the file providing the global MoinMoin configuration." 663 664 if self.farm_config: 665 return self.farm_config 666 else: 667 return join(self.common_dir, "wikiconfig.py") 668 669 def get_static_identifier(self): 670 671 "Return the static URL/directory identifier for the Wiki." 672 673 return "moin_static%s" % self.moin_version.replace(".", "") 674 675 def get_plugin_directory(self, plugin_type): 676 677 "Return the directory for plugins of the given 'plugin_type'." 678 679 data_dir = join(self.common_dir, "data") 680 return join(data_dir, "plugin", plugin_type) 681 682 def limited_hosting(self): 683 684 "Return whether limited Web hosting is being used." 685 686 return not self.web_site_dir 687 688 def ensure_directories(self): 689 690 "Make sure that all the directories are available." 691 692 for d in (self.common_dir, self.web_app_dir, self.web_static_dir, self.web_site_dir): 693 if d is not None and not exists(d): 694 makedirs(d) 695 696 def get_theme_directories(self, theme_name=None): 697 698 """ 699 Return tuples of the form (theme name, theme directory) for all themes, 700 or for a single theme if the optional 'theme_name' is specified. 701 """ 702 703 filenames = theme_name and [theme_name] or listdir(self.htdocs_dir) 704 directories = [] 705 706 for filename in filenames: 707 theme_dir = join(self.htdocs_dir, filename) 708 709 if not exists(theme_dir) or not isdir(theme_dir): 710 continue 711 712 directories.append((filename, theme_dir)) 713 714 return directories 715 716 # Main methods. 717 718 def setup(self): 719 720 "Set up the installation." 721 722 self.ensure_directories() 723 self.install_moin() 724 self.edit_moin_script() 725 self._setup_wiki() 726 727 def setup_wiki(self): 728 729 "Set up a Wiki without installing MoinMoin." 730 731 self.ensure_directories() 732 self._setup_wiki() 733 734 def _setup_wiki(self): 735 736 "Set up a Wiki without installing MoinMoin." 737 738 self.install_data() 739 self.install_static_data() 740 self.configure_moin() 741 self.add_superuser() 742 self.edit_moin_web_script(self.make_site_files()) 743 self.make_post_install_script() 744 745 if self.moin_version.startswith("1.9"): 746 note("You may need to visit the LanguageSetup page in the Wiki to create the standard set of pages.") 747 748 def install_moin(self): 749 750 "Enter the distribution directory and run the setup script." 751 752 # NOTE: Possibly check for an existing installation and skip repeated 753 # NOTE: installation attempts. 754 755 if not self.moin_distribution: 756 raise SetupException, "Cannot install MoinMoin without a 'moin_distribution' setting being defined." 757 758 this_dir = os.getcwd() 759 chdir(self.moin_distribution) 760 761 log_filename = "install-%s.log" % split(self.common_dir)[-1] 762 763 status("Installing MoinMoin in %s..." % self.prefix) 764 765 install_cmd = "install" 766 options = "--prefix='%s' --record='%s'" % (self.prefix, log_filename) 767 768 os.system("python setup.py --quiet %s %s --force" % (install_cmd, options)) 769 770 chdir(this_dir) 771 772 def install_data(self): 773 774 "Install Wiki data into an instance." 775 776 moin_data = self.get_moin_data() 777 778 if not moin_data: 779 raise SetupException, \ 780 "Cannot install MoinMoin data without either a 'moin_distribution' or a 'prefix' setting being defined." 781 782 # The default wikiconfig assumes data and underlay in the same directory. 783 784 status("Installing data and underlay in %s..." % self.common_dir) 785 786 for d in ("data", "underlay"): 787 source = join(moin_data, d) 788 source_tar = source + extsep + "tar" 789 790 if exists(source): 791 shutil.copytree(source, join(self.common_dir, d)) 792 elif exists(source_tar): 793 794 note("Copying archive %s instead of directory %s. Running...\n" 795 "make pagepacks\n" 796 "in the distribution directory should rectify this." % (source_tar, source)) 797 798 shutil.copy(source_tar, self.common_dir) 799 os.system("tar xf %s -C %s" % (source_tar, self.common_dir)) 800 else: 801 status("Could not copy %s into installed Wiki." % d) 802 803 def install_static_data(self): 804 805 "Install static Web data if appropriate." 806 807 if not exists(self.htdocs_dir): 808 mkdir(self.htdocs_dir) 809 810 for item in listdir(self.htdocs_dir_source): 811 path = join(self.htdocs_dir_source, item) 812 if isdir(path): 813 shutil.copytree(path, join(self.htdocs_dir, item)) 814 else: 815 shutil.copy(path, join(self.htdocs_dir, item)) 816 817 def configure_moin(self, reset=0): 818 819 """ 820 Edit the Wiki configuration file. If the optional 'reset' parameter is 821 specified as a true value, a default configuration will be copied from 822 the distribution if appropriate. 823 """ 824 825 moin_data = self.get_moin_data() 826 827 if not moin_data: 828 raise SetupException, \ 829 "Cannot configure MoinMoin without either a 'moin_distribution' or a 'prefix' setting being defined." 830 831 # NOTE: MoinMoin usually uses an apparently common URL space associated 832 # NOTE: with the version, but more specific locations are probably 833 # NOTE: acceptable if less efficient. 834 835 url_prefix_static = "%r" % self.static_url_resources_path 836 837 # Use a farm configuration file. 838 839 if self.farm_config: 840 wikiconfig_py = self.farm_config 841 842 # Or copy the Wiki configuration file from the distribution. 843 844 else: 845 wikiconfig_py = join(self.common_dir, "wikiconfig.py") 846 847 if not exists(wikiconfig_py) or reset: 848 shutil.copyfile(join(moin_data, "config", "wikiconfig.py"), wikiconfig_py) 849 850 status("Editing configuration from %s..." % wikiconfig_py) 851 852 # Edit the Wiki configuration file. 853 854 wikiconfig = Configuration(wikiconfig_py) 855 856 try: 857 wikiconfig.set("url_prefix_static", url_prefix_static, raw=1) 858 if self.superuser: 859 wikiconfig.set("superuser", [self.superuser]) 860 wikiconfig.set("acl_rights_before", u"%s:read,write,delete,revert,admin" % self.superuser) 861 else: 862 note("Superuser not defined. The ACL rules should be fixed in the configuration.") 863 864 # Edit any created Wiki configuration. 865 866 if not self.site_config: 867 self._configure_moin(wikiconfig) 868 869 finally: 870 wikiconfig.close() 871 872 # Edit any separate site configuration file. 873 874 if self.site_config: 875 status("Editing configuration from %s..." % self.site_config) 876 877 wikiconfig = Configuration(self.site_config) 878 879 try: 880 self._configure_moin(wikiconfig) 881 finally: 882 wikiconfig.close() 883 884 def _configure_moin(self, wikiconfig): 885 886 """ 887 Configure Moin, accessing the configuration file using 'wikiconfig'. 888 """ 889 890 # Specific site configurations also appear to need 'data_dir', even in 891 # 1.9. 892 893 if not self.moin_version.startswith("1.9") or self.site_config: 894 data_dir = join(self.common_dir, "data") 895 data_underlay_dir = join(self.common_dir, "underlay") 896 897 wikiconfig.set("data_dir", data_dir) 898 wikiconfig.set("data_underlay_dir", data_underlay_dir) 899 900 wikiconfig.set("site_name", self.site_name) 901 wikiconfig.set("page_front_page", self.front_page_name, count=1) 902 903 if self.theme_default is not None: 904 wikiconfig.set("theme_default", self.theme_default) 905 906 def edit_moin_script(self): 907 908 "Edit the moin script." 909 910 moin_script = self.get_moin_script() 911 status("Editing moin script at %s..." % moin_script) 912 913 s = readfile(moin_script) 914 s = s.replace("#import sys", "import sys\nsys.path.insert(0, %r)" % self.prefix_site_packages) 915 916 writefile(moin_script, s) 917 918 def edit_moin_web_script(self, site_file_configured=1): 919 920 "Edit and install CGI script." 921 922 # NOTE: CGI only so far. 923 # NOTE: Permissions should be checked. 924 925 moin_data = self.get_moin_data() 926 927 if self.moin_version.startswith("1.9"): 928 moin_cgi_script = "moin.fcgi" 929 else: 930 moin_cgi_script = "moin.cgi" 931 932 moin_cgi = join(moin_data, "server", moin_cgi_script) 933 moin_cgi_installed = join(self.web_app_dir, "moin.cgi") 934 935 status("Editing moin.cgi script from %s, writing to %s..." % (moin_cgi, moin_cgi_installed)) 936 937 s = readfile(moin_cgi) 938 s = moin_cgi_prefix.sub("sys.path.insert(0, %r)" % self.prefix_site_packages, s) 939 s = moin_cgi_wikiconfig.sub("sys.path.insert(0, %r)" % self.common_dir, s) 940 941 # Handle differences in script names when using limited hosting with 942 # URL rewriting. 943 944 if self.limited_hosting(): 945 if not site_file_configured: 946 note("Site file not configured: script name not changed.") 947 else: 948 url_path = self.final_url_path 949 950 if self.moin_version.startswith("1.9"): 951 s = moin_cgi_fix_script_name.sub(r"\1\2 %r" % url_path, s) 952 else: 953 s = moin_cgi_properties.sub(r"\1\2 %r" % {"script_name" : url_path}, s) 954 955 # NOTE: Use CGI for now. 956 957 if self.moin_version.startswith("1.9"): 958 s = moin_cgi_force_cgi.sub(r"\1", s) 959 960 writefile(moin_cgi_installed, s) 961 os.system("chmod a+rx '%s'" % moin_cgi_installed) 962 963 # Fix the cause of opaque errors in some Apache environments. 964 965 os.system("chmod go-w '%s'" % moin_cgi_installed) 966 967 def add_superuser(self): 968 969 "Add the superuser account." 970 971 if not self.superuser: 972 return 973 974 print "Creating superuser", self.superuser, "using..." 975 email = raw_input("E-mail address: ") 976 password = getpass("Password: ") 977 978 path = os.environ.get("PYTHONPATH", "") 979 980 if path: 981 os.environ["PYTHONPATH"] = path + ":" + self.common_dir 982 else: 983 os.environ["PYTHONPATH"] = self.common_dir 984 985 cmd = "%s --config-dir='%s' account create --name='%s' --email='%s' --password='%s'" % ( 986 self.get_moin_script(), self.common_dir, self.superuser, email, password) 987 os.system(cmd) 988 989 if path: 990 os.environ["PYTHONPATH"] = path 991 else: 992 del os.environ["PYTHONPATH"] 993 994 def make_site_files(self): 995 996 "Make the Apache site files." 997 998 # NOTE: Using local namespace for substitution. 999 1000 # Where the site definitions and applications directories are different, 1001 # use a normal site definition. 1002 1003 if not self.limited_hosting(): 1004 1005 site_def = join(self.web_site_dir, self.site_identifier) 1006 1007 s = apache_site % self.__dict__ 1008 s += apache_site_extra % self.__dict__ 1009 1010 status("Writing Apache site definitions to %s..." % site_def) 1011 writefile(site_def, s) 1012 1013 note("Copy the site definitions to the appropriate sites directory if appropriate.\n" 1014 "Then, make sure that the site is enabled by running the appropriate tools (such as a2ensite).") 1015 1016 return 1 1017 1018 # Otherwise, use an .htaccess file. 1019 1020 else: 1021 site_def = join(self.web_static_dir or self.web_app_dir, ".htaccess") 1022 1023 s = apache_htaccess_combined_mod_rewrite % self.__dict__ 1024 1025 status("Writing .htaccess file to %s..." % site_def) 1026 try: 1027 writefile(site_def, s) 1028 except IOError: 1029 note("The .htaccess file could not be written. This will also affect the script name setting.") 1030 return 0 1031 else: 1032 return 1 1033 1034 def make_post_install_script(self): 1035 1036 "Write a post-install script with additional actions." 1037 1038 # Work out whether setfacl works. 1039 1040 fd, temp_filename = tempfile.mkstemp(dir=self.common_dir) 1041 os.close(fd) 1042 1043 have_setfacl = os.system("setfacl -m user:%(web_user)s:r %(file)s > /dev/null 2>&1" % { 1044 "web_user" : self.web_user, "file" : temp_filename}) == 0 1045 1046 remove(temp_filename) 1047 1048 # Create the scripts. 1049 1050 this_user = os.environ["USER"] 1051 postinst_scripts = "moinsetup-post-chown.sh", "moinsetup-post-setfacl.sh" 1052 1053 vars = {} 1054 vars.update(Installation.__dict__) 1055 vars.update(self.__dict__) 1056 vars.update(locals()) 1057 1058 for postinst_script, start, extra, logs in [ 1059 (postinst_scripts[0], postsetup_chown_chmod, postsetup_chown_extra, postsetup_chown_logs), 1060 (postinst_scripts[1], postsetup_setfacl, postsetup_setfacl_extra, postsetup_setfacl_logs) 1061 ]: 1062 1063 s = start % vars 1064 s += extra % vars 1065 s += logs % vars 1066 1067 writefile(postinst_script, s) 1068 chmod(postinst_script, 0755) 1069 1070 if have_setfacl: 1071 note("Run %s to set file ownership and permissions.\n" 1072 "If this somehow fails..." % postinst_scripts[1]) 1073 1074 note("Run %s as root to set file ownership and permissions." % postinst_scripts[0]) 1075 1076 # Accessory methods. 1077 1078 def reconfigure_moin(self, name=None, value=None, raw=0): 1079 1080 """ 1081 Edit the installed Wiki configuration file, setting a parameter with any 1082 given 'name' to the given 'value', treating the value as a raw 1083 expression (not a string) if 'raw' is set to a true value. 1084 1085 If 'name' and the remaining parameters are omitted, the default 1086 configuration activity is performed. 1087 1088 If the 'site_config' setting is defined, the specific site configuration 1089 will be changed. 1090 """ 1091 1092 wikiconfig_py = self.get_site_config() 1093 1094 status("Editing configuration from %s..." % wikiconfig_py) 1095 1096 wikiconfig = Configuration(wikiconfig_py) 1097 1098 try: 1099 # Perform default configuration. 1100 1101 if name is None and value is None: 1102 self._configure_moin(wikiconfig) 1103 else: 1104 wikiconfig.set(name, value, raw=raw) 1105 1106 finally: 1107 wikiconfig.close() 1108 1109 def set_auth_method(self, method_name): 1110 1111 """ 1112 Edit the installed Wiki configuration file, configuring the 1113 authentication method having the given 'method_name'. 1114 1115 If the 'farm_config' setting is defined, the Wiki farm configuration 1116 will be changed. 1117 """ 1118 1119 wikiconfig_py = self.get_global_config() 1120 1121 status("Editing configuration from %s..." % wikiconfig_py) 1122 1123 wikiconfig = Configuration(wikiconfig_py) 1124 1125 try: 1126 # OpenID authentication. 1127 1128 if method_name.lower() == "openid": 1129 wikiconfig.set_import("MoinMoin.auth.openidrp", ["OpenIDAuth"]) 1130 1131 if self.moin_version.startswith("1.9"): 1132 if wikiconfig.get("cookie_lifetime"): 1133 wikiconfig.replace("cookie_lifetime", "(12, 12)", raw=1) 1134 else: 1135 wikiconfig.set("cookie_lifetime", "(12, 12)", raw=1) 1136 else: 1137 if wikiconfig.get("anonymous_session_lifetime"): 1138 wikiconfig.replace("anonymous_session_lifetime", "1000", raw=1) 1139 else: 1140 wikiconfig.set("anonymous_session_lifetime", "1000", raw=1) 1141 1142 auth_object = "OpenIDAuth()" 1143 1144 # Default Moin authentication. 1145 1146 elif method_name.lower() in ("moin", "default"): 1147 wikiconfig.set_import("MoinMoin.auth", ["MoinAuth"]) 1148 auth_object = "MoinAuth()" 1149 1150 # REMOTE_USER authentication. 1151 1152 elif method_name.lower() in ("given", "remote-user"): 1153 wikiconfig.set_import("MoinMoin.auth.http", ["HTTPAuth"]) 1154 auth_object = "HTTPAuth(autocreate=True)" 1155 1156 # Other methods are not currently supported. 1157 1158 else: 1159 return 1160 1161 # Edit the authentication setting. 1162 1163 auth = wikiconfig.get("auth") 1164 if auth: 1165 wikiconfig.replace("auth", "%s + [%s]" % (auth, auth_object), raw=1) 1166 else: 1167 wikiconfig.set("auth", "[%s]" % auth_object, raw=1) 1168 1169 finally: 1170 wikiconfig.close() 1171 1172 def migrate_instance(self, test=0, change_site=0): 1173 1174 """ 1175 Migrate the Wiki to the currently supported layout. If 'test' is 1176 specified and set to a non-empty or true value, only print whether the 1177 migration can be performed. 1178 1179 If 'change_site' is specified and set to a non-empty or true value, the 1180 site definitions will be updated; this will overwrite any changes made 1181 to the site definitions after they were last produced by moinsetup, and 1182 care must be taken to ensure that things like access controls are 1183 re-added to the definitions after this action is performed. 1184 """ 1185 1186 conf_dir = join(self.common_dir, "conf") 1187 if exists(conf_dir): 1188 for filename in listdir(conf_dir): 1189 pathname = join(conf_dir, filename) 1190 target = join(self.common_dir, filename) 1191 if not exists(target): 1192 print "Move", filename, "from conf directory." 1193 if not test: 1194 rename(pathname, target) 1195 else: 1196 print "No conf directory." 1197 1198 wikidata = join(self.common_dir, "wikidata") 1199 if exists(wikidata): 1200 htdocs = join(wikidata, "share", "moin", "htdocs") 1201 if exists(htdocs): 1202 target = join(self.common_dir, "htdocs") 1203 if not exists(target): 1204 print "Move htdocs from wikidata directory." 1205 if not test: 1206 rename(htdocs, target) 1207 else: 1208 print "No wikidata directory." 1209 1210 # Remove links and directories. 1211 1212 for name in ("conf", "wikidata"): 1213 d = join(self.common_dir, name) 1214 if islink(d): 1215 print "Remove %s symbolic link." % name 1216 if not test: 1217 remove(d) 1218 1219 if isdir(conf_dir): 1220 print "Remove conf directory." 1221 if not test: 1222 rmdir(conf_dir) 1223 1224 # Add any missing htdocs directory. 1225 1226 if not exists(self.htdocs_dir): 1227 print "Copy htdocs into the instance." 1228 if not test: 1229 self.install_static_data() 1230 1231 # Now attempt to reconfigure the Wiki. 1232 1233 print "Reconfigure the Wiki, the Web script%s." % (change_site and " and the site files" or "") 1234 if not test: 1235 self.configure_moin() 1236 self.edit_moin_web_script() 1237 if change_site: 1238 self.make_site_files() 1239 1240 def install_theme(self, theme_dir, theme_name=None): 1241 1242 """ 1243 Install Wiki theme provided in the given 'theme_dir' having the given 1244 optional 'theme_name' (if different from the 'theme_dir' name). 1245 """ 1246 1247 theme_dir = normpath(theme_dir) 1248 theme_name = theme_name or split(theme_dir)[-1] 1249 theme_module = join(theme_dir, theme_name + extsep + "py") 1250 1251 plugin_theme_dir = self.get_plugin_directory("theme") 1252 1253 # Copy the theme module. 1254 1255 status("Copying theme module to %s..." % plugin_theme_dir) 1256 1257 shutil.copy(theme_module, plugin_theme_dir) 1258 1259 # Copy the resources. 1260 1261 resources_dir = join(self.htdocs_dir, theme_name) 1262 1263 if not exists(resources_dir): 1264 mkdir(resources_dir) 1265 1266 status("Copying theme resources to %s..." % resources_dir) 1267 1268 for d in ("css", "img"): 1269 target_dir = join(resources_dir, d) 1270 if exists(target_dir): 1271 status("Replacing %s..." % target_dir) 1272 shutil.rmtree(target_dir) 1273 shutil.copytree(join(theme_dir, d), target_dir) 1274 1275 # Copy additional resources from other themes. 1276 1277 resources_source_dir = join(self.htdocs_dir, self.theme_master) 1278 target_dir = join(resources_dir, "css") 1279 1280 status("Copying resources from %s..." % resources_source_dir) 1281 1282 for css_file in self.extra_theme_css_files: 1283 css_file_path = join(resources_source_dir, "css", css_file) 1284 if exists(css_file_path): 1285 shutil.copy(css_file_path, target_dir) 1286 1287 note("Don't forget to add theme resources for extensions for this theme.\n" 1288 "Don't forget to edit this theme's stylesheets for extensions.") 1289 1290 def install_extension_package(self, extension_dir): 1291 1292 "Install any libraries from 'extension_dir' using a setup script." 1293 1294 this_dir = os.getcwd() 1295 chdir(extension_dir) 1296 1297 try: 1298 options = "install --install-lib=%s" % self.prefix_site_packages 1299 os.system("python setup.py %s" % options) 1300 finally: 1301 chdir(this_dir) 1302 1303 def install_plugins(self, plugins_dir, plugin_type): 1304 1305 """ 1306 Install Wiki actions provided in the given 'plugins_dir' of the 1307 specified 'plugin_type'. 1308 """ 1309 1310 plugin_target_dir = self.get_plugin_directory(plugin_type) 1311 1312 # Copy the modules. 1313 1314 status("Copying %s modules to %s..." % (plugin_type, plugin_target_dir)) 1315 1316 for module in glob(join(plugins_dir, "*%spy" % extsep)): 1317 shutil.copy(module, plugin_target_dir) 1318 1319 def install_actions(self, actions_dir): 1320 1321 "Install Wiki actions provided in the given 'actions_dir'." 1322 1323 self.install_plugins(actions_dir, "action") 1324 1325 def install_macros(self, macros_dir): 1326 1327 "Install Wiki macros provided in the given 'macros_dir'." 1328 1329 self.install_plugins(macros_dir, "macro") 1330 1331 def install_parsers(self, parsers_dir): 1332 1333 "Install Wiki parsers provided in the given 'parsers_dir'." 1334 1335 self.install_plugins(parsers_dir, "parser") 1336 1337 def install_event_handlers(self, events_dir): 1338 1339 "Install Wiki event handlers provided in the given 'events_dir'." 1340 1341 self.install_plugins(events_dir, "events") 1342 1343 def install_theme_resources(self, theme_resources_dir, theme_name=None): 1344 1345 """ 1346 Install theme resources provided in the given 'theme_resources_dir'. If 1347 a specific 'theme_name' is given, only that theme will be given the 1348 specified resources. 1349 """ 1350 1351 for theme_name, theme_dir in self.get_theme_directories(theme_name): 1352 1353 # Copy the resources. 1354 1355 copied = 0 1356 1357 for d in ("css", "img"): 1358 source_dir = join(theme_resources_dir, d) 1359 target_dir = join(theme_dir, d) 1360 1361 if not exists(target_dir): 1362 continue 1363 1364 for resource in glob(join(source_dir, "*%s*" % extsep)): 1365 shutil.copy(resource, target_dir) 1366 copied = 1 1367 1368 if copied: 1369 status("Copied theme resources into %s..." % theme_dir) 1370 1371 note("Don't forget to edit theme stylesheets for any extensions.") 1372 1373 def edit_theme_stylesheet(self, theme_stylesheet, imported_stylesheet, action="ensure", theme_name=None): 1374 1375 """ 1376 Edit the given 'theme_stylesheet', ensuring (or removing) a reference to 1377 the 'imported_stylesheet' according to the given 'action' (optional, 1378 defaulting to "ensure"). If a specific 'theme_name' is given, only that 1379 theme will be affected. 1380 """ 1381 1382 if action == "ensure": 1383 ensure = 1 1384 elif action == "remove": 1385 ensure = 0 1386 else: 1387 error("Action %s not valid: it must be given as either 'ensure' or 'remove'." % action) 1388 return 1389 1390 for theme_name, theme_dir in self.get_theme_directories(theme_name): 1391 1392 # Locate the resources. 1393 1394 css_dir = join(theme_dir, "css") 1395 1396 if not exists(css_dir): 1397 continue 1398 1399 theme_stylesheet_filename = join(css_dir, theme_stylesheet) 1400 imported_stylesheet_filename = join(css_dir, imported_stylesheet) 1401 1402 if not exists(theme_stylesheet_filename): 1403 error("Stylesheet %s not defined in theme %s." % (theme_stylesheet, theme_name)) 1404 continue 1405 1406 if not exists(imported_stylesheet_filename): 1407 error("Stylesheet %s not defined in theme %s." % (imported_stylesheet, theme_name)) 1408 continue 1409 1410 # Edit the resources. 1411 1412 s = readfile(theme_stylesheet_filename) 1413 after_point = 0 1414 1415 for stylesheet_import in css_import_stylesheet.finditer(s): 1416 before, filename, after = stylesheet_import.groups() 1417 before_point, after_point = stylesheet_import.span() 1418 1419 # Test the import for a reference to the requested imported 1420 # stylesheet. 1421 1422 if filename == imported_stylesheet: 1423 if ensure: 1424 break 1425 else: 1426 if s[after_point:after_point+1] == "\n": 1427 after_point += 1 1428 s = "%s%s" % (s[:before_point], s[after_point:]) 1429 1430 status("Removing %s from %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name)) 1431 writefile(theme_stylesheet_filename, s) 1432 break 1433 1434 # Where no import references the imported stylesheet, insert a 1435 # reference into the theme stylesheet. 1436 1437 else: 1438 if ensure: 1439 1440 # Assume that the stylesheet can follow other imports. 1441 1442 if s[after_point:after_point+1] == "\n": 1443 after_point += 1 1444 s = "%s%s\n%s" % (s[:after_point], '@import "%s";' % imported_stylesheet, s[after_point:]) 1445 1446 status("Adding %s to %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name)) 1447 writefile(theme_stylesheet_filename, s) 1448 1449 def make_page_package(self, page_directory, package_filename): 1450 1451 """ 1452 Make a package containing the pages in 'page_directory', using the 1453 filenames as the page names, and writing the package to a file with the 1454 given 'package_filename'. 1455 """ 1456 1457 package = ZipFile(package_filename, "w") 1458 1459 try: 1460 script = ["MoinMoinPackage|1"] 1461 1462 for filename in listdir(page_directory): 1463 pathname = join(page_directory, filename) 1464 1465 # Add files as pages having the filename as page name. 1466 1467 if os.path.isfile(pathname): 1468 package.write(pathname, filename) 1469 script.append("AddRevision|%s|%s" % (filename, filename)) 1470 1471 # Add directories ending with "-attachments" as collections of 1472 # attachments for a particular page. 1473 1474 elif os.path.isdir(pathname) and filename.endswith("-attachments"): 1475 parent = filename[:-len("-attachments")] 1476 1477 # Add each file as an attachment. 1478 1479 for attachment in listdir(pathname): 1480 zipname = "%s_%s" % (filename, attachment) 1481 package.write(join(pathname, attachment), zipname) 1482 script.append("AddAttachment|%s|%s|%s||" % (zipname, attachment, parent)) 1483 1484 package.writestr("MOIN_PACKAGE", "\n".join(script)) 1485 1486 finally: 1487 package.close() 1488 1489 def install_page_package(self, package_filename): 1490 1491 """ 1492 Install a package from the file with the given 'package_filename'. 1493 """ 1494 1495 path = os.environ.get("PYTHONPATH", "") 1496 1497 conf_dir = self.get_wikiconfig_directory() 1498 1499 if path: 1500 os.environ["PYTHONPATH"] = path + ":" + self.prefix_site_packages + ":" + conf_dir 1501 else: 1502 os.environ["PYTHONPATH"] = self.prefix_site_packages + ":" + conf_dir 1503 1504 installer = join(self.prefix_site_packages, "MoinMoin", "packages.py") 1505 cmd = "python %s i %s" % (installer, package_filename) 1506 os.system(cmd) 1507 1508 if path: 1509 os.environ["PYTHONPATH"] = path 1510 else: 1511 del os.environ["PYTHONPATH"] 1512 1513 def show_methods(): 1514 print "Methods:" 1515 print 1516 for method_name in Installation.method_names: 1517 doc = getattr(Installation, method_name).__doc__.strip() 1518 print "%-30s%-s" % (method_name, format(doc, 30)) 1519 print 1520 1521 # Command line option syntax. 1522 1523 syntax_description = "[ -f <config-filename> ] ( -m <method> | --method=METHOD ) [ <method-argument> ... ]" 1524 1525 # Main program. 1526 1527 if __name__ == "__main__": 1528 from ConfigParser import ConfigParser 1529 import sys, cmdsyntax 1530 1531 # Check the command syntax. 1532 1533 syntax = cmdsyntax.Syntax(syntax_description) 1534 try: 1535 matches = syntax.get_args(sys.argv[1:]) 1536 args = matches[0] 1537 except IndexError: 1538 print "Syntax:" 1539 print sys.argv[0], syntax_description 1540 print 1541 show_methods() 1542 sys.exit(1) 1543 1544 # Obtain configuration details. 1545 1546 try: 1547 config_filename = args.get("config-filename", "moinsetup.cfg") 1548 1549 if not exists(config_filename): 1550 print "Configuration", config_filename, "not found." 1551 sys.exit(1) 1552 1553 config = ConfigParser() 1554 config.read(config_filename) 1555 1556 # Obtain as many arguments as needed from the configuration. 1557 1558 config_arguments = dict(config.items("installation") + config.items("site")) 1559 method_arguments = args.get("method-argument", []) 1560 1561 # Attempt to initialise the configuration. 1562 1563 installation = Installation(**config_arguments) 1564 1565 except TypeError, exc: 1566 print "Error:" 1567 print 1568 print exc.args[0] 1569 print 1570 print "Configuration settings:" 1571 print 1572 print Installation.__init__.__doc__ 1573 print 1574 sys.exit(1) 1575 1576 # Obtain the method. 1577 1578 try: 1579 method = getattr(installation, args["method"]) 1580 except AttributeError: 1581 show_methods() 1582 sys.exit(1) 1583 1584 try: 1585 method(*method_arguments) 1586 except TypeError, exc: 1587 print "Error:" 1588 print 1589 print exc.args[0] 1590 print 1591 print "Method documentation:" 1592 print 1593 print method.__doc__ 1594 print 1595 sys.exit(1) 1596 1597 # vim: tabstop=4 expandtab shiftwidth=4