Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
A script to search the undocumented symbols
authorMartin Quinson <martin.quinson@ens-rennes.fr>
Fri, 8 Nov 2019 22:42:43 +0000 (23:42 +0100)
committerMartin Quinson <martin.quinson@ens-rennes.fr>
Sun, 10 Nov 2019 18:09:06 +0000 (19:09 +0100)
docs/find-missing.py [new file with mode: 0755]

diff --git a/docs/find-missing.py b/docs/find-missing.py
new file mode 100755 (executable)
index 0000000..c944e85
--- /dev/null
@@ -0,0 +1,185 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019. The SimGrid Team.
+# All rights reserved.
+
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the license (GNU LGPL) which comes with this package.
+
+"""
+Search for symbols documented in both the XML files produced by Doxygen and the python modules,
+but not documented with autodoxy in the RST files.
+
+This script is tailored to SimGrid own needs and should be made more generic for autodoxy.
+"""
+
+import fnmatch
+import os
+import re
+import sys
+import xml.etree.ElementTree as ET
+import inspect
+
+xml_files = [
+#    'build/xml/classsimgrid_1_1s4u_1_1Activity.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Actor.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Barrier.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Comm.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1ConditionVariable.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Disk.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Engine.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1ExecPar.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1ExecSeq.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Exec.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Host.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Io.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Link.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Mailbox.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Mutex.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1NetZone.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1Semaphore.xml',
+    'build/xml/classsimgrid_1_1s4u_1_1VirtualMachine.xml'
+]
+
+python_modules = [
+    'simgrid'
+]
+python_ignore = [
+    'simgrid.ActorKilled'
+]
+
+
+############ Search the python elements in the source, and report undocumented ones
+############
+
+# data structure in which we store the declaration we find
+python_decl = {}
+
+def handle_python_module(fullname, englobing, elm):
+    """Recursive function exploring the python modules."""
+
+    def found_decl(kind, obj):
+        """Helper function that add an object in the python_decl data structure"""
+        if not kind in python_decl: python_decl[kind] = []
+        python_decl[kind].append(obj)
+
+
+    if fullname in python_ignore:
+        print ("Ignore Python symbol '{}' as requested.".format(fullname))
+        return
+
+    if inspect.isroutine(elm) and inspect.isclass(englobing):
+        found_decl("method", fullname)
+#        print('.. automethod:: {}'.format(fullname))
+    elif inspect.isroutine(elm) and (not inspect.isclass(englobing)):
+        found_decl("function", fullname)
+#        print('.. autofunction:: {}'.format(fullname))
+    elif inspect.isdatadescriptor(elm):
+        found_decl("attribute", fullname)
+#        print('.. autoattribute:: {}'.format(fullname))
+    elif isinstance(elm, str) or isinstance(elm, int): # We do have such a data, directly in the SimGrid top module
+        found_decl("data", fullname)
+#        print('.. autodata:: {}'.format(fullname))
+    elif inspect.ismodule(elm) or inspect.isclass(elm):
+        for name, data in inspect.getmembers(elm):
+            if name.startswith('__'):
+                continue
+#            print("Recurse on {}.{}".format(fullname, name))
+            handle_python_module("{}.{}".format(fullname, name), elm, data)
+    else:
+        print('UNHANDLED TYPE {} : {!r} Type: {}'.format(fullname, elm, type(elm)))
+
+# Start the recursion on the provided Python modules
+for name in python_modules:
+    module = __import__(name)
+    for sub in dir(module):
+        if sub[0] == '_':
+            continue
+        handle_python_module("{}.{}".format(name, sub), module, getattr(module, sub))
+
+# Forget about the python declarations that were actually done in the RST
+for kind in python_decl:
+    with os.popen('grep \'[[:blank:]]*auto{}::\' source/*rst|sed \'s/^.*auto{}:: //\''.format(kind, kind)) as pse:
+        for fullname in (l.strip() for l in pse):
+            if not fullname in python_decl[kind]:
+                print("Warning: {} documented but declaration not found in python.".format(fullname))
+            else:
+                python_decl[kind].remove(fullname)
+# Dump the missing ones
+for kind in python_decl:
+    for fullname in python_decl[kind]:
+        print("Missing decl: .. auto{}:: {}".format(kind, fullname))
+
+################ And now deal with Doxygen declarations
+################
+
+doxy_funs = {} # {classname: {func_name: [args]} }
+doxy_vars = {} # {classname: [names]}
+
+# find the declarations in the XML files
+for arg in xml_files[:1]:
+    if arg[-4:] != '.xml':
+        print ("Argument '{}' does not end with '.xml'".format(arg))
+        continue
+    print("Parse file {}".format(arg))
+    tree = ET.parse(arg)
+    for elem in tree.findall(".//compounddef"):
+        if elem.attrib["prot"] != "public":
+            continue
+        if "compoundname" in elem:
+            raise Exception("Compound {} has no 'compoundname' child tag.".format(elem))
+        compoundname = elem.find("compoundname").text
+        #print ("compoundname {}".format(compoundname))
+        for member in elem.findall('.//memberdef'):
+            if member.attrib["prot"] != "public":
+                continue
+            kind = member.attrib["kind"]
+            name = member.find("name").text
+            if kind == "variable":
+                if not compoundname in doxy_vars: doxy_vars[compoundname] = []
+                doxy_vars[compoundname].append(name)
+            elif kind == "function":
+                args = member.find('argsstring').text
+                args = re.sub('\)[^)]*$', ')', args) # ignore what's after the parameters (eg, '=0' or ' const')
+
+                if not compoundname in doxy_funs: doxy_funs[compoundname] = {}
+                if not name in doxy_funs[compoundname]: doxy_funs[compoundname][name] = []
+                doxy_funs[compoundname][name].append(args)
+            else:
+                print ("member {}::{} is of kind {}".format(compoundname, name, kind))
+
+# Forget about the declarations that are done in the RST
+with os.popen('grep autodoxymethod:: source/*rst|sed \'s/^.*autodoxymethod:: //\'') as pse:
+    for line in (l.strip() for l in pse):
+        (klass, obj, args) = (None, None, None)
+        if "(" in line:
+            (line, args) = line.split('(', 1)
+            args = "({}".format(args)
+        (klass, obj) = line.rsplit('::', 1)
+
+        if not klass in doxy_funs:
+            print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
+            continue
+        if not obj in doxy_funs[klass]:
+            print("Warning: Object {} documented but not found in {}".format(line, klass))
+        elif len(doxy_funs[klass][obj])==1:
+            del doxy_funs[klass][obj]
+        elif not args in doxy_funs[klass][obj]:
+            print("Warning: Function {}{} not found in {}".format(obj, args, klass))
+        else:
+#            print("Found {} in {}".format(line, klass))
+            doxy_funs[klass][obj].remove(args)
+            if len(doxy_funs[klass][obj]) == 0:
+                del doxy_funs[klass][obj]
+
+# Dump the undocumented Doxygen declarations 
+for obj in doxy_funs:
+    for meth in doxy_funs[obj]:
+        for args in doxy_funs[obj][meth]:
+            print("Missing decl: .. autodoxymethod:: {}::{}{}".format(obj, meth, args))
+
+for obj in doxy_vars:
+    for meth in doxy_vars[obj]:
+        print("Missing decl: .. autodoxyfield:: {}::{}".format(obj, meth))
+