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