generate_docstrings.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # This file is part of libigl, a simple c++ geometry processing library.
  2. #
  3. # Copyright (C) 2017 Sebastian Koch <s.koch@tu-berlin.de> and Daniele Panozzo <daniele.panozzo@gmail.com>
  4. #
  5. # This Source Code Form is subject to the terms of the Mozilla Public License
  6. # v. 2.0. If a copy of the MPL was not distributed with this file, You can
  7. # obtain one at http://mozilla.org/MPL/2.0/.
  8. #!/usr/bin/env python
  9. #
  10. # Syntax: generate_docstrings.py <path to libigl C++ header_files> <path to python binding C++ files>
  11. #
  12. # Extract documentation from C++ header files to use it in libiglPython bindings
  13. #
  14. import os, sys, glob
  15. from joblib import Parallel, delayed
  16. from multiprocessing import cpu_count
  17. from mako.template import Template
  18. from parser import parse
  19. # http://stackoverflow.com/questions/3207219/how-to-list-all-files-of-a-directory-in-python
  20. def get_filepaths(directory):
  21. """
  22. This function will generate the file names in a directory
  23. tree by walking the tree either top-down or bottom-up. For each
  24. directory in the tree rooted at directory top (including top itself),
  25. it yields a 3-tuple (dirpath, dirnames, filenames).
  26. """
  27. file_paths = [] # List which will store all of the full filepaths.
  28. root_file_paths = []
  29. # Walk the tree.
  30. for root, directories, files in os.walk(directory):
  31. for filename in files:
  32. # Join the two strings in order to form the full filepath.
  33. filepath = os.path.join(root, filename)
  34. file_paths.append(filepath) # Add it to the list.
  35. if root.endswith(directory): # Add only the files in the root directory
  36. root_file_paths.append(filepath)
  37. return file_paths, root_file_paths # file_paths contains all file paths, core_file_paths only the ones in <directory>
  38. def get_name_from_path(path, basepath, prefix, postfix):
  39. f_clean = os.path.relpath(path, basepath)
  40. f_clean = f_clean.replace(postfix, "")
  41. f_clean = f_clean.replace(prefix, "")
  42. f_clean = f_clean.replace("/", "_")
  43. f_clean = f_clean.replace("\\", "_")
  44. f_clean = f_clean.replace(" ", "_")
  45. f_clean = f_clean.replace(".", "_")
  46. return f_clean
  47. if __name__ == '__main__':
  48. if len(sys.argv) != 3:
  49. print('Syntax: %s generate_docstrings.py <path to libigl C++ header_files> <path to python binding C++ files>' % sys.argv[0])
  50. exit(-1)
  51. # List all files in the given folder and subfolders
  52. cpp_base_path = sys.argv[1]
  53. py_base_path = sys.argv[2]
  54. cpp_file_paths, cpp_root_file_paths = get_filepaths(cpp_base_path)
  55. py_file_paths, py_root_file_paths = get_filepaths(py_base_path)
  56. # Add all the .h filepaths to a dict
  57. mapping = {}
  58. for f in cpp_file_paths:
  59. if f.endswith(".h"):
  60. name = get_name_from_path(f, cpp_base_path, "", ".h")
  61. mapping[name] = f
  62. # Add all python binding files to a list
  63. implemented_names = []
  64. core_implemented_names = []
  65. for f in py_file_paths:
  66. if f.endswith(".cpp"):
  67. name = get_name_from_path(f, py_base_path, "py_", ".cpp")
  68. implemented_names.append(name)
  69. if f in py_root_file_paths:
  70. core_implemented_names.append(name)
  71. implemented_names.sort()
  72. core_implemented_names.sort()
  73. # Create a list of cpp header files for which a python binding file exists
  74. files_to_parse = []
  75. for n in implemented_names:
  76. if n not in mapping:
  77. print("No cpp header file for python function %s found." % n)
  78. continue
  79. files_to_parse.append(mapping[n])
  80. # print(mapping[n])
  81. # Parse c++ header files
  82. job_count = cpu_count()
  83. dicts = Parallel(n_jobs=job_count)(delayed(parse)(path) for path in files_to_parse)
  84. hpplines = []
  85. cpplines = []
  86. for idx, n in enumerate(implemented_names):
  87. d = dicts[idx]
  88. contained_elements = sum(map(lambda x: len(x), d.values()))
  89. # Check for files that don't contain functions/enums/classes
  90. if contained_elements == 0:
  91. print("Function %s contains no parseable content in cpp header. Something might be wrong." % n)
  92. continue
  93. else:
  94. names = []
  95. namespaces = "_".join(d["namespaces"]) # Assumption that all entities lie in deepest namespace
  96. for f in d["functions"]:
  97. h_string = "extern const char *__doc_" + namespaces + "_" + f.name + ";\n"
  98. docu_string = "See " + f.name + " for the documentation."
  99. if f.documentation:
  100. docu_string = f.documentation
  101. cpp_string = "const char *__doc_" + namespaces + "_" + f.name + " = R\"igl_Qu8mg5v7(" + docu_string + ")igl_Qu8mg5v7\";\n"
  102. if f.name not in names: # Prevent multiple additions of declarations, TODO: Possible fix is to merge comments and add them to all functions
  103. hpplines.append(h_string)
  104. cpplines.append(cpp_string)
  105. names.append(f.name)
  106. # Change directory to become independent of execution directory
  107. path = os.path.dirname(__file__)
  108. if path != "":
  109. os.chdir(path)
  110. # Update the two files py_doc.h and py_doc.cpp
  111. with open('../py_doc.h', 'w') as fh:
  112. fh.writelines(hpplines)
  113. with open('../py_doc.cpp', 'w') as fc:
  114. fc.writelines(cpplines)
  115. # Write python_shared_cpp file
  116. tpl = Template(filename='python_shared.mako')
  117. rendered = tpl.render(functions=implemented_names)
  118. with open("../python_shared.cpp", 'w') as fs:
  119. fs.write(rendered)
  120. # Write py_igl_cpp file with all core library files
  121. tpl = Template(filename='py_igl.mako')
  122. rendered = tpl.render(functions=core_implemented_names)
  123. with open("../py_igl.cpp", 'w') as fs:
  124. fs.write(rendered)