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