/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/plugin.py

More work on roundtrip push support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2007 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
18
 
"""bzr python plugin support.
19
 
 
20
 
When load_plugins() is invoked, any python module in any directory in
21
 
$BZR_PLUGIN_PATH will be imported.  The module will be imported as
22
 
'bzrlib.plugins.$BASENAME(PLUGIN)'.  In the plugin's main body, it should
23
 
update any bzrlib registries it wants to extend; for example, to add new
24
 
commands, import bzrlib.commands and add your new command to the plugin_cmds
25
 
variable.
26
 
 
27
 
BZR_PLUGIN_PATH is also honoured for any plugins imported via
28
 
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been 
29
 
called.
30
 
"""
31
 
 
32
 
import os
33
 
import sys
34
 
 
35
 
from bzrlib.lazy_import import lazy_import
36
 
lazy_import(globals(), """
37
 
import imp
38
 
import re
39
 
import types
40
 
import zipfile
41
 
 
42
 
from bzrlib import (
43
 
    config,
44
 
    osutils,
45
 
    )
46
 
from bzrlib import plugins as _mod_plugins
47
 
""")
48
 
 
49
 
from bzrlib.symbol_versioning import deprecated_function, one_three
50
 
from bzrlib.trace import mutter, warning, log_exception_quietly
51
 
 
52
 
 
53
 
DEFAULT_PLUGIN_PATH = None
54
 
_loaded = False
55
 
 
56
 
def get_default_plugin_path():
57
 
    """Get the DEFAULT_PLUGIN_PATH"""
58
 
    global DEFAULT_PLUGIN_PATH
59
 
    if DEFAULT_PLUGIN_PATH is None:
60
 
        DEFAULT_PLUGIN_PATH = osutils.pathjoin(config.config_dir(), 'plugins')
61
 
    return DEFAULT_PLUGIN_PATH
62
 
 
63
 
 
64
 
def disable_plugins():
65
 
    """Disable loading plugins.
66
 
 
67
 
    Future calls to load_plugins() will be ignored.
68
 
    """
69
 
    # TODO: jam 20060131 This should probably also disable
70
 
    #       load_from_dirs()
71
 
    global _loaded
72
 
    _loaded = True
73
 
 
74
 
 
75
 
def _strip_trailing_sep(path):
76
 
    return path.rstrip("\\/")
77
 
 
78
 
 
79
 
def set_plugins_path():
80
 
    """Set the path for plugins to be loaded from."""
81
 
    path = os.environ.get('BZR_PLUGIN_PATH',
82
 
                          get_default_plugin_path()).split(os.pathsep)
83
 
    bzr_exe = bool(getattr(sys, 'frozen', None))
84
 
    if bzr_exe:    # expand path for bzr.exe
85
 
        # We need to use relative path to system-wide plugin
86
 
        # directory because bzrlib from standalone bzr.exe
87
 
        # could be imported by another standalone program
88
 
        # (e.g. bzr-config; or TortoiseBzr/Olive if/when they
89
 
        # will become standalone exe). [bialix 20071123]
90
 
        # __file__ typically is
91
 
        # C:\Program Files\Bazaar\lib\library.zip\bzrlib\plugin.pyc
92
 
        # then plugins directory is
93
 
        # C:\Program Files\Bazaar\plugins
94
 
        # so relative path is ../../../plugins
95
 
        path.append(osutils.abspath(osutils.pathjoin(
96
 
            osutils.dirname(__file__), '../../../plugins')))
97
 
    # Get rid of trailing slashes, since Python can't handle them when
98
 
    # it tries to import modules.
99
 
    path = map(_strip_trailing_sep, path)
100
 
    if not bzr_exe:     # don't look inside library.zip
101
 
        # search the plugin path before the bzrlib installed dir
102
 
        path.append(os.path.dirname(_mod_plugins.__file__))
103
 
    _mod_plugins.__path__ = path
104
 
    return path
105
 
 
106
 
 
107
 
def load_plugins():
108
 
    """Load bzrlib plugins.
109
 
 
110
 
    The environment variable BZR_PLUGIN_PATH is considered a delimited
111
 
    set of paths to look through. Each entry is searched for *.py
112
 
    files (and whatever other extensions are used in the platform,
113
 
    such as *.pyd).
114
 
 
115
 
    load_from_dirs() provides the underlying mechanism and is called with
116
 
    the default directory list to provide the normal behaviour.
117
 
    """
