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