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