~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugin.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-05-28 00:25:32 UTC
  • mfrom: (5264.1.2 command-help-bug-177500)
  • Revision ID: pqm@pqm.ubuntu.com-20100528002532-9bzj1fajyxckd1rg
(lifeless) Stop raising at runtime when a command has no help,
 instead have a test in the test suite that checks all known command objects.
 (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
36
36
from bzrlib import osutils
37
37
 
38
38
from bzrlib.lazy_import import lazy_import
 
39
 
39
40
lazy_import(globals(), """
40
41
import imp
41
42
import re
51
52
from bzrlib import plugins as _mod_plugins
52
53
""")
53
54
 
 
55
from bzrlib.symbol_versioning import (
 
56
    deprecated_function,
 
57
    deprecated_in,
 
58
    )
 
59
 
54
60
 
55
61
DEFAULT_PLUGIN_PATH = None
56
62
_loaded = False
57
63
_plugins_disabled = False
58
64
 
59
65
 
60
 
plugin_warnings = {}
61
 
# Map from plugin name, to list of string warnings about eg plugin
62
 
# dependencies.
63
 
 
64
 
 
65
66
def are_plugins_disabled():
66
67
    return _plugins_disabled
67
68
 
76
77
    load_plugins([])
77
78
 
78
79
 
79
 
def describe_plugins(show_paths=False):
80
 
    """Generate text description of plugins.
81
 
 
82
 
    Includes both those that have loaded, and those that failed to 
83
 
    load.
84
 
 
85
 
    :param show_paths: If true,
86
 
    :returns: Iterator of text lines (including newlines.)
87
 
    """
88
 
    from inspect import getdoc
89
 
    loaded_plugins = plugins()
90
 
    all_names = sorted(list(set(
91
 
        loaded_plugins.keys() + plugin_warnings.keys())))
92
 
    for name in all_names:
93
 
        if name in loaded_plugins:
94
 
            plugin = loaded_plugins[name]
95
 
            version = plugin.__version__
96
 
            if version == 'unknown':
97
 
                version = ''
98
 
            yield '%s %s\n' % (name, version)
99
 
            d = getdoc(plugin.module)
100
 
            if d:
101
 
                doc = d.split('\n')[0]
102
 
            else:
103
 
                doc = '(no description)'
104
 
            yield ("  %s\n" % doc)
105
 
            if show_paths:
106
 
                yield ("   %s\n" % plugin.path())
107
 
            del plugin
108
 
        else:
109
 
            yield "%s (failed to load)\n" % name
110
 
        if name in plugin_warnings:
111
 
            for line in plugin_warnings[name]:
112
 
                yield "  ** " + line + '\n'
113
 
        yield '\n'
114
 
 
115
 
 
116
80
def _strip_trailing_sep(path):
117
81
    return path.rstrip("\\/")
118
82
 
119
83
 
120
 
def _get_specific_plugin_paths(paths):
121
 
    """Returns the plugin paths from a string describing the associations.
122
 
 
123
 
    :param paths: A string describing the paths associated with the plugins.
124
 
 
125
 
    :returns: A list of (plugin name, path) tuples.
126
 
 
127
 
    For example, if paths is my_plugin@/test/my-test:her_plugin@/production/her,
128
 
    [('my_plugin', '/test/my-test'), ('her_plugin', '/production/her')] 
129
 
    will be returned.
130
 
 
131
 
    Note that ':' in the example above depends on the os.
132
 
    """
133
 
    if not paths:
134
 
        return []
135
 
    specs = []
136
 
    for spec in paths.split(os.pathsep):
137
 
        try:
138
 
            name, path = spec.split('@')
139
 
        except ValueError:
140
 
            raise errors.BzrCommandError(
141
 
                '"%s" is not a valid <plugin_name>@<plugin_path> description '
142
 
                % spec)
143
 
        specs.append((name, path))
144
 
    return specs
145
 
 
146
 
 
147
84
def set_plugins_path(path=None):
148
85
    """Set the path for plugins to be loaded from.
149
86
 
161
98
        for name in disabled_plugins.split(os.pathsep):
162
99
            PluginImporter.blacklist.add('bzrlib.plugins.' + name)
163
100
    # Set up a the specific paths for plugins
164
 
    for plugin_name, plugin_path in _get_specific_plugin_paths(os.environ.get(
165
 
            'BZR_PLUGINS_AT', None)):
 
101
    specific_plugins = os.environ.get('BZR_PLUGINS_AT', None)
 
102
    if specific_plugins is not None:
 
103
        for spec in specific_plugins.split(os.pathsep):
 
104
            plugin_name, plugin_path = spec.split('@')
166
105
            PluginImporter.specific_paths[
167
106
                'bzrlib.plugins.%s' % plugin_name] = plugin_path
168
107
    return path
272
211
    """Load bzrlib plugins.
273
212
 
274
213
    The environment variable BZR_PLUGIN_PATH is considered a delimited
275
 
    set of paths to look through. Each entry is searched for `*.py`
 
214
    set of paths to look through. Each entry is searched for *.py
276
215
    files (and whatever other extensions are used in the platform,
277
 
    such as `*.pyd`).
 
216
    such as *.pyd).
