Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Replace non-breaking spaces.
[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 kind not 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     try:
96         module = __import__(name)
97     except Exception:
98         print("Cannot import {}. Did you set PYTHONPATH=../lib accordingly?".format(name))
99         sys.exit(1)
100     for sub in dir(module):
101         if sub[0] == '_':
102             continue
103         handle_python_module("{}.{}".format(name, sub), module, getattr(module, sub))
104
105 # Forget about the python declarations that were actually done in the RST
106 for kind in python_decl:
107     with os.popen('grep \'[[:blank:]]*auto{}::\' source/*rst|sed \'s/^.*auto{}:: //\''.format(kind, kind)) as pse:
108         for fullname in (l.strip() for l in pse):
109             if fullname not in python_decl[kind]:
110                 print("Warning: {} documented but declaration not found in python.".format(fullname))
111             else:
112                 python_decl[kind].remove(fullname)
113 # Dump the missing ones
114 for kind in python_decl:
115     for fullname in python_decl[kind]:
116         print(" .. auto{}:: {}".format(kind, fullname))
117
118 ################ And now deal with Doxygen declarations
119 ################
120
121 doxy_funs = {} # {classname: {func_name: [args]} }
122 doxy_vars = {} # {classname: [names]}
123
124 # find the declarations in the XML files
125 for arg in xml_files[:3]:
126     if arg[-4:] != '.xml':
127         print ("Argument '{}' does not end with '.xml'".format(arg))
128         continue
129     print("Parse file {}".format(arg))
130     tree = ET.parse(arg)
131     for elem in tree.findall(".//compounddef"):
132         if elem.attrib["prot"] != "public":
133             continue
134         if "compoundname" in elem:
135             raise Exception("Compound {} has no 'compoundname' child tag.".format(elem))
136         compoundname = elem.find("compoundname").text
137         #print ("compoundname {}".format(compoundname))
138         for member in elem.findall('.//memberdef'):
139             if member.attrib["prot"] != "public":
140                 continue
141             kind = member.attrib["kind"]
142             name = member.find("name").text
143             if kind == "variable":
144                 if compoundname not in doxy_vars: doxy_vars[compoundname] = []
145                 doxy_vars[compoundname].append(name)
146             elif kind == "function":
147                 args = member.find('argsstring').text
148                 args = re.sub('\)[^)]*$', ')', args) # ignore what's after the parameters (eg, '=0' or ' const')
149
150                 if compoundname not in doxy_funs: doxy_funs[compoundname] = {}
151                 if name not in doxy_funs[compoundname]: doxy_funs[compoundname][name] = []
152                 doxy_funs[compoundname][name].append(args)
153             else:
154                 print ("member {}::{} is of kind {}".format(compoundname, name, kind))
155
156 # Forget about the declarations that are done in the RST
157 with os.popen('grep autodoxymethod:: source/*rst|sed \'s/^.*autodoxymethod:: //\'') as pse:
158     for line in (l.strip() for l in pse):
159         (klass, obj, args) = (None, None, None)
160         if "(" in line:
161             (line, args) = line.split('(', 1)
162             args = "({}".format(args)
163         (klass, obj) = line.rsplit('::', 1)
164
165         if klass not in doxy_funs:
166             print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
167             continue
168         if obj not in doxy_funs[klass]:
169             print("Warning: Object {} documented but not found in {}".format(line, klass))
170         elif len(doxy_funs[klass][obj])==1:
171             del doxy_funs[klass][obj]
172         elif args not in doxy_funs[klass][obj]:
173             print("Warning: Function {}{} not found in {}".format(obj, args, klass))
174         else:
175 #            print("Found {} in {}".format(line, klass))
176             doxy_funs[klass][obj].remove(args)
177             if len(doxy_funs[klass][obj]) == 0:
178                 del doxy_funs[klass][obj]
179 with os.popen('grep autodoxyvar:: source/*rst|sed \'s/^.*autodoxyvar:: //\'') as pse:
180     for line in (l.strip() for l in pse):
181         (klass, var) = line.rsplit('::', 1)
182
183         if klass not in doxy_vars:
184             print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
185             continue
186         if var not in doxy_vars[klass]:
187             print("Warning: Object {} documented but not found in {}".format(line, klass))
188         else:
189 #            print("Found {} in {}".format(line, klass))
190             doxy_vars[klass].remove(var)
191             if len(doxy_vars[klass]) == 0:
192                 del doxy_vars[klass]
193
194 # Dump the undocumented Doxygen declarations 
195 for obj in sorted(doxy_funs):
196     for meth in sorted(doxy_funs[obj]):
197         for args in sorted(doxy_funs[obj][meth]):
198             print(".. autodoxymethod:: {}::{}{}".format(obj, meth, args))
199
200 for obj in doxy_vars:
201     for meth in sorted(doxy_vars[obj]):
202         print(".. autodoxyvar:: {}::{}".format(obj, meth))
203