Commits

Anonymous committed 34b6882

* Change the configuration for disabling components: instead of a `[disabled_components]` section that lists all components to be disabled, you can now enable/disable components in a `[components]` section. This also means that plugins need to be explicitly enabled for every environment. Closes #2019.
* Support a global `trac.ini` configuration file (by default in `$prefix/share/trac/conf`). Closes #1051. Thanks to Alec Thomas for the patch.

Comments (0)

Files changed (4)

scripts/trac-postinstall.py

                                  'site-packages')
     prefix = sysconfig.get_config_var('prefix')
 
+    conf_dir = os.path.join(prefix, 'share', 'trac', 'conf')
     templates_dir = os.path.join(prefix, 'share', 'trac', 'templates')
     htdocs_dir = os.path.join(prefix, 'share', 'trac', 'htdocs')
     wiki_dir = os.path.join(prefix, 'share', 'trac', 'wiki-default')
 # PLEASE DO NOT EDIT THIS FILE!
 # This file was autogenerated when installing Trac %(version)s.
 #
+__default_conf_dir__ = %(conf)r
 __default_templates_dir__ = %(templates)r
 __default_htdocs_dir__ = %(htdocs)r
 __default_wiki_dir__ = %(wiki)r
 __default_macros_dir__ = %(macros)r
 
-""" % {'version': trac.__version__, 'templates': templates_dir,
-       'htdocs': htdocs_dir, 'wiki': wiki_dir, 'macros': macros_dir})
+""" % {'version': trac.__version__, 'conf': conf_dir,
+       'templates': templates_dir, 'htdocs': htdocs_dir,
+       'wiki': wiki_dir, 'macros': macros_dir})
     fd.close()
 
     file_created(siteconfig)
          self.siteconfig()
 
      def siteconfig(self):
+         conf_dir = os.path.join(self.prefix, 'share', 'trac', 'conf')
          templates_dir = os.path.join(self.prefix, 'share', 'trac', 'templates')
          htdocs_dir = os.path.join(self.prefix, 'share', 'trac', 'htdocs')
          wiki_dir = os.path.join(self.prefix, 'share', 'trac', 'wiki-default')
          macros_dir = os.path.join(self.prefix, 'share', 'trac', 'wiki-macros')
-         f = open(_p('trac/siteconfig.py'),'w')
+         f = open(_p('trac/siteconfig.py'), 'w')
          f.write("""
 # PLEASE DO NOT EDIT THIS FILE!
 # This file was autogenerated when installing %(trac)s %(ver)s.
 #
+__default_conf_dir__ = %(conf)r
 __default_templates_dir__ = %(templates)r
 __default_htdocs_dir__ = %(htdocs)r
 __default_wiki_dir__ = %(wiki)r
 __default_macros_dir__ = %(macros)r
 
-""" % {'trac':PACKAGE, 'ver':VERSION, 'templates':_p(templates_dir),
-       'htdocs':_p(htdocs_dir), 'wiki':_p(wiki_dir), 'macros':_p(macros_dir)})
+""" % {'trac': PACKAGE, 'ver': VERSION, 'conf': _p(conf_dir),
+       'templates': _p(templates_dir), 'htdocs': _p(htdocs_dir),
+       'wiki': _p(wiki_dir), 'macros': _p(macros_dir)})
          f.close()
 
          # Run actual install
                 mode |= 044
                 os.chmod(path, mode)
 
-
 # Our custom bdist_wininst
 import distutils.command.bdist_wininst
 from distutils.command.bdist_wininst import bdist_wininst
 from __future__ import generators
 
 from ConfigParser import ConfigParser
-import os.path
+import os
+import sys
 
 
 class Configuration:
-    """
-    Thin layer over ConfigParser from the Python standard library.
+    """Thin layer over `ConfigParser` from the Python standard library.
+
     In addition to providing some convenience methods, the class remembers
     the last modification time of the configuration file, and reparses it
     when the file has changed.
     """
 
     def __init__(self, filename):
+        self._defaults = {}
         self.filename = filename
         self.parser = ConfigParser()
-        self.__defaults = {}
-        self.__lastmtime = 0
+        self._lastmtime = 0
+        self.site_filename = os.path.join(default_dir('conf'), 'trac.ini')
+        self.site_parser = ConfigParser()
+        self._lastsitemtime = 0
         self.parse_if_needed()
 
     def get(self, section, name, default=None):
         if not self.parser.has_option(section, name):
             if default is None:
-                return self.__defaults.get((section, name), '')
+                return self._defaults.get((section, name), '')
             return default
         return self.parser.get(section, name)
 
     def setdefault(self, section, name, value):
-        self.__defaults[(section, name)] = value
+        if (section, name) not in self._defaults:
+            self._defaults[(section, name)] = value
 
     def set(self, section, name, value):
