moinsetup

moinsetup.py

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