##// END OF EJS Templates
extensions: copy extra __dict__ of original function...
Yuya Nishihara -
r28312:24f1d3c7 default
parent child Browse files
Show More
@@ -1,481 +1,482 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 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
27 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
28 '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
104
105 # Before we do anything with the extension, check against minimum stated
105 # Before we do anything with the extension, check against minimum stated
106 # compatibility. This gives extension authors a mechanism to have their
106 # compatibility. This gives extension authors a mechanism to have their
107 # extensions short circuit when loaded with a known incompatible version
107 # extensions short circuit when loaded with a known incompatible version
108 # of Mercurial.
108 # of Mercurial.
109 minver = getattr(mod, 'minimumhgversion', None)
109 minver = getattr(mod, 'minimumhgversion', None)
110 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
110 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
111 ui.warn(_('(third party extension %s requires version %s or newer '
111 ui.warn(_('(third party extension %s requires version %s or newer '
112 'of Mercurial; disabling)\n') % (shortname, minver))
112 'of Mercurial; disabling)\n') % (shortname, minver))
113 return
113 return
114
114
115 _extensions[shortname] = mod
115 _extensions[shortname] = mod
116 _order.append(shortname)
116 _order.append(shortname)
117 for fn in _aftercallbacks.get(shortname, []):
117 for fn in _aftercallbacks.get(shortname, []):
118 fn(loaded=True)
118 fn(loaded=True)
119 return mod
119 return mod
120
120
121 def loadall(ui):
121 def loadall(ui):
122 result = ui.configitems("extensions")
122 result = ui.configitems("extensions")
123 newindex = len(_order)
123 newindex = len(_order)
124 for (name, path) in result:
124 for (name, path) in result:
125 if path:
125 if path:
126 if path[0] == '!':
126 if path[0] == '!':
127 continue
127 continue
128 try:
128 try:
129 load(ui, name, path)
129 load(ui, name, path)
130 except KeyboardInterrupt:
130 except KeyboardInterrupt:
131 raise
131 raise
132 except Exception as inst:
132 except Exception as inst:
133 if path:
133 if path:
134 ui.warn(_("*** failed to import extension %s from %s: %s\n")
134 ui.warn(_("*** failed to import extension %s from %s: %s\n")
135 % (name, path, inst))
135 % (name, path, inst))
136 else:
136 else:
137 ui.warn(_("*** failed to import extension %s: %s\n")
137 ui.warn(_("*** failed to import extension %s: %s\n")
138 % (name, inst))
138 % (name, inst))
139 ui.traceback()
139 ui.traceback()
140
140
141 for name in _order[newindex:]:
141 for name in _order[newindex:]:
142 uisetup = getattr(_extensions[name], 'uisetup', None)
142 uisetup = getattr(_extensions[name], 'uisetup', None)
143 if uisetup:
143 if uisetup:
144 uisetup(ui)
144 uisetup(ui)
145
145
146 for name in _order[newindex:]:
146 for name in _order[newindex:]:
147 extsetup = getattr(_extensions[name], 'extsetup', None)
147 extsetup = getattr(_extensions[name], 'extsetup', None)
148 if extsetup:
148 if extsetup:
149 try:
149 try:
150 extsetup(ui)
150 extsetup(ui)
151 except TypeError:
151 except TypeError:
152 if extsetup.func_code.co_argcount != 0:
152 if extsetup.func_code.co_argcount != 0:
153 raise
153 raise
154 extsetup() # old extsetup with no ui argument
154 extsetup() # old extsetup with no ui argument
155
155
156 # Call aftercallbacks that were never met.
156 # Call aftercallbacks that were never met.
157 for shortname in _aftercallbacks:
157 for shortname in _aftercallbacks:
158 if shortname in _extensions:
158 if shortname in _extensions:
159 continue
159 continue
160
160
161 for fn in _aftercallbacks[shortname]:
161 for fn in _aftercallbacks[shortname]:
162 fn(loaded=False)
162 fn(loaded=False)
163
163
164 # loadall() is called multiple times and lingering _aftercallbacks
164 # loadall() is called multiple times and lingering _aftercallbacks
165 # entries could result in double execution. See issue4646.
165 # entries could result in double execution. See issue4646.
166 _aftercallbacks.clear()
166 _aftercallbacks.clear()
167
167
168 def afterloaded(extension, callback):
168 def afterloaded(extension, callback):
169 '''Run the specified function after a named extension is loaded.
169 '''Run the specified function after a named extension is loaded.
170
170
171 If the named extension is already loaded, the callback will be called
171 If the named extension is already loaded, the callback will be called
172 immediately.
172 immediately.
173
173
174 If the named extension never loads, the callback will be called after
174 If the named extension never loads, the callback will be called after
175 all extensions have been loaded.
175 all extensions have been loaded.
176
176
177 The callback receives the named argument ``loaded``, which is a boolean
177 The callback receives the named argument ``loaded``, which is a boolean
178 indicating whether the dependent extension actually loaded.
178 indicating whether the dependent extension actually loaded.
179 '''
179 '''
180
180
181 if extension in _extensions:
181 if extension in _extensions:
182 callback(loaded=True)
182 callback(loaded=True)
183 else:
183 else:
184 _aftercallbacks.setdefault(extension, []).append(callback)
184 _aftercallbacks.setdefault(extension, []).append(callback)
185
185
186 def bind(func, *args):
186 def bind(func, *args):
187 '''Partial function application
187 '''Partial function application
188
188
189 Returns a new function that is the partial application of args and kwargs
189 Returns a new function that is the partial application of args and kwargs
190 to func. For example,
190 to func. For example,
191
191
192 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
192 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
193 assert callable(func)
193 assert callable(func)
194 def closure(*a, **kw):
194 def closure(*a, **kw):
195 return func(*(args + a), **kw)
195 return func(*(args + a), **kw)
196 return closure
196 return closure
197
197
198 def _updatewrapper(wrap, origfn):
198 def _updatewrapper(wrap, origfn):
199 '''Copy attributes to wrapper function'''
199 '''Copy attributes to wrapper function'''
200 wrap.__module__ = getattr(origfn, '__module__')
200 wrap.__module__ = getattr(origfn, '__module__')
201 wrap.__doc__ = getattr(origfn, '__doc__')
201 wrap.__doc__ = getattr(origfn, '__doc__')
202 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
202
203
203 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
204 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
204 '''Wrap the command named `command' in table
205 '''Wrap the command named `command' in table
205
206
206 Replace command in the command table with wrapper. The wrapped command will
207 Replace command in the command table with wrapper. The wrapped command will
207 be inserted into the command table specified by the table argument.
208 be inserted into the command table specified by the table argument.
208
209
209 The wrapper will be called like
210 The wrapper will be called like
210
211
211 wrapper(orig, *args, **kwargs)
212 wrapper(orig, *args, **kwargs)
212
213
213 where orig is the original (wrapped) function, and *args, **kwargs
214 where orig is the original (wrapped) function, and *args, **kwargs
214 are the arguments passed to it.
215 are the arguments passed to it.
215
216
216 Optionally append to the command synopsis and docstring, used for help.
217 Optionally append to the command synopsis and docstring, used for help.
217 For example, if your extension wraps the ``bookmarks`` command to add the
218 For example, if your extension wraps the ``bookmarks`` command to add the
218 flags ``--remote`` and ``--all`` you might call this function like so:
219 flags ``--remote`` and ``--all`` you might call this function like so:
219
220
220 synopsis = ' [-a] [--remote]'
221 synopsis = ' [-a] [--remote]'
221 docstring = """
222 docstring = """
222
223
223 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
224 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
224 flags to the bookmarks command. Either flag will show the remote bookmarks
225 flags to the bookmarks command. Either flag will show the remote bookmarks
225 known to the repository; ``--remote`` will also suppress the output of the
226 known to the repository; ``--remote`` will also suppress the output of the
226 local bookmarks.
227 local bookmarks.
227 """
228 """
228
229
229 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
230 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
230 synopsis, docstring)
231 synopsis, docstring)
231 '''
232 '''
232 assert callable(wrapper)
233 assert callable(wrapper)
233 aliases, entry = cmdutil.findcmd(command, table)
234 aliases, entry = cmdutil.findcmd(command, table)
234 for alias, e in table.iteritems():
235 for alias, e in table.iteritems():
235 if e is entry:
236 if e is entry:
236 key = alias
237 key = alias
237 break
238 break
238
239
239 origfn = entry[0]
240 origfn = entry[0]
240 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
241 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
241 _updatewrapper(wrap, origfn)
242 _updatewrapper(wrap, origfn)
242 if docstring is not None:
243 if docstring is not None:
243 wrap.__doc__ += docstring
244 wrap.__doc__ += docstring
244
245
245 newentry = list(entry)
246 newentry = list(entry)
246 newentry[0] = wrap
247 newentry[0] = wrap
247 if synopsis is not None:
248 if synopsis is not None:
248 newentry[2] += synopsis
249 newentry[2] += synopsis
249 table[key] = tuple(newentry)
250 table[key] = tuple(newentry)
250 return entry
251 return entry
251
252
252 def wrapfunction(container, funcname, wrapper):
253 def wrapfunction(container, funcname, wrapper):
253 '''Wrap the function named funcname in container
254 '''Wrap the function named funcname in container
254
255
255 Replace the funcname member in the given container with the specified
256 Replace the funcname member in the given container with the specified
256 wrapper. The container is typically a module, class, or instance.
257 wrapper. The container is typically a module, class, or instance.
257
258
258 The wrapper will be called like
259 The wrapper will be called like
259
260
260 wrapper(orig, *args, **kwargs)
261 wrapper(orig, *args, **kwargs)
261
262
262 where orig is the original (wrapped) function, and *args, **kwargs
263 where orig is the original (wrapped) function, and *args, **kwargs
263 are the arguments passed to it.
264 are the arguments passed to it.
264
265
265 Wrapping methods of the repository object is not recommended since
266 Wrapping methods of the repository object is not recommended since
266 it conflicts with extensions that extend the repository by
267 it conflicts with extensions that extend the repository by
267 subclassing. All extensions that need to extend methods of
268 subclassing. All extensions that need to extend methods of
268 localrepository should use this subclassing trick: namely,
269 localrepository should use this subclassing trick: namely,
269 reposetup() should look like
270 reposetup() should look like
270
271
271 def reposetup(ui, repo):
272 def reposetup(ui, repo):
272 class myrepo(repo.__class__):
273 class myrepo(repo.__class__):
273 def whatever(self, *args, **kwargs):
274 def whatever(self, *args, **kwargs):
274 [...extension stuff...]
275 [...extension stuff...]
275 super(myrepo, self).whatever(*args, **kwargs)
276 super(myrepo, self).whatever(*args, **kwargs)
276 [...extension stuff...]
277 [...extension stuff...]
277
278
278 repo.__class__ = myrepo
279 repo.__class__ = myrepo
279
280
280 In general, combining wrapfunction() with subclassing does not
281 In general, combining wrapfunction() with subclassing does not
281 work. Since you cannot control what other extensions are loaded by
282 work. Since you cannot control what other extensions are loaded by
282 your end users, you should play nicely with others by using the
283 your end users, you should play nicely with others by using the
283 subclass trick.
284 subclass trick.
284 '''
285 '''
285 assert callable(wrapper)
286 assert callable(wrapper)
286
287
287 origfn = getattr(container, funcname)
288 origfn = getattr(container, funcname)
288 assert callable(origfn)
289 assert callable(origfn)
289 wrap = bind(wrapper, origfn)
290 wrap = bind(wrapper, origfn)
290 _updatewrapper(wrap, origfn)
291 _updatewrapper(wrap, origfn)
291 setattr(container, funcname, wrap)
292 setattr(container, funcname, wrap)
292 return origfn
293 return origfn
293
294
294 def _disabledpaths(strip_init=False):
295 def _disabledpaths(strip_init=False):
295 '''find paths of disabled extensions. returns a dict of {name: path}
296 '''find paths of disabled extensions. returns a dict of {name: path}
296 removes /__init__.py from packages if strip_init is True'''
297 removes /__init__.py from packages if strip_init is True'''
297 import hgext
298 import hgext
298 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
299 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
299 try: # might not be a filesystem path
300 try: # might not be a filesystem path
300 files = os.listdir(extpath)
301 files = os.listdir(extpath)
301 except OSError:
302 except OSError:
302 return {}
303 return {}
303
304
304 exts = {}
305 exts = {}
305 for e in files:
306 for e in files:
306 if e.endswith('.py'):
307 if e.endswith('.py'):
307 name = e.rsplit('.', 1)[0]
308 name = e.rsplit('.', 1)[0]
308 path = os.path.join(extpath, e)
309 path = os.path.join(extpath, e)
309 else:
310 else:
310 name = e
311 name = e
311 path = os.path.join(extpath, e, '__init__.py')
312 path = os.path.join(extpath, e, '__init__.py')
312 if not os.path.exists(path):
313 if not os.path.exists(path):
313 continue
314 continue
314 if strip_init:
315 if strip_init:
315 path = os.path.dirname(path)
316 path = os.path.dirname(path)
316 if name in exts or name in _order or name == '__init__':
317 if name in exts or name in _order or name == '__init__':
317 continue
318 continue
318 exts[name] = path
319 exts[name] = path
319 return exts
320 return exts
320
321
321 def _moduledoc(file):
322 def _moduledoc(file):
322 '''return the top-level python documentation for the given file
323 '''return the top-level python documentation for the given file
323
324
324 Loosely inspired by pydoc.source_synopsis(), but rewritten to
325 Loosely inspired by pydoc.source_synopsis(), but rewritten to
325 handle triple quotes and to return the whole text instead of just
326 handle triple quotes and to return the whole text instead of just
326 the synopsis'''
327 the synopsis'''
327 result = []
328 result = []
328
329
329 line = file.readline()
330 line = file.readline()
330 while line[:1] == '#' or not line.strip():
331 while line[:1] == '#' or not line.strip():
331 line = file.readline()
332 line = file.readline()
332 if not line:
333 if not line:
333 break
334 break
334
335
335 start = line[:3]
336 start = line[:3]
336 if start == '"""' or start == "'''":
337 if start == '"""' or start == "'''":
337 line = line[3:]
338 line = line[3:]
338 while line:
339 while line:
339 if line.rstrip().endswith(start):
340 if line.rstrip().endswith(start):
340 line = line.split(start)[0]
341 line = line.split(start)[0]
341 if line:
342 if line:
342 result.append(line)
343 result.append(line)
343 break
344 break
344 elif not line:
345 elif not line:
345 return None # unmatched delimiter
346 return None # unmatched delimiter
346 result.append(line)
347 result.append(line)
347 line = file.readline()
348 line = file.readline()
348 else:
349 else:
349 return None
350 return None
350
351
351 return ''.join(result)
352 return ''.join(result)
352
353
353 def _disabledhelp(path):
354 def _disabledhelp(path):
354 '''retrieve help synopsis of a disabled extension (without importing)'''
355 '''retrieve help synopsis of a disabled extension (without importing)'''
355 try:
356 try:
356 file = open(path)
357 file = open(path)
357 except IOError:
358 except IOError:
358 return
359 return
359 else:
360 else:
360 doc = _moduledoc(file)
361 doc = _moduledoc(file)
361 file.close()
362 file.close()
362
363
363 if doc: # extracting localized synopsis
364 if doc: # extracting localized synopsis
364 return gettext(doc).splitlines()[0]
365 return gettext(doc).splitlines()[0]
365 else:
366 else:
366 return _('(no help text available)')
367 return _('(no help text available)')
367
368
368 def disabled():
369 def disabled():
369 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
370 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
370 try:
371 try:
371 from hgext import __index__
372 from hgext import __index__
372 return dict((name, gettext(desc))
373 return dict((name, gettext(desc))
373 for name, desc in __index__.docs.iteritems()
374 for name, desc in __index__.docs.iteritems()
374 if name not in _order)
375 if name not in _order)
375 except (ImportError, AttributeError):
376 except (ImportError, AttributeError):
376 pass
377 pass
377
378
378 paths = _disabledpaths()
379 paths = _disabledpaths()
379 if not paths:
380 if not paths:
380 return {}
381 return {}
381
382
382 exts = {}
383 exts = {}
383 for name, path in paths.iteritems():
384 for name, path in paths.iteritems():
384 doc = _disabledhelp(path)
385 doc = _disabledhelp(path)
385 if doc:
386 if doc:
386 exts[name] = doc
387 exts[name] = doc
387
388
388 return exts
389 return exts
389
390
390 def disabledext(name):
391 def disabledext(name):
391 '''find a specific disabled extension from hgext. returns desc'''
392 '''find a specific disabled extension from hgext. returns desc'''
392 try:
393 try:
393 from hgext import __index__
394 from hgext import __index__
394 if name in _order: # enabled
395 if name in _order: # enabled
395 return
396 return
396 else:
397 else:
397 return gettext(__index__.docs.get(name))
398 return gettext(__index__.docs.get(name))
398 except (ImportError, AttributeError):
399 except (ImportError, AttributeError):
399 pass
400 pass
400
401
401 paths = _disabledpaths()
402 paths = _disabledpaths()
402 if name in paths:
403 if name in paths:
403 return _disabledhelp(paths[name])
404 return _disabledhelp(paths[name])
404
405
405 def disabledcmd(ui, cmd, strict=False):
406 def disabledcmd(ui, cmd, strict=False):
406 '''import disabled extensions until cmd is found.
407 '''import disabled extensions until cmd is found.
407 returns (cmdname, extname, module)'''
408 returns (cmdname, extname, module)'''
408
409
409 paths = _disabledpaths(strip_init=True)
410 paths = _disabledpaths(strip_init=True)
410 if not paths:
411 if not paths:
411 raise error.UnknownCommand(cmd)
412 raise error.UnknownCommand(cmd)
412
413
413 def findcmd(cmd, name, path):
414 def findcmd(cmd, name, path):
414 try:
415 try:
415 mod = loadpath(path, 'hgext.%s' % name)
416 mod = loadpath(path, 'hgext.%s' % name)
416 except Exception:
417 except Exception:
417 return
418 return
418 try:
419 try:
419 aliases, entry = cmdutil.findcmd(cmd,
420 aliases, entry = cmdutil.findcmd(cmd,
420 getattr(mod, 'cmdtable', {}), strict)
421 getattr(mod, 'cmdtable', {}), strict)
421 except (error.AmbiguousCommand, error.UnknownCommand):
422 except (error.AmbiguousCommand, error.UnknownCommand):
422 return
423 return
423 except Exception:
424 except Exception:
424 ui.warn(_('warning: error finding commands in %s\n') % path)
425 ui.warn(_('warning: error finding commands in %s\n') % path)
425 ui.traceback()
426 ui.traceback()
426 return
427 return
427 for c in aliases:
428 for c in aliases:
428 if c.startswith(cmd):
429 if c.startswith(cmd):
429 cmd = c
430 cmd = c
430 break
431 break
431 else:
432 else:
432 cmd = aliases[0]
433 cmd = aliases[0]
433 return (cmd, name, mod)
434 return (cmd, name, mod)
434
435
435 ext = None
436 ext = None
436 # first, search for an extension with the same name as the command
437 # first, search for an extension with the same name as the command
437 path = paths.pop(cmd, None)
438 path = paths.pop(cmd, None)
438 if path:
439 if path:
439 ext = findcmd(cmd, cmd, path)
440 ext = findcmd(cmd, cmd, path)
440 if not ext:
441 if not ext:
441 # otherwise, interrogate each extension until there's a match
442 # otherwise, interrogate each extension until there's a match
442 for name, path in paths.iteritems():
443 for name, path in paths.iteritems():
443 ext = findcmd(cmd, name, path)
444 ext = findcmd(cmd, name, path)
444 if ext:
445 if ext:
445 break
446 break
446 if ext and 'DEPRECATED' not in ext.__doc__:
447 if ext and 'DEPRECATED' not in ext.__doc__:
447 return ext
448 return ext
448
449
449 raise error.UnknownCommand(cmd)
450 raise error.UnknownCommand(cmd)
450
451
451 def enabled(shortname=True):
452 def enabled(shortname=True):
452 '''return a dict of {name: desc} of extensions'''
453 '''return a dict of {name: desc} of extensions'''
453 exts = {}
454 exts = {}
454 for ename, ext in extensions():
455 for ename, ext in extensions():
455 doc = (gettext(ext.__doc__) or _('(no help text available)'))
456 doc = (gettext(ext.__doc__) or _('(no help text available)'))
456 if shortname:
457 if shortname:
457 ename = ename.split('.')[-1]
458 ename = ename.split('.')[-1]
458 exts[ename] = doc.splitlines()[0].strip()
459 exts[ename] = doc.splitlines()[0].strip()
459
460
460 return exts
461 return exts
461
462
462 def notloaded():
463 def notloaded():
463 '''return short names of extensions that failed to load'''
464 '''return short names of extensions that failed to load'''
464 return [name for name, mod in _extensions.iteritems() if mod is None]
465 return [name for name, mod in _extensions.iteritems() if mod is None]
465
466
466 def moduleversion(module):
467 def moduleversion(module):
467 '''return version information from given module as a string'''
468 '''return version information from given module as a string'''
468 if (util.safehasattr(module, 'getversion')
469 if (util.safehasattr(module, 'getversion')
469 and callable(module.getversion)):
470 and callable(module.getversion)):
470 version = module.getversion()
471 version = module.getversion()
471 elif util.safehasattr(module, '__version__'):
472 elif util.safehasattr(module, '__version__'):
472 version = module.__version__
473 version = module.__version__
473 else:
474 else:
474 version = ''
475 version = ''
475 if isinstance(version, (list, tuple)):
476 if isinstance(version, (list, tuple)):
476 version = '.'.join(str(o) for o in version)
477 version = '.'.join(str(o) for o in version)
477 return version
478 return version
478
479
479 def ismoduleinternal(module):
480 def ismoduleinternal(module):
480 exttestedwith = getattr(module, 'testedwith', None)
481 exttestedwith = getattr(module, 'testedwith', None)
481 return exttestedwith == "internal"
482 return exttestedwith == "internal"
General Comments 0
You need to be logged in to leave comments. Login now