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