/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

Partially fix pull.

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
 
                warning("Unable to load %r in %r as a plugin because file path"
203
 
                        " isn't a valid module name; try renaming it to %r."
204
 
                        % (name, d, sanitised_name))
205
 
            else:
206
 
                warning('Unable to load plugin %r from %r' % (name, d))
207
 
            log_exception_quietly()
208
 
 
209
 
 
210
 
@deprecated_function(one_three)
211
 
def load_from_zip(zip_name):
212
 
    """Load all the plugins in a zip."""
213
 
    valid_suffixes = ('.py', '.pyc', '.pyo')    # only python modules/packages
214
 
                                                # is allowed
215
 
    try:
216
 
        index = zip_name.rindex('.zip')
217
 
    except ValueError:
218
 
        return
219
 
    archive = zip_name[:index+4]
220
 
    prefix = zip_name[index+5:]
221
 
 
222
 
    mutter('Looking for plugins in %r', zip_name)
223
 
 
224
 
    # use zipfile to get list of files/dirs inside zip
225
 
    try:
226
 
        z = zipfile.ZipFile(archive)
227
 
        namelist = z.namelist()
228
 
        z.close()
229
 
    except zipfile.error:
230
 
        # not a valid zip
231
 
        return
232
 
 
233
 
    if prefix:
234
 
        prefix = prefix.replace('\\','/')
235
 
        if prefix[-1] != '/':
236
 
            prefix += '/'
237
 
        ix = len(prefix)
238
 
        namelist = [name[ix:]
239
 
                    for name in namelist
240
 
                    if name.startswith(prefix)]
241
 
 
242
 
    mutter('Names in archive: %r', namelist)
243
 
    
244
 
    for name in namelist:
245
 
        if not name or name.endswith('/'):
246
 
            continue
247
 
    
248
 
        # '/' is used to separate pathname components inside zip archives
249
 
        ix = name.rfind('/')
250
 
        if ix == -1:
251
 
            head, tail = '', name
252
 
        else:
253
 
            head, tail = name.rsplit('/',1)
254
 
        if '/' in head:
255
 
            # we don't need looking in subdirectories
256
 
            continue
257
 
    
258
 
        base, suffix = osutils.splitext(tail)
259
 
        if suffix not in valid_suffixes:
260
 
            continue
261
 
    
262
 
        if base == '__init__':
263
 
            # package
264
 
            plugin_name = head
265
 
        elif head == '':
266
 
            # module
267
 
            plugin_name = base
268
 
        else:
269
 
            continue
270
 
    
271
 
        if not plugin_name:
272
 
            continue
273
 
        if getattr(_mod_plugins, plugin_name, None):
274
 
            mutter('Plugin name %s already loaded', plugin_name)
275
 
            continue
276
 
    
277
 
        try:
278
 
            exec "import bzrlib.plugins.%s" % plugin_name in {}
279
 
            mutter('Load plugin %s from zip %r', plugin_name, zip_name)
280
 
        except KeyboardInterrupt:
281
 
            raise
282
 
        except Exception, e:
283
 
            ## import pdb; pdb.set_trace()
284
 
            warning('Unable to load plugin %r from %r'
285
 
                    % (name, zip_name))
286
 
            log_exception_quietly()
287
 
 
288
 
 
289
 
def plugins():
290
 
    """Return a dictionary of the plugins.
291
 
    
292
 
    Each item in the dictionary is a PlugIn object.
293
 
    """
294
 
    result = {}
295
 
    for name, plugin in _mod_plugins.__dict__.items():
296
 
        if isinstance(plugin, types.ModuleType):
297
 
            result[name] = PlugIn(name, plugin)
298
 
    return result
299
 
 
300
 
 
301
 
class PluginsHelpIndex(object):
302
 
    """A help index that returns help topics for plugins."""
303
 
 
304
 
    def __init__(self):
305
 
        self.prefix = 'plugins/'
306
 
 
307
 
    def get_topics(self, topic):
308
 
        """Search for topic in the loaded plugins.
309
 
 
310
 
        This will not trigger loading of new plugins.
311
 
 
312
 
        :param topic: A topic to search for.
313
 
        :return: A list which is either empty or contains a single
314
 
            RegisteredTopic entry.
315
 
        """
316
 
        if not topic:
