Rev 4127: Get missing command support sorted out. in http://people.ubuntu.com/~robertc/baz2.0/pending/Commands.hooks

Robert Collins robertc at robertcollins.net
Sat May 23 22:01:53 BST 2009


At http://people.ubuntu.com/~robertc/baz2.0/pending/Commands.hooks

------------------------------------------------------------
revno: 4127
revision-id: robertc at robertcollins.net-20090523210151-69jmrka5l4eh0zf3
parent: robertc at robertcollins.net-20090523205712-lcwbfqk6vwavinuv
committer: Robert Collins <robertc at robertcollins.net>
branch nick: Commands.hooks
timestamp: Sun 2009-05-24 07:01:51 +1000
message:
  Get missing command support sorted out.
=== modified file 'bzrlib/commands.py'
--- a/bzrlib/commands.py	2009-05-23 20:57:12 +0000
+++ b/bzrlib/commands.py	2009-05-23 21:01:51 +0000
@@ -49,10 +49,11 @@
     )
 """)
 
-from bzrlib import registry
-# Compatibility
 from bzrlib.hooks import HookPoint, Hooks
+# Compatibility - Option used to be in commands.
 from bzrlib.option import Option
+from bzrlib import registry
+from bzrlib.symbol_versioning import deprecated_function, deprecated_in
 
 
 class CommandInfo(object):
@@ -182,17 +183,20 @@
     return plugin_cmds.keys()
 
 
-def _get_cmd_dict(plugins_override=True):
-    """Return name->class mapping for all commands."""
+ at deprecated_function(deprecated_in((1, 16, 0)))
+def get_all_cmds():
+    """Return canonical name and class for most commands.
+    
+    NB: This does not return all commands since the introduction of
+    command hooks, and returning the class is not sufficient to 
+    get correctly setup commands, which is why it is deprecated.
+
+    Use 'all_command_names' + 'get_cmd_object' instead.
+    """
     d = _builtin_commands()
     if plugins_override:
         d.update(plugin_cmds.iteritems())
-    return d
-
-
-def get_all_cmds(plugins_override=True):
-    """Return canonical name and class for all registered commands."""
-    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
+    for k, v in d.iteritems():
         yield k,v
 
 
@@ -220,7 +224,6 @@
     # in a Unicode name. In that case, they should just get a
     # 'command not found' error later.
     # In the future, we may actually support Unicode command names.
-
     cmd = None
     # Get a command
     for hook in Command.hooks['get_command']:
@@ -231,12 +234,11 @@
             if not cmd.plugin_name():
                 break
     if cmd is None:
-        try:
-            plugin_metadata, provider = probe_for_provider(cmd_name)
-            raise errors.CommandAvailableInPlugin(cmd_name,
-                plugin_metadata, provider)
-        except errors.NoPluginAvailable:
-            pass
+        for hook in Command.hooks['get_missing_command']:
+            cmd = hook(cmd_name)
+            if cmd is not None:
+                break
+    if cmd is None:
         # No command found.
         raise KeyError
     # Allow plugins to extend commands
@@ -245,6 +247,16 @@
     return cmd
 
 
+def _try_plugin_provider(cmd_name):
+    """Probe for a plugin provider having cmd_name."""
+    try:
+        plugin_metadata, provider = probe_for_provider(cmd_name)
+        raise errors.CommandAvailableInPlugin(cmd_name,
+            plugin_metadata, provider)
+    except errors.NoPluginAvailable:
+        pass
+
+
 def probe_for_provider(cmd_name):
     """Look for a provider for cmd_name.
 
@@ -263,7 +275,7 @@
 
 def _get_bzr_command(cmd_or_None, cmd_name):
     """Get a command from bzr's core."""
-    cmds = _get_cmd_dict(plugins_override=False)
+    cmds = _builtin_commands()
     try:
         return cmds[cmd_name]()
     except KeyError:
@@ -684,24 +696,19 @@
             "Called when creating a single command. Called with "
             "(cmd_or_None, command_name). get_command should either return "
             "the cmd_or_None parameter, or a replacement Command object that "
