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