~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/hooks.py

  • Committer: Vincent Ladeuil
  • Date: 2010-06-23 08:19:28 UTC
  • mfrom: (5317 +trunk)
  • mto: (5247.1.11 first-try)
  • mto: This revision was merged to the branch mainline in revision 5326.
  • Revision ID: v.ladeuil+lp@free.fr-20100623081928-z9q18q30oo5as831
Merge bzr.dev into cleanup

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
 
18
18
"""Support for plugin hooking logic."""
19
 
 
20
 
from bzrlib import (
21
 
    registry,
22
 
    symbol_versioning,
23
 
    )
 
19
from bzrlib import registry
24
20
from bzrlib.lazy_import import lazy_import
25
21
lazy_import(globals(), """
26
22
import textwrap
27
23
 
28
24
from bzrlib import (
29
 
    _format_version_tuple,
30
 
    errors,
31
 
    pyutils,
32
 
    )
33
 
from bzrlib.i18n import gettext
 
25
        _format_version_tuple,
 
26
        errors,
 
27
        )
 
28
from bzrlib.help_topics import help_as_plain_text
34
29
""")
35
30
 
36
31
 
37
 
class KnownHooksRegistry(registry.Registry):
38
 
    # known_hooks registry contains
39
 
    # tuple of (module, member name) which is the hook point
40
 
    # module where the specific hooks are defined
41
 
    # callable to get the empty specific Hooks for that attribute
42
 
 
43
 
    def register_lazy_hook(self, hook_module_name, hook_member_name,
44
 
            hook_factory_member_name):
45
 
        self.register_lazy((hook_module_name, hook_member_name),
46
 
            hook_module_name, hook_factory_member_name)
47
 
 
48
 
    def iter_parent_objects(self):
49
 
        """Yield (hook_key, (parent_object, attr)) tuples for every registered
50
 
        hook, where 'parent_object' is the object that holds the hook
51
 
        instance.
52
 
 
53
 
        This is useful for resetting/restoring all the hooks to a known state,
54
 
        as is done in bzrlib.tests.TestCase._clear_hooks.
55
 
        """
56
 
        for key in self.keys():
57
 
            yield key, self.key_to_parent_and_attribute(key)
58
 
 
59
 
    def key_to_parent_and_attribute(self, (module_name, member_name)):
60
 
        """Convert a known_hooks key to a (parent_obj, attr) pair.
61
 
 
62
 
        :param key: A tuple (module_name, member_name) as found in the keys of
63
 
            the known_hooks registry.
64
 
        :return: The parent_object of the hook and the name of the attribute on
65
 
            that parent object where the hook is kept.
66
 
        """
67
 
        parent_mod, parent_member, attr = pyutils.calc_parent_name(module_name,
68
 
            member_name)
69
 
        return pyutils.get_named_object(parent_mod, parent_member), attr
70
 
 
71
 
 
72
 
_builtin_known_hooks = (
73
 
    ('bzrlib.branch', 'Branch.hooks', 'BranchHooks'),
74
 
    ('bzrlib.controldir', 'ControlDir.hooks', 'ControlDirHooks'),
75
 
    ('bzrlib.commands', 'Command.hooks', 'CommandHooks'),
76
 
    ('bzrlib.config', 'ConfigHooks', '_ConfigHooks'),
77
 
    ('bzrlib.info', 'hooks', 'InfoHooks'),
78
 
    ('bzrlib.lock', 'Lock.hooks', 'LockHooks'),
79
 
    ('bzrlib.merge', 'Merger.hooks', 'MergeHooks'),
80
 
    ('bzrlib.msgeditor', 'hooks', 'MessageEditorHooks'),
81
 
    ('bzrlib.mutabletree', 'MutableTree.hooks', 'MutableTreeHooks'),
82
 
    ('bzrlib.smart.client', '_SmartClient.hooks', 'SmartClientHooks'),
83
 
    ('bzrlib.smart.server', 'SmartTCPServer.hooks', 'SmartServerHooks'),
84
 
    ('bzrlib.status', 'hooks', 'StatusHooks'),
85
 
    ('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks',
86
 
        'RioVersionInfoBuilderHooks'),
87
 
    ('bzrlib.merge_directive', 'BaseMergeDirective.hooks',
88
 
        'MergeDirectiveHooks'),
89
 
    )
