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