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