d0ba2c552cfde8b893b6debac268cf53db26ac7d
[blender-staging.git] / tests / python / 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 tests/python/bl_rst_completeness.py
24
25 # Paste this into the bge and run on an always actuator.
26 '''
27 filepath = "/src/blender/tests/python/bl_rst_completeness.py"
28 exec(compile(open(filepath).read(), filepath, 'exec'))
29 '''
30
31 import os
32
33 THIS_DIR = os.path.dirname(__file__)
34 RST_DIR = os.path.normpath(os.path.join(THIS_DIR, "..", "..", "doc", "python_api", "rst"))
35
36 import sys
37 sys.path.append(THIS_DIR)
38
39 import rst_to_doctree_mini
40
41 try:
42     import bge
43 except:
44     bge = None
45
46 # (file, module)
47 modules = (
48     ("bge.constraints.rst", "bge.constraints", False),
49     ("bge.events.rst", "bge.events", False),
50     ("bge.logic.rst", "bge.logic", False),
51     ("bge.render.rst", "bge.render", False),
52     ("bge.texture.rst", "bge.texture", False),
53     ("bge.types.rst", "bge.types", False),
54
55     ("bgl.rst", "bgl", True),
56     ("gpu.rst", "gpu", False),
57 )
58
59
60 def is_directive_pydata(filepath, directive):
61     if directive.type in {"function", "method", "class", "attribute", "data"}:
62         return True
63     elif directive.type in {"module", "note", "warning", "code-block", "hlist", "seealso"}:
64         return False
65     elif directive.type in {"literalinclude"}:  # TODO
66         return False
67     else:
68         print(directive_to_str(filepath, directive), end=" ")
69         print("unknown directive type %r" % directive.type)
70         return False
71
72
73 def directive_to_str(filepath, directive):
74     return "%s:%d:%d:" % (filepath, directive.line + 1, directive.indent)
75
76
77 def directive_members_dict(filepath, directive_members):
78     return {directive.value_strip: directive for directive in directive_members
79             if is_directive_pydata(filepath, directive)}
80
81
82 def module_validate(filepath, mod, mod_name, doctree, partial_ok):
83     # RST member missing from MODULE ???
84     for directive in doctree:
85         # print(directive.type)
86         if is_directive_pydata(filepath, directive):
87             attr = directive.value_strip
88             has_attr = hasattr(mod, attr)
89             ok = False
90             if not has_attr:
91                 # so we can have glNormal docs cover glNormal3f
92                 if partial_ok:
93                     for s in dir(mod):
94                         if s.startswith(attr):
95                             ok = True
96                             break
97
98                 if not ok:
99                     print(directive_to_str(filepath, directive), end=" ")
100                     print("rst contains non existing member %r" % attr)
101
102             # if its a class, scan down the class...
103             # print(directive.type)
104             if has_attr:
105                 if directive.type == "class":
106                     cls = getattr(mod, attr)
107                     # print("directive:      ", directive)
108                     for directive_child in directive.members:
109                         # print("directive_child: ", directive_child)
110                         if is_directive_pydata(filepath, directive_child):
111                             attr_child = directive_child.value_strip
112                             if attr_child not in cls.__dict__:
113                                 attr_id = "%s.%s" % (attr, attr_child)
114                                 print(directive_to_str(filepath, directive_child), end=" ")
115                                 print("rst contains non existing class member %r" % attr_id)
116
117     # MODULE member missing from RST ???
118     doctree_dict = directive_members_dict(filepath, doctree)
119     for attr in dir(mod):
120         if attr.startswith("_"):
121             continue
122
123         directive = doctree_dict.get(attr)
124         if directive is None:
125             print("module contains undocumented member %r from %r" % ("%s.%s" % (mod_name, attr), filepath))
126         else:
127             if directive.type == "class":
128                 directive_dict = directive_members_dict(filepath, directive.members)
129                 cls = getattr(mod, attr)
130                 for attr_child in cls.__dict__.keys():
131                     if attr_child.startswith("_"):
132                         continue
133                     if attr_child not in directive_dict:
134                         attr_id = "%s.%s.%s" % (mod_name, attr, attr_child), filepath
135                         print("module contains undocumented member %r from %r" % attr_id)
136
137
138 def main():
139
140     if bge is None:
141         print("Skipping BGE modules!")
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()