Edgewall Software

root/trunk/trac/core.py

Revision 7722, 8.2 KB (checked in by rblank, 4 days ago)

0.12dev: Merged [7718:7721] from 0.11-stable.

  • Property svn:eol-style set to native
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2003-2008 Edgewall Software
4# Copyright (C) 2003-2004 Jonas Borgström <jonas@edgewall.com>
5# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
6# All rights reserved.
7#
8# This software is licensed as described in the file COPYING, which
9# you should have received as part of this distribution. The terms
10# are also available at http://trac.edgewall.org/wiki/TracLicense.
11#
12# This software consists of voluntary contributions made by many
13# individuals. For the exact contribution history, see the revision
14# history and logs, available at http://trac.edgewall.org/log/.
15#
16# Author: Jonas Borgström <jonas@edgewall.com>
17#         Christopher Lenz <cmlenz@gmx.de>
18
19__all__ = ['Component', 'ExtensionPoint', 'implements', 'Interface',
20           'TracError']
21
22
23class TracError(Exception):
24    """Exception base class for errors in Trac."""
25
26    title = 'Trac Error'
27   
28    def __init__(self, message, title=None, show_traceback=False):
29        """If message is a genshi.builder.tag object, everything up to the
30        first <p> will be displayed in the red box, and everything after will
31        be displayed below the red box.
32        If title is given, it will be displayed as the large header above the
33        error message.
34        """
35        Exception.__init__(self, message)
36        self.message = message
37        if title:
38            self.title = title
39        self.show_traceback = show_traceback
40
41    def __unicode__(self):
42        return unicode(self.message)
43
44class Interface(object):
45    """Marker base class for extension point interfaces."""
46
47
48class ExtensionPoint(property):
49    """Marker class for extension points in components."""
50
51    def __init__(self, interface):
52        """Create the extension point.
53       
54        @param interface: the `Interface` subclass that defines the protocol
55            for the extension point
56        """
57        property.__init__(self, self.extensions)
58        self.interface = interface
59        self.__doc__ = 'List of components that implement `%s`' % \
60                       self.interface.__name__
61
62    def extensions(self, component):
63        """Return a list of components that declare to implement the extension
64        point interface.
65        """
66        extensions = ComponentMeta._registry.get(self.interface, [])
67        return filter(None, [component.compmgr[cls] for cls in extensions])
68
69    def __repr__(self):
70        """Return a textual representation of the extension point."""
71        return '<ExtensionPoint %s>' % self.interface.__name__
72
73
74class ComponentMeta(type):
75    """Meta class for components.
76   
77    Takes care of component and extension point registration.
78    """
79    _components = []
80    _registry = {}
81
82    def __new__(cls, name, bases, d):
83        """Create the component class."""
84
85        new_class = type.__new__(cls, name, bases, d)
86        if name == 'Component':
87            # Don't put the Component base class in the registry
88            return new_class
89
90        # Only override __init__ for Components not inheriting ComponentManager
91        if True not in [issubclass(x, ComponentManager) for x in bases]:
92            # Allow components to have a no-argument initializer so that
93            # they don't need to worry about accepting the component manager
94            # as argument and invoking the super-class initializer
95            init = d.get('__init__')
96            if not init:
97                # Because we're replacing the initializer, we need to make sure
98                # that any inherited initializers are also called.
99                for init in [b.__init__._original for b in new_class.mro()
100                             if issubclass(b, Component)
101                             and '__init__' in b.__dict__]:
102                    break
103            def maybe_init(self, compmgr, init=init, cls=new_class):
104                if cls not in compmgr.components:
105                    compmgr.components[cls] = self
106                    if init:
107                        try:
108                            init(self)
109                        except:
110                            del compmgr.components[cls]
111                            raise
112            maybe_init._original = init
113            new_class.__init__ = maybe_init
114
115        if d.get('abstract'):
116            # Don't put abstract component classes in the registry
117            return new_class
118
119        ComponentMeta._components.append(new_class)
120        registry = ComponentMeta._registry
121        for interface in d.get('_implements', []):
122            registry.setdefault(interface, []).append(new_class)
123        for base in [base for base in bases if hasattr(base, '_implements')]:
124            for interface in base._implements:
125                registry.setdefault(interface, []).append(new_class)
126
127        return new_class
128
129
130class Component(object):
131    """Base class for components.
132
133    Every component can declare what extension points it provides, as well as
134    what extension points of other components it extends.
135    """
136    __metaclass__ = ComponentMeta
137
138    def __new__(cls, *args, **kwargs):
139        """Return an existing instance of the component if it has already been
140        activated, otherwise create a new instance.
141        """
142        # If this component is also the component manager, just invoke that
143        if issubclass(cls, ComponentManager):
144            self = super(Component, cls).__new__(cls)
145            self.compmgr = self
146            return self
147
148        # The normal case where the component is not also the component manager
149        compmgr = args[0]
150        self = compmgr.components.get(cls)
151        if self is None:
152            self = super(Component, cls).__new__(cls)
153            self.compmgr = compmgr
154            compmgr.component_activated(self)
155        return self
156
157    @staticmethod
158    def implements(*interfaces):
159        """Can be used in the class definiton of `Component` subclasses to
160        declare the extension points that are extended.
161        """
162        import sys
163
164        frame = sys._getframe(1)
165        locals_ = frame.f_locals
166
167        # Some sanity checks
168        assert locals_ is not frame.f_globals and '__module__' in locals_, \
169               'implements() can only be used in a class definition'
170
171        locals_.setdefault('_implements', []).extend(interfaces)
172
173
174implements = Component.implements
175
176
177class ComponentManager(object):
178    """The component manager keeps a pool of active components."""
179
180    def __init__(self):
181        """Initialize the component manager."""
182        self.components = {}
183        self.enabled = {}
184        if isinstance(self, Component):
185            self.components[self.__class__] = self
186
187    def __contains__(self, cls):
188        """Return wether the given class is in the list of active components."""
189        return cls in self.components
190
191    def __getitem__(self, cls):
192        """Activate the component instance for the given class, or return the
193        existing the instance if the component has already been activated.
194        """
195        if cls not in self.enabled:
196            self.enabled[cls] = self.is_component_enabled(cls)
197        if not self.enabled[cls]:
198            return None
199        component = self.components.get(cls)
200        if not component:
201            if cls not in ComponentMeta._components:
202                raise TracError('Component "%s" not registered' % cls.__name__)
203            try:
204                component = cls(self)
205            except TypeError, e:
206                raise TracError('Unable to instantiate component %r (%s)' %
207                                (cls, e))
208        return component
209
210    def disable_component(self, component):
211        """Force a component to be disabled.
212       
213        The argument `component` can be a class or an instance.
214        """
215        if not isinstance(component, type):
216            component = component.__class__
217        self.enabled[component] = False
218        self.components[component] = None
219
220    def component_activated(self, component):
221        """Can be overridden by sub-classes so that special initialization for
222        components can be provided.
223        """
224
225    def is_component_enabled(self, cls):
226        """Can be overridden by sub-classes to veto the activation of a
227        component.
228
229        If this method returns False, the component with the given class will
230        not be available.
231        """
232        return True
Note: See TracBrowser for help on using the browser.