gen-dxd.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. #!/usr/bin/env python
  2. #
  3. # gen-dxd.py - Generate Doxygen Directives
  4. #
  5. # This code is in the Public Domain (or CC0 licensed, at your option.)
  6. # Unless required by applicable law or agreed to in writing, this
  7. # software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  8. # CONDITIONS OF ANY KIND, either express or implied.
  9. #
  10. from __future__ import print_function
  11. from __future__ import unicode_literals
  12. from builtins import range
  13. from io import open
  14. import sys
  15. import os
  16. import re
  17. # Determime build directory
  18. builddir = '_build'
  19. if 'BUILDDIR' in os.environ:
  20. builddir = os.environ['BUILDDIR']
  21. # Script configuration
  22. header_file_path_prefix = "../../components/"
  23. """string: path prefix for header files.
  24. """
  25. doxyfile_path = "../Doxyfile"
  26. """string: path to a file containing header files to processs.
  27. """
  28. xml_directory_path = "xml"
  29. """string: path to directory with XML files by Doxygen.
  30. """
  31. inc_directory_path = os.path.join(builddir, 'inc')
  32. """string: path prefix for header files.
  33. """
  34. all_kinds = [
  35. ("function", "Functions"),
  36. ("union", "Unions"),
  37. ("struct", "Structures"),
  38. ("define", "Macros"),
  39. ("typedef", "Type Definitions"),
  40. ("enum", "Enumerations")
  41. ]
  42. """list of items that will be generated for a single API file
  43. """
  44. def get_doxyfile_input():
  45. """Get contents of Doxyfile's INPUT statement.
  46. Returns:
  47. Contents of Doxyfile's INPUT.
  48. """
  49. if not os.path.isfile(doxyfile_path):
  50. print("Doxyfile '%s' does not exist!" % doxyfile_path)
  51. sys.exit()
  52. print("Getting Doxyfile's INPUT")
  53. input_file = open(doxyfile_path, "r", encoding='utf-8')
  54. line = input_file.readline()
  55. # read contents of Doxyfile until 'INPUT' statement
  56. while line:
  57. if line.find("INPUT") == 0:
  58. break
  59. line = input_file.readline()
  60. doxyfile_INPUT = ""
  61. line = input_file.readline()
  62. # skip input_file contents until end of 'INPUT' statement
  63. while line:
  64. if line.isspace():
  65. # we have reached the end of 'INPUT' statement
  66. break
  67. # process only lines that are not comments
  68. if line.find("#") == -1:
  69. # extract header file path inside components folder
  70. m = re.search(header_file_path_prefix + "(.*\.h)", line) # noqa: W605 - regular expression
  71. header_file_path = m.group(1)
  72. doxyfile_INPUT += header_file_path + "\n"
  73. # proceed reading next line
  74. line = input_file.readline()
  75. input_file.close()
  76. return doxyfile_INPUT
  77. def get_api_name(header_file_path):
  78. """Get name of API from header file path.
  79. Args:
  80. header_file_path: path to the header file.
  81. Returns:
  82. The name of API.
  83. """
  84. api_name = ""
  85. regex = r".*/(.*)\.h"
  86. m = re.search(regex, header_file_path)
  87. if m:
  88. api_name = m.group(1)
  89. return api_name
  90. def get_rst_header(header_name):
  91. """Get rst formatted code with a header.
  92. Args:
  93. header_name: name of header.
  94. Returns:
  95. Formatted rst code with the header.
  96. """
  97. rst_output = ""
  98. rst_output += header_name + "\n"
  99. rst_output += "^" * len(header_name) + "\n"
  100. rst_output += "\n"
  101. return rst_output
  102. def select_unions(innerclass_list):
  103. """Select unions from innerclass list.
  104. Args:
  105. innerclass_list: raw list with unions and structures
  106. extracted from Dogygen's xml file.
  107. Returns:
  108. Doxygen directives with unions selected from the list.
  109. """
  110. rst_output = ""
  111. for line in innerclass_list.splitlines():
  112. # union is denoted by "union" at the beginning of line
  113. if line.find("union") == 0:
  114. union_id, union_name = re.split(r"\t+", line)
  115. rst_output += ".. doxygenunion:: "
  116. rst_output += union_name
  117. rst_output += "\n"
  118. return rst_output
  119. def select_structs(innerclass_list):
  120. """Select structures from innerclass list.
  121. Args:
  122. innerclass_list: raw list with unions and structures
  123. extracted from Dogygen's xml file.
  124. Returns:
  125. Doxygen directives with structures selected from the list.
  126. Note: some structures are excluded as described on code below.
  127. """
  128. rst_output = ""
  129. for line in innerclass_list.splitlines():
  130. # structure is denoted by "struct" at the beginning of line
  131. if line.find("struct") == 0:
  132. # skip structures that are part of union
  133. # they are documented by 'doxygenunion' directive
  134. if line.find("::") > 0:
  135. continue
  136. struct_id, struct_name = re.split(r"\t+", line)
  137. rst_output += ".. doxygenstruct:: "
  138. rst_output += struct_name
  139. rst_output += "\n"
  140. rst_output += " :members:\n"
  141. rst_output += "\n"
  142. return rst_output
  143. def get_directives(tree, kind):
  144. """Get directives for specific 'kind'.
  145. Args:
  146. tree: the ElementTree 'tree' of XML by Doxygen
  147. kind: name of API "kind" to be generated
  148. Returns:
  149. Doxygen directives for selected 'kind'.
  150. Note: the header with "kind" name is included.
  151. """
  152. rst_output = ""
  153. if kind in ["union", "struct"]:
  154. innerclass_list = ""
  155. for elem in tree.iterfind('compounddef/innerclass'):
  156. innerclass_list += elem.attrib["refid"] + "\t" + elem.text + "\n"
  157. if kind == "union":
  158. rst_output += select_unions(innerclass_list)
  159. else:
  160. rst_output += select_structs(innerclass_list)
  161. else:
  162. for elem in tree.iterfind(
  163. 'compounddef/sectiondef/memberdef[@kind="%s"]' % kind):
  164. name = elem.find('name')
  165. rst_output += ".. doxygen%s:: " % kind
  166. rst_output += name.text + "\n"
  167. if rst_output:
  168. all_kinds_dict = dict(all_kinds)
  169. rst_output = get_rst_header(all_kinds_dict[kind]) + rst_output + "\n"
  170. return rst_output
  171. def generate_directives(header_file_path):
  172. """Generate API reference with Doxygen directives for a header file.
  173. Args:
  174. header_file_path: a path to the header file with API.
  175. Returns:
  176. Doxygen directives for the header file.
  177. """
  178. api_name = get_api_name(header_file_path)
  179. # in XLT file name each "_" in the api name is expanded by Doxygen to "__"
  180. xlt_api_name = api_name.replace("_", "__")
  181. xml_file_path = "%s/%s_8h.xml" % (xml_directory_path, xlt_api_name)
  182. rst_output = ""
  183. rst_output = ".. File automatically generated by 'gen-dxd.py'\n"
  184. rst_output += "\n"
  185. rst_output += get_rst_header("Header File")
  186. rst_output += "* :component_file:`" + header_file_path + "`\n"
  187. rst_output += "\n"
  188. try:
  189. import xml.etree.cElementTree as ET
  190. except ImportError:
  191. import xml.etree.ElementTree as ET
  192. tree = ET.ElementTree(file=xml_file_path)
  193. for i in range(len(all_kinds)):
  194. kind = all_kinds[i][0]
  195. rst_output += get_directives(tree, kind)
  196. return rst_output
  197. def generate_api_inc_files():
  198. """Generate header_file.inc files
  199. with API reference made of doxygen directives
  200. for each header file
  201. specified in the 'INPUT' statement of Doxyfile.
  202. """
  203. if not os.path.isdir(xml_directory_path):
  204. print("Directory %s does not exist!" % xml_directory_path)
  205. sys.exit()
  206. if not os.path.exists(inc_directory_path):
  207. os.makedirs(inc_directory_path)
  208. list_to_generate = get_doxyfile_input()
  209. print("Generating 'api_name.inc' files with Doxygen directives")
  210. for header_file_path in list_to_generate.splitlines():
  211. api_name = get_api_name(header_file_path)
  212. inc_file_path = inc_directory_path + "/" + api_name + ".inc"
  213. rst_output = generate_directives(header_file_path)
  214. previous_rst_output = ''
  215. if os.path.isfile(inc_file_path):
  216. with open(inc_file_path, "r", encoding='utf-8') as inc_file_old:
  217. previous_rst_output = inc_file_old.read()
  218. if previous_rst_output != rst_output:
  219. with open(inc_file_path, "w", encoding='utf-8') as inc_file:
  220. inc_file.write(rst_output)
  221. if __name__ == "__main__":
  222. """The main script that generates
  223. Doxygen directives.
  224. """
  225. # Process command line arguments, if any
  226. if len(sys.argv) > 1:
  227. if not os.path.isdir(xml_directory_path):
  228. print("Directory %s does not exist!" % xml_directory_path)
  229. sys.exit()
  230. header_file_path = sys.argv[1]
  231. api_name = get_api_name(header_file_path)
  232. if api_name:
  233. rst_output = generate_directives(header_file_path)
  234. print("Doxygen directives for '%s'" % header_file_path)
  235. print()
  236. print(rst_output)
  237. else:
  238. print("Options to execute 'gen-dxd.py' application:")
  239. print("1: $ python gen-dxd.py")
  240. print(" Generate API 'header_file.inc' files for headers defined in '%s'" % doxyfile_path)
  241. print("2: $ python gen-dxd.py header_file_path")
  242. print(" Print out Doxygen directives for a single header file")
  243. print(" example: $ python gen-dxd.py mdns/include/mdns.h")
  244. print(" NOTE: Run Doxygen first to get XML files for the header file")
  245. sys.exit()
  246. # No command line arguments given
  247. generate_api_inc_files()