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