scripts to report missing py reference api docs. (simple RST parse and compare with python)

This commit is contained in:
Campbell Barton 2012-11-21 07:40:46 +00:00
parent 36d2d090f6
commit e6f9ef8f7d
2 changed files with 250 additions and 0 deletions

@ -0,0 +1,159 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# run this script in the game engine.
# or on the command line with...
# ./blender.bin --background -noaudio --python source/tests/bl_rst_completeness.py
# Paste this into the bge and run on an always actuator.
'''
filepath = "/dsk/data/src/blender/blender/source/tests/bl_rst_completeness.py"
exec(compile(open(filepath).read(), filepath, 'exec'))
'''
import os
THIS_DIR = os.path.dirname(__file__)
RST_DIR = os.path.normpath(os.path.join(THIS_DIR, "..", "..", "doc", "python_api", "rst"))
import sys
sys.path.append(THIS_DIR)
import rst_to_doctree_mini
try:
import bge
except:
bge = None
# (file, module)
modules = (
("bge.constraints.rst", "bge.constraints", False),
("bge.events.rst", "bge.events", False),
("bge.logic.rst", "bge.logic", False),
("bge.render.rst", "bge.render", False),
("bge.texture.rst", "bge.texture", False),
("bge.types.rst", "bge.types", False),
("bgl.rst", "bgl", True),
("gpu.rst", "gpu", False),
)
def is_directive_pydata(filepath, directive):
if directive.type in {"function", "method", "class", "attribute", "data"}:
return True
elif directive.type in {"module", "note", "warning", "code-block", "hlist", "seealso"}:
return False
elif directive.type in {"literalinclude"}: # TODO
return False
else:
print(directive_to_str(filepath, directive), end=" ")
print("unknown directive type %r" % directive.type)
return False
def directive_to_str(filepath, directive):
return "%s:%d:%d:" % (filepath, directive.line + 1, directive.indent)
def directive_members_dict(filepath, directive_members):
return {directive.value_strip: directive for directive in directive_members
if is_directive_pydata(filepath, directive)}
def module_validate(filepath, mod, mod_name, doctree, partial_ok):
# RST member missing from MODULE ???
for directive in doctree:
# print(directive.type)
if is_directive_pydata(filepath, directive):
attr = directive.value_strip
has_attr = hasattr(mod, attr)
ok = False
if not has_attr:
# so we can have glNormal docs cover glNormal3f
if partial_ok:
for s in dir(mod):
if s.startswith(attr):
ok = True
break
if not ok:
print(directive_to_str(filepath, directive), end=" ")
print("rst contains non existing member %r" % attr)
# if its a class, scan down the class...
# print(directive.type)
if has_attr:
if directive.type == "class":
cls = getattr(mod, attr)
# print("directive: ", directive)
for directive_child in directive.members:
# print("directive_child: ", directive_child)
if is_directive_pydata(filepath, directive_child):
attr_child = directive_child.value_strip
if attr_child not in cls.__dict__:
attr_id = "%s.%s" % (attr, attr_child)
print(directive_to_str(filepath, directive_child), end=" ")
print("rst contains non existing class member %r" % attr_id)
# MODULE member missing from RST ???
doctree_dict = directive_members_dict(filepath, doctree)
for attr in dir(mod):
if attr.startswith("_"):
continue
directive = doctree_dict.get(attr)
if directive is None:
print("module contains undocumented member %r from %r" % ("%s.%s" % (mod_name, attr), filepath))
else:
if directive.type == "class":
directive_dict = directive_members_dict(filepath, directive.members)
cls = getattr(mod, attr)
for attr_child in cls.__dict__.keys():
if attr_child.startswith("_"):
continue
if attr_child not in directive_dict:
attr_id = "%s.%s.%s" % (mod_name, attr, attr_child), filepath
print("module contains undocumented member %r from %r" % attr_id)
def main():
if bge is None:
print("Skipping BGE modules!")
continue
for filename, modname, partial_ok in modules:
if bge is None and modname.startswith("bge"):
continue
filepath = os.path.join(RST_DIR, filename)
if not os.path.exists(filepath):
raise Exception("%r not found" % filepath)
doctree = rst_to_doctree_mini.parse_rst_py(filepath)
__import__(modname)
mod = sys.modules[modname]
module_validate(filepath, mod, modname, doctree, partial_ok)
if __name__ == "__main__":
main()

@ -0,0 +1,91 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Module with function to extract a doctree from an reStructuredText file.
# Named 'Mini' because we only parse the minimum data needed to check
# Python classes, methods and attributes match up to those in existing modules.
# (To test for documentation completeness)
# note: literalinclude's are not followed.
# could be nice to add but not really needed either right now.
import collections
Directive = collections.namedtuple('Directive',
("type",
"value",
"value_strip",
"line",
"indent",
"members"))
def parse_rst_py(filepath):
import re
# Get the prefix assuming the line is lstrip()'d
# ..foo:: bar
# -->
# ("foo", "bar")
re_prefix = re.compile(r"^\.\.\s([a-zA-Z09\-]+)::\s*(.*)\s*$")
tree = collections.defaultdict(list)
f = open(filepath, encoding="utf-8")
indent_lists = []
for i, line in enumerate(f):
line_strip = line.lstrip()
# ^\.\.\s[a-zA-Z09\-]+::.*$
#if line.startswith(".. "):
march = re_prefix.match(line_strip)
if march:
directive, value = march.group(1, 2)
indent = len(line) - len(line_strip)
value_strip = value.replace("(", " ").split()
value_strip = value_strip[0] if value_strip else ""
item = Directive(type=directive,
value=value,
value_strip=value_strip,
line=i,
indent=indent,
members=[])
tree[indent].append(item)
if indent > 0:
# get the previous indent, ok this isn't fast but no matter.
keys = list(sorted(tree.keys()))
key_index = keys.index(indent)
parent_indent = keys[key_index - 1]
tree[parent_indent][-1].members.append(item)
f.close()
return tree[0]
if __name__ == "__main__":
# not intended use, but may as well print rst files passed as a test.
import sys
for arg in sys.argv:
if arg.lower().endswith((".txt", ".rst")):
items = parse_rst_py(arg)
for i in items:
print(i)