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