| 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 | |
|---|
| 23 | class 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 | |
|---|
| 44 | class Interface(object): |
|---|
| 45 | """Marker base class for extension point interfaces.""" |
|---|
| 46 | |
|---|
| 47 | |
|---|
| 48 | class 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 | |
|---|
| 74 | class 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 | |
|---|
| 130 | class 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 | |
|---|
| 174 | implements = Component.implements |
|---|
| 175 | |
|---|
| 176 | |
|---|
| 177 | class 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 |
|---|