-Changes to header files to support parsing

-Added header parser and docstring generator
This commit is contained in:
Sebastian Koch
2016-05-12 15:36:04 +02:00
parent 4baa661e5c
commit e879d27f15
17 changed files with 993 additions and 900 deletions
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
#
# Syntax: generate_docstrings.py <path_to_c++_header_files> <path_to_python_files>
#
# Extract documentation from C++ header files to use it in libiglPython bindings
#
import os, sys, glob
from joblib import Parallel, delayed
from multiprocessing import cpu_count
from mako.template import Template
from parser import parse
# http://stackoverflow.com/questions/3207219/how-to-list-all-files-of-a-directory-in-python
def get_filepaths(directory):
"""
This function will generate the file names in a directory
tree by walking the tree either top-down or bottom-up. For each
directory in the tree rooted at directory top (including top itself),
it yields a 3-tuple (dirpath, dirnames, filenames).
"""
file_paths = [] # List which will store all of the full filepaths.
# Walk the tree.
for root, directories, files in os.walk(directory):
for filename in files:
# Join the two strings in order to form the full filepath.
filepath = os.path.join(root, filename)
file_paths.append(filepath) # Add it to the list.
return file_paths # Self-explanatory.
def get_name_from_path(path, basepath, prefix, postfix):
f_clean = path[len(basepath):]
f_clean = f_clean.replace(basepath, "")
f_clean = f_clean.replace(postfix, "")
f_clean = f_clean.replace(prefix, "")
f_clean = f_clean.replace("/", "_")
f_clean = f_clean.replace("\\", "_")
f_clean = f_clean.replace(" ", "_")
f_clean = f_clean.replace(".", "_")
return f_clean
if __name__ == '__main__':
if len(sys.argv) != 3:
print('Syntax: %s <path_to_c++_header_files> <path_to_python_files>' % sys.argv[0])
exit(-1)
# List all files in the given folder and subfolders
cpp_base_path = sys.argv[1]
py_base_path = sys.argv[2]
cpp_file_paths = get_filepaths(cpp_base_path)
py_file_paths = get_filepaths(py_base_path)
# Add all the .h filepaths to a dict
mapping = {}
for f in cpp_file_paths:
if f.endswith(".h"):
name = get_name_from_path(f, cpp_base_path, "", ".h")
mapping[name] = f
# Add all python binding files to a list
implemented_names = []
for f in py_file_paths:
if f.endswith(".cpp"):
name = get_name_from_path(f, py_base_path, "py_", ".cpp")
implemented_names.append(name)
# Create a list of cpp header files for which a python binding file exists
files_to_parse = []
for n in implemented_names:
if n not in mapping:
print("No cpp header file for python function %s found." % n)
continue
files_to_parse.append(mapping[n])
# print(mapping[n])
# Parse c++ header files
job_count = cpu_count()
dicts = Parallel(n_jobs=job_count)(delayed(parse)(path) for path in files_to_parse)
hpplines = []
cpplines = []
for idx, n in enumerate(implemented_names):
dict = dicts[idx]
contained_elements = sum(map(lambda x: len(x), dict.values()))
# Check for files that don't contain functions/enums/classes
if contained_elements == 0:
print("Function %s contains no parseable content in cpp header. Something might be wrong." % n)
continue
else:
names = []
namespaces = "_".join(dict["namespaces"]) # Assumption that all entities lie in deepest namespace
for f in dict["functions"]:
h_string = "extern const char *__doc_" + namespaces + "_" + f.name + ";\n"
docu_string = "See " + f.name + " for the documentation."
if f.documentation != "":
docu_string = f.documentation
cpp_string = "const char *__doc_" + namespaces + "_" + f.name + " = R\"igl_Qu8mg5v7(" + docu_string + ")igl_Qu8mg5v7\";\n"
if f.name not in names: # Prevent multiple additions of declarations, TODO: Possible fix is to merge comments and add them to all functions
hpplines.append(h_string)
cpplines.append(cpp_string)
names.append(f.name)
# Change directory to become independent of execution directory
path = os.path.dirname(__file__)
if path != "":
os.chdir(path)
# Update the two files py_doc.h and py_doc.cpp
with open('../py_doc.h', 'w') as fh:
fh.writelines(hpplines)
with open('../py_doc.cpp', 'w') as fc:
fc.writelines(cpplines)
# Write python_shared_cpp file
tpl = Template(filename='python_shared.mako')
rendered = tpl.render(functions=implemented_names)
with open("../python_shared.cpp", 'w') as fs:
fs.write(rendered)
-68
View File
@@ -1,68 +0,0 @@
#!/usr/bin/env python3
#
# Syntax: mkdoc.py <path_of_header_files>
#
# Extract documentation from C++ header files to use it in libiglPython bindings
#
import os, sys, glob
#http://stackoverflow.com/questions/3207219/how-to-list-all-files-of-a-directory-in-python
def get_filepaths(directory):
"""
This function will generate the file names in a directory
tree by walking the tree either top-down or bottom-up. For each
directory in the tree rooted at directory top (including top itself),
it yields a 3-tuple (dirpath, dirnames, filenames).
"""
file_paths = [] # List which will store all of the full filepaths.
# Walk the tree.
for root, directories, files in os.walk(directory):
for filename in files:
# Join the two strings in order to form the full filepath.
filepath = os.path.join(root, filename)
file_paths.append(filepath) # Add it to the list.
return file_paths # Self-explanatory.
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Syntax: %s <path_of_header_files>' % sys.argv[0])
exit(-1)
# Open two files, py_doc.h and py_doc.cpp
fh = open('py_doc.h', 'w')
fc = open('py_doc.cpp', 'w')
# List all files in the given folder and subfolders
base_path = sys.argv[1]
full_file_paths = get_filepaths(base_path)
# Add all the .h files
for f in full_file_paths:
if f.endswith(".h"):
f_clean = f[len(base_path):]
f_clean = f_clean.replace(base_path, "")
f_clean = f_clean.replace(".h", "")
f_clean = f_clean.replace("/", "_")
f_clean = f_clean.replace("\\", "_")
f_clean = f_clean.replace(" ", "_")
f_clean = f_clean.replace(".", "_")
#tmp = open(f, 'r', encoding="utf8")
tmp_string = f.replace("../include/", "libigl/") # " " # tmp.read()
tmp_string = "See " + tmp_string + " for the documentation."
#tmp.close()
h_string = "extern const char *__doc_" + f_clean + ";\n"
cpp_string = "const char *__doc_" + f_clean + " = R\"igl_Qu8mg5v7(" + tmp_string + ")igl_Qu8mg5v7\";\n"
fh.write(h_string)
fc.write(cpp_string)
# Close files
fh.close()
fc.close()
+123
View File
@@ -0,0 +1,123 @@
import sys
import os
from threading import Thread
import clang.cindex
import itertools
from mako.template import Template
def get_annotations(node):
return [c.displayname for c in node.get_children()
if c.kind == clang.cindex.CursorKind.ANNOTATE_ATTR]
class Function(object):
def __init__(self, cursor):
self.name = cursor.spelling
self.annotations = get_annotations(cursor)
self.access = cursor.access_specifier
# template_pars = [c.extent for c in cursor.get_children() if c.kind == clang.cindex.CursorKind.TEMPLATE_TYPE_PARAMETER]
# parameter_dec = [c for c in cursor.get_children() if c.kind == clang.cindex.CursorKind.PARM_DECL]
# print(parameter_dec, template_pars)
# print(cursor.get_num_template_arguments(), cursor.get_template_argument_type(0), cursor.get_template_argument_value(0), template_pars, parameter_dec)
self.parameters = []
self.parnames = []
self.documentation = cursor.raw_comment
class Enum(object):
def __init__(self, cursor):
self.name = cursor.spelling
self.annotations = get_annotations(cursor)
self.access = cursor.access_specifier
# template_pars = [c.extent for c in cursor.get_children() if c.kind == clang.cindex.CursorKind.TEMPLATE_TYPE_PARAMETER]
# parameter_dec = [c for c in cursor.get_children() if c.kind == clang.cindex.CursorKind.PARM_DECL]
# print(parameter_dec, template_pars)
# print(cursor.get_num_template_arguments(), cursor.get_template_argument_type(0), cursor.get_template_argument_value(0), template_pars, parameter_dec)
self.parameters = []
self.parnames = []
self.documentation = cursor.raw_comment
# class Class(object):
# def __init__(self, cursor):
# self.name = cursor.spelling
# self.functions = []
# self.annotations = get_annotations(cursor)
# for c in cursor.get_children():
# if (c.kind == clang.cindex.CursorKind.CXX_METHOD and
# c.access_specifier == clang.cindex.AccessSpecifier.PUBLIC):
# f = Function(c)
# self.functions.append(f)
def find_namespace_node(c):
if (c.kind == clang.cindex.CursorKind.NAMESPACE and c.spelling == "igl"):
return c
else:
for child_node in c.get_children():
return find_namespace_node(child_node)
def traverse(c, path, objects):
if c.location.file and not c.location.file.name.endswith(path):
return
# print(c.kind, c.spelling)
if c.kind == clang.cindex.CursorKind.TRANSLATION_UNIT or c.kind == clang.cindex.CursorKind.UNEXPOSED_DECL:
# Ignore other cursor kinds
pass
elif c.kind == clang.cindex.CursorKind.NAMESPACE:
objects["namespaces"].append(c.spelling)
# print("Namespace", c.spelling, c.get_children())
pass
elif c.kind == clang.cindex.CursorKind.FUNCTION_TEMPLATE:
# print("Function Template", c.spelling, c.raw_comment)
objects["functions"].append(Function(c))
return
elif c.kind == clang.cindex.CursorKind.FUNCTION_DECL:
# print("FUNCTION_DECL", c.spelling, c.raw_comment)
objects["functions"].append(Function(c))
return
elif c.kind == clang.cindex.CursorKind.ENUM_DECL:
# print("ENUM_DECL", c.spelling, c.raw_comment)
objects["enums"].append(Enum(c))
return
else:
# print("Unknown", c.kind, c.spelling)
pass
for child_node in c.get_children():
traverse(child_node, path, objects)
def parse(path):
index = clang.cindex.Index.create()
tu = index.parse(path, ['-x', 'c++', '-std=c++11', '-fparse-all-comments', '-DIGL_STATIC_LIBRARY'])
# Clang can't parse files with missing definitions, add static library definition
objects = {"functions": [], "enums": [], "namespaces": [], "classes": []}
traverse(tu.cursor, path, objects)
# tpl = Template(filename='bind.mako')
# rendered = tpl.render(functions=functions)
# OUTPUT_DIR = 'generated'
# if not os.path.isdir(OUTPUT_DIR): os.mkdir(OUTPUT_DIR)
# with open("generated/{}.bind.cc".format(sys.argv[1]), "w") as f:
# f.write(rendered)
return objects
if __name__ == '__main__':
if len(sys.argv) != 2:
print("Usage: python3 parser.py <headerfile_path>")
exit(-1)
parse(sys.argv[1])
+46
View File
@@ -0,0 +1,46 @@
#include "python_shared.h"
#include <sstream>
#include <string>
#include <fstream>
extern void python_export_vector(py::module &);
extern void python_export_igl(py::module &);
#ifdef PY_VIEWER
extern void python_export_igl_viewer(py::module &);
#endif
#ifdef PY_COMISO
extern void python_export_igl_comiso(py::module &);
#endif
PYBIND11_PLUGIN(pyigl) {
py::module m("pyigl", R"pyigldoc(
Python wrappers for libigl
--------------------------
.. currentmodule:: pyigl
.. autosummary::
:toctree: _generate
% for f in functions:
${f}
% endfor
)pyigldoc");
python_export_vector(m);
python_export_igl(m);
#ifdef PY_VIEWER
python_export_igl_viewer(m);
#endif
#ifdef PY_COMISO
python_export_igl_comiso(m);
#endif
return m.ptr();
}