317
 
            return []
318
 
        if topic.startswith(self.prefix):
319
 
            topic = topic[len(self.prefix):]
320
 
        plugin_module_name = 'bzrlib.plugins.%s' % topic
321
 
        try:
322
 
            module = sys.modules[plugin_module_name]
323
 
        except KeyError:
324
 
            return []
325
 
        else:
326
 
            return [ModuleHelpTopic(module)]
327
 
 
328
 
 
329
 
class ModuleHelpTopic(object):
330
 
    """A help topic which returns the docstring for a module."""
331
 
 
332
 
    def __init__(self, module):
333
 
        """Constructor.
334
 
 
335
 
        :param module: The module for which help should be generated.
336
 
        """
337
 
        self.module = module
338
 
 
339
 
    def get_help_text(self, additional_see_also=None):
340
 
        """Return a string with the help for this topic.
341
 
 
342
 
        :param additional_see_also: Additional help topics to be
343
 
            cross-referenced.
344
 
        """
345
 
        if not self.module.__doc__:
346
 
            result = "Plugin '%s' has no docstring.\n" % self.module.__name__
347
 
        else:
348
 
            result = self.module.__doc__
349
 
        if result[-1] != '\n':
350
 
            result += '\n'
351
 
        # there is code duplicated here and in bzrlib/help_topic.py's 
352
 
        # matching Topic code. This should probably be factored in
353
 
        # to a helper function and a common base class.
354
 
        if additional_see_also is not None:
355
 
            see_also = sorted(set(additional_see_also))
356
 
        else:
357
 
            see_also = None
358
 
        if see_also:
359
 
            result += 'See also: '
360
 
            result += ', '.join(see_also)
361
 
            result += '\n'
362
 
        return result
363
 
 
364
 
    def get_help_topic(self):
365
 
        """Return the modules help topic - its __name__ after bzrlib.plugins.."""
366
 
        return self.module.__name__[len('bzrlib.plugins.'):]
367
 
 
368
 
 
369
 
class PlugIn(object):
370
 
    """The bzrlib representation of a plugin.
371
 
 
372
 
    The PlugIn object provides a way to manipulate a given plugin module.
373
 
    """
374
 
 
375
 
    def __init__(self, name, module):
376
 
        """Construct a plugin for module."""
377
 
        self.name = name
378
 
        self.module = module
379
 
 
380
 
    def path(self):
381
 
        """Get the path that this plugin was loaded from."""
382
 
        if getattr(self.module, '__path__', None) is not None:
383
 
            return os.path.abspath(self.module.__path__[0])
384
 
        elif getattr(self.module, '__file__', None) is not None:
385
 
            path = os.path.abspath(self.module.__file__)
386
 
            if path[-4:] in ('.pyc', '.pyo'):
387
 
                pypath = path[:-4] + '.py'
388
 
                if os.path.isfile(pypath):
389
 
                    path = pypath
390
 
            return path
391
 
        else:
392
 
            return repr(self.module)
393
 
 
394
 
    def __str__(self):
395
 
        return "<%s.%s object at %s, name=%s, module=%s>" % (
396
 
            self.__class__.__module__, self.__class__.__name__, id(self),
397
 
            self.name, self.module)
398
 
 
399
 
    __repr__ = __str__
400
 
 
401
 
    def test_suite(self):
402
 
        """Return the plugin's test suite."""
403
 
        if getattr(self.module, 'test_suite', None) is not None:
404
 
            return self.module.test_suite()
405
 
        else:
406
 
            return None
407
 
 
408
 
    def version_info(self):
409
 
        """Return the plugin's version_tuple or None if unknown."""
410
 
        version_info = getattr(self.module, 'version_info', None)
411
 
        if version_info is not None and len(version_info) == 3:
412
 
            version_info = tuple(version_info) + ('final', 0)
413
 
        return version_info
414
 
    
415
 
    def _get__version__(self):
416
 
        version_info = self.version_info()
417
 
        if version_info is None:
418
 
            return "unknown"
419
 
        if version_info[3] == 'final':
420
 
            version_string = '%d.%d.%d' % version_info[:3]
421
 
        else:
422
 
            version_string = '%d.%d.%d%s%d' % version_info
423
 
        return version_string
424
 
 
425
 
    __version__ = property(_get__version__)