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() 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 s = moin_cgi_force_cgi.sub(r"\1", s) 546 else: 547 s = moin_cgi_properties.sub(r"\1\2 %r" % {"script_name" : self.url_path}, s) 548 549 writefile(moin_cgi_installed, s) 550 os.system("chmod a+rx '%s'" % moin_cgi_installed) 551 552 def add_superuser(self): 553 554 "Add the superuser account." 555 556 moin_script = join(self.prefix, "bin", "moin") 557 558 print "Creating superuser", self.superuser, "using..." 559 email = raw_input("E-mail address: ") 560 password = getpass("Password: ") 561 562 path = os.environ.get("PYTHONPATH", "") 563 564 if path: 565 os.environ["PYTHONPATH"] = path + ":" + self.conf_dir 566 else: 567 os.environ["PYTHONPATH"] = self.conf_dir 568 569 os.system(moin_script + " account create --name='%s' --email='%s' --password='%s'" % (self.superuser, email, password)) 570 571 if path: 572 os.environ["PYTHONPATH"] = path 573 else: 574 del os.environ["PYTHONPATH"] 575 576 def make_site_files(self): 577 578 "Make the Apache site files." 579 580 # NOTE: Using local namespace for substitution. 581 582 # Where the site definitions and applications directories are different, 583 # use a normal site definition. 584 585 if not self.limited_hosting(): 586 587 site_def = join(self.web_site_dir, self.site_name) 588 589 s = apache_site % self.__dict__ 590 591 if not self.moin_version.startswith("1.9"): 592 s += apache_site_extra_moin18 % self.__dict__ 593 594 # Otherwise, use an .htaccess file. 595 596 else: 597 site_def = join(self.web_site_dir, ".htaccess") 598 599 s = apache_htaccess_combined_mod_rewrite % self.__dict__ 600 601 status("Writing Apache site definitions to %s..." % site_def) 602 603 writefile(site_def, s) 604 605 def make_post_install_script(self): 606 607 "Write a post-install script with additional actions." 608 609 this_user = os.environ["USER"] 610 postinst_script = "moinsetup-post.sh" 611 612 s = "#!/bin/sh\n" 613 614 for d in ("data", "underlay"): 615 s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, join(self.conf_dir, d)) 616 s += "chmod -R g+w '%s'\n" % join(self.conf_dir, d) 617 618 if not self.moin_version.startswith("1.9"): 619 s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, self.htdocs_dir) 620 621 writefile(postinst_script, s) 622 os.chmod(postinst_script, 0755) 623 note("Run %s as root to set file ownership and permissions." % postinst_script) 624 625 # Accessory methods. 626 627 def reconfigure_moin(self, name=None, value=None, raw=0): 628 629 "Edit the installed Wiki configuration file." 630 631 wikiconfig_py = join(self.conf_dir, "wikiconfig.py") 632 633 status("Editing configuration from %s..." % wikiconfig_py) 634 635 wikiconfig = Configuration(wikiconfig_py) 636 637 try: 638 # Perform default configuration. 639 640 if name is None and value is None: 641 self._configure_moin(wikiconfig) 642 else: 643 wikiconfig.set(name, value, raw=raw) 644 645 finally: 646 wikiconfig.close() 647 648 def install_theme(self, theme_dir): 649 650 "Install Wiki theme provided in the given 'theme_dir'." 651 652 theme_dir = normpath(theme_dir) 653 theme_name = split(theme_dir)[-1] 654 theme_module = join(theme_dir, theme_name + extsep + "py") 655 656 plugin_theme_dir = self.get_plugin_directory("theme") 657 658 # Copy the theme module. 659 660 status("Copying theme module to %s..." % plugin_theme_dir) 661 662 shutil.copy(theme_module, plugin_theme_dir) 663 664 # Copy the resources. 665 666 resources_dir = join(self.htdocs_dir, theme_name) 667 668 if not exists(resources_dir): 669 os.mkdir(resources_dir) 670 671 status("Copying theme resources to %s..." % resources_dir) 672 673 for d in ("css", "img"): 674 target_dir = join(resources_dir, d) 675 if exists(target_dir): 676 status("Replacing %s..." % target_dir) 677 shutil.rmtree(target_dir) 678 shutil.copytree(join(theme_dir, d), target_dir) 679 680 # Copy additional resources from other themes. 681 682 resources_source_dir = join(self.htdocs_dir, self.theme_master) 683 target_dir = join(resources_dir, "css") 684 685 status("Copying resources from %s..." % resources_source_dir) 686 687 for css_file in self.extra_theme_css_files: 688 css_file_path = join(resources_source_dir, "css", css_file) 689 if exists(css_file_path): 690 shutil.copy(css_file_path, target_dir) 691 692 note("Don't forget to add theme resources for extensions for this theme.") 693 note("Don't forget to edit theme stylesheets for any extensions.") 694 695 def install_extension_package(self, extension_dir): 696 697 "Install any libraries from 'extension_dir' using a setup script." 698 699 this_dir = os.getcwd() 700 os.chdir(extension_dir) 701 os.system("python setup.py install --prefix=%s" % self.prefix) 702 os.chdir(this_dir) 703 704 def install_plugins(self, plugins_dir, plugin_type): 705 706 """ 707 Install Wiki actions provided in the given 'plugins_dir' of the 708 specified 'plugin_type'. 709 """ 710 711 plugin_target_dir = self.get_plugin_directory(plugin_type) 712 713 # Copy the modules. 714 715 status("Copying %s modules to %s..." % (plugin_type, plugin_target_dir)) 716 717 for module in glob(join(plugins_dir, "*%spy" % extsep)): 718 shutil.copy(module, plugin_target_dir) 719 720 def install_actions(self, actions_dir): 721 722 "Install Wiki actions provided in the given 'actions_dir'." 723 724 self.install_plugins(actions_dir, "action") 725 726 def install_macros(self, macros_dir): 727 728 "Install Wiki macros provided in the given 'macros_dir'." 729 730 self.install_plugins(macros_dir, "macro") 731 732 def install_theme_resources(self, theme_resources_dir, theme_name=None): 733 734 """ 735 Install theme resources provided in the given 'theme_resources_dir'. If 736 a specific 'theme_name' is given, only that theme will be given the 737 specified resources. 738 """ 739 740 for theme_name, theme_dir in self.get_theme_directories(theme_name): 741 742 # Copy the resources. 743 744 copied = 0 745 746 for d in ("css", "img"): 747 source_dir = join(theme_resources_dir, d) 748 target_dir = join(theme_dir, d) 749 750 if not exists(target_dir): 751 continue 752 753 for resource in glob(join(source_dir, "*%s*" % extsep)): 754 shutil.copy(resource, target_dir) 755 copied = 1 756 757 if copied: 758 status("Copied theme resources into %s..." % theme_dir) 759 760 note("Don't forget to edit theme stylesheets for any extensions.") 761 762 def edit_theme_stylesheet(self, theme_stylesheet, imported_stylesheet, action="ensure", theme_name=None): 763 764 """ 765 Edit the given 'theme_stylesheet', ensuring (or removing) a reference to 766 the 'imported_stylesheet' according to the given 'action' (optional, 767 defaulting to "ensure"). If a specific 'theme_name' is given, only that 768 theme will be affected. 769 """ 770 771 if action == "ensure": 772 ensure = 1 773 elif action == "remove": 774 ensure = 0 775 else: 776 error("Action %s not valid: it must be given as either 'ensure' or 'remove'." % action) 777 return 778 779 for theme_name, theme_dir in self.get_theme_directories(theme_name): 780 781 # Locate the resources. 782 783 css_dir = join(theme_dir, "css") 784 785 if not exists(css_dir): 786 continue 787 788 theme_stylesheet_filename = join(css_dir, theme_stylesheet) 789 imported_stylesheet_filename = join(css_dir, imported_stylesheet) 790 791 if not exists(theme_stylesheet_filename): 792 error("Stylesheet %s not defined in theme %s." % (theme_stylesheet, theme_name)) 793 continue 794 795 if not exists(imported_stylesheet_filename): 796 error("Stylesheet %s not defined in theme %s." % (imported_stylesheet, theme_name)) 797 continue 798 799 # Edit the resources. 800 801 s = readfile(theme_stylesheet_filename) 802 after_point = 0 803 804 for stylesheet_import in css_import_stylesheet.finditer(s): 805 before, filename, after = stylesheet_import.groups() 806 before_point, after_point = stylesheet_import.span() 807 808 # Test the import for a reference to the requested imported 809 # stylesheet. 810 811 if filename == imported_stylesheet: 812 if ensure: 813 break 814 else: 815 if s[after_point:after_point+1] == "\n": 816 after_point += 1 817 s = "%s%s" % (s[:before_point], s[after_point:]) 818 819 status("Removing %s from %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name)) 820 writefile(theme_stylesheet_filename, s) 821 break 822 823 # Where no import references the imported stylesheet, insert a 824 # reference into the theme stylesheet. 825 826 else: 827 if ensure: 828 829 # Assume that the stylesheet can follow other imports. 830 831 if s[after_point:after_point+1] == "\n": 832 after_point += 1 833 s = "%s%s\n%s" % (s[:after_point], '@import "%s";' % imported_stylesheet, s[after_point:]) 834 835 status("Adding %s to %s in theme %s..." % (imported_stylesheet, theme_stylesheet, theme_name)) 836 writefile(theme_stylesheet_filename, s) 837 838 # Command line option syntax. 839 840 syntax_description = "<argument> ... --method=METHOD [ <method-argument> ... ]" 841 842 # Main program. 843 844 if __name__ == "__main__": 845 import sys, cmdsyntax 846 847 # Check the command syntax. 848 849 syntax = cmdsyntax.Syntax(syntax_description) 850 try: 851 matches = syntax.get_args(sys.argv[1:]) 852 args = matches[0] 853 854 # Obtain as many arguments as needed for the configuration. 855 856 arguments = args["argument"] 857 method_arguments = args.get("method-argument", []) 858 859 # Attempt to initialise the configuration. 860 861 installation = Installation(*arguments) 862 863 except (IndexError, TypeError): 864 print "Syntax:" 865 print sys.argv[0], syntax_description 866 print 867 print "Arguments:" 868 print Installation.__init__.__doc__ 869 print 870 print "Methods:" 871 print 872 for method_name in Installation.method_names: 873 doc = getattr(Installation, method_name).__doc__.strip() 874 print "%-30s%-s" % (method_name, format(doc, 30)) 875 print 876 sys.exit(1) 877 878 # Obtain and perform the method. 879 880 if args.has_key("method"): 881 method = getattr(installation, args["method"]) 882 else: 883 method = installation.setup 884 885 method(*method_arguments) 886 887 # vim: tabstop=4 expandtab shiftwidth=4