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