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