##// END OF EJS Templates
extensions: drop dead code trying to exclude deprecated disabled commands...
Yuya Nishihara -
r37994:5b60f7d6 default
parent child Browse files
Show More
@@ -1,734 +1,734 b''
1 # extensions.py - extension handling for mercurial
1 # extensions.py - extension handling for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import functools
10 import functools
11 import imp
11 import imp
12 import inspect
12 import inspect
13 import os
13 import os
14
14
15 from .i18n import (
15 from .i18n import (
16 _,
16 _,
17 gettext,
17 gettext,
18 )
18 )
19
19
20 from . import (
20 from . import (
21 cmdutil,
21 cmdutil,
22 configitems,
22 configitems,
23 error,
23 error,
24 pycompat,
24 pycompat,
25 util,
25 util,
26 )
26 )
27
27
28 from .utils import (
28 from .utils import (
29 stringutil,
29 stringutil,
30 )
30 )
31
31
32 _extensions = {}
32 _extensions = {}
33 _disabledextensions = {}
33 _disabledextensions = {}
34 _aftercallbacks = {}
34 _aftercallbacks = {}
35 _order = []
35 _order = []
36 _builtin = {
36 _builtin = {
37 'hbisect',
37 'hbisect',
38 'bookmarks',
38 'bookmarks',
39 'color',
39 'color',
40 'parentrevspec',
40 'parentrevspec',
41 'progress',
41 'progress',
42 'interhg',
42 'interhg',
43 'inotify',
43 'inotify',
44 'hgcia'
44 'hgcia'
45 }
45 }
46
46
47 def extensions(ui=None):
47 def extensions(ui=None):
48 if ui:
48 if ui:
49 def enabled(name):
49 def enabled(name):
50 for format in ['%s', 'hgext.%s']:
50 for format in ['%s', 'hgext.%s']:
51 conf = ui.config('extensions', format % name)
51 conf = ui.config('extensions', format % name)
52 if conf is not None and not conf.startswith('!'):
52 if conf is not None and not conf.startswith('!'):
53 return True
53 return True
54 else:
54 else:
55 enabled = lambda name: True
55 enabled = lambda name: True
56 for name in _order:
56 for name in _order:
57 module = _extensions[name]
57 module = _extensions[name]
58 if module and enabled(name):
58 if module and enabled(name):
59 yield name, module
59 yield name, module
60
60
61 def find(name):
61 def find(name):
62 '''return module with given extension name'''
62 '''return module with given extension name'''
63 mod = None
63 mod = None
64 try:
64 try:
65 mod = _extensions[name]
65 mod = _extensions[name]
66 except KeyError:
66 except KeyError:
67 for k, v in _extensions.iteritems():
67 for k, v in _extensions.iteritems():
68 if k.endswith('.' + name) or k.endswith('/' + name):
68 if k.endswith('.' + name) or k.endswith('/' + name):
69 mod = v
69 mod = v
70 break
70 break
71 if not mod:
71 if not mod:
72 raise KeyError(name)
72 raise KeyError(name)
73 return mod
73 return mod
74
74
75 def loadpath(path, module_name):
75 def loadpath(path, module_name):
76 module_name = module_name.replace('.', '_')
76 module_name = module_name.replace('.', '_')
77 path = util.normpath(util.expandpath(path))
77 path = util.normpath(util.expandpath(path))
78 module_name = pycompat.fsdecode(module_name)
78 module_name = pycompat.fsdecode(module_name)
79 path = pycompat.fsdecode(path)
79 path = pycompat.fsdecode(path)
80 if os.path.isdir(path):
80 if os.path.isdir(path):
81 # module/__init__.py style
81 # module/__init__.py style
82 d, f = os.path.split(path)
82 d, f = os.path.split(path)
83 fd, fpath, desc = imp.find_module(f, [d])
83 fd, fpath, desc = imp.find_module(f, [d])
84 return imp.load_module(module_name, fd, fpath, desc)
84 return imp.load_module(module_name, fd, fpath, desc)
85 else:
85 else:
86 try:
86 try:
87 return imp.load_source(module_name, path)
87 return imp.load_source(module_name, path)
88 except IOError as exc:
88 except IOError as exc:
89 if not exc.filename:
89 if not exc.filename:
90 exc.filename = path # python does not fill this
90 exc.filename = path # python does not fill this
91 raise
91 raise
92
92
93 def _importh(name):
93 def _importh(name):
94 """import and return the <name> module"""
94 """import and return the <name> module"""
95 mod = __import__(pycompat.sysstr(name))
95 mod = __import__(pycompat.sysstr(name))
96 components = name.split('.')
96 components = name.split('.')
97 for comp in components[1:]:
97 for comp in components[1:]:
98 mod = getattr(mod, comp)
98 mod = getattr(mod, comp)
99 return mod
99 return mod
100
100
101 def _importext(name, path=None, reportfunc=None):
101 def _importext(name, path=None, reportfunc=None):
102 if path:
102 if path:
103 # the module will be loaded in sys.modules
103 # the module will be loaded in sys.modules
104 # choose an unique name so that it doesn't
104 # choose an unique name so that it doesn't
105 # conflicts with other modules
105 # conflicts with other modules
106 mod = loadpath(path, 'hgext.%s' % name)
106 mod = loadpath(path, 'hgext.%s' % name)
107 else:
107 else:
108 try:
108 try:
109 mod = _importh("hgext.%s" % name)
109 mod = _importh("hgext.%s" % name)
110 except ImportError as err:
110 except ImportError as err:
111 if reportfunc:
111 if reportfunc:
112 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
112 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
113 try:
113 try:
114 mod = _importh("hgext3rd.%s" % name)
114 mod = _importh("hgext3rd.%s" % name)
115 except ImportError as err:
115 except ImportError as err:
116 if reportfunc:
116 if reportfunc:
117 reportfunc(err, "hgext3rd.%s" % name, name)
117 reportfunc(err, "hgext3rd.%s" % name, name)
118 mod = _importh(name)
118 mod = _importh(name)
119 return mod
119 return mod
120
120
121 def _reportimporterror(ui, err, failed, next):
121 def _reportimporterror(ui, err, failed, next):
122 # note: this ui.debug happens before --debug is processed,
122 # note: this ui.debug happens before --debug is processed,
123 # Use --config ui.debug=1 to see them.
123 # Use --config ui.debug=1 to see them.
124 ui.debug('could not import %s (%s): trying %s\n'
124 ui.debug('could not import %s (%s): trying %s\n'
125 % (failed, stringutil.forcebytestr(err), next))
125 % (failed, stringutil.forcebytestr(err), next))
126 if ui.debugflag:
126 if ui.debugflag:
127 ui.traceback()
127 ui.traceback()
128
128
129 def _rejectunicode(name, xs):
129 def _rejectunicode(name, xs):
130 if isinstance(xs, (list, set, tuple)):
130 if isinstance(xs, (list, set, tuple)):
131 for x in xs:
131 for x in xs:
132 _rejectunicode(name, x)
132 _rejectunicode(name, x)
133 elif isinstance(xs, dict):
133 elif isinstance(xs, dict):
134 for k, v in xs.items():
134 for k, v in xs.items():
135 _rejectunicode(name, k)
135 _rejectunicode(name, k)
136 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
136 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
137 elif isinstance(xs, type(u'')):
137 elif isinstance(xs, type(u'')):
138 raise error.ProgrammingError(b"unicode %r found in %s" % (xs, name),
138 raise error.ProgrammingError(b"unicode %r found in %s" % (xs, name),
139 hint="use b'' to make it byte string")
139 hint="use b'' to make it byte string")
140
140
141 # attributes set by registrar.command
141 # attributes set by registrar.command
142 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
142 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
143
143
144 def _validatecmdtable(ui, cmdtable):
144 def _validatecmdtable(ui, cmdtable):
145 """Check if extension commands have required attributes"""
145 """Check if extension commands have required attributes"""
146 for c, e in cmdtable.iteritems():
146 for c, e in cmdtable.iteritems():
147 f = e[0]
147 f = e[0]
148 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
148 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
149 if not missing:
149 if not missing:
150 continue
150 continue
151 raise error.ProgrammingError(
151 raise error.ProgrammingError(
152 'missing attributes: %s' % ', '.join(missing),
152 'missing attributes: %s' % ', '.join(missing),
153 hint="use @command decorator to register '%s'" % c)
153 hint="use @command decorator to register '%s'" % c)
154
154
155 def _validatetables(ui, mod):
155 def _validatetables(ui, mod):
156 """Sanity check for loadable tables provided by extension module"""
156 """Sanity check for loadable tables provided by extension module"""
157 for t in ['cmdtable', 'colortable', 'configtable']:
157 for t in ['cmdtable', 'colortable', 'configtable']:
158 _rejectunicode(t, getattr(mod, t, {}))
158 _rejectunicode(t, getattr(mod, t, {}))
159 for t in ['filesetpredicate', 'internalmerge', 'revsetpredicate',
159 for t in ['filesetpredicate', 'internalmerge', 'revsetpredicate',
160 'templatefilter', 'templatefunc', 'templatekeyword']:
160 'templatefilter', 'templatefunc', 'templatekeyword']:
161 o = getattr(mod, t, None)
161 o = getattr(mod, t, None)
162 if o:
162 if o:
163 _rejectunicode(t, o._table)
163 _rejectunicode(t, o._table)
164 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
164 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
165
165
166 def load(ui, name, path):
166 def load(ui, name, path):
167 if name.startswith('hgext.') or name.startswith('hgext/'):
167 if name.startswith('hgext.') or name.startswith('hgext/'):
168 shortname = name[6:]
168 shortname = name[6:]
169 else:
169 else:
170 shortname = name
170 shortname = name
171 if shortname in _builtin:
171 if shortname in _builtin:
172 return None
172 return None
173 if shortname in _extensions:
173 if shortname in _extensions:
174 return _extensions[shortname]
174 return _extensions[shortname]
175 _extensions[shortname] = None
175 _extensions[shortname] = None
176 mod = _importext(name, path, bind(_reportimporterror, ui))
176 mod = _importext(name, path, bind(_reportimporterror, ui))
177
177
178 # Before we do anything with the extension, check against minimum stated
178 # Before we do anything with the extension, check against minimum stated
179 # compatibility. This gives extension authors a mechanism to have their
179 # compatibility. This gives extension authors a mechanism to have their
180 # extensions short circuit when loaded with a known incompatible version
180 # extensions short circuit when loaded with a known incompatible version
181 # of Mercurial.
181 # of Mercurial.
182 minver = getattr(mod, 'minimumhgversion', None)
182 minver = getattr(mod, 'minimumhgversion', None)
183 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
183 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
184 ui.warn(_('(third party extension %s requires version %s or newer '
184 ui.warn(_('(third party extension %s requires version %s or newer '
185 'of Mercurial; disabling)\n') % (shortname, minver))
185 'of Mercurial; disabling)\n') % (shortname, minver))
186 return
186 return
187 _validatetables(ui, mod)
187 _validatetables(ui, mod)
188
188
189 _extensions[shortname] = mod
189 _extensions[shortname] = mod
190 _order.append(shortname)
190 _order.append(shortname)
191 for fn in _aftercallbacks.get(shortname, []):
191 for fn in _aftercallbacks.get(shortname, []):
192 fn(loaded=True)
192 fn(loaded=True)
193 return mod
193 return mod
194
194
195 def _runuisetup(name, ui):
195 def _runuisetup(name, ui):
196 uisetup = getattr(_extensions[name], 'uisetup', None)
196 uisetup = getattr(_extensions[name], 'uisetup', None)
197 if uisetup:
197 if uisetup:
198 try:
198 try:
199 uisetup(ui)
199 uisetup(ui)
200 except Exception as inst:
200 except Exception as inst:
201 ui.traceback(force=True)
201 ui.traceback(force=True)
202 msg = stringutil.forcebytestr(inst)
202 msg = stringutil.forcebytestr(inst)
203 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
203 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
204 return False
204 return False
205 return True
205 return True
206
206
207 def _runextsetup(name, ui):
207 def _runextsetup(name, ui):
208 extsetup = getattr(_extensions[name], 'extsetup', None)
208 extsetup = getattr(_extensions[name], 'extsetup', None)
209 if extsetup:
209 if extsetup:
210 try:
210 try:
211 try:
211 try:
212 extsetup(ui)
212 extsetup(ui)
213 except TypeError:
213 except TypeError:
214 if pycompat.getargspec(extsetup).args:
214 if pycompat.getargspec(extsetup).args:
215 raise
215 raise
216 extsetup() # old extsetup with no ui argument
216 extsetup() # old extsetup with no ui argument
217 except Exception as inst:
217 except Exception as inst:
218 ui.traceback(force=True)
218 ui.traceback(force=True)
219 msg = stringutil.forcebytestr(inst)
219 msg = stringutil.forcebytestr(inst)
220 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
220 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
221 return False
221 return False
222 return True
222 return True
223
223
224 def loadall(ui, whitelist=None):
224 def loadall(ui, whitelist=None):
225 result = ui.configitems("extensions")
225 result = ui.configitems("extensions")
226 if whitelist is not None:
226 if whitelist is not None:
227 result = [(k, v) for (k, v) in result if k in whitelist]
227 result = [(k, v) for (k, v) in result if k in whitelist]
228 newindex = len(_order)
228 newindex = len(_order)
229 for (name, path) in result:
229 for (name, path) in result:
230 if path:
230 if path:
231 if path[0:1] == '!':
231 if path[0:1] == '!':
232 _disabledextensions[name] = path[1:]
232 _disabledextensions[name] = path[1:]
233 continue
233 continue
234 try:
234 try:
235 load(ui, name, path)
235 load(ui, name, path)
236 except Exception as inst:
236 except Exception as inst:
237 msg = stringutil.forcebytestr(inst)
237 msg = stringutil.forcebytestr(inst)
238 if path:
238 if path:
239 ui.warn(_("*** failed to import extension %s from %s: %s\n")
239 ui.warn(_("*** failed to import extension %s from %s: %s\n")
240 % (name, path, msg))
240 % (name, path, msg))
241 else:
241 else:
242 ui.warn(_("*** failed to import extension %s: %s\n")
242 ui.warn(_("*** failed to import extension %s: %s\n")
243 % (name, msg))
243 % (name, msg))
244 if isinstance(inst, error.Hint) and inst.hint:
244 if isinstance(inst, error.Hint) and inst.hint:
245 ui.warn(_("*** (%s)\n") % inst.hint)
245 ui.warn(_("*** (%s)\n") % inst.hint)
246 ui.traceback()
246 ui.traceback()
247 # list of (objname, loadermod, loadername) tuple:
247 # list of (objname, loadermod, loadername) tuple:
248 # - objname is the name of an object in extension module,
248 # - objname is the name of an object in extension module,
249 # from which extra information is loaded
249 # from which extra information is loaded
250 # - loadermod is the module where loader is placed
250 # - loadermod is the module where loader is placed
251 # - loadername is the name of the function,
251 # - loadername is the name of the function,
252 # which takes (ui, extensionname, extraobj) arguments
252 # which takes (ui, extensionname, extraobj) arguments
253 #
253 #
254 # This one is for the list of item that must be run before running any setup
254 # This one is for the list of item that must be run before running any setup
255 earlyextraloaders = [
255 earlyextraloaders = [
256 ('configtable', configitems, 'loadconfigtable'),
256 ('configtable', configitems, 'loadconfigtable'),
257 ]
257 ]
258 _loadextra(ui, newindex, earlyextraloaders)
258 _loadextra(ui, newindex, earlyextraloaders)
259
259
260 broken = set()
260 broken = set()
261 for name in _order[newindex:]:
261 for name in _order[newindex:]:
262 if not _runuisetup(name, ui):
262 if not _runuisetup(name, ui):
263 broken.add(name)
263 broken.add(name)
264
264
265 for name in _order[newindex:]:
265 for name in _order[newindex:]:
266 if name in broken:
266 if name in broken:
267 continue
267 continue
268 if not _runextsetup(name, ui):
268 if not _runextsetup(name, ui):
269 broken.add(name)
269 broken.add(name)
270
270
271 for name in broken:
271 for name in broken:
272 _extensions[name] = None
272 _extensions[name] = None
273
273
274 # Call aftercallbacks that were never met.
274 # Call aftercallbacks that were never met.
275 for shortname in _aftercallbacks:
275 for shortname in _aftercallbacks:
276 if shortname in _extensions:
276 if shortname in _extensions:
277 continue
277 continue
278
278
279 for fn in _aftercallbacks[shortname]:
279 for fn in _aftercallbacks[shortname]:
280 fn(loaded=False)
280 fn(loaded=False)
281
281
282 # loadall() is called multiple times and lingering _aftercallbacks
282 # loadall() is called multiple times and lingering _aftercallbacks
283 # entries could result in double execution. See issue4646.
283 # entries could result in double execution. See issue4646.
284 _aftercallbacks.clear()
284 _aftercallbacks.clear()
285
285
286 # delay importing avoids cyclic dependency (especially commands)
286 # delay importing avoids cyclic dependency (especially commands)
287 from . import (
287 from . import (
288 color,
288 color,
289 commands,
289 commands,
290 filemerge,
290 filemerge,
291 fileset,
291 fileset,
292 revset,
292 revset,
293 templatefilters,
293 templatefilters,
294 templatefuncs,
294 templatefuncs,
295 templatekw,
295 templatekw,
296 )
296 )
297
297
298 # list of (objname, loadermod, loadername) tuple:
298 # list of (objname, loadermod, loadername) tuple:
299 # - objname is the name of an object in extension module,
299 # - objname is the name of an object in extension module,
300 # from which extra information is loaded
300 # from which extra information is loaded
301 # - loadermod is the module where loader is placed
301 # - loadermod is the module where loader is placed
302 # - loadername is the name of the function,
302 # - loadername is the name of the function,
303 # which takes (ui, extensionname, extraobj) arguments
303 # which takes (ui, extensionname, extraobj) arguments
304 extraloaders = [
304 extraloaders = [
305 ('cmdtable', commands, 'loadcmdtable'),
305 ('cmdtable', commands, 'loadcmdtable'),
306 ('colortable', color, 'loadcolortable'),
306 ('colortable', color, 'loadcolortable'),
307 ('filesetpredicate', fileset, 'loadpredicate'),
307 ('filesetpredicate', fileset, 'loadpredicate'),
308 ('internalmerge', filemerge, 'loadinternalmerge'),
308 ('internalmerge', filemerge, 'loadinternalmerge'),
309 ('revsetpredicate', revset, 'loadpredicate'),
309 ('revsetpredicate', revset, 'loadpredicate'),
310 ('templatefilter', templatefilters, 'loadfilter'),
310 ('templatefilter', templatefilters, 'loadfilter'),
311 ('templatefunc', templatefuncs, 'loadfunction'),
311 ('templatefunc', templatefuncs, 'loadfunction'),
312 ('templatekeyword', templatekw, 'loadkeyword'),
312 ('templatekeyword', templatekw, 'loadkeyword'),
313 ]
313 ]
314 _loadextra(ui, newindex, extraloaders)
314 _loadextra(ui, newindex, extraloaders)
315
315
316 def _loadextra(ui, newindex, extraloaders):
316 def _loadextra(ui, newindex, extraloaders):
317 for name in _order[newindex:]:
317 for name in _order[newindex:]:
318 module = _extensions[name]
318 module = _extensions[name]
319 if not module:
319 if not module:
320 continue # loading this module failed
320 continue # loading this module failed
321
321
322 for objname, loadermod, loadername in extraloaders:
322 for objname, loadermod, loadername in extraloaders:
323 extraobj = getattr(module, objname, None)
323 extraobj = getattr(module, objname, None)
324 if extraobj is not None:
324 if extraobj is not None:
325 getattr(loadermod, loadername)(ui, name, extraobj)
325 getattr(loadermod, loadername)(ui, name, extraobj)
326
326
327 def afterloaded(extension, callback):
327 def afterloaded(extension, callback):
328 '''Run the specified function after a named extension is loaded.
328 '''Run the specified function after a named extension is loaded.
329
329
330 If the named extension is already loaded, the callback will be called
330 If the named extension is already loaded, the callback will be called
331 immediately.
331 immediately.
332
332
333 If the named extension never loads, the callback will be called after
333 If the named extension never loads, the callback will be called after
334 all extensions have been loaded.
334 all extensions have been loaded.
335
335
336 The callback receives the named argument ``loaded``, which is a boolean
336 The callback receives the named argument ``loaded``, which is a boolean
337 indicating whether the dependent extension actually loaded.
337 indicating whether the dependent extension actually loaded.
338 '''
338 '''
339
339
340 if extension in _extensions:
340 if extension in _extensions:
341 # Report loaded as False if the extension is disabled
341 # Report loaded as False if the extension is disabled
342 loaded = (_extensions[extension] is not None)
342 loaded = (_extensions[extension] is not None)
343 callback(loaded=loaded)
343 callback(loaded=loaded)
344 else:
344 else:
345 _aftercallbacks.setdefault(extension, []).append(callback)
345 _aftercallbacks.setdefault(extension, []).append(callback)
346
346
347 def bind(func, *args):
347 def bind(func, *args):
348 '''Partial function application
348 '''Partial function application
349
349
350 Returns a new function that is the partial application of args and kwargs
350 Returns a new function that is the partial application of args and kwargs
351 to func. For example,
351 to func. For example,
352
352
353 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
353 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
354 assert callable(func)
354 assert callable(func)
355 def closure(*a, **kw):
355 def closure(*a, **kw):
356 return func(*(args + a), **kw)
356 return func(*(args + a), **kw)
357 return closure
357 return closure
358
358
359 def _updatewrapper(wrap, origfn, unboundwrapper):
359 def _updatewrapper(wrap, origfn, unboundwrapper):
360 '''Copy and add some useful attributes to wrapper'''
360 '''Copy and add some useful attributes to wrapper'''
361 try:
361 try:
362 wrap.__name__ = origfn.__name__
362 wrap.__name__ = origfn.__name__
363 except AttributeError:
363 except AttributeError:
364 pass
364 pass
365 wrap.__module__ = getattr(origfn, '__module__')
365 wrap.__module__ = getattr(origfn, '__module__')
366 wrap.__doc__ = getattr(origfn, '__doc__')
366 wrap.__doc__ = getattr(origfn, '__doc__')
367 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
367 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
368 wrap._origfunc = origfn
368 wrap._origfunc = origfn
369 wrap._unboundwrapper = unboundwrapper
369 wrap._unboundwrapper = unboundwrapper
370
370
371 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
371 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
372 '''Wrap the command named `command' in table
372 '''Wrap the command named `command' in table
373
373
374 Replace command in the command table with wrapper. The wrapped command will
374 Replace command in the command table with wrapper. The wrapped command will
375 be inserted into the command table specified by the table argument.
375 be inserted into the command table specified by the table argument.
376
376
377 The wrapper will be called like
377 The wrapper will be called like
378
378
379 wrapper(orig, *args, **kwargs)
379 wrapper(orig, *args, **kwargs)
380
380
381 where orig is the original (wrapped) function, and *args, **kwargs
381 where orig is the original (wrapped) function, and *args, **kwargs
382 are the arguments passed to it.
382 are the arguments passed to it.
383
383
384 Optionally append to the command synopsis and docstring, used for help.
384 Optionally append to the command synopsis and docstring, used for help.
385 For example, if your extension wraps the ``bookmarks`` command to add the
385 For example, if your extension wraps the ``bookmarks`` command to add the
386 flags ``--remote`` and ``--all`` you might call this function like so:
386 flags ``--remote`` and ``--all`` you might call this function like so:
387
387
388 synopsis = ' [-a] [--remote]'
388 synopsis = ' [-a] [--remote]'
389 docstring = """
389 docstring = """
390
390
391 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
391 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
392 flags to the bookmarks command. Either flag will show the remote bookmarks
392 flags to the bookmarks command. Either flag will show the remote bookmarks
393 known to the repository; ``--remote`` will also suppress the output of the
393 known to the repository; ``--remote`` will also suppress the output of the
394 local bookmarks.
394 local bookmarks.
395 """
395 """
396
396
397 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
397 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
398 synopsis, docstring)
398 synopsis, docstring)
399 '''
399 '''
400 assert callable(wrapper)
400 assert callable(wrapper)
401 aliases, entry = cmdutil.findcmd(command, table)
401 aliases, entry = cmdutil.findcmd(command, table)
402 for alias, e in table.iteritems():
402 for alias, e in table.iteritems():
403 if e is entry:
403 if e is entry:
404 key = alias
404 key = alias
405 break
405 break
406
406
407 origfn = entry[0]
407 origfn = entry[0]
408 wrap = functools.partial(util.checksignature(wrapper),
408 wrap = functools.partial(util.checksignature(wrapper),
409 util.checksignature(origfn))
409 util.checksignature(origfn))
410 _updatewrapper(wrap, origfn, wrapper)
410 _updatewrapper(wrap, origfn, wrapper)
411 if docstring is not None:
411 if docstring is not None:
412 wrap.__doc__ += docstring
412 wrap.__doc__ += docstring
413
413
414 newentry = list(entry)
414 newentry = list(entry)
415 newentry[0] = wrap
415 newentry[0] = wrap
416 if synopsis is not None:
416 if synopsis is not None:
417 newentry[2] += synopsis
417 newentry[2] += synopsis
418 table[key] = tuple(newentry)
418 table[key] = tuple(newentry)
419 return entry
419 return entry
420
420
421 def wrapfilecache(cls, propname, wrapper):
421 def wrapfilecache(cls, propname, wrapper):
422 """Wraps a filecache property.
422 """Wraps a filecache property.
423
423
424 These can't be wrapped using the normal wrapfunction.
424 These can't be wrapped using the normal wrapfunction.
425 """
425 """
426 propname = pycompat.sysstr(propname)
426 propname = pycompat.sysstr(propname)
427 assert callable(wrapper)
427 assert callable(wrapper)
428 for currcls in cls.__mro__:
428 for currcls in cls.__mro__:
429 if propname in currcls.__dict__:
429 if propname in currcls.__dict__:
430 origfn = currcls.__dict__[propname].func
430 origfn = currcls.__dict__[propname].func
431 assert callable(origfn)
431 assert callable(origfn)
432 def wrap(*args, **kwargs):
432 def wrap(*args, **kwargs):
433 return wrapper(origfn, *args, **kwargs)
433 return wrapper(origfn, *args, **kwargs)
434 currcls.__dict__[propname].func = wrap
434 currcls.__dict__[propname].func = wrap
435 break
435 break
436
436
437 if currcls is object:
437 if currcls is object:
438 raise AttributeError(r"type '%s' has no property '%s'" % (
438 raise AttributeError(r"type '%s' has no property '%s'" % (
439 cls, propname))
439 cls, propname))
440
440
441 class wrappedfunction(object):
441 class wrappedfunction(object):
442 '''context manager for temporarily wrapping a function'''
442 '''context manager for temporarily wrapping a function'''
443
443
444 def __init__(self, container, funcname, wrapper):
444 def __init__(self, container, funcname, wrapper):
445 assert callable(wrapper)
445 assert callable(wrapper)
446 self._container = container
446 self._container = container
447 self._funcname = funcname
447 self._funcname = funcname
448 self._wrapper = wrapper
448 self._wrapper = wrapper
449
449
450 def __enter__(self):
450 def __enter__(self):
451 wrapfunction(self._container, self._funcname, self._wrapper)
451 wrapfunction(self._container, self._funcname, self._wrapper)
452
452
453 def __exit__(self, exctype, excvalue, traceback):
453 def __exit__(self, exctype, excvalue, traceback):
454 unwrapfunction(self._container, self._funcname, self._wrapper)
454 unwrapfunction(self._container, self._funcname, self._wrapper)
455
455
456 def wrapfunction(container, funcname, wrapper):
456 def wrapfunction(container, funcname, wrapper):
457 '''Wrap the function named funcname in container
457 '''Wrap the function named funcname in container
458
458
459 Replace the funcname member in the given container with the specified
459 Replace the funcname member in the given container with the specified
460 wrapper. The container is typically a module, class, or instance.
460 wrapper. The container is typically a module, class, or instance.
461
461
462 The wrapper will be called like
462 The wrapper will be called like
463
463
464 wrapper(orig, *args, **kwargs)
464 wrapper(orig, *args, **kwargs)
465
465
466 where orig is the original (wrapped) function, and *args, **kwargs
466 where orig is the original (wrapped) function, and *args, **kwargs
467 are the arguments passed to it.
467 are the arguments passed to it.
468
468
469 Wrapping methods of the repository object is not recommended since
469 Wrapping methods of the repository object is not recommended since
470 it conflicts with extensions that extend the repository by
470 it conflicts with extensions that extend the repository by
471 subclassing. All extensions that need to extend methods of
471 subclassing. All extensions that need to extend methods of
472 localrepository should use this subclassing trick: namely,
472 localrepository should use this subclassing trick: namely,
473 reposetup() should look like
473 reposetup() should look like
474
474
475 def reposetup(ui, repo):
475 def reposetup(ui, repo):
476 class myrepo(repo.__class__):
476 class myrepo(repo.__class__):
477 def whatever(self, *args, **kwargs):
477 def whatever(self, *args, **kwargs):
478 [...extension stuff...]
478 [...extension stuff...]
479 super(myrepo, self).whatever(*args, **kwargs)
479 super(myrepo, self).whatever(*args, **kwargs)
480 [...extension stuff...]
480 [...extension stuff...]
481
481
482 repo.__class__ = myrepo
482 repo.__class__ = myrepo
483
483
484 In general, combining wrapfunction() with subclassing does not
484 In general, combining wrapfunction() with subclassing does not
485 work. Since you cannot control what other extensions are loaded by
485 work. Since you cannot control what other extensions are loaded by
486 your end users, you should play nicely with others by using the
486 your end users, you should play nicely with others by using the
487 subclass trick.
487 subclass trick.
488 '''
488 '''
489 assert callable(wrapper)
489 assert callable(wrapper)
490
490
491 origfn = getattr(container, funcname)
491 origfn = getattr(container, funcname)
492 assert callable(origfn)
492 assert callable(origfn)
493 if inspect.ismodule(container):
493 if inspect.ismodule(container):
494 # origfn is not an instance or class method. "partial" can be used.
494 # origfn is not an instance or class method. "partial" can be used.
495 # "partial" won't insert a frame in traceback.
495 # "partial" won't insert a frame in traceback.
496 wrap = functools.partial(wrapper, origfn)
496 wrap = functools.partial(wrapper, origfn)
497 else:
497 else:
498 # "partial" cannot be safely used. Emulate its effect by using "bind".
498 # "partial" cannot be safely used. Emulate its effect by using "bind".
499 # The downside is one more frame in traceback.
499 # The downside is one more frame in traceback.
500 wrap = bind(wrapper, origfn)
500 wrap = bind(wrapper, origfn)
501 _updatewrapper(wrap, origfn, wrapper)
501 _updatewrapper(wrap, origfn, wrapper)
502 setattr(container, funcname, wrap)
502 setattr(container, funcname, wrap)
503 return origfn
503 return origfn
504
504
505 def unwrapfunction(container, funcname, wrapper=None):
505 def unwrapfunction(container, funcname, wrapper=None):
506 '''undo wrapfunction
506 '''undo wrapfunction
507
507
508 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
508 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
509 from the chain of wrappers.
509 from the chain of wrappers.
510
510
511 Return the removed wrapper.
511 Return the removed wrapper.
512 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
512 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
513 wrapper is not None but is not found in the wrapper chain.
513 wrapper is not None but is not found in the wrapper chain.
514 '''
514 '''
515 chain = getwrapperchain(container, funcname)
515 chain = getwrapperchain(container, funcname)
516 origfn = chain.pop()
516 origfn = chain.pop()
517 if wrapper is None:
517 if wrapper is None:
518 wrapper = chain[0]
518 wrapper = chain[0]
519 chain.remove(wrapper)
519 chain.remove(wrapper)
520 setattr(container, funcname, origfn)
520 setattr(container, funcname, origfn)
521 for w in reversed(chain):
521 for w in reversed(chain):
522 wrapfunction(container, funcname, w)
522 wrapfunction(container, funcname, w)
523 return wrapper
523 return wrapper
524
524
525 def getwrapperchain(container, funcname):
525 def getwrapperchain(container, funcname):
526 '''get a chain of wrappers of a function
526 '''get a chain of wrappers of a function
527
527
528 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
528 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
529
529
530 The wrapper functions are the ones passed to wrapfunction, whose first
530 The wrapper functions are the ones passed to wrapfunction, whose first
531 argument is origfunc.
531 argument is origfunc.
532 '''
532 '''
533 result = []
533 result = []
534 fn = getattr(container, funcname)
534 fn = getattr(container, funcname)
535 while fn:
535 while fn:
536 assert callable(fn)
536 assert callable(fn)
537 result.append(getattr(fn, '_unboundwrapper', fn))
537 result.append(getattr(fn, '_unboundwrapper', fn))
538 fn = getattr(fn, '_origfunc', None)
538 fn = getattr(fn, '_origfunc', None)
539 return result
539 return result
540
540
541 def _disabledpaths(strip_init=False):
541 def _disabledpaths(strip_init=False):
542 '''find paths of disabled extensions. returns a dict of {name: path}
542 '''find paths of disabled extensions. returns a dict of {name: path}
543 removes /__init__.py from packages if strip_init is True'''
543 removes /__init__.py from packages if strip_init is True'''
544 import hgext
544 import hgext
545 extpath = os.path.dirname(
545 extpath = os.path.dirname(
546 os.path.abspath(pycompat.fsencode(hgext.__file__)))
546 os.path.abspath(pycompat.fsencode(hgext.__file__)))
547 try: # might not be a filesystem path
547 try: # might not be a filesystem path
548 files = os.listdir(extpath)
548 files = os.listdir(extpath)
549 except OSError:
549 except OSError:
550 return {}
550 return {}
551
551
552 exts = {}
552 exts = {}
553 for e in files:
553 for e in files:
554 if e.endswith('.py'):
554 if e.endswith('.py'):
555 name = e.rsplit('.', 1)[0]
555 name = e.rsplit('.', 1)[0]
556 path = os.path.join(extpath, e)
556 path = os.path.join(extpath, e)
557 else:
557 else:
558 name = e
558 name = e
559 path = os.path.join(extpath, e, '__init__.py')
559 path = os.path.join(extpath, e, '__init__.py')
560 if not os.path.exists(path):
560 if not os.path.exists(path):
561 continue
561 continue
562 if strip_init:
562 if strip_init:
563 path = os.path.dirname(path)
563 path = os.path.dirname(path)
564 if name in exts or name in _order or name == '__init__':
564 if name in exts or name in _order or name == '__init__':
565 continue
565 continue
566 exts[name] = path
566 exts[name] = path
567 for name, path in _disabledextensions.iteritems():
567 for name, path in _disabledextensions.iteritems():
568 # If no path was provided for a disabled extension (e.g. "color=!"),
568 # If no path was provided for a disabled extension (e.g. "color=!"),
569 # don't replace the path we already found by the scan above.
569 # don't replace the path we already found by the scan above.
570 if path:
570 if path:
571 exts[name] = path
571 exts[name] = path
572 return exts
572 return exts
573
573
574 def _moduledoc(file):
574 def _moduledoc(file):
575 '''return the top-level python documentation for the given file
575 '''return the top-level python documentation for the given file
576
576
577 Loosely inspired by pydoc.source_synopsis(), but rewritten to
577 Loosely inspired by pydoc.source_synopsis(), but rewritten to
578 handle triple quotes and to return the whole text instead of just
578 handle triple quotes and to return the whole text instead of just
579 the synopsis'''
579 the synopsis'''
580 result = []
580 result = []
581
581
582 line = file.readline()
582 line = file.readline()
583 while line[:1] == '#' or not line.strip():
583 while line[:1] == '#' or not line.strip():
584 line = file.readline()
584 line = file.readline()
585 if not line:
585 if not line:
586 break
586 break
587
587
588 start = line[:3]
588 start = line[:3]
589 if start == '"""' or start == "'''":
589 if start == '"""' or start == "'''":
590 line = line[3:]
590 line = line[3:]
591 while line:
591 while line:
592 if line.rstrip().endswith(start):
592 if line.rstrip().endswith(start):
593 line = line.split(start)[0]
593 line = line.split(start)[0]
594 if line:
594 if line:
595 result.append(line)
595 result.append(line)
596 break
596 break
597 elif not line:
597 elif not line:
598 return None # unmatched delimiter
598 return None # unmatched delimiter
599 result.append(line)
599 result.append(line)
600 line = file.readline()
600 line = file.readline()
601 else:
601 else:
602 return None
602 return None
603
603
604 return ''.join(result)
604 return ''.join(result)
605
605
606 def _disabledhelp(path):
606 def _disabledhelp(path):
607 '''retrieve help synopsis of a disabled extension (without importing)'''
607 '''retrieve help synopsis of a disabled extension (without importing)'''
608 try:
608 try:
609 file = open(path)
609 file = open(path)
610 except IOError:
610 except IOError:
611 return
611 return
612 else:
612 else:
613 doc = _moduledoc(file)
613 doc = _moduledoc(file)
614 file.close()
614 file.close()
615
615
616 if doc: # extracting localized synopsis
616 if doc: # extracting localized synopsis
617 return gettext(doc)
617 return gettext(doc)
618 else:
618 else:
619 return _('(no help text available)')
619 return _('(no help text available)')
620
620
621 def disabled():
621 def disabled():
622 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
622 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
623 try:
623 try:
624 from hgext import __index__
624 from hgext import __index__
625 return dict((name, gettext(desc))
625 return dict((name, gettext(desc))
626 for name, desc in __index__.docs.iteritems()
626 for name, desc in __index__.docs.iteritems()
627 if name not in _order)
627 if name not in _order)
628 except (ImportError, AttributeError):
628 except (ImportError, AttributeError):
629 pass
629 pass
630
630
631 paths = _disabledpaths()
631 paths = _disabledpaths()
632 if not paths:
632 if not paths:
633 return {}
633 return {}
634
634
635 exts = {}
635 exts = {}
636 for name, path in paths.iteritems():
636 for name, path in paths.iteritems():
637 doc = _disabledhelp(path)
637 doc = _disabledhelp(path)
638 if doc:
638 if doc:
639 exts[name] = doc.splitlines()[0]
639 exts[name] = doc.splitlines()[0]
640
640
641 return exts
641 return exts
642
642
643 def disabledext(name):
643 def disabledext(name):
644 '''find a specific disabled extension from hgext. returns desc'''
644 '''find a specific disabled extension from hgext. returns desc'''
645 try:
645 try:
646 from hgext import __index__
646 from hgext import __index__
647 if name in _order: # enabled
647 if name in _order: # enabled
648 return
648 return
649 else:
649 else:
650 return gettext(__index__.docs.get(name))
650 return gettext(__index__.docs.get(name))
651 except (ImportError, AttributeError):
651 except (ImportError, AttributeError):
652 pass
652 pass
653
653
654 paths = _disabledpaths()
654 paths = _disabledpaths()
655 if name in paths:
655 if name in paths:
656 return _disabledhelp(paths[name])
656 return _disabledhelp(paths[name])
657
657
658 def disabledcmd(ui, cmd, strict=False):
658 def disabledcmd(ui, cmd, strict=False):
659 '''import disabled extensions until cmd is found.
659 '''import disabled extensions until cmd is found.
660 returns (cmdname, extname, module)'''
660 returns (cmdname, extname, module)'''
661
661
662 paths = _disabledpaths(strip_init=True)
662 paths = _disabledpaths(strip_init=True)
663 if not paths:
663 if not paths:
664 raise error.UnknownCommand(cmd)
664 raise error.UnknownCommand(cmd)
665
665
666 def findcmd(cmd, name, path):
666 def findcmd(cmd, name, path):
667 try:
667 try:
668 mod = loadpath(path, 'hgext.%s' % name)
668 mod = loadpath(path, 'hgext.%s' % name)
669 except Exception:
669 except Exception:
670 return
670 return
671 try:
671 try:
672 aliases, entry = cmdutil.findcmd(cmd,
672 aliases, entry = cmdutil.findcmd(cmd,
673 getattr(mod, 'cmdtable', {}), strict)
673 getattr(mod, 'cmdtable', {}), strict)
674 except (error.AmbiguousCommand, error.UnknownCommand):
674 except (error.AmbiguousCommand, error.UnknownCommand):
675 return
675 return
676 except Exception:
676 except Exception:
677 ui.warn(_('warning: error finding commands in %s\n') % path)
677 ui.warn(_('warning: error finding commands in %s\n') % path)
678 ui.traceback()
678 ui.traceback()
679 return
679 return
680 for c in aliases:
680 for c in aliases:
681 if c.startswith(cmd):
681 if c.startswith(cmd):
682 cmd = c
682 cmd = c
683 break
683 break
684 else:
684 else:
685 cmd = aliases[0]
685 cmd = aliases[0]
686 return (cmd, name, mod)
686 return (cmd, name, mod)
687
687
688 ext = None
688 ext = None
689 # first, search for an extension with the same name as the command
689 # first, search for an extension with the same name as the command
690 path = paths.pop(cmd, None)
690 path = paths.pop(cmd, None)
691 if path:
691 if path:
692 ext = findcmd(cmd, cmd, path)
692 ext = findcmd(cmd, cmd, path)
693 if not ext:
693 if not ext:
694 # otherwise, interrogate each extension until there's a match
694 # otherwise, interrogate each extension until there's a match
695 for name, path in paths.iteritems():
695 for name, path in paths.iteritems():
696 ext = findcmd(cmd, name, path)
696 ext = findcmd(cmd, name, path)
697 if ext:
697 if ext:
698 break
698 break
699 if ext and 'DEPRECATED' not in ext.__doc__:
699 if ext:
700 return ext
700 return ext
701
701
702 raise error.UnknownCommand(cmd)
702 raise error.UnknownCommand(cmd)
703
703
704 def enabled(shortname=True):
704 def enabled(shortname=True):
705 '''return a dict of {name: desc} of extensions'''
705 '''return a dict of {name: desc} of extensions'''
706 exts = {}
706 exts = {}
707 for ename, ext in extensions():
707 for ename, ext in extensions():
708 doc = (gettext(ext.__doc__) or _('(no help text available)'))
708 doc = (gettext(ext.__doc__) or _('(no help text available)'))
709 if shortname:
709 if shortname:
710 ename = ename.split('.')[-1]
710 ename = ename.split('.')[-1]
711 exts[ename] = doc.splitlines()[0].strip()
711 exts[ename] = doc.splitlines()[0].strip()
712
712
713 return exts
713 return exts
714
714
715 def notloaded():
715 def notloaded():
716 '''return short names of extensions that failed to load'''
716 '''return short names of extensions that failed to load'''
717 return [name for name, mod in _extensions.iteritems() if mod is None]
717 return [name for name, mod in _extensions.iteritems() if mod is None]
718
718
719 def moduleversion(module):
719 def moduleversion(module):
720 '''return version information from given module as a string'''
720 '''return version information from given module as a string'''
721 if (util.safehasattr(module, 'getversion')
721 if (util.safehasattr(module, 'getversion')
722 and callable(module.getversion)):
722 and callable(module.getversion)):
723 version = module.getversion()
723 version = module.getversion()
724 elif util.safehasattr(module, '__version__'):
724 elif util.safehasattr(module, '__version__'):
725 version = module.__version__
725 version = module.__version__
726 else:
726 else:
727 version = ''
727 version = ''
728 if isinstance(version, (list, tuple)):
728 if isinstance(version, (list, tuple)):
729 version = '.'.join(str(o) for o in version)
729 version = '.'.join(str(o) for o in version)
730 return version
730 return version
731
731
732 def ismoduleinternal(module):
732 def ismoduleinternal(module):
733 exttestedwith = getattr(module, 'testedwith', None)
733 exttestedwith = getattr(module, 'testedwith', None)
734 return exttestedwith == "ships-with-hg-core"
734 return exttestedwith == "ships-with-hg-core"
General Comments 0
You need to be logged in to leave comments. Login now