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 if self.limited_hosting(): 551 print "Limited hosting configuration detected." 552 print "Published resources will be configured using .htaccess." 553 else: 554 print "Privileged hosting configuration detected..." 555 print "Published resources will be configured using site definition files." 556 print 557 for section in ("source", "instance", "site"): 558 print section.title() 559 print "-" * len(section) 560 print 561 for setting in getattr(self, "%s_config_names" % section): 562 print "%-24s%s" % (setting, getattr(self, setting)) 563 print 564 565 print "Configuration locations" 566 print "-----------------------" 567 print 568 print "%-24s%s" % ("site-level", self.get_site_config()) 569 print "%-24s%s" % ("global", self.get_global_config()) 570 print 571 print "Theme directories" 572 print "-----------------" 573 print 574 575 try: 576 for theme in self.get_theme_directories(): 577 print "%-24s%s" % theme 578 except OSError, exc: 579 print "Not shown:", str(exc) 580 581 def _get_abspath(self, d): 582 return d and abspath(d) or None 583 584 def _tidy_url_path(self, url_path): 585 if url_path != "/" and url_path.endswith("/"): 586 return url_path[:-1] 587 else: 588 return url_path 589 590 def get_moin_version(self): 591 592 "Return the MoinMoin version." 593 594 this_dir = os.getcwd() 595 596 for dir in [self.moin_distribution, self.prefix_site_packages]: 597 if dir: 598 try: 599 chdir(dir) 600 version = self.get_moin_version_from_package_info() or \ 601 self.get_moin_version_from_import() 602 if version: 603 return version 604 605 finally: 606 chdir(this_dir) 607 else: 608 return self.get_moin_version_from_import() 609 610 def get_moin_version_from_package_info(self): 611 612 "Inspect the MoinMoin package information, returning the version." 613 614 try: 615 f = open("PKG-INFO") 616 try: 617 for line in f.xreadlines(): 618 columns = line.split() 619 if columns[0] == "Version:": 620 return columns[1] 621 finally: 622 f.close() 623 624 except IOError: 625 pass 626 627 return None 628 629 def get_moin_version_from_import(self): 630 631 "Return the MoinMoin version from an import of the package itself." 632 633 # Where no distribution information can be read, try and import an 634 # installed version module. 635 636 f = os.popen("%s -c 'from MoinMoin.version import release; print release'" % sys.executable) 637 try: 638 return f.read().strip() 639 finally: 640 f.close() 641 642 def get_moin_data(self): 643 644 "Return the exact location of MoinMoin data." 645 646 return self.moin_distribution and join(self.moin_distribution, "wiki") or \ 647 self.prefix and join(self.prefix, "share", "moin") or None 648 649 def get_moin_script(self): 650 651 "Return the location of the general-purpose moin script." 652 653 return join(self.prefix, "bin", "moin") 654 655 def get_wikiconfig_directory(self): 656 657 "Return the location of the Wiki configuration." 658 659 if self.site_config: 660 return split(self.site_config)[0] 661 else: 662 return self.common_dir 663 664 def get_site_config(self): 665 666 "Return the file providing the site-level configuration." 667 668 if self.site_config: 669 return self.site_config 670 else: 671 return join(self.common_dir, "wikiconfig.py") 672 673 def get_global_config(self): 674 675 "Return the file providing the global MoinMoin configuration." 676 677 if self.farm_config: 678 return self.farm_config 679 else: 680 return join(self.common_dir, "wikiconfig.py") 681 682 def get_static_identifier(self): 683 684 "Return the static URL/directory identifier for the Wiki." 685 686 return "moin_static%s" % self.moin_version.replace(".", "") 687 688 def get_plugin_directory(self, plugin_type): 689 690 "Return the directory for plugins of the given 'plugin_type'." 691 692 data_dir = join(self.common_dir, "data") 693 return join(data_dir, "plugin", plugin_type) 694 695 def limited_hosting(self): 696 697 "Return whether limited Web hosting is being used." 698 699 return not self.web_site_dir 700 701 def ensure_directories(self): 702 703 "Make sure that all the directories are available." 704 705 for d in (self.common_dir, self.web_app_dir, self.web_static_dir, self.web_site_dir): 706 if d is not None and not exists(d): 707 makedirs(d) 708 709 def get_theme_directories(self, theme_name=None): 710 711 """ 712 Return tuples of the form (theme name, theme directory) for all themes, 713 or for a single theme if the optional 'theme_name' is specified. 714 """ 715 716 filenames = theme_name and [theme_name] or listdir(self.htdocs_dir) 717 directories = [] 718 719 for filename in filenames: 720 theme_dir = join(self.htdocs_dir, filename) 721 722 if not exists(theme_dir) or not isdir(theme_dir): 723 continue 724 725 directories.append((filename, theme_dir)) 726 727 return directories 728 729 # Main methods. 730 731 def setup(self): 732 733 "Set up the installation." 734 735 self.ensure_directories() 736 self.install_moin() 737 self.edit_moin_script() 738 self._setup_wiki() 739 740 def setup_wiki(self): 741 742 "Set up a Wiki without installing MoinMoin." 743 744 self.ensure_directories() 745 self._setup_wiki() 746 747 def _setup_wiki(self): 748 749 "Set up a Wiki without installing MoinMoin." 750 751 self.install_data() 752 self.install_static_data() 753 self.configure_moin() 754 self.add_superuser() 755 self.edit_moin_web_script(self.make_site_files()) 756 self.make_post_install_script() 757 758 if self.moin_version.startswith("1.9"): 759 note("You may need to visit the LanguageSetup page in the Wiki to create the standard set of pages.") 760 761 def install_moin(self): 762 763 "Enter the distribution directory and run the setup script." 764 765 # NOTE: Possibly check for an existing installation and skip repeated 766 # NOTE: installation attempts. 767 768 if not self.moin_distribution: 769 raise SetupException, "Cannot install MoinMoin without a 'moin_distribution' setting being defined." 770 771 this_dir = os.getcwd() 772 chdir(self.moin_distribution) 773 774 log_filename = "install-%s.log" % split(self.common_dir)[-1] 775 776 status("Installing MoinMoin in %s..." % self.prefix) 777 778 install_cmd = "install" 779 options = "--prefix='%s' --record='%s'" % (self.prefix, log_filename) 780 781 os.system("python setup.py --quiet %s %s --force" % (install_cmd, options)) 782 783 chdir(this_dir) 784 785 def install_data(self): 786 787 "Install Wiki data into an instance." 788 789 moin_data = self.get_moin_data() 790 791 if not moin_data: 792 raise SetupException, \ 793 "Cannot install MoinMoin data without either a 'moin_distribution' or a 'prefix' setting being defined." 794 795 # The default wikiconfig assumes data and underlay in the same directory. 796 797 status("Installing data and underlay in %s..." % self.common_dir) 798 799 for d in ("data", "underlay"): 800 source = join(moin_data, d) 801 source_tar = source + extsep + "tar" 802 803 if exists(source): 804 shutil.copytree(source, join(self.common_dir, d)) 805 elif exists(source_tar): 806 807 note("Copying archive %s instead of directory %s. Running...\n" 808 "make pagepacks\n" 809 "in the distribution directory should rectify this." % (source_tar, source)) 810 811 shutil.copy(source_tar, self.common_dir) 812 os.system("tar xf %s -C %s" % (source_tar, self.common_dir)) 813 else: 814 status("Could not copy %s into installed Wiki." % d) 815 816 def install_static_data(self): 817 818 "Install static Web data if appropriate." 819 820 if not exists(self.htdocs_dir): 821 mkdir(self.htdocs_dir) 822 823 for item in listdir(self.htdocs_dir_source): 824 path = join(self.htdocs_dir_source, item) 825 if isdir(path): 826 shutil.copytree(path, join(self.htdocs_dir, item)) 827 else: 828 shutil.copy(path, join(self.htdocs_dir, item)) 829 830 def configure_moin(self, reset=0): 831 832 """ 833 Edit the Wiki configuration file. If the optional 'reset' parameter is 834 specified as a true value, a default configuration will be copied from 835 the distribution if appropriate. 836 """ 837 838 moin_data = self.get_moin_data() 839 840 if not moin_data: 841 raise SetupException, \ 842 "Cannot configure MoinMoin without either a 'moin_distribution' or a 'prefix' setting being defined." 843 844 # NOTE: MoinMoin usually uses an apparently common URL space associated 845 # NOTE: with the version, but more specific locations are probably 846 # NOTE: acceptable if less efficient. 847 848 url_prefix_static = "%r" % self.static_url_resources_path 849 850 # Use a farm configuration file. 851 852 if self.farm_config: 853 wikiconfig_py = self.farm_config 854 855 # Or copy the Wiki configuration file from the distribution. 856 857 else: 858 wikiconfig_py = join(self.common_dir, "wikiconfig.py") 859 860 if not exists(wikiconfig_py) or reset: 861 shutil.copyfile(join(moin_data, "config", "wikiconfig.py"), wikiconfig_py) 862 863 status("Editing configuration from %s..." % wikiconfig_py) 864 865 # Edit the Wiki configuration file. 866 867 wikiconfig = Configuration(wikiconfig_py) 868 869 try: 870 wikiconfig.set("url_prefix_static", url_prefix_static, raw=1) 871 if self.superuser: 872 wikiconfig.set("superuser", [self.superuser]) 873 wikiconfig.set("acl_rights_before", u"%s:read,write,delete,revert,admin" % self.superuser) 874 else: 875 note("Superuser not defined. The ACL rules should be fixed in the configuration.") 876 877 # Edit any created Wiki configuration. 878 879 if not self.site_config: 880 self._configure_moin(wikiconfig) 881 882 finally: 883 wikiconfig.close() 884 885 # Edit any separate site configuration file. 886 887 if self.site_config: 888 status("Editing configuration from %s..." % self.site_config) 889 890 wikiconfig = Configuration(self.site_config) 891 892 try: 893 self._configure_moin(wikiconfig) 894 finally: 895 wikiconfig.close() 896 897 def _configure_moin(self, wikiconfig): 898 899 """ 900 Configure Moin, accessing the configuration file using 'wikiconfig'. 901 """ 902 903 # Specific site configurations also appear to need 'data_dir', even in 904 # 1.9. 905 906 if not self.moin_version.startswith("1.9") or self.site_config: 907 data_dir = join(self.common_dir, "data") 908 data_underlay_dir = join(self.common_dir, "underlay") 909 910 wikiconfig.set("data_dir", data_dir) 911 wikiconfig.set("data_underlay_dir", data_underlay_dir) 912 913 wikiconfig.set("site_name", self.site_name) 914 wikiconfig.set("page_front_page", self.front_page_name, count=1) 915 916 if self.theme_default is not None: 917 wikiconfig.set("theme_default", self.theme_default) 918 919 def edit_moin_script(self): 920 921 "Edit the moin script." 922 923 moin_script = self.get_moin_script() 924 status("Editing moin script at %s..." % moin_script) 925 926 s = readfile(moin_script) 927 s = s.replace("#import sys", "import sys\nsys.path.insert(0, %r)" % self.prefix_site_packages) 928 929 writefile(moin_script, s) 930 931 def edit_moin_web_script(self, site_file_configured=1): 932 933 "Edit and install CGI script." 934 935 # NOTE: CGI only so far. 936 # NOTE: Permissions should be checked. 937 938 moin_data = self.get_moin_data() 939 940 if self.moin_version.startswith("1.9"): 941 moin_cgi_script = "moin.fcgi" 942 else: 943 moin_cgi_script = "moin.cgi" 944 945 moin_cgi = join(moin_data, "server", moin_cgi_script) 946 moin_cgi_installed = join(self.web_app_dir, "moin.cgi") 947 948 status("Editing moin.cgi script from %s, writing to %s..." % (moin_cgi, moin_cgi_installed)) 949 950 s = readfile(moin_cgi) 951 s = moin_cgi_prefix.sub("sys.path.insert(0, %r)" % self.prefix_site_packages, s) 952 s = moin_cgi_wikiconfig.sub("sys.path.insert(0, %r)" % self.common_dir, s) 953 954 # Handle differences in script names when using limited hosting with 955 # URL rewriting. 956 957 if self.limited_hosting(): 958 if not site_file_configured: 959 note("Site file not configured: script name not changed.") 960 else: 961 url_path = self.final_url_path 962 963 if self.moin_version.startswith("1.9"): 964 s = moin_cgi_fix_script_name.sub(r"\1\2 %r" % url_path, s) 965 else: 966 s = moin_cgi_properties.sub(r"\1\2 %r" % {"script_name" : url_path}, s) 967 968 # NOTE: Use CGI for now. 969 970 if self.moin_version.startswith("1.9"): 971 s = moin_cgi_force_cgi.sub(r"\1", s) 972 973 writefile(moin_cgi_installed, s) 974 os.system("chmod a+rx '%s'" % moin_cgi_installed) 975 976 # Fix the cause of opaque errors in some Apache environments. 977 978 os.system("chmod go-w '%s'" % moin_cgi_installed) 979 980 def add_superuser(self): 981 982 "Add the superuser account." 983 984 if not self.superuser: 985 return 986 987 print "Creating superuser", self.superuser, "using..." 988 email = raw_input("E-mail address: ") 989 password = getpass("Password: ") 990 991 path = os.environ.get("PYTHONPATH", "") 992 993 if path: 994 os.environ["PYTHONPATH"] = path + ":" + self.common_dir 995 else: 996 os.environ["PYTHONPATH"] = self.common_dir 997 998 cmd = "%s --config-dir='%s' account create --name='%s' --email='%s' --password='%s'" % ( 999 self.get_moin_script(), self.common_dir, self.superuser, email, password) 1000 os.system(cmd) 1001 1002 if path: 1003 os.environ["PYTHONPATH"] = path 1004 else: 1005 del os.environ["PYTHONPATH"] 1006 1007 def make_site_files(self): 1008 1009 "Make the Apache site files." 1010 1011 # NOTE: Using local namespace for substitution. 1012 1013 # Where the site definitions and applications directories are different, 1014 # use a normal site definition. 1015 1016 if not self.limited_hosting(): 1017 1018 site_def = join(self.web_site_dir, self.site_identifier) 1019 1020 s = apache_site % self.__dict__ 1021 s += apache_site_extra % self.__dict__ 1022 1023 status("Writing Apache site definitions to %s..." % site_def) 1024 writefile(site_def, s) 1025 1026 note("Copy the site definitions to the appropriate sites directory if appropriate.\n" 1027 "Then, make sure that the site is enabled by running the appropriate tools (such as a2ensite).") 1028 1029 return 1 1030 1031 # Otherwise, use an .htaccess file. 1032 1033 else: 1034 site_def = join(self.web_static_dir or self.web_app_dir, ".htaccess") 1035 1036 s = apache_htaccess_combined_mod_rewrite % self.__dict__ 1037 1038 status("Writing .htaccess file to %s..." % site_def) 1039 try: 1040 writefile(site_def, s) 1041 except IOError: 1042 note("The .htaccess file could not be written. This will also affect the script name setting.") 1043 return 0 1044 else: 1045 return 1 1046 1047 def make_post_install_script(self): 1048 1049 "Write a post-install script with additional actions." 1050 1051 # Work out whether setfacl works. 1052 1053 fd, temp_filename = tempfile.mkstemp(dir=self.common_dir) 1054 os.close(fd) 1055 1056 have_setfacl = os.system("setfacl -m user:%(web_user)s:r %(file)s > /dev/null 2>&1" % { 1057 "web_user" : self.web_user, "file" : temp_filename}) == 0 1058 1059 remove(temp_filename) 1060 1061 # Create the scripts. 1062 1063 this_user = os.environ["USER"] 1064 postinst_scripts = "moinsetup-post-chown.sh", "moinsetup-post-setfacl.sh" 1065 1066 vars = {} 1067 vars.update(Installation.__dict__) 1068 vars.update(self.__dict__) 1069 vars.update(locals()) 1070 1071 for postinst_script, start, extra, logs in [ 1072 (postinst_scripts[0], postsetup_chown_chmod, postsetup_chown_extra, postsetup_chown_logs), 1073 (postinst_scripts[1], postsetup_setfacl, postsetup_setfacl_extra, postsetup_setfacl_logs) 1074 ]: 1075 1076 s = start % vars 1077 s += extra % vars 1078 s += logs % vars 1079 1080 writefile(postinst_script, s) 1081 chmod(postinst_script, 0755) 1082 1083 if have_setfacl: 1084 note("Run %s to set file ownership and permissions.\n" 1085 "If this somehow fails..." % postinst_scripts[1]) 1086 1087 note("Run %s as root to set file ownership and permissions." % postinst_scripts[0]) 1088 1089 # Accessory methods. 1090 1091 def reconfigure_moin(self, name=None, value=None, raw=0): 1092 1093 """ 1094 Edit the installed Wiki configuration file, setting a parameter with any 1095 given 'name' to the given 'value', treating the value as a raw 1096 expression (not a string) if 'raw' is set to a true value. 1097 1098 If 'name' and the remaining parameters are omitted, the default 1099 configuration activity is performed. 1100 1101 If the 'site_config' setting is defined, the specific site configuration 1102 will be changed. 1103 """ 1104 1105 wikiconfig_py = self.get_site_config() 1106 1107 status("Editing configuration from %s..." % wikiconfig_py) 1108 1109 wikiconfig = Configuration(wikiconfig_py) 1110 1111 try: 1112 # Perform default configuration. 1113 1114 if name is None and value is None: 1115 self._configure_moin(wikiconfig) 1116 else: 1117 wikiconfig.set(name, value, raw=raw) 1118 1119 finally: 1120 wikiconfig.close() 1121 1122 def set_auth_method(self, method_name): 1123 1124 """ 1125 Edit the installed Wiki configuration file, configuring the 1126 authentication method having the given 'method_name'. 1127 1128 Currently recognised authentication methods are: 1129 1130 * openid (uses OpenIDAuth to access OpenID providers) 1131 * moin, default (use MoinAuth to provide a login form) 1132 * given, remote-user (use HTTPAuth to obtain Web server credentials) 1133 1134 If the 'farm_config' setting is defined, the Wiki farm configuration 1135 will be changed. 1136 """ 1137 1138 wikiconfig_py = self.get_global_config() 1139 1140 status("Editing configuration from %s..." % wikiconfig_py) 1141 1142 wikiconfig = Configuration(wikiconfig_py) 1143 1144 try: 1145 # OpenID authentication. 1146 1147 if method_name.lower() == "openid": 1148 wikiconfig.set_import("MoinMoin.auth.openidrp", ["OpenIDAuth"]) 1149 1150 if self.moin_version.startswith("1.9"): 1151 if wikiconfig.get("cookie_lifetime"): 1152 wikiconfig.replace("cookie_lifetime", "(12, 12)", raw=1) 1153 else: 1154 wikiconfig.set("cookie_lifetime", "(12, 12)", raw=1) 1155 else: 1156 if wikiconfig.get("anonymous_session_lifetime"): 1157 wikiconfig.replace("anonymous_session_lifetime", "1000", raw=1) 1158 else: 1159 wikiconfig.set("anonymous_session_lifetime", "1000", raw=1) 1160 1161 auth_object = "OpenIDAuth()" 1162 1163 # Default Moin authentication. 1164 1165 elif method_name.lower() in ("moin", "default"): 1166 wikiconfig.set_import("MoinMoin.auth", ["MoinAuth"]) 1167 auth_object = "MoinAuth()" 1168 1169 # REMOTE_USER authentication. 1170 1171 elif method_name.lower() in ("given", "remote-user"): 1172 wikiconfig.set_import("MoinMoin.auth.http", ["HTTPAuth"]) 1173 auth_object = "HTTPAuth(autocreate=True)" 1174 1175 # Other methods are not currently supported. 1176 1177 else: 1178 return 1179 1180 # Edit the authentication setting. 1181 1182 auth = wikiconfig.get("auth") 1183 if auth: 1184 wikiconfig.replace("auth", "%s + [%s]" % (auth, auth_object), raw=1) 1185 else: 1186 wikiconfig.set("auth", "[%s]" % auth_object, raw=1) 1187 1188 finally: 1189 wikiconfig.close() 1190 1191 def migrate_instance(self, test=0, change_site=0): 1192 1193 """ 1194 Migrate the Wiki to the currently supported layout. If 'test' is 1195 specified and set to a non-empty or true value, only print whether the 1196 migration can be performed. 1197 1198 If 'change_site' is specified and set to a non-empty or true value, the 1199 site definitions will be updated; this will overwrite any changes made 1200 to the site definitions after they were last produced by moinsetup, and 1201 care must be taken to ensure that things like access controls are 1202 re-added to the definitions after this action is performed. 1203 """ 1204 1205 conf_dir = join(self.common_dir, "conf") 1206 if exists(conf_dir): 1207 for filename in listdir(conf_dir): 1208 pathname = join(conf_dir, filename) 1209 target = join(self.common_dir, filename) 1210 if not exists(target): 1211 print "Move", filename, "from conf directory." 1212 if not test: 1213 rename(pathname, target) 1214 else: 1215 print "No conf directory." 1216 1217 wikidata = join(self.common_dir, "wikidata") 1218 if exists(wikidata): 1219 htdocs = join(wikidata, "share", "moin", "htdocs") 1220 if exists(htdocs): 1221 target = join(self.common_dir, "htdocs") 1222 if not exists(target): 1223 print "Move htdocs from wikidata directory." 1224 if not test: 1225 rename(htdocs, target) 1226 else: 1227 print "No wikidata directory." 1228 1229 # Remove links and directories. 1230 1231 for name in ("conf", "wikidata"): 1232 d = join(self.common_dir, name) 1233 if islink(d): 1234 print "Remove %s symbolic link." % name 1235 if not test: 1236 remove(d) 1237 1238 if isdir(conf_dir): 1239 print "Remove conf directory." 1240 if not test: 1241 rmdir(conf_dir) 1242 1243 # Add any missing htdocs directory. 1244 1245 if not exists(self.htdocs_dir): 1246 print "Copy htdocs into the instance." 1247 if not test: 1248 self.install_static_data() 1249 1250 # Now attempt to reconfigure the Wiki. 1251 1252 print "Reconfigure the Wiki, the Web script%s." % (change_site and " and the site files" or "") 1253 if not test: 1254 self.configure_moin() 1255 self.edit_moin_web_script() 1256 if change_site: 1257 self.make_site_files() 1258 1259 def install_theme(self, theme_dir, theme_name=None): 1260 1261 """ 1262 Install Wiki theme provided in the given 'theme_dir' having the given 1263 optional 'theme_name' (if different from the 'theme_dir' name). 1264 """ 1265 1266 theme_dir = normpath(theme_dir) 1267 theme_name = theme_name or split(theme_dir)[-1] 1268 theme_module = join(theme_dir, theme_name + extsep + "py") 1269 1270 plugin_theme_dir = self.get_plugin_directory("theme") 1271 1272 # Copy the theme module. 1273 1274 status("Copying theme module to %s..." % plugin_theme_dir) 1275 1276 shutil.copy(theme_module, plugin_theme_dir) 1277 1278 # Copy the resources. 1279 1280 resources_dir = join(self.htdocs_dir, theme_name) 1281 1282 if not exists(resources_dir): 1283 mkdir(resources_dir) 1284 1285 status("Copying theme resources to %s..." % resources_dir) 1286 1287 for d in ("css", "img"): 1288 target_dir = join(resources_dir, d) 1289 if exists(target_dir): 1290 status("Replacing %s..." % target_dir) 1291 shutil.rmtree(target_dir) 1292 shutil.copytree(join(theme_dir, d), target_dir) 1293 1294 # Copy additional resources from other themes. 1295 1296 resources_source_dir = join(self.htdocs_dir, self.theme_master) 1297 target_dir = join(resources_dir, "css") 1298 1299 status("Copying resources from %s..." % resources_source_dir) 1300 1301 for css_file in self.extra_theme_css_files: 1302 css_file_path = join(resources_source_dir, "css", css_file) 1303 if exists(css_file_path): 1304 shutil.copy(css_file_path, target_dir) 1305 1306 note("Don't forget to add theme resources for extensions for this theme.\n" 1307 "Don't forget to edit this theme's stylesheets for extensions.") 1308 1309 def install_extension_package(self, extension_dir): 1310 1311 "Install any libraries from 'extension_dir' using a setup script." 1312 1313 this_dir = os.getcwd() 1314 chdir(extension_dir) 1315 1316 try: 1317 options = "install --install-lib=%s" % self.prefix_site_packages 1318 os.system("python setup.py %s" % options) 1319 finally: 1320 chdir(this_dir) 1321 1322 def install_plugins(self, plugins_dir, plugin_type): 1323 1324 """ 1325 Install Wiki actions provided in the given 'plugins_dir' of the 1326 specified 'plugin_type'. 1327 """ 1328 1329 plugin_target_dir = self.get_plugin_directory(plugin_type) 1330 1331 # Copy the modules. 1332 1333 status("Copying %s modules to %s..." % (plugin_type, plugin_target_dir)) 1334 1335 # If a setup file is detected, abandon the installation. 1336 1337 if exists(join(plugins_dir, "setup%spy" % extsep)): 1338 print "Plugins not installed: setup%spy was detected." % extsep 1339 return 1340 1341 for module in glob(join(plugins_dir, "*%spy" % extsep)): 1342 shutil.copy(module, plugin_target_dir) 1343 1344 def install_actions(self, actions_dir): 1345 1346 "Install Wiki actions provided in the given 'actions_dir'." 1347 1348 self.install_plugins(actions_dir, "action") 1349 1350 def install_macros(self, macros_dir): 1351 1352 "Install Wiki macros provided in the given 'macros_dir'." 1353 1354 self.install_plugins(macros_dir, "macro") 1355 1356 def install_parsers(self, parsers_dir): 1357 1358 "Install Wiki parsers provided in the given 'parsers_dir'." 1359 1360 self.install_plugins(parsers_dir, "parser") 1361 1362 def install_event_handlers(self, events_dir): 1363 1364 "Install Wiki event handlers provided in the given 'events_dir'." 1365 1366 self.install_plugins(events_dir, "events") 1367 1368 def install_theme_resources(self, theme_resources_dir, theme_name=None): 1369 1370 """ 1371 Install theme resources provided in the given 'theme_resources_dir'. If 1372 a specific 'theme_name' is given, only that theme will be given the 1373 specified resources. 1374 """ 1375 1376 for theme_name, theme_dir in self.get_theme_directories(theme_name): 1377 1378 # Copy the resources. 1379 1380 copied = 0 1381 1382 for d in ("css", "img"): 1383 source_dir = join(theme_resources_dir, d) 1384 target_dir = join(theme_dir, d) 1385 1386 if not exists(target_dir): 1387 continue 1388 1389 for resource in glob(join(source_dir, "*%s*" % extsep)): 1390 shutil.copy(resource, target_dir) 1391 copied = 1 1392 1393 if copied: 1394 status("Copied theme resources into %s..." % theme_dir) 1395 1396 note("Don't forget to edit theme stylesheets for any extensions.") 1397 1398 def edit_theme_stylesheet(self, theme_stylesheet, imported_stylesheet, action="ensure", theme_name=None): 1399 1400 """ 1401 Edit the given 'theme_stylesheet', ensuring (or removing) a reference to 1402 the 'imported_stylesheet' according to the given 'action' (optional, 1403 defaulting to "ensure"). If a specific 'theme_name' is given, only that 1404 theme will be affected. 1405 """ 1406 1407 if action == "ensure": 1408 ensure = 1 1409 elif action == "remove": 1410 ensure = 0 1411 else: 1412 error("Action %s not valid: it must be given as either 'ensure' or 'remove'." % action) 1413 return 1414 1415 for theme_name, theme_dir in self.get_theme_directories(theme_name): 1416 1417 # Locate the resources. 1418 1419 css_dir = join(theme_dir, "css") 1420 1421 if not exists(css_dir): 1422 continue 1423 1424 theme_stylesheet_filename = join(css_dir, theme_stylesheet) 1425 imported_stylesheet_filename = join(css_dir, imported_stylesheet) 1426 1427 if not exists(theme_stylesheet_filename): 1428 error("Stylesheet %s not defined in theme %s." % (theme_stylesheet, theme_name)) 1429 continue 1430 1431 if not exists(imported_stylesheet_filename): 1432 error("Stylesheet %s not defined in theme %s." % (imported_stylesheet, theme_name)) 1433 continue 1434 1435 # Edit the resources. 1436 1437 s = readfile(theme_stylesheet_filename) 1438 after_point = 0 1439 1440 for stylesheet_import in css_import_stylesheet.finditer(s): 1441 before, filename, after = stylesheet_import.groups() 1442 before_point, after_point = stylesheet_import.span() 1443 1444 # Test the import for a reference to the requested imported 1445 # stylesheet. 1446 1447 if filename == imported_stylesheet: 1448 if ensure: 1449 break 1450 else: 1451 if s[after_point:after_point+1] == "\n": 1452 after_point += 1 1453 s = "%s%s" % (s[:before_point], s[after_point:]) 1454 1455 status("Removing %s from %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name)) 1456 writefile(theme_stylesheet_filename, s) 1457 break 1458 1459 # Where no import references the imported stylesheet, insert a 1460 # reference into the theme stylesheet. 1461 1462 else: 1463 if ensure: 1464 1465 # Assume that the stylesheet can follow other imports. 1466 1467 if s[after_point:after_point+1] == "\n": 1468 after_point += 1 1469 s = "%s%s\n%s" % (s[:after_point], '@import "%s";' % imported_stylesheet, s[after_point:]) 1470 1471 status("Adding %s to %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name)) 1472 writefile(theme_stylesheet_filename, s) 1473 1474 def make_page_package(self, page_directory, package_filename): 1475 1476 """ 1477 Make a package containing the pages in 'page_directory', using the 1478 filenames as the page names, and writing the package to a file with the 1479 given 'package_filename'. 1480 """ 1481 1482 package = ZipFile(package_filename, "w") 1483 1484 try: 1485 script = ["MoinMoinPackage|1"] 1486 self._add_page_package_files(page_directory, "", package, script) 1487 package.writestr("MOIN_PACKAGE", "\n".join(script) + "\n") 1488 1489 finally: 1490 package.close() 1491 1492 def _add_page_package_files(self, page_directory, prefix, package, script): 1493 1494 """ 1495 Add files in the given 'page_directory' as pages with the given 'prefix' 1496 to the given 'package', recording them in the given 'script'. 1497 """ 1498 1499 filenames = listdir(page_directory) 1500 filenames.sort() 1501 1502 for filename in filenames: 1503 pathname = join(page_directory, filename) 1504 1505 # Add files as pages having the filename as page name. 1506 1507 if os.path.isfile(pathname): 1508 pagename = prefix + filename 1509 zipname = pagename.replace("/", "__") 1510 package.write(pathname, zipname) 1511 script.append("AddRevision|%s|%s" % (zipname, pagename)) 1512 1513 elif os.path.isdir(pathname): 1514 1515 # Add directories ending with "-attachments" as collections of 1516 # attachments for a particular page. 1517 1518 if filename.endswith("-attachments"): 1519 pagename = prefix + filename[:-len("-attachments")] 1520 zipname = pagename.replace("/", "__") 1521 1522 # Add each file as an attachment. 1523 1524 for attachment in listdir(pathname): 1525 azipname = "%s_%s" % (zipname, attachment) 1526 package.write(join(pathname, attachment), azipname) 1527 script.append("AddAttachment|%s|%s|%s||" % ( 1528 azipname, attachment, pagename)) 1529 1530 # Descend into other directories. 1531 1532 else: 1533 pagename = prefix + filename 1534 self._add_page_package_files(pathname, "%s/" % pagename, package, script) 1535 1536 def install_page_package(self, package_filename): 1537 1538 """ 1539 Install a package from the file with the given 'package_filename'. 1540 """ 1541 1542 path = os.environ.get("PYTHONPATH", "") 1543 1544 conf_dir = self.get_wikiconfig_directory() 1545 1546 if path: 1547 os.environ["PYTHONPATH"] = path + ":" + self.prefix_site_packages + ":" + conf_dir 1548 else: 1549 os.environ["PYTHONPATH"] = self.prefix_site_packages + ":" + conf_dir 1550 1551 installer = join(self.prefix_site_packages, "MoinMoin", "packages.py") 1552 cmd = "python %s i %s" % (installer, package_filename) 1553 os.system(cmd) 1554 1555 if path: 1556 os.environ["PYTHONPATH"] = path 1557 else: 1558 del os.environ["PYTHONPATH"] 1559 1560 def show_methods(): 1561 print "Methods:" 1562 print 1563 for method_name in Installation.method_names: 1564 doc = getattr(Installation, method_name).__doc__.strip() 1565 print "%-30s%-s" % (method_name, format(doc, 30)) 1566 print 1567 1568 # Command line option syntax. 1569 1570 syntax_description = "[ -f <config-filename> ] ( -m <method> | --method=METHOD ) [ <method-argument> ... ]" 1571 1572 # Main program. 1573 1574 if __name__ == "__main__": 1575 from ConfigParser import ConfigParser 1576 import sys, cmdsyntax 1577 1578 # Check the command syntax. 1579 1580 syntax = cmdsyntax.Syntax(syntax_description) 1581 try: 1582 matches = syntax.get_args(sys.argv[1:]) 1583 args = matches[0] 1584 except IndexError: 1585 print "Syntax:" 1586 print sys.argv[0], syntax_description 1587 print 1588 show_methods() 1589 sys.exit(1) 1590 1591 # Obtain configuration details. 1592 1593 try: 1594 config_filename = args.get("config-filename", "moinsetup.cfg") 1595 1596 if not exists(config_filename): 1597 print "Configuration", config_filename, "not found." 1598 sys.exit(1) 1599 1600 config = ConfigParser() 1601 config.read(config_filename) 1602 1603 # Obtain as many arguments as needed from the configuration. 1604 1605 config_arguments = dict(config.items("installation") + config.items("site")) 1606 method_arguments = args.get("method-argument", []) 1607 1608 # Attempt to initialise the configuration. 1609 1610 installation = Installation(**config_arguments) 1611 1612 except TypeError, exc: 1613 print "Error:" 1614 print 1615 print exc.args[0] 1616 print 1617 print "Configuration settings:" 1618 print 1619 print Installation.__init__.__doc__ 1620 print 1621 sys.exit(1) 1622 1623 # Obtain the method. 1624 1625 try: 1626 method = getattr(installation, args["method"]) 1627 except AttributeError: 1628 show_methods() 1629 sys.exit(1) 1630 1631 try: 1632 method(*method_arguments) 1633 except TypeError, exc: 1634 print "Error:" 1635 print 1636 print exc.args[0] 1637 print 1638 print "Method documentation:" 1639 print 1640 print method.__doc__ 1641 print 1642 sys.exit(1) 1643 1644 # vim: tabstop=4 expandtab shiftwidth=4