Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
c944e8532b0ebfae3e2953be51c25e68c3ad1bfc
[simgrid.git] / docs / find-missing.py
1 #! /usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 # Copyright (c) 2019. The SimGrid Team.
5 # All rights reserved.
6
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the license (GNU LGPL) which comes with this package.
9
10 """
11 Search for symbols documented in both the XML files produced by Doxygen and the python modules,
12 but not documented with autodoxy in the RST files.
13
14 This script is tailored to SimGrid own needs and should be made more generic for autodoxy.
15 """
16
17 import fnmatch
18 import os
19 import re
20 import sys
21 import xml.etree.ElementTree as ET
22 import inspect
23
24 xml_files = [
25 #    'build/xml/classsimgrid_1_1s4u_1_1Activity.xml',
26     'build/xml/classsimgrid_1_1s4u_1_1Actor.xml',
27     'build/xml/classsimgrid_1_1s4u_1_1Barrier.xml',
28     'build/xml/classsimgrid_1_1s4u_1_1Comm.xml',
29     'build/xml/classsimgrid_1_1s4u_1_1ConditionVariable.xml',
30     'build/xml/classsimgrid_1_1s4u_1_1Disk.xml',
31     'build/xml/classsimgrid_1_1s4u_1_1Engine.xml',
32     'build/xml/classsimgrid_1_1s4u_1_1ExecPar.xml',
33     'build/xml/classsimgrid_1_1s4u_1_1ExecSeq.xml',
34     'build/xml/classsimgrid_1_1s4u_1_1Exec.xml',
35     'build/xml/classsimgrid_1_1s4u_1_1Host.xml',
36     'build/xml/classsimgrid_1_1s4u_1_1Io.xml',
37     'build/xml/classsimgrid_1_1s4u_1_1Link.xml',
38     'build/xml/classsimgrid_1_1s4u_1_1Mailbox.xml',
39     'build/xml/classsimgrid_1_1s4u_1_1Mutex.xml',
40     'build/xml/classsimgrid_1_1s4u_1_1NetZone.xml',
41     'build/xml/classsimgrid_1_1s4u_1_1Semaphore.xml',
42     'build/xml/classsimgrid_1_1s4u_1_1VirtualMachine.xml'
43 ]
44
45 python_modules = [
46     'simgrid'
47 ]
48 python_ignore = [
49     'simgrid.ActorKilled'
50 ]
51
52
53 ############ Search the python elements in the source, and report undocumented ones
54 ############
55
56 # data structure in which we store the declaration we find
57 python_decl = {}
58
59 def handle_python_module(fullname, englobing, elm):
60     """Recursive function exploring the python modules."""
61
62     def found_decl(kind, obj):
63         """Helper function that add an object in the python_decl data structure"""
64         if not kind in python_decl: python_decl[kind] = []
65         python_decl[kind].append(obj)
66
67
68     if fullname in python_ignore:
69         print ("Ignore Python symbol '{}' as requested.".format(fullname))
70         return
71
72     if inspect.isroutine(elm) and inspect.isclass(englobing):
73         found_decl("method", fullname)
74 #        print('.. automethod:: {}'.format(fullname))
75     elif inspect.isroutine(elm) and (not inspect.isclass(englobing)):
76         found_decl("function", fullname)
77 #        print('.. autofunction:: {}'.format(fullname))
78     elif inspect.isdatadescriptor(elm):
79         found_decl("attribute", fullname)
80 #        print('.. autoattribute:: {}'.format(fullname))
81     elif isinstance(elm, str) or isinstance(elm, int): # We do have such a data, directly in the SimGrid top module
82         found_decl("data", fullname)
83 #        print('.. autodata:: {}'.format(fullname))
84     elif inspect.ismodule(elm) or inspect.isclass(elm):
85         for name, data in inspect.getmembers(elm):
86             if name.startswith('__'):
87                 continue
88 #            print("Recurse on {}.{}".format(fullname, name))
89             handle_python_module("{}.{}".format(fullname, name), elm, data)
90     else:
91         print('UNHANDLED TYPE {} : {!r} Type: {}'.format(fullname, elm, type(elm)))
92
93 # Start the recursion on the provided Python modules
94 for name in python_modules:
95     module = __import__(name)
96     for sub in dir(module):
97         if sub[0] == '_':
98             continue
99         handle_python_module("{}.{}".format(name, sub), module, getattr(module, sub))
100
101 # Forget about the python declarations that were actually done in the RST
102 for kind in python_decl:
103     with os.popen('grep \'[[:blank:]]*auto{}::\' source/*rst|sed \'s/^.*auto{}:: //\''.format(kind, kind)) as pse:
104         for fullname in (l.strip() for l in pse):
105             if not fullname in python_decl[kind]:
106                 print("Warning: {} documented but declaration not found in python.".format(fullname))
107             else:
108                 python_decl[kind].remove(fullname)
109 # Dump the missing ones
110 for kind in python_decl:
111     for fullname in python_decl[kind]:
112         print("Missing decl: .. auto{}:: {}".format(kind, fullname))
113
114 ################ And now deal with Doxygen declarations
115 ################
116
117 doxy_funs = {} # {classname: {func_name: [args]} }
118 doxy_vars = {} # {classname: [names]}
119
120 # find the declarations in the XML files
121 for arg in xml_files[:1]:
122     if arg[-4:] != '.xml':
123         print ("Argument '{}' does not end with '.xml'".format(arg))
124         continue
125     print("Parse file {}".format(arg))
126     tree = ET.parse(arg)
127     for elem in tree.findall(".//compounddef"):
128         if elem.attrib["prot"] != "public":
129             continue
130         if "compoundname" in elem:
131             raise Exception("Compound {} has no 'compoundname' child tag.".format(elem))
132         compoundname = elem.find("compoundname").text
133         #print ("compoundname {}".format(compoundname))
134         for member in elem.findall('.//memberdef'):
135             if member.attrib["prot"] != "public":
136                 continue
137             kind = member.attrib["kind"]
138             name = member.find("name").text
139             if kind == "variable":
140                 if not compoundname in doxy_vars: doxy_vars[compoundname] = []
141                 doxy_vars[compoundname].append(name)
142             elif kind == "function":
143                 args = member.find('argsstring').text
144                 args = re.sub('\)[^)]*$', ')', args) # ignore what's after the parameters (eg, '=0' or ' const')
145
146                 if not compoundname in doxy_funs: doxy_funs[compoundname] = {}
147                 if not name in doxy_funs[compoundname]: doxy_funs[compoundname][name] = []
148                 doxy_funs[compoundname][name].append(args)
149             else:
150                 print ("member {}::{} is of kind {}".format(compoundname, name, kind))
151
152 # Forget about the declarations that are done in the RST
153 with os.popen('grep autodoxymethod:: source/*rst|sed \'s/^.*autodoxymethod:: //\'') as pse:
154     for line in (l.strip() for l in pse):
155         (klass, obj, args) = (None, None, None)
156         if "(" in line:
157             (line, args) = line.split('(', 1)
158             args = "({}".format(args)
159         (klass, obj) = line.rsplit('::', 1)
160
161         if not klass in doxy_funs:
162             print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
163             continue
164         if not obj in doxy_funs[klass]:
165             print("Warning: Object {} documented but not found in {}".format(line, klass))
166         elif len(doxy_funs[klass][obj])==1:
167             del doxy_funs[klass][obj]
168         elif not args in doxy_funs[klass][obj]:
169             print("Warning: Function {}{} not found in {}".format(obj, args, klass))
170         else:
171 #            print("Found {} in {}".format(line, klass))
172             doxy_funs[klass][obj].remove(args)
173             if len(doxy_funs[klass][obj]) == 0:
174                 del doxy_funs[klass][obj]
175
176 # Dump the undocumented Doxygen declarations 
177 for obj in doxy_funs:
178     for meth in doxy_funs[obj]:
179         for args in doxy_funs[obj][meth]:
180             print("Missing decl: .. autodoxymethod:: {}::{}{}".format(obj, meth, args))
181
182 for obj in doxy_vars:
183     for meth in doxy_vars[obj]:
184         print("Missing decl: .. autodoxyfield:: {}::{}".format(obj, meth))
185