90
 
 
91
 
known_hooks = KnownHooksRegistry()
92
 
for (_hook_module, _hook_attribute, _hook_class) in _builtin_known_hooks:
93
 
    known_hooks.register_lazy_hook(_hook_module, _hook_attribute, _hook_class)
94
 
del _builtin_known_hooks, _hook_module, _hook_attribute, _hook_class
 
32
known_hooks = registry.Registry()
 
33
# known_hooks registry contains
 
34
# tuple of (module, member name) which is the hook point
 
35
# module where the specific hooks are defined
 
36
# callable to get the empty specific Hooks for that attribute
 
37
known_hooks.register_lazy(('bzrlib.branch', 'Branch.hooks'), 'bzrlib.branch',
 
38
    'BranchHooks')
 
39
known_hooks.register_lazy(('bzrlib.bzrdir', 'BzrDir.hooks'), 'bzrlib.bzrdir',
 
40
    'BzrDirHooks')
 
41
known_hooks.register_lazy(('bzrlib.commands', 'Command.hooks'),
 
42
    'bzrlib.commands', 'CommandHooks')
 
43
known_hooks.register_lazy(('bzrlib.info', 'hooks'),
 
44
    'bzrlib.info', 'InfoHooks')
 
45
known_hooks.register_lazy(('bzrlib.lock', 'Lock.hooks'), 'bzrlib.lock',
 
46
    'LockHooks')
 
47
known_hooks.register_lazy(('bzrlib.merge', 'Merger.hooks'), 'bzrlib.merge',
 
48
    'MergeHooks')
 
49
known_hooks.register_lazy(('bzrlib.msgeditor', 'hooks'), 'bzrlib.msgeditor',
 
50
    'MessageEditorHooks')
 
51
known_hooks.register_lazy(('bzrlib.mutabletree', 'MutableTree.hooks'),
 
52
    'bzrlib.mutabletree', 'MutableTreeHooks')
 
53
known_hooks.register_lazy(('bzrlib.smart.client', '_SmartClient.hooks'),
 
54
    'bzrlib.smart.client', 'SmartClientHooks')
 
55
known_hooks.register_lazy(('bzrlib.smart.server', 'SmartTCPServer.hooks'),
 
56
    'bzrlib.smart.server', 'SmartServerHooks')
 
57
known_hooks.register_lazy(
 
58
    ('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks'),
 
59
    'bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilderHooks')
 
60
known_hooks.register_lazy(
 
61
    ('bzrlib.merge_directive', 'BaseMergeDirective.hooks'),
 
62
    'bzrlib.merge_directive', 'MergeDirectiveHooks')
95
63
 
96
64
 
97
65
def known_hooks_key_to_object((module_name, member_name)):
101
69
        the known_hooks registry.
102
70
    :return: The object this specifies.
103
71
    """
104
 
    return pyutils.get_named_object(module_name, member_name)
105
 
 
106
 
 
107
 
@symbol_versioning.deprecated_function(symbol_versioning.deprecated_in((2, 3)))
108
 
def known_hooks_key_to_parent_and_attribute(key):
109
 
    """See KnownHooksRegistry.key_to_parent_and_attribute."""
110
 
    return known_hooks.key_to_parent_and_attribute(key)
 
72
    return registry._LazyObjectGetter(module_name, member_name).get_obj()
 
73
 
 
74
 
 
75
def known_hooks_key_to_parent_and_attribute((module_name, member_name)):
 
76
    """Convert a known_hooks key to a object.
 
77
 
 
78
    :param key: A tuple (module_name, member_name) as found in the keys of
 
79
        the known_hooks registry.
 
80
    :return: The object this specifies.
 