-            "should be used for the command.", (1, 14), None))
+            "should be used for the command.", (1, 16), None))
+        self.create_hook(HookPoint('get_missing_command',
+            "Called when creating a single command if no command could be "
+            "found. Called with (command_name). get_missing_command should "
+            "either return None, or a Command object to be used for the "
+            "command.", (1, 16), None))
         self.create_hook(HookPoint('list_commands',
             "Called when enumerating commands. Called with a dict of "
             "cmd_name: cmd_class tuples for all the commands found "
             "so far. This dict is safe to mutate - to remove a command or "
             "to replace it with another (eg plugin supplied) version. "
             "list_commands should return the updated dict of commands.",
-            (1, 14), None))
-        # We currently ship default hooks to get builtin and plugin supplied
-        # command names.
-        self.install_named_hook("list_commands", _list_bzr_commands,
-            "bzr commands")
-        self.install_named_hook("get_command", _get_bzr_command,
-            "bzr commands")
-        self.install_named_hook("get_command", _get_plugin_command,
-            "bzr plugin commands")
-        self.install_named_hook("get_command", _get_external_command,
-            "bzr external command lookup")
+            (1, 16), None))
 
 Command.hooks = CommandHooks()
 
@@ -1046,6 +1053,20 @@
     return ignore_pipe
 
 
