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