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