moinsetup

moinsetup.py

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