##// END OF EJS Templates
extensions: set attributes to wrappers so we can trace them back...
Jun Wu -
r29763:ce6317dc default
parent child Browse files
Show More
@@ -1,497 +1,499 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 os
11 import os
12
12
13 from .i18n import (
13 from .i18n import (
14 _,
14 _,
15 gettext,
15 gettext,
16 )
16 )
17
17
18 from . import (
18 from . import (
19 cmdutil,
19 cmdutil,
20 error,
20 error,
21 util,
21 util,
22 )
22 )
23
23
24 _extensions = {}
24 _extensions = {}
25 _aftercallbacks = {}
25 _aftercallbacks = {}
26 _order = []
26 _order = []
27 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
27 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
28 'inotify', 'hgcia'])
28 'inotify', 'hgcia'])
29
29
30 def extensions(ui=None):
30 def extensions(ui=None):
31 if ui:
31 if ui:
32 def enabled(name):
32 def enabled(name):
33 for format in ['%s', 'hgext.%s']:
33 for format in ['%s', 'hgext.%s']:
34 conf = ui.config('extensions', format % name)
34 conf = ui.config('extensions', format % name)
35 if conf is not None and not conf.startswith('!'):
35 if conf is not None and not conf.startswith('!'):
36 return True
36 return True
37 else:
37 else:
38 enabled = lambda name: True
38 enabled = lambda name: True
39 for name in _order:
39 for name in _order:
40 module = _extensions[name]
40 module = _extensions[name]
41 if module and enabled(name):
41 if module and enabled(name):
42 yield name, module
42 yield name, module
43
43
44 def find(name):
44 def find(name):
45 '''return module with given extension name'''
45 '''return module with given extension name'''
46 mod = None
46 mod = None
47 try:
47 try:
48 mod = _extensions[name]
48 mod = _extensions[name]
49 except KeyError:
49 except KeyError:
50 for k, v in _extensions.iteritems():
50 for k, v in _extensions.iteritems():
51 if k.endswith('.' + name) or k.endswith('/' + name):
51 if k.endswith('.' + name) or k.endswith('/' + name):
52 mod = v
52 mod = v
53 break
53 break
54 if not mod:
54 if not mod:
55 raise KeyError(name)
55 raise KeyError(name)
56 return mod
56 return mod
57
57
58 def loadpath(path, module_name):
58 def loadpath(path, module_name):
59 module_name = module_name.replace('.', '_')
59 module_name = module_name.replace('.', '_')
60 path = util.normpath(util.expandpath(path))
60 path = util.normpath(util.expandpath(path))
61 if os.path.isdir(path):
61 if os.path.isdir(path):
62 # module/__init__.py style
62 # module/__init__.py style
63 d, f = os.path.split(path)
63 d, f = os.path.split(path)
64 fd, fpath, desc = imp.find_module(f, [d])
64 fd, fpath, desc = imp.find_module(f, [d])
65 return imp.load_module(module_name, fd, fpath, desc)
65 return imp.load_module(module_name, fd, fpath, desc)
66 else:
66 else:
67 try:
67 try:
68 return imp.load_source(module_name, path)
68 return imp.load_source(module_name, path)
69 except IOError as exc:
69 except IOError as exc:
70 if not exc.filename:
70 if not exc.filename:
71 exc.filename = path # python does not fill this
71 exc.filename = path # python does not fill this
72 raise
72 raise
73
73
74 def _importh(name):
74 def _importh(name):
75 """import and return the <name> module"""
75 """import and return the <name> module"""
76 mod = __import__(name)
76 mod = __import__(name)
77 components = name.split('.')
77 components = name.split('.')
78 for comp in components[1:]:
78 for comp in components[1:]:
79 mod = getattr(mod, comp)
79 mod = getattr(mod, comp)
80 return mod
80 return mod
81
81
82 def _reportimporterror(ui, err, failed, next):
82 def _reportimporterror(ui, err, failed, next):
83 ui.debug('could not import %s (%s): trying %s\n'
83 ui.debug('could not import %s (%s): trying %s\n'
84 % (failed, err, next))
84 % (failed, err, next))
85 if ui.debugflag:
85 if ui.debugflag:
86 ui.traceback()
86 ui.traceback()
87
87
88 def load(ui, name, path):
88 def load(ui, name, path):
89 if name.startswith('hgext.') or name.startswith('hgext/'):
89 if name.startswith('hgext.') or name.startswith('hgext/'):
90 shortname = name[6:]
90 shortname = name[6:]
91 else:
91 else:
92 shortname = name
92 shortname = name
93 if shortname in _builtin:
93 if shortname in _builtin:
94 return None
94 return None
95 if shortname in _extensions:
95 if shortname in _extensions:
96 return _extensions[shortname]
96 return _extensions[shortname]
97 _extensions[shortname] = None
97 _extensions[shortname] = None
98 if path:
98 if path:
99 # the module will be loaded in sys.modules
99 # the module will be loaded in sys.modules
100 # choose an unique name so that it doesn't
100 # choose an unique name so that it doesn't
101 # conflicts with other modules
101 # conflicts with other modules
102 mod = loadpath(path, 'hgext.%s' % name)
102 mod = loadpath(path, 'hgext.%s' % name)
103 else:
103 else:
104 try:
104 try:
105 mod = _importh("hgext.%s" % name)
105 mod = _importh("hgext.%s" % name)
106 except ImportError as err:
106 except ImportError as err:
107 _reportimporterror(ui, err, "hgext.%s" % name, name)
107 _reportimporterror(ui, err, "hgext.%s" % name, 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 _reportimporterror(ui, err, "hgext3rd.%s" % name, name)
111 _reportimporterror(ui, err, "hgext3rd.%s" % name, name)
112 mod = _importh(name)
112 mod = _importh(name)
113
113
114 # Before we do anything with the extension, check against minimum stated
114 # Before we do anything with the extension, check against minimum stated
115 # compatibility. This gives extension authors a mechanism to have their
115 # compatibility. This gives extension authors a mechanism to have their
116 # extensions short circuit when loaded with a known incompatible version
116 # extensions short circuit when loaded with a known incompatible version
117 # of Mercurial.
117 # of Mercurial.
118 minver = getattr(mod, 'minimumhgversion', None)
118 minver = getattr(mod, 'minimumhgversion', None)
119 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
119 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
120 ui.warn(_('(third party extension %s requires version %s or newer '
120 ui.warn(_('(third party extension %s requires version %s or newer '
121 'of Mercurial; disabling)\n') % (shortname, minver))
121 'of Mercurial; disabling)\n') % (shortname, minver))
122 return
122 return
123
123
124 _extensions[shortname] = mod
124 _extensions[shortname] = mod
125 _order.append(shortname)
125 _order.append(shortname)
126 for fn in _aftercallbacks.get(shortname, []):
126 for fn in _aftercallbacks.get(shortname, []):
127 fn(loaded=True)
127 fn(loaded=True)
128 return mod
128 return mod
129
129
130 def _runuisetup(name, ui):
130 def _runuisetup(name, ui):
131 uisetup = getattr(_extensions[name], 'uisetup', None)
131 uisetup = getattr(_extensions[name], 'uisetup', None)
132 if uisetup:
132 if uisetup:
133 uisetup(ui)
133 uisetup(ui)
134
134
135 def _runextsetup(name, ui):
135 def _runextsetup(name, ui):
136 extsetup = getattr(_extensions[name], 'extsetup', None)
136 extsetup = getattr(_extensions[name], 'extsetup', None)
137 if extsetup:
137 if extsetup:
138 try:
138 try:
139 extsetup(ui)
139 extsetup(ui)
140 except TypeError:
140 except TypeError:
141 if extsetup.func_code.co_argcount != 0:
141 if extsetup.func_code.co_argcount != 0:
142 raise
142 raise
143 extsetup() # old extsetup with no ui argument
143 extsetup() # old extsetup with no ui argument
144
144
145 def loadall(ui):
145 def loadall(ui):
146 result = ui.configitems("extensions")
146 result = ui.configitems("extensions")
147 newindex = len(_order)
147 newindex = len(_order)
148 for (name, path) in result:
148 for (name, path) in result:
149 if path:
149 if path:
150 if path[0] == '!':
150 if path[0] == '!':
151 continue
151 continue
152 try:
152 try:
153 load(ui, name, path)
153 load(ui, name, path)
154 except KeyboardInterrupt:
154 except KeyboardInterrupt:
155 raise
155 raise
156 except Exception as inst:
156 except Exception as inst:
157 if path:
157 if path:
158 ui.warn(_("*** failed to import extension %s from %s: %s\n")
158 ui.warn(_("*** failed to import extension %s from %s: %s\n")
159 % (name, path, inst))
159 % (name, path, inst))
160 else:
160 else:
161 ui.warn(_("*** failed to import extension %s: %s\n")
161 ui.warn(_("*** failed to import extension %s: %s\n")
162 % (name, inst))
162 % (name, inst))
163 ui.traceback()
163 ui.traceback()
164
164
165 for name in _order[newindex:]:
165 for name in _order[newindex:]:
166 _runuisetup(name, ui)
166 _runuisetup(name, ui)
167
167
168 for name in _order[newindex:]:
168 for name in _order[newindex:]:
169 _runextsetup(name, ui)
169 _runextsetup(name, ui)
170
170
171 # Call aftercallbacks that were never met.
171 # Call aftercallbacks that were never met.
172 for shortname in _aftercallbacks:
172 for shortname in _aftercallbacks:
173 if shortname in _extensions:
173 if shortname in _extensions:
174 continue
174 continue
175
175
176 for fn in _aftercallbacks[shortname]:
176 for fn in _aftercallbacks[shortname]:
177 fn(loaded=False)
177 fn(loaded=False)
178
178
179 # loadall() is called multiple times and lingering _aftercallbacks
179 # loadall() is called multiple times and lingering _aftercallbacks
180 # entries could result in double execution. See issue4646.
180 # entries could result in double execution. See issue4646.
181 _aftercallbacks.clear()
181 _aftercallbacks.clear()
182
182
183 def afterloaded(extension, callback):
183 def afterloaded(extension, callback):
184 '''Run the specified function after a named extension is loaded.
184 '''Run the specified function after a named extension is loaded.
185
185
186 If the named extension is already loaded, the callback will be called
186 If the named extension is already loaded, the callback will be called
187 immediately.
187 immediately.
188
188
189 If the named extension never loads, the callback will be called after
189 If the named extension never loads, the callback will be called after
190 all extensions have been loaded.
190 all extensions have been loaded.
191
191
192 The callback receives the named argument ``loaded``, which is a boolean
192 The callback receives the named argument ``loaded``, which is a boolean
193 indicating whether the dependent extension actually loaded.
193 indicating whether the dependent extension actually loaded.
194 '''
194 '''
195
195
196 if extension in _extensions:
196 if extension in _extensions:
197 callback(loaded=True)
197 callback(loaded=True)
198 else:
198 else:
199 _aftercallbacks.setdefault(extension, []).append(callback)
199 _aftercallbacks.setdefault(extension, []).append(callback)
200
200
201 def bind(func, *args):
201 def bind(func, *args):
202 '''Partial function application
202 '''Partial function application
203
203
204 Returns a new function that is the partial application of args and kwargs
204 Returns a new function that is the partial application of args and kwargs
205 to func. For example,
205 to func. For example,
206
206
207 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
207 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
208 assert callable(func)
208 assert callable(func)
209 def closure(*a, **kw):
209 def closure(*a, **kw):
210 return func(*(args + a), **kw)
210 return func(*(args + a), **kw)
211 return closure
211 return closure
212
212
213 def _updatewrapper(wrap, origfn):
213 def _updatewrapper(wrap, origfn, unboundwrapper):
214 '''Copy attributes to wrapper function'''
214 '''Copy and add some useful attributes to wrapper'''
215 wrap.__module__ = getattr(origfn, '__module__')
215 wrap.__module__ = getattr(origfn, '__module__')
216 wrap.__doc__ = getattr(origfn, '__doc__')
216 wrap.__doc__ = getattr(origfn, '__doc__')
217 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
217 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
218 wrap._origfunc = origfn
219 wrap._unboundwrapper = unboundwrapper
218
220
219 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
221 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
220 '''Wrap the command named `command' in table
222 '''Wrap the command named `command' in table
221
223
222 Replace command in the command table with wrapper. The wrapped command will
224 Replace command in the command table with wrapper. The wrapped command will
223 be inserted into the command table specified by the table argument.
225 be inserted into the command table specified by the table argument.
224
226
225 The wrapper will be called like
227 The wrapper will be called like
226
228
227 wrapper(orig, *args, **kwargs)
229 wrapper(orig, *args, **kwargs)
228
230
229 where orig is the original (wrapped) function, and *args, **kwargs
231 where orig is the original (wrapped) function, and *args, **kwargs
230 are the arguments passed to it.
232 are the arguments passed to it.
231
233
232 Optionally append to the command synopsis and docstring, used for help.
234 Optionally append to the command synopsis and docstring, used for help.
233 For example, if your extension wraps the ``bookmarks`` command to add the
235 For example, if your extension wraps the ``bookmarks`` command to add the
234 flags ``--remote`` and ``--all`` you might call this function like so:
236 flags ``--remote`` and ``--all`` you might call this function like so:
235
237
236 synopsis = ' [-a] [--remote]'
238 synopsis = ' [-a] [--remote]'
237 docstring = """
239 docstring = """
238
240
239 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
241 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
240 flags to the bookmarks command. Either flag will show the remote bookmarks
242 flags to the bookmarks command. Either flag will show the remote bookmarks
241 known to the repository; ``--remote`` will also suppress the output of the
243 known to the repository; ``--remote`` will also suppress the output of the
242 local bookmarks.
244 local bookmarks.
243 """
245 """
244
246
245 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
247 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
246 synopsis, docstring)
248 synopsis, docstring)
247 '''
249 '''
248 assert callable(wrapper)
250 assert callable(wrapper)
249 aliases, entry = cmdutil.findcmd(command, table)
251 aliases, entry = cmdutil.findcmd(command, table)
250 for alias, e in table.iteritems():
252 for alias, e in table.iteritems():
251 if e is entry:
253 if e is entry:
252 key = alias
254 key = alias
253 break
255 break
254
256
255 origfn = entry[0]
257 origfn = entry[0]
256 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
258 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
257 _updatewrapper(wrap, origfn)
259 _updatewrapper(wrap, origfn, wrapper)
258 if docstring is not None:
260 if docstring is not None:
259 wrap.__doc__ += docstring
261 wrap.__doc__ += docstring
260
262
261 newentry = list(entry)
263 newentry = list(entry)
262 newentry[0] = wrap
264 newentry[0] = wrap
263 if synopsis is not None:
265 if synopsis is not None:
264 newentry[2] += synopsis
266 newentry[2] += synopsis
265 table[key] = tuple(newentry)
267 table[key] = tuple(newentry)
266 return entry
268 return entry
267
269
268 def wrapfunction(container, funcname, wrapper):
270 def wrapfunction(container, funcname, wrapper):
269 '''Wrap the function named funcname in container
271 '''Wrap the function named funcname in container
270
272
271 Replace the funcname member in the given container with the specified
273 Replace the funcname member in the given container with the specified
272 wrapper. The container is typically a module, class, or instance.
274 wrapper. The container is typically a module, class, or instance.
273
275
274 The wrapper will be called like
276 The wrapper will be called like
275
277
276 wrapper(orig, *args, **kwargs)
278 wrapper(orig, *args, **kwargs)
277
279
278 where orig is the original (wrapped) function, and *args, **kwargs
280 where orig is the original (wrapped) function, and *args, **kwargs
279 are the arguments passed to it.
281 are the arguments passed to it.
280
282
281 Wrapping methods of the repository object is not recommended since
283 Wrapping methods of the repository object is not recommended since
282 it conflicts with extensions that extend the repository by
284 it conflicts with extensions that extend the repository by
283 subclassing. All extensions that need to extend methods of
285 subclassing. All extensions that need to extend methods of
284 localrepository should use this subclassing trick: namely,
286 localrepository should use this subclassing trick: namely,
285 reposetup() should look like
287 reposetup() should look like
286
288
287 def reposetup(ui, repo):
289 def reposetup(ui, repo):
288 class myrepo(repo.__class__):
290 class myrepo(repo.__class__):
289 def whatever(self, *args, **kwargs):
291 def whatever(self, *args, **kwargs):
290 [...extension stuff...]
292 [...extension stuff...]
291 super(myrepo, self).whatever(*args, **kwargs)
293 super(myrepo, self).whatever(*args, **kwargs)
292 [...extension stuff...]
294 [...extension stuff...]
293
295
294 repo.__class__ = myrepo
296 repo.__class__ = myrepo
295
297
296 In general, combining wrapfunction() with subclassing does not
298 In general, combining wrapfunction() with subclassing does not
297 work. Since you cannot control what other extensions are loaded by
299 work. Since you cannot control what other extensions are loaded by
298 your end users, you should play nicely with others by using the
300 your end users, you should play nicely with others by using the
299 subclass trick.
301 subclass trick.
300 '''
302 '''
301 assert callable(wrapper)
303 assert callable(wrapper)
302
304
303 origfn = getattr(container, funcname)
305 origfn = getattr(container, funcname)
304 assert callable(origfn)
306 assert callable(origfn)
305 wrap = bind(wrapper, origfn)
307 wrap = bind(wrapper, origfn)
306 _updatewrapper(wrap, origfn)
308 _updatewrapper(wrap, origfn, wrapper)
307 setattr(container, funcname, wrap)
309 setattr(container, funcname, wrap)
308 return origfn
310 return origfn
309
311
310 def _disabledpaths(strip_init=False):
312 def _disabledpaths(strip_init=False):
311 '''find paths of disabled extensions. returns a dict of {name: path}
313 '''find paths of disabled extensions. returns a dict of {name: path}
312 removes /__init__.py from packages if strip_init is True'''
314 removes /__init__.py from packages if strip_init is True'''
313 import hgext
315 import hgext
314 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
316 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
315 try: # might not be a filesystem path
317 try: # might not be a filesystem path
316 files = os.listdir(extpath)
318 files = os.listdir(extpath)
317 except OSError:
319 except OSError:
318 return {}
320 return {}
319
321
320 exts = {}
322 exts = {}
321 for e in files:
323 for e in files:
322 if e.endswith('.py'):
324 if e.endswith('.py'):
323 name = e.rsplit('.', 1)[0]
325 name = e.rsplit('.', 1)[0]
324 path = os.path.join(extpath, e)
326 path = os.path.join(extpath, e)
325 else:
327 else:
326 name = e
328 name = e
327 path = os.path.join(extpath, e, '__init__.py')
329 path = os.path.join(extpath, e, '__init__.py')
328 if not os.path.exists(path):
330 if not os.path.exists(path):
329 continue
331 continue
330 if strip_init:
332 if strip_init:
331 path = os.path.dirname(path)
333 path = os.path.dirname(path)
332 if name in exts or name in _order or name == '__init__':
334 if name in exts or name in _order or name == '__init__':
333 continue
335 continue
334 exts[name] = path
336 exts[name] = path
335 return exts
337 return exts
336
338
337 def _moduledoc(file):
339 def _moduledoc(file):
338 '''return the top-level python documentation for the given file
340 '''return the top-level python documentation for the given file
339
341
340 Loosely inspired by pydoc.source_synopsis(), but rewritten to
342 Loosely inspired by pydoc.source_synopsis(), but rewritten to
341 handle triple quotes and to return the whole text instead of just
343 handle triple quotes and to return the whole text instead of just
342 the synopsis'''
344 the synopsis'''
343 result = []
345 result = []
344
346
345 line = file.readline()
347 line = file.readline()
346 while line[:1] == '#' or not line.strip():
348 while line[:1] == '#' or not line.strip():
347 line = file.readline()
349 line = file.readline()
348 if not line:
350 if not line:
349 break
351 break
350
352
351 start = line[:3]
353 start = line[:3]
352 if start == '"""' or start == "'''":
354 if start == '"""' or start == "'''":
353 line = line[3:]
355 line = line[3:]
354 while line:
356 while line:
355 if line.rstrip().endswith(start):
357 if line.rstrip().endswith(start):
356 line = line.split(start)[0]
358 line = line.split(start)[0]
357 if line:
359 if line:
358 result.append(line)
360 result.append(line)
359 break
361 break
360 elif not line:
362 elif not line:
361 return None # unmatched delimiter
363 return None # unmatched delimiter
362 result.append(line)
364 result.append(line)
363 line = file.readline()
365 line = file.readline()
364 else:
366 else:
365 return None
367 return None
366
368
367 return ''.join(result)
369 return ''.join(result)
368
370
369 def _disabledhelp(path):
371 def _disabledhelp(path):
370 '''retrieve help synopsis of a disabled extension (without importing)'''
372 '''retrieve help synopsis of a disabled extension (without importing)'''
371 try:
373 try:
372 file = open(path)
374 file = open(path)
373 except IOError:
375 except IOError:
374 return
376 return
375 else:
377 else:
376 doc = _moduledoc(file)
378 doc = _moduledoc(file)
377 file.close()
379 file.close()
378
380
379 if doc: # extracting localized synopsis
381 if doc: # extracting localized synopsis
380 return gettext(doc).splitlines()[0]
382 return gettext(doc).splitlines()[0]
381 else:
383 else:
382 return _('(no help text available)')
384 return _('(no help text available)')
383
385
384 def disabled():
386 def disabled():
385 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
387 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
386 try:
388 try:
387 from hgext import __index__
389 from hgext import __index__
388 return dict((name, gettext(desc))
390 return dict((name, gettext(desc))
389 for name, desc in __index__.docs.iteritems()
391 for name, desc in __index__.docs.iteritems()
390 if name not in _order)
392 if name not in _order)
391 except (ImportError, AttributeError):
393 except (ImportError, AttributeError):
392 pass
394 pass
393
395
394 paths = _disabledpaths()
396 paths = _disabledpaths()
395 if not paths:
397 if not paths:
396 return {}
398 return {}
397
399
398 exts = {}
400 exts = {}
399 for name, path in paths.iteritems():
401 for name, path in paths.iteritems():
400 doc = _disabledhelp(path)
402 doc = _disabledhelp(path)
401 if doc:
403 if doc:
402 exts[name] = doc
404 exts[name] = doc
403
405
404 return exts
406 return exts
405
407
406 def disabledext(name):
408 def disabledext(name):
407 '''find a specific disabled extension from hgext. returns desc'''
409 '''find a specific disabled extension from hgext. returns desc'''
408 try:
410 try:
409 from hgext import __index__
411 from hgext import __index__
410 if name in _order: # enabled
412 if name in _order: # enabled
411 return
413 return
412 else:
414 else:
413 return gettext(__index__.docs.get(name))
415 return gettext(__index__.docs.get(name))
414 except (ImportError, AttributeError):
416 except (ImportError, AttributeError):
415 pass
417 pass
416
418
417 paths = _disabledpaths()
419 paths = _disabledpaths()
418 if name in paths:
420 if name in paths:
419 return _disabledhelp(paths[name])
421 return _disabledhelp(paths[name])
420
422
421 def disabledcmd(ui, cmd, strict=False):
423 def disabledcmd(ui, cmd, strict=False):
422 '''import disabled extensions until cmd is found.
424 '''import disabled extensions until cmd is found.
423 returns (cmdname, extname, module)'''
425 returns (cmdname, extname, module)'''
424
426
425 paths = _disabledpaths(strip_init=True)
427 paths = _disabledpaths(strip_init=True)
426 if not paths:
428 if not paths:
427 raise error.UnknownCommand(cmd)
429 raise error.UnknownCommand(cmd)
428
430
429 def findcmd(cmd, name, path):
431 def findcmd(cmd, name, path):
430 try:
432 try:
431 mod = loadpath(path, 'hgext.%s' % name)
433 mod = loadpath(path, 'hgext.%s' % name)
432 except Exception:
434 except Exception:
433 return
435 return
434 try:
436 try:
435 aliases, entry = cmdutil.findcmd(cmd,
437 aliases, entry = cmdutil.findcmd(cmd,
436 getattr(mod, 'cmdtable', {}), strict)
438 getattr(mod, 'cmdtable', {}), strict)
437 except (error.AmbiguousCommand, error.UnknownCommand):
439 except (error.AmbiguousCommand, error.UnknownCommand):
438 return
440 return
439 except Exception:
441 except Exception:
440 ui.warn(_('warning: error finding commands in %s\n') % path)
442 ui.warn(_('warning: error finding commands in %s\n') % path)
441 ui.traceback()
443 ui.traceback()
442 return
444 return
443 for c in aliases:
445 for c in aliases:
444 if c.startswith(cmd):
446 if c.startswith(cmd):
445 cmd = c
447 cmd = c
446 break
448 break
447 else:
449 else:
448 cmd = aliases[0]
450 cmd = aliases[0]
449 return (cmd, name, mod)
451 return (cmd, name, mod)
450
452
451 ext = None
453 ext = None
452 # first, search for an extension with the same name as the command
454 # first, search for an extension with the same name as the command
453 path = paths.pop(cmd, None)
455 path = paths.pop(cmd, None)
454 if path:
456 if path:
455 ext = findcmd(cmd, cmd, path)
457 ext = findcmd(cmd, cmd, path)
456 if not ext:
458 if not ext:
457 # otherwise, interrogate each extension until there's a match
459 # otherwise, interrogate each extension until there's a match
458 for name, path in paths.iteritems():
460 for name, path in paths.iteritems():
459 ext = findcmd(cmd, name, path)
461 ext = findcmd(cmd, name, path)
460 if ext:
462 if ext:
461 break
463 break
462 if ext and 'DEPRECATED' not in ext.__doc__:
464 if ext and 'DEPRECATED' not in ext.__doc__:
463 return ext
465 return ext
464
466
465 raise error.UnknownCommand(cmd)
467 raise error.UnknownCommand(cmd)
466
468
467 def enabled(shortname=True):
469 def enabled(shortname=True):
468 '''return a dict of {name: desc} of extensions'''
470 '''return a dict of {name: desc} of extensions'''
469 exts = {}
471 exts = {}
470 for ename, ext in extensions():
472 for ename, ext in extensions():
471 doc = (gettext(ext.__doc__) or _('(no help text available)'))
473 doc = (gettext(ext.__doc__) or _('(no help text available)'))
472 if shortname:
474 if shortname:
473 ename = ename.split('.')[-1]
475 ename = ename.split('.')[-1]
474 exts[ename] = doc.splitlines()[0].strip()
476 exts[ename] = doc.splitlines()[0].strip()
475
477
476 return exts
478 return exts
477
479
478 def notloaded():
480 def notloaded():
479 '''return short names of extensions that failed to load'''
481 '''return short names of extensions that failed to load'''
480 return [name for name, mod in _extensions.iteritems() if mod is None]
482 return [name for name, mod in _extensions.iteritems() if mod is None]
481
483
482 def moduleversion(module):
484 def moduleversion(module):
483 '''return version information from given module as a string'''
485 '''return version information from given module as a string'''
484 if (util.safehasattr(module, 'getversion')
486 if (util.safehasattr(module, 'getversion')
485 and callable(module.getversion)):
487 and callable(module.getversion)):
486 version = module.getversion()
488 version = module.getversion()
487 elif util.safehasattr(module, '__version__'):
489 elif util.safehasattr(module, '__version__'):
488 version = module.__version__
490 version = module.__version__
489 else:
491 else:
490 version = ''
492 version = ''
491 if isinstance(version, (list, tuple)):
493 if isinstance(version, (list, tuple)):
492 version = '.'.join(str(o) for o in version)
494 version = '.'.join(str(o) for o in version)
493 return version
495 return version
494
496
495 def ismoduleinternal(module):
497 def ismoduleinternal(module):
496 exttestedwith = getattr(module, 'testedwith', None)
498 exttestedwith = getattr(module, 'testedwith', None)
497 return exttestedwith == "internal"
499 return exttestedwith == "internal"
General Comments 0
You need to be logged in to leave comments. Login now