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