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