moinsetup

moinsetup.py

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