]> granicus.if.org Git - python/commitdiff
Add tool to check documentation against declaration.
authorMartin v. Löwis <martin@v.loewis.de>
Wed, 1 Mar 2006 05:18:07 +0000 (05:18 +0000)
committerMartin v. Löwis <martin@v.loewis.de>
Wed, 1 Mar 2006 05:18:07 +0000 (05:18 +0000)
Doc/tools/cmpcsyms [new file with mode: 0755]

diff --git a/Doc/tools/cmpcsyms b/Doc/tools/cmpcsyms
new file mode 100755 (executable)
index 0000000..55f9954
--- /dev/null
@@ -0,0 +1,157 @@
+#! /usr/bin/env python
+from __future__ import with_statement
+import errno
+import os
+import re
+import sys
+import string
+
+if __name__ == "__main__":
+    _base = sys.argv[0]
+else:
+    _base = __file__
+
+_script_home = os.path.abspath(os.path.dirname(_base))
+
+srcdir = os.path.dirname(os.path.dirname(_script_home))
+
+EXCLUDES = ["bitset.h", "cStringIO.h", "graminit.h", "grammar.h",
+            "longintrepr.h", "metagrammar.h",
+            "node.h", "opcode.h", "osdefs.h", "pgenheaders.h",
+            "py_curses.h", "parsetok.h", "symtable.h", "token.h"]
+
+
+def list_headers():
+    """Return a list of headers."""
+    incdir = os.path.join(srcdir, "Include")
+    return [os.path.join(incdir, fn) for fn in os.listdir(incdir)
+            if fn.endswith(".h") and fn not in EXCLUDES]
+
+
+def matcher(pattern):
+    return re.compile(pattern).search
+
+MATCHERS = [
+    # XXX this should also deal with ctypedesc, cvardesc and cmemberdesc
+    matcher(r"\\begin\{cfuncdesc\}\{(?P<result>[^}]*)\}\{(?P<sym>[^}]*)\}{(?P<params>[^}]*)\}"),
+    matcher(r"\\cfuncline\{(?P<result>[^})]*)\}\{(?P<sym>[^}]*)\}{(?P<params>[^}]*)\}"),
+    ]
+
+def list_documented_items():
+    """Return a list of everything that's already documented."""
+    apidir = os.path.join(srcdir, "Doc", "api")
+    files = [fn for fn in os.listdir(apidir) if fn.endswith(".tex")]
+    L = []
+    for fn in files:
+        fullname = os.path.join(apidir, fn)
+       data = open(fullname).read()
+        for matcher in MATCHERS:
+            pos = 0
+            while 1:
+                m = matcher(data, pos)
+                if not m: break
+                pos = m.end()
+                sym = m.group("sym")
+                result = m.group("result")
+                params = m.group("params")
+               # replace all whitespace with a single one
+               params = " ".join(params.split()) 
+                L.append((sym, result, params, fn))
+    return L
+
+def normalize_type(t):
+    t = t.strip()
+    s = t.rfind("*")
+    if s != -1:
+        # strip everything after the pointer name
+        t = t[:s+1]
+    # Drop the variable name
+    s = t.split()
+    typenames = 1
+    if len(s)>1 and s[0]=='unsigned' and s[1]=='int':
+        typenames = 2
+    if len(s) > typenames and s[-1][0] in string.letters:
+        del s[-1]
+    if not s:
+       print "XXX", t
+       return ""
+    # Drop register
+    if s[0] == "register":
+        del s[0]
+    # discard all spaces
+    return ''.join(s)
+    
+def compare_type(t1, t2):
+    t1 = normalize_type(t1)
+    t2 = normalize_type(t2)
+    if t1 == r'\moreargs' and t2 == '...':
+        return False
+    if t1 != t2:
+        #print "different:", t1, t2
+        return False
+    return True
+
+
+def compare_types(ret, params, hret, hparams):
+    if not compare_type(ret, hret):
+        return False
+    params = params.split(",")
+    hparams = hparams.split(",")
+    if not params and hparams == ['void']:
+        return True
+    if not hparams and params == ['void']:
+        return True
+    if len(params) != len(hparams):
+        return False
+    for p1, p2 in zip(params, hparams):
+        if not compare_type(p1, p2):
+            return False
+    return True
+
+def main():
+    headers = list_headers()
+    documented = list_documented_items()
+
+    lines = []
+    for h in headers:
+        data = open(h).read()
+        data, n = re.subn(r"PyAPI_FUNC\(([^)]*)\)", r"\1", data)
+        name = os.path.basename(h)
+        with open(name, "w") as f:
+            f.write(data)
+        cmd = ("ctags -f - --file-scope=no --c-kinds=p --fields=S "
+               "-Istaticforward -Istatichere=static " + name)
+        with os.popen(cmd) as f:
+            lines.extend(f.readlines())
+        os.unlink(name)
+    L = {}
+    prevsym = None
+    for line in lines:
+        if not line:
+            break
+        sym, filename, signature = line.split(None, 2)
+        if sym == prevsym:
+            continue
+       expr = "\^(.*)%s" % sym
+       m = re.search(expr, signature)
+        if not m:
+           print "Could not split",signature, "using",expr
+       rettype = m.group(1).strip()
+       m = re.search("signature:\(([^)]*)\)", signature)
+       if not m:
+           print "Could not get signature from", signature
+       params = m.group(1)
+       L[sym] = (rettype, params)
+
+    for sym, ret, params, fn in documented:
+        if sym not in L:
+           print "No declaration for '%s'" % sym
+           continue
+        hret, hparams = L[sym]
+        if not compare_types(ret, params, hret, hparams):
+           print "Declaration error for %s (%s):" % (sym, fn)
+           print ret+": "+params
+           print hret+": "+hparams
+
+if __name__ == "__main__":
+    main()