118
 
    global _loaded
119
 
    if _loaded:
120
 
        # People can make sure plugins are loaded, they just won't be twice
121
 
        return
122
 
    _loaded = True
123
 
 
124
 
    # scan for all plugins in the path.
125
 
    load_from_path(set_plugins_path())
126
 
 
127
 
 
128
 
def load_from_path(dirs):
129
 
    """Load bzrlib plugins found in each dir in dirs.
130
 
 
131
 
    Loading a plugin means importing it into the python interpreter.
132
 
    The plugin is expected to make calls to register commands when
133
 
    it's loaded (or perhaps access other hooks in future.)
134
 
 
135
 
    Plugins are loaded into bzrlib.plugins.NAME, and can be found there
136
 
    for future reference.
137
 
 
138
 
    The python module path for bzrlib.plugins will be modified to be 'dirs'.
139
 
    """
140
 
    # We need to strip the trailing separators here as well as in the
141
 
    # set_plugins_path function because calling code can pass anything in to
142
 
    # this function, and since it sets plugins.__path__, it should set it to
143
 
    # something that will be valid for Python to use (in case people try to
144
 
    # run "import bzrlib.plugins.PLUGINNAME" after calling this function).
145
 
    _mod_plugins.__path__ = map(_strip_trailing_sep, dirs)
146
 
    for d in dirs:
147
 
        if not d:
148
 
            continue
149
 
        mutter('looking for plugins in %s', d)
150
 
        if os.path.isdir(d):
151
 
            load_from_dir(d)
152
 
 
153
 
 
154
 
# backwards compatability: load_from_dirs was the old name
155
 
# This was changed in 0.15
156
 
load_from_dirs = load_from_path
157
 
 
158
 
 
159
 
def load_from_dir(d):
160
 
    """Load the plugins in directory d."""
161
 
    # Get the list of valid python suffixes for __init__.py?
162
 
    # this includes .py, .pyc, and .pyo (depending on if we are running -O)
163
 
    # but it doesn't include compiled modules (.so, .dll, etc)
164
 
    valid_suffixes = [suffix for suffix, mod_type, flags in imp.get_suffixes()
165
 
                              if flags in (imp.PY_SOURCE, imp.PY_COMPILED)]
166
 
    package_entries = ['__init__'+suffix for suffix in valid_suffixes]
167
 
    plugin_names = set()
168
 
    for f in os.listdir(d):
169
 
        path = osutils.pathjoin(d, f)
170
 
        if os.path.isdir(path):
171
 
            for entry in package_entries:
172
 
                # This directory should be a package, and thus added to
173
 
                # the list
174
 
                if os.path.isfile(osutils.pathjoin(path, entry)):
175
 
                    break
176
 
            else: # This directory is not a package
177
 
                continue
178
 
        else:
179
 
            for suffix_info in imp.get_suffixes():
180
 
                if f.endswith(suffix_info[0]):
181
 
                    f = f[:-len(suffix_info[0])]
182
 
                    if suffix_info[2] == imp.C_EXTENSION and f.endswith('module'):
183
 
                        f = f[:-len('module')]
184
 
                    break
185
 
            else:
186
 
                continue
187
 
        if getattr(_mod_plugins, f, None):
188
 
            mutter('Plugin name %s already loaded', f)
189
 
        else:
190
 
            # mutter('add plugin name %s', f)
191
 
            plugin_names.add(f)
192
 
    
193
 
    for name in plugin_names:
194
 
        try:
195
 
            exec "import bzrlib.plugins.%s" % name in {}
196
 
        except KeyboardInterrupt:
197
 
            raise
198
 
        except Exception, e:
199
 
            ## import pdb; pdb.set_trace()
200
 
            if re.search('\.|-| ', name):
201
 
                sanitised_name = re.sub('[-. ]', '_', name)
202
 
                if sanitised_name.startswith('bzr_'):
203
 
                    sanitised_name = sanitised_name[len('bzr_'):]
