Mercurial > hg
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" |