~bzr-pqm/bzr/bzr.dev

3331.3.6 by Martin Pool
merge trunk
1
# Copyright (C) 2007, 2008 Canonical Ltd
2370.4.1 by Robert Collins
New SmartServer hooks facility. There are two initial hooks documented
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
18
"""Support for plugin hooking logic."""
19
from bzrlib.lazy_import import lazy_import
3256.2.30 by Daniel Watkins
Updated deprecation warnings and tests.
20
from bzrlib.symbol_versioning import deprecated_method, one_five
2370.4.1 by Robert Collins
New SmartServer hooks facility. There are two initial hooks documented
21
lazy_import(globals(), """
4098.2.1 by Robert Collins
Allow self documenting hooks.
22
import textwrap
23
2370.4.1 by Robert Collins
New SmartServer hooks facility. There are two initial hooks documented
24
from bzrlib import (
4098.2.1 by Robert Collins
Allow self documenting hooks.
25
        _format_version_tuple,
2370.4.1 by Robert Collins
New SmartServer hooks facility. There are two initial hooks documented
26
        errors,
27
        )
28
""")
29
30
31
class Hooks(dict):
32
    """A dictionary mapping hook name to a list of callables.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
33
2370.4.1 by Robert Collins
New SmartServer hooks facility. There are two initial hooks documented
34
    e.g. ['FOO'] Is the list of items to be called when the
35
    FOO hook is triggered.
36
    """
37
2553.1.1 by Robert Collins
Give Hooks names.
38
    def __init__(self):
39
        dict.__init__(self)
40
        self._callable_names = {}
41
4098.2.1 by Robert Collins
Allow self documenting hooks.
42
    def create_hook(self, hook):
43
        """Create a hook which can have callbacks registered for it.
44
45
        :param hook: The hook to create. An object meeting the protocol of
46
            bzrlib.hooks.Hook. It's name is used as the key for future
47
            lookups.
48
        """
49
        if hook.name in self:
50
            raise errors.DuplicateKey(hook.name)
51
        self[hook.name] = hook
52
53
    def docs(self):
54
        """Generate the documentation for this Hooks instance.
55
56
        This introspects all the individual hooks and returns their docs as well.
57
        """
58
        hook_names = sorted(self.keys())
59
        hook_docs = []
60
        for hook_name in hook_names:
61
            hook = self[hook_name]
62
            try:
63
                hook_docs.append(hook.docs())
64
            except AttributeError:
65
                # legacy hook
66
                strings = []
67
                strings.append(hook_name)
68
                strings.append("-" * len(hook_name))
69
                strings.append("")
70
                strings.append("An old-style hook. For documentation see the __init__ "
71
                    "method of '%s'\n" % (self.__class__.__name__,))
72
                hook_docs.extend(strings)
73
        return "\n".join(hook_docs)
74
2553.1.1 by Robert Collins
Give Hooks names.
75
    def get_hook_name(self, a_callable):
76
        """Get the name for a_callable for UI display.
77
78
        If no name has been registered, the string 'No hook name' is returned.
2553.1.3 by Robert Collins
Increase docs in response to review feedback.
79
        We use a fixed string rather than repr or the callables module because
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
80
        the code names are rarely meaningful for end users and this is not
2553.1.3 by Robert Collins
Increase docs in response to review feedback.
81
        intended for debugging.
2553.1.1 by Robert Collins
Give Hooks names.
82
        """
83
        return self._callable_names.get(a_callable, "No hook name")
84
3256.2.30 by Daniel Watkins
Updated deprecation warnings and tests.
85
    @deprecated_method(one_five)
3256.2.8 by Daniel Watkins
Alphabetised hooks.py.
86
    def install_hook(self, hook_name, a_callable):
87
        """Install a_callable in to the hook hook_name.
88
89
        :param hook_name: A hook name. See the __init__ method of BranchHooks
90
            for the complete list of hooks.
91
        :param a_callable: The callable to be invoked when the hook triggers.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
92
            The exact signature will depend on the hook - see the __init__
3256.2.8 by Daniel Watkins
Alphabetised hooks.py.
93
            method of BranchHooks for details on each hook.
94
        """
3256.2.11 by Daniel Watkins
Modified install_hook to call install_named_hook, as suggested by Aaron.
95
        self.install_named_hook(hook_name, a_callable, None)
3256.2.8 by Daniel Watkins
Alphabetised hooks.py.
96
3256.2.5 by Daniel Watkins
Added install_named_hook.
97
    def install_named_hook(self, hook_name, a_callable, name):
