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("#!/usr/bin/env python", "#!%s" % sys.executable) 954 s = s.replace("#import sys", "import sys\nsys.path.insert(0, %r)" % self.prefix_site_packages) 955 956 writefile(moin_script, s) 957 958 def edit_moin_web_script(self, site_file_configured=1): 959 960 "Edit and install CGI script." 961 962 # NOTE: CGI only so far. 963 # NOTE: Permissions should be checked. 964 965 moin_data = self.get_moin_data() 966 967 if self.moin_version.startswith("1.9"): 968 moin_cgi_script = "moin.fcgi" 969 else: 970 moin_cgi_script = "moin.cgi" 971 972 moin_cgi = join(moin_data, "server", moin_cgi_script) 973 moin_cgi_installed = join(self.web_app_dir, "moin.cgi") 974 975 status("Editing moin.cgi script from %s, writing to %s..." % (moin_cgi, moin_cgi_installed)) 976 977 s = readfile(moin_cgi) 978 s = s.replace("#!/usr/bin/env python", "#!%s" % sys.executable) 979 s = moin_cgi_prefix.sub("sys.path.insert(0, %r)" % self.prefix_site_packages, s) 980 s = moin_cgi_wikiconfig.sub("sys.path.insert(0, %r)" % self.common_dir, s) 981 982 # Handle differences in script names when using limited hosting with 983 # URL rewriting. 984 985 if self.limited_hosting(): 986 if not site_file_configured: 987 note("Site file not configured: script name not changed.") 988 else: 989 url_path = self.final_url_path 990 991 if self.moin_version.startswith("1.9"): 992 s = moin_cgi_fix_script_name.sub(r"\1\2 %r" % url_path, s) 993 else: 994 s = moin_cgi_properties.sub(r"\1\2 %r" % {"script_name" : url_path}, s) 995 996 # NOTE: Use CGI for now. 997 998 if self.moin_version.startswith("1.9"): 999 s = moin_cgi_force_cgi.sub(r"\1", s) 1000 1001 writefile(moin_cgi_installed, s) 1002 os.system("chmod a+rx '%s'" % moin_cgi_installed) 1003 1004 # Fix the cause of opaque errors in some Apache environments. 1005 1006 os.system("chmod go-w '%s'" % moin_cgi_installed) 1007 1008 def add_superuser(self): 1009 1010 "Add the superuser account." 1011 1012 if not self.superuser: 1013 return 1014 1015 print "Creating superuser", self.superuser, "using..." 1016 email = raw_input("E-mail address: ") 1017 password = getpass("Password: ") 1018 1019 path = self._set_pythonpath() 1020 1021 cmd = "%s --config-dir='%s' account create --name='%s' --email='%s' --password='%s'" % ( 1022 self.get_moin_script(), self.common_dir, self.superuser, email, password) 1023 os.system(cmd) 1024 1025 self._reset_pythonpath(path) 1026 1027 def make_site_files(self): 1028 1029 "Make the Apache site files." 1030 1031 # NOTE: Using local namespace for substitution. 1032 1033 # Where the site definitions and applications directories are different, 1034 # use a normal site definition. 1035 1036 if not self.limited_hosting(): 1037 1038 site_def = join(self.web_site_dir, self.site_identifier) 1039 1040 s = apache_site % self.__dict__ 1041 s += apache_site_extra % self.__dict__ 1042 1043 status("Writing Apache site definitions to %s..." % site_def) 1044 writefile(site_def, s) 1045 1046 note("Copy the site definitions to the appropriate sites directory if appropriate.\n" 1047 "Then, make sure that the site is enabled by running the appropriate tools (such as a2ensite).") 1048 1049 return 1 1050 1051 # Otherwise, use an .htaccess file. 1052 1053 else: 1054 site_def = join(self.web_static_dir or self.web_app_dir, ".htaccess") 1055 1056 s = apache_htaccess_combined_mod_rewrite % self.__dict__ 1057 1058 status("Writing .htaccess file to %s..." % site_def) 1059 try: 1060 writefile(site_def, s) 1061 except IOError: 1062 note("The .htaccess file could not be written. This will also affect the script name setting.") 1063 return 0 1064 else: 1065 return 1 1066 1067 def make_post_install_script(self): 1068 1069 "Write a post-install script with additional actions." 1070 1071 # Work out whether setfacl works. 1072 1073 fd, temp_filename = tempfile.mkstemp(dir=self.common_dir) 1074 os.close(fd) 1075 1076 have_setfacl = os.system("setfacl -m user:%(web_user)s:r %(file)s > /dev/null 2>&1" % { 1077 "web_user" : self.web_user, "file" : temp_filename}) == 0 1078 1079 remove(temp_filename) 1080 1081 # Create the scripts. 1082 1083 this_user = os.environ["USER"] 1084 postinst_scripts = "moinsetup-post-chown.sh", "moinsetup-post-setfacl.sh" 1085 1086 vars = {} 1087 vars.update(Installation.__dict__) 1088 vars.update(self.__dict__) 1089 vars.update(locals()) 1090 1091 for postinst_script, start, extra, logs in [ 1092 (postinst_scripts[0], postsetup_chown_chmod, postsetup_chown_extra, postsetup_chown_logs), 1093 (postinst_scripts[1], postsetup_setfacl, postsetup_setfacl_extra, postsetup_setfacl_logs) 1094 ]: 1095 1096 s = start % vars 1097 s += extra % vars 1098 s += logs % vars 1099 1100 writefile(postinst_script, s) 1101 chmod(postinst_script, 0755) 1102 1103 if have_setfacl: 1104 note("Run %s to set file ownership and permissions.\n" 1105 "If this somehow fails..." % postinst_scripts[1]) 1106 1107 note("Run %s as root to set file ownership and permissions." % postinst_scripts[0]) 1108 1109 # Accessory methods. 1110 1111 def reconfigure_moin(self, name=None, value=None, raw=0): 1112 1113 """ 1114 Edit the installed Wiki configuration file, setting a parameter with any 1115 given 'name' to the given 'value', treating the value as a raw 1116 expression (not a string) if 'raw' is set to a true value. 1117 1118 If 'name' and the remaining parameters are omitted, the default 1119 configuration activity is performed. 1120 1121 If the 'site_config' setting is defined, the specific site configuration 1122 will be changed. 1123 """ 1124 1125 wikiconfig_py = self.get_site_config() 1126 1127 status("Editing configuration from %s..." % wikiconfig_py) 1128 1129 wikiconfig = Configuration(wikiconfig_py) 1130 1131 try: 1132 # Perform default configuration. 1133 1134 if name is None and value is None: 1135 self._configure_moin(wikiconfig) 1136 else: 1137 wikiconfig.set(name, value, raw=raw) 1138 1139 finally: 1140 wikiconfig.close() 1141 1142 def set_auth_method(self, method_name): 1143 1144 """ 1145 Edit the installed Wiki configuration file, configuring the 1146 authentication method having the given 'method_name'. 1147 1148 Currently recognised authentication methods are: 1149 1150 * openid (uses OpenIDAuth to access OpenID providers) 1151 * moin, default (use MoinAuth to provide a login form) 1152 * given, remote-user (use HTTPAuth to obtain Web server credentials) 1153 1154 If the 'farm_config' setting is defined, the Wiki farm configuration 1155 will be changed. 1156 """ 1157 1158 wikiconfig_py = self.get_global_config() 1159 1160 status("Editing configuration from %s..." % wikiconfig_py) 1161 1162 wikiconfig = Configuration(wikiconfig_py) 1163 1164 try: 1165 # OpenID authentication. 1166 1167 if method_name.lower() == "openid": 1168 wikiconfig.set_import("MoinMoin.auth.openidrp", ["OpenIDAuth"]) 1169 1170 if self.moin_version.startswith("1.9"): 1171 if wikiconfig.get("cookie_lifetime"): 1172 wikiconfig.replace("cookie_lifetime", "(12, 12)", raw=1) 1173 else: 1174 wikiconfig.set("cookie_lifetime", "(12, 12)", raw=1) 1175 else: 1176 if wikiconfig.get("anonymous_session_lifetime"): 1177 wikiconfig.replace("anonymous_session_lifetime", "1000", raw=1) 1178 else: 1179 wikiconfig.set("anonymous_session_lifetime", "1000", raw=1) 1180 1181 auth_object = "OpenIDAuth()" 1182 1183 # Default Moin authentication. 1184 1185 elif method_name.lower() in ("moin", "default"): 1186 wikiconfig.set_import("MoinMoin.auth", ["MoinAuth"]) 1187 auth_object = "MoinAuth()" 1188 1189 # REMOTE_USER authentication. 1190 1191 elif method_name.lower() in ("given", "remote-user"): 1192 wikiconfig.set_import("MoinMoin.auth.http", ["HTTPAuth"]) 1193 auth_object = "HTTPAuth(autocreate=True)" 1194 1195 # Other methods are not currently supported. 1196 1197 else: 1198 return 1199 1200 # Edit the authentication setting. 1201 1202 auth = wikiconfig.get("auth") 1203 if auth: 1204 wikiconfig.replace("auth", "%s + [%s]" % (auth, auth_object), raw=1) 1205 else: 1206 wikiconfig.set("auth", "[%s]" % auth_object, raw=1) 1207 1208 finally: 1209 wikiconfig.close() 1210 1211 def migrate_instance(self, test=0, change_site=0): 1212 1213 """ 1214 Migrate the Wiki to the currently supported layout. If 'test' is 1215 specified and set to a non-empty or true value, only print whether the 1216 migration can be performed. 1217 1218 If 'change_site' is specified and set to a non-empty or true value, the 1219 site definitions will be updated; this will overwrite any changes made 1220 to the site definitions after they were last produced by moinsetup, and 1221 care must be taken to ensure that things like access controls are 1222 re-added to the definitions after this action is performed. 1223 """ 1224 1225 conf_dir = join(self.common_dir, "conf") 1226 if exists(conf_dir): 1227 for filename in listdir(conf_dir): 1228 pathname = join(conf_dir, filename) 1229 target = join(self.common_dir, filename) 1230 if not exists(target): 1231 print "Move", filename, "from conf directory." 1232 if not test: 1233 rename(pathname, target) 1234 else: 1235 print "No conf directory." 1236 1237 wikidata = join(self.common_dir, "wikidata") 1238 if exists(wikidata): 1239 htdocs = join(wikidata, "share", "moin", "htdocs") 1240 if exists(htdocs): 1241 target = join(self.common_dir, "htdocs") 1242 if not exists(target): 1243 print "Move htdocs from wikidata directory." 1244 if not test: 1245 rename(htdocs, target) 1246 else: 1247 print "No wikidata directory." 1248 1249 # Remove links and directories. 1250 1251 for name in ("conf", "wikidata"): 1252 d = join(self.common_dir, name) 1253 if islink(d): 1254 print "Remove %s symbolic link." % name 1255 if not test: 1256 remove(d) 1257 1258 if isdir(conf_dir): 1259 print "Remove conf directory." 1260 if not test: 1261 rmdir(conf_dir) 1262 1263 # Add any missing htdocs directory. 1264 1265 if not exists(self.htdocs_dir): 1266 print "Copy htdocs into the instance." 1267 if not test: 1268 self.install_static_data() 1269 1270 # Now attempt to reconfigure the Wiki. 1271 1272 print "Reconfigure the Wiki, the Web script%s." % (change_site and " and the site files" or "") 1273 if not test: 1274 self.configure_moin() 1275 self.edit_moin_web_script() 1276 if change_site: 1277 self.make_site_files() 1278 1279 def install_theme(self, theme_dir, theme_name=None): 1280 1281 """ 1282 Install Wiki theme provided in the given 'theme_dir' having the given 1283 optional 'theme_name' (if different from the 'theme_dir' name). 1284 """ 1285 1286 theme_dir = normpath(theme_dir) 1287 theme_name = theme_name or split(theme_dir)[-1] 1288 theme_module = join(theme_dir, theme_name + extsep + "py") 1289 1290 plugin_theme_dir = self.get_plugin_directory("theme") 1291 1292 # Copy the theme module. 1293 1294 status("Copying theme module to %s..." % plugin_theme_dir) 1295 1296 shutil.copy(theme_module, plugin_theme_dir) 1297 1298 # Copy the resources. 1299 1300 resources_dir = join(self.htdocs_dir, theme_name) 1301 1302 if not exists(resources_dir): 1303 mkdir(resources_dir) 1304 1305 status("Copying theme resources to %s..." % resources_dir) 1306 1307 for d in ("css", "img"): 1308 target_dir = join(resources_dir, d) 1309 if exists(target_dir): 1310 status("Replacing %s..." % target_dir) 1311 shutil.rmtree(target_dir) 1312 shutil.copytree(join(theme_dir, d), target_dir) 1313 1314 # Copy additional resources from other themes. 1315 1316 resources_source_dir = join(self.htdocs_dir, self.theme_master) 1317 target_dir = join(resources_dir, "css") 1318 1319 status("Copying resources from %s..." % resources_source_dir) 1320 1321 for css_file in self.extra_theme_css_files: 1322 css_file_path = join(resources_source_dir, "css", css_file) 1323 if exists(css_file_path): 1324 shutil.copy(css_file_path, target_dir) 1325 1326 note("Don't forget to add theme resources for extensions for this theme.\n" 1327 "Don't forget to edit this theme's stylesheets for extensions.") 1328 1329 def install_extension_package(self, extension_dir): 1330 1331 "Install any libraries from 'extension_dir' using a setup script." 1332 1333 this_dir = os.getcwd() 1334 chdir(extension_dir) 1335 1336 try: 1337 options = "install --install-lib=%s" % self.prefix_site_packages 1338 os.system("%s setup.py %s" % (sys.executable, options)) 1339 finally: 1340 chdir(this_dir) 1341 1342 def install_plugins(self, plugins_dir, plugin_type): 1343 1344 """ 1345 Install Wiki actions provided in the given 'plugins_dir' of the 1346 specified 'plugin_type'. 1347 """ 1348 1349 plugin_target_dir = self.get_plugin_directory(plugin_type) 1350 1351 # Copy the modules. 1352 1353 status("Copying %s modules to %s..." % (plugin_type, plugin_target_dir)) 1354 1355 # If a setup file is detected, abandon the installation. 1356 1357 if exists(join(plugins_dir, "setup%spy" % extsep)): 1358 print "Plugins not installed: setup%spy was detected." % extsep 1359 return 1360 1361 for module in glob(join(plugins_dir, "*%spy" % extsep)): 1362 shutil.copy(module, plugin_target_dir) 1363 1364 def install_actions(self, actions_dir): 1365 1366 "Install Wiki actions provided in the given 'actions_dir'." 1367 1368 self.install_plugins(actions_dir, "action") 1369 1370 def install_macros(self, macros_dir): 1371 1372 "Install Wiki macros provided in the given 'macros_dir'." 1373 1374 self.install_plugins(macros_dir, "macro") 1375 1376 def install_parsers(self, parsers_dir): 1377 1378 "Install Wiki parsers provided in the given 'parsers_dir'." 1379 1380 self.install_plugins(parsers_dir, "parser") 1381 1382 def install_event_handlers(self, events_dir): 1383 1384 "Install Wiki event handlers provided in the given 'events_dir'." 1385 1386 self.install_plugins(events_dir, "events") 1387 1388 def install_theme_resources(self, theme_resources_dir, theme_name=None): 1389 1390 """ 1391 Install theme resources provided in the given 'theme_resources_dir'. If 1392 a specific 'theme_name' is given, only that theme will be given the 1393 specified resources. 1394 """ 1395 1396 for theme_name, theme_dir in self.get_theme_directories(theme_name): 1397 1398 # Copy the resources. 1399 1400 copied = 0 1401 1402 for d in ("css", "img"): 1403 source_dir = join(theme_resources_dir, d) 1404 target_dir = join(theme_dir, d) 1405 1406 if not exists(target_dir): 1407 continue 1408 1409 for resource in glob(join(source_dir, "*%s*" % extsep)): 1410 shutil.copy(resource, target_dir) 1411 copied = 1 1412 1413 if copied: 1414 status("Copied theme resources into %s..." % theme_dir) 1415 1416 note("Don't forget to edit theme stylesheets for any extensions.") 1417 1418 def edit_theme_stylesheet(self, theme_stylesheet, imported_stylesheet, action="ensure", theme_name=None): 1419 1420 """ 1421 Edit the given 'theme_stylesheet', ensuring (or removing) a reference to 1422 the 'imported_stylesheet' according to the given 'action' (optional, 1423 defaulting to "ensure"). If a specific 'theme_name' is given, only that 1424 theme will be affected. 1425 """ 1426 1427 if action == "ensure": 1428 ensure = 1 1429 elif action == "remove": 1430 ensure = 0 1431 else: 1432 error("Action %s not valid: it must be given as either 'ensure' or 'remove'." % action) 1433 return 1434 1435 for theme_name, theme_dir in self.get_theme_directories(theme_name): 1436 1437 # Locate the resources. 1438 1439 css_dir = join(theme_dir, "css") 1440 1441 if not exists(css_dir): 1442 continue 1443 1444 theme_stylesheet_filename = join(css_dir, theme_stylesheet) 1445 imported_stylesheet_filename = join(css_dir, imported_stylesheet) 1446 1447 if not exists(theme_stylesheet_filename): 1448 error("Stylesheet %s not defined in theme %s." % (theme_stylesheet, theme_name)) 1449 continue 1450 1451 if not exists(imported_stylesheet_filename): 1452 error("Stylesheet %s not defined in theme %s." % (imported_stylesheet, theme_name)) 1453 continue 1454 1455 # Edit the resources. 1456 1457 s = readfile(theme_stylesheet_filename) 1458 after_point = 0 1459 1460 for stylesheet_import in css_import_stylesheet.finditer(s): 1461 before, filename, after = stylesheet_import.groups() 1462 before_point, after_point = stylesheet_import.span() 1463 1464 # Test the import for a reference to the requested imported 1465 # stylesheet. 1466 1467 if filename == imported_stylesheet: 1468 if ensure: 1469 break 1470 else: 1471 if s[after_point:after_point+1] == "\n": 1472 after_point += 1 1473 s = "%s%s" % (s[:before_point], s[after_point:]) 1474 1475 status("Removing %s from %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name)) 1476 writefile(theme_stylesheet_filename, s) 1477 break 1478 1479 # Where no import references the imported stylesheet, insert a 1480 # reference into the theme stylesheet. 1481 1482 else: 1483 if ensure: 1484 1485 # Assume that the stylesheet can follow other imports. 1486 1487 if s[after_point:after_point+1] == "\n": 1488 after_point += 1 1489 s = "%s%s\n%s" % (s[:after_point], '@import "%s";' % imported_stylesheet, s[after_point:]) 1490 1491 status("Adding %s to %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name)) 1492 writefile(theme_stylesheet_filename, s) 1493 1494 def make_page_package(self, page_directory, package_filename): 1495 1496 """ 1497 Make a package containing the pages in 'page_directory', using the 1498 filenames as the page names, and writing the package to a file with the 1499 given 'package_filename'. 1500 """ 1501 1502 package = ZipFile(package_filename, "w") 1503 1504 try: 1505 script = ["MoinMoinPackage|1"] 1506 self._add_page_package_files(page_directory, "", package, script) 1507 package.writestr("MOIN_PACKAGE", "\n".join(script) + "\n") 1508 1509 finally: 1510 package.close() 1511 1512 def _add_page_package_files(self, page_directory, prefix, package, script): 1513 1514 """ 1515 Add files in the given 'page_directory' as pages with the given 'prefix' 1516 to the given 'package', recording them in the given 'script'. 1517 """ 1518 1519 filenames = listdir(page_directory) 1520 filenames.sort() 1521 1522 for filename in filenames: 1523 pathname = join(page_directory, filename) 1524 1525 # Add files as pages having the filename as page name. 1526 1527 if os.path.isfile(pathname): 1528 pagename = prefix + filename 1529 zipname = pagename.replace("/", "__") 1530 package.write(pathname, zipname) 1531 script.append("AddRevision|%s|%s" % (zipname, pagename)) 1532 1533 elif os.path.isdir(pathname): 1534 1535 # Add directories ending with "-attachments" as collections of 1536 # attachments for a particular page. 1537 1538 if filename.endswith("-attachments"): 1539 pagename = prefix + filename[:-len("-attachments")] 1540 zipname = pagename.replace("/", "__") 1541 1542 # Add each file as an attachment. 1543 1544 for attachment in listdir(pathname): 1545 azipname = "%s_%s" % (zipname, attachment) 1546 package.write(join(pathname, attachment), azipname) 1547 script.append("AddAttachment|%s|%s|%s||" % ( 1548 azipname, attachment, pagename)) 1549 1550 # Descend into other directories. 1551 1552 else: 1553 pagename = prefix + filename 1554 self._add_page_package_files(pathname, "%s/" % pagename, package, script) 1555 1556 def install_page_package(self, package_filename): 1557 1558 """ 1559 Install a package from the file with the given 'package_filename'. 1560 """ 1561 1562 path = self._set_pythonpath() 1563 installer = join(self.prefix_site_packages, "MoinMoin", "packages.py") 1564 cmd = "%s %s i %s" % (sys.executable, installer, package_filename) 1565 os.system(cmd) 1566 self._reset_pythonpath(path) 1567 1568 def show_methods(): 1569 print "Methods:" 1570 print 1571 for method_name in Installation.method_names: 1572 doc = getattr(Installation, method_name).__doc__.strip() 1573 print "%-30s%-s" % (method_name, format(doc, 30)) 1574 print 1575 1576 # Command line option syntax. 1577 1578 syntax_description = "[ -f <config-filename> ] ( -m <method> | --method=METHOD ) [ <method-argument> ... ]" 1579 1580 # Main program. 1581 1582 if __name__ == "__main__": 1583 from ConfigParser import ConfigParser 1584 import sys, cmdsyntax 1585 1586 # Check the command syntax. 1587 1588 syntax = cmdsyntax.Syntax(syntax_description) 1589 try: 1590 matches = syntax.get_args(sys.argv[1:]) 1591 args = matches[0] 1592 except IndexError: 1593 print "Syntax:" 1594 print sys.argv[0], syntax_description 1595 print 1596 show_methods() 1597 sys.exit(1) 1598 1599 # Obtain configuration details. 1600 1601 try: 1602 config_filename = args.get("config-filename", "moinsetup.cfg") 1603 1604 if not exists(config_filename): 1605 print "Configuration", config_filename, "not found." 1606 sys.exit(1) 1607 1608 config = ConfigParser() 1609 config.read(config_filename) 1610 1611 # Obtain as many arguments as needed from the configuration. 1612 1613 config_arguments = dict(config.items("installation") + config.items("site")) 1614 method_arguments = args.get("method-argument", []) 1615 1616 # Attempt to initialise the configuration. 1617 1618 installation = Installation(**config_arguments) 1619 1620 except TypeError, exc: 1621 print "Error:" 1622 print 1623 print exc.args[0] 1624 print 1625 print "Configuration settings:" 1626 print 1627 print Installation.__init__.__doc__ 1628 print 1629 sys.exit(1) 1630 1631 # Obtain the method. 1632 1633 try: 1634 method = getattr(installation, args["method"]) 1635 except AttributeError: 1636 show_methods() 1637 sys.exit(1) 1638 1639 try: 1640 method(*method_arguments) 1641 except TypeError, exc: 1642 print "Error:" 1643 print 1644 print exc.args[0] 1645 print 1646 print "Method documentation:" 1647 print 1648 print method.__doc__ 1649 print 1650 sys.exit(1) 1651 1652 # vim: tabstop=4 expandtab shiftwidth=4