~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugin.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2004, 2005, 2007 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
18
"""bzr python plugin support.
26
26
plugins.
27
27
 
28
28
BZR_PLUGIN_PATH is also honoured for any plugins imported via
29
 
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been
 
29
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been 
30
30
called.
31
31
"""
32
32
 
33
33
import os
34
34
import sys
35
35
 
36
 
from bzrlib import osutils
37
 
 
38
36
from bzrlib.lazy_import import lazy_import
39
 
 
40
37
lazy_import(globals(), """
41
38
import imp
42
39
import re
43
40
import types
 
41
import zipfile
44
42
 
45
43
from bzrlib import (
46
 
    _format_version_tuple,
47
44
    config,
48
45
    debug,
49
 
    errors,
 
46
    osutils,
50
47
    trace,
51
48
    )
52
49
from bzrlib import plugins as _mod_plugins
53
50
""")
54
51
 
55
 
from bzrlib.symbol_versioning import deprecated_function
 
52
from bzrlib.symbol_versioning import deprecated_function, one_three
 
53
from bzrlib.trace import mutter, warning, log_exception_quietly
56
54
 
57
55
 
58
56
DEFAULT_PLUGIN_PATH = None
71
69
 
72
70
    Future calls to load_plugins() will be ignored.
73
71
    """
74
 
    load_plugins([])
 
72
    # TODO: jam 20060131 This should probably also disable
 
73
    #       load_from_dirs()
 
74
    global _loaded
 
75
    _loaded = True
75
76
 
76
77
 
77
78
def _strip_trailing_sep(path):
78
79
    return path.rstrip("\\/")
79
80
 
80
81
 
81
 
def set_plugins_path(path=None):
82
 
    """Set the path for plugins to be loaded from.
83
 
 
84
 
    :param path: The list of paths to search for plugins.  By default,
85
 
        path will be determined using get_standard_plugins_path.
86
 
        if path is [], no plugins can be loaded.
87
 
    """
88
 
    if path is None:
89
 
        path = get_standard_plugins_path()
90
 
    _mod_plugins.__path__ = path
91
 
    return path
92
 
 
93
 
 
94
 
def get_standard_plugins_path():
95
 
    """Determine a plugin path suitable for general use."""
 
82
def set_plugins_path():
 
83
    """Set the path for plugins to be loaded from."""
96
84
    path = os.environ.get('BZR_PLUGIN_PATH',
97
85
                          get_default_plugin_path()).split(os.pathsep)
98
 
    # Get rid of trailing slashes, since Python can't handle them when
99
 
    # it tries to import modules.
100
 
    path = map(_strip_trailing_sep, path)
101
86
    bzr_exe = bool(getattr(sys, 'frozen', None))
102
87
    if bzr_exe:    # expand path for bzr.exe
103
88
        # We need to use relative path to system-wide plugin
112
97
        # so relative path is ../../../plugins
113
98
        path.append(osutils.abspath(osutils.pathjoin(
114
99
            osutils.dirname(__file__), '../../../plugins')))
 
100
    # Get rid of trailing slashes, since Python can't handle them when
 
101
    # it tries to import modules.
 
102
    path = map(_strip_trailing_sep, path)
115
103
    if not bzr_exe:     # don't look inside library.zip
116
104
        # search the plugin path before the bzrlib installed dir
117
105
        path.append(os.path.dirname(_mod_plugins.__file__))
128
116
                    'plugins')
129
117
            if archless_path not in path:
130
118
                path.append(archless_path)
 
119
    _mod_plugins.__path__ = path
131
120
    return path
132
121
 
133
122
 
134
 
def load_plugins(path=None):
 
123
def load_plugins():
135
124
    """Load bzrlib plugins.
136
125
 
137
126
    The environment variable BZR_PLUGIN_PATH is considered a delimited
141
130
 
142
131
    load_from_dirs() provides the underlying mechanism and is called with
143
132
    the default directory list to provide the normal behaviour.
144
 
 
145
 
    :param path: The list of paths to search for plugins.  By default,
146
 
        path will be determined using get_standard_plugins_path.
147
 
        if path is [], no plugins can be loaded.
