[PATCH 1 of 1] extensions: extract doc using python facilities instead of custom parser
Cédric Duval
cedricduval at free.fr
Fri Aug 7 14:46:59 CDT 2009
# HG changeset patch
# User Cédric Duval <cedricduval at free.fr>
# Date 1248811268 -7200
# Node ID f21de9bbc4374078d1d82bc4ede060244d6eed4f
# Parent 9a69ab6d7cf765c1a3ed43ae8b0e5d770c6eacf6
extensions: extract doc using python facilities instead of custom parser
For disabled extensions, the docstring is obtained using the extension's
code object. This way, no code needs to be imported/executed.
Special handling is required for zip libraries as imp.find_module does
not provide transparent support for them.
Thanks to Henrik Stuart for the marshal/compile trick and persistence
in looking for solutions avoiding module import.
diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -5,8 +5,9 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.
-import imp, os
-import util, cmdutil, help
+import imp, marshal, os, sys, zipimport
+import util, cmdutil
+from opcode import HAVE_ARGUMENT
from i18n import _, gettext
_extensions = {}
@@ -118,6 +119,81 @@
setattr(container, funcname, wrap)
return origfn
+def listexts(path):
+ '''return a set of extensions from a given path'''
+ exts = set()
+ path = os.path.abspath(path)
+ try: # might not be a filesystem path
+ list = os.listdir(path)
+ except OSError:
+ return exts
+
+ for e in list:
+ if e.endswith('.py') and e != '__init__.py':
+ exts.add(e.rsplit('.', 1)[0])
+ else:
+ f = os.path.join(path, e, '__init__.py')
+ if os.path.exists(f):
+ exts.add(e)
+ return exts
+
+def getcode(name):
+ '''return the code object of a given extension'''
+ for path in sys.path:
+ if path.endswith('.zip'):
+ z = zipimport.zipimporter('%s/hgext' % path)
+ try:
+ return z.get_code(name)
+ except ImportError:
+ continue
+ else:
+ try:
+ extpath = imp.find_module('hgext', [path])[1]
+ fh, extpath, desc = imp.find_module(name, [extpath])
+ if not fh:
+ fh, path, desc = imp.find_module('__init__', [extpath])
+ except ImportError:
+ continue
+
+ if desc[0] == '.py':
+ return compile(fh.read(), path, 'exec')
+ elif desc[0] == '.pyc':
+ fh.read(8)
+ return marshal.load(fh)
+
+def codedoc(code):
+ '''return the docstring for a given code object'''
+ co = [ord(c) for c in code.co_code]
+ codelen = len(co)
+ i = 0
+ idx = None
+ while i != codelen:
+ opcode = co[i]
+ if opcode == 100: # LOAD_CONST
+ idx = co[i+1] + (co[i+2] << 8)
+ elif idx is not None and opcode == 90: # STORE_NAME
+ if code.co_names[co[i+1] + (co[i+2] << 8)] == '__doc__':
+ return code.co_consts[idx]
+ else:
+ idx = None
+ i += 1
+ if opcode >= HAVE_ARGUMENT:
+ i += 2
+
+def rawdoc(name):
+ '''return the untranslated docstring or None for a given extension'''
+ try:
+ return find(name).__doc__
+ except KeyError: # not loaded
+ code = getcode(name)
+ if code is not None:
+ return codedoc(code)
+ return None
+
+def doc(name):
+ '''return the translated docstring of a given extension'''
+ return gettext(rawdoc(name)) or _('(no help text available)')
+
def disabled():
'''find disabled extensions from hgext
returns a dict of {name: desc}, and the max name length'''
@@ -125,39 +201,13 @@
import hgext
extpath = os.path.dirname(os.path.abspath(hgext.__file__))
- try: # might not be a filesystem path
- files = os.listdir(extpath)
- except OSError:
- return None, 0
-
exts = {}
maxlength = 0
- for e in files:
-
- if e.endswith('.py'):
- name = e.rsplit('.', 1)[0]
- path = os.path.join(extpath, e)
- else:
- name = e
- path = os.path.join(extpath, e, '__init__.py')
- if not os.path.exists(path):
- continue
-
- if name in exts or name in _order or name == '__init__':
+ for name in listexts(extpath):
+ if name in _order:
continue
- try:
- file = open(path)
- except IOError:
- continue
- else:
- doc = help.moduledoc(file)
- file.close()
-
- if doc: # extracting localized synopsis
- exts[name] = gettext(doc).splitlines()[0]
- else:
- exts[name] = _('(no help text available)')
+ exts[name] = doc(name).splitlines()[0]
if len(name) > maxlength:
maxlength = len(name)
diff --git a/mercurial/help.py b/mercurial/help.py
--- a/mercurial/help.py
+++ b/mercurial/help.py
@@ -9,36 +9,6 @@
import extensions, util
-def moduledoc(file):
- '''return the top-level python documentation for the given file
-
- Loosely inspired by pydoc.source_synopsis(), but rewritten to handle \'''
- as well as """ and to return the whole text instead of just the synopsis'''
- result = []
-
- line = file.readline()
- while line[:1] == '#' or not line.strip():
- line = file.readline()
- if not line: break
-
- start = line[:3]
- if start == '"""' or start == "'''":
- line = line[3:]
- while line:
- if line.rstrip().endswith(start):
- line = line.split(start)[0]
- if line:
- result.append(line)
- break
- elif not line:
- return None # unmatched delimiter
- result.append(line)
- line = file.readline()
- else:
- return None
-
- return ''.join(result)
-
def listexts(header, exts, maxlength):
'''return a text listing of the given extensions'''
if not exts:
More information about the Mercurial-devel
mailing list