extensions.py
301 lines
| 9.0 KiB
| text/x-python
|
PythonLexer
/ mercurial / extensions.py
Matt Mackall
|
r4544 | # extensions.py - extension handling for mercurial | ||
# | ||||
Thomas Arendsen Hein
|
r4635 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||
Matt Mackall
|
r4544 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Matt Mackall
|
r4544 | |||
Cédric Duval
|
r8896 | import imp, os | ||
Brodie Rao
|
r10364 | import util, cmdutil, help, error | ||
Cédric Duval
|
r8871 | from i18n import _, gettext | ||
Matt Mackall
|
r4544 | |||
_extensions = {} | ||||
Alexis S. L. Carvalho
|
r5192 | _order = [] | ||
def extensions(): | ||||
for name in _order: | ||||
module = _extensions[name] | ||||
if module: | ||||
yield name, module | ||||
Matt Mackall
|
r4544 | |||
def find(name): | ||||
'''return module with given extension name''' | ||||
try: | ||||
return _extensions[name] | ||||
except KeyError: | ||||
for k, v in _extensions.iteritems(): | ||||
Matt Mackall
|
r4560 | if k.endswith('.' + name) or k.endswith('/' + name): | ||
return v | ||||
Matt Mackall
|
r4544 | raise KeyError(name) | ||
Alexander Solovyov
|
r7916 | def loadpath(path, module_name): | ||
module_name = module_name.replace('.', '_') | ||||
Alexander Solovyov
|
r9610 | path = util.expandpath(path) | ||
Alexander Solovyov
|
r7916 | if os.path.isdir(path): | ||
# module/__init__.py style | ||||
Alexander Solovyov
|
r7960 | d, f = os.path.split(path.rstrip('/')) | ||
Alexander Solovyov
|
r7916 | fd, fpath, desc = imp.find_module(f, [d]) | ||
return imp.load_module(module_name, fd, fpath, desc) | ||||
else: | ||||
return imp.load_source(module_name, path) | ||||
Matt Mackall
|
r4544 | def load(ui, name, path): | ||
Martin Geisler
|
r9410 | # unused ui argument kept for backwards compatibility | ||
Benoit Boissinot
|
r7011 | if name.startswith('hgext.') or name.startswith('hgext/'): | ||
Bryan O'Sullivan
|
r5031 | shortname = name[6:] | ||
else: | ||||
shortname = name | ||||
if shortname in _extensions: | ||||
Matt Mackall
|
r4544 | return | ||
Brendan Cully
|
r5087 | _extensions[shortname] = None | ||
Matt Mackall
|
r4544 | if path: | ||
# the module will be loaded in sys.modules | ||||
# choose an unique name so that it doesn't | ||||
# conflicts with other modules | ||||
Alexander Solovyov
|
r7916 | mod = loadpath(path, 'hgext.%s' % name) | ||
Matt Mackall
|
r4544 | else: | ||
def importh(name): | ||||
mod = __import__(name) | ||||
components = name.split('.') | ||||
for comp in components[1:]: | ||||
mod = getattr(mod, comp) | ||||
return mod | ||||
try: | ||||
mod = importh("hgext.%s" % name) | ||||
except ImportError: | ||||
mod = importh(name) | ||||
Bryan O'Sullivan
|
r5031 | _extensions[shortname] = mod | ||
Alexis S. L. Carvalho
|
r5192 | _order.append(shortname) | ||
Matt Mackall
|
r4544 | |||
def loadall(ui): | ||||
Matt Mackall
|
r4617 | result = ui.configitems("extensions") | ||
Martin Geisler
|
r9410 | newindex = len(_order) | ||
Peter Arrenbrecht
|
r7876 | for (name, path) in result: | ||
Matt Mackall
|
r4617 | if path: | ||
Steve Borho
|
r5469 | if path[0] == '!': | ||
continue | ||||
Matt Mackall
|
r4544 | try: | ||
load(ui, name, path) | ||||
Matt Mackall
|
r7644 | except KeyboardInterrupt: | ||
Matt Mackall
|
r4544 | raise | ||
except Exception, inst: | ||||
Jesse Glick
|
r6204 | if path: | ||
ui.warn(_("*** failed to import extension %s from %s: %s\n") | ||||
% (name, path, inst)) | ||||
else: | ||||
ui.warn(_("*** failed to import extension %s: %s\n") | ||||
% (name, inst)) | ||||
Matt Mackall
|
r8206 | if ui.traceback(): | ||
Matt Mackall
|
r4544 | return 1 | ||
Martin Geisler
|
r9410 | for name in _order[newindex:]: | ||
uisetup = getattr(_extensions[name], 'uisetup', None) | ||||
if uisetup: | ||||
uisetup(ui) | ||||
Yuya Nishihara
|
r9660 | for name in _order[newindex:]: | ||
extsetup = getattr(_extensions[name], 'extsetup', None) | ||||
if extsetup: | ||||
try: | ||||
extsetup(ui) | ||||
except TypeError: | ||||
if extsetup.func_code.co_argcount != 0: | ||||
raise | ||||
extsetup() # old extsetup with no ui argument | ||||
Matt Mackall
|
r7215 | def wrapcommand(table, command, wrapper): | ||
Dan Villiom Podlaski Christiansen
|
r11519 | '''Wrap the command named `command' in table | ||
Replace command in the command table with wrapper. The wrapped command will | ||||
be inserted into the command table specified by the table argument. | ||||
The wrapper will be called like | ||||
wrapper(orig, *args, **kwargs) | ||||
where orig is the original (wrapped) function, and *args, **kwargs | ||||
are the arguments passed to it. | ||||
''' | ||||
Dan Villiom Podlaski Christiansen
|
r11521 | assert hasattr(wrapper, '__call__') | ||
Matt Mackall
|
r7215 | aliases, entry = cmdutil.findcmd(command, table) | ||
for alias, e in table.iteritems(): | ||||
if e is entry: | ||||
key = alias | ||||
break | ||||
origfn = entry[0] | ||||
def wrap(*args, **kwargs): | ||||
Matt Mackall
|
r7388 | return util.checksignature(wrapper)( | ||
util.checksignature(origfn), *args, **kwargs) | ||||
Matt Mackall
|
r7215 | |||
wrap.__doc__ = getattr(origfn, '__doc__') | ||||
Dirkjan Ochtman
|
r7373 | wrap.__module__ = getattr(origfn, '__module__') | ||
Matt Mackall
|
r7215 | |||
newentry = list(entry) | ||||
newentry[0] = wrap | ||||
table[key] = tuple(newentry) | ||||
return entry | ||||
def wrapfunction(container, funcname, wrapper): | ||||
Greg Ward
|
r11402 | '''Wrap the function named funcname in container | ||
Dan Villiom Podlaski Christiansen
|
r11520 | Replace the funcname member in the given container with the specified | ||
wrapper. The container is typically a module, class, or instance. | ||||
Greg Ward
|
r11402 | |||
The wrapper will be called like | ||||
wrapper(orig, *args, **kwargs) | ||||
where orig is the original (wrapped) function, and *args, **kwargs | ||||
are the arguments passed to it. | ||||
Wrapping methods of the repository object is not recommended since | ||||
it conflicts with extensions that extend the repository by | ||||
subclassing. All extensions that need to extend methods of | ||||
localrepository should use this subclassing trick: namely, | ||||
reposetup() should look like | ||||
def reposetup(ui, repo): | ||||
class myrepo(repo.__class__): | ||||
def whatever(self, *args, **kwargs): | ||||
[...extension stuff...] | ||||
super(myrepo, self).whatever(*args, **kwargs) | ||||
[...extension stuff...] | ||||
repo.__class__ = myrepo | ||||
In general, combining wrapfunction() with subclassing does not | ||||
work. Since you cannot control what other extensions are loaded by | ||||
your end users, you should play nicely with others by using the | ||||
subclass trick. | ||||
''' | ||||
Dan Villiom Podlaski Christiansen
|
r11521 | assert hasattr(wrapper, '__call__') | ||
Matt Mackall
|
r7215 | def wrap(*args, **kwargs): | ||
return wrapper(origfn, *args, **kwargs) | ||||
origfn = getattr(container, funcname) | ||||
Dan Villiom Podlaski Christiansen
|
r11521 | assert hasattr(origfn, '__call__') | ||
Matt Mackall
|
r7215 | setattr(container, funcname, wrap) | ||
return origfn | ||||
Cédric Duval
|
r8871 | |||
Brodie Rao
|
r10364 | def _disabledpaths(strip_init=False): | ||
'''find paths of disabled extensions. returns a dict of {name: path} | ||||
removes /__init__.py from packages if strip_init is True''' | ||||
Dirkjan Ochtman
|
r8872 | import hgext | ||
extpath = os.path.dirname(os.path.abspath(hgext.__file__)) | ||||
Cédric Duval
|
r8964 | try: # might not be a filesystem path | ||
files = os.listdir(extpath) | ||||
except OSError: | ||||
Brodie Rao
|
r10363 | return {} | ||
Cédric Duval
|
r8964 | |||
Cédric Duval
|
r8871 | exts = {} | ||
Cédric Duval
|
r8964 | for e in files: | ||
Dirkjan Ochtman
|
r8872 | if e.endswith('.py'): | ||
name = e.rsplit('.', 1)[0] | ||||
path = os.path.join(extpath, e) | ||||
else: | ||||
name = e | ||||
path = os.path.join(extpath, e, '__init__.py') | ||||
Cédric Duval
|
r8877 | if not os.path.exists(path): | ||
continue | ||||
Brodie Rao
|
r10364 | if strip_init: | ||
path = os.path.dirname(path) | ||||
Brodie Rao
|
r10363 | if name in exts or name in _order or name == '__init__': | ||
continue | ||||
exts[name] = path | ||||
return exts | ||||
Dirkjan Ochtman
|
r8872 | |||
Brodie Rao
|
r10363 | def _disabledhelp(path): | ||
'''retrieve help synopsis of a disabled extension (without importing)''' | ||||
try: | ||||
file = open(path) | ||||
except IOError: | ||||
return | ||||
else: | ||||
doc = help.moduledoc(file) | ||||
file.close() | ||||
if doc: # extracting localized synopsis | ||||
return gettext(doc).splitlines()[0] | ||||
else: | ||||
return _('(no help text available)') | ||||
def disabled(): | ||||
'''find disabled extensions from hgext | ||||
returns a dict of {name: desc}, and the max name length''' | ||||
paths = _disabledpaths() | ||||
if not paths: | ||||
return None, 0 | ||||
exts = {} | ||||
maxlength = 0 | ||||
for name, path in paths.iteritems(): | ||||
doc = _disabledhelp(path) | ||||
if not doc: | ||||
Dirkjan Ochtman
|
r8872 | continue | ||
Cédric Duval
|
r8871 | |||
Brodie Rao
|
r10363 | exts[name] = doc | ||
Dirkjan Ochtman
|
r8872 | if len(name) > maxlength: | ||
maxlength = len(name) | ||||
Cédric Duval
|
r8871 | |||
return exts, maxlength | ||||
Brodie Rao
|
r10364 | def disabledext(name): | ||
'''find a specific disabled extension from hgext. returns desc''' | ||||
paths = _disabledpaths() | ||||
if name in paths: | ||||
return _disabledhelp(paths[name]) | ||||
def disabledcmd(cmd, strict=False): | ||||
'''import disabled extensions until cmd is found. | ||||
returns (cmdname, extname, doc)''' | ||||
paths = _disabledpaths(strip_init=True) | ||||
if not paths: | ||||
raise error.UnknownCommand(cmd) | ||||
def findcmd(cmd, name, path): | ||||
try: | ||||
mod = loadpath(path, 'hgext.%s' % name) | ||||
except Exception: | ||||
return | ||||
try: | ||||
aliases, entry = cmdutil.findcmd(cmd, | ||||
getattr(mod, 'cmdtable', {}), strict) | ||||
except (error.AmbiguousCommand, error.UnknownCommand): | ||||
return | ||||
for c in aliases: | ||||
if c.startswith(cmd): | ||||
cmd = c | ||||
break | ||||
else: | ||||
cmd = aliases[0] | ||||
return (cmd, name, mod) | ||||
# first, search for an extension with the same name as the command | ||||
path = paths.pop(cmd, None) | ||||
if path: | ||||
ext = findcmd(cmd, cmd, path) | ||||
if ext: | ||||
return ext | ||||
# otherwise, interrogate each extension until there's a match | ||||
for name, path in paths.iteritems(): | ||||
ext = findcmd(cmd, name, path) | ||||
if ext: | ||||
return ext | ||||
raise error.UnknownCommand(cmd) | ||||
Cédric Duval
|
r8871 | def enabled(): | ||
'''return a dict of {name: desc} of extensions, and the max name length''' | ||||
exts = {} | ||||
maxlength = 0 | ||||
for ename, ext in extensions(): | ||||
doc = (gettext(ext.__doc__) or _('(no help text available)')) | ||||
ename = ename.split('.')[-1] | ||||
maxlength = max(len(ename), maxlength) | ||||
Nicolas Dumazet
|
r9136 | exts[ename] = doc.splitlines()[0].strip() | ||
Cédric Duval
|
r8871 | |||
return exts, maxlength | ||||