Rev 4001: Add a new hook Commands['extend_command'] for plugins that want to alter commands without overriding the entire command. in http://people.ubuntu.com/~robertc/baz2.0/extend_command

Robert Collins robertc at robertcollins.net
Wed Feb 11 12:50:00 GMT 2009


At http://people.ubuntu.com/~robertc/baz2.0/extend_command

------------------------------------------------------------
revno: 4001
revision-id: robertc at robertcollins.net-20090211124950-70c4aa2a8ub65d0s
parent: pqm at pqm.ubuntu.com-20090211011240-gv0zdxmwomt3ndtn
committer: Robert Collins <robertc at robertcollins.net>
branch nick: extend_command
timestamp: Wed 2009-02-11 23:49:50 +1100
message:
  Add a new hook Commands['extend_command'] for plugins that want to alter commands without overriding the entire command.
=== modified file 'NEWS'
--- a/NEWS	2009-02-11 00:29:57 +0000
+++ b/NEWS	2009-02-11 12:49:50 +0000
@@ -37,6 +37,10 @@
 
   INTERNALS:
 
+    * New hook Commands['extend_command'] to allow plugins to access a
+      command object before the command is run (or help generated from
+      it), without overriding the command. (Robert Collins)
+
 
 bzr 1.12rc1 "1234567890" 2009-02-10
 -----------------------------------

=== modified file 'bzrlib/commands.py'
--- a/bzrlib/commands.py	2009-02-04 04:10:14 +0000
+++ b/bzrlib/commands.py	2009-02-11 12:49:50 +0000
@@ -50,6 +50,7 @@
 
 from bzrlib import registry
 # Compatibility
+from bzrlib.hooks import Hooks
 from bzrlib.option import Option
 
 
@@ -170,7 +171,11 @@
         If true, plugin commands can override builtins.
     """
     try:
-        return _get_cmd_object(cmd_name, plugins_override)
+        cmd = _get_cmd_object(cmd_name, plugins_override)
+        # Allow plugins to extend commands
+        for hook in Command.hooks['extend_command']:
+            hook(cmd)
+        return cmd
     except KeyError:
         raise errors.BzrCommandError('unknown command "%s"' % cmd_name)
 
@@ -216,9 +221,8 @@
         except errors.NoPluginAvailable:
             pass
         else:
-            raise errors.CommandAvailableInPlugin(cmd_name, 
+            raise errors.CommandAvailableInPlugin(cmd_name,
                                                   plugin_metadata, provider)
-
     raise KeyError
 
 
@@ -279,6 +283,7 @@
             sys.stdout is forced to be a binary stream, and line-endings
             will not mangled.
 
+    :cvar hooks: An instance of CommandHooks.
     """
     aliases = []
     takes_args = []
@@ -573,6 +578,25 @@
             return None
 
 