+def install_bzr_command_hooks():
+    """Install the hooks to supply bzr's own commands."""
+    Command.hooks.install_named_hook("list_commands", _list_bzr_commands,
+        "bzr commands")
+    Command.hooks.install_named_hook("get_command", _get_bzr_command,
+        "bzr commands")
+    Command.hooks.install_named_hook("get_command", _get_plugin_command,
+        "bzr plugin commands")
+    Command.hooks.install_named_hook("get_command", _get_external_command,
+        "bzr external command lookup")
+    Command.hooks.install_named_hook("get_missing_command", _try_plugin_provider,
+        "bzr plugin-provider-db check")
+
+
 def main(argv=None):
     """Main entry point of command-line interface.
 
@@ -1062,7 +1083,6 @@
 
     # Is this a final release version? If so, we should suppress warnings
     if bzrlib.version_info[3] == 'final':
-        from bzrlib import symbol_versioning
         symbol_versioning.suppress_deprecation_warnings(override=False)
     if argv is None:
         argv = osutils.get_unicode_argv()
@@ -1078,6 +1098,7 @@
         except UnicodeDecodeError:
             raise errors.BzrError("argv should be list of unicode strings.")
         argv = new_argv
+    install_bzr_command_hooks()
     ret = run_bzr_catch_errors(argv)
     trace.mutter("return code %d", ret)
     return ret

=== modified file 'bzrlib/shellcomplete.py'
--- a/bzrlib/shellcomplete.py	2009-03-23 14:59:43 +0000
+++ b/bzrlib/shellcomplete.py	2009-05-23 21:01:51 +0000
@@ -64,15 +64,16 @@
         outfile = sys.stdout
 
     cmds = []
-    for cmdname, cmdclass in commands.get_all_cmds():
-        cmds.append((cmdname, cmdclass))
-        for alias in cmdclass.aliases:
-            cmds.append((alias, cmdclass))
+    for cmdname in commands.all_command_names():
+        cmd = commands.get_cmd_object(cmdname)))
+        cmds.append((cmdname, cmd))
+        for alias in cmd.aliases:
+            cmds.append((alias, cmd))
     cmds.sort()
-    for cmdname, cmdclass in cmds:
-        if cmdclass.hidden:
+    for cmdname, cmd in cmds:
+        if cmd.hidden:
             continue
-        doc = getdoc(cmdclass)
+        doc = getdoc(cmd)
         if doc is None:
             outfile.write(cmdname + '\n')
         else:

=== modified file 'bzrlib/tests/test_commands.py'
--- a/bzrlib/tests/test_commands.py	2009-05-23 20:57:12 +0000
+++ b/bzrlib/tests/test_commands.py	2009-05-23 21:01:51 +0000
@@ -163,6 +163,7 @@
         del sys.modules['bzrlib.tests.fake_command']
         global lazy_command_imported
         lazy_command_imported = False
+        commands.install_bzr_command_hooks()
 
     @staticmethod
     def remove_fake():
@@ -205,6 +206,7 @@
         # commands are registered).
         # when they are simply created.
         hook_calls = []
+        commands.install_bzr_command_hooks()
         commands.Command.hooks.install_named_hook(
             "extend_command", hook_calls.append, None)
         # create a command, should not fire
@@ -244,6 +246,7 @@
     def test_fires_on_get_cmd_object(self):
         # The get_command(cmd) hook fires when commands are delivered to the
         # ui.
+        commands.install_bzr_command_hooks()
         hook_calls = []
         class ACommand(commands.Command):
             """A sample command."""
@@ -271,11 +274,42 @@
         self.assertIsInstance(hook_calls[0][1], builtins.cmd_info)
 
 
+class TestGetMissingCommandHook(tests.TestCase):
+
+    def test_fires_on_get_cmd_object(self):
+        # The get_missing_command(cmd) hook fires when commands are delivered to the
+        # ui.
+        hook_calls = []
+        class ACommand(commands.Command):
+            """A sample command."""
+        def get_missing_cmd(cmd_name):
+            hook_calls.append(('called', cmd_name))
+            if cmd_name in ('foo', 'info'):
+                return ACommand()
+        commands.Command.hooks.install_named_hook(
+            "get_missing_command", get_missing_cmd, None)
+        # create a command directly, should not fire
+        cmd = ACommand()
+        self.assertEqual([], hook_calls)
+        # ask by name, should fire and give us our command
+        cmd = commands.get_cmd_object('foo')
+        self.assertEqual([('called', 'foo')], hook_calls)
+        self.assertIsInstance(cmd, ACommand)
+        del hook_calls[:]
+        # ask by a name that is supplied by a builtin - the hook should not
+        # fire and we still get our object.
+        commands.install_bzr_command_hooks()
+        cmd = commands.get_cmd_object('info')
+        self.assertNotEqual(None, cmd)
+        self.assertEqual(0, len(hook_calls))
+
+
 class TestListCommandHook(tests.TestCase):
 
     def test_fires_on_all_command_names(self):
         # The list_commands() hook fires when all_command_names() is invoked.
         hook_calls = []
+        commands.install_bzr_command_hooks()
         def list_my_commands(cmd_names):
             hook_calls.append('called')
             cmd_names.update(['foo', 'bar'])

=== modified file 'bzrlib/tests/test_options.py'
--- a/bzrlib/tests/test_options.py	2009-04-03 20:05:25 +0000
+++ b/bzrlib/tests/test_options.py	2009-05-23 21:01:51 +0000
@@ -324,8 +324,8 @@
 
     def get_builtin_command_options(self):
         g = []
-        for cmd_name, cmd_class in sorted(commands.get_all_cmds()):
-            cmd = cmd_class()
+        for cmd_name in sorted(commands.all_command_names()):
+            cmd = commands.get_cmd_object(cmd_name)
             for opt_name, opt in sorted(cmd.options().items()):
                 g.append((cmd_name, opt))
         return g
@@ -338,14 +338,15 @@
         g = dict(option.Option.OPTIONS.items())
         used_globals = {}
         msgs = []
-        for cmd_name, cmd_class in sorted(commands.get_all_cmds()):
-            for option_or_name in sorted(cmd_class.takes_options):
+        for cmd_name in sorted(commands.all_command_names()):
+            cmd = commands.get_cmd_object(cmd_name)
+            for option_or_name in sorted(cmd.takes_options):
                 if not isinstance(option_or_name, basestring):
                     self.assertIsInstance(option_or_name, option.Option)
                 elif not option_or_name in g:
                     msgs.append("apparent reference to undefined "
                         "global option %r from %r"
-                        % (option_or_name, cmd_class))
+                        % (option_or_name, cmd))
                 else:
                     used_globals.setdefault(option_or_name, []).append(cmd_name)
         unused_globals = set(g.keys()) - set(used_globals.keys())




More information about the bazaar-commits mailing list