|
1 # extensions.py - extension handling for mercurial |
|
2 # |
|
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
4 # |
|
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. |
|
7 |
|
8 import imp, os |
|
9 import util, cmdutil, help, error |
|
10 from i18n import _, gettext |
|
11 |
|
12 _extensions = {} |
|
13 _order = [] |
|
14 |
|
15 def extensions(): |
|
16 for name in _order: |
|
17 module = _extensions[name] |
|
18 if module: |
|
19 yield name, module |
|
20 |
|
21 def find(name): |
|
22 '''return module with given extension name''' |
|
23 try: |
|
24 return _extensions[name] |
|
25 except KeyError: |
|
26 for k, v in _extensions.iteritems(): |
|
27 if k.endswith('.' + name) or k.endswith('/' + name): |
|
28 return v |
|
29 raise KeyError(name) |
|
30 |
|
31 def loadpath(path, module_name): |
|
32 module_name = module_name.replace('.', '_') |
|
33 path = util.expandpath(path) |
|
34 if os.path.isdir(path): |
|
35 # module/__init__.py style |
|
36 d, f = os.path.split(path.rstrip('/')) |
|
37 fd, fpath, desc = imp.find_module(f, [d]) |
|
38 return imp.load_module(module_name, fd, fpath, desc) |
|
39 else: |
|
40 return imp.load_source(module_name, path) |
|
41 |
|
42 def load(ui, name, path): |
|
43 # unused ui argument kept for backwards compatibility |
|
44 if name.startswith('hgext.') or name.startswith('hgext/'): |
|
45 shortname = name[6:] |
|
46 else: |
|
47 shortname = name |
|
48 if shortname in _extensions: |
|
49 return _extensions[shortname] |
|
50 _extensions[shortname] = None |
|
51 if path: |
|
52 # the module will be loaded in sys.modules |
|
53 # choose an unique name so that it doesn't |
|
54 # conflicts with other modules |
|
55 mod = loadpath(path, 'hgext.%s' % name) |
|
56 else: |
|
57 def importh(name): |
|
58 mod = __import__(name) |
|
59 components = name.split('.') |
|
60 for comp in components[1:]: |
|
61 mod = getattr(mod, comp) |
|
62 return mod |
|
63 try: |
|
64 mod = importh("hgext.%s" % name) |
|
65 except ImportError: |
|
66 mod = importh(name) |
|
67 _extensions[shortname] = mod |
|
68 _order.append(shortname) |
|
69 return mod |
|
70 |
|
71 def loadall(ui): |
|
72 result = ui.configitems("extensions") |
|
73 newindex = len(_order) |
|
74 for (name, path) in result: |
|
75 if path: |
|
76 if path[0] == '!': |
|
77 continue |
|
78 try: |
|
79 load(ui, name, path) |
|
80 except KeyboardInterrupt: |
|
81 raise |
|
82 except Exception, inst: |
|
83 if path: |
|
84 ui.warn(_("*** failed to import extension %s from %s: %s\n") |
|
85 % (name, path, inst)) |
|
86 else: |
|
87 ui.warn(_("*** failed to import extension %s: %s\n") |
|
88 % (name, inst)) |
|
89 if ui.traceback(): |
|
90 return 1 |
|
91 |
|
92 for name in _order[newindex:]: |
|
93 uisetup = getattr(_extensions[name], 'uisetup', None) |
|
94 if uisetup: |
|
95 uisetup(ui) |
|
96 |
|
97 for name in _order[newindex:]: |
|
98 extsetup = getattr(_extensions[name], 'extsetup', None) |
|
99 if extsetup: |
|
100 try: |
|
101 extsetup(ui) |
|
102 except TypeError: |
|
103 if extsetup.func_code.co_argcount != 0: |
|
104 raise |
|
105 extsetup() # old extsetup with no ui argument |
|
106 |
|
107 def wrapcommand(table, command, wrapper): |
|
108 '''Wrap the command named `command' in table |
|
109 |
|
110 Replace command in the command table with wrapper. The wrapped command will |
|
111 be inserted into the command table specified by the table argument. |
|
112 |
|
113 The wrapper will be called like |
|
114 |
|
115 wrapper(orig, *args, **kwargs) |
|
116 |
|
117 where orig is the original (wrapped) function, and *args, **kwargs |
|
118 are the arguments passed to it. |
|
119 ''' |
|
120 assert hasattr(wrapper, '__call__') |
|
121 aliases, entry = cmdutil.findcmd(command, table) |
|
122 for alias, e in table.iteritems(): |
|
123 if e is entry: |
|
124 key = alias |
|
125 break |
|
126 |
|
127 origfn = entry[0] |
|
128 def wrap(*args, **kwargs): |
|
129 return util.checksignature(wrapper)( |
|
130 util.checksignature(origfn), *args, **kwargs) |
|
131 |
|
132 wrap.__doc__ = getattr(origfn, '__doc__') |
|
133 wrap.__module__ = getattr(origfn, '__module__') |
|
134 |
|
135 newentry = list(entry) |
|
136 newentry[0] = wrap |
|
137 table[key] = tuple(newentry) |
|
138 return entry |
|
139 |
|
140 def wrapfunction(container, funcname, wrapper): |
|
141 '''Wrap the function named funcname in container |
|
142 |
|
143 Replace the funcname member in the given container with the specified |
|
144 wrapper. The container is typically a module, class, or instance. |
|
145 |
|
146 The wrapper will be called like |
|
147 |
|
148 wrapper(orig, *args, **kwargs) |
|
149 |
|
150 where orig is the original (wrapped) function, and *args, **kwargs |
|
151 are the arguments passed to it. |
|
152 |
|
153 Wrapping methods of the repository object is not recommended since |
|
154 it conflicts with extensions that extend the repository by |
|
155 subclassing. All extensions that need to extend methods of |
|
156 localrepository should use this subclassing trick: namely, |
|
157 reposetup() should look like |
|
158 |
|
159 def reposetup(ui, repo): |
|
160 class myrepo(repo.__class__): |
|
161 def whatever(self, *args, **kwargs): |
|
162 [...extension stuff...] |
|
163 super(myrepo, self).whatever(*args, **kwargs) |
|
164 [...extension stuff...] |
|
165 |
|
166 repo.__class__ = myrepo |
|
167 |
|
168 In general, combining wrapfunction() with subclassing does not |
|
169 work. Since you cannot control what other extensions are loaded by |
|
170 your end users, you should play nicely with others by using the |
|
171 subclass trick. |
|
172 ''' |
|
173 assert hasattr(wrapper, '__call__') |
|
174 def wrap(*args, **kwargs): |
|
175 return wrapper(origfn, *args, **kwargs) |
|
176 |
|
177 origfn = getattr(container, funcname) |
|
178 assert hasattr(origfn, '__call__') |
|
179 setattr(container, funcname, wrap) |
|
180 return origfn |
|
181 |
|
182 def _disabledpaths(strip_init=False): |
|
183 '''find paths of disabled extensions. returns a dict of {name: path} |
|
184 removes /__init__.py from packages if strip_init is True''' |
|
185 import hgext |
|
186 extpath = os.path.dirname(os.path.abspath(hgext.__file__)) |
|
187 try: # might not be a filesystem path |
|
188 files = os.listdir(extpath) |
|
189 except OSError: |
|
190 return {} |
|
191 |
|
192 exts = {} |
|
193 for e in files: |
|
194 if e.endswith('.py'): |
|
195 name = e.rsplit('.', 1)[0] |
|
196 path = os.path.join(extpath, e) |
|
197 else: |
|
198 name = e |
|
199 path = os.path.join(extpath, e, '__init__.py') |
|
200 if not os.path.exists(path): |
|
201 continue |
|
202 if strip_init: |
|
203 path = os.path.dirname(path) |
|
204 if name in exts or name in _order or name == '__init__': |
|
205 continue |
|
206 exts[name] = path |
|
207 return exts |
|
208 |
|
209 def _disabledhelp(path): |
|
210 '''retrieve help synopsis of a disabled extension (without importing)''' |
|
211 try: |
|
212 file = open(path) |
|
213 except IOError: |
|
214 return |
|
215 else: |
|
216 doc = help.moduledoc(file) |
|
217 file.close() |
|
218 |
|
219 if doc: # extracting localized synopsis |
|
220 return gettext(doc).splitlines()[0] |
|
221 else: |
|
222 return _('(no help text available)') |
|
223 |
|
224 def disabled(): |
|
225 '''find disabled extensions from hgext |
|
226 returns a dict of {name: desc}, and the max name length''' |
|
227 |
|
228 paths = _disabledpaths() |
|
229 if not paths: |
|
230 return None, 0 |
|
231 |
|
232 exts = {} |
|
233 maxlength = 0 |
|
234 for name, path in paths.iteritems(): |
|
235 doc = _disabledhelp(path) |
|
236 if not doc: |
|
237 continue |
|
238 |
|
239 exts[name] = doc |
|
240 if len(name) > maxlength: |
|
241 maxlength = len(name) |
|
242 |
|
243 return exts, maxlength |
|
244 |
|
245 def disabledext(name): |
|
246 '''find a specific disabled extension from hgext. returns desc''' |
|
247 paths = _disabledpaths() |
|
248 if name in paths: |
|
249 return _disabledhelp(paths[name]) |
|
250 |
|
251 def disabledcmd(cmd, strict=False): |
|
252 '''import disabled extensions until cmd is found. |
|
253 returns (cmdname, extname, doc)''' |
|
254 |
|
255 paths = _disabledpaths(strip_init=True) |
|
256 if not paths: |
|
257 raise error.UnknownCommand(cmd) |
|
258 |
|
259 def findcmd(cmd, name, path): |
|
260 try: |
|
261 mod = loadpath(path, 'hgext.%s' % name) |
|
262 except Exception: |
|
263 return |
|
264 try: |
|
265 aliases, entry = cmdutil.findcmd(cmd, |
|
266 getattr(mod, 'cmdtable', {}), strict) |
|
267 except (error.AmbiguousCommand, error.UnknownCommand): |
|
268 return |
|
269 for c in aliases: |
|
270 if c.startswith(cmd): |
|
271 cmd = c |
|
272 break |
|
273 else: |
|
274 cmd = aliases[0] |
|
275 return (cmd, name, mod) |
|
276 |
|
277 # first, search for an extension with the same name as the command |
|
278 path = paths.pop(cmd, None) |
|
279 if path: |
|
280 ext = findcmd(cmd, cmd, path) |
|
281 if ext: |
|
282 return ext |
|
283 |
|
284 # otherwise, interrogate each extension until there's a match |
|
285 for name, path in paths.iteritems(): |
|
286 ext = findcmd(cmd, name, path) |
|
287 if ext: |
|
288 return ext |
|
289 |
|
290 raise error.UnknownCommand(cmd) |
|
291 |
|
292 def enabled(): |
|
293 '''return a dict of {name: desc} of extensions, and the max name length''' |
|
294 exts = {} |
|
295 maxlength = 0 |
|
296 for ename, ext in extensions(): |
|
297 doc = (gettext(ext.__doc__) or _('(no help text available)')) |
|
298 ename = ename.split('.')[-1] |
|
299 maxlength = max(len(ename), maxlength) |
|
300 exts[ename] = doc.splitlines()[0].strip() |
|
301 |
|
302 return exts, maxlength |