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