##// END OF EJS Templates
extensions: fix a debug message when searching for extensions...
Pierre-Yves David -
r30027:ebe488e0 default
parent child Browse files
Show More
@@ -1,538 +1,538 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 imp
11 11 import os
12 12
13 13 from .i18n import (
14 14 _,
15 15 gettext,
16 16 )
17 17
18 18 from . import (
19 19 cmdutil,
20 20 error,
21 21 util,
22 22 )
23 23
24 24 _extensions = {}
25 25 _disabledextensions = {}
26 26 _aftercallbacks = {}
27 27 _order = []
28 28 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
29 29 'inotify', 'hgcia'])
30 30
31 31 def extensions(ui=None):
32 32 if ui:
33 33 def enabled(name):
34 34 for format in ['%s', 'hgext.%s']:
35 35 conf = ui.config('extensions', format % name)
36 36 if conf is not None and not conf.startswith('!'):
37 37 return True
38 38 else:
39 39 enabled = lambda name: True
40 40 for name in _order:
41 41 module = _extensions[name]
42 42 if module and enabled(name):
43 43 yield name, module
44 44
45 45 def find(name):
46 46 '''return module with given extension name'''
47 47 mod = None
48 48 try:
49 49 mod = _extensions[name]
50 50 except KeyError:
51 51 for k, v in _extensions.iteritems():
52 52 if k.endswith('.' + name) or k.endswith('/' + name):
53 53 mod = v
54 54 break
55 55 if not mod:
56 56 raise KeyError(name)
57 57 return mod
58 58
59 59 def loadpath(path, module_name):
60 60 module_name = module_name.replace('.', '_')
61 61 path = util.normpath(util.expandpath(path))
62 62 if os.path.isdir(path):
63 63 # module/__init__.py style
64 64 d, f = os.path.split(path)
65 65 fd, fpath, desc = imp.find_module(f, [d])
66 66 return imp.load_module(module_name, fd, fpath, desc)
67 67 else:
68 68 try:
69 69 return imp.load_source(module_name, path)
70 70 except IOError as exc:
71 71 if not exc.filename:
72 72 exc.filename = path # python does not fill this
73 73 raise
74 74
75 75 def _importh(name):
76 76 """import and return the <name> module"""
77 77 mod = __import__(name)
78 78 components = name.split('.')
79 79 for comp in components[1:]:
80 80 mod = getattr(mod, comp)
81 81 return mod
82 82
83 83 def _reportimporterror(ui, err, failed, next):
84 84 ui.debug('could not import %s (%s): trying %s\n'
85 85 % (failed, err, next))
86 86 if ui.debugflag:
87 87 ui.traceback()
88 88
89 89 def load(ui, name, path):
90 90 if name.startswith('hgext.') or name.startswith('hgext/'):
91 91 shortname = name[6:]
92 92 else:
93 93 shortname = name
94 94 if shortname in _builtin:
95 95 return None
96 96 if shortname in _extensions:
97 97 return _extensions[shortname]
98 98 _extensions[shortname] = None
99 99 if path:
100 100 # the module will be loaded in sys.modules
101 101 # choose an unique name so that it doesn't
102 102 # conflicts with other modules
103 103 mod = loadpath(path, 'hgext.%s' % name)
104 104 else:
105 105 try:
106 106 mod = _importh("hgext.%s" % name)
107 107 except ImportError as err:
108 _reportimporterror(ui, err, "hgext.%s" % name, name)
108 _reportimporterror(ui, 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 _reportimporterror(ui, err, "hgext3rd.%s" % name, name)
113 113 mod = _importh(name)
114 114
115 115 # Before we do anything with the extension, check against minimum stated
116 116 # compatibility. This gives extension authors a mechanism to have their
117 117 # extensions short circuit when loaded with a known incompatible version
118 118 # of Mercurial.
119 119 minver = getattr(mod, 'minimumhgversion', None)
120 120 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
121 121 ui.warn(_('(third party extension %s requires version %s or newer '
122 122 'of Mercurial; disabling)\n') % (shortname, minver))
123 123 return
124 124
125 125 _extensions[shortname] = mod
126 126 _order.append(shortname)
127 127 for fn in _aftercallbacks.get(shortname, []):
128 128 fn(loaded=True)
129 129 return mod
130 130
131 131 def _runuisetup(name, ui):
132 132 uisetup = getattr(_extensions[name], 'uisetup', None)
133 133 if uisetup:
134 134 uisetup(ui)
135 135
136 136 def _runextsetup(name, ui):
137 137 extsetup = getattr(_extensions[name], 'extsetup', None)
138 138 if extsetup:
139 139 try:
140 140 extsetup(ui)
141 141 except TypeError:
142 142 if extsetup.func_code.co_argcount != 0:
143 143 raise
144 144 extsetup() # old extsetup with no ui argument
145 145
146 146 def loadall(ui):
147 147 result = ui.configitems("extensions")
148 148 newindex = len(_order)
149 149 for (name, path) in result:
150 150 if path:
151 151 if path[0] == '!':
152 152 _disabledextensions[name] = path[1:]
153 153 continue
154 154 try:
155 155 load(ui, name, path)
156 156 except KeyboardInterrupt:
157 157 raise
158 158 except Exception as inst:
159 159 if path:
160 160 ui.warn(_("*** failed to import extension %s from %s: %s\n")
161 161 % (name, path, inst))
162 162 else:
163 163 ui.warn(_("*** failed to import extension %s: %s\n")
164 164 % (name, inst))
165 165 ui.traceback()
166 166
167 167 for name in _order[newindex:]:
168 168 _runuisetup(name, ui)
169 169
170 170 for name in _order[newindex:]:
171 171 _runextsetup(name, ui)
172 172
173 173 # Call aftercallbacks that were never met.
174 174 for shortname in _aftercallbacks:
175 175 if shortname in _extensions:
176 176 continue
177 177
178 178 for fn in _aftercallbacks[shortname]:
179 179 fn(loaded=False)
180 180
181 181 # loadall() is called multiple times and lingering _aftercallbacks
182 182 # entries could result in double execution. See issue4646.
183 183 _aftercallbacks.clear()
184 184
185 185 def afterloaded(extension, callback):
186 186 '''Run the specified function after a named extension is loaded.
187 187
188 188 If the named extension is already loaded, the callback will be called
189 189 immediately.
190 190
191 191 If the named extension never loads, the callback will be called after
192 192 all extensions have been loaded.
193 193
194 194 The callback receives the named argument ``loaded``, which is a boolean
195 195 indicating whether the dependent extension actually loaded.
196 196 '''
197 197
198 198 if extension in _extensions:
199 199 callback(loaded=True)
200 200 else:
201 201 _aftercallbacks.setdefault(extension, []).append(callback)
202 202
203 203 def bind(func, *args):
204 204 '''Partial function application
205 205
206 206 Returns a new function that is the partial application of args and kwargs
207 207 to func. For example,
208 208
209 209 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
210 210 assert callable(func)
211 211 def closure(*a, **kw):
212 212 return func(*(args + a), **kw)
213 213 return closure
214 214
215 215 def _updatewrapper(wrap, origfn, unboundwrapper):
216 216 '''Copy and add some useful attributes to wrapper'''
217 217 wrap.__module__ = getattr(origfn, '__module__')
218 218 wrap.__doc__ = getattr(origfn, '__doc__')
219 219 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
220 220 wrap._origfunc = origfn
221 221 wrap._unboundwrapper = unboundwrapper
222 222
223 223 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
224 224 '''Wrap the command named `command' in table
225 225
226 226 Replace command in the command table with wrapper. The wrapped command will
227 227 be inserted into the command table specified by the table argument.
228 228
229 229 The wrapper will be called like
230 230
231 231 wrapper(orig, *args, **kwargs)
232 232
233 233 where orig is the original (wrapped) function, and *args, **kwargs
234 234 are the arguments passed to it.
235 235
236 236 Optionally append to the command synopsis and docstring, used for help.
237 237 For example, if your extension wraps the ``bookmarks`` command to add the
238 238 flags ``--remote`` and ``--all`` you might call this function like so:
239 239
240 240 synopsis = ' [-a] [--remote]'
241 241 docstring = """
242 242
243 243 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
244 244 flags to the bookmarks command. Either flag will show the remote bookmarks
245 245 known to the repository; ``--remote`` will also suppress the output of the
246 246 local bookmarks.
247 247 """
248 248
249 249 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
250 250 synopsis, docstring)
251 251 '''
252 252 assert callable(wrapper)
253 253 aliases, entry = cmdutil.findcmd(command, table)
254 254 for alias, e in table.iteritems():
255 255 if e is entry:
256 256 key = alias
257 257 break
258 258
259 259 origfn = entry[0]
260 260 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
261 261 _updatewrapper(wrap, origfn, wrapper)
262 262 if docstring is not None:
263 263 wrap.__doc__ += docstring
264 264
265 265 newentry = list(entry)
266 266 newentry[0] = wrap
267 267 if synopsis is not None:
268 268 newentry[2] += synopsis
269 269 table[key] = tuple(newentry)
270 270 return entry
271 271
272 272 def wrapfunction(container, funcname, wrapper):
273 273 '''Wrap the function named funcname in container
274 274
275 275 Replace the funcname member in the given container with the specified
276 276 wrapper. The container is typically a module, class, or instance.
277 277
278 278 The wrapper will be called like
279 279
280 280 wrapper(orig, *args, **kwargs)
281 281
282 282 where orig is the original (wrapped) function, and *args, **kwargs
283 283 are the arguments passed to it.
284 284
285 285 Wrapping methods of the repository object is not recommended since
286 286 it conflicts with extensions that extend the repository by
287 287 subclassing. All extensions that need to extend methods of
288 288 localrepository should use this subclassing trick: namely,
289 289 reposetup() should look like
290 290
291 291 def reposetup(ui, repo):
292 292 class myrepo(repo.__class__):
293 293 def whatever(self, *args, **kwargs):
294 294 [...extension stuff...]
295 295 super(myrepo, self).whatever(*args, **kwargs)
296 296 [...extension stuff...]
297 297
298 298 repo.__class__ = myrepo
299 299
300 300 In general, combining wrapfunction() with subclassing does not
301 301 work. Since you cannot control what other extensions are loaded by
302 302 your end users, you should play nicely with others by using the
303 303 subclass trick.
304 304 '''
305 305 assert callable(wrapper)
306 306
307 307 origfn = getattr(container, funcname)
308 308 assert callable(origfn)
309 309 wrap = bind(wrapper, origfn)
310 310 _updatewrapper(wrap, origfn, wrapper)
311 311 setattr(container, funcname, wrap)
312 312 return origfn
313 313
314 314 def unwrapfunction(container, funcname, wrapper=None):
315 315 '''undo wrapfunction
316 316
317 317 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
318 318 from the chain of wrappers.
319 319
320 320 Return the removed wrapper.
321 321 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
322 322 wrapper is not None but is not found in the wrapper chain.
323 323 '''
324 324 chain = getwrapperchain(container, funcname)
325 325 origfn = chain.pop()
326 326 if wrapper is None:
327 327 wrapper = chain[0]
328 328 chain.remove(wrapper)
329 329 setattr(container, funcname, origfn)
330 330 for w in reversed(chain):
331 331 wrapfunction(container, funcname, w)
332 332 return wrapper
333 333
334 334 def getwrapperchain(container, funcname):
335 335 '''get a chain of wrappers of a function
336 336
337 337 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
338 338
339 339 The wrapper functions are the ones passed to wrapfunction, whose first
340 340 argument is origfunc.
341 341 '''
342 342 result = []
343 343 fn = getattr(container, funcname)
344 344 while fn:
345 345 assert callable(fn)
346 346 result.append(getattr(fn, '_unboundwrapper', fn))
347 347 fn = getattr(fn, '_origfunc', None)
348 348 return result
349 349
350 350 def _disabledpaths(strip_init=False):
351 351 '''find paths of disabled extensions. returns a dict of {name: path}
352 352 removes /__init__.py from packages if strip_init is True'''
353 353 import hgext
354 354 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
355 355 try: # might not be a filesystem path
356 356 files = os.listdir(extpath)
357 357 except OSError:
358 358 return {}
359 359
360 360 exts = {}
361 361 for e in files:
362 362 if e.endswith('.py'):
363 363 name = e.rsplit('.', 1)[0]
364 364 path = os.path.join(extpath, e)
365 365 else:
366 366 name = e
367 367 path = os.path.join(extpath, e, '__init__.py')
368 368 if not os.path.exists(path):
369 369 continue
370 370 if strip_init:
371 371 path = os.path.dirname(path)
372 372 if name in exts or name in _order or name == '__init__':
373 373 continue
374 374 exts[name] = path
375 375 exts.update(_disabledextensions)
376 376 return exts
377 377
378 378 def _moduledoc(file):
379 379 '''return the top-level python documentation for the given file
380 380
381 381 Loosely inspired by pydoc.source_synopsis(), but rewritten to
382 382 handle triple quotes and to return the whole text instead of just
383 383 the synopsis'''
384 384 result = []
385 385
386 386 line = file.readline()
387 387 while line[:1] == '#' or not line.strip():
388 388 line = file.readline()
389 389 if not line:
390 390 break
391 391
392 392 start = line[:3]
393 393 if start == '"""' or start == "'''":
394 394 line = line[3:]
395 395 while line:
396 396 if line.rstrip().endswith(start):
397 397 line = line.split(start)[0]
398 398 if line:
399 399 result.append(line)
400 400 break
401 401 elif not line:
402 402 return None # unmatched delimiter
403 403 result.append(line)
404 404 line = file.readline()
405 405 else:
406 406 return None
407 407
408 408 return ''.join(result)
409 409
410 410 def _disabledhelp(path):
411 411 '''retrieve help synopsis of a disabled extension (without importing)'''
412 412 try:
413 413 file = open(path)
414 414 except IOError:
415 415 return
416 416 else:
417 417 doc = _moduledoc(file)
418 418 file.close()
419 419
420 420 if doc: # extracting localized synopsis
421 421 return gettext(doc).splitlines()[0]
422 422 else:
423 423 return _('(no help text available)')
424 424
425 425 def disabled():
426 426 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
427 427 try:
428 428 from hgext import __index__
429 429 return dict((name, gettext(desc))
430 430 for name, desc in __index__.docs.iteritems()
431 431 if name not in _order)
432 432 except (ImportError, AttributeError):
433 433 pass
434 434
435 435 paths = _disabledpaths()
436 436 if not paths:
437 437 return {}
438 438
439 439 exts = {}
440 440 for name, path in paths.iteritems():
441 441 doc = _disabledhelp(path)
442 442 if doc:
443 443 exts[name] = doc
444 444
445 445 return exts
446 446
447 447 def disabledext(name):
448 448 '''find a specific disabled extension from hgext. returns desc'''
449 449 try:
450 450 from hgext import __index__
451 451 if name in _order: # enabled
452 452 return
453 453 else:
454 454 return gettext(__index__.docs.get(name))
455 455 except (ImportError, AttributeError):
456 456 pass
457 457
458 458 paths = _disabledpaths()
459 459 if name in paths:
460 460 return _disabledhelp(paths[name])
461 461
462 462 def disabledcmd(ui, cmd, strict=False):
463 463 '''import disabled extensions until cmd is found.
464 464 returns (cmdname, extname, module)'''
465 465
466 466 paths = _disabledpaths(strip_init=True)
467 467 if not paths:
468 468 raise error.UnknownCommand(cmd)
469 469
470 470 def findcmd(cmd, name, path):
471 471 try:
472 472 mod = loadpath(path, 'hgext.%s' % name)
473 473 except Exception:
474 474 return
475 475 try:
476 476 aliases, entry = cmdutil.findcmd(cmd,
477 477 getattr(mod, 'cmdtable', {}), strict)
478 478 except (error.AmbiguousCommand, error.UnknownCommand):
479 479 return
480 480 except Exception:
481 481 ui.warn(_('warning: error finding commands in %s\n') % path)
482 482 ui.traceback()
483 483 return
484 484 for c in aliases:
485 485 if c.startswith(cmd):
486 486 cmd = c
487 487 break
488 488 else:
489 489 cmd = aliases[0]
490 490 return (cmd, name, mod)
491 491
492 492 ext = None
493 493 # first, search for an extension with the same name as the command
494 494 path = paths.pop(cmd, None)
495 495 if path:
496 496 ext = findcmd(cmd, cmd, path)
497 497 if not ext:
498 498 # otherwise, interrogate each extension until there's a match
499 499 for name, path in paths.iteritems():
500 500 ext = findcmd(cmd, name, path)
501 501 if ext:
502 502 break
503 503 if ext and 'DEPRECATED' not in ext.__doc__:
504 504 return ext
505 505
506 506 raise error.UnknownCommand(cmd)
507 507
508 508 def enabled(shortname=True):
509 509 '''return a dict of {name: desc} of extensions'''
510 510 exts = {}
511 511 for ename, ext in extensions():
512 512 doc = (gettext(ext.__doc__) or _('(no help text available)'))
513 513 if shortname:
514 514 ename = ename.split('.')[-1]
515 515 exts[ename] = doc.splitlines()[0].strip()
516 516
517 517 return exts
518 518
519 519 def notloaded():
520 520 '''return short names of extensions that failed to load'''
521 521 return [name for name, mod in _extensions.iteritems() if mod is None]
522 522
523 523 def moduleversion(module):
524 524 '''return version information from given module as a string'''
525 525 if (util.safehasattr(module, 'getversion')
526 526 and callable(module.getversion)):
527 527 version = module.getversion()
528 528 elif util.safehasattr(module, '__version__'):
529 529 version = module.__version__
530 530 else:
531 531 version = ''
532 532 if isinstance(version, (list, tuple)):
533 533 version = '.'.join(str(o) for o in version)
534 534 return version
535 535
536 536 def ismoduleinternal(module):
537 537 exttestedwith = getattr(module, 'testedwith', None)
538 538 return exttestedwith == "ships-with-hg-core"
@@ -1,73 +1,73 b''
1 1 $ echo 'raise Exception("bit bucket overflow")' > badext.py
2 2 $ abspathexc=`pwd`/badext.py
3 3
4 4 $ cat >baddocext.py <<EOF
5 5 > """
6 6 > baddocext is bad
7 7 > """
8 8 > EOF
9 9 $ abspathdoc=`pwd`/baddocext.py
10 10
11 11 $ cat <<EOF >> $HGRCPATH
12 12 > [extensions]
13 13 > gpg =
14 14 > hgext.gpg =
15 15 > badext = $abspathexc
16 16 > baddocext = $abspathdoc
17 17 > badext2 =
18 18 > EOF
19 19
20 20 $ hg -q help help 2>&1 |grep extension
21 21 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
22 22 *** failed to import extension badext2: No module named badext2
23 23
24 24 show traceback
25 25
26 26 $ hg -q help help --traceback 2>&1 | egrep ' extension|^Exception|Traceback|ImportError'
27 27 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
28 28 Traceback (most recent call last):
29 29 Exception: bit bucket overflow
30 30 *** failed to import extension badext2: No module named badext2
31 31 Traceback (most recent call last):
32 32 ImportError: No module named badext2
33 33
34 34 names of extensions failed to load can be accessed via extensions.notloaded()
35 35
36 36 $ cat <<EOF > showbadexts.py
37 37 > from mercurial import cmdutil, commands, extensions
38 38 > cmdtable = {}
39 39 > command = cmdutil.command(cmdtable)
40 40 > @command('showbadexts', norepo=True)
41 41 > def showbadexts(ui, *pats, **opts):
42 42 > ui.write('BADEXTS: %s\n' % ' '.join(sorted(extensions.notloaded())))
43 43 > EOF
44 44 $ hg --config extensions.badexts=showbadexts.py showbadexts 2>&1 | grep '^BADEXTS'
45 45 BADEXTS: badext badext2
46 46
47 47 show traceback for ImportError of hgext.name if debug is set
48 48 (note that --debug option isn't applied yet when loading extensions)
49 49
50 50 $ (hg -q help help --traceback --config ui.debug=True 2>&1) \
51 51 > | grep -v '^ ' \
52 52 > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|not import'
53 53 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
54 54 Traceback (most recent call last):
55 55 Exception: bit bucket overflow
56 could not import hgext.badext2 (No module named *badext2): trying badext2 (glob)
56 could not import hgext.badext2 (No module named *badext2): trying hgext3rd.badext2 (glob)
57 57 Traceback (most recent call last):
58 58 ImportError: No module named *badext2 (glob)
59 59 could not import hgext3rd.badext2 (No module named *badext2): trying badext2 (glob)
60 60 Traceback (most recent call last):
61 61 ImportError: No module named *badext2 (glob)
62 62 *** failed to import extension badext2: No module named badext2
63 63 Traceback (most recent call last):
64 64 ImportError: No module named badext2
65 65
66 66 confirm that there's no crash when an extension's documentation is bad
67 67
68 68 $ hg help --keyword baddocext
69 69 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
70 70 *** failed to import extension badext2: No module named badext2
71 71 Topics:
72 72
73 73 extensions Using Additional Features
General Comments 0
You need to be logged in to leave comments. Login now