moinsetup

Annotated moinsetup.py

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