81
    """
 
82
    member_list = member_name.rsplit('.', 1)
 
83
    if len(member_list) == 2:
 
84
        parent_name, attribute = member_list
 
85
    else:
 
86
        parent_name = None
 
87
        attribute = member_name
 
88
    parent = known_hooks_key_to_object((module_name, parent_name))
 
89
    return parent, attribute
111
90
 
112
91
 
113
92
class Hooks(dict):
117
96
    FOO hook is triggered.
118
97
    """
119
98
 
120
 
    def __init__(self, module=None, member_name=None):
121
 
        """Create a new hooks dictionary.
122
 
 
123
 
        :param module: The module from which this hooks dictionary should be loaded
124
 
            (used for lazy hooks)
125
 
        :param member_name: Name under which this hooks dictionary should be loaded.
126
 
            (used for lazy hooks)
127
 
        """
 
99
    def __init__(self):
128
100
        dict.__init__(self)
129
101
        self._callable_names = {}
130
 
        self._lazy_callable_names = {}
131
 
        self._module = module
132
 
        self._member_name = member_name
133
 
 
134
 
    def add_hook(self, name, doc, introduced, deprecated=None):
135
 
        """Add a hook point to this dictionary.
136
 
 
137
 
        :param name: The name of the hook, for clients to use when registering.
138
 
        :param doc: The docs for the hook.
139
 
        :param introduced: When the hook was introduced (e.g. (0, 15)).
140
 
        :param deprecated: When the hook was deprecated, None for
141
 
            not-deprecated.
142
 
        """
143
 
        if name in self:
144
 
            raise errors.DuplicateKey(name)
145
 
        if self._module:
146
 
            callbacks = _lazy_hooks.setdefault(
147
 
                (self._module, self._member_name, name), [])
148
 
        else:
149
 
            callbacks = None
150
 
        hookpoint = HookPoint(name=name, doc=doc, introduced=introduced,
151
 
                              deprecated=deprecated, callbacks=callbacks)
152
 
        self[name] = hookpoint
153
 
 
154
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4)))
 
102
 
155
103
    def create_hook(self, hook):
156
104
        """Create a hook which can have callbacks registered for it.
157
105
 
197
145
        the code names are rarely meaningful for end users and this is not
198
146
        intended for debugging.
199
147
        """
200
 
        name = self._callable_names.get(a_callable, None)
201
 
        if name is None and a_callable is not None:
202
 
            name = self._lazy_callable_names.get((a_callable.__module__,
203
 
                                                  a_callable.__name__),
204
 
                                                 None)
205
 
        if name is None:
206
 
            return 'No hook name'
207
 
        return name
208
 
 
209
 
 
210
 
    def install_named_hook_lazy(self, hook_name, callable_module,
211
 
        callable_member, name):
212
 
        """Install a_callable in to the hook hook_name lazily, and label it.
213
 
 
214
 
        :param hook_name: A hook name. See the __init__ method for the complete
215
 
            list of hooks.
216
 
        :param callable_module: Name of the module in which the callable is
217
 
            present.
218
 
        :param callable_member: Member name of the callable.
219
 
        :param name: A name to associate the callable with, to show users what
220
 
            is running.
221
 
        """
222
 
        try:
223
 
            hook = self[hook_name]
224
 
        except KeyError:
225
 
            raise errors.UnknownHook(self.__class__.__name__, hook_name)
226
 
        try:
227
 
            hook_lazy = getattr(hook, "hook_lazy")
228
 
        except AttributeError:
229
 
            raise errors.UnsupportedOperation(self.install_named_hook_lazy,
230
 
                self)
231
 
        else:
232
 
            hook_lazy(callable_module, callable_member, name)
233
 
        if name is not None:
234
 
            self.name_hook_lazy(callable_module, callable_member, name)
 
148
        return self._callable_names.get(a_callable, "No hook name")
235
149
 
236
150
    def install_named_hook(self, hook_name, a_callable, name):
