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