moinsetup

moinsetup.py

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