[Pkg-bazaar-commits] ./bzr/unstable r737: - add plugin patch, still being integrated

Martin Pool mbp at sourcefrog.net
Fri Apr 10 08:13:45 UTC 2009


------------------------------------------------------------
revno: 737
committer: Martin Pool <mbp at sourcefrog.net>
timestamp: Wed 2005-06-22 16:38:18 +1000
message:
  - add plugin patch, still being integrated
added:
  patches/plugins-no-plugins.patch
-------------- next part --------------
=== added file 'patches/plugins-no-plugins.patch'
--- a/patches/plugins-no-plugins.patch	1970-01-01 00:00:00 +0000
+++ b/patches/plugins-no-plugins.patch	2005-06-22 06:38:18 +0000
@@ -0,0 +1,519 @@
+*** added file 'bzrlib/plugin.py'
+--- /dev/null
++++ bzrlib/plugin.py
+@@ -0,0 +1,92 @@
++# Copyright (C) 2004, 2005 by Canonical Ltd
++
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation; either version 2 of the License, or
++# (at your option) any later version.
++
++# This program is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++
++# You should have received a copy of the GNU General Public License
++# along with this program; if not, write to the Free Software
++# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++
++
++# This module implements plug-in support.
++# Any python module in $BZR_PLUGIN_PATH will be imported upon initialization
++# of bzrlib (and then forgotten about).  In the plugin's main body, it should
++# update any bzrlib registries it wants to extend; for example, to add new
++# commands, import bzrlib.commands and add your new command to the
++# plugin_cmds variable.
++
++import sys, os, imp
++try:
++    set
++except NameError:
++    from sets import Set as set
++from bzrlib.trace import log_error
++
++
++def load_plugins():
++    """Find all python files which are plugins, and load them
++
++    The environment variable BZR_PLUGIN_PATH is considered a delimited set of
++    paths to look through. Each entry is searched for *.py files (and whatever
++    other extensions are used in the platform, such as *.pyd).
++    """
++    bzrpath = os.environ.get('BZR_PLUGIN_PATH', os.path.expanduser('~/.bzr/plugins'))
++
++    # The problem with imp.get_suffixes() is that it doesn't include
++    # .pyo which is technically valid
++    # It also means that "testmodule.so" will show up as both test and testmodule
++    # though it is only valid as 'test'
++    # but you should be careful, because "testmodule.py" loads as testmodule.
++    suffixes = imp.get_suffixes()
++    suffixes.append(('.pyo', 'rb', imp.PY_COMPILED))
++    package_entries = ['__init__.py', '__init__.pyc', '__init__.pyo']
++    for d in bzrpath.split(os.pathsep):
++        # going trough them one by one allows different plugins with the same
++        # filename in different directories in the path
++        if not d:
++            continue
++        plugin_names = set()
++        if not os.path.isdir(d):
++            continue
++        for f in os.listdir(d):
++            path = os.path.join(d, f)
++            if os.path.isdir(path):
++                for entry in package_entries:
++                    # This directory should be a package, and thus added to
++                    # the list
++                    if os.path.isfile(os.path.join(path, entry)):
++                        break
++                else: # This directory is not a package
++                    continue
++            else:
++                for suffix_info in suffixes:
++                    if f.endswith(suffix_info[0]):
++                        f = f[:-len(suffix_info[0])]
++                        if suffix_info[2] == imp.C_EXTENSION and f.endswith('module'):
++                            f = f[:-len('module')]
++                        break
++                else:
++                    continue
++            plugin_names.add(f)
++
++        plugin_names = list(plugin_names)
++        plugin_names.sort()
++        for name in plugin_names:
++            try:
++                plugin_info = imp.find_module(name, [d])
++                try:
++                    plugin = imp.load_module('bzrlib.plugin.' + name,
++                                             *plugin_info)
++                finally:
++                    if plugin_info[0] is not None:
++                        plugin_info[0].close()
++            except Exception, e:
++                log_error('Unable to load plugin: %r from %r\n%s' % (name, d, e))
++
+
+*** modified file 'bzrlib/__init__.py'
+--- bzrlib/__init__.py
++++ bzrlib/__init__.py
+@@ -23,6 +23,7@@
+ from diff import compare_trees
+ from trace import mutter, warning, open_tracefile
+ from log import show_log
++from plugin import load_plugins
+ import add
+
+ BZRDIR = ".bzr"
+@@ -62,4 +63,4 @@
+             return None
+     except BzrError:
+         return None
+-
++
+
+*** modified file 'bzrlib/commands.py'
+--- bzrlib/commands.py
++++ bzrlib/commands.py
+@@ -24,6 +24,24 @@
+ from bzrlib.osutils import quotefn
+ from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
+      format_date
++
++
++plugin_cmds = {}
++
++
++def register_plugin_command(cmd):
++    "Utility function to help register a command"
++    global plugin_cmds
++    k = cmd.__name__
++    if k.startswith("cmd_"):
++        k_unsquished = _unsquish_command_name(k)
++    else:
++        k_unsquished = k
++    if not plugin_cmds.has_key(k_unsquished):
++        plugin_cmds[k_unsquished] = cmd
++    else:
++        log_error('Two plugins defined the same command: %r' % k)
++        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
+
+
+ def _squish_command_name(cmd):
+@@ -68,100 +86,34 @@
+         revs = int(revstr)
+     return revs
+
+-def _find_plugins():
+-    """Find all python files which are plugins, and load their commands
+-    to add to the list of "all commands"
+-
+-    The environment variable BZRPATH is considered a delimited set of
+-    paths to look through. Each entry is searched for *.py files.
+-    If a directory is found, it is also searched, but they are
+-    not searched recursively. This allows you to revctl the plugins.
+-
+-    Inside the plugin should be a series of cmd_* function, which inherit from
+-    the bzrlib.commands.Command class.
+-    """
+-    bzrpath = os.environ.get('BZRPLUGINPATH', '')
+-
+-    plugin_cmds = {}
+-    if not bzrpath:
+-        return plugin_cmds
+-    _platform_extensions = {
+-        'win32':'.pyd',
+-        'cygwin':'.dll',
+-        'darwin':'.dylib',
+-        'linux2':'.so'
+-        }
+-    if _platform_extensions.has_key(sys.platform):
+-        platform_extension = _platform_extensions[sys.platform]
+-    else:
+-        platform_extension = None
+-    for d in bzrpath.split(os.pathsep):
+-        plugin_names = {} # This should really be a set rather than a dict
+-        for f in os.listdir(d):
+-            if f.endswith('.py'):
+-                f = f[:-3]
+-            elif f.endswith('.pyc') or f.endswith('.pyo'):
+-                f = f[:-4]
+-            elif platform_extension and f.endswith(platform_extension):
+-                f = f[:-len(platform_extension)]
+-                if f.endswidth('module'):
+-                    f = f[:-len('module')]
+-            else:
+-                continue
+-            if not plugin_names.has_key(f):
+-                plugin_names[f] = True
+-
+-        plugin_names = plugin_names.keys()
+-        plugin_names.sort()
+-        try:
+-            sys.path.insert(0, d)
+-            for name in plugin_names:
+-                try:
+-                    old_module = None
+-                    try:
+-                        if sys.modules.has_key(name):
+-                            old_module = sys.modules[name]
+-                            del sys.modules[name]
+-                        plugin = __import__(name, locals())
+-                        for k in dir(plugin):
+-                            if k.startswith('cmd_'):
+-                                k_unsquished = _unsquish_command_name(k)
+-                                if not plugin_cmds.has_key(k_unsquished):
+-                                    plugin_cmds[k_unsquished] = getattr(plugin, k)
+-                                else:
+-                                    log_error('Two plugins defined the same command: %r' % k)
+-                                    log_error('Not loading the one in %r in dir %r' % (name, d))
+-                    finally:
+-                        if old_module:
+-                            sys.modules[name] = old_module
+-                except ImportError, e:
+-                    log_error('Unable to load plugin: %r from %r\n%s' % (name, d, e))
+-        finally:
+-            sys.path.pop(0)
+-    return plugin_cmds
+-
+-def _get_cmd_dict(include_plugins=True):
++def _get_cmd_dict(plugins_override=True):
+     d = {}
+     for k, v in globals().iteritems():
+         if k.startswith("cmd_"):
+             d[_unsquish_command_name(k)] = v
+-    if include_plugins:
+-        d.update(_find_plugins())
++    # If we didn't load plugins, the plugin_cmds dict will be empty
++    if plugins_override:
++        d.update(plugin_cmds)
++    else:
++        d2 = {}
++        d2.update(plugin_cmds)
++        d2.update(d)
++        d = d2
+     return d
+
+-def get_all_cmds(include_plugins=True):
++def get_all_cmds(plugins_override=True):
+     """Return canonical name and class for all registered commands."""
+-    for k, v in _get_cmd_dict(include_plugins=include_plugins).iteritems():
++    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
+         yield k,v
+
+
+-def get_cmd_class(cmd,include_plugins=True):
++def get_cmd_class(cmd, plugins_override=True):
+     """Return the canonical name and command class for a command.
+     """
+     cmd = str(cmd)                      # not unicode
+
+     # first look up this command under the specified name
+-    cmds = _get_cmd_dict(include_plugins=include_plugins)
++    cmds = _get_cmd_dict(plugins_override=plugins_override)
+     try:
+         return cmd, cmds[cmd]
+     except KeyError:
+@@ -1461,6 +1413,75 @@
+     return argdict
+
+
++def _parse_master_args(argv):
++    """Parse the arguments that always go with the original command.
++    These are things like bzr --no-plugins, etc.
++
++    There are now 2 types of option flags. Ones that come *before* the command,
++    and ones that come *after* the command.
++    Ones coming *before* the command are applied against all possible commands.
++    And are generally applied before plugins are loaded.
++
++    The current list are:
++        --builtin   Allow plugins to load, but don't let them override builtin commands,
++                    they will still be allowed if they do not override a builtin.
++        --no-plugins    Don't load any plugins. This lets you get back to official source
++                        behavior.
++        --profile   Enable the hotspot profile before running the command.
++                    For backwards compatibility, this is also a non-master option.
++        --version   Spit out the version of bzr that is running and exit.
++                    This is also a non-master option.
++        --help      Run help and exit, also a non-master option (I think that should stay, though)
++
++    >>> argv, opts = _parse_master_args(['bzr', '--test'])
++    Traceback (most recent call last):
++    ...
++    BzrCommandError: Invalid master option: 'test'
++    >>> argv, opts = _parse_master_args(['bzr', '--version', 'command'])
++    >>> print argv
++    ['command']
++    >>> print opts['version']
++    True
++    >>> argv, opts = _parse_master_args(['bzr', '--profile', 'command', '--more-options'])
++    >>> print argv
++    ['command', '--more-options']
++    >>> print opts['profile']
++    True
++    >>> argv, opts = _parse_master_args(['bzr', '--no-plugins', 'command'])
++    >>> print argv
++    ['command']
++    >>> print opts['no-plugins']
++    True
++    >>> print opts['profile']
++    False
++    >>> argv, opts = _parse_master_args(['bzr', 'command', '--profile'])
++    >>> print argv
++    ['command', '--profile']
++    >>> print opts['profile']
++    False
++    """
++    master_opts = {'builtin':False,
++        'no-plugins':False,
++        'version':False,
++        'profile':False,
++        'help':False
++    }
++
++    # This is the point where we could hook into argv[0] to determine
++    # what front-end is supposed to be run
++    # For now, we are just ignoring it.
++    cmd_name = argv.pop(0)
++    for arg in argv[:]:
++        if arg[:2] != '--': # at the first non-option, we return the rest
++            break
++        arg = arg[2:] # Remove '--'
++        if arg not in master_opts:
++            # We could say that this is not an error, that we should
++            # just let it be handled by the main section instead
++            raise BzrCommandError('Invalid master option: %r' % arg)
++        argv.pop(0) # We are consuming this entry
++        master_opts[arg] = True
++    return argv, master_opts
+
+ def run_bzr(argv):
+     """Execute a command.
+@@ -1470,22 +1491,21 @@
+     """
+     argv = [a.decode(bzrlib.user_encoding) for a in argv]
+
+-    include_plugins=True
+     try:
+-        args, opts = parse_args(argv[1:])
+-        if 'help' in opts:
++        argv, master_opts = _parse_master_args(argv)
++        if not master_opts['no-plugins']:
++            bzrlib.load_plugins()
++        args, opts = parse_args(argv)
++        if 'help' in opts or master_opts['help']:
+             import help
+             if args:
+                 help.help(args[0])
+             else:
+                 help.help()
+             return 0
+-        elif 'version' in opts:
++        elif 'version' in opts or master_opts['version']:
+             show_version()
+             return 0
+-        elif args and args[0] == 'builtin':
+-            include_plugins=False
+-            args = args[1:]
+         cmd = str(args.pop(0))
+     except IndexError:
+         import help
+@@ -1493,14 +1513,15 @@
+         return 1
+
+
+-    canonical_cmd, cmd_class = get_cmd_class(cmd,include_plugins=include_plugins)
+-
+-    # global option
++    plugins_override = not (master_opts['builtin'])
++    canonical_cmd, cmd_class = get_cmd_class(cmd, plugins_override=plugins_override)
++
++    profile = master_opts['profile']
++    # For backwards compatibility, I would rather stick with --profile being a
++    # master/global option
+     if 'profile' in opts:
+         profile = True
+         del opts['profile']
+-    else:
+-        profile = False
+
+     # check options are reasonable
+     allowed = cmd_class.takes_options
+
+*** modified file 'testbzr'
+--- testbzr
++++ testbzr
+@@ -149,6 +149,7 @@
+     """Run a test involving creating a plugin to load,
+     and making sure it is seen properly.
+     """
++    orig_help = backtick('bzr help commands') # No plugins yet
+     mkdir('plugin_test')
+     f = open(os.path.join('plugin_test', 'myplug.py'), 'wb')
+     f.write("""import bzrlib, bzrlib.commands
+@@ -157,24 +158,36 @@
+     aliases = ['mplg']
+     def run(self):
+         print 'Hello from my plugin'
++class cmd_myplug_with_opt(bzrlib.commands.Command):
++    '''A simple plugin that requires a special option'''
++    takes_options = ['aspecialoptionthatdoesntexist']
++    def run(self, aspecialoptionthatdoesntexist=None):
++        print 'Found: %s' % aspecialoptionthatdoesntexist
++
++bzrlib.commands.register_plugin_command(cmd_myplug)
++bzrlib.commands.register_plugin_command(cmd_myplug_with_opt)
++bzrlib.commands.OPTIONS['aspecialoptionthatdoesntexist'] = str
+ """)
+     f.close()
+
+-    os.environ['BZRPLUGINPATH'] = os.path.abspath('plugin_test')
+-    help = backtick('bzr help commands')
++    os.environ['BZR_PLUGIN_PATH'] = os.path.abspath('plugin_test')
++    help = backtick('bzr help commands') #Help with user-visible plugins
+     assert help.find('myplug') != -1
+     assert help.find('Just a simple test plugin.') != -1
+
+
+     assert backtick('bzr myplug') == 'Hello from my plugin\n'
+     assert backtick('bzr mplg') == 'Hello from my plugin\n'
++    assert backtick('bzr myplug-with-opt') == 'Found: None\n'
++    assert backtick('bzr myplug-with-opt --aspecialoptionthatdoesntexist=2') == 'Found: 2\n'
+
+     f = open(os.path.join('plugin_test', 'override.py'), 'wb')
+     f.write("""import bzrlib, bzrlib.commands
+-class cmd_commit(bzrlib.commands.cmd_commit):
+-    '''Commit changes into a new revision.'''
++class cmd_revno(bzrlib.commands.cmd_revno):
++    '''Show current revision number.'''
+     def run(self, *args, **kwargs):
+         print "I'm sorry dave, you can't do that"
++        return 1
+
+ class cmd_help(bzrlib.commands.cmd_help):
+     '''Show help on a command or other topic.'''
+@@ -182,16 +195,67 @@
+         print "You have been overridden"
+         bzrlib.commands.cmd_help.run(self, *args, **kwargs)
+
++bzrlib.commands.register_plugin_command(cmd_revno)
++bzrlib.commands.register_plugin_command(cmd_help)
+ """)
+     f.close()
+
+-    newhelp = backtick('bzr help commands')
++    newhelp = backtick('bzr help commands') # Help with no new commands,
+     assert newhelp.startswith('You have been overridden\n')
+     # We added a line, but the rest should work
+     assert newhelp[25:] == help
+-
+-    assert backtick('bzr commit -m test') == "I'm sorry dave, you can't do that\n"
+-
++    # Make sure we can get back to the original command
++    # Not overridden, and no extra commands present
++    assert backtick('bzr --builtin help commands') == help
++    assert backtick('bzr --no-plugins help commands') == orig_help
++
++    assert backtick('bzr revno', retcode=1) == "I'm sorry dave, you can't do that\n"
++
++    print_txt = '** Loading noop plugin'
++    f = open(os.path.join('plugin_test', 'loading.py'), 'wb')
++    f.write("""import bzrlib, bzrlib.commands
++class cmd_noop(bzrlib.commands.Command):
++    def run(self, *args, **kwargs):
++        pass
++
++print %r
++bzrlib.commands.register_plugin_command(cmd_noop)
++""" % print_txt)
++    f.close()
++    print_txt += '\n'
++
++    # Check that --builtin still loads the plugin, and enables it as
++    # an extra command, but not as an override
++    # and that --no-plugins doesn't load the command at all
++    assert backtick('bzr noop') == print_txt
++    assert backtick('bzr --builtin help')[:len(print_txt)] == print_txt
++    assert backtick('bzr --no-plugins help')[:len(print_txt)] != print_txt
++    runcmd('bzr revno', retcode=1)
++    runcmd('bzr --builtin revno', retcode=0)
++    runcmd('bzr --no-plugins revno', retcode=0)
++    runcmd('bzr --builtin noop', retcode=0)
++    runcmd('bzr --no-plugins noop', retcode=1)
++
++    # Check that packages can also be loaded
++    test_str = 'packages work'
++    os.mkdir(os.path.join('plugin_test', 'testpkg'))
++    f = open(os.path.join('plugin_test', 'testpkg', '__init__.py'), 'wb')
++    f.write("""import bzrlib, bzrlib.commands
++class testpkgcmd(bzrlib.commands.Command):
++    def run(self, *args, **kwargs):
++        print %r
++
++bzrlib.commands.register_plugin_command(testpkgcmd)
++""" % test_str)
++    f.close()
++    test_str += '\n'
++    assert backtick('bzr testpkgcmd') == print_txt + test_str
++    runcmd('bzr --no-plugins testpkgcmd', retcode=1)
++
++    # Make sure that setting BZR_PLUGIN_PATH to empty is the same as using --no-plugins
++    os.environ['BZR_PLUGIN_PATH'] = ''
++    assert backtick('bzr help commands') == orig_help
++
+     shutil.rmtree('plugin_test')
+
+ try:
+@@ -221,6 +285,9 @@
+
+     runcmd(['mkdir', TESTDIR])
+     cd(TESTDIR)
++    # This means that any command that is naively run in this directory
++    # Won't affect the parent directory.
++    runcmd('bzr init')
+     test_root = os.getcwd()
+
+     progress("introductory commands")
+



More information about the Pkg-bazaar-commits mailing list