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