##// END OF EJS Templates
extensions: add docstring for wrapcommand().
Dan Villiom Podlaski Christiansen -
r11519:bbdf1fb1 stable
parent child Browse files
Show More
@@ -1,286 +1,298
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 '''Wrap the command named `command' in table
108
109 Replace command in the command table with wrapper. The wrapped command will
110 be inserted into the command table specified by the table argument.
111
112 The wrapper will be called like
113
114 wrapper(orig, *args, **kwargs)
115
116 where orig is the original (wrapped) function, and *args, **kwargs
117 are the arguments passed to it.
118 '''
107 aliases, entry = cmdutil.findcmd(command, table)
119 aliases, entry = cmdutil.findcmd(command, table)
108 for alias, e in table.iteritems():
120 for alias, e in table.iteritems():
109 if e is entry:
121 if e is entry:
110 key = alias
122 key = alias
111 break
123 break
112
124
113 origfn = entry[0]
125 origfn = entry[0]
114 def wrap(*args, **kwargs):
126 def wrap(*args, **kwargs):
115 return util.checksignature(wrapper)(
127 return util.checksignature(wrapper)(
116 util.checksignature(origfn), *args, **kwargs)
128 util.checksignature(origfn), *args, **kwargs)
117
129
118 wrap.__doc__ = getattr(origfn, '__doc__')
130 wrap.__doc__ = getattr(origfn, '__doc__')
119 wrap.__module__ = getattr(origfn, '__module__')
131 wrap.__module__ = getattr(origfn, '__module__')
120
132
121 newentry = list(entry)
133 newentry = list(entry)
122 newentry[0] = wrap
134 newentry[0] = wrap
123 table[key] = tuple(newentry)
135 table[key] = tuple(newentry)
124 return entry
136 return entry
125
137
126 def wrapfunction(container, funcname, wrapper):
138 def wrapfunction(container, funcname, wrapper):
127 '''Wrap the function named funcname in container
139 '''Wrap the function named funcname in container
128
140
129 It is replacing with your wrapper. The container is typically a
141 It is replacing with your wrapper. The container is typically a
130 module, class, or instance.
142 module, class, or instance.
131
143
132 The wrapper will be called like
144 The wrapper will be called like
133
145
134 wrapper(orig, *args, **kwargs)
146 wrapper(orig, *args, **kwargs)
135
147
136 where orig is the original (wrapped) function, and *args, **kwargs
148 where orig is the original (wrapped) function, and *args, **kwargs
137 are the arguments passed to it.
149 are the arguments passed to it.
138
150
139 Wrapping methods of the repository object is not recommended since
151 Wrapping methods of the repository object is not recommended since
140 it conflicts with extensions that extend the repository by
152 it conflicts with extensions that extend the repository by
141 subclassing. All extensions that need to extend methods of
153 subclassing. All extensions that need to extend methods of
142 localrepository should use this subclassing trick: namely,
154 localrepository should use this subclassing trick: namely,
143 reposetup() should look like
155 reposetup() should look like
144
156
145 def reposetup(ui, repo):
157 def reposetup(ui, repo):
146 class myrepo(repo.__class__):
158 class myrepo(repo.__class__):
147 def whatever(self, *args, **kwargs):
159 def whatever(self, *args, **kwargs):
148 [...extension stuff...]
160 [...extension stuff...]
149 super(myrepo, self).whatever(*args, **kwargs)
161 super(myrepo, self).whatever(*args, **kwargs)
150 [...extension stuff...]
162 [...extension stuff...]
151
163
152 repo.__class__ = myrepo
164 repo.__class__ = myrepo
153
165
154 In general, combining wrapfunction() with subclassing does not
166 In general, combining wrapfunction() with subclassing does not
155 work. Since you cannot control what other extensions are loaded by
167 work. Since you cannot control what other extensions are loaded by
156 your end users, you should play nicely with others by using the
168 your end users, you should play nicely with others by using the
157 subclass trick.
169 subclass trick.
158 '''
170 '''
159 def wrap(*args, **kwargs):
171 def wrap(*args, **kwargs):
160 return wrapper(origfn, *args, **kwargs)
172 return wrapper(origfn, *args, **kwargs)
161
173
162 origfn = getattr(container, funcname)
174 origfn = getattr(container, funcname)
163 setattr(container, funcname, wrap)
175 setattr(container, funcname, wrap)
164 return origfn
176 return origfn
165
177
166 def _disabledpaths(strip_init=False):
178 def _disabledpaths(strip_init=False):
167 '''find paths of disabled extensions. returns a dict of {name: path}
179 '''find paths of disabled extensions. returns a dict of {name: path}
168 removes /__init__.py from packages if strip_init is True'''
180 removes /__init__.py from packages if strip_init is True'''
169 import hgext
181 import hgext
170 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
182 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
171 try: # might not be a filesystem path
183 try: # might not be a filesystem path
172 files = os.listdir(extpath)
184 files = os.listdir(extpath)
173 except OSError:
185 except OSError:
174 return {}
186 return {}
175
187
176 exts = {}
188 exts = {}
177 for e in files:
189 for e in files:
178 if e.endswith('.py'):
190 if e.endswith('.py'):
179 name = e.rsplit('.', 1)[0]
191 name = e.rsplit('.', 1)[0]
180 path = os.path.join(extpath, e)
192 path = os.path.join(extpath, e)
181 else:
193 else:
182 name = e
194 name = e
183 path = os.path.join(extpath, e, '__init__.py')
195 path = os.path.join(extpath, e, '__init__.py')
184 if not os.path.exists(path):
196 if not os.path.exists(path):
185 continue
197 continue
186 if strip_init:
198 if strip_init:
187 path = os.path.dirname(path)
199 path = os.path.dirname(path)
188 if name in exts or name in _order or name == '__init__':
200 if name in exts or name in _order or name == '__init__':
189 continue
201 continue
190 exts[name] = path
202 exts[name] = path
191 return exts
203 return exts
192
204
193 def _disabledhelp(path):
205 def _disabledhelp(path):
194 '''retrieve help synopsis of a disabled extension (without importing)'''
206 '''retrieve help synopsis of a disabled extension (without importing)'''
195 try:
207 try:
196 file = open(path)
208 file = open(path)
197 except IOError:
209 except IOError:
198 return
210 return
199 else:
211 else:
200 doc = help.moduledoc(file)
212 doc = help.moduledoc(file)
201 file.close()
213 file.close()
202
214
203 if doc: # extracting localized synopsis
215 if doc: # extracting localized synopsis
204 return gettext(doc).splitlines()[0]
216 return gettext(doc).splitlines()[0]
205 else:
217 else:
206 return _('(no help text available)')
218 return _('(no help text available)')
207
219
208 def disabled():
220 def disabled():
209 '''find disabled extensions from hgext
221 '''find disabled extensions from hgext
210 returns a dict of {name: desc}, and the max name length'''
222 returns a dict of {name: desc}, and the max name length'''
211
223
212 paths = _disabledpaths()
224 paths = _disabledpaths()
213 if not paths:
225 if not paths:
214 return None, 0
226 return None, 0
215
227
216 exts = {}
228 exts = {}
217 maxlength = 0
229 maxlength = 0
218 for name, path in paths.iteritems():
230 for name, path in paths.iteritems():
219 doc = _disabledhelp(path)
231 doc = _disabledhelp(path)
220 if not doc:
232 if not doc:
221 continue
233 continue
222
234
223 exts[name] = doc
235 exts[name] = doc
224 if len(name) > maxlength:
236 if len(name) > maxlength:
225 maxlength = len(name)
237 maxlength = len(name)
226
238
227 return exts, maxlength
239 return exts, maxlength
228
240
229 def disabledext(name):
241 def disabledext(name):
230 '''find a specific disabled extension from hgext. returns desc'''
242 '''find a specific disabled extension from hgext. returns desc'''
231 paths = _disabledpaths()
243 paths = _disabledpaths()
232 if name in paths:
244 if name in paths:
233 return _disabledhelp(paths[name])
245 return _disabledhelp(paths[name])
234
246
235 def disabledcmd(cmd, strict=False):
247 def disabledcmd(cmd, strict=False):
236 '''import disabled extensions until cmd is found.
248 '''import disabled extensions until cmd is found.
237 returns (cmdname, extname, doc)'''
249 returns (cmdname, extname, doc)'''
238
250
239 paths = _disabledpaths(strip_init=True)
251 paths = _disabledpaths(strip_init=True)
240 if not paths:
252 if not paths:
241 raise error.UnknownCommand(cmd)
253 raise error.UnknownCommand(cmd)
242
254
243 def findcmd(cmd, name, path):
255 def findcmd(cmd, name, path):
244 try:
256 try:
245 mod = loadpath(path, 'hgext.%s' % name)
257 mod = loadpath(path, 'hgext.%s' % name)
246 except Exception:
258 except Exception:
247 return
259 return
248 try:
260 try:
249 aliases, entry = cmdutil.findcmd(cmd,
261 aliases, entry = cmdutil.findcmd(cmd,
250 getattr(mod, 'cmdtable', {}), strict)
262 getattr(mod, 'cmdtable', {}), strict)
251 except (error.AmbiguousCommand, error.UnknownCommand):
263 except (error.AmbiguousCommand, error.UnknownCommand):
252 return
264 return
253 for c in aliases:
265 for c in aliases:
254 if c.startswith(cmd):
266 if c.startswith(cmd):
255 cmd = c
267 cmd = c
256 break
268 break
257 else:
269 else:
258 cmd = aliases[0]
270 cmd = aliases[0]
259 return (cmd, name, mod)
271 return (cmd, name, mod)
260
272
261 # first, search for an extension with the same name as the command
273 # first, search for an extension with the same name as the command
262 path = paths.pop(cmd, None)
274 path = paths.pop(cmd, None)
263 if path:
275 if path:
264 ext = findcmd(cmd, cmd, path)
276 ext = findcmd(cmd, cmd, path)
265 if ext:
277 if ext:
266 return ext
278 return ext
267
279
268 # otherwise, interrogate each extension until there's a match
280 # otherwise, interrogate each extension until there's a match
269 for name, path in paths.iteritems():
281 for name, path in paths.iteritems():
270 ext = findcmd(cmd, name, path)
282 ext = findcmd(cmd, name, path)
271 if ext:
283 if ext:
272 return ext
284 return ext
273
285
274 raise error.UnknownCommand(cmd)
286 raise error.UnknownCommand(cmd)
275
287
276 def enabled():
288 def enabled():
277 '''return a dict of {name: desc} of extensions, and the max name length'''
289 '''return a dict of {name: desc} of extensions, and the max name length'''
278 exts = {}
290 exts = {}
279 maxlength = 0
291 maxlength = 0
280 for ename, ext in extensions():
292 for ename, ext in extensions():
281 doc = (gettext(ext.__doc__) or _('(no help text available)'))
293 doc = (gettext(ext.__doc__) or _('(no help text available)'))
282 ename = ename.split('.')[-1]
294 ename = ename.split('.')[-1]
283 maxlength = max(len(ename), maxlength)
295 maxlength = max(len(ename), maxlength)
284 exts[ename] = doc.splitlines()[0].strip()
296 exts[ename] = doc.splitlines()[0].strip()
285
297
286 return exts, maxlength
298 return exts, maxlength
General Comments 0
You need to be logged in to leave comments. Login now