~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugin.py

Initial commit for russian version of documents.

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.
20
20
When load_plugins() is invoked, any python module in any directory in
21
21
$BZR_PLUGIN_PATH will be imported.  The module will be imported as
22
22
'bzrlib.plugins.$BASENAME(PLUGIN)'.  In the plugin's main body, it should
23
 
update any bzrlib registries it wants to extend.
24
 
 
25
 
See the plugin-api developer documentation for information about writing
26
 
plugins.
 
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.
27
26
 
28
27
BZR_PLUGIN_PATH is also honoured for any plugins imported via
29
 
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been
 
28
'import bzrlib.plugins.PLUGINNAME', as long as set_plugins_path has been 
30
29
called.
31
30
"""
32
31
 
33
32
import os
34
33
import sys
35
34
 
36
 
from bzrlib import osutils
37
 
 
38
35
from bzrlib.lazy_import import lazy_import
39
 
 
40
36
lazy_import(globals(), """
41
37
import imp
42
38
import re
43
39
import types
 
40
import zipfile
44
41
 
45
42
from bzrlib import (
46
 
    _format_version_tuple,
47
43
    config,
48
44
    debug,
49
 
    errors,
 
45
    osutils,
50
46
    trace,
51
47
    )
52
48
from bzrlib import plugins as _mod_plugins
53
49
""")
54
50
 
55
 
from bzrlib.symbol_versioning import deprecated_function
 
51
from bzrlib.symbol_versioning import deprecated_function, one_three
 
52
from bzrlib.trace import mutter, warning, log_exception_quietly
56
53
 
57
54
 
58
55
DEFAULT_PLUGIN_PATH = None
71
68
 
72
69
    Future calls to load_plugins() will be ignored.
73
70
    """
74
 
    load_plugins([])
 
71
    # TODO: jam 20060131 This should probably also disable
 
72
    #       load_from_dirs()
 
73
    global _loaded
 
74
    _loaded = True
75
75
 
76
76
 
77
77
def _strip_trailing_sep(path):
78
78
    return path.rstrip("\\/")
79
79
 
80
80
 
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."""
 
81
def set_plugins_path():
 
82
    """Set the path for plugins to be loaded from."""
96
83
    path = os.environ.get('BZR_PLUGIN_PATH',
97
84
                          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
85
    bzr_exe = bool(getattr(sys, 'frozen', None))
102
86
    if bzr_exe:    # expand path for bzr.exe
103
87
        # We need to use relative path to system-wide plugin
112
96
        # so relative path is ../../../plugins
113
97
        path.append(osutils.abspath(osutils.pathjoin(
114
98
            osutils.dirname(__file__), '../../../plugins')))
 
99
    # Get rid of trailing slashes, since Python can't handle them when
 
100
    # it tries to import modules.
 
101
    path = map(_strip_trailing_sep, path)
115
102
    if not bzr_exe:     # don't look inside library.zip
116
103
        # search the plugin path before the bzrlib installed dir
117
104
        path.append(os.path.dirname(_mod_plugins.__file__))
128
115
                    'plugins')
129
116
            if archless_path not in path:
130
117
                path.append(archless_path)
 
118
    _mod_plugins.__path__ = path
131
119
    return path
132
120
 
133
121
 
134
 
def load_plugins(path=None):
 
122
def load_plugins():
135
123
    """Load bzrlib plugins.
136
124
 
137
125
    The environment variable BZR_PLUGIN_PATH is considered a delimited
141
129
 
142
130
    load_from_dirs() provides the underlying mechanism and is called with
143
131
    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
132
    """
149
133
    global _loaded
150
134
    if _loaded:
153
137
    _loaded = True
154
138
 
155
139
    # scan for all plugins in the path.
156
 
    load_from_path(set_plugins_path(path))
 
140
    load_from_path(set_plugins_path())
157
141
 
158
142
 
159
143
def load_from_path(dirs):
177
161
    for d in dirs:
178
162
        if not d:
179
163
            continue
180
 
        trace.mutter('looking for plugins in %s', d)
 
164
        mutter('looking for plugins in %s', d)
181
165
        if os.path.isdir(d):
182
166
            load_from_dir(d)
183
167
 
188
172
 
189
173
 
190
174
def load_from_dir(d):
191
 
    """Load the plugins in directory d.
192
 
 
193
 
    d must be in the plugins module path already.