237
151
        """Install a_callable in to the hook hook_name, and label it name.
238
152
 
239
 
        :param hook_name: A hook name. See the __init__ method for the complete
240
 
            list of hooks.
 
153
        :param hook_name: A hook name. See the __init__ method of BranchHooks
 
154
            for the complete list of hooks.
241
155
        :param a_callable: The callable to be invoked when the hook triggers.
242
156
            The exact signature will depend on the hook - see the __init__
243
 
            method for details on each hook.
 
157
            method of BranchHooks for details on each hook.
244
158
        :param name: A name to associate a_callable with, to show users what is
245
159
            running.
246
160
        """
256
170
        if name is not None:
257
171
            self.name_hook(a_callable, name)
258
172
 
259
 
    def uninstall_named_hook(self, hook_name, label):
260
 
        """Uninstall named hooks.
261
 
 
262
 
        :param hook_name: Hook point name
263
 
        :param label: Label of the callable to uninstall
264
 
        """
265
 
        try:
266
 
            hook = self[hook_name]
267
 
        except KeyError:
268
 
            raise errors.UnknownHook(self.__class__.__name__, hook_name)
269
 
        try:
270
 
            uninstall = getattr(hook, "uninstall")
271
 
        except AttributeError:
272
 
            raise errors.UnsupportedOperation(self.uninstall_named_hook, self)
273
 
        else:
274
 
            uninstall(label)
275
 
 
276
173
    def name_hook(self, a_callable, name):
277
174
        """Associate name with a_callable to show users what is running."""
278
175
        self._callable_names[a_callable] = name
279
176
 
280
 
    def name_hook_lazy(self, callable_module, callable_member, callable_name):
281
 
        self._lazy_callable_names[(callable_module, callable_member)]= \
282
 
            callable_name
283
 
 
284
177
 
285
178
class HookPoint(object):
286
179
    """A single hook that clients can register to be called back when it fires.
287
180
 
288
181
    :ivar name: The name of the hook.
289
 
    :ivar doc: The docs for using the hook.
290
182
    :ivar introduced: A version tuple specifying what version the hook was
291
183
        introduced in. None indicates an unknown version.
292
184
    :ivar deprecated: A version tuple specifying what version the hook was
293
185
        deprecated or superseded in. None indicates that the hook is not
294
186
        superseded or deprecated. If the hook is superseded then the doc
295
187
        should describe the recommended replacement hook to register for.
 
188
    :ivar doc: The docs for using the hook.
296
189
    """
297
190
 
298
 
    def __init__(self, name, doc, introduced, deprecated=None, callbacks=None):
 
191
    def __init__(self, name, doc, introduced, deprecated):
299
192
        """Create a HookPoint.
300
193
 
301
194
        :param name: The name of the hook, for clients to use when registering.
308
201
        self.__doc__ = doc
309
202
        self.introduced = introduced
310
203
        self.deprecated = deprecated
311
 
        if callbacks is None:
312
 
            self._callbacks = []
313
 
        else:
314
 
            self._callbacks = callbacks
 
204
        self._callbacks = []
 
205
        self._callback_names = {}
315
206
 
316
207
    def docs(self):
317
208
        """Generate the documentation for this HookPoint.
326
217
            introduced_string = _format_version_tuple(self.introduced)
327
218
        else:
328
219
            introduced_string = 'unknown'
329
 
        strings.append(gettext('Introduced in: %s') % introduced_string)
 
220
        strings.append('Introduced in: %s' % introduced_string)
330
221
        if self.deprecated:
331
222
            deprecated_string = _format_version_tuple(self.deprecated)
332
 
            strings.append(gettext('Deprecated in: %s') % deprecated_string)
 
223
            strings.append('Deprecated in: %s' % deprecated_string)
333
224
        strings.append('')
334
225
        strings.extend(textwrap.wrap(self.__doc__,
335
226
            break_long_words=False))
337
228
        return '\n'.join(strings)
338
229
 
339
230
    def __eq__(self, other):
340
 
        return (type(other) == type(self) and other.__dict__ == self.__dict__)
