##// END OF EJS Templates
extensions: use absolute_import
Gregory Szorc -
r25946:5e0d8019 default
parent child Browse files
Show More
@@ -1,446 +1,458
1 # extensions.py - extension handling for mercurial
1 # extensions.py - extension handling for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import imp, os
8 from __future__ import absolute_import
9 import util, cmdutil, error
9
10 from i18n import _, gettext
10 import imp
11 import os
12
13 from .i18n import (
14 _,
15 gettext,
16 )
17
18 from . import (
19 cmdutil,
20 error,
21 util,
22 )
11
23
12 _extensions = {}
24 _extensions = {}
13 _aftercallbacks = {}
25 _aftercallbacks = {}
14 _order = []
26 _order = []
15 _ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
27 _ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
16
28
17 def extensions(ui=None):
29 def extensions(ui=None):
18 if ui:
30 if ui:
19 def enabled(name):
31 def enabled(name):
20 for format in ['%s', 'hgext.%s']:
32 for format in ['%s', 'hgext.%s']:
21 conf = ui.config('extensions', format % name)
33 conf = ui.config('extensions', format % name)
22 if conf is not None and not conf.startswith('!'):
34 if conf is not None and not conf.startswith('!'):
23 return True
35 return True
24 else:
36 else:
25 enabled = lambda name: True
37 enabled = lambda name: True
26 for name in _order:
38 for name in _order:
27 module = _extensions[name]
39 module = _extensions[name]
28 if module and enabled(name):
40 if module and enabled(name):
29 yield name, module
41 yield name, module
30
42
31 def find(name):
43 def find(name):
32 '''return module with given extension name'''
44 '''return module with given extension name'''
33 mod = None
45 mod = None
34 try:
46 try:
35 mod = _extensions[name]
47 mod = _extensions[name]
36 except KeyError:
48 except KeyError:
37 for k, v in _extensions.iteritems():
49 for k, v in _extensions.iteritems():
38 if k.endswith('.' + name) or k.endswith('/' + name):
50 if k.endswith('.' + name) or k.endswith('/' + name):
39 mod = v
51 mod = v
40 break
52 break
41 if not mod:
53 if not mod:
42 raise KeyError(name)
54 raise KeyError(name)
43 return mod
55 return mod
44
56
45 def loadpath(path, module_name):
57 def loadpath(path, module_name):
46 module_name = module_name.replace('.', '_')
58 module_name = module_name.replace('.', '_')
47 path = util.normpath(util.expandpath(path))
59 path = util.normpath(util.expandpath(path))
48 if os.path.isdir(path):
60 if os.path.isdir(path):
49 # module/__init__.py style
61 # module/__init__.py style
50 d, f = os.path.split(path)
62 d, f = os.path.split(path)
51 fd, fpath, desc = imp.find_module(f, [d])
63 fd, fpath, desc = imp.find_module(f, [d])
52 return imp.load_module(module_name, fd, fpath, desc)
64 return imp.load_module(module_name, fd, fpath, desc)
53 else:
65 else:
54 try:
66 try:
55 return imp.load_source(module_name, path)
67 return imp.load_source(module_name, path)
56 except IOError as exc:
68 except IOError as exc:
57 if not exc.filename:
69 if not exc.filename:
58 exc.filename = path # python does not fill this
70 exc.filename = path # python does not fill this
59 raise
71 raise
60
72
61 def load(ui, name, path):
73 def load(ui, name, path):
62 if name.startswith('hgext.') or name.startswith('hgext/'):
74 if name.startswith('hgext.') or name.startswith('hgext/'):
63 shortname = name[6:]
75 shortname = name[6:]
64 else:
76 else:
65 shortname = name
77 shortname = name
66 if shortname in _ignore:
78 if shortname in _ignore:
67 return None
79 return None
68 if shortname in _extensions:
80 if shortname in _extensions:
69 return _extensions[shortname]
81 return _extensions[shortname]
70 _extensions[shortname] = None
82 _extensions[shortname] = None
71 if path:
83 if path:
72 # the module will be loaded in sys.modules
84 # the module will be loaded in sys.modules
73 # choose an unique name so that it doesn't
85 # choose an unique name so that it doesn't
74 # conflicts with other modules
86 # conflicts with other modules
75 mod = loadpath(path, 'hgext.%s' % name)
87 mod = loadpath(path, 'hgext.%s' % name)
76 else:
88 else:
77 def importh(name):
89 def importh(name):
78 mod = __import__(name)
90 mod = __import__(name)
79 components = name.split('.')
91 components = name.split('.')
80 for comp in components[1:]:
92 for comp in components[1:]:
81 mod = getattr(mod, comp)
93 mod = getattr(mod, comp)
82 return mod
94 return mod
83 try:
95 try:
84 mod = importh("hgext.%s" % name)
96 mod = importh("hgext.%s" % name)
85 except ImportError as err:
97 except ImportError as err:
86 ui.debug('could not import hgext.%s (%s): trying %s\n'
98 ui.debug('could not import hgext.%s (%s): trying %s\n'
87 % (name, err, name))
99 % (name, err, name))
88 if ui.debugflag:
100 if ui.debugflag:
89 ui.traceback()
101 ui.traceback()
90 mod = importh(name)
102 mod = importh(name)
91 _extensions[shortname] = mod
103 _extensions[shortname] = mod
92 _order.append(shortname)
104 _order.append(shortname)
93 for fn in _aftercallbacks.get(shortname, []):
105 for fn in _aftercallbacks.get(shortname, []):
94 fn(loaded=True)
106 fn(loaded=True)
95 return mod
107 return mod
96
108
97 def loadall(ui):
109 def loadall(ui):
98 result = ui.configitems("extensions")
110 result = ui.configitems("extensions")
99 newindex = len(_order)
111 newindex = len(_order)
100 for (name, path) in result:
112 for (name, path) in result:
101 if path:
113 if path:
102 if path[0] == '!':
114 if path[0] == '!':
103 continue
115 continue
104 try:
116 try:
105 load(ui, name, path)
117 load(ui, name, path)
106 except KeyboardInterrupt:
118 except KeyboardInterrupt:
107 raise
119 raise
108 except Exception as inst:
120 except Exception as inst:
109 if path:
121 if path:
110 ui.warn(_("*** failed to import extension %s from %s: %s\n")
122 ui.warn(_("*** failed to import extension %s from %s: %s\n")
111 % (name, path, inst))
123 % (name, path, inst))
112 else:
124 else:
113 ui.warn(_("*** failed to import extension %s: %s\n")
125 ui.warn(_("*** failed to import extension %s: %s\n")
114 % (name, inst))
126 % (name, inst))
115 ui.traceback()
127 ui.traceback()
116
128
117 for name in _order[newindex:]:
129 for name in _order[newindex:]:
118 uisetup = getattr(_extensions[name], 'uisetup', None)
130 uisetup = getattr(_extensions[name], 'uisetup', None)
119 if uisetup:
131 if uisetup:
120 uisetup(ui)
132 uisetup(ui)
121
133
122 for name in _order[newindex:]:
134 for name in _order[newindex:]:
123 extsetup = getattr(_extensions[name], 'extsetup', None)
135 extsetup = getattr(_extensions[name], 'extsetup', None)
124 if extsetup:
136 if extsetup:
125 try:
137 try:
126 extsetup(ui)
138 extsetup(ui)
127 except TypeError:
139 except TypeError:
128 if extsetup.func_code.co_argcount != 0:
140 if extsetup.func_code.co_argcount != 0:
129 raise
141 raise
130 extsetup() # old extsetup with no ui argument
142 extsetup() # old extsetup with no ui argument
131
143
132 # Call aftercallbacks that were never met.
144 # Call aftercallbacks that were never met.
133 for shortname in _aftercallbacks:
145 for shortname in _aftercallbacks:
134 if shortname in _extensions:
146 if shortname in _extensions:
135 continue
147 continue
136
148
137 for fn in _aftercallbacks[shortname]:
149 for fn in _aftercallbacks[shortname]:
138 fn(loaded=False)
150 fn(loaded=False)
139
151
140 # loadall() is called multiple times and lingering _aftercallbacks
152 # loadall() is called multiple times and lingering _aftercallbacks
141 # entries could result in double execution. See issue4646.
153 # entries could result in double execution. See issue4646.
142 _aftercallbacks.clear()
154 _aftercallbacks.clear()
143
155
144 def afterloaded(extension, callback):
156 def afterloaded(extension, callback):
145 '''Run the specified function after a named extension is loaded.
157 '''Run the specified function after a named extension is loaded.
146
158
147 If the named extension is already loaded, the callback will be called
159 If the named extension is already loaded, the callback will be called
148 immediately.
160 immediately.
149
161
150 If the named extension never loads, the callback will be called after
162 If the named extension never loads, the callback will be called after
151 all extensions have been loaded.
163 all extensions have been loaded.
152
164
153 The callback receives the named argument ``loaded``, which is a boolean
165 The callback receives the named argument ``loaded``, which is a boolean
154 indicating whether the dependent extension actually loaded.
166 indicating whether the dependent extension actually loaded.
155 '''
167 '''
156
168
157 if extension in _extensions:
169 if extension in _extensions:
158 callback(loaded=True)
170 callback(loaded=True)
159 else:
171 else:
160 _aftercallbacks.setdefault(extension, []).append(callback)
172 _aftercallbacks.setdefault(extension, []).append(callback)
161
173
162 def bind(func, *args):
174 def bind(func, *args):
163 '''Partial function application
175 '''Partial function application
164
176
165 Returns a new function that is the partial application of args and kwargs
177 Returns a new function that is the partial application of args and kwargs
166 to func. For example,
178 to func. For example,
167
179
168 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
180 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
169 assert callable(func)
181 assert callable(func)
170 def closure(*a, **kw):
182 def closure(*a, **kw):
171 return func(*(args + a), **kw)
183 return func(*(args + a), **kw)
172 return closure
184 return closure
173
185
174 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
186 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
175 '''Wrap the command named `command' in table
187 '''Wrap the command named `command' in table
176
188
177 Replace command in the command table with wrapper. The wrapped command will
189 Replace command in the command table with wrapper. The wrapped command will
178 be inserted into the command table specified by the table argument.
190 be inserted into the command table specified by the table argument.
179
191
180 The wrapper will be called like
192 The wrapper will be called like
181
193
182 wrapper(orig, *args, **kwargs)
194 wrapper(orig, *args, **kwargs)
183
195
184 where orig is the original (wrapped) function, and *args, **kwargs
196 where orig is the original (wrapped) function, and *args, **kwargs
185 are the arguments passed to it.
197 are the arguments passed to it.
186
198
187 Optionally append to the command synopsis and docstring, used for help.
199 Optionally append to the command synopsis and docstring, used for help.
188 For example, if your extension wraps the ``bookmarks`` command to add the
200 For example, if your extension wraps the ``bookmarks`` command to add the
189 flags ``--remote`` and ``--all`` you might call this function like so:
201 flags ``--remote`` and ``--all`` you might call this function like so:
190
202
191 synopsis = ' [-a] [--remote]'
203 synopsis = ' [-a] [--remote]'
192 docstring = """
204 docstring = """
193
205
194 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
206 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
195 flags to the bookmarks command. Either flag will show the remote bookmarks
207 flags to the bookmarks command. Either flag will show the remote bookmarks
196 known to the repository; ``--remote`` will also supress the output of the
208 known to the repository; ``--remote`` will also supress the output of the
197 local bookmarks.
209 local bookmarks.
198 """
210 """
199
211
200 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
212 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
201 synopsis, docstring)
213 synopsis, docstring)
202 '''
214 '''
203 assert callable(wrapper)
215 assert callable(wrapper)
204 aliases, entry = cmdutil.findcmd(command, table)
216 aliases, entry = cmdutil.findcmd(command, table)
205 for alias, e in table.iteritems():
217 for alias, e in table.iteritems():
206 if e is entry:
218 if e is entry:
207 key = alias
219 key = alias
208 break
220 break
209
221
210 origfn = entry[0]
222 origfn = entry[0]
211 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
223 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
212
224
213 wrap.__module__ = getattr(origfn, '__module__')
225 wrap.__module__ = getattr(origfn, '__module__')
214
226
215 doc = getattr(origfn, '__doc__')
227 doc = getattr(origfn, '__doc__')
216 if docstring is not None:
228 if docstring is not None:
217 doc += docstring
229 doc += docstring
218 wrap.__doc__ = doc
230 wrap.__doc__ = doc
219
231
220 newentry = list(entry)
232 newentry = list(entry)
221 newentry[0] = wrap
233 newentry[0] = wrap
222 if synopsis is not None:
234 if synopsis is not None:
223 newentry[2] += synopsis
235 newentry[2] += synopsis
224 table[key] = tuple(newentry)
236 table[key] = tuple(newentry)
225 return entry
237 return entry
226
238
227 def wrapfunction(container, funcname, wrapper):
239 def wrapfunction(container, funcname, wrapper):
228 '''Wrap the function named funcname in container
240 '''Wrap the function named funcname in container
229
241
230 Replace the funcname member in the given container with the specified
242 Replace the funcname member in the given container with the specified
231 wrapper. The container is typically a module, class, or instance.
243 wrapper. The container is typically a module, class, or instance.
232
244
233 The wrapper will be called like
245 The wrapper will be called like
234
246
235 wrapper(orig, *args, **kwargs)
247 wrapper(orig, *args, **kwargs)
236
248
237 where orig is the original (wrapped) function, and *args, **kwargs
249 where orig is the original (wrapped) function, and *args, **kwargs
238 are the arguments passed to it.
250 are the arguments passed to it.
239
251
240 Wrapping methods of the repository object is not recommended since
252 Wrapping methods of the repository object is not recommended since
241 it conflicts with extensions that extend the repository by
253 it conflicts with extensions that extend the repository by
242 subclassing. All extensions that need to extend methods of
254 subclassing. All extensions that need to extend methods of
243 localrepository should use this subclassing trick: namely,
255 localrepository should use this subclassing trick: namely,
244 reposetup() should look like
256 reposetup() should look like
245
257
246 def reposetup(ui, repo):
258 def reposetup(ui, repo):
247 class myrepo(repo.__class__):
259 class myrepo(repo.__class__):
248 def whatever(self, *args, **kwargs):
260 def whatever(self, *args, **kwargs):
249 [...extension stuff...]
261 [...extension stuff...]
250 super(myrepo, self).whatever(*args, **kwargs)
262 super(myrepo, self).whatever(*args, **kwargs)
251 [...extension stuff...]
263 [...extension stuff...]
252
264
253 repo.__class__ = myrepo
265 repo.__class__ = myrepo
254
266
255 In general, combining wrapfunction() with subclassing does not
267 In general, combining wrapfunction() with subclassing does not
256 work. Since you cannot control what other extensions are loaded by
268 work. Since you cannot control what other extensions are loaded by
257 your end users, you should play nicely with others by using the
269 your end users, you should play nicely with others by using the
258 subclass trick.
270 subclass trick.
259 '''
271 '''
260 assert callable(wrapper)
272 assert callable(wrapper)
261
273
262 origfn = getattr(container, funcname)
274 origfn = getattr(container, funcname)
263 assert callable(origfn)
275 assert callable(origfn)
264 setattr(container, funcname, bind(wrapper, origfn))
276 setattr(container, funcname, bind(wrapper, origfn))
265 return origfn
277 return origfn
266
278
267 def _disabledpaths(strip_init=False):
279 def _disabledpaths(strip_init=False):
268 '''find paths of disabled extensions. returns a dict of {name: path}
280 '''find paths of disabled extensions. returns a dict of {name: path}
269 removes /__init__.py from packages if strip_init is True'''
281 removes /__init__.py from packages if strip_init is True'''
270 import hgext
282 import hgext
271 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
283 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
272 try: # might not be a filesystem path
284 try: # might not be a filesystem path
273 files = os.listdir(extpath)
285 files = os.listdir(extpath)
274 except OSError:
286 except OSError:
275 return {}
287 return {}
276
288
277 exts = {}
289 exts = {}
278 for e in files:
290 for e in files:
279 if e.endswith('.py'):
291 if e.endswith('.py'):
280 name = e.rsplit('.', 1)[0]
292 name = e.rsplit('.', 1)[0]
281 path = os.path.join(extpath, e)
293 path = os.path.join(extpath, e)
282 else:
294 else:
283 name = e
295 name = e
284 path = os.path.join(extpath, e, '__init__.py')
296 path = os.path.join(extpath, e, '__init__.py')
285 if not os.path.exists(path):
297 if not os.path.exists(path):
286 continue
298 continue
287 if strip_init:
299 if strip_init:
288 path = os.path.dirname(path)
300 path = os.path.dirname(path)
289 if name in exts or name in _order or name == '__init__':
301 if name in exts or name in _order or name == '__init__':
290 continue
302 continue
291 exts[name] = path
303 exts[name] = path
292 return exts
304 return exts
293
305
294 def _moduledoc(file):
306 def _moduledoc(file):
295 '''return the top-level python documentation for the given file
307 '''return the top-level python documentation for the given file
296
308
297 Loosely inspired by pydoc.source_synopsis(), but rewritten to
309 Loosely inspired by pydoc.source_synopsis(), but rewritten to
298 handle triple quotes and to return the whole text instead of just
310 handle triple quotes and to return the whole text instead of just
299 the synopsis'''
311 the synopsis'''
300 result = []
312 result = []
301
313
302 line = file.readline()
314 line = file.readline()
303 while line[:1] == '#' or not line.strip():
315 while line[:1] == '#' or not line.strip():
304 line = file.readline()
316 line = file.readline()
305 if not line:
317 if not line:
306 break
318 break
307
319
308 start = line[:3]
320 start = line[:3]
309 if start == '"""' or start == "'''":
321 if start == '"""' or start == "'''":
310 line = line[3:]
322 line = line[3:]
311 while line:
323 while line:
312 if line.rstrip().endswith(start):
324 if line.rstrip().endswith(start):
313 line = line.split(start)[0]
325 line = line.split(start)[0]
314 if line:
326 if line:
315 result.append(line)
327 result.append(line)
316 break
328 break
317 elif not line:
329 elif not line:
318 return None # unmatched delimiter
330 return None # unmatched delimiter
319 result.append(line)
331 result.append(line)
320 line = file.readline()
332 line = file.readline()
321 else:
333 else:
322 return None
334 return None
323
335
324 return ''.join(result)
336 return ''.join(result)
325
337
326 def _disabledhelp(path):
338 def _disabledhelp(path):
327 '''retrieve help synopsis of a disabled extension (without importing)'''
339 '''retrieve help synopsis of a disabled extension (without importing)'''
328 try:
340 try:
329 file = open(path)
341 file = open(path)
330 except IOError:
342 except IOError:
331 return
343 return
332 else:
344 else:
333 doc = _moduledoc(file)
345 doc = _moduledoc(file)
334 file.close()
346 file.close()
335
347
336 if doc: # extracting localized synopsis
348 if doc: # extracting localized synopsis
337 return gettext(doc).splitlines()[0]
349 return gettext(doc).splitlines()[0]
338 else:
350 else:
339 return _('(no help text available)')
351 return _('(no help text available)')
340
352
341 def disabled():
353 def disabled():
342 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
354 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
343 try:
355 try:
344 from hgext import __index__
356 from hgext import __index__
345 return dict((name, gettext(desc))
357 return dict((name, gettext(desc))
346 for name, desc in __index__.docs.iteritems()
358 for name, desc in __index__.docs.iteritems()
347 if name not in _order)
359 if name not in _order)
348 except (ImportError, AttributeError):
360 except (ImportError, AttributeError):
349 pass
361 pass
350
362
351 paths = _disabledpaths()
363 paths = _disabledpaths()
352 if not paths:
364 if not paths:
353 return {}
365 return {}
354
366
355 exts = {}
367 exts = {}
356 for name, path in paths.iteritems():
368 for name, path in paths.iteritems():
357 doc = _disabledhelp(path)
369 doc = _disabledhelp(path)
358 if doc:
370 if doc:
359 exts[name] = doc
371 exts[name] = doc
360
372
361 return exts
373 return exts
362
374
363 def disabledext(name):
375 def disabledext(name):
364 '''find a specific disabled extension from hgext. returns desc'''
376 '''find a specific disabled extension from hgext. returns desc'''
365 try:
377 try:
366 from hgext import __index__
378 from hgext import __index__
367 if name in _order: # enabled
379 if name in _order: # enabled
368 return
380 return
369 else:
381 else:
370 return gettext(__index__.docs.get(name))
382 return gettext(__index__.docs.get(name))
371 except (ImportError, AttributeError):
383 except (ImportError, AttributeError):
372 pass
384 pass
373
385
374 paths = _disabledpaths()
386 paths = _disabledpaths()
375 if name in paths:
387 if name in paths:
376 return _disabledhelp(paths[name])
388 return _disabledhelp(paths[name])
377
389
378 def disabledcmd(ui, cmd, strict=False):
390 def disabledcmd(ui, cmd, strict=False):
379 '''import disabled extensions until cmd is found.
391 '''import disabled extensions until cmd is found.
380 returns (cmdname, extname, module)'''
392 returns (cmdname, extname, module)'''
381
393
382 paths = _disabledpaths(strip_init=True)
394 paths = _disabledpaths(strip_init=True)
383 if not paths:
395 if not paths:
384 raise error.UnknownCommand(cmd)
396 raise error.UnknownCommand(cmd)
385
397
386 def findcmd(cmd, name, path):
398 def findcmd(cmd, name, path):
387 try:
399 try:
388 mod = loadpath(path, 'hgext.%s' % name)
400 mod = loadpath(path, 'hgext.%s' % name)
389 except Exception:
401 except Exception:
390 return
402 return
391 try:
403 try:
392 aliases, entry = cmdutil.findcmd(cmd,
404 aliases, entry = cmdutil.findcmd(cmd,
393 getattr(mod, 'cmdtable', {}), strict)
405 getattr(mod, 'cmdtable', {}), strict)
394 except (error.AmbiguousCommand, error.UnknownCommand):
406 except (error.AmbiguousCommand, error.UnknownCommand):
395 return
407 return
396 except Exception:
408 except Exception:
397 ui.warn(_('warning: error finding commands in %s\n') % path)
409 ui.warn(_('warning: error finding commands in %s\n') % path)
398 ui.traceback()
410 ui.traceback()
399 return
411 return
400 for c in aliases:
412 for c in aliases:
401 if c.startswith(cmd):
413 if c.startswith(cmd):
402 cmd = c
414 cmd = c
403 break
415 break
404 else:
416 else:
405 cmd = aliases[0]
417 cmd = aliases[0]
406 return (cmd, name, mod)
418 return (cmd, name, mod)
407
419
408 ext = None
420 ext = None
409 # first, search for an extension with the same name as the command
421 # first, search for an extension with the same name as the command
410 path = paths.pop(cmd, None)
422 path = paths.pop(cmd, None)
411 if path:
423 if path:
412 ext = findcmd(cmd, cmd, path)
424 ext = findcmd(cmd, cmd, path)
413 if not ext:
425 if not ext:
414 # otherwise, interrogate each extension until there's a match
426 # otherwise, interrogate each extension until there's a match
415 for name, path in paths.iteritems():
427 for name, path in paths.iteritems():
416 ext = findcmd(cmd, name, path)
428 ext = findcmd(cmd, name, path)
417 if ext:
429 if ext:
418 break
430 break
419 if ext and 'DEPRECATED' not in ext.__doc__:
431 if ext and 'DEPRECATED' not in ext.__doc__:
420 return ext
432 return ext
421
433
422 raise error.UnknownCommand(cmd)
434 raise error.UnknownCommand(cmd)
423
435
424 def enabled(shortname=True):
436 def enabled(shortname=True):
425 '''return a dict of {name: desc} of extensions'''
437 '''return a dict of {name: desc} of extensions'''
426 exts = {}
438 exts = {}
427 for ename, ext in extensions():
439 for ename, ext in extensions():
428 doc = (gettext(ext.__doc__) or _('(no help text available)'))
440 doc = (gettext(ext.__doc__) or _('(no help text available)'))
429 if shortname:
441 if shortname:
430 ename = ename.split('.')[-1]
442 ename = ename.split('.')[-1]
431 exts[ename] = doc.splitlines()[0].strip()
443 exts[ename] = doc.splitlines()[0].strip()
432
444
433 return exts
445 return exts
434
446
435 def moduleversion(module):
447 def moduleversion(module):
436 '''return version information from given module as a string'''
448 '''return version information from given module as a string'''
437 if (util.safehasattr(module, 'getversion')
449 if (util.safehasattr(module, 'getversion')
438 and callable(module.getversion)):
450 and callable(module.getversion)):
439 version = module.getversion()
451 version = module.getversion()
440 elif util.safehasattr(module, '__version__'):
452 elif util.safehasattr(module, '__version__'):
441 version = module.__version__
453 version = module.__version__
442 else:
454 else:
443 version = ''
455 version = ''
444 if isinstance(version, (list, tuple)):
456 if isinstance(version, (list, tuple)):
445 version = '.'.join(str(o) for o in version)
457 version = '.'.join(str(o) for o in version)
446 return version
458 return version
General Comments 0
You need to be logged in to leave comments. Login now