Logo AND Algorithmique Numérique Distribuée

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