moinsetup

moinsetup.py

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