moinsetup

moinsetup.py

76:dc7f2e6f5d9d
2013-10-30 Paul Boddie Added tag rel-0-4 for changeset 99a9b8425abb
     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 # 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 and not self.site_packages:   526             raise TypeError, "Either the 'prefix' or the 'site_packages' 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 = "--prefix='%s' --record='%s'" % (self.prefix, log_filename)   875    876         os.system("%s setup.py --quiet %s %s --force" % (sys.executable, install_cmd, options))   877    878         chdir(this_dir)   879    880     def install_data(self):   881    882         "Install Wiki data into an instance."   883    884         moin_data = self.get_moin_data()   885    886         if not moin_data:   887             raise SetupException, \   888                 "Cannot install MoinMoin data without either a 'moin_distribution' or a 'prefix' setting being defined."   889    890         # The default wikiconfig assumes data and underlay in the same directory.   891    892         status("Installing data and underlay in %s..." % self.common_dir)   893    894         for d in ("data", "underlay"):   895             source = join(moin_data, d)   896             source_tar = source + extsep + "tar"   897    898             if exists(source):   899                 shutil.copytree(source, join(self.common_dir, d))   900             elif exists(source_tar):   901    902                 note("Copying archive %s instead of directory %s. Running...\n"   903                     "make pagepacks\n"   904                     "in the distribution directory should rectify this." % (source_tar, source))   905    906                 shutil.copy(source_tar, self.common_dir)   907                 os.system("tar xf %s -C %s" % (source_tar, self.common_dir))   908             else:   909                 status("Could not copy %s into installed Wiki." % d)   910    911     def install_static_data(self):   912    913         "Install static Web data if appropriate."   914    915         if not exists(self.htdocs_dir):   916             mkdir(self.htdocs_dir)   917    918         for item in listdir(self.htdocs_dir_source):   919             path = join(self.htdocs_dir_source, item)   920             if isdir(path):   921                 shutil.copytree(path, join(self.htdocs_dir, item))   922             else:   923                 shutil.copy(path, join(self.htdocs_dir, item))   924    925     def configure_moin(self, reset=0):   926    927         """   928         Edit the Wiki configuration file. If the optional 'reset' parameter is   929         specified as a true value, a default configuration will be copied from   930         the distribution if appropriate.   931         """   932    933         moin_data = self.get_moin_data()   934    935         if not moin_data:   936             raise SetupException, \   937                 "Cannot configure MoinMoin without either a 'moin_distribution' or a 'prefix' setting being defined."   938    939         # NOTE: MoinMoin usually uses an apparently common URL space associated   940         # NOTE: with the version, but more specific locations are probably   941         # NOTE: acceptable if less efficient.   942    943         url_prefix_static = "%r" % self.static_url_resources_path   944    945         # Use a farm configuration file.   946    947         if self.farm_config:   948             wikiconfig_py = self.farm_config   949    950         # Or copy the Wiki configuration file from the distribution.   951    952         else:   953             wikiconfig_py = join(self.common_dir, "wikiconfig.py")   954    955             if not exists(wikiconfig_py) or reset:   956                 shutil.copyfile(join(moin_data, "config", "wikiconfig.py"), wikiconfig_py)   957    958         status("Editing configuration from %s..." % wikiconfig_py)   959    960         # Edit the Wiki configuration file.   961    962         wikiconfig = Configuration(wikiconfig_py)   963    964         try:   965             wikiconfig.set("url_prefix_static", url_prefix_static, raw=1)   966             if self.superuser:   967                 wikiconfig.set("superuser", [self.superuser])   968                 wikiconfig.set("acl_rights_before", u"%s:read,write,delete,revert,admin" % self.superuser)   969             else:   970                 note("Superuser not defined. The ACL rules should be fixed in the configuration.")   971    972             # Edit any created Wiki configuration.   973    974             if not self.site_config:   975                 self._configure_moin(wikiconfig)   976    977         finally:   978             wikiconfig.close()   979    980         # Edit any separate site configuration file.   981    982         if self.site_config:   983             status("Editing configuration from %s..." % self.site_config)   984    985             wikiconfig = Configuration(self.site_config)   986    987             try:   988                 self._configure_moin(wikiconfig)   989             finally:   990                 wikiconfig.close()   991    992     def _configure_moin(self, wikiconfig):   993    994         """   995         Configure Moin, accessing the configuration file using 'wikiconfig'.   996         """   997    998         # Specific site configurations also appear to need 'data_dir', even in   999         # 1.9.  1000   1001         if not self.moin_version.startswith("1.9") or self.site_config:  1002             data_dir = join(self.common_dir, "data")  1003             data_underlay_dir = join(self.common_dir, "underlay")  1004   1005             wikiconfig.set("data_dir", data_dir)  1006             wikiconfig.set("data_underlay_dir", data_underlay_dir)  1007   1008         wikiconfig.set("site_name", self.site_name)  1009         wikiconfig.set("page_front_page", self.front_page_name, count=1)  1010   1011         if self.theme_default is not None:  1012             wikiconfig.set("theme_default", self.theme_default)  1013   1014     def edit_moin_script(self):  1015   1016         "Edit the moin script."  1017   1018         moin_script = self.get_moin_script()  1019         status("Editing moin script at %s..." % moin_script)  1020   1021         s = readfile(moin_script)  1022         s = s.replace("#!/usr/bin/env python", "#!%s" % sys.executable)  1023         s = s.replace("#import sys", "import sys\nsys.path.insert(0, %r)" % self.prefix_site_packages)  1024   1025         writefile(moin_script, s)  1026   1027     def edit_moin_web_script(self, site_file_configured=1):  1028   1029         "Edit and install CGI script."  1030   1031         # NOTE: CGI only so far.  1032         # NOTE: Permissions should be checked.  1033   1034         moin_data = self.get_moin_data()  1035   1036         if self.moin_version.startswith("1.9"):  1037             moin_cgi_script = "moin.fcgi"  1038         else:  1039             moin_cgi_script = "moin.cgi"  1040   1041         moin_cgi = join(moin_data, "server", moin_cgi_script)  1042         moin_cgi_installed = join(self.web_app_dir, "moin.cgi")  1043   1044         status("Editing moin.cgi script from %s, writing to %s..." % (moin_cgi, moin_cgi_installed))  1045   1046         s = readfile(moin_cgi)  1047         s = s.replace("#!/usr/bin/env python", "#!%s" % sys.executable)  1048         s = moin_cgi_prefix.sub("sys.path.insert(0, %r)" % self.prefix_site_packages, s)  1049         s = moin_cgi_wikiconfig.sub("sys.path.insert(0, %r)" % self.common_dir, s)  1050   1051         # Handle differences in script names when using limited hosting with  1052         # URL rewriting.  1053   1054         if self.limited_hosting():  1055             if not site_file_configured:  1056                 note("Site file not configured: script name not changed.")  1057             else:  1058                 url_path = self.final_url_path  1059   1060                 if self.moin_version.startswith("1.9"):  1061                     s = moin_cgi_fix_script_name.sub(r"\1\2 %r" % url_path, s)  1062                 else:  1063                     s = moin_cgi_properties.sub(r"\1\2 %r" % {"script_name" : url_path}, s)  1064   1065         # NOTE: Use CGI for now.  1066   1067         if self.moin_version.startswith("1.9"):  1068             s = moin_cgi_force_cgi.sub(r"\1", s)  1069   1070         writefile(moin_cgi_installed, s)  1071         os.system("chmod a+rx '%s'" % moin_cgi_installed)  1072   1073         # Fix the cause of opaque errors in some Apache environments.  1074   1075         os.system("chmod go-w '%s'" % moin_cgi_installed)  1076   1077     def add_superuser(self):  1078   1079         "Add the superuser account."  1080   1081         if not self.superuser:  1082             return  1083   1084         print "Creating superuser", self.superuser, "using..."  1085         email = raw_input("E-mail address: ")  1086         password = getpass("Password: ")  1087   1088         path = self._set_pythonpath()  1089   1090         cmd = "%s --config-dir='%s' account create --name='%s' --email='%s' --password='%s'" % (  1091             self.get_moin_script(), self.common_dir, self.superuser, email, password)  1092         os.system(cmd)  1093   1094         self._reset_pythonpath(path)  1095   1096     def make_site_files(self):  1097   1098         "Make the Apache site files."  1099   1100         # NOTE: Using local namespace for substitution.  1101   1102         # Where the site definitions and applications directories are different,  1103         # use a normal site definition.  1104   1105         if not self.limited_hosting():  1106   1107             site_def = join(self.web_site_dir, self.site_identifier)  1108   1109             s = apache_site % self.__dict__  1110             s += apache_site_extra % self.__dict__  1111   1112             status("Writing Apache site definitions to %s..." % site_def)  1113             writefile(site_def, s)  1114   1115             note("Copy the site definitions to the appropriate sites directory if appropriate.\n"  1116                 "Then, make sure that the site is enabled by running the appropriate tools (such as a2ensite).")  1117   1118             return 1  1119   1120         # Otherwise, use an .htaccess file.  1121   1122         else:  1123             site_def = join(self.web_static_dir or self.web_app_dir, ".htaccess")  1124   1125             s = apache_htaccess_combined_mod_rewrite % self.__dict__  1126   1127             status("Writing .htaccess file to %s..." % site_def)  1128             try:  1129                 writefile(site_def, s)  1130             except IOError:  1131                 note("The .htaccess file could not be written. This will also affect the script name setting.")  1132                 return 0  1133             else:  1134                 return 1  1135   1136     def make_post_install_script(self):  1137   1138         "Write a post-install script with additional actions."  1139   1140         # Create the scripts.  1141   1142         this_user = os.environ["USER"]  1143   1144         vars = {}  1145         vars.update(Installation.__dict__)  1146         vars.update(self.__dict__)  1147         vars.update(locals())  1148   1149         for postinst_script, start, extra, logs in postinst_scripts.values():  1150   1151             s = start % vars  1152             s += extra % vars  1153             s += logs % vars  1154   1155             writefile(postinst_script, s)  1156             chmod(postinst_script, 0755)  1157   1158         if self.have_setfacl():  1159             note("Run %s to set file access permissions.\n"  1160                 "If this somehow fails..." % postinst_scripts["setfacl"][0])  1161   1162         note("Run %s as root to set file ownership and permissions." % postinst_scripts["chown"][0])  1163   1164         note("Run %s as root to set SELinux permissions, if applicable." % postinst_scripts["semanage"][0])  1165   1166     # Accessory methods.  1167   1168     def reconfigure_moin(self, name=None, value=None, raw=0):  1169   1170         """  1171         Edit the installed Wiki configuration file, setting a parameter with any  1172         given 'name' to the given 'value', treating the value as a raw  1173         expression (not a string) if 'raw' is set to a true value.  1174   1175         If 'name' and the remaining parameters are omitted, the default  1176         configuration activity is performed.  1177   1178         If the 'site_config' setting is defined, the specific site configuration  1179         will be changed.  1180         """  1181   1182         wikiconfig_py = self.get_site_config()  1183   1184         status("Editing configuration from %s..." % wikiconfig_py)  1185   1186         wikiconfig = Configuration(wikiconfig_py)  1187   1188         try:  1189             # Perform default configuration.  1190   1191             if name is None and value is None:  1192                 self._configure_moin(wikiconfig)  1193             else:  1194                 wikiconfig.set(name, value, raw=raw)  1195   1196         finally:  1197             wikiconfig.close()  1198   1199     def set_auth_method(self, method_name):  1200   1201         """  1202         Edit the installed Wiki configuration file, configuring the  1203         authentication method having the given 'method_name'.  1204   1205         Currently recognised authentication methods are:  1206   1207           * openid             (uses OpenIDAuth to access OpenID providers)  1208           * moin, default      (use MoinAuth to provide a login form)  1209           * given, remote-user (use HTTPAuth to obtain Web server credentials)  1210   1211         If the 'farm_config' setting is defined, the Wiki farm configuration  1212         will be changed.  1213         """  1214   1215         wikiconfig_py = self.get_global_config()  1216   1217         status("Editing configuration from %s..." % wikiconfig_py)  1218   1219         wikiconfig = Configuration(wikiconfig_py)  1220   1221         try:  1222             # OpenID authentication.  1223   1224             if method_name.lower() == "openid":  1225                 wikiconfig.set_import("MoinMoin.auth.openidrp", ["OpenIDAuth"])  1226   1227                 if self.moin_version.startswith("1.9"):  1228                     if wikiconfig.get("cookie_lifetime"):  1229                         wikiconfig.replace("cookie_lifetime", "(12, 12)", raw=1)  1230                     else:  1231                         wikiconfig.set("cookie_lifetime", "(12, 12)", raw=1)  1232                 else:  1233                     if wikiconfig.get("anonymous_session_lifetime"):  1234                         wikiconfig.replace("anonymous_session_lifetime", "1000", raw=1)  1235                     else:  1236                         wikiconfig.set("anonymous_session_lifetime", "1000", raw=1)  1237   1238                 auth_object = "OpenIDAuth()"  1239   1240             # Default Moin authentication.  1241   1242             elif method_name.lower() in ("moin", "default"):  1243                 wikiconfig.set_import("MoinMoin.auth", ["MoinAuth"])  1244                 auth_object = "MoinAuth()"  1245   1246             # REMOTE_USER authentication.  1247   1248             elif method_name.lower() in ("given", "remote-user"):  1249                 wikiconfig.set_import("MoinMoin.auth.http", ["HTTPAuth"])  1250                 auth_object = "HTTPAuth(autocreate=True)"  1251   1252             # Other methods are not currently supported.  1253   1254             else:  1255                 return  1256   1257             # Edit the authentication setting.  1258   1259             auth = wikiconfig.get("auth")  1260             if auth:  1261                 wikiconfig.replace("auth", "%s + [%s]" % (auth, auth_object), raw=1)  1262             else:  1263                 wikiconfig.set("auth", "[%s]" % auth_object, raw=1)  1264   1265         finally:  1266             wikiconfig.close()  1267   1268     def migrate_instance(self, test=0, change_site=0):  1269   1270         """  1271         Migrate the Wiki to the currently supported layout. If 'test' is  1272         specified and set to a non-empty or true value, only print whether the  1273         migration can be performed.  1274   1275         If 'change_site' is specified and set to a non-empty or true value, the  1276         site definitions will be updated; this will overwrite any changes made  1277         to the site definitions after they were last produced by moinsetup, and  1278         care must be taken to ensure that things like access controls are  1279         re-added to the definitions after this action is performed.  1280         """  1281   1282         conf_dir = join(self.common_dir, "conf")  1283         if exists(conf_dir):  1284             for filename in listdir(conf_dir):  1285                 pathname = join(conf_dir, filename)  1286                 target = join(self.common_dir, filename)  1287                 if not exists(target):  1288                     print "Move", filename, "from conf directory."  1289                     if not test:  1290                         rename(pathname, target)  1291         else:  1292             print "No conf directory."  1293   1294         wikidata = join(self.common_dir, "wikidata")  1295         if exists(wikidata):  1296             htdocs = join(wikidata, "share", "moin", "htdocs")  1297             if exists(htdocs):  1298                 target = join(self.common_dir, "htdocs")  1299                 if not exists(target):  1300                     print "Move htdocs from wikidata directory."  1301                     if not test:  1302                         rename(htdocs, target)  1303         else:  1304             print "No wikidata directory."  1305   1306         # Remove links and directories.  1307   1308         for name in ("conf", "wikidata"):  1309             d = join(self.common_dir, name)  1310             if islink(d):  1311                 print "Remove %s symbolic link." % name  1312                 if not test:  1313                     remove(d)  1314   1315         if isdir(conf_dir):  1316             print "Remove conf directory."  1317             if not test:  1318                 rmdir(conf_dir)  1319   1320         # Add any missing htdocs directory.  1321   1322         if not exists(self.htdocs_dir):  1323             print "Copy htdocs into the instance."  1324             if not test:  1325                 self.install_static_data()  1326   1327         # Now attempt to reconfigure the Wiki.  1328   1329         print "Reconfigure the Wiki, the Web script%s." % (change_site and " and the site files" or "")  1330         if not test:  1331             self.configure_moin()  1332             self.edit_moin_web_script()  1333             if change_site:  1334                 self.make_site_files()  1335   1336     def install_theme(self, theme_dir, theme_name=None):  1337   1338         """  1339         Install Wiki theme provided in the given 'theme_dir' having the given  1340         optional 'theme_name' (if different from the 'theme_dir' name).  1341         """  1342   1343         theme_dir = normpath(theme_dir)  1344         theme_name = theme_name or split(theme_dir)[-1]  1345         theme_module = join(theme_dir, theme_name + extsep + "py")  1346   1347         plugin_theme_dir = self.get_plugin_directory("theme")  1348   1349         # Copy the theme module.  1350   1351         status("Copying theme module to %s..." % plugin_theme_dir)  1352   1353         shutil.copy(theme_module, plugin_theme_dir)  1354   1355         # Copy the resources.  1356   1357         resources_dir = join(self.htdocs_dir, theme_name)  1358   1359         if not exists(resources_dir):  1360             mkdir(resources_dir)  1361   1362         status("Copying theme resources to %s..." % resources_dir)  1363   1364         for d in ("css", "img"):  1365             target_dir = join(resources_dir, d)  1366             if exists(target_dir):  1367                 status("Replacing %s..." % target_dir)  1368                 shutil.rmtree(target_dir)  1369             shutil.copytree(join(theme_dir, d), target_dir)  1370   1371         # Copy additional resources from other themes.  1372   1373         resources_source_dir = join(self.htdocs_dir, self.theme_master)  1374         target_dir = join(resources_dir, "css")  1375   1376         status("Copying resources from %s..." % resources_source_dir)  1377   1378         for css_file in self.extra_theme_css_files:  1379             css_file_path = join(resources_source_dir, "css", css_file)  1380             if exists(css_file_path):  1381                 shutil.copy(css_file_path, target_dir)  1382   1383         note("Don't forget to add theme resources for extensions for this theme.\n"  1384             "Don't forget to edit this theme's stylesheets for extensions.")  1385   1386     def install_extension_package(self, extension_dir):  1387   1388         "Install any libraries from 'extension_dir' using a setup script."  1389   1390         this_dir = os.getcwd()  1391         chdir(extension_dir)  1392   1393         try:  1394             options = "install --install-lib=%s" % self.prefix_site_packages  1395             os.system("%s setup.py %s" % (sys.executable, options))  1396         finally:  1397             chdir(this_dir)  1398   1399     def install_plugins(self, plugins_dir, plugin_type):  1400   1401         """  1402         Install Wiki actions provided in the given 'plugins_dir' of the  1403         specified 'plugin_type'.  1404         """  1405   1406         plugin_target_dir = self.get_plugin_directory(plugin_type)  1407   1408         # Copy the modules.  1409   1410         status("Copying %s modules to %s..." % (plugin_type, plugin_target_dir))  1411   1412         # If a setup file is detected, abandon the installation.  1413   1414         if exists(join(plugins_dir, "setup%spy" % extsep)):  1415             print "Plugins not installed: setup%spy was detected." % extsep  1416             return  1417   1418         for module in glob(join(plugins_dir, "*%spy" % extsep)):  1419             shutil.copy(module, plugin_target_dir)  1420   1421             # Compile the plugin.  1422   1423             plugin = join(plugins_dir, split(module)[-1])  1424             py_compile.compile(plugin)  1425   1426     def install_actions(self, actions_dir):  1427   1428         "Install Wiki actions provided in the given 'actions_dir'."  1429   1430         self.install_plugins(actions_dir, "action")  1431   1432     def install_macros(self, macros_dir):  1433   1434         "Install Wiki macros provided in the given 'macros_dir'."  1435   1436         self.install_plugins(macros_dir, "macro")  1437   1438     def install_parsers(self, parsers_dir):  1439   1440         "Install Wiki parsers provided in the given 'parsers_dir'."  1441   1442         self.install_plugins(parsers_dir, "parser")  1443   1444     def install_event_handlers(self, events_dir):  1445   1446         "Install Wiki event handlers provided in the given 'events_dir'."  1447   1448         self.install_plugins(events_dir, "events")  1449   1450     def install_theme_resources(self, theme_resources_dir, theme_name=None):  1451   1452         """  1453         Install theme resources provided in the given 'theme_resources_dir'. If  1454         a specific 'theme_name' is given, only that theme will be given the  1455         specified resources.  1456         """  1457   1458         for theme_name, theme_dir in self.get_theme_directories(theme_name):  1459   1460             # Copy the resources.  1461   1462             copied = 0  1463   1464             for d in ("css", "img"):  1465                 source_dir = join(theme_resources_dir, d)  1466                 target_dir = join(theme_dir, d)  1467   1468                 if not exists(target_dir):  1469                     continue  1470   1471                 for resource in glob(join(source_dir, "*%s*" % extsep)):  1472                     shutil.copy(resource, target_dir)  1473                     copied = 1  1474   1475             if copied:  1476                 status("Copied theme resources into %s..." % theme_dir)  1477   1478         note("Don't forget to edit theme stylesheets for any extensions.")  1479   1480     def edit_theme_stylesheet(self, theme_stylesheet, imported_stylesheet, action="ensure", theme_name=None):  1481   1482         """  1483         Edit the given 'theme_stylesheet', ensuring (or removing) a reference to  1484         the 'imported_stylesheet' according to the given 'action' (optional,  1485         defaulting to "ensure"). If a specific 'theme_name' is given, only that  1486         theme will be affected.  1487         """  1488   1489         if action == "ensure":  1490             ensure = 1  1491         elif action == "remove":  1492             ensure = 0  1493         else:  1494             error("Action %s not valid: it must be given as either 'ensure' or 'remove'." % action)  1495             return  1496   1497         for theme_name, theme_dir in self.get_theme_directories(theme_name):  1498   1499             # Locate the resources.  1500   1501             css_dir = join(theme_dir, "css")  1502   1503             if not exists(css_dir):  1504                 continue  1505   1506             theme_stylesheet_filename = join(css_dir, theme_stylesheet)  1507             imported_stylesheet_filename = join(css_dir, imported_stylesheet)  1508   1509             if not exists(theme_stylesheet_filename):  1510                 error("Stylesheet %s not defined in theme %s." % (theme_stylesheet, theme_name))  1511                 continue  1512   1513             if not exists(imported_stylesheet_filename):  1514                 error("Stylesheet %s not defined in theme %s." % (imported_stylesheet, theme_name))  1515                 continue  1516   1517             # Edit the resources.  1518   1519             s = readfile(theme_stylesheet_filename)  1520             after_point = 0  1521   1522             for stylesheet_import in css_import_stylesheet.finditer(s):  1523                 before, filename, after = stylesheet_import.groups()  1524                 before_point, after_point = stylesheet_import.span()  1525   1526                 # Test the import for a reference to the requested imported  1527                 # stylesheet.  1528   1529                 if filename == imported_stylesheet:  1530                     if ensure:  1531                         break  1532                     else:  1533                         if s[after_point:after_point+1] == "\n":  1534                             after_point += 1  1535                         s = "%s%s" % (s[:before_point], s[after_point:])  1536   1537                         status("Removing %s from %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name))  1538                         writefile(theme_stylesheet_filename, s)  1539                         break  1540   1541             # Where no import references the imported stylesheet, insert a  1542             # reference into the theme stylesheet.  1543   1544             else:  1545                 if ensure:  1546   1547                     # Assume that the stylesheet can follow other imports.  1548   1549                     if s[after_point:after_point+1] == "\n":  1550                         after_point += 1  1551                     s = "%s%s\n%s" % (s[:after_point], '@import "%s";' % imported_stylesheet, s[after_point:])  1552   1553                     status("Adding %s to %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name))  1554                     writefile(theme_stylesheet_filename, s)  1555   1556     def make_page_package(self, page_directory, package_filename):  1557   1558         """  1559         Make a package containing the pages in 'page_directory', using the  1560         filenames as the page names, and writing the package to a file with the  1561         given 'package_filename'.  1562         """  1563   1564         package = ZipFile(package_filename, "w")  1565   1566         try:  1567             script = ["MoinMoinPackage|1"]  1568             self._add_page_package_files(page_directory, "", package, script)  1569             package.writestr("MOIN_PACKAGE", "\n".join(script) + "\n")  1570   1571         finally:  1572             package.close()  1573   1574     def _add_page_package_files(self, page_directory, prefix, package, script):  1575   1576         """  1577         Add files in the given 'page_directory' as pages with the given 'prefix'  1578         to the given 'package', recording them in the given 'script'.  1579         """  1580   1581         filenames = listdir(page_directory)  1582         filenames.sort()  1583   1584         for filename in filenames:  1585             pathname = join(page_directory, filename)  1586   1587             # Add files as pages having the filename as page name.  1588   1589             if os.path.isfile(pathname):  1590                 pagename = prefix + filename  1591                 zipname = pagename.replace("/", "__")  1592                 package.write(pathname, zipname)  1593                 script.append("AddRevision|%s|%s" % (zipname, pagename))  1594   1595             elif os.path.isdir(pathname):  1596   1597                 # Add directories ending with "-attachments" as collections of  1598                 # attachments for a particular page.  1599   1600                 if filename.endswith("-attachments"):  1601                     pagename = prefix + filename[:-len("-attachments")]  1602                     zipname = pagename.replace("/", "__")  1603   1604                     # Add each file as an attachment.  1605   1606                     for attachment in listdir(pathname):  1607                         azipname = "%s_%s" % (zipname, attachment)  1608                         package.write(join(pathname, attachment), azipname)  1609                         script.append("AddAttachment|%s|%s|%s||" % (  1610                             azipname, attachment, pagename))  1611   1612                 # Descend into other directories.  1613   1614                 else:  1615                     pagename = prefix + filename  1616                     self._add_page_package_files(pathname, "%s/" % pagename, package, script)  1617   1618     def install_page_package(self, package_filename):  1619   1620         """  1621         Install a package from the file with the given 'package_filename'.  1622         """  1623   1624         path = self._set_pythonpath()  1625         installer = join(self.prefix_site_packages, "MoinMoin", "packages.py")  1626         cmd = "%s %s i %s" % (sys.executable, installer, package_filename)  1627         os.system(cmd)  1628         self._reset_pythonpath(path)  1629   1630 def show_methods():  1631     print "Methods:"  1632     print  1633     for method_name in Installation.method_names:  1634         doc = getattr(Installation, method_name).__doc__.strip()  1635         print "%-30s%-s" % (method_name, format(doc, 30))  1636     print  1637   1638 # Command line option syntax.  1639   1640 syntax_description = "[ -f <config-filename> ] ( -m <method> | --method=METHOD ) [ <method-argument> ... ]"  1641   1642 # Main program.  1643   1644 if __name__ == "__main__":  1645     from ConfigParser import ConfigParser  1646     import sys, cmdsyntax  1647   1648     # Check the command syntax.  1649   1650     syntax = cmdsyntax.Syntax(syntax_description)  1651     try:  1652         matches = syntax.get_args(sys.argv[1:])  1653         args = matches[0]  1654     except IndexError:  1655         print "Syntax:"  1656         print sys.argv[0], syntax_description  1657         print  1658         show_methods()  1659         sys.exit(1)  1660   1661     # Obtain configuration details.  1662   1663     try:  1664         config_filename = args.get("config-filename", "moinsetup.cfg")  1665   1666         if not exists(config_filename):  1667             print "Configuration", config_filename, "not found."  1668             sys.exit(1)  1669   1670         config = ConfigParser()  1671         config.read(config_filename)  1672   1673         # Obtain as many arguments as needed from the configuration.  1674   1675         config_arguments = dict(config.items("installation") + config.items("site"))  1676         method_arguments = args.get("method-argument", [])  1677   1678         # Attempt to initialise the configuration.  1679   1680         installation = Installation(**config_arguments)  1681   1682     except TypeError, exc:  1683         print "Error:"  1684         print  1685         print exc.args[0]  1686         print  1687         print "Configuration settings:"  1688         print  1689         print Installation.__init__.__doc__  1690         print  1691         sys.exit(1)  1692   1693     # Obtain the method.  1694   1695     try:  1696         method = getattr(installation, args["method"])  1697     except AttributeError:  1698         show_methods()  1699         sys.exit(1)  1700   1701     try:  1702         method(*method_arguments)  1703     except TypeError, exc:  1704         print "Error:"  1705         print  1706         print exc.args[0]  1707         print  1708         print "Method documentation:"  1709         print  1710         print method.__doc__  1711         print  1712         sys.exit(1)  1713   1714 # vim: tabstop=4 expandtab shiftwidth=4