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