moinsetup

moinsetup.py

25:46b79e2490e8
2010-12-19 Paul Boddie Added methods for making page packages and installing them. Simplified the command syntax slightly.
     1 #!/usr/bin/env python     2      3 from os.path import abspath, exists, extsep, isdir, join, normpath, split     4 from getpass import getpass     5 from glob import glob     6 from zipfile import ZipFile     7 import os     8 import sys     9 import shutil    10 import re    11     12 # Regular expressions for editing MoinMoin scripts and configuration files.    13     14 def compile_definition(name):    15     return re.compile(r"^(\s*)#*\s*(%s =).*$" % name, re.MULTILINE)    16     17 moin_cgi_prefix          = re.compile("^#sys\.path\.insert\(0, 'PREFIX.*$", re.MULTILINE)    18 moin_cgi_wikiconfig      = re.compile("^#sys\.path\.insert\(0, '/path/to/wikiconfigdir.*$", re.MULTILINE)    19 moin_cgi_properties      = compile_definition("properties")    20 moin_cgi_fix_script_name = compile_definition("fix_script_name")    21 moin_cgi_force_cgi       = re.compile("^#(os.environ\['FCGI_FORCE_CGI'\].*)$", re.MULTILINE)    22     23 css_import_stylesheet    = re.compile("(\s*@import\s+[\"'])(.*?)([\"']\s*;)")    24     25 # Templates for Apache site definitions.    26     27 apache_site = """    28 ScriptAlias %(url_path)s "%(web_app_dir)s/moin.cgi"    29 """    30     31 apache_site_extra_moin18 = """    32 Alias %(static_url_path)s "%(htdocs_dir)s/"    33 """    34     35 # Limited hosting .htaccess definitions require the following settings to be    36 # configured in the main Apache configuration files:    37 #    38 # Options ExecCGI FollowSymLinks Indexes SymLinksIfOwnerMatch    39 # AllowOverride FileInfo Limit    40 # AddHandler cgi-script .cgi    41     42 apache_htaccess_combined_mod_rewrite = """    43 DirectoryIndex moin.cgi    44 RewriteEngine On    45 RewriteBase %(url_path)s    46 RewriteCond %%{REQUEST_FILENAME} !-f    47 RewriteCond %%{REQUEST_FILENAME} !-d    48 RewriteRule ^(.*) moin.cgi/$1 [PT,L,QSA]    49 """    50     51 # Utility functions.    52     53 def readfile(filename):    54     f = open(filename)    55     try:    56         return f.read()    57     finally:    58         f.close()    59     60 def writefile(filename, s):    61     f = open(filename, "w")    62     try:    63         f.write(s)    64     finally:    65         f.close()    66     67 def status(message):    68     print message    69     70 note = status    71 error = status    72     73 def format(s, indent):    74     return re.sub("\n\s+", "\n" + " " * indent, s)    75     76 # Classes.    77     78 class Configuration:    79     80     "A class representing the configuration."    81     82     special_names = ["site_name"]    83     84     def __init__(self, filename):    85         self.content = readfile(filename)    86         self.filename = filename    87     88     def get_pattern(self, name):    89     90         # Make underscores optional for certain names.    91     92         if name in self.special_names:    93             name = name.replace("_", "_?")    94     95         return compile_definition(name)    96     97     def set(self, name, value, count=None, raw=0):    98     99         """   100         Set the configuration parameter having the given 'name' with the given   101         'value', limiting the number of appropriately named parameters changed   102         to 'count', if specified.   103    104         If the configuration parameter of the given 'name' does not exist,   105         insert such a parameter at the end of the file.   106    107         If the optional 'raw' parameter is specified and set to a true value,   108         the provided 'value' is inserted directly into the configuration file.   109         """   110    111         if not self.replace(name, value, count, raw):   112             self.insert(name, value, raw)   113    114     def replace(self, name, value, count=None, raw=0):   115    116         """   117         Replace configuration parameters having the given 'name' with the given   118         'value', limiting the number of appropriately named parameters changed   119         to 'count', if specified.   120    121         If the optional 'raw' parameter is specified and set to a true value,   122         the provided 'value' is inserted directly into the configuration file.   123    124         Return the number of substitutions made.   125         """   126    127         if raw:   128             substitution = r"\1\2 %s" % value   129         else:   130             substitution = r"\1\2 %r" % value   131    132         pattern = self.get_pattern(name)   133    134         if count is None:   135             self.content, n = pattern.subn(substitution, self.content)   136         else:   137             self.content, n = pattern.subn(substitution, self.content, count=count)   138    139         return n   140    141     def insert(self, name, value, raw=0):   142    143         """   144         Insert the configuration parameter having the given 'name' and 'value'.   145    146         If the optional 'raw' parameter is specified and set to a true value,   147         the provided 'value' is inserted directly into the configuration file.   148         """   149    150         if raw:   151             insertion = "\n    %s = %s\n"   152         else:   153             insertion = "\n    %s = %r\n"   154    155         self.content += insertion % (name, value)   156    157     def close(self):   158    159         "Close the file, writing the content."   160    161         writefile(self.filename, self.content)   162    163 class Installation:   164    165     "A class for installing and initialising MoinMoin."   166    167     method_names = (   168         "setup",   169         "setup_wiki",   170         "install_moin",   171         "install_data",   172         "configure_moin",   173         "edit_moin_script",   174         "edit_moin_web_script",   175         "add_superuser",   176         "make_site_files",   177         "make_post_install_script",   178         "reconfigure_moin",   179    180         # Post-installation activities.   181    182         "install_theme",   183         "install_extension_package",   184         "install_plugins",   185         "install_actions",   186         "install_macros",   187         "install_theme_resources",   188         "edit_theme_stylesheet",   189    190         # Other activities.   191    192         "make_page_package",   193         "install_page_package",   194         )   195    196     # NOTE: Need to detect Web server user.   197    198     web_user = "www-data"   199     web_group = "www-data"   200    201     # MoinMoin resources.   202    203     theme_master = "modernized"   204     extra_theme_css_files = ["SlideShow.css"]   205    206     def __init__(self, moin_distribution, prefix, web_app_dir, web_site_dir,   207         common_dir, url_path, superuser, site_name, front_page_name,   208         theme_default=None):   209    210         """   211         Initialise a Wiki installation using the following:   212    213           * moin_distribution - the directory containing a MoinMoin source   214                                 distribution   215           * prefix            - the installation prefix (equivalent to /usr)   216           * web_app_dir       - the directory where Web applications and scripts   217                                 reside (such as /home/www-user/cgi-bin)   218           * web_site_dir      - the directory where Web site definitions reside   219                                 (such as /etc/apache2/sites-available)   220           * common_dir        - the directory where the Wiki configuration,   221                                 resources and instance will reside (such as   222                                 /home/www-user/mywiki)   223           * url_path          - the URL path at which the Wiki will be made   224                                 available (such as / or /mywiki)   225           * superuser         - the name of the site's superuser (such as   226                                 "AdminUser")   227           * site_name         - the name of the site (such as "My Wiki")   228           * front_page_name   - the front page name for the site (such as   229                                 "FrontPage" or a specific name for the site)   230           * theme_default     - optional: the default theme (such as modern)   231         """   232    233         self.moin_distribution = moin_distribution   234         self.superuser = superuser   235         self.site_name = site_name   236         self.front_page_name = front_page_name   237         self.theme_default = theme_default   238    239         # NOTE: Support the detection of the Apache sites directory.   240    241         self.prefix, self.web_app_dir, self.web_site_dir, self.common_dir = \   242             map(abspath, (prefix, web_app_dir, web_site_dir, common_dir))   243    244         # Strip any trailing "/" from the URL path.   245    246         if url_path != "/" and url_path.endswith("/"):   247             self.url_path = url_path[:-1]   248         else:   249             self.url_path = url_path   250    251         # Define and create specific directories.   252         # Here are the configuration and actual Wiki data directories.   253    254         self.conf_dir = join(self.common_dir, "conf")   255         self.instance_dir = join(self.common_dir, "wikidata")   256    257         # Define the place where the MoinMoin package will actually reside.   258    259         self.prefix_site_packages = join(self.prefix, "lib", "python%s.%s" % sys.version_info[:2], "site-packages")   260    261         # Find the version.   262    263         self.moin_version = self.get_moin_version()   264    265         # The static resources reside in different locations depending on the   266         # version of MoinMoin. Moreover, these resources may end up in a   267         # published directory for 1.8 installations where the Web server cannot   268         # be instructed to fetch the content from outside certain designated   269         # locations.   270    271         # 1.9: moin/lib/python2.x/site-packages/MoinMoin/web/static/htdocs   272    273         if self.moin_version.startswith("1.9"):   274             self.htdocs_dir = self.htdocs_dir_source = join(self.prefix_site_packages, "MoinMoin", "web", "static", "htdocs")   275    276         # 1.8: moin/share/moin/htdocs (optionally copied to a Web directory)   277    278         else:   279             self.htdocs_dir_source = join(self.instance_dir, "share", "moin", "htdocs")   280    281             if self.limited_hosting():   282                 self.htdocs_dir = join(self.web_app_dir, self.get_static_identifier())   283             else:   284                 self.htdocs_dir = self.htdocs_dir_source   285    286     def get_moin_version(self):   287    288         "Inspect the MoinMoin package information, returning the version."   289    290         this_dir = os.getcwd()   291         os.chdir(self.moin_distribution)   292    293         try:   294             try:   295                 f = open("PKG-INFO")   296                 try:   297                     for line in f.xreadlines():   298                         columns = line.split()   299                         if columns[0] == "Version:":   300                             return columns[1]   301    302                     return None   303    304                 finally:   305                     f.close()   306    307             except IOError:   308                 f = os.popen("%s -c 'from MoinMoin.version import release; print release'" % sys.executable)   309                 try:   310                     return f.read().strip()   311                 finally:   312                     f.close()   313         finally:   314             os.chdir(this_dir)   315    316     def get_static_identifier(self):   317    318         "Return the static URL/directory identifier for the Wiki."   319    320         return "moin_static%s" % self.moin_version.replace(".", "")   321    322     def get_plugin_directory(self, plugin_type):   323    324         "Return the directory for plugins of the given 'plugin_type'."   325    326         data_dir = join(self.conf_dir, "data")   327         return join(data_dir, "plugin", plugin_type)   328    329     def limited_hosting(self):   330    331         "Return whether limited Web hosting is being used."   332    333         return self.web_site_dir == self.web_app_dir   334    335     def ensure_directories(self):   336    337         "Make sure that all the directories are available."   338    339         for d in (self.conf_dir, self.instance_dir, self.web_app_dir, self.web_site_dir):   340             if not exists(d):   341                 os.makedirs(d)   342    343     def get_theme_directories(self, theme_name=None):   344    345         """   346         Return tuples of the form (theme name, theme directory) for all themes,   347         or for a single theme if the optional 'theme_name' is specified.   348         """   349    350         filenames = theme_name and [theme_name] or os.listdir(self.htdocs_dir)   351         directories = []   352    353         for filename in filenames:   354             theme_dir = join(self.htdocs_dir, filename)   355    356             if not exists(theme_dir) or not isdir(theme_dir):   357                 continue   358    359             directories.append((filename, theme_dir))   360    361         return directories   362    363     # Main methods.   364    365     def setup(self):   366    367         "Set up the installation."   368    369         self.ensure_directories()   370         self.install_moin()   371         self._setup_wiki()   372    373     def setup_wiki(self):   374    375         "Set up a Wiki without installing MoinMoin."   376    377         self.ensure_directories()   378         self.install_moin(data_only=1)   379         self._setup_wiki()   380    381     def _setup_wiki(self):   382    383         "Set up a Wiki without installing MoinMoin."   384    385         self.install_data()   386         self.configure_moin()   387         self.edit_moin_script()   388         self.edit_moin_web_script()   389         self.add_superuser()   390         self.make_site_files()   391         self.make_post_install_script()   392    393     def install_moin(self, data_only=0):   394    395         "Enter the distribution directory and run the setup script."   396    397         # NOTE: Possibly check for an existing installation and skip repeated   398         # NOTE: installation attempts.   399    400         this_dir = os.getcwd()   401         os.chdir(self.moin_distribution)   402    403         log_filename = "install-%s.log" % split(self.common_dir)[-1]   404    405         status("Installing MoinMoin in %s..." % self.prefix)   406    407         if data_only:   408             install_cmd = "install_data"   409             options = "--install-dir='%s'" % self.instance_dir   410         else:   411             install_cmd = "install"   412             options = "--prefix='%s' --install-data='%s' --record='%s'" % (self.prefix, self.instance_dir, log_filename)   413    414         os.system("python setup.py --quiet %s %s --force" % (install_cmd, options))   415    416         os.chdir(this_dir)   417    418     def install_data(self):   419    420         "Install Wiki data."   421    422         # The default wikiconfig assumes data and underlay in the same directory.   423    424         status("Installing data and underlay in %s..." % self.conf_dir)   425    426         for d in ("data", "underlay"):   427             source = join(self.moin_distribution, "wiki", d)   428             source_tar = source + os.path.extsep + "tar"   429             d_tar = source + os.path.extsep + "tar"   430    431             if os.path.exists(source):   432                 shutil.copytree(source, join(self.conf_dir, d))   433             elif os.path.exists(source_tar):   434                 shutil.copy(source_tar, self.conf_dir)   435                 os.system("tar xf %s -C %s" % (d_tar, self.conf_dir))   436             else:   437                 status("Could not copy %s into installed Wiki." % d)   438    439         # Copy static Web data if appropriate.   440    441         if not self.moin_version.startswith("1.9") and self.limited_hosting():   442    443             if not exists(self.htdocs_dir):   444                 os.mkdir(self.htdocs_dir)   445    446             for item in os.listdir(self.htdocs_dir_source):   447                 path = join(self.htdocs_dir_source, item)   448                 if isdir(path):   449                     shutil.copytree(path, join(self.htdocs_dir, item))   450                 else:   451                     shutil.copy(path, join(self.htdocs_dir, item))   452    453     def configure_moin(self):   454    455         "Edit the Wiki configuration file."   456    457         # NOTE: Single Wiki only so far.   458    459         # Static URLs seem to be different in MoinMoin 1.9.x.   460         # For earlier versions, reserve URL space alongside the Wiki.   461         # NOTE: MoinMoin usually uses an apparently common URL space associated   462         # NOTE: with the version, but more specific locations are probably   463         # NOTE: acceptable if less efficient.   464    465         if self.moin_version.startswith("1.9"):   466             self.static_url_path = self.url_path   467             url_prefix_static = "%r + url_prefix_static" % self.static_url_path   468         else:   469             # Add the static identifier to the URL path. For example:   470             # /         -> /moin_static187   471             # /hgwiki   -> /hgwiki/moin_static187   472    473             self.static_url_path = self.url_path + (self.url_path != "/" and "-" or "") + self.get_static_identifier()   474             url_prefix_static = "%r" % self.static_url_path   475    476         # Copy the Wiki configuration file from the distribution.   477    478         wikiconfig_py = join(self.conf_dir, "wikiconfig.py")   479         shutil.copyfile(join(self.moin_distribution, "wiki", "config", "wikiconfig.py"), wikiconfig_py)   480    481         status("Editing configuration from %s..." % wikiconfig_py)   482    483         # Edit the Wiki configuration file.   484    485         wikiconfig = Configuration(wikiconfig_py)   486    487         try:   488             wikiconfig.set("url_prefix_static", url_prefix_static, raw=1)   489             wikiconfig.set("superuser", [self.superuser])   490             wikiconfig.set("acl_rights_before", u"%s:read,write,delete,revert,admin" % self.superuser)   491    492             if not self.moin_version.startswith("1.9"):   493                 data_dir = join(self.conf_dir, "data")   494                 data_underlay_dir = join(self.conf_dir, "underlay")   495    496                 wikiconfig.set("data_dir", data_dir)   497                 wikiconfig.set("data_underlay_dir", data_underlay_dir)   498    499             self._configure_moin(wikiconfig)   500    501         finally:   502             wikiconfig.close()   503    504     def _configure_moin(self, wikiconfig):   505    506         """   507         Configure Moin, accessing the configuration file using 'wikiconfig'.   508         """   509    510         wikiconfig.set("site_name", self.site_name)   511         wikiconfig.set("page_front_page", self.front_page_name, count=1)   512    513         if self.theme_default is not None:   514             wikiconfig.set("theme_default", self.theme_default)   515    516     def edit_moin_script(self):   517    518         "Edit the moin script."   519    520         moin_script = join(self.prefix, "bin", "moin")   521    522         status("Editing moin script at %s..." % moin_script)   523    524         s = readfile(moin_script)   525         s = s.replace("#import sys", "import sys\nsys.path.insert(0, %r)" % self.prefix_site_packages)   526    527         writefile(moin_script, s)   528    529     def edit_moin_web_script(self):   530    531         "Edit and install CGI script."   532    533         # NOTE: CGI only so far.   534         # NOTE: Permissions should be checked.   535    536         if self.moin_version.startswith("1.9"):   537             moin_cgi = join(self.instance_dir, "share", "moin", "server", "moin.fcgi")   538         else:   539             moin_cgi = join(self.instance_dir, "share", "moin", "server", "moin.cgi")   540    541         moin_cgi_installed = join(self.web_app_dir, "moin.cgi")   542    543         status("Editing moin.cgi script from %s..." % moin_cgi)   544    545         s = readfile(moin_cgi)   546         s = moin_cgi_prefix.sub("sys.path.insert(0, %r)" % self.prefix_site_packages, s)   547         s = moin_cgi_wikiconfig.sub("sys.path.insert(0, %r)" % self.conf_dir, s)   548    549         # Handle differences in script names when using limited hosting with   550         # URL rewriting.   551    552         if self.limited_hosting():   553             if self.moin_version.startswith("1.9"):   554                 s = moin_cgi_fix_script_name.sub(r"\1\2 %r" % self.url_path, s)   555             else:   556                 s = moin_cgi_properties.sub(r"\1\2 %r" % {"script_name" : self.url_path}, s)   557    558         # NOTE: Use CGI for now.   559    560         if self.moin_version.startswith("1.9"):   561             s = moin_cgi_force_cgi.sub(r"\1", s)   562    563         writefile(moin_cgi_installed, s)   564         os.system("chmod a+rx '%s'" % moin_cgi_installed)   565    566     def add_superuser(self):   567    568         "Add the superuser account."   569    570         moin_script = join(self.prefix, "bin", "moin")   571    572         print "Creating superuser", self.superuser, "using..."   573         email = raw_input("E-mail address: ")   574         password = getpass("Password: ")   575    576         path = os.environ.get("PYTHONPATH", "")   577    578         if path:   579             os.environ["PYTHONPATH"] = path + ":" + self.conf_dir   580         else:   581             os.environ["PYTHONPATH"] = self.conf_dir   582    583         os.system(moin_script + " account create --name='%s' --email='%s' --password='%s'" % (self.superuser, email, password))   584    585         if path:   586             os.environ["PYTHONPATH"] = path   587         else:   588             del os.environ["PYTHONPATH"]   589    590     def make_site_files(self):   591    592         "Make the Apache site files."   593    594         # NOTE: Using local namespace for substitution.   595    596         # Where the site definitions and applications directories are different,   597         # use a normal site definition.   598    599         if not self.limited_hosting():   600    601             site_def = join(self.web_site_dir, self.site_name)   602    603             s = apache_site % self.__dict__   604    605             if not self.moin_version.startswith("1.9"):   606                 s += apache_site_extra_moin18 % self.__dict__   607    608         # Otherwise, use an .htaccess file.   609    610         else:   611             site_def = join(self.web_site_dir, ".htaccess")   612    613             s = apache_htaccess_combined_mod_rewrite % self.__dict__   614    615         status("Writing Apache site definitions to %s..." % site_def)   616    617         writefile(site_def, s)   618    619     def make_post_install_script(self):   620    621         "Write a post-install script with additional actions."   622    623         this_user = os.environ["USER"]   624         postinst_script = "moinsetup-post.sh"   625    626         s = "#!/bin/sh\n"   627    628         for d in ("data", "underlay"):   629             s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, join(self.conf_dir, d))   630             s += "chmod -R g+w '%s'\n" % join(self.conf_dir, d)   631    632         if not self.moin_version.startswith("1.9"):   633             s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, self.htdocs_dir)   634    635         writefile(postinst_script, s)   636         os.chmod(postinst_script, 0755)   637         note("Run %s as root to set file ownership and permissions." % postinst_script)   638    639     # Accessory methods.   640    641     def reconfigure_moin(self, name=None, value=None, raw=0):   642    643         "Edit the installed Wiki configuration file."   644    645         wikiconfig_py = join(self.conf_dir, "wikiconfig.py")   646    647         status("Editing configuration from %s..." % wikiconfig_py)   648    649         wikiconfig = Configuration(wikiconfig_py)   650    651         try:   652             # Perform default configuration.   653    654             if name is None and value is None:   655                 self._configure_moin(wikiconfig)   656             else:   657                 wikiconfig.set(name, value, raw=raw)   658    659         finally:   660             wikiconfig.close()   661    662     def install_theme(self, theme_dir):   663    664         "Install Wiki theme provided in the given 'theme_dir'."   665    666         theme_dir = normpath(theme_dir)   667         theme_name = split(theme_dir)[-1]   668         theme_module = join(theme_dir, theme_name + extsep + "py")   669    670         plugin_theme_dir = self.get_plugin_directory("theme")   671    672         # Copy the theme module.   673    674         status("Copying theme module to %s..." % plugin_theme_dir)   675    676         shutil.copy(theme_module, plugin_theme_dir)   677    678         # Copy the resources.   679    680         resources_dir = join(self.htdocs_dir, theme_name)   681    682         if not exists(resources_dir):   683             os.mkdir(resources_dir)   684    685         status("Copying theme resources to %s..." % resources_dir)   686    687         for d in ("css", "img"):   688             target_dir = join(resources_dir, d)   689             if exists(target_dir):   690                 status("Replacing %s..." % target_dir)   691                 shutil.rmtree(target_dir)   692             shutil.copytree(join(theme_dir, d), target_dir)   693    694         # Copy additional resources from other themes.   695    696         resources_source_dir = join(self.htdocs_dir, self.theme_master)   697         target_dir = join(resources_dir, "css")   698    699         status("Copying resources from %s..." % resources_source_dir)   700    701         for css_file in self.extra_theme_css_files:   702             css_file_path = join(resources_source_dir, "css", css_file)   703             if exists(css_file_path):   704                 shutil.copy(css_file_path, target_dir)   705    706         note("Don't forget to add theme resources for extensions for this theme.")   707         note("Don't forget to edit theme stylesheets for any extensions.")   708    709     def install_extension_package(self, extension_dir):   710    711         "Install any libraries from 'extension_dir' using a setup script."   712    713         this_dir = os.getcwd()   714         os.chdir(extension_dir)   715         os.system("python setup.py install --prefix=%s" % self.prefix)   716         os.chdir(this_dir)   717    718     def install_plugins(self, plugins_dir, plugin_type):   719    720         """   721         Install Wiki actions provided in the given 'plugins_dir' of the   722         specified 'plugin_type'.   723         """   724    725         plugin_target_dir = self.get_plugin_directory(plugin_type)   726    727         # Copy the modules.   728    729         status("Copying %s modules to %s..." % (plugin_type, plugin_target_dir))   730    731         for module in glob(join(plugins_dir, "*%spy" % extsep)):   732             shutil.copy(module, plugin_target_dir)   733    734     def install_actions(self, actions_dir):   735    736         "Install Wiki actions provided in the given 'actions_dir'."   737    738         self.install_plugins(actions_dir, "action")   739    740     def install_macros(self, macros_dir):   741    742         "Install Wiki macros provided in the given 'macros_dir'."   743    744         self.install_plugins(macros_dir, "macro")   745    746     def install_theme_resources(self, theme_resources_dir, theme_name=None):   747    748         """   749         Install theme resources provided in the given 'theme_resources_dir'. If   750         a specific 'theme_name' is given, only that theme will be given the   751         specified resources.   752         """   753    754         for theme_name, theme_dir in self.get_theme_directories(theme_name):   755    756             # Copy the resources.   757    758             copied = 0   759    760             for d in ("css", "img"):   761                 source_dir = join(theme_resources_dir, d)   762                 target_dir = join(theme_dir, d)   763    764                 if not exists(target_dir):   765                     continue   766    767                 for resource in glob(join(source_dir, "*%s*" % extsep)):   768                     shutil.copy(resource, target_dir)   769                     copied = 1   770    771             if copied:   772                 status("Copied theme resources into %s..." % theme_dir)   773    774         note("Don't forget to edit theme stylesheets for any extensions.")   775    776     def edit_theme_stylesheet(self, theme_stylesheet, imported_stylesheet, action="ensure", theme_name=None):   777    778         """   779         Edit the given 'theme_stylesheet', ensuring (or removing) a reference to   780         the 'imported_stylesheet' according to the given 'action' (optional,   781         defaulting to "ensure"). If a specific 'theme_name' is given, only that   782         theme will be affected.   783         """   784    785         if action == "ensure":   786             ensure = 1   787         elif action == "remove":   788             ensure = 0   789         else:   790             error("Action %s not valid: it must be given as either 'ensure' or 'remove'." % action)   791             return   792    793         for theme_name, theme_dir in self.get_theme_directories(theme_name):   794    795             # Locate the resources.   796    797             css_dir = join(theme_dir, "css")   798    799             if not exists(css_dir):   800                 continue   801    802             theme_stylesheet_filename = join(css_dir, theme_stylesheet)   803             imported_stylesheet_filename = join(css_dir, imported_stylesheet)   804    805             if not exists(theme_stylesheet_filename):   806                 error("Stylesheet %s not defined in theme %s." % (theme_stylesheet, theme_name))   807                 continue   808    809             if not exists(imported_stylesheet_filename):   810                 error("Stylesheet %s not defined in theme %s." % (imported_stylesheet, theme_name))   811                 continue   812    813             # Edit the resources.   814    815             s = readfile(theme_stylesheet_filename)   816             after_point = 0   817    818             for stylesheet_import in css_import_stylesheet.finditer(s):   819                 before, filename, after = stylesheet_import.groups()   820                 before_point, after_point = stylesheet_import.span()   821    822                 # Test the import for a reference to the requested imported   823                 # stylesheet.   824    825                 if filename == imported_stylesheet:   826                     if ensure:   827                         break   828                     else:   829                         if s[after_point:after_point+1] == "\n":   830                             after_point += 1   831                         s = "%s%s" % (s[:before_point], s[after_point:])   832    833                         status("Removing %s from %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name))   834                         writefile(theme_stylesheet_filename, s)   835                         break   836    837             # Where no import references the imported stylesheet, insert a   838             # reference into the theme stylesheet.   839    840             else:   841                 if ensure:   842    843                     # Assume that the stylesheet can follow other imports.   844    845                     if s[after_point:after_point+1] == "\n":   846                         after_point += 1   847                     s = "%s%s\n%s" % (s[:after_point], '@import "%s";' % imported_stylesheet, s[after_point:])   848    849                     status("Adding %s to %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name))   850                     writefile(theme_stylesheet_filename, s)   851    852     def make_page_package(self, page_directory, package_filename):   853    854         """   855         Make a package containing the pages in 'page_directory', using the   856         filenames as the page names, and writing the package to a file with the   857         given 'package_filename'.   858         """   859    860         package = ZipFile(package_filename, "w")   861    862         try:   863             script = ["MoinMoinPackage|1"]   864    865             for filename in os.listdir(page_directory):   866                 package.write(join(page_directory, filename), filename)   867                 script.append("AddRevision|%s|%s" % (filename, filename))   868    869             package.writestr("MOIN_PACKAGE", "\n".join(script))   870    871         finally:   872             package.close()   873    874     def install_page_package(self, package_filename):   875    876         """   877         Install a package from the file with the given 'package_filename'.   878         """   879    880         path = os.environ.get("PYTHONPATH", "")   881    882         if path:   883             os.environ["PYTHONPATH"] = path + ":" + self.prefix_site_packages + ":" + self.conf_dir   884         else:   885             os.environ["PYTHONPATH"] = self.prefix_site_packages + ":" + self.conf_dir   886    887         installer = join(self.prefix_site_packages, "MoinMoin", "packages.py")   888         os.system("python %s i %s" % (installer, package_filename))   889    890         if path:   891             os.environ["PYTHONPATH"] = path   892         else:   893             del os.environ["PYTHONPATH"]   894    895 def show_methods():   896     print "Methods:"   897     print   898     for method_name in Installation.method_names:   899         doc = getattr(Installation, method_name).__doc__.strip()   900         print "%-30s%-s" % (method_name, format(doc, 30))   901     print   902    903 # Command line option syntax.   904    905 syntax_description = "[ -f <config-filename> ] ( <method> | --method=METHOD ) [ <method-argument> ... ]"   906    907 # Main program.   908    909 if __name__ == "__main__":   910     from ConfigParser import ConfigParser   911     import sys, cmdsyntax   912    913     # Check the command syntax.   914    915     syntax = cmdsyntax.Syntax(syntax_description)   916     try:   917         matches = syntax.get_args(sys.argv[1:])   918         args = matches[0]   919     except IndexError:   920         print "Syntax:"   921         print sys.argv[0], syntax_description   922         print   923         show_methods()   924         sys.exit(1)   925    926     # Obtain configuration details.   927    928     try:   929         config_filename = args.get("config-filename", "moinsetup.cfg")   930         config = ConfigParser()   931         config.read(config_filename)   932    933         # Obtain as many arguments as needed from the configuration.   934    935         config_arguments = dict(config.items("installation") + config.items("site"))   936         method_arguments = args.get("method-argument", [])   937    938         # Attempt to initialise the configuration.   939    940         installation = Installation(**config_arguments)   941    942     except TypeError:   943         print "Configuration settings:"   944         print   945         print Installation.__init__.__doc__   946         print   947         sys.exit(1)   948    949     # Obtain the method.   950    951     try:   952         method = getattr(installation, args["method"])   953     except AttributeError:   954         show_methods()   955         sys.exit(1)   956    957     try:   958         method(*method_arguments)   959     except TypeError:   960         print "Method documentation:"   961         print   962         print method.__doc__   963         print   964         raise   965    966 # vim: tabstop=4 expandtab shiftwidth=4