204
 
                warning("Unable to load %r in %r as a plugin because the "
205
 
                        "file path isn't a valid module name; try renaming "
206
 
                        "it to %r." % (name, d, sanitised_name))
207
 
            else:
208
 
                warning('Unable to load plugin %r from %r' % (name, d))
209
 
            log_exception_quietly()
210
 
 
211
 
 
212
 
@deprecated_function(one_three)
213
 
def load_from_zip(zip_name):
214
 
    """Load all the plugins in a zip."""
215
 
    valid_suffixes = ('.py', '.pyc', '.pyo')    # only python modules/packages
216
 
                                                # is allowed
217
 
    try:
218
 
        index = zip_name.rindex('.zip')
219
 
    except ValueError:
220
 
        return
221
 
    archive = zip_name[:index+4]
222
 
    prefix = zip_name[index+5:]
223
 
 
224
 
    mutter('Looking for plugins in %r', zip_name)
225
 
 
226
 
    # use zipfile to get list of files/dirs inside zip
227
 
    try:
228
 
        z = zipfile.ZipFile(archive)
229
 
        namelist = z.namelist()
230
 
        z.close()
231
 
    except zipfile.error:
232
 
        # not a valid zip
233
 
        return
234
 
 
235
 
    if prefix:
236
 
        prefix = prefix.replace('\\','/')
237
 
        if prefix[-1] != '/':
238
 
            prefix += '/'
239
 
        ix = len(prefix)
240
 
        namelist = [name[ix:]
241
 
                    for name in namelist
242
 
                    if name.startswith(prefix)]
243
 
 
244
 
    mutter('Names in archive: %r', namelist)
245
 
    
246
 
    for name in namelist:
247
 
        if not name or name.endswith('/'):
248
 
            continue
249
 
    
250
 
        # '/' is used to separate pathname components inside zip archives
251
 
        ix = name.rfind('/')
252
 
        if ix == -1:
253
 
            head, tail = '', name
254
 
        else:
255
 
            head, tail = name.rsplit('/',1)
256
 
        if '/' in head:
257
 
            # we don't need looking in subdirectories
258
 
            continue
259
 
    
260
 
        base, suffix = osutils.splitext(tail)
261
 
        if suffix not in valid_suffixes:
262
 
            continue
263
 
    
264
 
        if base == '__init__':
265
 
            # package
266
 
            plugin_name = head
267
 
        elif head == '':
268
 
            # module
269
 
            plugin_name = base
270
 
        else:
271
 
            continue
272
 
    
273
 
        if not plugin_name:
274
 
            continue
275
 
        if getattr(_mod_plugins, plugin_name, None):
276
 
            mutter('Plugin name %s already loaded', plugin_name)
277
 
            continue
278
 
    
279
 
        try:
280
 
            exec "import bzrlib.plugins.%s" % plugin_name in {}
281
 
            mutter('Load plugin %s from zip %r', plugin_name, zip_name)
282
 
        except KeyboardInterrupt:
283
 
            raise
284
 
        except Exception, e:
285
 
            ## import pdb; pdb.set_trace()
286
 
            warning('Unable to load plugin %r from %r'
287
 
                    % (name, zip_name))
288
 
            log_exception_quietly()
289
 
 
290
 
 
291
 
def plugins():
292
 
    """Return a dictionary of the plugins.
293
 
    
294
 
    Each item in the dictionary is a PlugIn object.
295
 
    """
296
 
    result = {}
297
 
    for name, plugin in _mod_plugins.__dict__.items():
298
 
        if isinstance(plugin, types.ModuleType):
299
 
            result[name] = PlugIn(name, plugin)
300
 
    return result
301
 
 
302
 
 
303
 
class PluginsHelpIndex(object):
304
 
    """A help index that returns help topics for plugins."""
305
 
 
306
 
    def __init__(self):
307
 
        self.prefix = 'plugins/'
308
 
 
309
 
    def get_topics(self, topic):
310
 
        """Search for topic in the loaded plugins.
311
 
 
312
 
        This will not trigger loading of new plugins.
313
 
 
314
 
        :param topic: A topic to search for.
315
 
        :return: A list which is either empty or contains a single
316
 
            RegisteredTopic entry.
317
 
        """
318
 
        if not topic:
319
 
            return []
320
 
        if topic.startswith(self.prefix):
