comparison mercurial/extensions.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 6a3872e34503
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
25 error, 25 error,
26 pycompat, 26 pycompat,
27 util, 27 util,
28 ) 28 )
29 29
30 from .utils import ( 30 from .utils import stringutil
31 stringutil,
32 )
33 31
34 _extensions = {} 32 _extensions = {}
35 _disabledextensions = {} 33 _disabledextensions = {}
36 _aftercallbacks = {} 34 _aftercallbacks = {}
37 _order = [] 35 _order = []
45 'inotify', 43 'inotify',
46 'hgcia', 44 'hgcia',
47 'shelve', 45 'shelve',
48 } 46 }
49 47
48
50 def extensions(ui=None): 49 def extensions(ui=None):
51 if ui: 50 if ui:
51
52 def enabled(name): 52 def enabled(name):
53 for format in ['%s', 'hgext.%s']: 53 for format in ['%s', 'hgext.%s']:
54 conf = ui.config('extensions', format % name) 54 conf = ui.config('extensions', format % name)
55 if conf is not None and not conf.startswith('!'): 55 if conf is not None and not conf.startswith('!'):
56 return True 56 return True
57
57 else: 58 else:
58 enabled = lambda name: True 59 enabled = lambda name: True
59 for name in _order: 60 for name in _order:
60 module = _extensions[name] 61 module = _extensions[name]
61 if module and enabled(name): 62 if module and enabled(name):
62 yield name, module 63 yield name, module
64
63 65
64 def find(name): 66 def find(name):
65 '''return module with given extension name''' 67 '''return module with given extension name'''
66 mod = None 68 mod = None
67 try: 69 try:
72 mod = v 74 mod = v
73 break 75 break
74 if not mod: 76 if not mod:
75 raise KeyError(name) 77 raise KeyError(name)
76 return mod 78 return mod
79
77 80
78 def loadpath(path, module_name): 81 def loadpath(path, module_name):
79 module_name = module_name.replace('.', '_') 82 module_name = module_name.replace('.', '_')
80 path = util.normpath(util.expandpath(path)) 83 path = util.normpath(util.expandpath(path))
81 module_name = pycompat.fsdecode(module_name) 84 module_name = pycompat.fsdecode(module_name)
88 else: 91 else:
89 try: 92 try:
90 return imp.load_source(module_name, path) 93 return imp.load_source(module_name, path)
91 except IOError as exc: 94 except IOError as exc:
92 if not exc.filename: 95 if not exc.filename:
93 exc.filename = path # python does not fill this 96 exc.filename = path # python does not fill this
94 raise 97 raise
98
95 99
96 def _importh(name): 100 def _importh(name):
97 """import and return the <name> module""" 101 """import and return the <name> module"""
98 mod = __import__(pycompat.sysstr(name)) 102 mod = __import__(pycompat.sysstr(name))
99 components = name.split('.') 103 components = name.split('.')
100 for comp in components[1:]: 104 for comp in components[1:]:
101 mod = getattr(mod, comp) 105 mod = getattr(mod, comp)
102 return mod 106 return mod
107
103 108
104 def _importext(name, path=None, reportfunc=None): 109 def _importext(name, path=None, reportfunc=None):
105 if path: 110 if path:
106 # the module will be loaded in sys.modules 111 # the module will be loaded in sys.modules
107 # choose an unique name so that it doesn't 112 # choose an unique name so that it doesn't
119 if reportfunc: 124 if reportfunc:
120 reportfunc(err, "hgext3rd.%s" % name, name) 125 reportfunc(err, "hgext3rd.%s" % name, name)
121 mod = _importh(name) 126 mod = _importh(name)
122 return mod 127 return mod
123 128
129
124 def _reportimporterror(ui, err, failed, next): 130 def _reportimporterror(ui, err, failed, next):
125 # note: this ui.log happens before --debug is processed, 131 # note: this ui.log happens before --debug is processed,
126 # Use --config ui.debug=1 to see them. 132 # Use --config ui.debug=1 to see them.
127 ui.log(b'extension', b' - could not import %s (%s): trying %s\n', 133 ui.log(
128 failed, stringutil.forcebytestr(err), next) 134 b'extension',
135 b' - could not import %s (%s): trying %s\n',
136 failed,
137 stringutil.forcebytestr(err),
138 next,
139 )
129 if ui.debugflag and ui.configbool('devel', 'debug.extensions'): 140 if ui.debugflag and ui.configbool('devel', 'debug.extensions'):
130 ui.traceback() 141 ui.traceback()
142
131 143
132 def _rejectunicode(name, xs): 144 def _rejectunicode(name, xs):
133 if isinstance(xs, (list, set, tuple)): 145 if isinstance(xs, (list, set, tuple)):
134 for x in xs: 146 for x in xs:
135 _rejectunicode(name, x) 147 _rejectunicode(name, x)
136 elif isinstance(xs, dict): 148 elif isinstance(xs, dict):
137 for k, v in xs.items(): 149 for k, v in xs.items():
138 _rejectunicode(name, k) 150 _rejectunicode(name, k)
139 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v) 151 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
140 elif isinstance(xs, type(u'')): 152 elif isinstance(xs, type(u'')):
141 raise error.ProgrammingError(b"unicode %r found in %s" % (xs, name), 153 raise error.ProgrammingError(
142 hint="use b'' to make it byte string") 154 b"unicode %r found in %s" % (xs, name),
155 hint="use b'' to make it byte string",
156 )
157
143 158
144 # attributes set by registrar.command 159 # attributes set by registrar.command
145 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo') 160 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
161
146 162
147 def _validatecmdtable(ui, cmdtable): 163 def _validatecmdtable(ui, cmdtable):
148 """Check if extension commands have required attributes""" 164 """Check if extension commands have required attributes"""
149 for c, e in cmdtable.iteritems(): 165 for c, e in cmdtable.iteritems():
150 f = e[0] 166 f = e[0]
151 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)] 167 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
152 if not missing: 168 if not missing:
153 continue 169 continue
154 raise error.ProgrammingError( 170 raise error.ProgrammingError(
155 'missing attributes: %s' % ', '.join(missing), 171 'missing attributes: %s' % ', '.join(missing),
156 hint="use @command decorator to register '%s'" % c) 172 hint="use @command decorator to register '%s'" % c,
173 )
174
157 175
158 def _validatetables(ui, mod): 176 def _validatetables(ui, mod):
159 """Sanity check for loadable tables provided by extension module""" 177 """Sanity check for loadable tables provided by extension module"""
160 for t in ['cmdtable', 'colortable', 'configtable']: 178 for t in ['cmdtable', 'colortable', 'configtable']:
161 _rejectunicode(t, getattr(mod, t, {})) 179 _rejectunicode(t, getattr(mod, t, {}))
162 for t in ['filesetpredicate', 'internalmerge', 'revsetpredicate', 180 for t in [
163 'templatefilter', 'templatefunc', 'templatekeyword']: 181 'filesetpredicate',
182 'internalmerge',
183 'revsetpredicate',
184 'templatefilter',
185 'templatefunc',
186 'templatekeyword',
187 ]:
164 o = getattr(mod, t, None) 188 o = getattr(mod, t, None)
165 if o: 189 if o:
166 _rejectunicode(t, o._table) 190 _rejectunicode(t, o._table)
167 _validatecmdtable(ui, getattr(mod, 'cmdtable', {})) 191 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
192
168 193
169 def load(ui, name, path, loadingtime=None): 194 def load(ui, name, path, loadingtime=None):
170 if name.startswith('hgext.') or name.startswith('hgext/'): 195 if name.startswith('hgext.') or name.startswith('hgext/'):
171 shortname = name[6:] 196 shortname = name[6:]
172 else: 197 else:
187 # compatibility. This gives extension authors a mechanism to have their 212 # compatibility. This gives extension authors a mechanism to have their
188 # extensions short circuit when loaded with a known incompatible version 213 # extensions short circuit when loaded with a known incompatible version
189 # of Mercurial. 214 # of Mercurial.
190 minver = getattr(mod, 'minimumhgversion', None) 215 minver = getattr(mod, 'minimumhgversion', None)
191 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2): 216 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
192 msg = _('(third party extension %s requires version %s or newer ' 217 msg = _(
193 'of Mercurial (current: %s); disabling)\n') 218 '(third party extension %s requires version %s or newer '
219 'of Mercurial (current: %s); disabling)\n'
220 )
194 ui.warn(msg % (shortname, minver, util.version())) 221 ui.warn(msg % (shortname, minver, util.version()))
195 return 222 return
196 ui.log(b'extension', b' - validating extension tables: %s\n', shortname) 223 ui.log(b'extension', b' - validating extension tables: %s\n', shortname)
197 _validatetables(ui, mod) 224 _validatetables(ui, mod)
198 225
199 _extensions[shortname] = mod 226 _extensions[shortname] = mod
200 _order.append(shortname) 227 _order.append(shortname)
201 ui.log(b'extension', b' - invoking registered callbacks: %s\n', 228 ui.log(
202 shortname) 229 b'extension', b' - invoking registered callbacks: %s\n', shortname
230 )
203 with util.timedcm('callbacks extension %s', shortname) as stats: 231 with util.timedcm('callbacks extension %s', shortname) as stats:
204 for fn in _aftercallbacks.get(shortname, []): 232 for fn in _aftercallbacks.get(shortname, []):
205 fn(loaded=True) 233 fn(loaded=True)
206 ui.log(b'extension', b' > callbacks completed in %s\n', stats) 234 ui.log(b'extension', b' > callbacks completed in %s\n', stats)
207 return mod 235 return mod
236
208 237
209 def _runuisetup(name, ui): 238 def _runuisetup(name, ui):
210 uisetup = getattr(_extensions[name], 'uisetup', None) 239 uisetup = getattr(_extensions[name], 'uisetup', None)
211 if uisetup: 240 if uisetup:
212 try: 241 try:
216 msg = stringutil.forcebytestr(inst) 245 msg = stringutil.forcebytestr(inst)
217 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg)) 246 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
218 return False 247 return False
219 return True 248 return True
220 249
250
221 def _runextsetup(name, ui): 251 def _runextsetup(name, ui):
222 extsetup = getattr(_extensions[name], 'extsetup', None) 252 extsetup = getattr(_extensions[name], 'extsetup', None)
223 if extsetup: 253 if extsetup:
224 try: 254 try:
225 extsetup(ui) 255 extsetup(ui)
228 msg = stringutil.forcebytestr(inst) 258 msg = stringutil.forcebytestr(inst)
229 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg)) 259 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
230 return False 260 return False
231 return True 261 return True
232 262
263
233 def loadall(ui, whitelist=None): 264 def loadall(ui, whitelist=None):
234 loadingtime = collections.defaultdict(int) 265 loadingtime = collections.defaultdict(int)
235 result = ui.configitems("extensions") 266 result = ui.configitems("extensions")
236 if whitelist is not None: 267 if whitelist is not None:
237 result = [(k, v) for (k, v) in result if k in whitelist] 268 result = [(k, v) for (k, v) in result if k in whitelist]
238 newindex = len(_order) 269 newindex = len(_order)
239 ui.log(b'extension', b'loading %sextensions\n', 270 ui.log(
240 'additional ' if newindex else '') 271 b'extension',
272 b'loading %sextensions\n',
273 'additional ' if newindex else '',
274 )
241 ui.log(b'extension', b'- processing %d entries\n', len(result)) 275 ui.log(b'extension', b'- processing %d entries\n', len(result))
242 with util.timedcm('load all extensions') as stats: 276 with util.timedcm('load all extensions') as stats:
243 for (name, path) in result: 277 for (name, path) in result:
244 if path: 278 if path:
245 if path[0:1] == '!': 279 if path[0:1] == '!':
246 if name not in _disabledextensions: 280 if name not in _disabledextensions:
247 ui.log(b'extension', 281 ui.log(
248 b' - skipping disabled extension: %s\n', name) 282 b'extension',
283 b' - skipping disabled extension: %s\n',
284 name,
285 )
249 _disabledextensions[name] = path[1:] 286 _disabledextensions[name] = path[1:]
250 continue 287 continue
251 try: 288 try:
252 load(ui, name, path, loadingtime) 289 load(ui, name, path, loadingtime)
253 except Exception as inst: 290 except Exception as inst:
254 msg = stringutil.forcebytestr(inst) 291 msg = stringutil.forcebytestr(inst)
255 if path: 292 if path:
256 ui.warn(_("*** failed to import extension %s from %s: %s\n") 293 ui.warn(
257 % (name, path, msg)) 294 _("*** failed to import extension %s from %s: %s\n")
295 % (name, path, msg)
296 )
258 else: 297 else:
259 ui.warn(_("*** failed to import extension %s: %s\n") 298 ui.warn(
260 % (name, msg)) 299 _("*** failed to import extension %s: %s\n")
300 % (name, msg)
301 )
261 if isinstance(inst, error.Hint) and inst.hint: 302 if isinstance(inst, error.Hint) and inst.hint:
262 ui.warn(_("*** (%s)\n") % inst.hint) 303 ui.warn(_("*** (%s)\n") % inst.hint)
263 ui.traceback() 304 ui.traceback()
264 305
265 ui.log(b'extension', b'> loaded %d extensions, total time %s\n', 306 ui.log(
266 len(_order) - newindex, stats) 307 b'extension',
308 b'> loaded %d extensions, total time %s\n',
309 len(_order) - newindex,
310 stats,
311 )
267 # list of (objname, loadermod, loadername) tuple: 312 # list of (objname, loadermod, loadername) tuple:
268 # - objname is the name of an object in extension module, 313 # - objname is the name of an object in extension module,
269 # from which extra information is loaded 314 # from which extra information is loaded
270 # - loadermod is the module where loader is placed 315 # - loadermod is the module where loader is placed
271 # - loadername is the name of the function, 316 # - loadername is the name of the function,
284 with util.timedcm('all uisetup') as alluisetupstats: 329 with util.timedcm('all uisetup') as alluisetupstats:
285 for name in _order[newindex:]: 330 for name in _order[newindex:]:
286 ui.log(b'extension', b' - running uisetup for %s\n', name) 331 ui.log(b'extension', b' - running uisetup for %s\n', name)
287 with util.timedcm('uisetup %s', name) as stats: 332 with util.timedcm('uisetup %s', name) as stats:
288 if not _runuisetup(name, ui): 333 if not _runuisetup(name, ui):
289 ui.log(b'extension', 334 ui.log(
290 b' - the %s extension uisetup failed\n', name) 335 b'extension',
336 b' - the %s extension uisetup failed\n',
337 name,
338 )
291 broken.add(name) 339 broken.add(name)
292 ui.log(b'extension', b' > uisetup for %s took %s\n', name, stats) 340 ui.log(b'extension', b' > uisetup for %s took %s\n', name, stats)
293 loadingtime[name] += stats.elapsed 341 loadingtime[name] += stats.elapsed
294 ui.log(b'extension', b'> all uisetup took %s\n', alluisetupstats) 342 ui.log(b'extension', b'> all uisetup took %s\n', alluisetupstats)
295 343
299 if name in broken: 347 if name in broken:
300 continue 348 continue
301 ui.log(b'extension', b' - running extsetup for %s\n', name) 349 ui.log(b'extension', b' - running extsetup for %s\n', name)
302 with util.timedcm('extsetup %s', name) as stats: 350 with util.timedcm('extsetup %s', name) as stats:
303 if not _runextsetup(name, ui): 351 if not _runextsetup(name, ui):
304 ui.log(b'extension', 352 ui.log(
305 b' - the %s extension extsetup failed\n', name) 353 b'extension',
354 b' - the %s extension extsetup failed\n',
355 name,
356 )
306 broken.add(name) 357 broken.add(name)
307 ui.log(b'extension', b' > extsetup for %s took %s\n', name, stats) 358 ui.log(b'extension', b' > extsetup for %s took %s\n', name, stats)
308 loadingtime[name] += stats.elapsed 359 loadingtime[name] += stats.elapsed
309 ui.log(b'extension', b'> all extsetup took %s\n', allextetupstats) 360 ui.log(b'extension', b'> all extsetup took %s\n', allextetupstats)
310 361
318 for shortname in _aftercallbacks: 369 for shortname in _aftercallbacks:
319 if shortname in _extensions: 370 if shortname in _extensions:
320 continue 371 continue
321 372
322 for fn in _aftercallbacks[shortname]: 373 for fn in _aftercallbacks[shortname]:
323 ui.log(b'extension', 374 ui.log(
324 b' - extension %s not loaded, notify callbacks\n', 375 b'extension',
325 shortname) 376 b' - extension %s not loaded, notify callbacks\n',
377 shortname,
378 )
326 fn(loaded=False) 379 fn(loaded=False)
327 ui.log(b'extension', b'> remaining aftercallbacks completed in %s\n', stats) 380 ui.log(b'extension', b'> remaining aftercallbacks completed in %s\n', stats)
328 381
329 # loadall() is called multiple times and lingering _aftercallbacks 382 # loadall() is called multiple times and lingering _aftercallbacks
330 # entries could result in double execution. See issue4646. 383 # entries could result in double execution. See issue4646.
359 ('templatefunc', templatefuncs, 'loadfunction'), 412 ('templatefunc', templatefuncs, 'loadfunction'),
360 ('templatekeyword', templatekw, 'loadkeyword'), 413 ('templatekeyword', templatekw, 'loadkeyword'),
361 ] 414 ]
362 with util.timedcm('load registration objects') as stats: 415 with util.timedcm('load registration objects') as stats:
363 _loadextra(ui, newindex, extraloaders) 416 _loadextra(ui, newindex, extraloaders)
364 ui.log(b'extension', b'> extension registration object loading took %s\n', 417 ui.log(
365 stats) 418 b'extension',
419 b'> extension registration object loading took %s\n',
420 stats,
421 )
366 422
367 # Report per extension loading time (except reposetup) 423 # Report per extension loading time (except reposetup)
368 for name in sorted(loadingtime): 424 for name in sorted(loadingtime):
369 ui.log(b'extension', b'> extension %s take a total of %s to load\n', 425 ui.log(
370 name, util.timecount(loadingtime[name])) 426 b'extension',
427 b'> extension %s take a total of %s to load\n',
428 name,
429 util.timecount(loadingtime[name]),
430 )
371 431
372 ui.log(b'extension', b'extension loading complete\n') 432 ui.log(b'extension', b'extension loading complete\n')
433
373 434
374 def _loadextra(ui, newindex, extraloaders): 435 def _loadextra(ui, newindex, extraloaders):
375 for name in _order[newindex:]: 436 for name in _order[newindex:]:
376 module = _extensions[name] 437 module = _extensions[name]
377 if not module: 438 if not module:
378 continue # loading this module failed 439 continue # loading this module failed
379 440
380 for objname, loadermod, loadername in extraloaders: 441 for objname, loadermod, loadername in extraloaders:
381 extraobj = getattr(module, objname, None) 442 extraobj = getattr(module, objname, None)
382 if extraobj is not None: 443 if extraobj is not None:
383 getattr(loadermod, loadername)(ui, name, extraobj) 444 getattr(loadermod, loadername)(ui, name, extraobj)
384 445
446
385 def afterloaded(extension, callback): 447 def afterloaded(extension, callback):
386 '''Run the specified function after a named extension is loaded. 448 '''Run the specified function after a named extension is loaded.
387 449
388 If the named extension is already loaded, the callback will be called 450 If the named extension is already loaded, the callback will be called
389 immediately. 451 immediately.
395 indicating whether the dependent extension actually loaded. 457 indicating whether the dependent extension actually loaded.
396 ''' 458 '''
397 459
398 if extension in _extensions: 460 if extension in _extensions:
399 # Report loaded as False if the extension is disabled 461 # Report loaded as False if the extension is disabled
400 loaded = (_extensions[extension] is not None) 462 loaded = _extensions[extension] is not None
401 callback(loaded=loaded) 463 callback(loaded=loaded)
402 else: 464 else:
403 _aftercallbacks.setdefault(extension, []).append(callback) 465 _aftercallbacks.setdefault(extension, []).append(callback)
466
404 467
405 def populateui(ui): 468 def populateui(ui):
406 """Run extension hooks on the given ui to populate additional members, 469 """Run extension hooks on the given ui to populate additional members,
407 extend the class dynamically, etc. 470 extend the class dynamically, etc.
408 471
416 continue 479 continue
417 try: 480 try:
418 hook(ui) 481 hook(ui)
419 except Exception as inst: 482 except Exception as inst:
420 ui.traceback(force=True) 483 ui.traceback(force=True)
421 ui.warn(_('*** failed to populate ui by extension %s: %s\n') 484 ui.warn(
422 % (name, stringutil.forcebytestr(inst))) 485 _('*** failed to populate ui by extension %s: %s\n')
486 % (name, stringutil.forcebytestr(inst))
487 )
488
423 489
424 def bind(func, *args): 490 def bind(func, *args):
425 '''Partial function application 491 '''Partial function application
426 492
427 Returns a new function that is the partial application of args and kwargs 493 Returns a new function that is the partial application of args and kwargs
428 to func. For example, 494 to func. For example,
429 495
430 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)''' 496 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
431 assert callable(func) 497 assert callable(func)
498
432 def closure(*a, **kw): 499 def closure(*a, **kw):
433 return func(*(args + a), **kw) 500 return func(*(args + a), **kw)
501
434 return closure 502 return closure
503
435 504
436 def _updatewrapper(wrap, origfn, unboundwrapper): 505 def _updatewrapper(wrap, origfn, unboundwrapper):
437 '''Copy and add some useful attributes to wrapper''' 506 '''Copy and add some useful attributes to wrapper'''
438 try: 507 try:
439 wrap.__name__ = origfn.__name__ 508 wrap.__name__ = origfn.__name__
443 wrap.__doc__ = getattr(origfn, '__doc__') 512 wrap.__doc__ = getattr(origfn, '__doc__')
444 wrap.__dict__.update(getattr(origfn, '__dict__', {})) 513 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
445 wrap._origfunc = origfn 514 wrap._origfunc = origfn
446 wrap._unboundwrapper = unboundwrapper 515 wrap._unboundwrapper = unboundwrapper
447 516
517
448 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None): 518 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
449 '''Wrap the command named `command' in table 519 '''Wrap the command named `command' in table
450 520
451 Replace command in the command table with wrapper. The wrapped command will 521 Replace command in the command table with wrapper. The wrapped command will
452 be inserted into the command table specified by the table argument. 522 be inserted into the command table specified by the table argument.
480 if e is entry: 550 if e is entry:
481 key = alias 551 key = alias
482 break 552 break
483 553
484 origfn = entry[0] 554 origfn = entry[0]
485 wrap = functools.partial(util.checksignature(wrapper), 555 wrap = functools.partial(
486 util.checksignature(origfn)) 556 util.checksignature(wrapper), util.checksignature(origfn)
557 )
487 _updatewrapper(wrap, origfn, wrapper) 558 _updatewrapper(wrap, origfn, wrapper)
488 if docstring is not None: 559 if docstring is not None:
489 wrap.__doc__ += docstring 560 wrap.__doc__ += docstring
490 561
491 newentry = list(entry) 562 newentry = list(entry)
492 newentry[0] = wrap 563 newentry[0] = wrap
493 if synopsis is not None: 564 if synopsis is not None:
494 newentry[2] += synopsis 565 newentry[2] += synopsis
495 table[key] = tuple(newentry) 566 table[key] = tuple(newentry)
496 return entry 567 return entry
568
497 569
498 def wrapfilecache(cls, propname, wrapper): 570 def wrapfilecache(cls, propname, wrapper):
499 """Wraps a filecache property. 571 """Wraps a filecache property.
500 572
501 These can't be wrapped using the normal wrapfunction. 573 These can't be wrapped using the normal wrapfunction.
504 assert callable(wrapper) 576 assert callable(wrapper)
505 for currcls in cls.__mro__: 577 for currcls in cls.__mro__:
506 if propname in currcls.__dict__: 578 if propname in currcls.__dict__:
507 origfn = currcls.__dict__[propname].func 579 origfn = currcls.__dict__[propname].func
508 assert callable(origfn) 580 assert callable(origfn)
581
509 def wrap(*args, **kwargs): 582 def wrap(*args, **kwargs):
510 return wrapper(origfn, *args, **kwargs) 583 return wrapper(origfn, *args, **kwargs)
584
511 currcls.__dict__[propname].func = wrap 585 currcls.__dict__[propname].func = wrap
512 break 586 break
513 587
514 if currcls is object: 588 if currcls is object:
515 raise AttributeError(r"type '%s' has no property '%s'" % ( 589 raise AttributeError(
516 cls, propname)) 590 r"type '%s' has no property '%s'" % (cls, propname)
591 )
592
517 593
518 class wrappedfunction(object): 594 class wrappedfunction(object):
519 '''context manager for temporarily wrapping a function''' 595 '''context manager for temporarily wrapping a function'''
520 596
521 def __init__(self, container, funcname, wrapper): 597 def __init__(self, container, funcname, wrapper):
527 def __enter__(self): 603 def __enter__(self):
528 wrapfunction(self._container, self._funcname, self._wrapper) 604 wrapfunction(self._container, self._funcname, self._wrapper)
529 605
530 def __exit__(self, exctype, excvalue, traceback): 606 def __exit__(self, exctype, excvalue, traceback):
531 unwrapfunction(self._container, self._funcname, self._wrapper) 607 unwrapfunction(self._container, self._funcname, self._wrapper)
608
532 609
533 def wrapfunction(container, funcname, wrapper): 610 def wrapfunction(container, funcname, wrapper):
534 '''Wrap the function named funcname in container 611 '''Wrap the function named funcname in container
535 612
536 Replace the funcname member in the given container with the specified 613 Replace the funcname member in the given container with the specified
577 wrap = bind(wrapper, origfn) 654 wrap = bind(wrapper, origfn)
578 _updatewrapper(wrap, origfn, wrapper) 655 _updatewrapper(wrap, origfn, wrapper)
579 setattr(container, funcname, wrap) 656 setattr(container, funcname, wrap)
580 return origfn 657 return origfn
581 658
659
582 def unwrapfunction(container, funcname, wrapper=None): 660 def unwrapfunction(container, funcname, wrapper=None):
583 '''undo wrapfunction 661 '''undo wrapfunction
584 662
585 If wrappers is None, undo the last wrap. Otherwise removes the wrapper 663 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
586 from the chain of wrappers. 664 from the chain of wrappers.
597 setattr(container, funcname, origfn) 675 setattr(container, funcname, origfn)
598 for w in reversed(chain): 676 for w in reversed(chain):
599 wrapfunction(container, funcname, w) 677 wrapfunction(container, funcname, w)
600 return wrapper 678 return wrapper
601 679
680
602 def getwrapperchain(container, funcname): 681 def getwrapperchain(container, funcname):
603 '''get a chain of wrappers of a function 682 '''get a chain of wrappers of a function
604 683
605 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc] 684 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
606 685
613 assert callable(fn) 692 assert callable(fn)
614 result.append(getattr(fn, '_unboundwrapper', fn)) 693 result.append(getattr(fn, '_unboundwrapper', fn))
615 fn = getattr(fn, '_origfunc', None) 694 fn = getattr(fn, '_origfunc', None)
616 return result 695 return result
617 696
697
618 def _disabledpaths(): 698 def _disabledpaths():
619 '''find paths of disabled extensions. returns a dict of {name: path}''' 699 '''find paths of disabled extensions. returns a dict of {name: path}'''
620 import hgext 700 import hgext
701
621 extpath = os.path.dirname( 702 extpath = os.path.dirname(
622 os.path.abspath(pycompat.fsencode(hgext.__file__))) 703 os.path.abspath(pycompat.fsencode(hgext.__file__))
623 try: # might not be a filesystem path 704 )
705 try: # might not be a filesystem path
624 files = os.listdir(extpath) 706 files = os.listdir(extpath)
625 except OSError: 707 except OSError:
626 return {} 708 return {}
627 709
628 exts = {} 710 exts = {}
643 # don't replace the path we already found by the scan above. 725 # don't replace the path we already found by the scan above.
644 if path: 726 if path:
645 exts[name] = path 727 exts[name] = path
646 return exts 728 return exts
647 729
730
648 def _moduledoc(file): 731 def _moduledoc(file):
649 '''return the top-level python documentation for the given file 732 '''return the top-level python documentation for the given file
650 733
651 Loosely inspired by pydoc.source_synopsis(), but rewritten to 734 Loosely inspired by pydoc.source_synopsis(), but rewritten to
652 handle triple quotes and to return the whole text instead of just 735 handle triple quotes and to return the whole text instead of just
667 line = line.split(start)[0] 750 line = line.split(start)[0]
668 if line: 751 if line:
669 result.append(line) 752 result.append(line)
670 break 753 break
671 elif not line: 754 elif not line:
672 return None # unmatched delimiter 755 return None # unmatched delimiter
673 result.append(line) 756 result.append(line)
674 line = file.readline() 757 line = file.readline()
675 else: 758 else:
676 return None 759 return None
677 760
678 return ''.join(result) 761 return ''.join(result)
762
679 763
680 def _disabledhelp(path): 764 def _disabledhelp(path):
681 '''retrieve help synopsis of a disabled extension (without importing)''' 765 '''retrieve help synopsis of a disabled extension (without importing)'''
682 try: 766 try:
683 with open(path, 'rb') as src: 767 with open(path, 'rb') as src:
684 doc = _moduledoc(src) 768 doc = _moduledoc(src)
685 except IOError: 769 except IOError:
686 return 770 return
687 771
688 if doc: # extracting localized synopsis 772 if doc: # extracting localized synopsis
689 return gettext(doc) 773 return gettext(doc)
690 else: 774 else:
691 return _('(no help text available)') 775 return _('(no help text available)')
776
692 777
693 def disabled(): 778 def disabled():
694 '''find disabled extensions from hgext. returns a dict of {name: desc}''' 779 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
695 try: 780 try:
696 from hgext import __index__ 781 from hgext import __index__
697 return dict((name, gettext(desc)) 782
698 for name, desc in __index__.docs.iteritems() 783 return dict(
699 if name not in _order) 784 (name, gettext(desc))
785 for name, desc in __index__.docs.iteritems()
786 if name not in _order
787 )
700 except (ImportError, AttributeError): 788 except (ImportError, AttributeError):
701 pass 789 pass
702 790
703 paths = _disabledpaths() 791 paths = _disabledpaths()
704 if not paths: 792 if not paths:
710 if doc: 798 if doc:
711 exts[name] = doc.splitlines()[0] 799 exts[name] = doc.splitlines()[0]
712 800
713 return exts 801 return exts
714 802
803
715 def disabledext(name): 804 def disabledext(name):
716 '''find a specific disabled extension from hgext. returns desc''' 805 '''find a specific disabled extension from hgext. returns desc'''
717 try: 806 try:
718 from hgext import __index__ 807 from hgext import __index__
808
719 if name in _order: # enabled 809 if name in _order: # enabled
720 return 810 return
721 else: 811 else:
722 return gettext(__index__.docs.get(name)) 812 return gettext(__index__.docs.get(name))
723 except (ImportError, AttributeError): 813 except (ImportError, AttributeError):
724 pass 814 pass
725 815
726 paths = _disabledpaths() 816 paths = _disabledpaths()
727 if name in paths: 817 if name in paths:
728 return _disabledhelp(paths[name]) 818 return _disabledhelp(paths[name])
819
729 820
730 def _walkcommand(node): 821 def _walkcommand(node):
731 """Scan @command() decorators in the tree starting at node""" 822 """Scan @command() decorators in the tree starting at node"""
732 todo = collections.deque([node]) 823 todo = collections.deque([node])
733 while todo: 824 while todo:
741 if not isinstance(d.func, ast.Name): 832 if not isinstance(d.func, ast.Name):
742 continue 833 continue
743 if d.func.id != r'command': 834 if d.func.id != r'command':
744 continue 835 continue
745 yield d 836 yield d
837
746 838
747 def _disabledcmdtable(path): 839 def _disabledcmdtable(path):
748 """Construct a dummy command table without loading the extension module 840 """Construct a dummy command table without loading the extension module
749 841
750 This may raise IOError or SyntaxError. 842 This may raise IOError or SyntaxError.
763 else: 855 else:
764 continue 856 continue
765 cmdtable[name] = (None, [], b'') 857 cmdtable[name] = (None, [], b'')
766 return cmdtable 858 return cmdtable
767 859
860
768 def _finddisabledcmd(ui, cmd, name, path, strict): 861 def _finddisabledcmd(ui, cmd, name, path, strict):
769 try: 862 try:
770 cmdtable = _disabledcmdtable(path) 863 cmdtable = _disabledcmdtable(path)
771 except (IOError, SyntaxError): 864 except (IOError, SyntaxError):
772 return 865 return
781 else: 874 else:
782 cmd = aliases[0] 875 cmd = aliases[0]
783 doc = _disabledhelp(path) 876 doc = _disabledhelp(path)
784 return (cmd, name, doc) 877 return (cmd, name, doc)
785 878
879
786 def disabledcmd(ui, cmd, strict=False): 880 def disabledcmd(ui, cmd, strict=False):
787 '''find cmd from disabled extensions without importing. 881 '''find cmd from disabled extensions without importing.
788 returns (cmdname, extname, doc)''' 882 returns (cmdname, extname, doc)'''
789 883
790 paths = _disabledpaths() 884 paths = _disabledpaths()
805 if ext: 899 if ext:
806 return ext 900 return ext
807 901
808 raise error.UnknownCommand(cmd) 902 raise error.UnknownCommand(cmd)
809 903
904
810 def enabled(shortname=True): 905 def enabled(shortname=True):
811 '''return a dict of {name: desc} of extensions''' 906 '''return a dict of {name: desc} of extensions'''
812 exts = {} 907 exts = {}
813 for ename, ext in extensions(): 908 for ename, ext in extensions():
814 doc = (gettext(ext.__doc__) or _('(no help text available)')) 909 doc = gettext(ext.__doc__) or _('(no help text available)')
815 if shortname: 910 if shortname:
816 ename = ename.split('.')[-1] 911 ename = ename.split('.')[-1]
817 exts[ename] = doc.splitlines()[0].strip() 912 exts[ename] = doc.splitlines()[0].strip()
818 913
819 return exts 914 return exts
820 915
916
821 def notloaded(): 917 def notloaded():
822 '''return short names of extensions that failed to load''' 918 '''return short names of extensions that failed to load'''
823 return [name for name, mod in _extensions.iteritems() if mod is None] 919 return [name for name, mod in _extensions.iteritems() if mod is None]
824 920
921
825 def moduleversion(module): 922 def moduleversion(module):
826 '''return version information from given module as a string''' 923 '''return version information from given module as a string'''
827 if (util.safehasattr(module, 'getversion') 924 if util.safehasattr(module, 'getversion') and callable(module.getversion):
828 and callable(module.getversion)):
829 version = module.getversion() 925 version = module.getversion()
830 elif util.safehasattr(module, '__version__'): 926 elif util.safehasattr(module, '__version__'):
831 version = module.__version__ 927 version = module.__version__
832 else: 928 else:
833 version = '' 929 version = ''
834 if isinstance(version, (list, tuple)): 930 if isinstance(version, (list, tuple)):
835 version = '.'.join(pycompat.bytestr(o) for o in version) 931 version = '.'.join(pycompat.bytestr(o) for o in version)
836 return version 932 return version
837 933
934
838 def ismoduleinternal(module): 935 def ismoduleinternal(module):
839 exttestedwith = getattr(module, 'testedwith', None) 936 exttestedwith = getattr(module, 'testedwith', None)
840 return exttestedwith == "ships-with-hg-core" 937 return exttestedwith == "ships-with-hg-core"