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