scripts to report missing py reference api docs. (simple RST parse and compare with...
[blender.git] / source / tests / bl_rst_completeness.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 # run this script in the game engine.
22 # or on the command line with...
23 #  ./blender.bin --background -noaudio --python source/tests/bl_rst_completeness.py
24
25 # Paste this into the bge and run on an always actuator.
26 '''
27 filepath = "/dsk/data/src/blender/blender/source/tests/bl_rst_completeness.py"
28 exec(compile(open(filepath).read(), filepath, 'exec'))
29 '''
30
31 import os
32 THIS_DIR = os.path.dirname(__file__)
33 RST_DIR = os.path.normpath(os.path.join(THIS_DIR, "..", "..", "doc", "python_api", "rst"))
34
35 import sys
36 sys.path.append(THIS_DIR)
37
38 import rst_to_doctree_mini
39
40 try:
41     import bge
42 except:
43     bge = None
44
45 # (file, module)
46 modules = (
47     ("bge.constraints.rst", "bge.constraints", False),
48     ("bge.events.rst", "bge.events", False),
49     ("bge.logic.rst", "bge.logic", False),
50     ("bge.render.rst", "bge.render", False),
51     ("bge.texture.rst", "bge.texture", False),
52     ("bge.types.rst", "bge.types", False),
53
54     ("bgl.rst", "bgl", True),
55     ("gpu.rst", "gpu", False),
56 )
57
58 def is_directive_pydata(filepath, directive):
59     if directive.type in {"function", "method", "class", "attribute", "data"}:
60         return True
61     elif directive.type in {"module", "note", "warning", "code-block", "hlist", "seealso"}:
62         return False
63     elif directive.type in {"literalinclude"}:  # TODO
64         return False
65     else:
66         print(directive_to_str(filepath, directive), end=" ")
67         print("unknown directive type %r" % directive.type)
68         return False
69
70
71 def directive_to_str(filepath, directive):
72     return "%s:%d:%d:" % (filepath, directive.line + 1, directive.indent)
73
74
75 def directive_members_dict(filepath, directive_members):
76     return {directive.value_strip: directive for directive in directive_members
77             if is_directive_pydata(filepath, directive)}
78
79
80 def module_validate(filepath, mod, mod_name, doctree, partial_ok):
81     # RST member missing from MODULE ???
82     for directive in doctree:
83         # print(directive.type)
84         if is_directive_pydata(filepath, directive):
85             attr = directive.value_strip
86             has_attr = hasattr(mod, attr)
87             ok = False
88             if not has_attr:
89                 # so we can have glNormal docs cover glNormal3f
90                 if partial_ok:
91                     for s in dir(mod):
92                         if s.startswith(attr):
93                             ok = True
94                             break
95
96                 if not ok:
97                     print(directive_to_str(filepath, directive), end=" ")
98                     print("rst contains non existing member %r" % attr)
99
100             # if its a class, scan down the class...
101             # print(directive.type)
102             if has_attr:
103                 if directive.type == "class":
104                     cls = getattr(mod, attr)
105                     # print("directive:      ", directive)
106                     for directive_child in directive.members:
107                         # print("directive_child: ", directive_child)
108                         if is_directive_pydata(filepath, directive_child):
109                             attr_child = directive_child.value_strip
110                             if attr_child not in cls.__dict__:
111                                 attr_id = "%s.%s" % (attr, attr_child)
112                                 print(directive_to_str(filepath, directive_child), end=" ")
113                                 print("rst contains non existing class member %r" % attr_id)
114
115
116     # MODULE member missing from RST ???
117     doctree_dict = directive_members_dict(filepath, doctree)
118     for attr in dir(mod):
119         if attr.startswith("_"):
120             continue
121
122         directive = doctree_dict.get(attr)
123         if directive is None:
124             print("module contains undocumented member %r from %r" % ("%s.%s" % (mod_name, attr), filepath))
125         else:
126             if directive.type == "class":
127                 directive_dict = directive_members_dict(filepath, directive.members)
128                 cls = getattr(mod, attr)
129                 for attr_child in cls.__dict__.keys():
130                     if attr_child.startswith("_"):
131                         continue
132                     if attr_child not in directive_dict:
133                         attr_id = "%s.%s.%s" % (mod_name, attr, attr_child), filepath
134                         print("module contains undocumented member %r from %r" % attr_id)
135
136
137 def main():
138     
139     if bge is None:
140         print("Skipping BGE modules!")
141         continue
142
143     for filename, modname, partial_ok in modules:
144         if bge is None and modname.startswith("bge"):
145             continue
146
147         filepath = os.path.join(RST_DIR, filename)
148         if not os.path.exists(filepath):
149             raise Exception("%r not found" % filepath)
150
151         doctree = rst_to_doctree_mini.parse_rst_py(filepath)
152         __import__(modname)
153         mod = sys.modules[modname]
154         
155         module_validate(filepath, mod, modname, doctree, partial_ok)
156
157
158 if __name__ == "__main__":
159     main()