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