##// END OF EJS Templates
extensions: use normpath to allow trailing '\' on Windows (issue4187)...
Ed Morley -
r20645:7d83c3b6 default
parent child Browse files
Show More
@@ -1,369 +1,369
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 import imp, os
8 import imp, os
9 import util, cmdutil, error
9 import util, cmdutil, error
10 from i18n import _, gettext
10 from i18n import _, gettext
11
11
12 _extensions = {}
12 _extensions = {}
13 _order = []
13 _order = []
14 _ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
14 _ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
15
15
16 def extensions(ui=None):
16 def extensions(ui=None):
17 if ui:
17 if ui:
18 def enabled(name):
18 def enabled(name):
19 for format in ['%s', 'hgext.%s']:
19 for format in ['%s', 'hgext.%s']:
20 conf = ui.config('extensions', format % name)
20 conf = ui.config('extensions', format % name)
21 if conf is not None and not conf.startswith('!'):
21 if conf is not None and not conf.startswith('!'):
22 return True
22 return True
23 else:
23 else:
24 enabled = lambda name: True
24 enabled = lambda name: True
25 for name in _order:
25 for name in _order:
26 module = _extensions[name]
26 module = _extensions[name]
27 if module and enabled(name):
27 if module and enabled(name):
28 yield name, module
28 yield name, module
29
29
30 def find(name):
30 def find(name):
31 '''return module with given extension name'''
31 '''return module with given extension name'''
32 mod = None
32 mod = None
33 try:
33 try:
34 mod = _extensions[name]
34 mod = _extensions[name]
35 except KeyError:
35 except KeyError:
36 for k, v in _extensions.iteritems():
36 for k, v in _extensions.iteritems():
37 if k.endswith('.' + name) or k.endswith('/' + name):
37 if k.endswith('.' + name) or k.endswith('/' + name):
38 mod = v
38 mod = v
39 break
39 break
40 if not mod:
40 if not mod:
41 raise KeyError(name)
41 raise KeyError(name)
42 return mod
42 return mod
43
43
44 def loadpath(path, module_name):
44 def loadpath(path, module_name):
45 module_name = module_name.replace('.', '_')
45 module_name = module_name.replace('.', '_')
46 path = util.expandpath(path)
46 path = util.normpath(util.expandpath(path))
47 if os.path.isdir(path):
47 if os.path.isdir(path):
48 # module/__init__.py style
48 # module/__init__.py style
49 d, f = os.path.split(path.rstrip('/'))
49 d, f = os.path.split(path)
50 fd, fpath, desc = imp.find_module(f, [d])
50 fd, fpath, desc = imp.find_module(f, [d])
51 return imp.load_module(module_name, fd, fpath, desc)
51 return imp.load_module(module_name, fd, fpath, desc)
52 else:
52 else:
53 try:
53 try:
54 return imp.load_source(module_name, path)
54 return imp.load_source(module_name, path)
55 except IOError, exc:
55 except IOError, exc:
56 if not exc.filename:
56 if not exc.filename:
57 exc.filename = path # python does not fill this
57 exc.filename = path # python does not fill this
58 raise
58 raise
59
59
60 def load(ui, name, path):
60 def load(ui, name, path):
61 if name.startswith('hgext.') or name.startswith('hgext/'):
61 if name.startswith('hgext.') or name.startswith('hgext/'):
62 shortname = name[6:]
62 shortname = name[6:]
63 else:
63 else:
64 shortname = name
64 shortname = name
65 if shortname in _ignore:
65 if shortname in _ignore:
66 return None
66 return None
67 if shortname in _extensions:
67 if shortname in _extensions:
68 return _extensions[shortname]
68 return _extensions[shortname]
69 _extensions[shortname] = None
69 _extensions[shortname] = None
70 if path:
70 if path:
71 # the module will be loaded in sys.modules
71 # the module will be loaded in sys.modules
72 # choose an unique name so that it doesn't
72 # choose an unique name so that it doesn't
73 # conflicts with other modules
73 # conflicts with other modules
74 mod = loadpath(path, 'hgext.%s' % name)
74 mod = loadpath(path, 'hgext.%s' % name)
75 else:
75 else:
76 def importh(name):
76 def importh(name):
77 mod = __import__(name)
77 mod = __import__(name)
78 components = name.split('.')
78 components = name.split('.')
79 for comp in components[1:]:
79 for comp in components[1:]:
80 mod = getattr(mod, comp)
80 mod = getattr(mod, comp)
81 return mod
81 return mod
82 try:
82 try:
83 mod = importh("hgext.%s" % name)
83 mod = importh("hgext.%s" % name)
84 except ImportError, err:
84 except ImportError, err:
85 ui.debug('could not import hgext.%s (%s): trying %s\n'
85 ui.debug('could not import hgext.%s (%s): trying %s\n'
86 % (name, err, name))
86 % (name, err, name))
87 mod = importh(name)
87 mod = importh(name)
88 _extensions[shortname] = mod
88 _extensions[shortname] = mod
89 _order.append(shortname)
89 _order.append(shortname)
90 return mod
90 return mod
91
91
92 def loadall(ui):
92 def loadall(ui):
93 result = ui.configitems("extensions")
93 result = ui.configitems("extensions")
94 newindex = len(_order)
94 newindex = len(_order)
95 for (name, path) in result:
95 for (name, path) in result:
96 if path:
96 if path:
97 if path[0] == '!':
97 if path[0] == '!':
98 continue
98 continue
99 try:
99 try:
100 load(ui, name, path)
100 load(ui, name, path)
101 except KeyboardInterrupt:
101 except KeyboardInterrupt:
102 raise
102 raise
103 except Exception, inst:
103 except Exception, inst:
104 if path:
104 if path:
105 ui.warn(_("*** failed to import extension %s from %s: %s\n")
105 ui.warn(_("*** failed to import extension %s from %s: %s\n")
106 % (name, path, inst))
106 % (name, path, inst))
107 else:
107 else:
108 ui.warn(_("*** failed to import extension %s: %s\n")
108 ui.warn(_("*** failed to import extension %s: %s\n")
109 % (name, inst))
109 % (name, inst))
110 if ui.traceback():
110 if ui.traceback():
111 return 1
111 return 1
112
112
113 for name in _order[newindex:]:
113 for name in _order[newindex:]:
114 uisetup = getattr(_extensions[name], 'uisetup', None)
114 uisetup = getattr(_extensions[name], 'uisetup', None)
115 if uisetup:
115 if uisetup:
116 uisetup(ui)
116 uisetup(ui)
117
117
118 for name in _order[newindex:]:
118 for name in _order[newindex:]:
119 extsetup = getattr(_extensions[name], 'extsetup', None)
119 extsetup = getattr(_extensions[name], 'extsetup', None)
120 if extsetup:
120 if extsetup:
121 try:
121 try:
122 extsetup(ui)
122 extsetup(ui)
123 except TypeError:
123 except TypeError:
124 if extsetup.func_code.co_argcount != 0:
124 if extsetup.func_code.co_argcount != 0:
125 raise
125 raise
126 extsetup() # old extsetup with no ui argument
126 extsetup() # old extsetup with no ui argument
127
127
128 def wrapcommand(table, command, wrapper):
128 def wrapcommand(table, command, wrapper):
129 '''Wrap the command named `command' in table
129 '''Wrap the command named `command' in table
130
130
131 Replace command in the command table with wrapper. The wrapped command will
131 Replace command in the command table with wrapper. The wrapped command will
132 be inserted into the command table specified by the table argument.
132 be inserted into the command table specified by the table argument.
133
133
134 The wrapper will be called like
134 The wrapper will be called like
135
135
136 wrapper(orig, *args, **kwargs)
136 wrapper(orig, *args, **kwargs)
137
137
138 where orig is the original (wrapped) function, and *args, **kwargs
138 where orig is the original (wrapped) function, and *args, **kwargs
139 are the arguments passed to it.
139 are the arguments passed to it.
140 '''
140 '''
141 assert util.safehasattr(wrapper, '__call__')
141 assert util.safehasattr(wrapper, '__call__')
142 aliases, entry = cmdutil.findcmd(command, table)
142 aliases, entry = cmdutil.findcmd(command, table)
143 for alias, e in table.iteritems():
143 for alias, e in table.iteritems():
144 if e is entry:
144 if e is entry:
145 key = alias
145 key = alias
146 break
146 break
147
147
148 origfn = entry[0]
148 origfn = entry[0]
149 def wrap(*args, **kwargs):
149 def wrap(*args, **kwargs):
150 return util.checksignature(wrapper)(
150 return util.checksignature(wrapper)(
151 util.checksignature(origfn), *args, **kwargs)
151 util.checksignature(origfn), *args, **kwargs)
152
152
153 wrap.__doc__ = getattr(origfn, '__doc__')
153 wrap.__doc__ = getattr(origfn, '__doc__')
154 wrap.__module__ = getattr(origfn, '__module__')
154 wrap.__module__ = getattr(origfn, '__module__')
155
155
156 newentry = list(entry)
156 newentry = list(entry)
157 newentry[0] = wrap
157 newentry[0] = wrap
158 table[key] = tuple(newentry)
158 table[key] = tuple(newentry)
159 return entry
159 return entry
160
160
161 def wrapfunction(container, funcname, wrapper):
161 def wrapfunction(container, funcname, wrapper):
162 '''Wrap the function named funcname in container
162 '''Wrap the function named funcname in container
163
163
164 Replace the funcname member in the given container with the specified
164 Replace the funcname member in the given container with the specified
165 wrapper. The container is typically a module, class, or instance.
165 wrapper. The container is typically a module, class, or instance.
166
166
167 The wrapper will be called like
167 The wrapper will be called like
168
168
169 wrapper(orig, *args, **kwargs)
169 wrapper(orig, *args, **kwargs)
170
170
171 where orig is the original (wrapped) function, and *args, **kwargs
171 where orig is the original (wrapped) function, and *args, **kwargs
172 are the arguments passed to it.
172 are the arguments passed to it.
173
173
174 Wrapping methods of the repository object is not recommended since
174 Wrapping methods of the repository object is not recommended since
175 it conflicts with extensions that extend the repository by
175 it conflicts with extensions that extend the repository by
176 subclassing. All extensions that need to extend methods of
176 subclassing. All extensions that need to extend methods of
177 localrepository should use this subclassing trick: namely,
177 localrepository should use this subclassing trick: namely,
178 reposetup() should look like
178 reposetup() should look like
179
179
180 def reposetup(ui, repo):
180 def reposetup(ui, repo):
181 class myrepo(repo.__class__):
181 class myrepo(repo.__class__):
182 def whatever(self, *args, **kwargs):
182 def whatever(self, *args, **kwargs):
183 [...extension stuff...]
183 [...extension stuff...]
184 super(myrepo, self).whatever(*args, **kwargs)
184 super(myrepo, self).whatever(*args, **kwargs)
185 [...extension stuff...]
185 [...extension stuff...]
186
186
187 repo.__class__ = myrepo
187 repo.__class__ = myrepo
188
188
189 In general, combining wrapfunction() with subclassing does not
189 In general, combining wrapfunction() with subclassing does not
190 work. Since you cannot control what other extensions are loaded by
190 work. Since you cannot control what other extensions are loaded by
191 your end users, you should play nicely with others by using the
191 your end users, you should play nicely with others by using the
192 subclass trick.
192 subclass trick.
193 '''
193 '''
194 assert util.safehasattr(wrapper, '__call__')
194 assert util.safehasattr(wrapper, '__call__')
195 def wrap(*args, **kwargs):
195 def wrap(*args, **kwargs):
196 return wrapper(origfn, *args, **kwargs)
196 return wrapper(origfn, *args, **kwargs)
197
197
198 origfn = getattr(container, funcname)
198 origfn = getattr(container, funcname)
199 assert util.safehasattr(origfn, '__call__')
199 assert util.safehasattr(origfn, '__call__')
200 setattr(container, funcname, wrap)
200 setattr(container, funcname, wrap)
201 return origfn
201 return origfn
202
202
203 def _disabledpaths(strip_init=False):
203 def _disabledpaths(strip_init=False):
204 '''find paths of disabled extensions. returns a dict of {name: path}
204 '''find paths of disabled extensions. returns a dict of {name: path}
205 removes /__init__.py from packages if strip_init is True'''
205 removes /__init__.py from packages if strip_init is True'''
206 import hgext
206 import hgext
207 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
207 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
208 try: # might not be a filesystem path
208 try: # might not be a filesystem path
209 files = os.listdir(extpath)
209 files = os.listdir(extpath)
210 except OSError:
210 except OSError:
211 return {}
211 return {}
212
212
213 exts = {}
213 exts = {}
214 for e in files:
214 for e in files:
215 if e.endswith('.py'):
215 if e.endswith('.py'):
216 name = e.rsplit('.', 1)[0]
216 name = e.rsplit('.', 1)[0]
217 path = os.path.join(extpath, e)
217 path = os.path.join(extpath, e)
218 else:
218 else:
219 name = e
219 name = e
220 path = os.path.join(extpath, e, '__init__.py')
220 path = os.path.join(extpath, e, '__init__.py')
221 if not os.path.exists(path):
221 if not os.path.exists(path):
222 continue
222 continue
223 if strip_init:
223 if strip_init:
224 path = os.path.dirname(path)
224 path = os.path.dirname(path)
225 if name in exts or name in _order or name == '__init__':
225 if name in exts or name in _order or name == '__init__':
226 continue
226 continue
227 exts[name] = path
227 exts[name] = path
228 return exts
228 return exts
229
229
230 def _moduledoc(file):
230 def _moduledoc(file):
231 '''return the top-level python documentation for the given file
231 '''return the top-level python documentation for the given file
232
232
233 Loosely inspired by pydoc.source_synopsis(), but rewritten to
233 Loosely inspired by pydoc.source_synopsis(), but rewritten to
234 handle triple quotes and to return the whole text instead of just
234 handle triple quotes and to return the whole text instead of just
235 the synopsis'''
235 the synopsis'''
236 result = []
236 result = []
237
237
238 line = file.readline()
238 line = file.readline()
239 while line[:1] == '#' or not line.strip():
239 while line[:1] == '#' or not line.strip():
240 line = file.readline()
240 line = file.readline()
241 if not line:
241 if not line:
242 break
242 break
243
243
244 start = line[:3]
244 start = line[:3]
245 if start == '"""' or start == "'''":
245 if start == '"""' or start == "'''":
246 line = line[3:]
246 line = line[3:]
247 while line:
247 while line:
248 if line.rstrip().endswith(start):
248 if line.rstrip().endswith(start):
249 line = line.split(start)[0]
249 line = line.split(start)[0]
250 if line:
250 if line:
251 result.append(line)
251 result.append(line)
252 break
252 break
253 elif not line:
253 elif not line:
254 return None # unmatched delimiter
254 return None # unmatched delimiter
255 result.append(line)
255 result.append(line)
256 line = file.readline()
256 line = file.readline()
257 else:
257 else:
258 return None
258 return None
259
259
260 return ''.join(result)
260 return ''.join(result)
261
261
262 def _disabledhelp(path):
262 def _disabledhelp(path):
263 '''retrieve help synopsis of a disabled extension (without importing)'''
263 '''retrieve help synopsis of a disabled extension (without importing)'''
264 try:
264 try:
265 file = open(path)
265 file = open(path)
266 except IOError:
266 except IOError:
267 return
267 return
268 else:
268 else:
269 doc = _moduledoc(file)
269 doc = _moduledoc(file)
270 file.close()
270 file.close()
271
271
272 if doc: # extracting localized synopsis
272 if doc: # extracting localized synopsis
273 return gettext(doc).splitlines()[0]
273 return gettext(doc).splitlines()[0]
274 else:
274 else:
275 return _('(no help text available)')
275 return _('(no help text available)')
276
276
277 def disabled():
277 def disabled():
278 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
278 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
279 try:
279 try:
280 from hgext import __index__
280 from hgext import __index__
281 return dict((name, gettext(desc))
281 return dict((name, gettext(desc))
282 for name, desc in __index__.docs.iteritems()
282 for name, desc in __index__.docs.iteritems()
283 if name not in _order)
283 if name not in _order)
284 except ImportError:
284 except ImportError:
285 pass
285 pass
286
286
287 paths = _disabledpaths()
287 paths = _disabledpaths()
288 if not paths:
288 if not paths:
289 return {}
289 return {}
290
290
291 exts = {}
291 exts = {}
292 for name, path in paths.iteritems():
292 for name, path in paths.iteritems():
293 doc = _disabledhelp(path)
293 doc = _disabledhelp(path)
294 if doc:
294 if doc:
295 exts[name] = doc
295 exts[name] = doc
296
296
297 return exts
297 return exts
298
298
299 def disabledext(name):
299 def disabledext(name):
300 '''find a specific disabled extension from hgext. returns desc'''
300 '''find a specific disabled extension from hgext. returns desc'''
301 try:
301 try:
302 from hgext import __index__
302 from hgext import __index__
303 if name in _order: # enabled
303 if name in _order: # enabled
304 return
304 return
305 else:
305 else:
306 return gettext(__index__.docs.get(name))
306 return gettext(__index__.docs.get(name))
307 except ImportError:
307 except ImportError:
308 pass
308 pass
309
309
310 paths = _disabledpaths()
310 paths = _disabledpaths()
311 if name in paths:
311 if name in paths:
312 return _disabledhelp(paths[name])
312 return _disabledhelp(paths[name])
313
313
314 def disabledcmd(ui, cmd, strict=False):
314 def disabledcmd(ui, cmd, strict=False):
315 '''import disabled extensions until cmd is found.
315 '''import disabled extensions until cmd is found.
316 returns (cmdname, extname, module)'''
316 returns (cmdname, extname, module)'''
317
317
318 paths = _disabledpaths(strip_init=True)
318 paths = _disabledpaths(strip_init=True)
319 if not paths:
319 if not paths:
320 raise error.UnknownCommand(cmd)
320 raise error.UnknownCommand(cmd)
321
321
322 def findcmd(cmd, name, path):
322 def findcmd(cmd, name, path):
323 try:
323 try:
324 mod = loadpath(path, 'hgext.%s' % name)
324 mod = loadpath(path, 'hgext.%s' % name)
325 except Exception:
325 except Exception:
326 return
326 return
327 try:
327 try:
328 aliases, entry = cmdutil.findcmd(cmd,
328 aliases, entry = cmdutil.findcmd(cmd,
329 getattr(mod, 'cmdtable', {}), strict)
329 getattr(mod, 'cmdtable', {}), strict)
330 except (error.AmbiguousCommand, error.UnknownCommand):
330 except (error.AmbiguousCommand, error.UnknownCommand):
331 return
331 return
332 except Exception:
332 except Exception:
333 ui.warn(_('warning: error finding commands in %s\n') % path)
333 ui.warn(_('warning: error finding commands in %s\n') % path)
334 ui.traceback()
334 ui.traceback()
335 return
335 return
336 for c in aliases:
336 for c in aliases:
337 if c.startswith(cmd):
337 if c.startswith(cmd):
338 cmd = c
338 cmd = c
339 break
339 break
340 else:
340 else:
341 cmd = aliases[0]
341 cmd = aliases[0]
342 return (cmd, name, mod)
342 return (cmd, name, mod)
343
343
344 ext = None
344 ext = None
345 # first, search for an extension with the same name as the command
345 # first, search for an extension with the same name as the command
346 path = paths.pop(cmd, None)
346 path = paths.pop(cmd, None)
347 if path:
347 if path:
348 ext = findcmd(cmd, cmd, path)
348 ext = findcmd(cmd, cmd, path)
349 if not ext:
349 if not ext:
350 # otherwise, interrogate each extension until there's a match
350 # otherwise, interrogate each extension until there's a match
351 for name, path in paths.iteritems():
351 for name, path in paths.iteritems():
352 ext = findcmd(cmd, name, path)
352 ext = findcmd(cmd, name, path)
353 if ext:
353 if ext:
354 break
354 break
355 if ext and 'DEPRECATED' not in ext.__doc__:
355 if ext and 'DEPRECATED' not in ext.__doc__:
356 return ext
356 return ext
357
357
358 raise error.UnknownCommand(cmd)
358 raise error.UnknownCommand(cmd)
359
359
360 def enabled(shortname=True):
360 def enabled(shortname=True):
361 '''return a dict of {name: desc} of extensions'''
361 '''return a dict of {name: desc} of extensions'''
362 exts = {}
362 exts = {}
363 for ename, ext in extensions():
363 for ename, ext in extensions():
364 doc = (gettext(ext.__doc__) or _('(no help text available)'))
364 doc = (gettext(ext.__doc__) or _('(no help text available)'))
365 if shortname:
365 if shortname:
366 ename = ename.split('.')[-1]
366 ename = ename.split('.')[-1]
367 exts[ename] = doc.splitlines()[0].strip()
367 exts[ename] = doc.splitlines()[0].strip()
368
368
369 return exts
369 return exts
General Comments 0
You need to be logged in to leave comments. Login now