##// END OF EJS Templates
extensions: drop dead code trying to exclude deprecated disabled commands...
Yuya Nishihara -
r37994:5b60f7d6 default
parent child Browse files
Show More
@@ -1,734 +1,734 b''
1 1 # extensions.py - extension handling for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import functools
11 11 import imp
12 12 import inspect
13 13 import os
14 14
15 15 from .i18n import (
16 16 _,
17 17 gettext,
18 18 )
19 19
20 20 from . import (
21 21 cmdutil,
22 22 configitems,
23 23 error,
24 24 pycompat,
25 25 util,
26 26 )
27 27
28 28 from .utils import (
29 29 stringutil,
30 30 )
31 31
32 32 _extensions = {}
33 33 _disabledextensions = {}
34 34 _aftercallbacks = {}
35 35 _order = []
36 36 _builtin = {
37 37 'hbisect',
38 38 'bookmarks',
39 39 'color',
40 40 'parentrevspec',
41 41 'progress',
42 42 'interhg',
43 43 'inotify',
44 44 'hgcia'
45 45 }
46 46
47 47 def extensions(ui=None):
48 48 if ui:
49 49 def enabled(name):
50 50 for format in ['%s', 'hgext.%s']:
51 51 conf = ui.config('extensions', format % name)
52 52 if conf is not None and not conf.startswith('!'):
53 53 return True
54 54 else:
55 55 enabled = lambda name: True
56 56 for name in _order:
57 57 module = _extensions[name]
58 58 if module and enabled(name):
59 59 yield name, module
60 60
61 61 def find(name):
62 62 '''return module with given extension name'''
63 63 mod = None
64 64 try:
65 65 mod = _extensions[name]
66 66 except KeyError:
67 67 for k, v in _extensions.iteritems():
68 68 if k.endswith('.' + name) or k.endswith('/' + name):
69 69 mod = v
70 70 break
71 71 if not mod:
72 72 raise KeyError(name)
73 73 return mod
74 74
75 75 def loadpath(path, module_name):
76 76 module_name = module_name.replace('.', '_')
77 77 path = util.normpath(util.expandpath(path))
78 78 module_name = pycompat.fsdecode(module_name)
79 79 path = pycompat.fsdecode(path)
80 80 if os.path.isdir(path):
81 81 # module/__init__.py style
82 82 d, f = os.path.split(path)
83 83 fd, fpath, desc = imp.find_module(f, [d])
84 84 return imp.load_module(module_name, fd, fpath, desc)
85 85 else:
86 86 try:
87 87 return imp.load_source(module_name, path)
88 88 except IOError as exc:
89 89 if not exc.filename:
90 90 exc.filename = path # python does not fill this
91 91 raise
92 92
93 93 def _importh(name):
94 94 """import and return the <name> module"""
95 95 mod = __import__(pycompat.sysstr(name))
96 96 components = name.split('.')
97 97 for comp in components[1:]:
98 98 mod = getattr(mod, comp)
99 99 return mod
100 100
101 101 def _importext(name, path=None, reportfunc=None):
102 102 if path:
103 103 # the module will be loaded in sys.modules
104 104 # choose an unique name so that it doesn't
105 105 # conflicts with other modules
106 106 mod = loadpath(path, 'hgext.%s' % name)
107 107 else:
108 108 try:
109 109 mod = _importh("hgext.%s" % name)
110 110 except ImportError as err:
111 111 if reportfunc:
112 112 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
113 113 try:
114 114 mod = _importh("hgext3rd.%s" % name)
115 115 except ImportError as err:
116 116 if reportfunc:
117 117 reportfunc(err, "hgext3rd.%s" % name, name)
118 118 mod = _importh(name)
119 119 return mod
120 120
121 121 def _reportimporterror(ui, err, failed, next):
122 122 # note: this ui.debug happens before --debug is processed,
123 123 # Use --config ui.debug=1 to see them.
124 124 ui.debug('could not import %s (%s): trying %s\n'
125 125 % (failed, stringutil.forcebytestr(err), next))
126 126 if ui.debugflag:
127 127 ui.traceback()
128 128
129 129 def _rejectunicode(name, xs):
130 130 if isinstance(xs, (list, set, tuple)):
131 131 for x in xs:
132 132 _rejectunicode(name, x)
133 133 elif isinstance(xs, dict):
134 134 for k, v in xs.items():
135 135 _rejectunicode(name, k)
136 136 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
137 137 elif isinstance(xs, type(u'')):
138 138 raise error.ProgrammingError(b"unicode %r found in %s" % (xs, name),
139 139 hint="use b'' to make it byte string")
140 140
141 141 # attributes set by registrar.command
142 142 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
143 143
144 144 def _validatecmdtable(ui, cmdtable):
145 145 """Check if extension commands have required attributes"""
146 146 for c, e in cmdtable.iteritems():
147 147 f = e[0]
148 148 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
149 149 if not missing:
150 150 continue
151 151 raise error.ProgrammingError(
152 152 'missing attributes: %s' % ', '.join(missing),
153 153 hint="use @command decorator to register '%s'" % c)
154 154
155 155 def _validatetables(ui, mod):
156 156 """Sanity check for loadable tables provided by extension module"""
157 157 for t in ['cmdtable', 'colortable', 'configtable']:
158 158 _rejectunicode(t, getattr(mod, t, {}))
159 159 for t in ['filesetpredicate', 'internalmerge', 'revsetpredicate',
160 160 'templatefilter', 'templatefunc', 'templatekeyword']:
161 161 o = getattr(mod, t, None)
162 162 if o:
163 163 _rejectunicode(t, o._table)
164 164 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
165 165
166 166 def load(ui, name, path):
167 167 if name.startswith('hgext.') or name.startswith('hgext/'):
168 168 shortname = name[6:]
169 169 else:
170 170 shortname = name
171 171 if shortname in _builtin:
172 172 return None
173 173 if shortname in _extensions:
174 174 return _extensions[shortname]
175 175 _extensions[shortname] = None
176 176 mod = _importext(name, path, bind(_reportimporterror, ui))
177 177
178 178 # Before we do anything with the extension, check against minimum stated
179 179 # compatibility. This gives extension authors a mechanism to have their
180 180 # extensions short circuit when loaded with a known incompatible version
181 181 # of Mercurial.
182 182 minver = getattr(mod, 'minimumhgversion', None)
183 183 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
184 184 ui.warn(_('(third party extension %s requires version %s or newer '
185 185 'of Mercurial; disabling)\n') % (shortname, minver))
186 186 return
187 187 _validatetables(ui, mod)
188 188
189 189 _extensions[shortname] = mod
190 190 _order.append(shortname)
191 191 for fn in _aftercallbacks.get(shortname, []):
192 192 fn(loaded=True)
193 193 return mod
194 194
195 195 def _runuisetup(name, ui):
196 196 uisetup = getattr(_extensions[name], 'uisetup', None)
197 197 if uisetup:
198 198 try:
199 199 uisetup(ui)
200 200 except Exception as inst:
201 201 ui.traceback(force=True)
202 202 msg = stringutil.forcebytestr(inst)
203 203 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
204 204 return False
205 205 return True
206 206
207 207 def _runextsetup(name, ui):
208 208 extsetup = getattr(_extensions[name], 'extsetup', None)
209 209 if extsetup:
210 210 try:
211 211 try:
212 212 extsetup(ui)
213 213 except TypeError:
214 214 if pycompat.getargspec(extsetup).args:
215 215 raise
216 216 extsetup() # old extsetup with no ui argument
217 217 except Exception as inst:
218 218 ui.traceback(force=True)
219 219 msg = stringutil.forcebytestr(inst)
220 220 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
221 221 return False
222 222 return True
223 223
224 224 def loadall(ui, whitelist=None):
225 225 result = ui.configitems("extensions")
226 226 if whitelist is not None:
227 227 result = [(k, v) for (k, v) in result if k in whitelist]
228 228 newindex = len(_order)
229 229 for (name, path) in result:
230 230 if path:
231 231 if path[0:1] == '!':
232 232 _disabledextensions[name] = path[1:]
233 233 continue
234 234 try:
235 235 load(ui, name, path)
236 236 except Exception as inst:
237 237 msg = stringutil.forcebytestr(inst)
238 238 if path:
239 239 ui.warn(_("*** failed to import extension %s from %s: %s\n")
240 240 % (name, path, msg))
241 241 else:
242 242 ui.warn(_("*** failed to import extension %s: %s\n")
243 243 % (name, msg))
244 244 if isinstance(inst, error.Hint) and inst.hint:
245 245 ui.warn(_("*** (%s)\n") % inst.hint)
246 246 ui.traceback()
247 247 # list of (objname, loadermod, loadername) tuple:
248 248 # - objname is the name of an object in extension module,
249 249 # from which extra information is loaded
250 250 # - loadermod is the module where loader is placed
251 251 # - loadername is the name of the function,
252 252 # which takes (ui, extensionname, extraobj) arguments
253 253 #
254 254 # This one is for the list of item that must be run before running any setup
255 255 earlyextraloaders = [
256 256 ('configtable', configitems, 'loadconfigtable'),
257 257 ]
258 258 _loadextra(ui, newindex, earlyextraloaders)
259 259
260 260 broken = set()
261 261 for name in _order[newindex:]:
262 262 if not _runuisetup(name, ui):
263 263 broken.add(name)
264 264
265 265 for name in _order[newindex:]:
266 266 if name in broken:
267 267 continue
268 268 if not _runextsetup(name, ui):
269 269 broken.add(name)
270 270
271 271 for name in broken:
272 272 _extensions[name] = None
273 273
274 274 # Call aftercallbacks that were never met.
275 275 for shortname in _aftercallbacks:
276 276 if shortname in _extensions:
277 277 continue
278 278
279 279 for fn in _aftercallbacks[shortname]:
280 280 fn(loaded=False)
281 281
282 282 # loadall() is called multiple times and lingering _aftercallbacks
283 283 # entries could result in double execution. See issue4646.
284 284 _aftercallbacks.clear()
285 285
286 286 # delay importing avoids cyclic dependency (especially commands)
287 287 from . import (
288 288 color,
289 289 commands,
290 290 filemerge,
291 291 fileset,
292 292 revset,
293 293 templatefilters,
294 294 templatefuncs,
295 295 templatekw,
296 296 )
297 297
298 298 # list of (objname, loadermod, loadername) tuple:
299 299 # - objname is the name of an object in extension module,
300 300 # from which extra information is loaded
301 301 # - loadermod is the module where loader is placed
302 302 # - loadername is the name of the function,
303 303 # which takes (ui, extensionname, extraobj) arguments
304 304 extraloaders = [
305 305 ('cmdtable', commands, 'loadcmdtable'),
306 306 ('colortable', color, 'loadcolortable'),
307 307 ('filesetpredicate', fileset, 'loadpredicate'),
308 308 ('internalmerge', filemerge, 'loadinternalmerge'),
309 309 ('revsetpredicate', revset, 'loadpredicate'),
310 310 ('templatefilter', templatefilters, 'loadfilter'),
311 311 ('templatefunc', templatefuncs, 'loadfunction'),
312 312 ('templatekeyword', templatekw, 'loadkeyword'),
313 313 ]
314 314 _loadextra(ui, newindex, extraloaders)
315 315
316 316 def _loadextra(ui, newindex, extraloaders):
317 317 for name in _order[newindex:]:
318 318 module = _extensions[name]
319 319 if not module:
320 320 continue # loading this module failed
321 321
322 322 for objname, loadermod, loadername in extraloaders:
323 323 extraobj = getattr(module, objname, None)
324 324 if extraobj is not None:
325 325 getattr(loadermod, loadername)(ui, name, extraobj)
326 326
327 327 def afterloaded(extension, callback):
328 328 '''Run the specified function after a named extension is loaded.
329 329
330 330 If the named extension is already loaded, the callback will be called
331 331 immediately.
332 332
333 333 If the named extension never loads, the callback will be called after
334 334 all extensions have been loaded.
335 335
336 336 The callback receives the named argument ``loaded``, which is a boolean
337 337 indicating whether the dependent extension actually loaded.
338 338 '''
339 339
340 340 if extension in _extensions:
341 341 # Report loaded as False if the extension is disabled
342 342 loaded = (_extensions[extension] is not None)
343 343 callback(loaded=loaded)
344 344 else:
345 345 _aftercallbacks.setdefault(extension, []).append(callback)
346 346
347 347 def bind(func, *args):
348 348 '''Partial function application
349 349
350 350 Returns a new function that is the partial application of args and kwargs
351 351 to func. For example,
352 352
353 353 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
354 354 assert callable(func)
355 355 def closure(*a, **kw):
356 356 return func(*(args + a), **kw)
357 357 return closure
358 358
359 359 def _updatewrapper(wrap, origfn, unboundwrapper):
360 360 '''Copy and add some useful attributes to wrapper'''
361 361 try:
362 362 wrap.__name__ = origfn.__name__
363 363 except AttributeError:
364 364 pass
365 365 wrap.__module__ = getattr(origfn, '__module__')
366 366 wrap.__doc__ = getattr(origfn, '__doc__')
367 367 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
368 368 wrap._origfunc = origfn
369 369 wrap._unboundwrapper = unboundwrapper
370 370
371 371 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
372 372 '''Wrap the command named `command' in table
373 373
374 374 Replace command in the command table with wrapper. The wrapped command will
375 375 be inserted into the command table specified by the table argument.
376 376
377 377 The wrapper will be called like
378 378
379 379 wrapper(orig, *args, **kwargs)
380 380
381 381 where orig is the original (wrapped) function, and *args, **kwargs
382 382 are the arguments passed to it.
383 383
384 384 Optionally append to the command synopsis and docstring, used for help.
385 385 For example, if your extension wraps the ``bookmarks`` command to add the
386 386 flags ``--remote`` and ``--all`` you might call this function like so:
387 387
388 388 synopsis = ' [-a] [--remote]'
389 389 docstring = """
390 390
391 391 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
392 392 flags to the bookmarks command. Either flag will show the remote bookmarks
393 393 known to the repository; ``--remote`` will also suppress the output of the
394 394 local bookmarks.
395 395 """
396 396
397 397 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
398 398 synopsis, docstring)
399 399 '''
400 400 assert callable(wrapper)
401 401 aliases, entry = cmdutil.findcmd(command, table)
402 402 for alias, e in table.iteritems():
403 403 if e is entry:
404 404 key = alias
405 405 break
406 406
407 407 origfn = entry[0]
408 408 wrap = functools.partial(util.checksignature(wrapper),
409 409 util.checksignature(origfn))
410 410 _updatewrapper(wrap, origfn, wrapper)
411 411 if docstring is not None:
412 412 wrap.__doc__ += docstring
413 413
414 414 newentry = list(entry)
415 415 newentry[0] = wrap
416 416 if synopsis is not None:
417 417 newentry[2] += synopsis
418 418 table[key] = tuple(newentry)
419 419 return entry
420 420
421 421 def wrapfilecache(cls, propname, wrapper):
422 422 """Wraps a filecache property.
423 423
424 424 These can't be wrapped using the normal wrapfunction.
425 425 """
426 426 propname = pycompat.sysstr(propname)
427 427 assert callable(wrapper)
428 428 for currcls in cls.__mro__:
429 429 if propname in currcls.__dict__:
430 430 origfn = currcls.__dict__[propname].func
431 431 assert callable(origfn)
432 432 def wrap(*args, **kwargs):
433 433 return wrapper(origfn, *args, **kwargs)
434 434 currcls.__dict__[propname].func = wrap
435 435 break
436 436
437 437 if currcls is object:
438 438 raise AttributeError(r"type '%s' has no property '%s'" % (
439 439 cls, propname))
440 440
441 441 class wrappedfunction(object):
442 442 '''context manager for temporarily wrapping a function'''
443 443
444 444 def __init__(self, container, funcname, wrapper):
445 445 assert callable(wrapper)
446 446 self._container = container
447 447 self._funcname = funcname
448 448 self._wrapper = wrapper
449 449
450 450 def __enter__(self):
451 451 wrapfunction(self._container, self._funcname, self._wrapper)
452 452
453 453 def __exit__(self, exctype, excvalue, traceback):
454 454 unwrapfunction(self._container, self._funcname, self._wrapper)
455 455
456 456 def wrapfunction(container, funcname, wrapper):
457 457 '''Wrap the function named funcname in container
458 458
459 459 Replace the funcname member in the given container with the specified
460 460 wrapper. The container is typically a module, class, or instance.
461 461
462 462 The wrapper will be called like
463 463
464 464 wrapper(orig, *args, **kwargs)
465 465
466 466 where orig is the original (wrapped) function, and *args, **kwargs
467 467 are the arguments passed to it.
468 468
469 469 Wrapping methods of the repository object is not recommended since
470 470 it conflicts with extensions that extend the repository by
471 471 subclassing. All extensions that need to extend methods of
472 472 localrepository should use this subclassing trick: namely,
473 473 reposetup() should look like
474 474
475 475 def reposetup(ui, repo):
476 476 class myrepo(repo.__class__):
477 477 def whatever(self, *args, **kwargs):
478 478 [...extension stuff...]
479 479 super(myrepo, self).whatever(*args, **kwargs)
480 480 [...extension stuff...]
481 481
482 482 repo.__class__ = myrepo
483 483
484 484 In general, combining wrapfunction() with subclassing does not
485 485 work. Since you cannot control what other extensions are loaded by
486 486 your end users, you should play nicely with others by using the
487 487 subclass trick.
488 488 '''
489 489 assert callable(wrapper)
490 490
491 491 origfn = getattr(container, funcname)
492 492 assert callable(origfn)
493 493 if inspect.ismodule(container):
494 494 # origfn is not an instance or class method. "partial" can be used.
495 495 # "partial" won't insert a frame in traceback.
496 496 wrap = functools.partial(wrapper, origfn)
497 497 else:
498 498 # "partial" cannot be safely used. Emulate its effect by using "bind".
499 499 # The downside is one more frame in traceback.
500 500 wrap = bind(wrapper, origfn)
501 501 _updatewrapper(wrap, origfn, wrapper)
502 502 setattr(container, funcname, wrap)
503 503 return origfn
504 504
505 505 def unwrapfunction(container, funcname, wrapper=None):
506 506 '''undo wrapfunction
507 507
508 508 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
509 509 from the chain of wrappers.
510 510
511 511 Return the removed wrapper.
512 512 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
513 513 wrapper is not None but is not found in the wrapper chain.
514 514 '''
515 515 chain = getwrapperchain(container, funcname)
516 516 origfn = chain.pop()
517 517 if wrapper is None:
518 518 wrapper = chain[0]
519 519 chain.remove(wrapper)
520 520 setattr(container, funcname, origfn)
521 521 for w in reversed(chain):
522 522 wrapfunction(container, funcname, w)
523 523 return wrapper
524 524
525 525 def getwrapperchain(container, funcname):
526 526 '''get a chain of wrappers of a function
527 527
528 528 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
529 529
530 530 The wrapper functions are the ones passed to wrapfunction, whose first
531 531 argument is origfunc.
532 532 '''
533 533 result = []
534 534 fn = getattr(container, funcname)
535 535 while fn:
536 536 assert callable(fn)
537 537 result.append(getattr(fn, '_unboundwrapper', fn))
538 538 fn = getattr(fn, '_origfunc', None)
539 539 return result
540 540
541 541 def _disabledpaths(strip_init=False):
542 542 '''find paths of disabled extensions. returns a dict of {name: path}
543 543 removes /__init__.py from packages if strip_init is True'''
544 544 import hgext
545 545 extpath = os.path.dirname(
546 546 os.path.abspath(pycompat.fsencode(hgext.__file__)))
547 547 try: # might not be a filesystem path
548 548 files = os.listdir(extpath)
549 549 except OSError:
550 550 return {}
551 551
552 552 exts = {}
553 553 for e in files:
554 554 if e.endswith('.py'):
555 555 name = e.rsplit('.', 1)[0]
556 556 path = os.path.join(extpath, e)
557 557 else:
558 558 name = e
559 559 path = os.path.join(extpath, e, '__init__.py')
560 560 if not os.path.exists(path):
561 561 continue
562 562 if strip_init:
563 563 path = os.path.dirname(path)
564 564 if name in exts or name in _order or name == '__init__':
565 565 continue
566 566 exts[name] = path
567 567 for name, path in _disabledextensions.iteritems():
568 568 # If no path was provided for a disabled extension (e.g. "color=!"),
569 569 # don't replace the path we already found by the scan above.
570 570 if path:
571 571 exts[name] = path
572 572 return exts
573 573
574 574 def _moduledoc(file):
575 575 '''return the top-level python documentation for the given file
576 576
577 577 Loosely inspired by pydoc.source_synopsis(), but rewritten to
578 578 handle triple quotes and to return the whole text instead of just
579 579 the synopsis'''
580 580 result = []
581 581
582 582 line = file.readline()
583 583 while line[:1] == '#' or not line.strip():
584 584 line = file.readline()
585 585 if not line:
586 586 break
587 587
588 588 start = line[:3]
589 589 if start == '"""' or start == "'''":
590 590 line = line[3:]
591 591 while line:
592 592 if line.rstrip().endswith(start):
593 593 line = line.split(start)[0]
594 594 if line:
595 595 result.append(line)
596 596 break
597 597 elif not line:
598 598 return None # unmatched delimiter
599 599 result.append(line)
600 600 line = file.readline()
601 601 else:
602 602 return None
603 603
604 604 return ''.join(result)
605 605
606 606 def _disabledhelp(path):
607 607 '''retrieve help synopsis of a disabled extension (without importing)'''
608 608 try:
609 609 file = open(path)
610 610 except IOError:
611 611 return
612 612 else:
613 613 doc = _moduledoc(file)
614 614 file.close()
615 615
616 616 if doc: # extracting localized synopsis
617 617 return gettext(doc)
618 618 else:
619 619 return _('(no help text available)')
620 620
621 621 def disabled():
622 622 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
623 623 try:
624 624 from hgext import __index__
625 625 return dict((name, gettext(desc))
626 626 for name, desc in __index__.docs.iteritems()
627 627 if name not in _order)
628 628 except (ImportError, AttributeError):
629 629 pass
630 630
631 631 paths = _disabledpaths()
632 632 if not paths:
633 633 return {}
634 634
635 635 exts = {}
636 636 for name, path in paths.iteritems():
637 637 doc = _disabledhelp(path)
638 638 if doc:
639 639 exts[name] = doc.splitlines()[0]
640 640
641 641 return exts
642 642
643 643 def disabledext(name):
644 644 '''find a specific disabled extension from hgext. returns desc'''
645 645 try:
646 646 from hgext import __index__
647 647 if name in _order: # enabled
648 648 return
649 649 else:
650 650 return gettext(__index__.docs.get(name))
651 651 except (ImportError, AttributeError):
652 652 pass
653 653
654 654 paths = _disabledpaths()
655 655 if name in paths:
656 656 return _disabledhelp(paths[name])
657 657
658 658 def disabledcmd(ui, cmd, strict=False):
659 659 '''import disabled extensions until cmd is found.
660 660 returns (cmdname, extname, module)'''
661 661
662 662 paths = _disabledpaths(strip_init=True)
663 663 if not paths:
664 664 raise error.UnknownCommand(cmd)
665 665
666 666 def findcmd(cmd, name, path):
667 667 try:
668 668 mod = loadpath(path, 'hgext.%s' % name)
669 669 except Exception:
670 670 return
671 671 try:
672 672 aliases, entry = cmdutil.findcmd(cmd,
673 673 getattr(mod, 'cmdtable', {}), strict)
674 674 except (error.AmbiguousCommand, error.UnknownCommand):
675 675 return
676 676 except Exception:
677 677 ui.warn(_('warning: error finding commands in %s\n') % path)
678 678 ui.traceback()
679 679 return
680 680 for c in aliases:
681 681 if c.startswith(cmd):
682 682 cmd = c
683 683 break
684 684 else:
685 685 cmd = aliases[0]
686 686 return (cmd, name, mod)
687 687
688 688 ext = None
689 689 # first, search for an extension with the same name as the command
690 690 path = paths.pop(cmd, None)
691 691 if path:
692 692 ext = findcmd(cmd, cmd, path)
693 693 if not ext:
694 694 # otherwise, interrogate each extension until there's a match
695 695 for name, path in paths.iteritems():
696 696 ext = findcmd(cmd, name, path)
697 697 if ext:
698 698 break
699 if ext and 'DEPRECATED' not in ext.__doc__:
699 if ext:
700 700 return ext
701 701
702 702 raise error.UnknownCommand(cmd)
703 703
704 704 def enabled(shortname=True):
705 705 '''return a dict of {name: desc} of extensions'''
706 706 exts = {}
707 707 for ename, ext in extensions():
708 708 doc = (gettext(ext.__doc__) or _('(no help text available)'))
709 709 if shortname:
710 710 ename = ename.split('.')[-1]
711 711 exts[ename] = doc.splitlines()[0].strip()
712 712
713 713 return exts
714 714
715 715 def notloaded():
716 716 '''return short names of extensions that failed to load'''
717 717 return [name for name, mod in _extensions.iteritems() if mod is None]
718 718
719 719 def moduleversion(module):
720 720 '''return version information from given module as a string'''
721 721 if (util.safehasattr(module, 'getversion')
722 722 and callable(module.getversion)):
723 723 version = module.getversion()
724 724 elif util.safehasattr(module, '__version__'):
725 725 version = module.__version__
726 726 else:
727 727 version = ''
728 728 if isinstance(version, (list, tuple)):
729 729 version = '.'.join(str(o) for o in version)
730 730 return version
731 731
732 732 def ismoduleinternal(module):
733 733 exttestedwith = getattr(module, 'testedwith', None)
734 734 return exttestedwith == "ships-with-hg-core"
General Comments 0
You need to be logged in to leave comments. Login now