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