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