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