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