98
        """Install a_callable in to the hook hook_name, and label it name.
2370.4.1 by Robert Collins
New SmartServer hooks facility. There are two initial hooks documented
99
100
        :param hook_name: A hook name. See the __init__ method of BranchHooks
101
            for the complete list of hooks.
102
        :param a_callable: The callable to be invoked when the hook triggers.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
103
            The exact signature will depend on the hook - see the __init__
2370.4.1 by Robert Collins
New SmartServer hooks facility. There are two initial hooks documented
104
            method of BranchHooks for details on each hook.
3256.2.3 by Daniel Watkins
Added docs.
105
        :param name: A name to associate a_callable with, to show users what is
106
            running.
2370.4.1 by Robert Collins
New SmartServer hooks facility. There are two initial hooks documented
107
        """
108
        try:
4098.2.1 by Robert Collins
Allow self documenting hooks.
109
            hook = self[hook_name]
2370.4.1 by Robert Collins
New SmartServer hooks facility. There are two initial hooks documented
110
        except KeyError:
111
            raise errors.UnknownHook(self.__class__.__name__, hook_name)
4098.2.1 by Robert Collins
Allow self documenting hooks.
112
        try:
113
            # list hooks, old-style, not yet deprecated but less useful.
114
            hook.append(a_callable)
115
        except AttributeError:
116
            hook.hook(a_callable, name)
3256.2.10 by Daniel Watkins
Tightened exception scope, as suggested by Aaron.
117
        if name is not None:
118
            self.name_hook(a_callable, name)
2553.1.1 by Robert Collins
Give Hooks names.
119
120
    def name_hook(self, a_callable, name):
121
        """Associate name with a_callable to show users what is running."""
122
        self._callable_names[a_callable] = name
4098.2.1 by Robert Collins
Allow self documenting hooks.
123
124
125
class Hook(object):
126
    """A single hook that clients can register to be called back when it fires.
127
128
    :ivar name: The name of the hook.
129
    :ivar introduced: A version tuple specifying what version the hook was
130
        introduced in. None indicates an unknown version.
131
    :ivar deprecated: A version tuple specifying what version the hook was
132
        deprecated or superceded in. None indicates that the hook is not
133
        superceded or deprecated. If the hook is superceded then the doc
134
        should describe the recommended replacement hook to register for.
135
    :ivar doc: The docs for using the hook.
136
    """
137
138
    def __init__(self, name, doc, introduced, deprecated):
139
        """Create a Hook.
140
        
141
        :param name: The name of the hook, for clients to use when registering.
142
        :param doc: The docs for the hook.
143
        :param introduced: When the hook was introduced (e.g. (0, 15)).
144
        :param deprecated: When the hook was deprecated, None for
145
            not-deprecated.
146
        """
147
        self.name = name
148
        self.__doc__ = doc
149
        self.introduced = introduced
150
        self.deprecated = deprecated
151
        self._callbacks = []
152
        self._callback_names = {}
153
154
    def docs(self):
155
        """Generate the documentation for this Hook.
156
        
157
        :return: A string terminated in \n.
158
        """
159
        strings = []
160
        strings.append(self.name)
161
        strings.append('-'*len(self.name))
162
        strings.append('')
163
        if self.introduced:
164
            introduced_string = _format_version_tuple(self.introduced)
165
        else:
166
            introduced_string = 'unknown'
167
        strings.append('Introduced in: %s' % introduced_string)
168
        if self.deprecated:
169
            deprecated_string = _format_version_tuple(self.deprecated)
170
        else:
171
            deprecated_string = 'Not deprecated'
172
        strings.append('Deprecated in: %s' % deprecated_string)
173
        strings.append('')
174
        strings.extend(textwrap.wrap(self.__doc__))
175
        strings.append('')
176
        return '\n'.join(strings)
177
178
    def hook(self, callback, callback_label):
179
        """Call this hook with callback, using callback_label to describe it.
180
181
        :param callback: The callable to use when this Hook fires.
182
        :param callback_label: A label to show in the UI while this callback is
183
            processing.
184
        """
185
        self._callbacks.append(callback)
186
        self._callback_names[callback] = callback_label
187
188
    def __iter__(self):
189
        return iter(self._callbacks)
190
191
    def __repr__(self):
192
        strings = []
193
        strings.append("<bzrlib.hooks.Hook(")
194
        strings.append(self.name)
195
        strings.append("), callbacks=[")
196
        for callback in self._callbacks:
197
            strings.append(repr(callback))
198
            strings.append("(")
199
            strings.append(self._callback_names[callback])
200
            strings.append("),")
201
        if len(self._callbacks) == 1:
202
            strings[-1] = ")"
203
        strings.append("]>")
204
        return ''.join(strings)