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