I also fixed a couple of bugs in generate_docstrings.py: 1) We were checking for an empty string where a result could also be Nonn. 2) If you forgot to end the argument path with a slash, the output would be incorrect 3) We were coupled to a specific version of clang for no reason 4) Python 3 clang binding support was only added in April 2017 (https://reviews.llvm.org/D31568). Using python3 by default will not work on the clang bindings which ship with pip and most Linux distros. Let's change the hashbang to use python2 for now. 5) The documentation for the script was a bit confusing.
142 lines
5.3 KiB
Python
Executable File
142 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# Syntax: generate_docstrings.py <path to libigl C++ header_files> <path to python binding C++ 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.
|
|
root_file_paths = []
|
|
|
|
# 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.
|
|
|
|
if root.endswith(directory): # Add only the files in the root directory
|
|
root_file_paths.append(filepath)
|
|
|
|
return file_paths, root_file_paths # file_paths contains all file paths, core_file_paths only the ones in <directory>
|
|
|
|
|
|
def get_name_from_path(path, basepath, prefix, postfix):
|
|
f_clean = os.path.relpath(path, 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 generate_docstrings.py <path to libigl C++ header_files> <path to python binding C++ 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, cpp_root_file_paths = get_filepaths(cpp_base_path)
|
|
py_file_paths, py_root_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 = []
|
|
core_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)
|
|
if f in py_root_file_paths:
|
|
core_implemented_names.append(name)
|
|
|
|
implemented_names.sort()
|
|
core_implemented_names.sort()
|
|
|
|
# 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):
|
|
d = dicts[idx]
|
|
contained_elements = sum(map(lambda x: len(x), d.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(d["namespaces"]) # Assumption that all entities lie in deepest namespace
|
|
for f in d["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)
|
|
|
|
# Write py_igl_cpp file with all core library files
|
|
tpl = Template(filename='py_igl.mako')
|
|
rendered = tpl.render(functions=core_implemented_names)
|
|
with open("../py_igl.cpp", 'w') as fs:
|
|
fs.write(rendered)
|