moinsetup

moinsetup.py

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