321
 
            topic = topic[len(self.prefix):]
322
 
        plugin_module_name = 'bzrlib.plugins.%s' % topic
323
 
        try:
324
 
            module = sys.modules[plugin_module_name]
325
 
        except KeyError:
326
 
            return []
327
 
        else:
328
 
            return [ModuleHelpTopic(module)]
329
 
 
330
 
 
331
 
class ModuleHelpTopic(object):
332
 
    """A help topic which returns the docstring for a module."""
333
 
 
334
 
    def __init__(self, module):
335
 
        """Constructor.
336
 
 
337
 
        :param module: The module for which help should be generated.
338
 
        """
339
 
        self.module = module
340
 
 
341
 
    def get_help_text(self, additional_see_also=None):
342
 
        """Return a string with the help for this topic.
343
 
 
344
 
        :param additional_see_also: Additional help topics to be
345
 
            cross-referenced.
346
 
        """
347
 
        if not self.module.__doc__:
348
 
            result = "Plugin '%s' has no docstring.\n" % self.module.__name__
349
 
        else:
350
 
            result = self.module.__doc__
351
 
        if result[-1] != '\n':
352
 
            result += '\n'
353
 
        # there is code duplicated here and in bzrlib/help_topic.py's 
354
 
        # matching Topic code. This should probably be factored in
355
 
        # to a helper function and a common base class.
356
 
        if additional_see_also is not None:
357
 
            see_also = sorted(set(additional_see_also))
358
 
        else:
359
 
            see_also = None
360
 
        if see_also:
361
 
            result += 'See also: '
362
 
            result += ', '.join(see_also)
363
 
            result += '\n'
364
 
        return result
365
 
 
366
 
    def get_help_topic(self):
367
 
        """Return the modules help topic - its __name__ after bzrlib.plugins.."""
368
 
        return self.module.__name__[len('bzrlib.plugins.'):]
369
 
 
370
 
 
371
 
class PlugIn(object):
372
 
    """The bzrlib representation of a plugin.
373
 
 
374
 
    The PlugIn object provides a way to manipulate a given plugin module.
375
 
    """
376
 
 
377
 
    def __init__(self, name, module):
378
 
        """Construct a plugin for module."""
379
 
        self.name = name
380
 
        self.module = module
381
 
 
382
 
    def path(self):
383
 
        """Get the path that this plugin was loaded from."""
384
 
        if getattr(self.module, '__path__', None) is not None:
385
 
            return os.path.abspath(self.module.__path__[0])
386
 
        elif getattr(self.module, '__file__', None) is not None:
387
 
            path = os.path.abspath(self.module.__file__)
388
 
            if path[-4:] in ('.pyc', '.pyo'):
389
 
                pypath = path[:-4] + '.py'
390
 
                if os.path.isfile(pypath):
391
 
                    path = pypath
392
 
            return path
393
 
        else:
394
 
            return repr(self.module)
395
 
 
396
 
    def __str__(self):
397
 
        return "<%s.%s object at %s, name=%s, module=%s>" % (
398
 
            self.__class__.__module__, self.__class__.__name__, id(self),
399
 
            self.name, self.module)
400
 
 
401
 
    __repr__ = __str__
402
 
 
403
 
    def test_suite(self):
404
 
        """Return the plugin's test suite."""
405
 
        if getattr(self.module, 'test_suite', None) is not None:
406
 
            return self.module.test_suite()
407
 
        else:
408
 
            return None
409
 
 
410
 
    def version_info(self):
411
 
        """Return the plugin's version_tuple or None if unknown."""
412
 
        version_info = getattr(self.module, 'version_info', None)
413
 
        if version_info is not None and len(version_info) == 3:
414
 
            version_info = tuple(version_info) + ('final', 0)
415
 
        return version_info
416
 
    
417
 
    def _get__version__(self):
418
 
        version_info = self.version_info()
419
 
        if version_info is None:
420
 
            return "unknown"
421
 
        if version_info[3] == 'final':
422
 
            version_string = '%d.%d.%d' % version_info[:3]
423
 
        else:
424
 
            version_string = '%d.%d.%d%s%d' % version_info
425
 
        return version_string
426
 
 
427
 
    __version__ = property(_get__version__)