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