| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2003-2011 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 | |
|---|
| 23 | def N_(string): |
|---|
| 24 | """No-op translation marker, inlined here to avoid importing from |
|---|
| 25 | `trac.util`. |
|---|
| 26 | """ |
|---|
| 27 | return string |
|---|
| 28 | |
|---|
| 29 | |
|---|
| 30 | class TracError(Exception): |
|---|
| 31 | """Exception base class for errors in Trac.""" |
|---|
| 32 | |
|---|
| 33 | title = N_('Trac Error') |
|---|
| 34 | |
|---|
| 35 | def __init__(self, message, title=None, show_traceback=False): |
|---|
| 36 | """If message is a genshi.builder.tag object, everything up to |
|---|
| 37 | the first <p> will be displayed in the red box, and everything |
|---|
| 38 | after will be displayed below the red box. If title is given, |
|---|
| 39 | it will be displayed as the large header above the error |
|---|
| 40 | message. |
|---|
| 41 | """ |
|---|
| 42 | from trac.util.translation import gettext |
|---|
| 43 | Exception.__init__(self, message) |
|---|
| 44 | self._message = message |
|---|
| 45 | self.title = title or gettext(self.title) |
|---|
| 46 | self.show_traceback = show_traceback |
|---|
| 47 | |
|---|
| 48 | message = property(lambda self: self._message, |
|---|
| 49 | lambda self, v: setattr(self, '_message', v)) |
|---|
| 50 | |
|---|
| 51 | def __unicode__(self): |
|---|
| 52 | return unicode(self.message) |
|---|
| 53 | |
|---|
| 54 | |
|---|
| 55 | class Interface(object): |
|---|
| 56 | """Marker base class for extension point interfaces.""" |
|---|
| 57 | |
|---|
| 58 | |
|---|
| 59 | class ExtensionPoint(property): |
|---|
| 60 | """Marker class for extension points in components.""" |
|---|
| 61 | |
|---|
| 62 | def __init__(self, interface): |
|---|
| 63 | """Create the extension point. |
|---|
| 64 | |
|---|
| 65 | :param interface: the `Interface` subclass that defines the |
|---|
| 66 | protocol for the extension point |
|---|
| 67 | """ |
|---|
| 68 | property.__init__(self, self.extensions) |
|---|
| 69 | self.interface = interface |
|---|
| 70 | self.__doc__ = ("List of components that implement `~%s.%s`" % |
|---|
| 71 | (self.interface.__module__, self.interface.__name__)) |
|---|
| 72 | |
|---|
| 73 | def extensions(self, component): |
|---|
| 74 | """Return a list of components that declare to implement the |
|---|
| 75 | extension point interface. |
|---|
| 76 | """ |
|---|
| 77 | classes = ComponentMeta._registry.get(self.interface, ()) |
|---|
| 78 | components = [component.compmgr[cls] for cls in classes] |
|---|
| 79 | return [c for c in components if c] |
|---|
| 80 | |
|---|
| 81 | def __repr__(self): |
|---|
| 82 | """Return a textual representation of the extension point.""" |
|---|
| 83 | return '<ExtensionPoint %s>' % self.interface.__name__ |
|---|
| 84 | |
|---|
| 85 | |
|---|
| 86 | class ComponentMeta(type): |
|---|
| 87 | """Meta class for components. |
|---|
| 88 | |
|---|
| 89 | Takes care of component and extension point registration. |
|---|
| 90 | """ |
|---|
| 91 | _components = [] |
|---|
| 92 | _registry = {} |
|---|
| 93 | |
|---|
| 94 | def __new__(mcs, name, bases, d): |
|---|
| 95 | """Create the component class.""" |
|---|
| 96 | |
|---|
| 97 | new_class = type.__new__(mcs, name, bases, d) |
|---|
| 98 | if name == 'Component': |
|---|
| 99 | # Don't put the Component base class in the registry |
|---|
| 100 | return new_class |
|---|
| 101 | |
|---|
| 102 | if d.get('abstract'): |
|---|
| 103 | # Don't put abstract component classes in the registry |
|---|
| 104 | return new_class |
|---|
| 105 | |
|---|
| 106 | ComponentMeta._components.append(new_class) |
|---|
| 107 | registry = ComponentMeta._registry |
|---|
| 108 | for cls in new_class.__mro__: |
|---|
| 109 | for interface in cls.__dict__.get('_implements', ()): |
|---|
| 110 | classes = registry.setdefault(interface, []) |
|---|
| 111 | if new_class not in classes: |
|---|
| 112 | classes.append(new_class) |
|---|
| 113 | |
|---|
| 114 | return new_class |
|---|
| 115 | |
|---|
| 116 | def __call__(cls, *args, **kwargs): |
|---|
| 117 | """Return an existing instance of the component if it has |
|---|
| 118 | already been activated, otherwise create a new instance. |
|---|
| 119 | """ |
|---|
| 120 | # If this component is also the component manager, just invoke that |
|---|
| 121 | if issubclass(cls, ComponentManager): |
|---|
| 122 | self = cls.__new__(cls) |
|---|
| 123 | self.compmgr = self |
|---|
| 124 | self.__init__(*args, **kwargs) |
|---|
| 125 | return self |
|---|
| 126 | |
|---|
| 127 | # The normal case where the component is not also the component manager |
|---|
| 128 | compmgr = args[0] |
|---|
| 129 | self = compmgr.components.get(cls) |
|---|
| 130 | # Note that this check is racy, we intentionally don't use a |
|---|
| 131 | # lock in order to keep things simple and avoid the risk of |
|---|
| 132 | # deadlocks, as the impact of having temporarily two (or more) |
|---|
| 133 | # instances for a given `cls` is negligible. |
|---|
| 134 | if self is None: |
|---|
| 135 | self = cls.__new__(cls) |
|---|
| 136 | self.compmgr = compmgr |
|---|
| 137 | compmgr.component_activated(self) |
|---|
| 138 | self.__init__() |
|---|
| 139 | # Only register the instance once it is fully initialized (#9418) |
|---|
| 140 | compmgr.components[cls] = self |
|---|
| 141 | return self |
|---|
| 142 | |
|---|
| 143 | |
|---|
| 144 | class Component(object): |
|---|
| 145 | """Base class for components. |
|---|
| 146 | |
|---|
| 147 | Every component can declare what extension points it provides, as |
|---|
| 148 | well as what extension points of other components it extends. |
|---|
| 149 | """ |
|---|
| 150 | __metaclass__ = ComponentMeta |
|---|
| 151 | |
|---|
| 152 | @staticmethod |
|---|
| 153 | def implements(*interfaces): |
|---|
| 154 | """Can be used in the class definition of `Component` |
|---|
| 155 | subclasses to declare the extension points that are extended. |
|---|
| 156 | """ |
|---|
| 157 | import sys |
|---|
| 158 | |
|---|
| 159 | frame = sys._getframe(1) |
|---|
| 160 | locals_ = frame.f_locals |
|---|
| 161 | |
|---|
| 162 | # Some sanity checks |
|---|
| 163 | assert locals_ is not frame.f_globals and '__module__' in locals_, \ |
|---|
| 164 | 'implements() can only be used in a class definition' |
|---|
| 165 | |
|---|
| 166 | locals_.setdefault('_implements', []).extend(interfaces) |
|---|
| 167 | |
|---|
| 168 | |
|---|
| 169 | implements = Component.implements |
|---|
| 170 | |
|---|
| 171 | |
|---|
| 172 | class ComponentManager(object): |
|---|
| 173 | """The component manager keeps a pool of active components.""" |
|---|
| 174 | |
|---|
| 175 | def __init__(self): |
|---|
| 176 | """Initialize the component manager.""" |
|---|
| 177 | self.components = {} |
|---|
| 178 | self.enabled = {} |
|---|
| 179 | if isinstance(self, Component): |
|---|
| 180 | self.components[self.__class__] = self |
|---|
| 181 | |
|---|
| 182 | def __contains__(self, cls): |
|---|
| 183 | """Return wether the given class is in the list of active |
|---|
| 184 | components.""" |
|---|
| 185 | return cls in self.components |
|---|
| 186 | |
|---|
| 187 | def __getitem__(self, cls): |
|---|
| 188 | """Activate the component instance for the given class, or |
|---|
| 189 | return the existing instance if the component has already been |
|---|
| 190 | activated. |
|---|
| 191 | """ |
|---|
| 192 | if not self.is_enabled(cls): |
|---|
| 193 | return None |
|---|
| 194 | component = self.components.get(cls) |
|---|
| 195 | if not component: |
|---|
| 196 | if cls not in ComponentMeta._components: |
|---|
| 197 | raise TracError('Component "%s" not registered' % cls.__name__) |
|---|
| 198 | try: |
|---|
| 199 | component = cls(self) |
|---|
| 200 | except TypeError, e: |
|---|
| 201 | raise TracError('Unable to instantiate component %r (%s)' % |
|---|
| 202 | (cls, e)) |
|---|
| 203 | return component |
|---|
| 204 | |
|---|
| 205 | def is_enabled(self, cls): |
|---|
| 206 | """Return whether the given component class is enabled.""" |
|---|
| 207 | if cls not in self.enabled: |
|---|
| 208 | self.enabled[cls] = self.is_component_enabled(cls) |
|---|
| 209 | return self.enabled[cls] |
|---|
| 210 | |
|---|
| 211 | def disable_component(self, component): |
|---|
| 212 | """Force a component to be disabled. |
|---|
| 213 | |
|---|
| 214 | :param component: can be a class or an instance. |
|---|
| 215 | """ |
|---|
| 216 | if not isinstance(component, type): |
|---|
| 217 | component = component.__class__ |
|---|
| 218 | self.enabled[component] = False |
|---|
| 219 | self.components[component] = None |
|---|
| 220 | |
|---|
| 221 | def component_activated(self, component): |
|---|
| 222 | """Can be overridden by sub-classes so that special |
|---|
| 223 | initialization for components can be provided. |
|---|
| 224 | """ |
|---|
| 225 | |
|---|
| 226 | def is_component_enabled(self, cls): |
|---|
| 227 | """Can be overridden by sub-classes to veto the activation of |
|---|
| 228 | a component. |
|---|
| 229 | |
|---|
| 230 | If this method returns `False`, the component was disabled |
|---|
| 231 | explicitly. If it returns `None`, the component was neither |
|---|
| 232 | enabled nor disabled explicitly. In both cases, the component |
|---|
| 233 | with the given class will not be available. |
|---|
| 234 | """ |
|---|
| 235 | return True |
|---|