moinsetup

moinsetup.py

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