148
133
    """
149
134
    global _loaded
150
135
    if _loaded:
153
138
    _loaded = True
154
139
 
155
140
    # scan for all plugins in the path.
156
 
    load_from_path(set_plugins_path(path))
 
141
    load_from_path(set_plugins_path())
157
142
 
158
143
 
159
144
def load_from_path(dirs):
177
162
    for d in dirs:
178
163
        if not d:
179
164
            continue
180
 
        trace.mutter('looking for plugins in %s', d)
 
165
        mutter('looking for plugins in %s', d)
181
166
        if os.path.isdir(d):
182
167
            load_from_dir(d)
183
168
 
188
173
 
189
174
 
190
175
def load_from_dir(d):
191
 
    """Load the plugins in directory d.
192
 
 
193
 
    d must be in the plugins module path already.
194
 
    """
 
176
    """Load the plugins in directory d."""
195
177
    # Get the list of valid python suffixes for __init__.py?
196
178
    # this includes .py, .pyc, and .pyo (depending on if we are running -O)
197
179
    # but it doesn't include compiled modules (.so, .dll, etc)
218
200
                    break
219
201
            else:
220
202
                continue
221
 
        if f == '__init__':
222
 
            continue # We don't load __init__.py again in the plugin dir
223
 
        elif getattr(_mod_plugins, f, None):
224
 
            trace.mutter('Plugin name %s already loaded', f)
 
203
        if getattr(_mod_plugins, f, None):
 
204
            mutter('Plugin name %s already loaded', f)
225
205
        else:
226
 
            # trace.mutter('add plugin name %s', f)
 
206
            # mutter('add plugin name %s', f)
227
207
            plugin_names.add(f)
228
 
 
 
208
    
229
209
    for name in plugin_names:
230
210
        try:
231
211
            exec "import bzrlib.plugins.%s" % name in {}
232
212
        except KeyboardInterrupt:
233
213
            raise
234
 
        except errors.IncompatibleAPI, e:
235
 
            trace.warning("Unable to load plugin %r. It requested API version "
236
 
                "%s of module %s but the minimum exported version is %s, and "
237
 
                "the maximum is %s" %
238
 
                (name, e.wanted, e.api, e.minimum, e.current))
239
214
        except Exception, e:
240
 
            trace.warning("%s" % e)
241
215
            ## import pdb; pdb.set_trace()
242
216
            if re.search('\.|-| ', name):
243
217
                sanitised_name = re.sub('[-. ]', '_', name)
244
218
                if sanitised_name.startswith('bzr_'):
245
219
                    sanitised_name = sanitised_name[len('bzr_'):]
246
 
                trace.warning("Unable to load %r in %r as a plugin because the "
 
220
                warning("Unable to load %r in %r as a plugin because the "
247
221
                        "file path isn't a valid module name; try renaming "
248
222
                        "it to %r." % (name, d, sanitised_name))
249
223
            else:
250
 
                trace.warning('Unable to load plugin %r from %r' % (name, d))
251
 
            trace.log_exception_quietly()
 
224
                warning('Unable to load plugin %r from %r' % (name, d))
 
225
            log_exception_quietly()
 
226
            if 'error' in debug.debug_flags:
 
227
                trace.print_exception(sys.exc_info(), sys.stderr)
 
228
 
 
229
 
 
230
@deprecated_function(one_three)
 
231
def load_from_zip(zip_name):
 
232
    """Load all the plugins in a zip."""
 
233
    valid_suffixes = ('.py', '.pyc', '.pyo')    # only python modules/packages
 
234
                                                # is allowed
 
235
    try:
 
236
        index = zip_name.rindex('.zip')
 
237
    except ValueError:
 
238
        return
 
239
    archive = zip_name[:index+4]
 
240
    prefix = zip_name[index+5:]
 
241
 
 
242
    mutter('Looking for plugins in %r', zip_name)
 
243
 
 
244
    # use zipfile to get list of files/dirs inside zip
 
245
    try:
 
246
        z = zipfile.ZipFile(archive)
 
247
        namelist = z.namelist()
 
248
        z.close()
 
249
    except zipfile.error:
 
250
        # not a valid zip
 
251
        return
 
252
 
 
253
    if prefix:
 
254
        prefix = prefix.replace('\\','/')
 
255
        if prefix[-1] != '/':
 
256
            prefix += '/'
 
257
        ix = len(prefix)
 
258
        namelist = [name[ix:]
 
259
                    for name in namelist
 
260
                    if name.startswith(prefix)]
 
261
 
 
262
    mutter('Names in archive: %r', namelist)
 
263
    
 
264
    for name in namelist:
 
265
        if not name or name.endswith('/'):
 
266
            continue
 
267
    
 
268
        # '/' is used to separate pathname components inside zip archives
 
269
        ix = name.rfind('/')
 
270
        if ix == -1:
 
271
            head, tail = '', name
 
272
        else:
 
273
            head, tail = name.rsplit('/',1)
 
274
        if '/' in head:
 
275
            # we don't need looking in subdirectories
 
276
            continue
 
277
    
 
278
        base, suffix = osutils.splitext(tail)
 
279
        if suffix not in valid_suffixes:
 
280
            continue
 
281
    
 
282
        if base == '__init__':
 
283
            # package
 
284
            plugin_name = head
 
285
        elif head == '':
 
286
            # module
 
287
            plugin_name = base
 
288
        else:
 
289
            continue
 
290
    
 
291
        if not plugin_name:
 
292
            continue
 
293
        if getattr(_mod_plugins, plugin_name, None):
 
294
            mutter('Plugin name %s already loaded', plugin_name)
 
295
            continue
 
296
    
 
297
        try:
 
298
            exec "import bzrlib.plugins.%s" % plugin_name in {}
 
299
            mutter('Load plugin %s from zip %r', plugin_name, zip_name)
 
300
        except KeyboardInterrupt:
 
301
            raise
 
302
        except Exception, e:
 
303
            ## import pdb; pdb.set_trace()
 
304
            warning('Unable to load plugin %r from %r'
 
305
                    % (name, zip_name))
 
306
            log_exception_quietly()
252
307
            if 'error' in debug.debug_flags:
253
308
                trace.print_exception(sys.exc_info(), sys.stderr)
254
309
 
255
310
 
256
311
def plugins():
257
312
    """Return a dictionary of the plugins.