-        """
-        Changes a config value, these changes are _not_ persistent unless saved
-        with `save()`.
+        """Change a configuration value.
+        
+        These changes are not persistent unless saved with `save()`.
         """
         if not self.parser.has_section(section):
             self.parser.add_section(section)
         return self.parser.set(section, name, value)
 
     def options(self, section):
-        if not self.parser.has_section(section):
-            return []
-        try:
-            return self.parser.items(section)
-        except AttributeError:
-            options = []
+        options = []
+        if self.parser.has_section(section):
             for option in self.parser.options(section):
                 options.append((option, self.parser.get(section, option)))
-            return options
+        for option, value in self._defaults.iteritems():
+            if option[0] == section:
+                if not [exists for exists in options if exists[0] == option[1]]:
+                    options.append((option[1], value))
+        return options
 
     def __contains__(self, name):
         return self.parser.has_section(name)
     def save(self):
         if not self.filename:
             return
-        self.parser.write(open(self.filename, 'w'))
+        fileobj = file(self.filename, 'w')
+        try:
+            self.parser.write(fileobj)
+        finally:
+            fileobj.close()
 
     def parse_if_needed(self):
+        # Merge global configuration option into _defaults
+        if os.path.isfile(self.site_filename):
+            modtime = os.path.getmtime(self.site_filename)
+            if modtime > self._lastsitemtime:
+                self.site_parser.read(self.site_filename)
+                for section in self.site_parser.sections():
+                    for option in self.site_parser.options(section):
+                        value = self.site_parser.get(section, option)
+                        self._defaults[(section, option)] = value
+                self._lastsitemtime = modtime
+
         if not self.filename:
             return
         modtime = os.path.getmtime(self.filename)
-        if modtime > self.__lastmtime:
-            self.parser.readfp(open(self.filename))
-            self.__lastmtime = modtime
+        if modtime > self._lastmtime:
+            self.parser.read(self.filename)
+            self._lastmtime = modtime
 
 
 def default_dir(name):
     except ImportError:
         # This is not a regular install with a generated siteconfig.py file,
         # so try to figure out the directory based on common setups
-        import os.path, sys
         special_dirs = {'wiki': 'wiki-default', 'macros': 'wiki-macros'}
         dirname = special_dirs.get(name, name)
 
 
 from __future__ import generators
 
+import os
+
 from trac import db, db_default, util
 from trac.config import Configuration
 from trac.core import Component, ComponentManager, implements, Interface, \
                       ExtensionPoint, TracError
 
-import os
-import os.path
-
 __all__ = ['Environment', 'IEnvironmentSetupParticipant', 'open_environment']
 
 
         """Called when a new Trac environment is created."""
 
     def environment_needs_upgrade(db):
-        """FIXME"""
+        """Called when Trac checks whether the environment needs to be upgraded.
+        
+        Should return `True` if this participant needs an upgrade to be
+        performed, `False` otherwise.
+        """
 
     def upgrade_environment(db):
-        """FIXME"""
+        """Actually perform an environment upgrade.
+        
+        Implementations of this method should not commit any database
+        transactions. This is done implicitly after all participants have
+        performed the upgrades they need without an error being raised.
+        """
 
 
 class Environment(Component, ComponentManager):
         This is called by the `ComponentManager` base class when a component is
         about to be activated. If this method returns false, the component does
         not get activated."""
-        component_name = (cls.__module__ + '.' + cls.__name__).lower()
-        for name,value in self.config.options('disabled_components'):
-            if value in util.TRUE and component_name.startswith(name):
-                return False
-        return True
+        if not isinstance(cls, (str, unicode)):
+            component_name = (cls.__module__ + '.' + cls.__name__).lower()
+        else:
+            component_name = cls
+
+        rules = [(name.lower(), value.lower() in ('enabled', 'on'))
+                 for name, value in self.config.options('components')]
+        rules.sort(lambda a, b: -cmp(len(a[0]), len(b[0])))
+
+        for pattern, enabled in rules:
+            if component_name == pattern or pattern.endswith('*') \
+                    and component_name.startswith(pattern[:-1]):
+                return enabled
+
+        # By default, all components in the trac package are enabled
+        return component_name.startswith('trac.')
 
     def verify(self):
         """Verify that the provided path points to a valid Trac environment
         os.mkdir(os.path.join(self.path, 'conf'))
         _create_file(os.path.join(self.path, 'conf', 'trac.ini'))
         self.load_config()
-        for section,name,value in db_default.default_config:
+        for section, name, value in db_default.default_config:
             self.config.set(section, name, value)
         self.config.set('trac', 'database', db_str)
         self.config.save()
     def load_config(self):
         """Load the configuration file."""
         self.config = Configuration(os.path.join(self.path, 'conf', 'trac.ini'))
-        for section,name,value in db_default.default_config:
+        for section, name, value in db_default.default_config:
             self.config.setdefault(section, name, value)
 
     def get_templates_dir(self):