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