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