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