##// END OF EJS Templates
extensions: don't quit loading extensions in the middle if traceback is on...
Siddharth Agarwal -
r23953:af73c05e stable
parent child Browse files
Show More
@@ -1,382 +1,380 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 import imp, os
8 import imp, os
9 import util, cmdutil, error
9 import util, cmdutil, error
10 from i18n import _, gettext
10 from i18n import _, gettext
11
11
12 _extensions = {}
12 _extensions = {}
13 _order = []
13 _order = []
14 _ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
14 _ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
15
15
16 def extensions(ui=None):
16 def extensions(ui=None):
17 if ui:
17 if ui:
18 def enabled(name):
18 def enabled(name):
19 for format in ['%s', 'hgext.%s']:
19 for format in ['%s', 'hgext.%s']:
20 conf = ui.config('extensions', format % name)
20 conf = ui.config('extensions', format % name)
21 if conf is not None and not conf.startswith('!'):
21 if conf is not None and not conf.startswith('!'):
22 return True
22 return True
23 else:
23 else:
24 enabled = lambda name: True
24 enabled = lambda name: True
25 for name in _order:
25 for name in _order:
26 module = _extensions[name]
26 module = _extensions[name]
27 if module and enabled(name):
27 if module and enabled(name):
28 yield name, module
28 yield name, module
29
29
30 def find(name):
30 def find(name):
31 '''return module with given extension name'''
31 '''return module with given extension name'''
32 mod = None
32 mod = None
33 try:
33 try:
34 mod = _extensions[name]
34 mod = _extensions[name]
35 except KeyError:
35 except KeyError:
36 for k, v in _extensions.iteritems():
36 for k, v in _extensions.iteritems():
37 if k.endswith('.' + name) or k.endswith('/' + name):
37 if k.endswith('.' + name) or k.endswith('/' + name):
38 mod = v
38 mod = v
39 break
39 break
40 if not mod:
40 if not mod:
41 raise KeyError(name)
41 raise KeyError(name)
42 return mod
42 return mod
43
43
44 def loadpath(path, module_name):
44 def loadpath(path, module_name):
45 module_name = module_name.replace('.', '_')
45 module_name = module_name.replace('.', '_')
46 path = util.normpath(util.expandpath(path))
46 path = util.normpath(util.expandpath(path))
47 if os.path.isdir(path):
47 if os.path.isdir(path):
48 # module/__init__.py style
48 # module/__init__.py style
49 d, f = os.path.split(path)
49 d, f = os.path.split(path)
50 fd, fpath, desc = imp.find_module(f, [d])
50 fd, fpath, desc = imp.find_module(f, [d])
51 return imp.load_module(module_name, fd, fpath, desc)
51 return imp.load_module(module_name, fd, fpath, desc)
52 else:
52 else:
53 try:
53 try:
54 return imp.load_source(module_name, path)
54 return imp.load_source(module_name, path)
55 except IOError, exc:
55 except IOError, exc:
56 if not exc.filename:
56 if not exc.filename:
57 exc.filename = path # python does not fill this
57 exc.filename = path # python does not fill this
58 raise
58 raise
59
59
60 def load(ui, name, path):
60 def load(ui, name, path):
61 if name.startswith('hgext.') or name.startswith('hgext/'):
61 if name.startswith('hgext.') or name.startswith('hgext/'):
62 shortname = name[6:]
62 shortname = name[6:]
63 else:
63 else:
64 shortname = name
64 shortname = name
65 if shortname in _ignore:
65 if shortname in _ignore:
66 return None
66 return None
67 if shortname in _extensions:
67 if shortname in _extensions:
68 return _extensions[shortname]
68 return _extensions[shortname]
69 _extensions[shortname] = None
69 _extensions[shortname] = None
70 if path:
70 if path:
71 # the module will be loaded in sys.modules
71 # the module will be loaded in sys.modules
72 # choose an unique name so that it doesn't
72 # choose an unique name so that it doesn't
73 # conflicts with other modules
73 # conflicts with other modules
74 mod = loadpath(path, 'hgext.%s' % name)
74 mod = loadpath(path, 'hgext.%s' % name)
75 else:
75 else:
76 def importh(name):
76 def importh(name):
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 try:
82 try:
83 mod = importh("hgext.%s" % name)
83 mod = importh("hgext.%s" % name)
84 except ImportError, err:
84 except ImportError, err:
85 ui.debug('could not import hgext.%s (%s): trying %s\n'
85 ui.debug('could not import hgext.%s (%s): trying %s\n'
86 % (name, err, name))
86 % (name, err, name))
87 mod = importh(name)
87 mod = importh(name)
88 _extensions[shortname] = mod
88 _extensions[shortname] = mod
89 _order.append(shortname)
89 _order.append(shortname)
90 return mod
90 return mod
91
91
92 def loadall(ui):
92 def loadall(ui):
93 result = ui.configitems("extensions")
93 result = ui.configitems("extensions")
94 newindex = len(_order)
94 newindex = len(_order)
95 for (name, path) in result:
95 for (name, path) in result:
96 if path:
96 if path:
97 if path[0] == '!':
97 if path[0] == '!':
98 continue
98 continue
99 try:
99 try:
100 load(ui, name, path)
100 load(ui, name, path)
101 except KeyboardInterrupt:
101 except KeyboardInterrupt:
102 raise
102 raise
103 except Exception, inst:
103 except Exception, inst:
104 if path:
104 if path:
105 ui.warn(_("*** failed to import extension %s from %s: %s\n")
105 ui.warn(_("*** failed to import extension %s from %s: %s\n")
106 % (name, path, inst))
106 % (name, path, inst))
107 else:
107 else:
108 ui.warn(_("*** failed to import extension %s: %s\n")
108 ui.warn(_("*** failed to import extension %s: %s\n")
109 % (name, inst))
109 % (name, inst))
110 if ui.traceback():
111 return 1
112
110
113 for name in _order[newindex:]:
111 for name in _order[newindex:]:
114 uisetup = getattr(_extensions[name], 'uisetup', None)
112 uisetup = getattr(_extensions[name], 'uisetup', None)
115 if uisetup:
113 if uisetup:
116 uisetup(ui)
114 uisetup(ui)
117
115
118 for name in _order[newindex:]:
116 for name in _order[newindex:]:
119 extsetup = getattr(_extensions[name], 'extsetup', None)
117 extsetup = getattr(_extensions[name], 'extsetup', None)
120 if extsetup:
118 if extsetup:
121 try:
119 try:
122 extsetup(ui)
120 extsetup(ui)
123 except TypeError:
121 except TypeError:
124 if extsetup.func_code.co_argcount != 0:
122 if extsetup.func_code.co_argcount != 0:
125 raise
123 raise
126 extsetup() # old extsetup with no ui argument
124 extsetup() # old extsetup with no ui argument
127
125
128 def wrapcommand(table, command, wrapper):
126 def wrapcommand(table, command, wrapper):
129 '''Wrap the command named `command' in table
127 '''Wrap the command named `command' in table
130
128
131 Replace command in the command table with wrapper. The wrapped command will
129 Replace command in the command table with wrapper. The wrapped command will
132 be inserted into the command table specified by the table argument.
130 be inserted into the command table specified by the table argument.
133
131
134 The wrapper will be called like
132 The wrapper will be called like
135
133
136 wrapper(orig, *args, **kwargs)
134 wrapper(orig, *args, **kwargs)
137
135
138 where orig is the original (wrapped) function, and *args, **kwargs
136 where orig is the original (wrapped) function, and *args, **kwargs
139 are the arguments passed to it.
137 are the arguments passed to it.
140 '''
138 '''
141 assert callable(wrapper)
139 assert callable(wrapper)
142 aliases, entry = cmdutil.findcmd(command, table)
140 aliases, entry = cmdutil.findcmd(command, table)
143 for alias, e in table.iteritems():
141 for alias, e in table.iteritems():
144 if e is entry:
142 if e is entry:
145 key = alias
143 key = alias
146 break
144 break
147
145
148 origfn = entry[0]
146 origfn = entry[0]
149 def wrap(*args, **kwargs):
147 def wrap(*args, **kwargs):
150 return util.checksignature(wrapper)(
148 return util.checksignature(wrapper)(
151 util.checksignature(origfn), *args, **kwargs)
149 util.checksignature(origfn), *args, **kwargs)
152
150
153 wrap.__doc__ = getattr(origfn, '__doc__')
151 wrap.__doc__ = getattr(origfn, '__doc__')
154 wrap.__module__ = getattr(origfn, '__module__')
152 wrap.__module__ = getattr(origfn, '__module__')
155
153
156 newentry = list(entry)
154 newentry = list(entry)
157 newentry[0] = wrap
155 newentry[0] = wrap
158 table[key] = tuple(newentry)
156 table[key] = tuple(newentry)
159 return entry
157 return entry
160
158
161 def wrapfunction(container, funcname, wrapper):
159 def wrapfunction(container, funcname, wrapper):
162 '''Wrap the function named funcname in container
160 '''Wrap the function named funcname in container
163
161
164 Replace the funcname member in the given container with the specified
162 Replace the funcname member in the given container with the specified
165 wrapper. The container is typically a module, class, or instance.
163 wrapper. The container is typically a module, class, or instance.
166
164
167 The wrapper will be called like
165 The wrapper will be called like
168
166
169 wrapper(orig, *args, **kwargs)
167 wrapper(orig, *args, **kwargs)
170
168
171 where orig is the original (wrapped) function, and *args, **kwargs
169 where orig is the original (wrapped) function, and *args, **kwargs
172 are the arguments passed to it.
170 are the arguments passed to it.
173
171
174 Wrapping methods of the repository object is not recommended since
172 Wrapping methods of the repository object is not recommended since
175 it conflicts with extensions that extend the repository by
173 it conflicts with extensions that extend the repository by
176 subclassing. All extensions that need to extend methods of
174 subclassing. All extensions that need to extend methods of
177 localrepository should use this subclassing trick: namely,
175 localrepository should use this subclassing trick: namely,
178 reposetup() should look like
176 reposetup() should look like
179
177
180 def reposetup(ui, repo):
178 def reposetup(ui, repo):
181 class myrepo(repo.__class__):
179 class myrepo(repo.__class__):
182 def whatever(self, *args, **kwargs):
180 def whatever(self, *args, **kwargs):
183 [...extension stuff...]
181 [...extension stuff...]
184 super(myrepo, self).whatever(*args, **kwargs)
182 super(myrepo, self).whatever(*args, **kwargs)
185 [...extension stuff...]
183 [...extension stuff...]
186
184
187 repo.__class__ = myrepo
185 repo.__class__ = myrepo
188
186
189 In general, combining wrapfunction() with subclassing does not
187 In general, combining wrapfunction() with subclassing does not
190 work. Since you cannot control what other extensions are loaded by
188 work. Since you cannot control what other extensions are loaded by
191 your end users, you should play nicely with others by using the
189 your end users, you should play nicely with others by using the
192 subclass trick.
190 subclass trick.
193 '''
191 '''
194 assert callable(wrapper)
192 assert callable(wrapper)
195 def wrap(*args, **kwargs):
193 def wrap(*args, **kwargs):
196 return wrapper(origfn, *args, **kwargs)
194 return wrapper(origfn, *args, **kwargs)
197
195
198 origfn = getattr(container, funcname)
196 origfn = getattr(container, funcname)
199 assert callable(origfn)
197 assert callable(origfn)
200 setattr(container, funcname, wrap)
198 setattr(container, funcname, wrap)
201 return origfn
199 return origfn
202
200
203 def _disabledpaths(strip_init=False):
201 def _disabledpaths(strip_init=False):
204 '''find paths of disabled extensions. returns a dict of {name: path}
202 '''find paths of disabled extensions. returns a dict of {name: path}
205 removes /__init__.py from packages if strip_init is True'''
203 removes /__init__.py from packages if strip_init is True'''
206 import hgext
204 import hgext
207 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
205 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
208 try: # might not be a filesystem path
206 try: # might not be a filesystem path
209 files = os.listdir(extpath)
207 files = os.listdir(extpath)
210 except OSError:
208 except OSError:
211 return {}
209 return {}
212
210
213 exts = {}
211 exts = {}
214 for e in files:
212 for e in files:
215 if e.endswith('.py'):
213 if e.endswith('.py'):
216 name = e.rsplit('.', 1)[0]
214 name = e.rsplit('.', 1)[0]
217 path = os.path.join(extpath, e)
215 path = os.path.join(extpath, e)
218 else:
216 else:
219 name = e
217 name = e
220 path = os.path.join(extpath, e, '__init__.py')
218 path = os.path.join(extpath, e, '__init__.py')
221 if not os.path.exists(path):
219 if not os.path.exists(path):
222 continue
220 continue
223 if strip_init:
221 if strip_init:
224 path = os.path.dirname(path)
222 path = os.path.dirname(path)
225 if name in exts or name in _order or name == '__init__':
223 if name in exts or name in _order or name == '__init__':
226 continue
224 continue
227 exts[name] = path
225 exts[name] = path
228 return exts
226 return exts
229
227
230 def _moduledoc(file):
228 def _moduledoc(file):
231 '''return the top-level python documentation for the given file
229 '''return the top-level python documentation for the given file
232
230
233 Loosely inspired by pydoc.source_synopsis(), but rewritten to
231 Loosely inspired by pydoc.source_synopsis(), but rewritten to
234 handle triple quotes and to return the whole text instead of just
232 handle triple quotes and to return the whole text instead of just
235 the synopsis'''
233 the synopsis'''
236 result = []
234 result = []
237
235
238 line = file.readline()
236 line = file.readline()
239 while line[:1] == '#' or not line.strip():
237 while line[:1] == '#' or not line.strip():
240 line = file.readline()
238 line = file.readline()
241 if not line:
239 if not line:
242 break
240 break
243
241
244 start = line[:3]
242 start = line[:3]
245 if start == '"""' or start == "'''":
243 if start == '"""' or start == "'''":
246 line = line[3:]
244 line = line[3:]
247 while line:
245 while line:
248 if line.rstrip().endswith(start):
246 if line.rstrip().endswith(start):
249 line = line.split(start)[0]
247 line = line.split(start)[0]
250 if line:
248 if line:
251 result.append(line)
249 result.append(line)
252 break
250 break
253 elif not line:
251 elif not line:
254 return None # unmatched delimiter
252 return None # unmatched delimiter
255 result.append(line)
253 result.append(line)
256 line = file.readline()
254 line = file.readline()
257 else:
255 else:
258 return None
256 return None
259
257
260 return ''.join(result)
258 return ''.join(result)
261
259
262 def _disabledhelp(path):
260 def _disabledhelp(path):
263 '''retrieve help synopsis of a disabled extension (without importing)'''
261 '''retrieve help synopsis of a disabled extension (without importing)'''
264 try:
262 try:
265 file = open(path)
263 file = open(path)
266 except IOError:
264 except IOError:
267 return
265 return
268 else:
266 else:
269 doc = _moduledoc(file)
267 doc = _moduledoc(file)
270 file.close()
268 file.close()
271
269
272 if doc: # extracting localized synopsis
270 if doc: # extracting localized synopsis
273 return gettext(doc).splitlines()[0]
271 return gettext(doc).splitlines()[0]
274 else:
272 else:
275 return _('(no help text available)')
273 return _('(no help text available)')
276
274
277 def disabled():
275 def disabled():
278 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
276 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
279 try:
277 try:
280 from hgext import __index__
278 from hgext import __index__
281 return dict((name, gettext(desc))
279 return dict((name, gettext(desc))
282 for name, desc in __index__.docs.iteritems()
280 for name, desc in __index__.docs.iteritems()
283 if name not in _order)
281 if name not in _order)
284 except (ImportError, AttributeError):
282 except (ImportError, AttributeError):
285 pass
283 pass
286
284
287 paths = _disabledpaths()
285 paths = _disabledpaths()
288 if not paths:
286 if not paths:
289 return {}
287 return {}
290
288
291 exts = {}
289 exts = {}
292 for name, path in paths.iteritems():
290 for name, path in paths.iteritems():
293 doc = _disabledhelp(path)
291 doc = _disabledhelp(path)
294 if doc:
292 if doc:
295 exts[name] = doc
293 exts[name] = doc
296
294
297 return exts
295 return exts
298
296
299 def disabledext(name):
297 def disabledext(name):
300 '''find a specific disabled extension from hgext. returns desc'''
298 '''find a specific disabled extension from hgext. returns desc'''
301 try:
299 try:
302 from hgext import __index__
300 from hgext import __index__
303 if name in _order: # enabled
301 if name in _order: # enabled
304 return
302 return
305 else:
303 else:
306 return gettext(__index__.docs.get(name))
304 return gettext(__index__.docs.get(name))
307 except (ImportError, AttributeError):
305 except (ImportError, AttributeError):
308 pass
306 pass
309
307
310 paths = _disabledpaths()
308 paths = _disabledpaths()
311 if name in paths:
309 if name in paths:
312 return _disabledhelp(paths[name])
310 return _disabledhelp(paths[name])
313
311
314 def disabledcmd(ui, cmd, strict=False):
312 def disabledcmd(ui, cmd, strict=False):
315 '''import disabled extensions until cmd is found.
313 '''import disabled extensions until cmd is found.
316 returns (cmdname, extname, module)'''
314 returns (cmdname, extname, module)'''
317
315
318 paths = _disabledpaths(strip_init=True)
316 paths = _disabledpaths(strip_init=True)
319 if not paths:
317 if not paths:
320 raise error.UnknownCommand(cmd)
318 raise error.UnknownCommand(cmd)
321
319
322 def findcmd(cmd, name, path):
320 def findcmd(cmd, name, path):
323 try:
321 try:
324 mod = loadpath(path, 'hgext.%s' % name)
322 mod = loadpath(path, 'hgext.%s' % name)
325 except Exception:
323 except Exception:
326 return
324 return
327 try:
325 try:
328 aliases, entry = cmdutil.findcmd(cmd,
326 aliases, entry = cmdutil.findcmd(cmd,
329 getattr(mod, 'cmdtable', {}), strict)
327 getattr(mod, 'cmdtable', {}), strict)
330 except (error.AmbiguousCommand, error.UnknownCommand):
328 except (error.AmbiguousCommand, error.UnknownCommand):
331 return
329 return
332 except Exception:
330 except Exception:
333 ui.warn(_('warning: error finding commands in %s\n') % path)
331 ui.warn(_('warning: error finding commands in %s\n') % path)
334 ui.traceback()
332 ui.traceback()
335 return
333 return
336 for c in aliases:
334 for c in aliases:
337 if c.startswith(cmd):
335 if c.startswith(cmd):
338 cmd = c
336 cmd = c
339 break
337 break
340 else:
338 else:
341 cmd = aliases[0]
339 cmd = aliases[0]
342 return (cmd, name, mod)
340 return (cmd, name, mod)
343
341
344 ext = None
342 ext = None
345 # first, search for an extension with the same name as the command
343 # first, search for an extension with the same name as the command
346 path = paths.pop(cmd, None)
344 path = paths.pop(cmd, None)
347 if path:
345 if path:
348 ext = findcmd(cmd, cmd, path)
346 ext = findcmd(cmd, cmd, path)
349 if not ext:
347 if not ext:
350 # otherwise, interrogate each extension until there's a match
348 # otherwise, interrogate each extension until there's a match
351 for name, path in paths.iteritems():
349 for name, path in paths.iteritems():
352 ext = findcmd(cmd, name, path)
350 ext = findcmd(cmd, name, path)
353 if ext:
351 if ext:
354 break
352 break
355 if ext and 'DEPRECATED' not in ext.__doc__:
353 if ext and 'DEPRECATED' not in ext.__doc__:
356 return ext
354 return ext
357
355
358 raise error.UnknownCommand(cmd)
356 raise error.UnknownCommand(cmd)
359
357
360 def enabled(shortname=True):
358 def enabled(shortname=True):
361 '''return a dict of {name: desc} of extensions'''
359 '''return a dict of {name: desc} of extensions'''
362 exts = {}
360 exts = {}
363 for ename, ext in extensions():
361 for ename, ext in extensions():
364 doc = (gettext(ext.__doc__) or _('(no help text available)'))
362 doc = (gettext(ext.__doc__) or _('(no help text available)'))
365 if shortname:
363 if shortname:
366 ename = ename.split('.')[-1]
364 ename = ename.split('.')[-1]
367 exts[ename] = doc.splitlines()[0].strip()
365 exts[ename] = doc.splitlines()[0].strip()
368
366
369 return exts
367 return exts
370
368
371 def moduleversion(module):
369 def moduleversion(module):
372 '''return version information from given module as a string'''
370 '''return version information from given module as a string'''
373 if (util.safehasattr(module, 'getversion')
371 if (util.safehasattr(module, 'getversion')
374 and callable(module.getversion)):
372 and callable(module.getversion)):
375 version = module.getversion()
373 version = module.getversion()
376 elif util.safehasattr(module, '__version__'):
374 elif util.safehasattr(module, '__version__'):
377 version = module.__version__
375 version = module.__version__
378 else:
376 else:
379 version = ''
377 version = ''
380 if isinstance(version, (list, tuple)):
378 if isinstance(version, (list, tuple)):
381 version = '.'.join(str(o) for o in version)
379 version = '.'.join(str(o) for o in version)
382 return version
380 return version
General Comments 0
You need to be logged in to leave comments. Login now