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