194
 
    """
 
175
    """Load the plugins in directory d."""
195
176
    # Get the list of valid python suffixes for __init__.py?
196
177
    # this includes .py, .pyc, and .pyo (depending on if we are running -O)
197
178
    # but it doesn't include compiled modules (.so, .dll, etc)
218
199
                    break
219
200
            else:
220
201
                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)
 
202
        if getattr(_mod_plugins, f, None):
 
203
            mutter('Plugin name %s already loaded', f)
225
204
        else:
226
 
            # trace.mutter('add plugin name %s', f)
 
205
            # mutter('add plugin name %s', f)
227
206
            plugin_names.add(f)
228
 
 
 
207
    
229
208
    for name in plugin_names:
230
209
        try:
231
210
            exec "import bzrlib.plugins.%s" % name in {}
232
211
        except KeyboardInterrupt:
233
212
            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
213
        except Exception, e:
240
 
            trace.warning("%s" % e)
241
214
            ## import pdb; pdb.set_trace()
242
215
            if re.search('\.|-| ', name):
243
216
                sanitised_name = re.sub('[-. ]', '_', name)
244
217
                if sanitised_name.startswith('bzr_'):
245
218
                    sanitised_name = sanitised_name[len('bzr_'):]
246
 
                trace.warning("Unable to load %r in %r as a plugin because the "
 
219
                warning("Unable to load %r in %r as a plugin because the "
247
220
                        "file path isn't a valid module name; try renaming "
248
221
                        "it to %r." % (name, d, sanitised_name))
249
222
            else:
250
 
                trace.warning('Unable to load plugin %r from %r' % (name, d))
251
 
            trace.log_exception_quietly()
 
223
                warning('Unable to load plugin %r from %r' % (name, d))
 
224
            log_exception_quietly()
 
225
            if 'error' in debug.debug_flags:
 
226
                trace.print_exception(sys.exc_info(), sys.stderr)
 
227
 
 
228
 
 
229
@deprecated_function(one_three)
 
230
def load_from_zip(zip_name):
 
231
    """Load all the plugins in a zip."""
 
232
    valid_suffixes = ('.py', '.pyc', '.pyo')    # only python modules/packages
 
233
                                                # is allowed
 
234
    try:
 
235
        index = zip_name.rindex('.zip')
 
236
    except ValueError:
 
237
        return
 
238
    archive = zip_name[:index+4]
 
239
    prefix = zip_name[index+5:]
 
240
 
 
241
    mutter('Looking for plugins in %r', zip_name)
 
242
 
 
243
    # use zipfile to get list of files/dirs inside zip
 
244
    try:
 
245
        z = zipfile.ZipFile(archive)
 
246
        namelist = z.namelist()
 
247
        z.close()
 
248
    except zipfile.error:
 
249
        # not a valid zip
 
250
        return
 
251
 
 
252
    if prefix:
 
253
        prefix = prefix.replace('\\','/')
 
254
        if prefix[-1] != '/':
 
255
            prefix += '/'
 
256
        ix = len(prefix)
 
257
        namelist = [name[ix:]
 
258
                    for name in namelist
 
259
                    if name.startswith(prefix)]
 
260
 
 
261
    mutter('Names in archive: %r', namelist)
 
262
    
 
263
    for name in namelist:
 
264
        if not name or name.endswith('/'):
 
265
            continue
 
266
    
 
267
        # '/' is used to separate pathname components inside zip archives
 
268
        ix = name.rfind('/')
 
269
        if ix == -1:
 
270
            head, tail = '', name
 
271
        else:
 
272
            head, tail = name.rsplit('/',1)
 
273
        if '/' in head:
 
274
            # we don't need looking in subdirectories
 
275
            continue
 
276
    
 
277
        base, suffix = osutils.splitext(tail)
 
278
        if suffix not in valid_suffixes:
 
279
            continue
 
280
    
 
281
        if base == '__init__':
 
282
            # package
 
283
            plugin_name = head
 
284
        elif head == '':
 
285
            # module
 
286
            plugin_name = base
 
287
        else:
 
288
            continue
 
289
    
 
290
        if not plugin_name:
 
291
            continue
 
292
        if getattr(_mod_plugins, plugin_name, None):
 
293
            mutter('Plugin name %s already loaded', plugin_name)
 
294
            continue
 
295
    
 
296
        try:
 
297
            exec "import bzrlib.plugins.%s" % plugin_name in {}
 
298
            mutter('Load plugin %s from zip %r', plugin_name, zip_name)
 
299
        except KeyboardInterrupt:
 
300
            raise
 
301
        except Exception, e:
 
302
            ## import pdb; pdb.set_trace()
 
303
            warning('Unable to load plugin %r from %r'
 
304
                    % (name, zip_name))
 
305
            log_exception_quietly()
252
306
            if 'error' in debug.debug_flags:
253
307
                trace.print_exception(sys.exc_info(), sys.stderr)
254
308
 
255
309
 
256
310
def plugins():
257
311
    """Return a dictionary of the plugins.
258
 
 
 
312
    
259
313
    Each item in the dictionary is a PlugIn object.
260
314
    """
261
315
    result = {}
303
357
        """
304
358
        self.module = module
305
359
 
306
 
    def get_help_text(self, additional_see_also=None, verbose=True):
 
360
    def get_help_text(self, additional_see_also=None):
307
361
        """Return a string with the help for this topic.
308
362
 
309
363
        :param additional_see_also: Additional help topics to be
315
369
            result = self.module.__doc__
316
370
        if result[-1] != '\n':
317
371
            result += '\n'
318
 
        # there is code duplicated here and in bzrlib/help_topic.py's
 
372
        # there is code duplicated here and in bzrlib/help_topic.py's 
319
373
        # matching Topic code. This should probably be factored in
320
374
        # to a helper function and a common base class.
321
375
        if additional_see_also is not None:
387
441
    def version_info(self):
388
442
        """Return the plugin's version_tuple or None if unknown."""
389
443
        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,)
 
444
        if version_info is not None and len(version_info) == 3:
 
445
            version_info = tuple(version_info) + ('final', 0)
400
446
        return version_info
401
447
 
402
448
    def _get__version__(self):
403
449
        version_info = self.version_info()
404
 
        if version_info is None or len(version_info) == 0:
 
450
        if version_info is None:
405
451
            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))
 
452
        if version_info[3] == 'final':
 
453
            version_string = '%d.%d.%d' % version_info[:3]
 
454
        else:
 
455
            version_string = '%d.%d.%d%s%d' % version_info
413
456
        return version_string
414
457
 
415
458
    __version__ = property(_get__version__)