258
 
 
 
313
    
259
314
    Each item in the dictionary is a PlugIn object.
260
315
    """
261
316
    result = {}
303
358
        """
304
359
        self.module = module
305
360
 
306
 
    def get_help_text(self, additional_see_also=None, verbose=True):
 
361
    def get_help_text(self, additional_see_also=None):
307
362
        """Return a string with the help for this topic.
308
363
 
309
364
        :param additional_see_also: Additional help topics to be
315
370
            result = self.module.__doc__
316
371
        if result[-1] != '\n':
317
372
            result += '\n'
318
 
        # there is code duplicated here and in bzrlib/help_topic.py's
 
373
        # there is code duplicated here and in bzrlib/help_topic.py's 
319
374
        # matching Topic code. This should probably be factored in
320
375
        # to a helper function and a common base class.
321
376
        if additional_see_also is not None:
387
442
    def version_info(self):
388
443
        """Return the plugin's version_tuple or None if unknown."""
389
444
        version_info = getattr(self.module, 'version_info', None)
390
 
        if version_info is not None:
391
 
            try:
392
 
                if isinstance(version_info, types.StringType):
393
 
                    version_info = version_info.split('.')
394
 
                elif len(version_info) == 3:
395
 
                    version_info = tuple(version_info) + ('final', 0)
396
 
            except TypeError, e:
397
 
                # The given version_info isn't even iteratible
398
 
                trace.log_exception_quietly()
399
 
                version_info = (version_info,)
 
445
        if version_info is not None and len(version_info) == 3:
 
446
            version_info = tuple(version_info) + ('final', 0)
400
447
        return version_info
401
448
 
402
449
    def _get__version__(self):
403
450
        version_info = self.version_info()
404
 
        if version_info is None or len(version_info) == 0:
 
451
        if version_info is None:
405
452
            return "unknown"
406
 
        try:
407
 
            version_string = _format_version_tuple(version_info)
408
 
        except (ValueError, TypeError, IndexError), e:
409
 
            trace.log_exception_quietly()
410
 
            # try to return something usefull for bad plugins, in stead of
411
 
            # stack tracing.
412
 
            version_string = '.'.join(map(str, version_info))
 
453
        if version_info[3] == 'final':
 
454
            version_string = '%d.%d.%d' % version_info[:3]
 
455
        else:
 
456
            version_string = '%d.%d.%d%s%d' % version_info
413
457
        return version_string
414
458
 
415
459
    __version__ = property(_get__version__)