341
 
 
342
 
    def hook_lazy(self, callback_module, callback_member, callback_label):
343
 
        """Lazily register a callback to be called when this HookPoint fires.
344
 
 
345
 
        :param callback_module: Module of the callable to use when this
346
 
            HookPoint fires.
347
 
        :param callback_member: Member name of the callback.
348
 
        :param callback_label: A label to show in the UI while this callback is
349
 
            processing.
350
 
        """
351
 
        obj_getter = registry._LazyObjectGetter(callback_module,
352
 
            callback_member)
353
 
        self._callbacks.append((obj_getter, callback_label))
 
231
        return (type(other) == type(self) and 
 
232
            other.__dict__ == self.__dict__)
354
233
 
355
234
    def hook(self, callback, callback_label):
356
235
        """Register a callback to be called when this HookPoint fires.
359
238
        :param callback_label: A label to show in the UI while this callback is
360
239
            processing.
361
240
        """
362
 
        obj_getter = registry._ObjectGetter(callback)
363
 
        self._callbacks.append((obj_getter, callback_label))
364
 
 
365
 
    def uninstall(self, label):
366
 
        """Uninstall the callback with the specified label.
367
 
 
368
 
        :param label: Label of the entry to uninstall
369
 
        """
370
 
        entries_to_remove = []
371
 
        for entry in self._callbacks:
372
 
            (entry_callback, entry_label) = entry
373
 
            if entry_label == label:
374
 
                entries_to_remove.append(entry)
375
 
        if entries_to_remove == []:
376
 
            raise KeyError("No entry with label %r" % label)
377
 
        for entry in entries_to_remove:
378
 
            self._callbacks.remove(entry)
 
241
        self._callbacks.append(callback)
 
242
        if callback_label is not None:
 
243
            self._callback_names[callback] = callback_label
379
244
 
380
245
    def __iter__(self):
381
 
        return (callback.get_obj() for callback, name in self._callbacks)
 
246
        return iter(self._callbacks)
382
247
 
383
248
    def __len__(self):
384
249
        return len(self._callbacks)
388
253
        strings.append("<%s(" % type(self).__name__)
389
254
        strings.append(self.name)
390
255
        strings.append("), callbacks=[")
391
 
        callbacks = self._callbacks
392
 
        for (callback, callback_name) in callbacks:
393
 
            strings.append(repr(callback.get_obj()))
 
256
        for callback in self._callbacks:
 
257
            strings.append(repr(callback))
394
258
            strings.append("(")
395
 
            strings.append(callback_name)
 
259
            strings.append(self._callback_names[callback])
396
260
            strings.append("),")
397
 
        if len(callbacks) == 1:
 
261
        if len(self._callbacks) == 1:
398
262
            strings[-1] = ")"
399
263
        strings.append("]>")
400
264
        return ''.join(strings)
439
303
        hooks = known_hooks_key_to_object(hook_key)
440
304
        segments.append(hooks.docs())
441
305
    return '\n'.join(segments)
442
 
 
443
 
 
444
 
# Lazily registered hooks. Maps (module, name, hook_name) tuples
445
 
# to lists of tuples with objectgetters and names
446
 
_lazy_hooks = {}
447
 
 
448
 
 
449
 
def install_lazy_named_hook(hookpoints_module, hookpoints_name, hook_name,
450
 
    a_callable, name):
451
 
    """Install a callable in to a hook lazily, and label it name.
452
 
 
453
 
    :param hookpoints_module: Module name of the hook points.
454
 
    :param hookpoints_name: Name of the hook points.
455
 
    :param hook_name: A hook name.
456
 
    :param callable: a callable to call for the hook.
457
 
    :param name: A name to associate a_callable with, to show users what is
458
 
        running.
459
 
    """
460
 
    key = (hookpoints_module, hookpoints_name, hook_name)
461
 
    obj_getter = registry._ObjectGetter(a_callable)
462
 
    _lazy_hooks.setdefault(key, []).append((obj_getter, name))