##// END OF EJS Templates
extensions: hide two confusing import statements from pytype...
Augie Fackler -
r44104:1ea33dff default
parent child Browse files
Show More
@@ -1,942 +1,942 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 ast
10 import ast
11 import collections
11 import collections
12 import functools
12 import functools
13 import imp
13 import imp
14 import inspect
14 import inspect
15 import os
15 import os
16
16
17 from .i18n import (
17 from .i18n import (
18 _,
18 _,
19 gettext,
19 gettext,
20 )
20 )
21 from .pycompat import (
21 from .pycompat import (
22 getattr,
22 getattr,
23 open,
23 open,
24 setattr,
24 setattr,
25 )
25 )
26
26
27 from . import (
27 from . import (
28 cmdutil,
28 cmdutil,
29 configitems,
29 configitems,
30 error,
30 error,
31 pycompat,
31 pycompat,
32 util,
32 util,
33 )
33 )
34
34
35 from .utils import stringutil
35 from .utils import stringutil
36
36
37 _extensions = {}
37 _extensions = {}
38 _disabledextensions = {}
38 _disabledextensions = {}
39 _aftercallbacks = {}
39 _aftercallbacks = {}
40 _order = []
40 _order = []
41 _builtin = {
41 _builtin = {
42 b'hbisect',
42 b'hbisect',
43 b'bookmarks',
43 b'bookmarks',
44 b'color',
44 b'color',
45 b'parentrevspec',
45 b'parentrevspec',
46 b'progress',
46 b'progress',
47 b'interhg',
47 b'interhg',
48 b'inotify',
48 b'inotify',
49 b'hgcia',
49 b'hgcia',
50 b'shelve',
50 b'shelve',
51 }
51 }
52
52
53
53
54 def extensions(ui=None):
54 def extensions(ui=None):
55 if ui:
55 if ui:
56
56
57 def enabled(name):
57 def enabled(name):
58 for format in [b'%s', b'hgext.%s']:
58 for format in [b'%s', b'hgext.%s']:
59 conf = ui.config(b'extensions', format % name)
59 conf = ui.config(b'extensions', format % name)
60 if conf is not None and not conf.startswith(b'!'):
60 if conf is not None and not conf.startswith(b'!'):
61 return True
61 return True
62
62
63 else:
63 else:
64 enabled = lambda name: True
64 enabled = lambda name: True
65 for name in _order:
65 for name in _order:
66 module = _extensions[name]
66 module = _extensions[name]
67 if module and enabled(name):
67 if module and enabled(name):
68 yield name, module
68 yield name, module
69
69
70
70
71 def find(name):
71 def find(name):
72 '''return module with given extension name'''
72 '''return module with given extension name'''
73 mod = None
73 mod = None
74 try:
74 try:
75 mod = _extensions[name]
75 mod = _extensions[name]
76 except KeyError:
76 except KeyError:
77 for k, v in pycompat.iteritems(_extensions):
77 for k, v in pycompat.iteritems(_extensions):
78 if k.endswith(b'.' + name) or k.endswith(b'/' + name):
78 if k.endswith(b'.' + name) or k.endswith(b'/' + name):
79 mod = v
79 mod = v
80 break
80 break
81 if not mod:
81 if not mod:
82 raise KeyError(name)
82 raise KeyError(name)
83 return mod
83 return mod
84
84
85
85
86 def loadpath(path, module_name):
86 def loadpath(path, module_name):
87 module_name = module_name.replace(b'.', b'_')
87 module_name = module_name.replace(b'.', b'_')
88 path = util.normpath(util.expandpath(path))
88 path = util.normpath(util.expandpath(path))
89 module_name = pycompat.fsdecode(module_name)
89 module_name = pycompat.fsdecode(module_name)
90 path = pycompat.fsdecode(path)
90 path = pycompat.fsdecode(path)
91 if os.path.isdir(path):
91 if os.path.isdir(path):
92 # module/__init__.py style
92 # module/__init__.py style
93 d, f = os.path.split(path)
93 d, f = os.path.split(path)
94 fd, fpath, desc = imp.find_module(f, [d])
94 fd, fpath, desc = imp.find_module(f, [d])
95 return imp.load_module(module_name, fd, fpath, desc)
95 return imp.load_module(module_name, fd, fpath, desc)
96 else:
96 else:
97 try:
97 try:
98 return imp.load_source(module_name, path)
98 return imp.load_source(module_name, path)
99 except IOError as exc:
99 except IOError as exc:
100 if not exc.filename:
100 if not exc.filename:
101 exc.filename = path # python does not fill this
101 exc.filename = path # python does not fill this
102 raise
102 raise
103
103
104
104
105 def _importh(name):
105 def _importh(name):
106 """import and return the <name> module"""
106 """import and return the <name> module"""
107 mod = __import__(pycompat.sysstr(name))
107 mod = __import__(pycompat.sysstr(name))
108 components = name.split(b'.')
108 components = name.split(b'.')
109 for comp in components[1:]:
109 for comp in components[1:]:
110 mod = getattr(mod, comp)
110 mod = getattr(mod, comp)
111 return mod
111 return mod
112
112
113
113
114 def _importext(name, path=None, reportfunc=None):
114 def _importext(name, path=None, reportfunc=None):
115 if path:
115 if path:
116 # the module will be loaded in sys.modules
116 # the module will be loaded in sys.modules
117 # choose an unique name so that it doesn't
117 # choose an unique name so that it doesn't
118 # conflicts with other modules
118 # conflicts with other modules
119 mod = loadpath(path, b'hgext.%s' % name)
119 mod = loadpath(path, b'hgext.%s' % name)
120 else:
120 else:
121 try:
121 try:
122 mod = _importh(b"hgext.%s" % name)
122 mod = _importh(b"hgext.%s" % name)
123 except ImportError as err:
123 except ImportError as err:
124 if reportfunc:
124 if reportfunc:
125 reportfunc(err, b"hgext.%s" % name, b"hgext3rd.%s" % name)
125 reportfunc(err, b"hgext.%s" % name, b"hgext3rd.%s" % name)
126 try:
126 try:
127 mod = _importh(b"hgext3rd.%s" % name)
127 mod = _importh(b"hgext3rd.%s" % name)
128 except ImportError as err:
128 except ImportError as err:
129 if reportfunc:
129 if reportfunc:
130 reportfunc(err, b"hgext3rd.%s" % name, name)
130 reportfunc(err, b"hgext3rd.%s" % name, name)
131 mod = _importh(name)
131 mod = _importh(name)
132 return mod
132 return mod
133
133
134
134
135 def _reportimporterror(ui, err, failed, next):
135 def _reportimporterror(ui, err, failed, next):
136 # note: this ui.log happens before --debug is processed,
136 # note: this ui.log happens before --debug is processed,
137 # Use --config ui.debug=1 to see them.
137 # Use --config ui.debug=1 to see them.
138 ui.log(
138 ui.log(
139 b'extension',
139 b'extension',
140 b' - could not import %s (%s): trying %s\n',
140 b' - could not import %s (%s): trying %s\n',
141 failed,
141 failed,
142 stringutil.forcebytestr(err),
142 stringutil.forcebytestr(err),
143 next,
143 next,
144 )
144 )
145 if ui.debugflag and ui.configbool(b'devel', b'debug.extensions'):
145 if ui.debugflag and ui.configbool(b'devel', b'debug.extensions'):
146 ui.traceback()
146 ui.traceback()
147
147
148
148
149 def _rejectunicode(name, xs):
149 def _rejectunicode(name, xs):
150 if isinstance(xs, (list, set, tuple)):
150 if isinstance(xs, (list, set, tuple)):
151 for x in xs:
151 for x in xs:
152 _rejectunicode(name, x)
152 _rejectunicode(name, x)
153 elif isinstance(xs, dict):
153 elif isinstance(xs, dict):
154 for k, v in xs.items():
154 for k, v in xs.items():
155 _rejectunicode(name, k)
155 _rejectunicode(name, k)
156 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
156 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
157 elif isinstance(xs, type(u'')):
157 elif isinstance(xs, type(u'')):
158 raise error.ProgrammingError(
158 raise error.ProgrammingError(
159 b"unicode %r found in %s" % (xs, name),
159 b"unicode %r found in %s" % (xs, name),
160 hint=b"use b'' to make it byte string",
160 hint=b"use b'' to make it byte string",
161 )
161 )
162
162
163
163
164 # attributes set by registrar.command
164 # attributes set by registrar.command
165 _cmdfuncattrs = (b'norepo', b'optionalrepo', b'inferrepo')
165 _cmdfuncattrs = (b'norepo', b'optionalrepo', b'inferrepo')
166
166
167
167
168 def _validatecmdtable(ui, cmdtable):
168 def _validatecmdtable(ui, cmdtable):
169 """Check if extension commands have required attributes"""
169 """Check if extension commands have required attributes"""
170 for c, e in pycompat.iteritems(cmdtable):
170 for c, e in pycompat.iteritems(cmdtable):
171 f = e[0]
171 f = e[0]
172 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
172 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
173 if not missing:
173 if not missing:
174 continue
174 continue
175 raise error.ProgrammingError(
175 raise error.ProgrammingError(
176 b'missing attributes: %s' % b', '.join(missing),
176 b'missing attributes: %s' % b', '.join(missing),
177 hint=b"use @command decorator to register '%s'" % c,
177 hint=b"use @command decorator to register '%s'" % c,
178 )
178 )
179
179
180
180
181 def _validatetables(ui, mod):
181 def _validatetables(ui, mod):
182 """Sanity check for loadable tables provided by extension module"""
182 """Sanity check for loadable tables provided by extension module"""
183 for t in [b'cmdtable', b'colortable', b'configtable']:
183 for t in [b'cmdtable', b'colortable', b'configtable']:
184 _rejectunicode(t, getattr(mod, t, {}))
184 _rejectunicode(t, getattr(mod, t, {}))
185 for t in [
185 for t in [
186 b'filesetpredicate',
186 b'filesetpredicate',
187 b'internalmerge',
187 b'internalmerge',
188 b'revsetpredicate',
188 b'revsetpredicate',
189 b'templatefilter',
189 b'templatefilter',
190 b'templatefunc',
190 b'templatefunc',
191 b'templatekeyword',
191 b'templatekeyword',
192 ]:
192 ]:
193 o = getattr(mod, t, None)
193 o = getattr(mod, t, None)
194 if o:
194 if o:
195 _rejectunicode(t, o._table)
195 _rejectunicode(t, o._table)
196 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
196 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
197
197
198
198
199 def load(ui, name, path, loadingtime=None):
199 def load(ui, name, path, loadingtime=None):
200 if name.startswith(b'hgext.') or name.startswith(b'hgext/'):
200 if name.startswith(b'hgext.') or name.startswith(b'hgext/'):
201 shortname = name[6:]
201 shortname = name[6:]
202 else:
202 else:
203 shortname = name
203 shortname = name
204 if shortname in _builtin:
204 if shortname in _builtin:
205 return None
205 return None
206 if shortname in _extensions:
206 if shortname in _extensions:
207 return _extensions[shortname]
207 return _extensions[shortname]
208 ui.log(b'extension', b' - loading extension: %s\n', shortname)
208 ui.log(b'extension', b' - loading extension: %s\n', shortname)
209 _extensions[shortname] = None
209 _extensions[shortname] = None
210 with util.timedcm('load extension %s', shortname) as stats:
210 with util.timedcm('load extension %s', shortname) as stats:
211 mod = _importext(name, path, bind(_reportimporterror, ui))
211 mod = _importext(name, path, bind(_reportimporterror, ui))
212 ui.log(b'extension', b' > %s extension loaded in %s\n', shortname, stats)
212 ui.log(b'extension', b' > %s extension loaded in %s\n', shortname, stats)
213 if loadingtime is not None:
213 if loadingtime is not None:
214 loadingtime[shortname] += stats.elapsed
214 loadingtime[shortname] += stats.elapsed
215
215
216 # Before we do anything with the extension, check against minimum stated
216 # Before we do anything with the extension, check against minimum stated
217 # compatibility. This gives extension authors a mechanism to have their
217 # compatibility. This gives extension authors a mechanism to have their
218 # extensions short circuit when loaded with a known incompatible version
218 # extensions short circuit when loaded with a known incompatible version
219 # of Mercurial.
219 # of Mercurial.
220 minver = getattr(mod, 'minimumhgversion', None)
220 minver = getattr(mod, 'minimumhgversion', None)
221 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
221 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
222 msg = _(
222 msg = _(
223 b'(third party extension %s requires version %s or newer '
223 b'(third party extension %s requires version %s or newer '
224 b'of Mercurial (current: %s); disabling)\n'
224 b'of Mercurial (current: %s); disabling)\n'
225 )
225 )
226 ui.warn(msg % (shortname, minver, util.version()))
226 ui.warn(msg % (shortname, minver, util.version()))
227 return
227 return
228 ui.log(b'extension', b' - validating extension tables: %s\n', shortname)
228 ui.log(b'extension', b' - validating extension tables: %s\n', shortname)
229 _validatetables(ui, mod)
229 _validatetables(ui, mod)
230
230
231 _extensions[shortname] = mod
231 _extensions[shortname] = mod
232 _order.append(shortname)
232 _order.append(shortname)
233 ui.log(
233 ui.log(
234 b'extension', b' - invoking registered callbacks: %s\n', shortname
234 b'extension', b' - invoking registered callbacks: %s\n', shortname
235 )
235 )
236 with util.timedcm('callbacks extension %s', shortname) as stats:
236 with util.timedcm('callbacks extension %s', shortname) as stats:
237 for fn in _aftercallbacks.get(shortname, []):
237 for fn in _aftercallbacks.get(shortname, []):
238 fn(loaded=True)
238 fn(loaded=True)
239 ui.log(b'extension', b' > callbacks completed in %s\n', stats)
239 ui.log(b'extension', b' > callbacks completed in %s\n', stats)
240 return mod
240 return mod
241
241
242
242
243 def _runuisetup(name, ui):
243 def _runuisetup(name, ui):
244 uisetup = getattr(_extensions[name], 'uisetup', None)
244 uisetup = getattr(_extensions[name], 'uisetup', None)
245 if uisetup:
245 if uisetup:
246 try:
246 try:
247 uisetup(ui)
247 uisetup(ui)
248 except Exception as inst:
248 except Exception as inst:
249 ui.traceback(force=True)
249 ui.traceback(force=True)
250 msg = stringutil.forcebytestr(inst)
250 msg = stringutil.forcebytestr(inst)
251 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
251 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
252 return False
252 return False
253 return True
253 return True
254
254
255
255
256 def _runextsetup(name, ui):
256 def _runextsetup(name, ui):
257 extsetup = getattr(_extensions[name], 'extsetup', None)
257 extsetup = getattr(_extensions[name], 'extsetup', None)
258 if extsetup:
258 if extsetup:
259 try:
259 try:
260 extsetup(ui)
260 extsetup(ui)
261 except Exception as inst:
261 except Exception as inst:
262 ui.traceback(force=True)
262 ui.traceback(force=True)
263 msg = stringutil.forcebytestr(inst)
263 msg = stringutil.forcebytestr(inst)
264 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
264 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
265 return False
265 return False
266 return True
266 return True
267
267
268
268
269 def loadall(ui, whitelist=None):
269 def loadall(ui, whitelist=None):
270 loadingtime = collections.defaultdict(int)
270 loadingtime = collections.defaultdict(int)
271 result = ui.configitems(b"extensions")
271 result = ui.configitems(b"extensions")
272 if whitelist is not None:
272 if whitelist is not None:
273 result = [(k, v) for (k, v) in result if k in whitelist]
273 result = [(k, v) for (k, v) in result if k in whitelist]
274 newindex = len(_order)
274 newindex = len(_order)
275 ui.log(
275 ui.log(
276 b'extension',
276 b'extension',
277 b'loading %sextensions\n',
277 b'loading %sextensions\n',
278 b'additional ' if newindex else b'',
278 b'additional ' if newindex else b'',
279 )
279 )
280 ui.log(b'extension', b'- processing %d entries\n', len(result))
280 ui.log(b'extension', b'- processing %d entries\n', len(result))
281 with util.timedcm('load all extensions') as stats:
281 with util.timedcm('load all extensions') as stats:
282 for (name, path) in result:
282 for (name, path) in result:
283 if path:
283 if path:
284 if path[0:1] == b'!':
284 if path[0:1] == b'!':
285 if name not in _disabledextensions:
285 if name not in _disabledextensions:
286 ui.log(
286 ui.log(
287 b'extension',
287 b'extension',
288 b' - skipping disabled extension: %s\n',
288 b' - skipping disabled extension: %s\n',
289 name,
289 name,
290 )
290 )
291 _disabledextensions[name] = path[1:]
291 _disabledextensions[name] = path[1:]
292 continue
292 continue
293 try:
293 try:
294 load(ui, name, path, loadingtime)
294 load(ui, name, path, loadingtime)
295 except Exception as inst:
295 except Exception as inst:
296 msg = stringutil.forcebytestr(inst)
296 msg = stringutil.forcebytestr(inst)
297 if path:
297 if path:
298 ui.warn(
298 ui.warn(
299 _(b"*** failed to import extension %s from %s: %s\n")
299 _(b"*** failed to import extension %s from %s: %s\n")
300 % (name, path, msg)
300 % (name, path, msg)
301 )
301 )
302 else:
302 else:
303 ui.warn(
303 ui.warn(
304 _(b"*** failed to import extension %s: %s\n")
304 _(b"*** failed to import extension %s: %s\n")
305 % (name, msg)
305 % (name, msg)
306 )
306 )
307 if isinstance(inst, error.Hint) and inst.hint:
307 if isinstance(inst, error.Hint) and inst.hint:
308 ui.warn(_(b"*** (%s)\n") % inst.hint)
308 ui.warn(_(b"*** (%s)\n") % inst.hint)
309 ui.traceback()
309 ui.traceback()
310
310
311 ui.log(
311 ui.log(
312 b'extension',
312 b'extension',
313 b'> loaded %d extensions, total time %s\n',
313 b'> loaded %d extensions, total time %s\n',
314 len(_order) - newindex,
314 len(_order) - newindex,
315 stats,
315 stats,
316 )
316 )
317 # list of (objname, loadermod, loadername) tuple:
317 # list of (objname, loadermod, loadername) tuple:
318 # - objname is the name of an object in extension module,
318 # - objname is the name of an object in extension module,
319 # from which extra information is loaded
319 # from which extra information is loaded
320 # - loadermod is the module where loader is placed
320 # - loadermod is the module where loader is placed
321 # - loadername is the name of the function,
321 # - loadername is the name of the function,
322 # which takes (ui, extensionname, extraobj) arguments
322 # which takes (ui, extensionname, extraobj) arguments
323 #
323 #
324 # This one is for the list of item that must be run before running any setup
324 # This one is for the list of item that must be run before running any setup
325 earlyextraloaders = [
325 earlyextraloaders = [
326 (b'configtable', configitems, b'loadconfigtable'),
326 (b'configtable', configitems, b'loadconfigtable'),
327 ]
327 ]
328
328
329 ui.log(b'extension', b'- loading configtable attributes\n')
329 ui.log(b'extension', b'- loading configtable attributes\n')
330 _loadextra(ui, newindex, earlyextraloaders)
330 _loadextra(ui, newindex, earlyextraloaders)
331
331
332 broken = set()
332 broken = set()
333 ui.log(b'extension', b'- executing uisetup hooks\n')
333 ui.log(b'extension', b'- executing uisetup hooks\n')
334 with util.timedcm('all uisetup') as alluisetupstats:
334 with util.timedcm('all uisetup') as alluisetupstats:
335 for name in _order[newindex:]:
335 for name in _order[newindex:]:
336 ui.log(b'extension', b' - running uisetup for %s\n', name)
336 ui.log(b'extension', b' - running uisetup for %s\n', name)
337 with util.timedcm('uisetup %s', name) as stats:
337 with util.timedcm('uisetup %s', name) as stats:
338 if not _runuisetup(name, ui):
338 if not _runuisetup(name, ui):
339 ui.log(
339 ui.log(
340 b'extension',
340 b'extension',
341 b' - the %s extension uisetup failed\n',
341 b' - the %s extension uisetup failed\n',
342 name,
342 name,
343 )
343 )
344 broken.add(name)
344 broken.add(name)
345 ui.log(b'extension', b' > uisetup for %s took %s\n', name, stats)
345 ui.log(b'extension', b' > uisetup for %s took %s\n', name, stats)
346 loadingtime[name] += stats.elapsed
346 loadingtime[name] += stats.elapsed
347 ui.log(b'extension', b'> all uisetup took %s\n', alluisetupstats)
347 ui.log(b'extension', b'> all uisetup took %s\n', alluisetupstats)
348
348
349 ui.log(b'extension', b'- executing extsetup hooks\n')
349 ui.log(b'extension', b'- executing extsetup hooks\n')
350 with util.timedcm('all extsetup') as allextetupstats:
350 with util.timedcm('all extsetup') as allextetupstats:
351 for name in _order[newindex:]:
351 for name in _order[newindex:]:
352 if name in broken:
352 if name in broken:
353 continue
353 continue
354 ui.log(b'extension', b' - running extsetup for %s\n', name)
354 ui.log(b'extension', b' - running extsetup for %s\n', name)
355 with util.timedcm('extsetup %s', name) as stats:
355 with util.timedcm('extsetup %s', name) as stats:
356 if not _runextsetup(name, ui):
356 if not _runextsetup(name, ui):
357 ui.log(
357 ui.log(
358 b'extension',
358 b'extension',
359 b' - the %s extension extsetup failed\n',
359 b' - the %s extension extsetup failed\n',
360 name,
360 name,
361 )
361 )
362 broken.add(name)
362 broken.add(name)
363 ui.log(b'extension', b' > extsetup for %s took %s\n', name, stats)
363 ui.log(b'extension', b' > extsetup for %s took %s\n', name, stats)
364 loadingtime[name] += stats.elapsed
364 loadingtime[name] += stats.elapsed
365 ui.log(b'extension', b'> all extsetup took %s\n', allextetupstats)
365 ui.log(b'extension', b'> all extsetup took %s\n', allextetupstats)
366
366
367 for name in broken:
367 for name in broken:
368 ui.log(b'extension', b' - disabling broken %s extension\n', name)
368 ui.log(b'extension', b' - disabling broken %s extension\n', name)
369 _extensions[name] = None
369 _extensions[name] = None
370
370
371 # Call aftercallbacks that were never met.
371 # Call aftercallbacks that were never met.
372 ui.log(b'extension', b'- executing remaining aftercallbacks\n')
372 ui.log(b'extension', b'- executing remaining aftercallbacks\n')
373 with util.timedcm('aftercallbacks') as stats:
373 with util.timedcm('aftercallbacks') as stats:
374 for shortname in _aftercallbacks:
374 for shortname in _aftercallbacks:
375 if shortname in _extensions:
375 if shortname in _extensions:
376 continue
376 continue
377
377
378 for fn in _aftercallbacks[shortname]:
378 for fn in _aftercallbacks[shortname]:
379 ui.log(
379 ui.log(
380 b'extension',
380 b'extension',
381 b' - extension %s not loaded, notify callbacks\n',
381 b' - extension %s not loaded, notify callbacks\n',
382 shortname,
382 shortname,
383 )
383 )
384 fn(loaded=False)
384 fn(loaded=False)
385 ui.log(b'extension', b'> remaining aftercallbacks completed in %s\n', stats)
385 ui.log(b'extension', b'> remaining aftercallbacks completed in %s\n', stats)
386
386
387 # loadall() is called multiple times and lingering _aftercallbacks
387 # loadall() is called multiple times and lingering _aftercallbacks
388 # entries could result in double execution. See issue4646.
388 # entries could result in double execution. See issue4646.
389 _aftercallbacks.clear()
389 _aftercallbacks.clear()
390
390
391 # delay importing avoids cyclic dependency (especially commands)
391 # delay importing avoids cyclic dependency (especially commands)
392 from . import (
392 from . import (
393 color,
393 color,
394 commands,
394 commands,
395 filemerge,
395 filemerge,
396 fileset,
396 fileset,
397 revset,
397 revset,
398 templatefilters,
398 templatefilters,
399 templatefuncs,
399 templatefuncs,
400 templatekw,
400 templatekw,
401 )
401 )
402
402
403 # list of (objname, loadermod, loadername) tuple:
403 # list of (objname, loadermod, loadername) tuple:
404 # - objname is the name of an object in extension module,
404 # - objname is the name of an object in extension module,
405 # from which extra information is loaded
405 # from which extra information is loaded
406 # - loadermod is the module where loader is placed
406 # - loadermod is the module where loader is placed
407 # - loadername is the name of the function,
407 # - loadername is the name of the function,
408 # which takes (ui, extensionname, extraobj) arguments
408 # which takes (ui, extensionname, extraobj) arguments
409 ui.log(b'extension', b'- loading extension registration objects\n')
409 ui.log(b'extension', b'- loading extension registration objects\n')
410 extraloaders = [
410 extraloaders = [
411 (b'cmdtable', commands, b'loadcmdtable'),
411 (b'cmdtable', commands, b'loadcmdtable'),
412 (b'colortable', color, b'loadcolortable'),
412 (b'colortable', color, b'loadcolortable'),
413 (b'filesetpredicate', fileset, b'loadpredicate'),
413 (b'filesetpredicate', fileset, b'loadpredicate'),
414 (b'internalmerge', filemerge, b'loadinternalmerge'),
414 (b'internalmerge', filemerge, b'loadinternalmerge'),
415 (b'revsetpredicate', revset, b'loadpredicate'),
415 (b'revsetpredicate', revset, b'loadpredicate'),
416 (b'templatefilter', templatefilters, b'loadfilter'),
416 (b'templatefilter', templatefilters, b'loadfilter'),
417 (b'templatefunc', templatefuncs, b'loadfunction'),
417 (b'templatefunc', templatefuncs, b'loadfunction'),
418 (b'templatekeyword', templatekw, b'loadkeyword'),
418 (b'templatekeyword', templatekw, b'loadkeyword'),
419 ]
419 ]
420 with util.timedcm('load registration objects') as stats:
420 with util.timedcm('load registration objects') as stats:
421 _loadextra(ui, newindex, extraloaders)
421 _loadextra(ui, newindex, extraloaders)
422 ui.log(
422 ui.log(
423 b'extension',
423 b'extension',
424 b'> extension registration object loading took %s\n',
424 b'> extension registration object loading took %s\n',
425 stats,
425 stats,
426 )
426 )
427
427
428 # Report per extension loading time (except reposetup)
428 # Report per extension loading time (except reposetup)
429 for name in sorted(loadingtime):
429 for name in sorted(loadingtime):
430 ui.log(
430 ui.log(
431 b'extension',
431 b'extension',
432 b'> extension %s take a total of %s to load\n',
432 b'> extension %s take a total of %s to load\n',
433 name,
433 name,
434 util.timecount(loadingtime[name]),
434 util.timecount(loadingtime[name]),
435 )
435 )
436
436
437 ui.log(b'extension', b'extension loading complete\n')
437 ui.log(b'extension', b'extension loading complete\n')
438
438
439
439
440 def _loadextra(ui, newindex, extraloaders):
440 def _loadextra(ui, newindex, extraloaders):
441 for name in _order[newindex:]:
441 for name in _order[newindex:]:
442 module = _extensions[name]
442 module = _extensions[name]
443 if not module:
443 if not module:
444 continue # loading this module failed
444 continue # loading this module failed
445
445
446 for objname, loadermod, loadername in extraloaders:
446 for objname, loadermod, loadername in extraloaders:
447 extraobj = getattr(module, objname, None)
447 extraobj = getattr(module, objname, None)
448 if extraobj is not None:
448 if extraobj is not None:
449 getattr(loadermod, loadername)(ui, name, extraobj)
449 getattr(loadermod, loadername)(ui, name, extraobj)
450
450
451
451
452 def afterloaded(extension, callback):
452 def afterloaded(extension, callback):
453 '''Run the specified function after a named extension is loaded.
453 '''Run the specified function after a named extension is loaded.
454
454
455 If the named extension is already loaded, the callback will be called
455 If the named extension is already loaded, the callback will be called
456 immediately.
456 immediately.
457
457
458 If the named extension never loads, the callback will be called after
458 If the named extension never loads, the callback will be called after
459 all extensions have been loaded.
459 all extensions have been loaded.
460
460
461 The callback receives the named argument ``loaded``, which is a boolean
461 The callback receives the named argument ``loaded``, which is a boolean
462 indicating whether the dependent extension actually loaded.
462 indicating whether the dependent extension actually loaded.
463 '''
463 '''
464
464
465 if extension in _extensions:
465 if extension in _extensions:
466 # Report loaded as False if the extension is disabled
466 # Report loaded as False if the extension is disabled
467 loaded = _extensions[extension] is not None
467 loaded = _extensions[extension] is not None
468 callback(loaded=loaded)
468 callback(loaded=loaded)
469 else:
469 else:
470 _aftercallbacks.setdefault(extension, []).append(callback)
470 _aftercallbacks.setdefault(extension, []).append(callback)
471
471
472
472
473 def populateui(ui):
473 def populateui(ui):
474 """Run extension hooks on the given ui to populate additional members,
474 """Run extension hooks on the given ui to populate additional members,
475 extend the class dynamically, etc.
475 extend the class dynamically, etc.
476
476
477 This will be called after the configuration is loaded, and/or extensions
477 This will be called after the configuration is loaded, and/or extensions
478 are loaded. In general, it's once per ui instance, but in command-server
478 are loaded. In general, it's once per ui instance, but in command-server
479 and hgweb, this may be called more than once with the same ui.
479 and hgweb, this may be called more than once with the same ui.
480 """
480 """
481 for name, mod in extensions(ui):
481 for name, mod in extensions(ui):
482 hook = getattr(mod, 'uipopulate', None)
482 hook = getattr(mod, 'uipopulate', None)
483 if not hook:
483 if not hook:
484 continue
484 continue
485 try:
485 try:
486 hook(ui)
486 hook(ui)
487 except Exception as inst:
487 except Exception as inst:
488 ui.traceback(force=True)
488 ui.traceback(force=True)
489 ui.warn(
489 ui.warn(
490 _(b'*** failed to populate ui by extension %s: %s\n')
490 _(b'*** failed to populate ui by extension %s: %s\n')
491 % (name, stringutil.forcebytestr(inst))
491 % (name, stringutil.forcebytestr(inst))
492 )
492 )
493
493
494
494
495 def bind(func, *args):
495 def bind(func, *args):
496 '''Partial function application
496 '''Partial function application
497
497
498 Returns a new function that is the partial application of args and kwargs
498 Returns a new function that is the partial application of args and kwargs
499 to func. For example,
499 to func. For example,
500
500
501 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
501 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
502 assert callable(func)
502 assert callable(func)
503
503
504 def closure(*a, **kw):
504 def closure(*a, **kw):
505 return func(*(args + a), **kw)
505 return func(*(args + a), **kw)
506
506
507 return closure
507 return closure
508
508
509
509
510 def _updatewrapper(wrap, origfn, unboundwrapper):
510 def _updatewrapper(wrap, origfn, unboundwrapper):
511 '''Copy and add some useful attributes to wrapper'''
511 '''Copy and add some useful attributes to wrapper'''
512 try:
512 try:
513 wrap.__name__ = origfn.__name__
513 wrap.__name__ = origfn.__name__
514 except AttributeError:
514 except AttributeError:
515 pass
515 pass
516 wrap.__module__ = getattr(origfn, '__module__')
516 wrap.__module__ = getattr(origfn, '__module__')
517 wrap.__doc__ = getattr(origfn, '__doc__')
517 wrap.__doc__ = getattr(origfn, '__doc__')
518 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
518 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
519 wrap._origfunc = origfn
519 wrap._origfunc = origfn
520 wrap._unboundwrapper = unboundwrapper
520 wrap._unboundwrapper = unboundwrapper
521
521
522
522
523 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
523 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
524 '''Wrap the command named `command' in table
524 '''Wrap the command named `command' in table
525
525
526 Replace command in the command table with wrapper. The wrapped command will
526 Replace command in the command table with wrapper. The wrapped command will
527 be inserted into the command table specified by the table argument.
527 be inserted into the command table specified by the table argument.
528
528
529 The wrapper will be called like
529 The wrapper will be called like
530
530
531 wrapper(orig, *args, **kwargs)
531 wrapper(orig, *args, **kwargs)
532
532
533 where orig is the original (wrapped) function, and *args, **kwargs
533 where orig is the original (wrapped) function, and *args, **kwargs
534 are the arguments passed to it.
534 are the arguments passed to it.
535
535
536 Optionally append to the command synopsis and docstring, used for help.
536 Optionally append to the command synopsis and docstring, used for help.
537 For example, if your extension wraps the ``bookmarks`` command to add the
537 For example, if your extension wraps the ``bookmarks`` command to add the
538 flags ``--remote`` and ``--all`` you might call this function like so:
538 flags ``--remote`` and ``--all`` you might call this function like so:
539
539
540 synopsis = ' [-a] [--remote]'
540 synopsis = ' [-a] [--remote]'
541 docstring = """
541 docstring = """
542
542
543 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
543 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
544 flags to the bookmarks command. Either flag will show the remote bookmarks
544 flags to the bookmarks command. Either flag will show the remote bookmarks
545 known to the repository; ``--remote`` will also suppress the output of the
545 known to the repository; ``--remote`` will also suppress the output of the
546 local bookmarks.
546 local bookmarks.
547 """
547 """
548
548
549 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
549 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
550 synopsis, docstring)
550 synopsis, docstring)
551 '''
551 '''
552 assert callable(wrapper)
552 assert callable(wrapper)
553 aliases, entry = cmdutil.findcmd(command, table)
553 aliases, entry = cmdutil.findcmd(command, table)
554 for alias, e in pycompat.iteritems(table):
554 for alias, e in pycompat.iteritems(table):
555 if e is entry:
555 if e is entry:
556 key = alias
556 key = alias
557 break
557 break
558
558
559 origfn = entry[0]
559 origfn = entry[0]
560 wrap = functools.partial(
560 wrap = functools.partial(
561 util.checksignature(wrapper), util.checksignature(origfn)
561 util.checksignature(wrapper), util.checksignature(origfn)
562 )
562 )
563 _updatewrapper(wrap, origfn, wrapper)
563 _updatewrapper(wrap, origfn, wrapper)
564 if docstring is not None:
564 if docstring is not None:
565 wrap.__doc__ += docstring
565 wrap.__doc__ += docstring
566
566
567 newentry = list(entry)
567 newentry = list(entry)
568 newentry[0] = wrap
568 newentry[0] = wrap
569 if synopsis is not None:
569 if synopsis is not None:
570 newentry[2] += synopsis
570 newentry[2] += synopsis
571 table[key] = tuple(newentry)
571 table[key] = tuple(newentry)
572 return entry
572 return entry
573
573
574
574
575 def wrapfilecache(cls, propname, wrapper):
575 def wrapfilecache(cls, propname, wrapper):
576 """Wraps a filecache property.
576 """Wraps a filecache property.
577
577
578 These can't be wrapped using the normal wrapfunction.
578 These can't be wrapped using the normal wrapfunction.
579 """
579 """
580 propname = pycompat.sysstr(propname)
580 propname = pycompat.sysstr(propname)
581 assert callable(wrapper)
581 assert callable(wrapper)
582 for currcls in cls.__mro__:
582 for currcls in cls.__mro__:
583 if propname in currcls.__dict__:
583 if propname in currcls.__dict__:
584 origfn = currcls.__dict__[propname].func
584 origfn = currcls.__dict__[propname].func
585 assert callable(origfn)
585 assert callable(origfn)
586
586
587 def wrap(*args, **kwargs):
587 def wrap(*args, **kwargs):
588 return wrapper(origfn, *args, **kwargs)
588 return wrapper(origfn, *args, **kwargs)
589
589
590 currcls.__dict__[propname].func = wrap
590 currcls.__dict__[propname].func = wrap
591 break
591 break
592
592
593 if currcls is object:
593 if currcls is object:
594 raise AttributeError("type '%s' has no property '%s'" % (cls, propname))
594 raise AttributeError("type '%s' has no property '%s'" % (cls, propname))
595
595
596
596
597 class wrappedfunction(object):
597 class wrappedfunction(object):
598 '''context manager for temporarily wrapping a function'''
598 '''context manager for temporarily wrapping a function'''
599
599
600 def __init__(self, container, funcname, wrapper):
600 def __init__(self, container, funcname, wrapper):
601 assert callable(wrapper)
601 assert callable(wrapper)
602 self._container = container
602 self._container = container
603 self._funcname = funcname
603 self._funcname = funcname
604 self._wrapper = wrapper
604 self._wrapper = wrapper
605
605
606 def __enter__(self):
606 def __enter__(self):
607 wrapfunction(self._container, self._funcname, self._wrapper)
607 wrapfunction(self._container, self._funcname, self._wrapper)
608
608
609 def __exit__(self, exctype, excvalue, traceback):
609 def __exit__(self, exctype, excvalue, traceback):
610 unwrapfunction(self._container, self._funcname, self._wrapper)
610 unwrapfunction(self._container, self._funcname, self._wrapper)
611
611
612
612
613 def wrapfunction(container, funcname, wrapper):
613 def wrapfunction(container, funcname, wrapper):
614 '''Wrap the function named funcname in container
614 '''Wrap the function named funcname in container
615
615
616 Replace the funcname member in the given container with the specified
616 Replace the funcname member in the given container with the specified
617 wrapper. The container is typically a module, class, or instance.
617 wrapper. The container is typically a module, class, or instance.
618
618
619 The wrapper will be called like
619 The wrapper will be called like
620
620
621 wrapper(orig, *args, **kwargs)
621 wrapper(orig, *args, **kwargs)
622
622
623 where orig is the original (wrapped) function, and *args, **kwargs
623 where orig is the original (wrapped) function, and *args, **kwargs
624 are the arguments passed to it.
624 are the arguments passed to it.
625
625
626 Wrapping methods of the repository object is not recommended since
626 Wrapping methods of the repository object is not recommended since
627 it conflicts with extensions that extend the repository by
627 it conflicts with extensions that extend the repository by
628 subclassing. All extensions that need to extend methods of
628 subclassing. All extensions that need to extend methods of
629 localrepository should use this subclassing trick: namely,
629 localrepository should use this subclassing trick: namely,
630 reposetup() should look like
630 reposetup() should look like
631
631
632 def reposetup(ui, repo):
632 def reposetup(ui, repo):
633 class myrepo(repo.__class__):
633 class myrepo(repo.__class__):
634 def whatever(self, *args, **kwargs):
634 def whatever(self, *args, **kwargs):
635 [...extension stuff...]
635 [...extension stuff...]
636 super(myrepo, self).whatever(*args, **kwargs)
636 super(myrepo, self).whatever(*args, **kwargs)
637 [...extension stuff...]
637 [...extension stuff...]
638
638
639 repo.__class__ = myrepo
639 repo.__class__ = myrepo
640
640
641 In general, combining wrapfunction() with subclassing does not
641 In general, combining wrapfunction() with subclassing does not
642 work. Since you cannot control what other extensions are loaded by
642 work. Since you cannot control what other extensions are loaded by
643 your end users, you should play nicely with others by using the
643 your end users, you should play nicely with others by using the
644 subclass trick.
644 subclass trick.
645 '''
645 '''
646 assert callable(wrapper)
646 assert callable(wrapper)
647
647
648 origfn = getattr(container, funcname)
648 origfn = getattr(container, funcname)
649 assert callable(origfn)
649 assert callable(origfn)
650 if inspect.ismodule(container):
650 if inspect.ismodule(container):
651 # origfn is not an instance or class method. "partial" can be used.
651 # origfn is not an instance or class method. "partial" can be used.
652 # "partial" won't insert a frame in traceback.
652 # "partial" won't insert a frame in traceback.
653 wrap = functools.partial(wrapper, origfn)
653 wrap = functools.partial(wrapper, origfn)
654 else:
654 else:
655 # "partial" cannot be safely used. Emulate its effect by using "bind".
655 # "partial" cannot be safely used. Emulate its effect by using "bind".
656 # The downside is one more frame in traceback.
656 # The downside is one more frame in traceback.
657 wrap = bind(wrapper, origfn)
657 wrap = bind(wrapper, origfn)
658 _updatewrapper(wrap, origfn, wrapper)
658 _updatewrapper(wrap, origfn, wrapper)
659 setattr(container, funcname, wrap)
659 setattr(container, funcname, wrap)
660 return origfn
660 return origfn
661
661
662
662
663 def unwrapfunction(container, funcname, wrapper=None):
663 def unwrapfunction(container, funcname, wrapper=None):
664 '''undo wrapfunction
664 '''undo wrapfunction
665
665
666 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
666 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
667 from the chain of wrappers.
667 from the chain of wrappers.
668
668
669 Return the removed wrapper.
669 Return the removed wrapper.
670 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
670 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
671 wrapper is not None but is not found in the wrapper chain.
671 wrapper is not None but is not found in the wrapper chain.
672 '''
672 '''
673 chain = getwrapperchain(container, funcname)
673 chain = getwrapperchain(container, funcname)
674 origfn = chain.pop()
674 origfn = chain.pop()
675 if wrapper is None:
675 if wrapper is None:
676 wrapper = chain[0]
676 wrapper = chain[0]
677 chain.remove(wrapper)
677 chain.remove(wrapper)
678 setattr(container, funcname, origfn)
678 setattr(container, funcname, origfn)
679 for w in reversed(chain):
679 for w in reversed(chain):
680 wrapfunction(container, funcname, w)
680 wrapfunction(container, funcname, w)
681 return wrapper
681 return wrapper
682
682
683
683
684 def getwrapperchain(container, funcname):
684 def getwrapperchain(container, funcname):
685 '''get a chain of wrappers of a function
685 '''get a chain of wrappers of a function
686
686
687 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
687 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
688
688
689 The wrapper functions are the ones passed to wrapfunction, whose first
689 The wrapper functions are the ones passed to wrapfunction, whose first
690 argument is origfunc.
690 argument is origfunc.
691 '''
691 '''
692 result = []
692 result = []
693 fn = getattr(container, funcname)
693 fn = getattr(container, funcname)
694 while fn:
694 while fn:
695 assert callable(fn)
695 assert callable(fn)
696 result.append(getattr(fn, '_unboundwrapper', fn))
696 result.append(getattr(fn, '_unboundwrapper', fn))
697 fn = getattr(fn, '_origfunc', None)
697 fn = getattr(fn, '_origfunc', None)
698 return result
698 return result
699
699
700
700
701 def _disabledpaths():
701 def _disabledpaths():
702 '''find paths of disabled extensions. returns a dict of {name: path}'''
702 '''find paths of disabled extensions. returns a dict of {name: path}'''
703 import hgext
703 import hgext
704
704
705 extpath = os.path.dirname(
705 extpath = os.path.dirname(
706 os.path.abspath(pycompat.fsencode(hgext.__file__))
706 os.path.abspath(pycompat.fsencode(hgext.__file__))
707 )
707 )
708 try: # might not be a filesystem path
708 try: # might not be a filesystem path
709 files = os.listdir(extpath)
709 files = os.listdir(extpath)
710 except OSError:
710 except OSError:
711 return {}
711 return {}
712
712
713 exts = {}
713 exts = {}
714 for e in files:
714 for e in files:
715 if e.endswith(b'.py'):
715 if e.endswith(b'.py'):
716 name = e.rsplit(b'.', 1)[0]
716 name = e.rsplit(b'.', 1)[0]
717 path = os.path.join(extpath, e)
717 path = os.path.join(extpath, e)
718 else:
718 else:
719 name = e
719 name = e
720 path = os.path.join(extpath, e, b'__init__.py')
720 path = os.path.join(extpath, e, b'__init__.py')
721 if not os.path.exists(path):
721 if not os.path.exists(path):
722 continue
722 continue
723 if name in exts or name in _order or name == b'__init__':
723 if name in exts or name in _order or name == b'__init__':
724 continue
724 continue
725 exts[name] = path
725 exts[name] = path
726 for name, path in pycompat.iteritems(_disabledextensions):
726 for name, path in pycompat.iteritems(_disabledextensions):
727 # If no path was provided for a disabled extension (e.g. "color=!"),
727 # If no path was provided for a disabled extension (e.g. "color=!"),
728 # don't replace the path we already found by the scan above.
728 # don't replace the path we already found by the scan above.
729 if path:
729 if path:
730 exts[name] = path
730 exts[name] = path
731 return exts
731 return exts
732
732
733
733
734 def _moduledoc(file):
734 def _moduledoc(file):
735 '''return the top-level python documentation for the given file
735 '''return the top-level python documentation for the given file
736
736
737 Loosely inspired by pydoc.source_synopsis(), but rewritten to
737 Loosely inspired by pydoc.source_synopsis(), but rewritten to
738 handle triple quotes and to return the whole text instead of just
738 handle triple quotes and to return the whole text instead of just
739 the synopsis'''
739 the synopsis'''
740 result = []
740 result = []
741
741
742 line = file.readline()
742 line = file.readline()
743 while line[:1] == b'#' or not line.strip():
743 while line[:1] == b'#' or not line.strip():
744 line = file.readline()
744 line = file.readline()
745 if not line:
745 if not line:
746 break
746 break
747
747
748 start = line[:3]
748 start = line[:3]
749 if start == b'"""' or start == b"'''":
749 if start == b'"""' or start == b"'''":
750 line = line[3:]
750 line = line[3:]
751 while line:
751 while line:
752 if line.rstrip().endswith(start):
752 if line.rstrip().endswith(start):
753 line = line.split(start)[0]
753 line = line.split(start)[0]
754 if line:
754 if line:
755 result.append(line)
755 result.append(line)
756 break
756 break
757 elif not line:
757 elif not line:
758 return None # unmatched delimiter
758 return None # unmatched delimiter
759 result.append(line)
759 result.append(line)
760 line = file.readline()
760 line = file.readline()
761 else:
761 else:
762 return None
762 return None
763
763
764 return b''.join(result)
764 return b''.join(result)
765
765
766
766
767 def _disabledhelp(path):
767 def _disabledhelp(path):
768 '''retrieve help synopsis of a disabled extension (without importing)'''
768 '''retrieve help synopsis of a disabled extension (without importing)'''
769 try:
769 try:
770 with open(path, b'rb') as src:
770 with open(path, b'rb') as src:
771 doc = _moduledoc(src)
771 doc = _moduledoc(src)
772 except IOError:
772 except IOError:
773 return
773 return
774
774
775 if doc: # extracting localized synopsis
775 if doc: # extracting localized synopsis
776 return gettext(doc)
776 return gettext(doc)
777 else:
777 else:
778 return _(b'(no help text available)')
778 return _(b'(no help text available)')
779
779
780
780
781 def disabled():
781 def disabled():
782 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
782 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
783 try:
783 try:
784 from hgext import __index__
784 from hgext import __index__ # pytype: disable=import-error
785
785
786 return dict(
786 return dict(
787 (name, gettext(desc))
787 (name, gettext(desc))
788 for name, desc in pycompat.iteritems(__index__.docs)
788 for name, desc in pycompat.iteritems(__index__.docs)
789 if name not in _order
789 if name not in _order
790 )
790 )
791 except (ImportError, AttributeError):
791 except (ImportError, AttributeError):
792 pass
792 pass
793
793
794 paths = _disabledpaths()
794 paths = _disabledpaths()
795 if not paths:
795 if not paths:
796 return {}
796 return {}
797
797
798 exts = {}
798 exts = {}
799 for name, path in pycompat.iteritems(paths):
799 for name, path in pycompat.iteritems(paths):
800 doc = _disabledhelp(path)
800 doc = _disabledhelp(path)
801 if doc:
801 if doc:
802 exts[name] = doc.splitlines()[0]
802 exts[name] = doc.splitlines()[0]
803
803
804 return exts
804 return exts
805
805
806
806
807 def disabledext(name):
807 def disabledext(name):
808 '''find a specific disabled extension from hgext. returns desc'''
808 '''find a specific disabled extension from hgext. returns desc'''
809 try:
809 try:
810 from hgext import __index__
810 from hgext import __index__ # pytype: disable=import-error
811
811
812 if name in _order: # enabled
812 if name in _order: # enabled
813 return
813 return
814 else:
814 else:
815 return gettext(__index__.docs.get(name))
815 return gettext(__index__.docs.get(name))
816 except (ImportError, AttributeError):
816 except (ImportError, AttributeError):
817 pass
817 pass
818
818
819 paths = _disabledpaths()
819 paths = _disabledpaths()
820 if name in paths:
820 if name in paths:
821 return _disabledhelp(paths[name])
821 return _disabledhelp(paths[name])
822
822
823
823
824 def _walkcommand(node):
824 def _walkcommand(node):
825 """Scan @command() decorators in the tree starting at node"""
825 """Scan @command() decorators in the tree starting at node"""
826 todo = collections.deque([node])
826 todo = collections.deque([node])
827 while todo:
827 while todo:
828 node = todo.popleft()
828 node = todo.popleft()
829 if not isinstance(node, ast.FunctionDef):
829 if not isinstance(node, ast.FunctionDef):
830 todo.extend(ast.iter_child_nodes(node))
830 todo.extend(ast.iter_child_nodes(node))
831 continue
831 continue
832 for d in node.decorator_list:
832 for d in node.decorator_list:
833 if not isinstance(d, ast.Call):
833 if not isinstance(d, ast.Call):
834 continue
834 continue
835 if not isinstance(d.func, ast.Name):
835 if not isinstance(d.func, ast.Name):
836 continue
836 continue
837 if d.func.id != 'command':
837 if d.func.id != 'command':
838 continue
838 continue
839 yield d
839 yield d
840
840
841
841
842 def _disabledcmdtable(path):
842 def _disabledcmdtable(path):
843 """Construct a dummy command table without loading the extension module
843 """Construct a dummy command table without loading the extension module
844
844
845 This may raise IOError or SyntaxError.
845 This may raise IOError or SyntaxError.
846 """
846 """
847 with open(path, b'rb') as src:
847 with open(path, b'rb') as src:
848 root = ast.parse(src.read(), path)
848 root = ast.parse(src.read(), path)
849 cmdtable = {}
849 cmdtable = {}
850 for node in _walkcommand(root):
850 for node in _walkcommand(root):
851 if not node.args:
851 if not node.args:
852 continue
852 continue
853 a = node.args[0]
853 a = node.args[0]
854 if isinstance(a, ast.Str):
854 if isinstance(a, ast.Str):
855 name = pycompat.sysbytes(a.s)
855 name = pycompat.sysbytes(a.s)
856 elif pycompat.ispy3 and isinstance(a, ast.Bytes):
856 elif pycompat.ispy3 and isinstance(a, ast.Bytes):
857 name = a.s
857 name = a.s
858 else:
858 else:
859 continue
859 continue
860 cmdtable[name] = (None, [], b'')
860 cmdtable[name] = (None, [], b'')
861 return cmdtable
861 return cmdtable
862
862
863
863
864 def _finddisabledcmd(ui, cmd, name, path, strict):
864 def _finddisabledcmd(ui, cmd, name, path, strict):
865 try:
865 try:
866 cmdtable = _disabledcmdtable(path)
866 cmdtable = _disabledcmdtable(path)
867 except (IOError, SyntaxError):
867 except (IOError, SyntaxError):
868 return
868 return
869 try:
869 try:
870 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
870 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
871 except (error.AmbiguousCommand, error.UnknownCommand):
871 except (error.AmbiguousCommand, error.UnknownCommand):
872 return
872 return
873 for c in aliases:
873 for c in aliases:
874 if c.startswith(cmd):
874 if c.startswith(cmd):
875 cmd = c
875 cmd = c
876 break
876 break
877 else:
877 else:
878 cmd = aliases[0]
878 cmd = aliases[0]
879 doc = _disabledhelp(path)
879 doc = _disabledhelp(path)
880 return (cmd, name, doc)
880 return (cmd, name, doc)
881
881
882
882
883 def disabledcmd(ui, cmd, strict=False):
883 def disabledcmd(ui, cmd, strict=False):
884 '''find cmd from disabled extensions without importing.
884 '''find cmd from disabled extensions without importing.
885 returns (cmdname, extname, doc)'''
885 returns (cmdname, extname, doc)'''
886
886
887 paths = _disabledpaths()
887 paths = _disabledpaths()
888 if not paths:
888 if not paths:
889 raise error.UnknownCommand(cmd)
889 raise error.UnknownCommand(cmd)
890
890
891 ext = None
891 ext = None
892 # first, search for an extension with the same name as the command
892 # first, search for an extension with the same name as the command
893 path = paths.pop(cmd, None)
893 path = paths.pop(cmd, None)
894 if path:
894 if path:
895 ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
895 ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
896 if not ext:
896 if not ext:
897 # otherwise, interrogate each extension until there's a match
897 # otherwise, interrogate each extension until there's a match
898 for name, path in pycompat.iteritems(paths):
898 for name, path in pycompat.iteritems(paths):
899 ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
899 ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
900 if ext:
900 if ext:
901 break
901 break
902 if ext:
902 if ext:
903 return ext
903 return ext
904
904
905 raise error.UnknownCommand(cmd)
905 raise error.UnknownCommand(cmd)
906
906
907
907
908 def enabled(shortname=True):
908 def enabled(shortname=True):
909 '''return a dict of {name: desc} of extensions'''
909 '''return a dict of {name: desc} of extensions'''
910 exts = {}
910 exts = {}
911 for ename, ext in extensions():
911 for ename, ext in extensions():
912 doc = gettext(ext.__doc__) or _(b'(no help text available)')
912 doc = gettext(ext.__doc__) or _(b'(no help text available)')
913 if shortname:
913 if shortname:
914 ename = ename.split(b'.')[-1]
914 ename = ename.split(b'.')[-1]
915 exts[ename] = doc.splitlines()[0].strip()
915 exts[ename] = doc.splitlines()[0].strip()
916
916
917 return exts
917 return exts
918
918
919
919
920 def notloaded():
920 def notloaded():
921 '''return short names of extensions that failed to load'''
921 '''return short names of extensions that failed to load'''
922 return [
922 return [
923 name for name, mod in pycompat.iteritems(_extensions) if mod is None
923 name for name, mod in pycompat.iteritems(_extensions) if mod is None
924 ]
924 ]
925
925
926
926
927 def moduleversion(module):
927 def moduleversion(module):
928 '''return version information from given module as a string'''
928 '''return version information from given module as a string'''
929 if util.safehasattr(module, b'getversion') and callable(module.getversion):
929 if util.safehasattr(module, b'getversion') and callable(module.getversion):
930 version = module.getversion()
930 version = module.getversion()
931 elif util.safehasattr(module, b'__version__'):
931 elif util.safehasattr(module, b'__version__'):
932 version = module.__version__
932 version = module.__version__
933 else:
933 else:
934 version = b''
934 version = b''
935 if isinstance(version, (list, tuple)):
935 if isinstance(version, (list, tuple)):
936 version = b'.'.join(pycompat.bytestr(o) for o in version)
936 version = b'.'.join(pycompat.bytestr(o) for o in version)
937 return version
937 return version
938
938
939
939
940 def ismoduleinternal(module):
940 def ismoduleinternal(module):
941 exttestedwith = getattr(module, 'testedwith', None)
941 exttestedwith = getattr(module, 'testedwith', None)
942 return exttestedwith == b"ships-with-hg-core"
942 return exttestedwith == b"ships-with-hg-core"
General Comments 0
You need to be logged in to leave comments. Login now