##// END OF EJS Templates
wrapcommand: use functools.partial...
Jun Wu -
r34090:a763c891 default
parent child Browse files
Show More
@@ -1,705 +1,706
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 235
236 236 broken = set()
237 237 for name in _order[newindex:]:
238 238 if not _runuisetup(name, ui):
239 239 broken.add(name)
240 240
241 241 for name in _order[newindex:]:
242 242 if name in broken:
243 243 continue
244 244 if not _runextsetup(name, ui):
245 245 broken.add(name)
246 246
247 247 for name in broken:
248 248 _extensions[name] = None
249 249
250 250 # Call aftercallbacks that were never met.
251 251 for shortname in _aftercallbacks:
252 252 if shortname in _extensions:
253 253 continue
254 254
255 255 for fn in _aftercallbacks[shortname]:
256 256 fn(loaded=False)
257 257
258 258 # loadall() is called multiple times and lingering _aftercallbacks
259 259 # entries could result in double execution. See issue4646.
260 260 _aftercallbacks.clear()
261 261
262 262 # delay importing avoids cyclic dependency (especially commands)
263 263 from . import (
264 264 color,
265 265 commands,
266 266 filemerge,
267 267 fileset,
268 268 revset,
269 269 templatefilters,
270 270 templatekw,
271 271 templater,
272 272 )
273 273
274 274 # list of (objname, loadermod, loadername) tuple:
275 275 # - objname is the name of an object in extension module,
276 276 # from which extra information is loaded
277 277 # - loadermod is the module where loader is placed
278 278 # - loadername is the name of the function,
279 279 # which takes (ui, extensionname, extraobj) arguments
280 280 extraloaders = [
281 281 ('cmdtable', commands, 'loadcmdtable'),
282 282 ('colortable', color, 'loadcolortable'),
283 283 ('configtable', configitems, 'loadconfigtable'),
284 284 ('filesetpredicate', fileset, 'loadpredicate'),
285 285 ('internalmerge', filemerge, 'loadinternalmerge'),
286 286 ('revsetpredicate', revset, 'loadpredicate'),
287 287 ('templatefilter', templatefilters, 'loadfilter'),
288 288 ('templatefunc', templater, 'loadfunction'),
289 289 ('templatekeyword', templatekw, 'loadkeyword'),
290 290 ]
291 291
292 292 for name in _order[newindex:]:
293 293 module = _extensions[name]
294 294 if not module:
295 295 continue # loading this module failed
296 296
297 297 for objname, loadermod, loadername in extraloaders:
298 298 extraobj = getattr(module, objname, None)
299 299 if extraobj is not None:
300 300 getattr(loadermod, loadername)(ui, name, extraobj)
301 301
302 302 def afterloaded(extension, callback):
303 303 '''Run the specified function after a named extension is loaded.
304 304
305 305 If the named extension is already loaded, the callback will be called
306 306 immediately.
307 307
308 308 If the named extension never loads, the callback will be called after
309 309 all extensions have been loaded.
310 310
311 311 The callback receives the named argument ``loaded``, which is a boolean
312 312 indicating whether the dependent extension actually loaded.
313 313 '''
314 314
315 315 if extension in _extensions:
316 316 # Report loaded as False if the extension is disabled
317 317 loaded = (_extensions[extension] is not None)
318 318 callback(loaded=loaded)
319 319 else:
320 320 _aftercallbacks.setdefault(extension, []).append(callback)
321 321
322 322 def bind(func, *args):
323 323 '''Partial function application
324 324
325 325 Returns a new function that is the partial application of args and kwargs
326 326 to func. For example,
327 327
328 328 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
329 329 assert callable(func)
330 330 def closure(*a, **kw):
331 331 return func(*(args + a), **kw)
332 332 return closure
333 333
334 334 def _updatewrapper(wrap, origfn, unboundwrapper):
335 335 '''Copy and add some useful attributes to wrapper'''
336 336 wrap.__name__ = origfn.__name__
337 337 wrap.__module__ = getattr(origfn, '__module__')
338 338 wrap.__doc__ = getattr(origfn, '__doc__')
339 339 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
340 340 wrap._origfunc = origfn
341 341 wrap._unboundwrapper = unboundwrapper
342 342
343 343 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
344 344 '''Wrap the command named `command' in table
345 345
346 346 Replace command in the command table with wrapper. The wrapped command will
347 347 be inserted into the command table specified by the table argument.
348 348
349 349 The wrapper will be called like
350 350
351 351 wrapper(orig, *args, **kwargs)
352 352
353 353 where orig is the original (wrapped) function, and *args, **kwargs
354 354 are the arguments passed to it.
355 355
356 356 Optionally append to the command synopsis and docstring, used for help.
357 357 For example, if your extension wraps the ``bookmarks`` command to add the
358 358 flags ``--remote`` and ``--all`` you might call this function like so:
359 359
360 360 synopsis = ' [-a] [--remote]'
361 361 docstring = """
362 362
363 363 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
364 364 flags to the bookmarks command. Either flag will show the remote bookmarks
365 365 known to the repository; ``--remote`` will also suppress the output of the
366 366 local bookmarks.
367 367 """
368 368
369 369 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
370 370 synopsis, docstring)
371 371 '''
372 372 assert callable(wrapper)
373 373 aliases, entry = cmdutil.findcmd(command, table)
374 374 for alias, e in table.iteritems():
375 375 if e is entry:
376 376 key = alias
377 377 break
378 378
379 379 origfn = entry[0]
380 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
380 wrap = functools.partial(util.checksignature(wrapper),
381 util.checksignature(origfn))
381 382 _updatewrapper(wrap, origfn, wrapper)
382 383 if docstring is not None:
383 384 wrap.__doc__ += docstring
384 385
385 386 newentry = list(entry)
386 387 newentry[0] = wrap
387 388 if synopsis is not None:
388 389 newentry[2] += synopsis
389 390 table[key] = tuple(newentry)
390 391 return entry
391 392
392 393 def wrapfilecache(cls, propname, wrapper):
393 394 """Wraps a filecache property.
394 395
395 396 These can't be wrapped using the normal wrapfunction.
396 397 """
397 398 propname = pycompat.sysstr(propname)
398 399 assert callable(wrapper)
399 400 for currcls in cls.__mro__:
400 401 if propname in currcls.__dict__:
401 402 origfn = currcls.__dict__[propname].func
402 403 assert callable(origfn)
403 404 def wrap(*args, **kwargs):
404 405 return wrapper(origfn, *args, **kwargs)
405 406 currcls.__dict__[propname].func = wrap
406 407 break
407 408
408 409 if currcls is object:
409 410 raise AttributeError(r"type '%s' has no property '%s'" % (
410 411 cls, propname))
411 412
412 413 class wrappedfunction(object):
413 414 '''context manager for temporarily wrapping a function'''
414 415
415 416 def __init__(self, container, funcname, wrapper):
416 417 assert callable(wrapper)
417 418 self._container = container
418 419 self._funcname = funcname
419 420 self._wrapper = wrapper
420 421
421 422 def __enter__(self):
422 423 wrapfunction(self._container, self._funcname, self._wrapper)
423 424
424 425 def __exit__(self, exctype, excvalue, traceback):
425 426 unwrapfunction(self._container, self._funcname, self._wrapper)
426 427
427 428 def wrapfunction(container, funcname, wrapper):
428 429 '''Wrap the function named funcname in container
429 430
430 431 Replace the funcname member in the given container with the specified
431 432 wrapper. The container is typically a module, class, or instance.
432 433
433 434 The wrapper will be called like
434 435
435 436 wrapper(orig, *args, **kwargs)
436 437
437 438 where orig is the original (wrapped) function, and *args, **kwargs
438 439 are the arguments passed to it.
439 440
440 441 Wrapping methods of the repository object is not recommended since
441 442 it conflicts with extensions that extend the repository by
442 443 subclassing. All extensions that need to extend methods of
443 444 localrepository should use this subclassing trick: namely,
444 445 reposetup() should look like
445 446
446 447 def reposetup(ui, repo):
447 448 class myrepo(repo.__class__):
448 449 def whatever(self, *args, **kwargs):
449 450 [...extension stuff...]
450 451 super(myrepo, self).whatever(*args, **kwargs)
451 452 [...extension stuff...]
452 453
453 454 repo.__class__ = myrepo
454 455
455 456 In general, combining wrapfunction() with subclassing does not
456 457 work. Since you cannot control what other extensions are loaded by
457 458 your end users, you should play nicely with others by using the
458 459 subclass trick.
459 460 '''
460 461 assert callable(wrapper)
461 462
462 463 origfn = getattr(container, funcname)
463 464 assert callable(origfn)
464 465 if inspect.ismodule(container):
465 466 # origfn is not an instance or class method. "partial" can be used.
466 467 # "partial" won't insert a frame in traceback.
467 468 wrap = functools.partial(wrapper, origfn)
468 469 else:
469 470 # "partial" cannot be safely used. Emulate its effect by using "bind".
470 471 # The downside is one more frame in traceback.
471 472 wrap = bind(wrapper, origfn)
472 473 _updatewrapper(wrap, origfn, wrapper)
473 474 setattr(container, funcname, wrap)
474 475 return origfn
475 476
476 477 def unwrapfunction(container, funcname, wrapper=None):
477 478 '''undo wrapfunction
478 479
479 480 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
480 481 from the chain of wrappers.
481 482
482 483 Return the removed wrapper.
483 484 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
484 485 wrapper is not None but is not found in the wrapper chain.
485 486 '''
486 487 chain = getwrapperchain(container, funcname)
487 488 origfn = chain.pop()
488 489 if wrapper is None:
489 490 wrapper = chain[0]
490 491 chain.remove(wrapper)
491 492 setattr(container, funcname, origfn)
492 493 for w in reversed(chain):
493 494 wrapfunction(container, funcname, w)
494 495 return wrapper
495 496
496 497 def getwrapperchain(container, funcname):
497 498 '''get a chain of wrappers of a function
498 499
499 500 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
500 501
501 502 The wrapper functions are the ones passed to wrapfunction, whose first
502 503 argument is origfunc.
503 504 '''
504 505 result = []
505 506 fn = getattr(container, funcname)
506 507 while fn:
507 508 assert callable(fn)
508 509 result.append(getattr(fn, '_unboundwrapper', fn))
509 510 fn = getattr(fn, '_origfunc', None)
510 511 return result
511 512
512 513 def _disabledpaths(strip_init=False):
513 514 '''find paths of disabled extensions. returns a dict of {name: path}
514 515 removes /__init__.py from packages if strip_init is True'''
515 516 import hgext
516 517 extpath = os.path.dirname(
517 518 os.path.abspath(pycompat.fsencode(hgext.__file__)))
518 519 try: # might not be a filesystem path
519 520 files = os.listdir(extpath)
520 521 except OSError:
521 522 return {}
522 523
523 524 exts = {}
524 525 for e in files:
525 526 if e.endswith('.py'):
526 527 name = e.rsplit('.', 1)[0]
527 528 path = os.path.join(extpath, e)
528 529 else:
529 530 name = e
530 531 path = os.path.join(extpath, e, '__init__.py')
531 532 if not os.path.exists(path):
532 533 continue
533 534 if strip_init:
534 535 path = os.path.dirname(path)
535 536 if name in exts or name in _order or name == '__init__':
536 537 continue
537 538 exts[name] = path
538 539 for name, path in _disabledextensions.iteritems():
539 540 # If no path was provided for a disabled extension (e.g. "color=!"),
540 541 # don't replace the path we already found by the scan above.
541 542 if path:
542 543 exts[name] = path
543 544 return exts
544 545
545 546 def _moduledoc(file):
546 547 '''return the top-level python documentation for the given file
547 548
548 549 Loosely inspired by pydoc.source_synopsis(), but rewritten to
549 550 handle triple quotes and to return the whole text instead of just
550 551 the synopsis'''
551 552 result = []
552 553
553 554 line = file.readline()
554 555 while line[:1] == '#' or not line.strip():
555 556 line = file.readline()
556 557 if not line:
557 558 break
558 559
559 560 start = line[:3]
560 561 if start == '"""' or start == "'''":
561 562 line = line[3:]
562 563 while line:
563 564 if line.rstrip().endswith(start):
564 565 line = line.split(start)[0]
565 566 if line:
566 567 result.append(line)
567 568 break
568 569 elif not line:
569 570 return None # unmatched delimiter
570 571 result.append(line)
571 572 line = file.readline()
572 573 else:
573 574 return None
574 575
575 576 return ''.join(result)
576 577
577 578 def _disabledhelp(path):
578 579 '''retrieve help synopsis of a disabled extension (without importing)'''
579 580 try:
580 581 file = open(path)
581 582 except IOError:
582 583 return
583 584 else:
584 585 doc = _moduledoc(file)
585 586 file.close()
586 587
587 588 if doc: # extracting localized synopsis
588 589 return gettext(doc)
589 590 else:
590 591 return _('(no help text available)')
591 592
592 593 def disabled():
593 594 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
594 595 try:
595 596 from hgext import __index__
596 597 return dict((name, gettext(desc))
597 598 for name, desc in __index__.docs.iteritems()
598 599 if name not in _order)
599 600 except (ImportError, AttributeError):
600 601 pass
601 602
602 603 paths = _disabledpaths()
603 604 if not paths:
604 605 return {}
605 606
606 607 exts = {}
607 608 for name, path in paths.iteritems():
608 609 doc = _disabledhelp(path)
609 610 if doc:
610 611 exts[name] = doc.splitlines()[0]
611 612
612 613 return exts
613 614
614 615 def disabledext(name):
615 616 '''find a specific disabled extension from hgext. returns desc'''
616 617 try:
617 618 from hgext import __index__
618 619 if name in _order: # enabled
619 620 return
620 621 else:
621 622 return gettext(__index__.docs.get(name))
622 623 except (ImportError, AttributeError):
623 624 pass
624 625
625 626 paths = _disabledpaths()
626 627 if name in paths:
627 628 return _disabledhelp(paths[name])
628 629
629 630 def disabledcmd(ui, cmd, strict=False):
630 631 '''import disabled extensions until cmd is found.
631 632 returns (cmdname, extname, module)'''
632 633
633 634 paths = _disabledpaths(strip_init=True)
634 635 if not paths:
635 636 raise error.UnknownCommand(cmd)
636 637
637 638 def findcmd(cmd, name, path):
638 639 try:
639 640 mod = loadpath(path, 'hgext.%s' % name)
640 641 except Exception:
641 642 return
642 643 try:
643 644 aliases, entry = cmdutil.findcmd(cmd,
644 645 getattr(mod, 'cmdtable', {}), strict)
645 646 except (error.AmbiguousCommand, error.UnknownCommand):
646 647 return
647 648 except Exception:
648 649 ui.warn(_('warning: error finding commands in %s\n') % path)
649 650 ui.traceback()
650 651 return
651 652 for c in aliases:
652 653 if c.startswith(cmd):
653 654 cmd = c
654 655 break
655 656 else:
656 657 cmd = aliases[0]
657 658 return (cmd, name, mod)
658 659
659 660 ext = None
660 661 # first, search for an extension with the same name as the command
661 662 path = paths.pop(cmd, None)
662 663 if path:
663 664 ext = findcmd(cmd, cmd, path)
664 665 if not ext:
665 666 # otherwise, interrogate each extension until there's a match
666 667 for name, path in paths.iteritems():
667 668 ext = findcmd(cmd, name, path)
668 669 if ext:
669 670 break
670 671 if ext and 'DEPRECATED' not in ext.__doc__:
671 672 return ext
672 673
673 674 raise error.UnknownCommand(cmd)
674 675
675 676 def enabled(shortname=True):
676 677 '''return a dict of {name: desc} of extensions'''
677 678 exts = {}
678 679 for ename, ext in extensions():
679 680 doc = (gettext(ext.__doc__) or _('(no help text available)'))
680 681 if shortname:
681 682 ename = ename.split('.')[-1]
682 683 exts[ename] = doc.splitlines()[0].strip()
683 684
684 685 return exts
685 686
686 687 def notloaded():
687 688 '''return short names of extensions that failed to load'''
688 689 return [name for name, mod in _extensions.iteritems() if mod is None]
689 690
690 691 def moduleversion(module):
691 692 '''return version information from given module as a string'''
692 693 if (util.safehasattr(module, 'getversion')
693 694 and callable(module.getversion)):
694 695 version = module.getversion()
695 696 elif util.safehasattr(module, '__version__'):
696 697 version = module.__version__
697 698 else:
698 699 version = ''
699 700 if isinstance(version, (list, tuple)):
700 701 version = '.'.join(str(o) for o in version)
701 702 return version
702 703
703 704 def ismoduleinternal(module):
704 705 exttestedwith = getattr(module, 'testedwith', None)
705 706 return exttestedwith == "ships-with-hg-core"
General Comments 0
You need to be logged in to leave comments. Login now