#!/usr/bin/env python3 # # Syntax: generate_docstrings.py # # 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 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 ' % 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)