| 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 |
def implements(*interfaces): |
|---|
| 158 |
"""Can be used in the class definiton of `Component` subclasses to |
|---|
| 159 |
declare the extension points that are extended. |
|---|
| 160 |
""" |
|---|
| 161 |
import sys |
|---|
| 162 |
|
|---|
| 163 |
frame = sys._getframe(1) |
|---|
| 164 |
locals_ = frame.f_locals |
|---|
| 165 |
|
|---|
| 166 |
# Some sanity checks |
|---|
| 167 |
assert locals_ is not frame.f_globals and '__module__' in locals_, \ |
|---|
| 168 |
'implements() can only be used in a class definition' |
|---|
| 169 |
|
|---|
| 170 |
locals_.setdefault('_implements', []).extend(interfaces) |
|---|
| 171 |
implements = staticmethod(implements) |
|---|
| 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 component_activated(self, component): |
|---|
| 211 |
"""Can be overridden by sub-classes so that special initialization for |
|---|
| 212 |
components can be provided. |
|---|
| 213 |
""" |
|---|
| 214 |
|
|---|
| 215 |
def is_component_enabled(self, cls): |
|---|
| 216 |
"""Can be overridden by sub-classes to veto the activation of a |
|---|
| 217 |
component. |
|---|
| 218 |
|
|---|
| 219 |
If this method returns False, the component with the given class will |
|---|
| 220 |
not be available. |
|---|
| 221 |
""" |
|---|
| 222 |
return True |
|---|