278
217
 
279
218
    load_from_path() provides the underlying mechanism and is called with
280
219
    the default directory list to provide the normal behaviour.
363
302
    return None, None, (None, None, None)
364
303
 
365
304
 
366
 
def record_plugin_warning(plugin_name, warning_message):
367
 
    trace.mutter(warning_message)
368
 
    plugin_warnings.setdefault(plugin_name, []).append(warning_message)
369
 
 
370
 
 
371
305
def _load_plugin_module(name, dir):
372
306
    """Load plugin name from dir.
373
307
 
381
315
    except KeyboardInterrupt:
382
316
        raise
383
317
    except errors.IncompatibleAPI, e:
384
 
        warning_message = (
385
 
            "Unable to load plugin %r. It requested API version "
 
318
        trace.warning("Unable to load plugin %r. It requested API version "
386
319
            "%s of module %s but the minimum exported version is %s, and "
387
320
            "the maximum is %s" %
388
321
            (name, e.wanted, e.api, e.minimum, e.current))
389
 
        record_plugin_warning(name, warning_message)
390
322
    except Exception, e:
391
323
        trace.warning("%s" % e)
392
324
        if re.search('\.|-| ', name):
397
329
                    "file path isn't a valid module name; try renaming "
398
330
                    "it to %r." % (name, dir, sanitised_name))
399
331
        else:
400
 
            record_plugin_warning(
401
 
                name,
402
 
                'Unable to load plugin %r from %r' % (name, dir))
 
332
            trace.warning('Unable to load plugin %r from %r' % (name, dir))
403
333
        trace.log_exception_quietly()
404
334
        if 'error' in debug.debug_flags:
405
335
            trace.print_exception(sys.exc_info(), sys.stderr)
445
375
    return result
446
376
 
447
377
 
448
 
def format_concise_plugin_list():
449
 
    """Return a string holding a concise list of plugins and their version.
450
 
    """
451
 
    items = []
452
 
    for name, a_plugin in sorted(plugins().items()):
453
 
        items.append("%s[%s]" %
454
 
            (name, a_plugin.__version__))
455
 
    return ', '.join(items)
456
 
 
457
 
 
458
 
 
459
378
class PluginsHelpIndex(object):
460
379
    """A help index that returns help topics for plugins."""
461
380
 
643
562
        # We are called only for specific paths
644
563
        plugin_path = self.specific_paths[fullname]
645
564
        loading_path = None
 
565
        package = False
646
566
        if os.path.isdir(plugin_path):
647
567
            for suffix, mode, kind in imp.get_suffixes():
648
568
                if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
650
570
                    continue
651
571
                init_path = osutils.pathjoin(plugin_path, '__init__' + suffix)
652
572
                if os.path.isfile(init_path):
653
 
                    # We've got a module here and load_module needs specific
654
 
                    # parameters.
655
 
                    loading_path = plugin_path
656
 
                    suffix = ''
657
 
                    mode = ''
658
 
                    kind = imp.PKG_DIRECTORY
 
573
                    loading_path = init_path
 
574
                    package = True
659
575
                    break
660
576
        else:
661
577
            for suffix, mode, kind in imp.get_suffixes():
665
581
        if loading_path is None:
666
582
            raise ImportError('%s cannot be loaded from %s'
667
583
                              % (fullname, plugin_path))
668
 
        if kind is imp.PKG_DIRECTORY:
669
 
            f = None
670
 
        else:
671
 
            f = open(loading_path, mode)
 
584
        f = open(loading_path, mode)
672
585
        try:
673
586
            mod = imp.load_module(fullname, f, loading_path,
674
587
                                  (suffix, mode, kind))
 
588
            if package:
 
589
                # The plugin can contain modules, so be ready
 
590
                mod.__path__ = [plugin_path]
675
591
            mod.__package__ = fullname
676
592
            return mod
677
593
        finally:
678
 
            if f is not None:
679
 
                f.close()
 
594
            f.close()
680
595
 
681
596
 
682
597
# Install a dedicated importer for plugins requiring special handling