+class CommandHooks(Hooks):
+    """Hooks related to Command object creation/enumeration."""
+
+    def __init__(self):
+        """Create the default hooks.
+
+        These are all empty initially, because by default nothing should get
+        notified.
+        """
+        Hooks.__init__(self)
+        # Introduced in 0.13:
+        # invoked after creating a command object to allow modifications such
+        # as adding or removing options, docs etc. Invoked with the command
+        # object.
+        self['extend_command'] = []
+
+Command.hooks = CommandHooks()
+
+
 def parse_args(command, argv, alias_argv=None):
     """Parse command line.
     
@@ -846,6 +870,7 @@
         # --verbose in their own way.
         option._verbosity_level = saved_verbosity_level
 
+
 def display_command(func):
     """Decorator that suppresses pipe/interrupt errors."""
     def ignore_pipe(*args, **kwargs):

=== modified file 'bzrlib/help_topics/en/hooks.txt'
--- a/bzrlib/help_topics/en/hooks.txt	2008-11-11 00:46:06 +0000
+++ b/bzrlib/help_topics/en/hooks.txt	2009-02-11 12:49:50 +0000
@@ -278,3 +278,14 @@
 The hook should return a new commit message template.
 
 (New in 1.10.)
+
+extend_command (Commands)
+-------------------------
+
+Invoked by the command line interface when constructing a command object, this
+hook permits the object to be altered after construction - for instance, to add
+options, hook into callbacks that that command offers or more.
+
+The hook signature is hook(cmd) -> None. 
+
+This hook was added in 1.13.

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2009-01-15 09:01:42 +0000
+++ b/bzrlib/tests/__init__.py	2009-02-11 12:49:50 +0000
@@ -808,12 +808,14 @@
             bzrlib.mutabletree.MutableTree: bzrlib.mutabletree.MutableTree.hooks,
             bzrlib.smart.client._SmartClient: bzrlib.smart.client._SmartClient.hooks,
             bzrlib.smart.server.SmartTCPServer: bzrlib.smart.server.SmartTCPServer.hooks,
+            bzrlib.commands.Command: bzrlib.commands.Command.hooks,
             }
         self.addCleanup(self._restoreHooks)
         # reset all hooks to an empty instance of the appropriate type
         bzrlib.branch.Branch.hooks = bzrlib.branch.BranchHooks()
         bzrlib.smart.client._SmartClient.hooks = bzrlib.smart.client.SmartClientHooks()
         bzrlib.smart.server.SmartTCPServer.hooks = bzrlib.smart.server.SmartServerHooks()
+        bzrlib.commands.Command.hooks = bzrlib.commands.CommandHooks()
 
     def _silenceUI(self):
         """Turn off UI for duration of test"""

=== modified file 'bzrlib/tests/test_commands.py'
--- a/bzrlib/tests/test_commands.py	2008-10-21 20:43:05 +0000
+++ b/bzrlib/tests/test_commands.py	2009-02-11 12:49:50 +0000
@@ -19,6 +19,7 @@
 import sys
 
 from bzrlib import (
+    builtins,
     commands,
     config,
     errors,
@@ -175,3 +176,45 @@
         self.addCleanup(self.remove_fake)
         fake_instance = commands.get_cmd_object('fake_alias')
         self.assertIsFakeCommand(fake_instance)
+
+
+class TestExtendCommandHook(tests.TestCase):
+
+    def test_fires_on_get_cmd_object(self):
+        # The extend_command(cmd) hook fires when commands are delivered to the
+        # ui, not simply at registration (because lazy registered plugin
+        # commands are registered).
+        # when they are simply created.
+        hook_calls = []
+        commands.Command.hooks.install_named_hook(
+            "extend_command", hook_calls.append, None)
+        # create a command, should not fire
+        class ACommand(commands.Command):
+            """A sample command."""
+        cmd = ACommand()
+        self.assertEqual([], hook_calls)
+        # -- as a builtin
+        # register the command class, should not fire
+        try:
+            builtins.cmd_test_extend_command_hook = ACommand
+            self.assertEqual([], hook_calls)
+            # and ask for the object, should fire
+            cmd = commands.get_cmd_object('test-extend-command-hook')
+            # For resilience - to ensure all code paths hit it - we
+            # fire on everything returned in the 'cmd_dict', which is currently
+            # all known commands, so assert that cmd is in hook_calls
+            self.assertSubset([cmd], hook_calls)
+            del hook_calls[:]
+        finally:
+            del builtins.cmd_test_extend_command_hook
+        # -- as a plugin lazy registration
+        try:
+            # register the command class, should not fire
+            commands.plugin_cmds.register_lazy('cmd_fake', [],
+                                               'bzrlib.tests.fake_command')
+            self.assertEqual([], hook_calls)
+            # and ask for the object, should fire
+            cmd = commands.get_cmd_object('fake')
+            self.assertEqual([cmd], hook_calls)
+        finally:
+            commands.plugin_cmds.remove('fake')

=== modified file 'bzrlib/tests/test_selftest.py'
--- a/bzrlib/tests/test_selftest.py	2009-01-08 16:57:10 +0000
+++ b/bzrlib/tests/test_selftest.py	2009-02-11 12:49:50 +0000
@@ -1444,10 +1444,15 @@
 
     def test_hooks_sanitised(self):
         """The bzrlib hooks should be sanitised by setUp."""
+        # Note this test won't fail with hooks that the core library doesn't
+        # use - but it trigger with a plugin that adds hooks, so its still a
+        # useful warning in that case.
         self.assertEqual(bzrlib.branch.BranchHooks(),
             bzrlib.branch.Branch.hooks)
         self.assertEqual(bzrlib.smart.server.SmartServerHooks(),
             bzrlib.smart.server.SmartTCPServer.hooks)
+        self.assertEqual(bzrlib.commands.CommandHooks(),
+            bzrlib.commands.Command.hooks)
 
     def test__gather_lsprof_in_benchmarks(self):
         """When _gather_lsprof_in_benchmarks is on, accumulate profile data.




More information about the bazaar-commits mailing list