Merge lp:~lifeless/bzr/Commands.hooks into lp:~bzr/bzr/trunk-old
- Commands.hooks
- Merge into trunk-old
Proposed by
Robert Collins
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~lifeless/bzr/Commands.hooks |
Merge into: | lp:~bzr/bzr/trunk-old |
Diff against target: |
510 lines (has conflicts)
Text conflict in NEWS |
To merge this branch: | bzr merge lp:~lifeless/bzr/Commands.hooks |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ian Clatworthy | Needs Fixing | ||
Review via email: mp+6936@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote : | # |
Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote : | # |
This was reviewed (via BB) last week and my review comments haven't been actioned yet. See https:/
review:
Needs Fixing
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'NEWS' | |||
2 | --- NEWS 2009-06-12 08:39:44 +0000 | |||
3 | +++ NEWS 2009-06-15 08:36:49 +0000 | |||
4 | @@ -184,9 +184,19 @@ | |||
5 | 184 | Internals | 184 | Internals |
6 | 185 | ********* | 185 | ********* |
7 | 186 | 186 | ||
8 | 187 | <<<<<<< TREE | ||
9 | 187 | * Remove ``weave.py`` script for accessing internals of old weave-format | 188 | * Remove ``weave.py`` script for accessing internals of old weave-format |
10 | 188 | repositories. (Martin Pool) | 189 | repositories. (Martin Pool) |
11 | 189 | 190 | ||
12 | 191 | ======= | ||
13 | 192 | * Command lookup has had hooks added. ``bzrlib.Command.hooks`` has | ||
14 | 193 | three new hook points: ``get_command``, ``get_missing_command`` and | ||
15 | 194 | ``list_commands``, which allow just-in-time command name provision | ||
16 | 195 | rather than requiring all command names be known a-priori. | ||
17 | 196 | (Robert Collins) | ||
18 | 197 | |||
19 | 198 | |||
20 | 199 | >>>>>>> MERGE-SOURCE | ||
21 | 190 | Testing | 200 | Testing |
22 | 191 | ******* | 201 | ******* |
23 | 192 | 202 | ||
24 | 193 | 203 | ||
25 | === modified file 'bzrlib/commands.py' | |||
26 | --- bzrlib/commands.py 2009-06-10 03:56:49 +0000 | |||
27 | +++ bzrlib/commands.py 2009-06-15 08:36:49 +0000 | |||
28 | @@ -49,10 +49,11 @@ | |||
29 | 49 | ) | 49 | ) |
30 | 50 | """) | 50 | """) |
31 | 51 | 51 | ||
32 | 52 | from bzrlib import registry | ||
33 | 53 | # Compatibility | ||
34 | 54 | from bzrlib.hooks import HookPoint, Hooks | 52 | from bzrlib.hooks import HookPoint, Hooks |
35 | 53 | # Compatibility - Option used to be in commands. | ||
36 | 55 | from bzrlib.option import Option | 54 | from bzrlib.option import Option |
37 | 55 | from bzrlib import registry | ||
38 | 56 | from bzrlib.symbol_versioning import deprecated_function, deprecated_in | ||
39 | 56 | 57 | ||
40 | 57 | 58 | ||
41 | 58 | class CommandInfo(object): | 59 | class CommandInfo(object): |
42 | @@ -133,98 +134,181 @@ | |||
43 | 133 | 134 | ||
44 | 134 | def _builtin_commands(): | 135 | def _builtin_commands(): |
45 | 135 | import bzrlib.builtins | 136 | import bzrlib.builtins |
46 | 137 | return _scan_module_for_commands(bzrlib.builtins) | ||
47 | 138 | |||
48 | 139 | |||
49 | 140 | def _scan_module_for_commands(module): | ||
50 | 136 | r = {} | 141 | r = {} |
53 | 137 | builtins = bzrlib.builtins.__dict__ | 142 | for name, obj in module.__dict__.iteritems(): |
52 | 138 | for name in builtins: | ||
54 | 139 | if name.startswith("cmd_"): | 143 | if name.startswith("cmd_"): |
55 | 140 | real_name = _unsquish_command_name(name) | 144 | real_name = _unsquish_command_name(name) |
57 | 141 | r[real_name] = builtins[name] | 145 | r[real_name] = obj |
58 | 142 | return r | 146 | return r |
59 | 143 | 147 | ||
60 | 144 | 148 | ||
61 | 149 | def _list_bzr_commands(names): | ||
62 | 150 | """Return a list of all the registered commands. | ||
63 | 151 | |||
64 | 152 | This searches plugins and the core. | ||
65 | 153 | """ | ||
66 | 154 | # to eliminate duplicates | ||
67 | 155 | names.update(builtin_command_names()) | ||
68 | 156 | names.update(plugin_command_names()) | ||
69 | 157 | return names | ||
70 | 158 | |||
71 | 159 | |||
72 | 160 | def all_command_names(): | ||
73 | 161 | """Return a list of all command names.""" | ||
74 | 162 | names = set() | ||
75 | 163 | for hook in Command.hooks['list_commands']: | ||
76 | 164 | new_names = hook(names) | ||
77 | 165 | if new_names is None: | ||
78 | 166 | raise AssertionError( | ||
79 | 167 | 'hook %s returned None' % Command.hooks.get_hook_name(hook)) | ||
80 | 168 | names = new_names | ||
81 | 169 | return names | ||
82 | 170 | |||
83 | 171 | |||
84 | 145 | def builtin_command_names(): | 172 | def builtin_command_names(): |
86 | 146 | """Return list of builtin command names.""" | 173 | """Return list of builtin command names. |
87 | 174 | |||
88 | 175 | Use of all_command_names() is encouraged rather than builtin_command_names | ||
89 | 176 | and/or plugin_command_names. | ||
90 | 177 | """ | ||
91 | 147 | return _builtin_commands().keys() | 178 | return _builtin_commands().keys() |
92 | 148 | 179 | ||
93 | 149 | 180 | ||
94 | 150 | def plugin_command_names(): | 181 | def plugin_command_names(): |
95 | 182 | """Returns command names from commands registered by plugins.""" | ||
96 | 151 | return plugin_cmds.keys() | 183 | return plugin_cmds.keys() |
97 | 152 | 184 | ||
98 | 153 | 185 | ||
101 | 154 | def _get_cmd_dict(plugins_override=True): | 186 | @deprecated_function(deprecated_in((1, 16, 0))) |
102 | 155 | """Return name->class mapping for all commands.""" | 187 | def get_all_cmds(): |
103 | 188 | """Return canonical name and class for most commands. | ||
104 | 189 | |||
105 | 190 | NB: This does not return all commands since the introduction of | ||
106 | 191 | command hooks, and returning the class is not sufficient to | ||
107 | 192 | get correctly setup commands, which is why it is deprecated. | ||
108 | 193 | |||
109 | 194 | Use 'all_command_names' + 'get_cmd_object' instead. | ||
110 | 195 | """ | ||
111 | 156 | d = _builtin_commands() | 196 | d = _builtin_commands() |
112 | 157 | if plugins_override: | 197 | if plugins_override: |
113 | 158 | d.update(plugin_cmds.iteritems()) | 198 | d.update(plugin_cmds.iteritems()) |
120 | 159 | return d | 199 | for k, v in d.iteritems(): |
115 | 160 | |||
116 | 161 | |||
117 | 162 | def get_all_cmds(plugins_override=True): | ||
118 | 163 | """Return canonical name and class for all registered commands.""" | ||
119 | 164 | for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems(): | ||
121 | 165 | yield k,v | 200 | yield k,v |
122 | 166 | 201 | ||
123 | 167 | 202 | ||
124 | 168 | def get_cmd_object(cmd_name, plugins_override=True): | 203 | def get_cmd_object(cmd_name, plugins_override=True): |
126 | 169 | """Return the canonical name and command class for a command. | 204 | """Return the command object for a command. |
127 | 170 | 205 | ||
128 | 171 | plugins_override | 206 | plugins_override |
129 | 172 | If true, plugin commands can override builtins. | 207 | If true, plugin commands can override builtins. |
130 | 173 | """ | 208 | """ |
131 | 174 | try: | 209 | try: |
137 | 175 | cmd = _get_cmd_object(cmd_name, plugins_override) | 210 | return _get_cmd_object(cmd_name, plugins_override) |
133 | 176 | # Allow plugins to extend commands | ||
134 | 177 | for hook in Command.hooks['extend_command']: | ||
135 | 178 | hook(cmd) | ||
136 | 179 | return cmd | ||
138 | 180 | except KeyError: | 211 | except KeyError: |
139 | 181 | raise errors.BzrCommandError('unknown command "%s"' % cmd_name) | 212 | raise errors.BzrCommandError('unknown command "%s"' % cmd_name) |
140 | 182 | 213 | ||
141 | 183 | 214 | ||
142 | 184 | def _get_cmd_object(cmd_name, plugins_override=True): | 215 | def _get_cmd_object(cmd_name, plugins_override=True): |
145 | 185 | """Worker for get_cmd_object which raises KeyError rather than BzrCommandError.""" | 216 | """Get a command object. |
144 | 186 | from bzrlib.externalcommand import ExternalCommand | ||
146 | 187 | 217 | ||
147 | 218 | :param cmd_name: The name of the command. | ||
148 | 219 | :param plugins_override: Allow plugins to override builtins. | ||
149 | 220 | :return: A Command object instance | ||
150 | 221 | :raises: KeyError if no command is found. | ||
151 | 222 | """ | ||
152 | 188 | # We want only 'ascii' command names, but the user may have typed | 223 | # We want only 'ascii' command names, but the user may have typed |
153 | 189 | # in a Unicode name. In that case, they should just get a | 224 | # in a Unicode name. In that case, they should just get a |
154 | 190 | # 'command not found' error later. | 225 | # 'command not found' error later. |
155 | 191 | # In the future, we may actually support Unicode command names. | 226 | # In the future, we may actually support Unicode command names. |
159 | 192 | 227 | cmd = None | |
160 | 193 | # first look up this command under the specified name | 228 | # Get a command |
161 | 194 | if plugins_override: | 229 | for hook in Command.hooks['get_command']: |
162 | 230 | cmd = hook(cmd, cmd_name) | ||
163 | 231 | if cmd is not None and not plugins_override: | ||
164 | 232 | # We've found a non-plugin command, don't permit it to be | ||
165 | 233 | # overridden. | ||
166 | 234 | if not cmd.plugin_name(): | ||
167 | 235 | break | ||
168 | 236 | if cmd is None: | ||
169 | 237 | for hook in Command.hooks['get_missing_command']: | ||
170 | 238 | cmd = hook(cmd_name) | ||
171 | 239 | if cmd is not None: | ||
172 | 240 | break | ||
173 | 241 | if cmd is None: | ||
174 | 242 | # No command found. | ||
175 | 243 | raise KeyError | ||
176 | 244 | # Allow plugins to extend commands | ||
177 | 245 | for hook in Command.hooks['extend_command']: | ||
178 | 246 | hook(cmd) | ||
179 | 247 | return cmd | ||
180 | 248 | |||
181 | 249 | |||
182 | 250 | def _try_plugin_provider(cmd_name): | ||
183 | 251 | """Probe for a plugin provider having cmd_name.""" | ||
184 | 252 | try: | ||
185 | 253 | plugin_metadata, provider = probe_for_provider(cmd_name) | ||
186 | 254 | raise errors.CommandAvailableInPlugin(cmd_name, | ||
187 | 255 | plugin_metadata, provider) | ||
188 | 256 | except errors.NoPluginAvailable: | ||
189 | 257 | pass | ||
190 | 258 | |||
191 | 259 | |||
192 | 260 | def probe_for_provider(cmd_name): | ||
193 | 261 | """Look for a provider for cmd_name. | ||
194 | 262 | |||
195 | 263 | :param cmd_name: The command name. | ||
196 | 264 | :return: plugin_metadata, provider for getting cmd_name. | ||
197 | 265 | :raises NoPluginAvailable: When no provider can supply the plugin. | ||
198 | 266 | """ | ||
199 | 267 | # look for providers that provide this command but aren't installed | ||
200 | 268 | for provider in command_providers_registry: | ||
201 | 195 | try: | 269 | try: |
204 | 196 | return plugin_cmds.get(cmd_name)() | 270 | return provider.plugin_for_command(cmd_name), provider |
205 | 197 | except KeyError: | 271 | except errors.NoPluginAvailable: |
206 | 198 | pass | 272 | pass |
208 | 199 | cmds = _get_cmd_dict(plugins_override=False) | 273 | raise errors.NoPluginAvailable(cmd_name) |
209 | 274 | |||
210 | 275 | |||
211 | 276 | def _get_bzr_command(cmd_or_None, cmd_name): | ||
212 | 277 | """Get a command from bzr's core.""" | ||
213 | 278 | cmds = _builtin_commands() | ||
214 | 200 | try: | 279 | try: |
215 | 201 | return cmds[cmd_name]() | 280 | return cmds[cmd_name]() |
216 | 202 | except KeyError: | 281 | except KeyError: |
217 | 203 | pass | 282 | pass |
218 | 204 | if plugins_override: | ||
219 | 205 | for key in plugin_cmds.keys(): | ||
220 | 206 | info = plugin_cmds.get_info(key) | ||
221 | 207 | if cmd_name in info.aliases: | ||
222 | 208 | return plugin_cmds.get(key)() | ||
223 | 209 | # look for any command which claims this as an alias | 283 | # look for any command which claims this as an alias |
224 | 210 | for real_cmd_name, cmd_class in cmds.iteritems(): | 284 | for real_cmd_name, cmd_class in cmds.iteritems(): |
225 | 211 | if cmd_name in cmd_class.aliases: | 285 | if cmd_name in cmd_class.aliases: |
226 | 212 | return cmd_class() | 286 | return cmd_class() |
228 | 213 | 287 | return cmd_or_None | |
229 | 288 | |||
230 | 289 | |||
231 | 290 | def _get_external_command(cmd_or_None, cmd_name): | ||
232 | 291 | """Lookup a command that is a shell script.""" | ||
233 | 292 | # Only do external command lookups when no command is found so far. | ||
234 | 293 | if cmd_or_None is not None: | ||
235 | 294 | return cmd_or_None | ||
236 | 295 | from bzrlib.externalcommand import ExternalCommand | ||
237 | 214 | cmd_obj = ExternalCommand.find_command(cmd_name) | 296 | cmd_obj = ExternalCommand.find_command(cmd_name) |
238 | 215 | if cmd_obj: | 297 | if cmd_obj: |
239 | 216 | return cmd_obj | 298 | return cmd_obj |
240 | 217 | 299 | ||
251 | 218 | # look for plugins that provide this command but aren't installed | 300 | |
252 | 219 | for provider in command_providers_registry: | 301 | def _get_plugin_command(cmd_or_None, cmd_name): |
253 | 220 | try: | 302 | """Get a command from bzr's plugins.""" |
254 | 221 | plugin_metadata = provider.plugin_for_command(cmd_name) | 303 | try: |
255 | 222 | except errors.NoPluginAvailable: | 304 | return plugin_cmds.get(cmd_name)() |
256 | 223 | pass | 305 | except KeyError: |
257 | 224 | else: | 306 | pass |
258 | 225 | raise errors.CommandAvailableInPlugin(cmd_name, | 307 | for key in plugin_cmds.keys(): |
259 | 226 | plugin_metadata, provider) | 308 | info = plugin_cmds.get_info(key) |
260 | 227 | raise KeyError | 309 | if cmd_name in info.aliases: |
261 | 310 | return plugin_cmds.get(key)() | ||
262 | 311 | return cmd_or_None | ||
263 | 228 | 312 | ||
264 | 229 | 313 | ||
265 | 230 | class Command(object): | 314 | class Command(object): |
266 | @@ -608,6 +692,23 @@ | |||
267 | 608 | "Called after creating a command object to allow modifications " | 692 | "Called after creating a command object to allow modifications " |
268 | 609 | "such as adding or removing options, docs etc. Called with the " | 693 | "such as adding or removing options, docs etc. Called with the " |
269 | 610 | "new bzrlib.commands.Command object.", (1, 13), None)) | 694 | "new bzrlib.commands.Command object.", (1, 13), None)) |
270 | 695 | self.create_hook(HookPoint('get_command', | ||
271 | 696 | "Called when creating a single command. Called with " | ||
272 | 697 | "(cmd_or_None, command_name). get_command should either return " | ||
273 | 698 | "the cmd_or_None parameter, or a replacement Command object that " | ||
274 | 699 | "should be used for the command.", (1, 16), None)) | ||
275 | 700 | self.create_hook(HookPoint('get_missing_command', | ||
276 | 701 | "Called when creating a single command if no command could be " | ||
277 | 702 | "found. Called with (command_name). get_missing_command should " | ||
278 | 703 | "either return None, or a Command object to be used for the " | ||
279 | 704 | "command.", (1, 16), None)) | ||
280 | 705 | self.create_hook(HookPoint('list_commands', | ||
281 | 706 | "Called when enumerating commands. Called with a dict of " | ||
282 | 707 | "cmd_name: cmd_class tuples for all the commands found " | ||
283 | 708 | "so far. This dict is safe to mutate - to remove a command or " | ||
284 | 709 | "to replace it with another (eg plugin supplied) version. " | ||
285 | 710 | "list_commands should return the updated dict of commands.", | ||
286 | 711 | (1, 16), None)) | ||
287 | 611 | 712 | ||
288 | 612 | Command.hooks = CommandHooks() | 713 | Command.hooks = CommandHooks() |
289 | 613 | 714 | ||
290 | @@ -952,6 +1053,20 @@ | |||
291 | 952 | return ignore_pipe | 1053 | return ignore_pipe |
292 | 953 | 1054 | ||
293 | 954 | 1055 | ||
294 | 1056 | def install_bzr_command_hooks(): | ||
295 | 1057 | """Install the hooks to supply bzr's own commands.""" | ||
296 | 1058 | Command.hooks.install_named_hook("list_commands", _list_bzr_commands, | ||
297 | 1059 | "bzr commands") | ||
298 | 1060 | Command.hooks.install_named_hook("get_command", _get_bzr_command, | ||
299 | 1061 | "bzr commands") | ||
300 | 1062 | Command.hooks.install_named_hook("get_command", _get_plugin_command, | ||
301 | 1063 | "bzr plugin commands") | ||
302 | 1064 | Command.hooks.install_named_hook("get_command", _get_external_command, | ||
303 | 1065 | "bzr external command lookup") | ||
304 | 1066 | Command.hooks.install_named_hook("get_missing_command", _try_plugin_provider, | ||
305 | 1067 | "bzr plugin-provider-db check") | ||
306 | 1068 | |||
307 | 1069 | |||
308 | 955 | def main(argv=None): | 1070 | def main(argv=None): |
309 | 956 | """Main entry point of command-line interface. | 1071 | """Main entry point of command-line interface. |
310 | 957 | 1072 | ||
311 | @@ -968,7 +1083,6 @@ | |||
312 | 968 | 1083 | ||
313 | 969 | # Is this a final release version? If so, we should suppress warnings | 1084 | # Is this a final release version? If so, we should suppress warnings |
314 | 970 | if bzrlib.version_info[3] == 'final': | 1085 | if bzrlib.version_info[3] == 'final': |
315 | 971 | from bzrlib import symbol_versioning | ||
316 | 972 | symbol_versioning.suppress_deprecation_warnings(override=False) | 1086 | symbol_versioning.suppress_deprecation_warnings(override=False) |
317 | 973 | if argv is None: | 1087 | if argv is None: |
318 | 974 | argv = osutils.get_unicode_argv() | 1088 | argv = osutils.get_unicode_argv() |
319 | @@ -984,6 +1098,7 @@ | |||
320 | 984 | except UnicodeDecodeError: | 1098 | except UnicodeDecodeError: |
321 | 985 | raise errors.BzrError("argv should be list of unicode strings.") | 1099 | raise errors.BzrError("argv should be list of unicode strings.") |
322 | 986 | argv = new_argv | 1100 | argv = new_argv |
323 | 1101 | install_bzr_command_hooks() | ||
324 | 987 | ret = run_bzr_catch_errors(argv) | 1102 | ret = run_bzr_catch_errors(argv) |
325 | 988 | trace.mutter("return code %d", ret) | 1103 | trace.mutter("return code %d", ret) |
326 | 989 | return ret | 1104 | return ret |
327 | 990 | 1105 | ||
328 | === modified file 'bzrlib/help.py' | |||
329 | --- bzrlib/help.py 2009-03-23 14:59:43 +0000 | |||
330 | +++ bzrlib/help.py 2009-06-15 08:36:49 +0000 | |||
331 | @@ -73,8 +73,7 @@ | |||
332 | 73 | hidden = True | 73 | hidden = True |
333 | 74 | else: | 74 | else: |
334 | 75 | hidden = False | 75 | hidden = False |
337 | 76 | names = set(_mod_commands.builtin_command_names()) # to eliminate duplicates | 76 | names = list(_mod_commands.all_command_names()) |
336 | 77 | names.update(_mod_commands.plugin_command_names()) | ||
338 | 78 | commands = ((n, _mod_commands.get_cmd_object(n)) for n in names) | 77 | commands = ((n, _mod_commands.get_cmd_object(n)) for n in names) |
339 | 79 | shown_commands = [(n, o) for n, o in commands if o.hidden == hidden] | 78 | shown_commands = [(n, o) for n, o in commands if o.hidden == hidden] |
340 | 80 | max_name = max(len(n) for n, o in shown_commands) | 79 | max_name = max(len(n) for n, o in shown_commands) |
341 | 81 | 80 | ||
342 | === modified file 'bzrlib/shellcomplete.py' | |||
343 | --- bzrlib/shellcomplete.py 2009-03-23 14:59:43 +0000 | |||
344 | +++ bzrlib/shellcomplete.py 2009-06-15 08:36:49 +0000 | |||
345 | @@ -64,15 +64,16 @@ | |||
346 | 64 | outfile = sys.stdout | 64 | outfile = sys.stdout |
347 | 65 | 65 | ||
348 | 66 | cmds = [] | 66 | cmds = [] |
353 | 67 | for cmdname, cmdclass in commands.get_all_cmds(): | 67 | for cmdname in commands.all_command_names(): |
354 | 68 | cmds.append((cmdname, cmdclass)) | 68 | cmd = commands.get_cmd_object(cmdname))) |
355 | 69 | for alias in cmdclass.aliases: | 69 | cmds.append((cmdname, cmd)) |
356 | 70 | cmds.append((alias, cmdclass)) | 70 | for alias in cmd.aliases: |
357 | 71 | cmds.append((alias, cmd)) | ||
358 | 71 | cmds.sort() | 72 | cmds.sort() |
361 | 72 | for cmdname, cmdclass in cmds: | 73 | for cmdname, cmd in cmds: |
362 | 73 | if cmdclass.hidden: | 74 | if cmd.hidden: |
363 | 74 | continue | 75 | continue |
365 | 75 | doc = getdoc(cmdclass) | 76 | doc = getdoc(cmd) |
366 | 76 | if doc is None: | 77 | if doc is None: |
367 | 77 | outfile.write(cmdname + '\n') | 78 | outfile.write(cmdname + '\n') |
368 | 78 | else: | 79 | else: |
369 | 79 | 80 | ||
370 | === modified file 'bzrlib/tests/test_commands.py' | |||
371 | --- bzrlib/tests/test_commands.py 2009-04-07 17:13:51 +0000 | |||
372 | +++ bzrlib/tests/test_commands.py 2009-06-15 08:36:49 +0000 | |||
373 | @@ -163,6 +163,7 @@ | |||
374 | 163 | del sys.modules['bzrlib.tests.fake_command'] | 163 | del sys.modules['bzrlib.tests.fake_command'] |
375 | 164 | global lazy_command_imported | 164 | global lazy_command_imported |
376 | 165 | lazy_command_imported = False | 165 | lazy_command_imported = False |
377 | 166 | commands.install_bzr_command_hooks() | ||
378 | 166 | 167 | ||
379 | 167 | @staticmethod | 168 | @staticmethod |
380 | 168 | def remove_fake(): | 169 | def remove_fake(): |
381 | @@ -205,6 +206,7 @@ | |||
382 | 205 | # commands are registered). | 206 | # commands are registered). |
383 | 206 | # when they are simply created. | 207 | # when they are simply created. |
384 | 207 | hook_calls = [] | 208 | hook_calls = [] |
385 | 209 | commands.install_bzr_command_hooks() | ||
386 | 208 | commands.Command.hooks.install_named_hook( | 210 | commands.Command.hooks.install_named_hook( |
387 | 209 | "extend_command", hook_calls.append, None) | 211 | "extend_command", hook_calls.append, None) |
388 | 210 | # create a command, should not fire | 212 | # create a command, should not fire |
389 | @@ -237,3 +239,87 @@ | |||
390 | 237 | self.assertEqual([cmd], hook_calls) | 239 | self.assertEqual([cmd], hook_calls) |
391 | 238 | finally: | 240 | finally: |
392 | 239 | commands.plugin_cmds.remove('fake') | 241 | commands.plugin_cmds.remove('fake') |
393 | 242 | |||
394 | 243 | |||
395 | 244 | class TestGetCommandHook(tests.TestCase): | ||
396 | 245 | |||
397 | 246 | def test_fires_on_get_cmd_object(self): | ||
398 | 247 | # The get_command(cmd) hook fires when commands are delivered to the | ||
399 | 248 | # ui. | ||
400 | 249 | commands.install_bzr_command_hooks() | ||
401 | 250 | hook_calls = [] | ||
402 | 251 | class ACommand(commands.Command): | ||
403 | 252 | """A sample command.""" | ||
404 | 253 | def get_cmd(cmd_or_None, cmd_name): | ||
405 | 254 | hook_calls.append(('called', cmd_or_None, cmd_name)) | ||
406 | 255 | if cmd_name in ('foo', 'info'): | ||
407 | 256 | return ACommand() | ||
408 | 257 | commands.Command.hooks.install_named_hook( | ||
409 | 258 | "get_command", get_cmd, None) | ||
410 | 259 | # create a command directly, should not fire | ||
411 | 260 | cmd = ACommand() | ||
412 | 261 | self.assertEqual([], hook_calls) | ||
413 | 262 | # ask by name, should fire and give us our command | ||
414 | 263 | cmd = commands.get_cmd_object('foo') | ||
415 | 264 | self.assertEqual([('called', None, 'foo')], hook_calls) | ||
416 | 265 | self.assertIsInstance(cmd, ACommand) | ||
417 | 266 | del hook_calls[:] | ||
418 | 267 | # ask by a name that is supplied by a builtin - the hook should still | ||
419 | 268 | # fire and we still get our object, but we should see the builtin | ||
420 | 269 | # passed to the hook. | ||
421 | 270 | cmd = commands.get_cmd_object('info') | ||
422 | 271 | self.assertIsInstance(cmd, ACommand) | ||
423 | 272 | self.assertEqual(1, len(hook_calls)) | ||
424 | 273 | self.assertEqual('info', hook_calls[0][2]) | ||
425 | 274 | self.assertIsInstance(hook_calls[0][1], builtins.cmd_info) | ||
426 | 275 | |||
427 | 276 | |||
428 | 277 | class TestGetMissingCommandHook(tests.TestCase): | ||
429 | 278 | |||
430 | 279 | def test_fires_on_get_cmd_object(self): | ||
431 | 280 | # The get_missing_command(cmd) hook fires when commands are delivered to the | ||
432 | 281 | # ui. | ||
433 | 282 | hook_calls = [] | ||
434 | 283 | class ACommand(commands.Command): | ||
435 | 284 | """A sample command.""" | ||
436 | 285 | def get_missing_cmd(cmd_name): | ||
437 | 286 | hook_calls.append(('called', cmd_name)) | ||
438 | 287 | if cmd_name in ('foo', 'info'): | ||
439 | 288 | return ACommand() | ||
440 | 289 | commands.Command.hooks.install_named_hook( | ||
441 | 290 | "get_missing_command", get_missing_cmd, None) | ||
442 | 291 | # create a command directly, should not fire | ||
443 | 292 | cmd = ACommand() | ||
444 | 293 | self.assertEqual([], hook_calls) | ||
445 | 294 | # ask by name, should fire and give us our command | ||
446 | 295 | cmd = commands.get_cmd_object('foo') | ||
447 | 296 | self.assertEqual([('called', 'foo')], hook_calls) | ||
448 | 297 | self.assertIsInstance(cmd, ACommand) | ||
449 | 298 | del hook_calls[:] | ||
450 | 299 | # ask by a name that is supplied by a builtin - the hook should not | ||
451 | 300 | # fire and we still get our object. | ||
452 | 301 | commands.install_bzr_command_hooks() | ||
453 | 302 | cmd = commands.get_cmd_object('info') | ||
454 | 303 | self.assertNotEqual(None, cmd) | ||
455 | 304 | self.assertEqual(0, len(hook_calls)) | ||
456 | 305 | |||
457 | 306 | |||
458 | 307 | class TestListCommandHook(tests.TestCase): | ||
459 | 308 | |||
460 | 309 | def test_fires_on_all_command_names(self): | ||
461 | 310 | # The list_commands() hook fires when all_command_names() is invoked. | ||
462 | 311 | hook_calls = [] | ||
463 | 312 | commands.install_bzr_command_hooks() | ||
464 | 313 | def list_my_commands(cmd_names): | ||
465 | 314 | hook_calls.append('called') | ||
466 | 315 | cmd_names.update(['foo', 'bar']) | ||
467 | 316 | return cmd_names | ||
468 | 317 | commands.Command.hooks.install_named_hook( | ||
469 | 318 | "list_commands", list_my_commands, None) | ||
470 | 319 | # Get a command, which should not trigger the hook. | ||
471 | 320 | cmd = commands.get_cmd_object('info') | ||
472 | 321 | self.assertEqual([], hook_calls) | ||
473 | 322 | # Get all command classes (for docs and shell completion). | ||
474 | 323 | cmds = list(commands.all_command_names()) | ||
475 | 324 | self.assertEqual(['called'], hook_calls) | ||
476 | 325 | self.assertSubset(['foo', 'bar'], cmds) | ||
477 | 240 | 326 | ||
478 | === modified file 'bzrlib/tests/test_options.py' | |||
479 | --- bzrlib/tests/test_options.py 2009-04-03 20:05:25 +0000 | |||
480 | +++ bzrlib/tests/test_options.py 2009-06-15 08:36:49 +0000 | |||
481 | @@ -324,8 +324,8 @@ | |||
482 | 324 | 324 | ||
483 | 325 | def get_builtin_command_options(self): | 325 | def get_builtin_command_options(self): |
484 | 326 | g = [] | 326 | g = [] |
487 | 327 | for cmd_name, cmd_class in sorted(commands.get_all_cmds()): | 327 | for cmd_name in sorted(commands.all_command_names()): |
488 | 328 | cmd = cmd_class() | 328 | cmd = commands.get_cmd_object(cmd_name) |
489 | 329 | for opt_name, opt in sorted(cmd.options().items()): | 329 | for opt_name, opt in sorted(cmd.options().items()): |
490 | 330 | g.append((cmd_name, opt)) | 330 | g.append((cmd_name, opt)) |
491 | 331 | return g | 331 | return g |
492 | @@ -338,14 +338,15 @@ | |||
493 | 338 | g = dict(option.Option.OPTIONS.items()) | 338 | g = dict(option.Option.OPTIONS.items()) |
494 | 339 | used_globals = {} | 339 | used_globals = {} |
495 | 340 | msgs = [] | 340 | msgs = [] |
498 | 341 | for cmd_name, cmd_class in sorted(commands.get_all_cmds()): | 341 | for cmd_name in sorted(commands.all_command_names()): |
499 | 342 | for option_or_name in sorted(cmd_class.takes_options): | 342 | cmd = commands.get_cmd_object(cmd_name) |
500 | 343 | for option_or_name in sorted(cmd.takes_options): | ||
501 | 343 | if not isinstance(option_or_name, basestring): | 344 | if not isinstance(option_or_name, basestring): |
502 | 344 | self.assertIsInstance(option_or_name, option.Option) | 345 | self.assertIsInstance(option_or_name, option.Option) |
503 | 345 | elif not option_or_name in g: | 346 | elif not option_or_name in g: |
504 | 346 | msgs.append("apparent reference to undefined " | 347 | msgs.append("apparent reference to undefined " |
505 | 347 | "global option %r from %r" | 348 | "global option %r from %r" |
507 | 348 | % (option_or_name, cmd_class)) | 349 | % (option_or_name, cmd)) |
508 | 349 | else: | 350 | else: |
509 | 350 | used_globals.setdefault(option_or_name, []).append(cmd_name) | 351 | used_globals.setdefault(option_or_name, []).append(cmd_name) |
510 | 351 | unused_globals = set(g.keys()) - set(used_globals.keys()) | 352 | unused_globals = set(g.keys()) - set(used_globals.keys()) |
This makes command lookup fully hookified.
Why? It makes the core logic much clearer IMO, and also makes it
possible to skin bzrlib more completely for things like loggerhead and
olive want to have their own command (not subcommand) but still want to
use bzr's Command and option handling infrastructure.
-Rob