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