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