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