hxtool.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. # coding=utf-8
  2. #
  3. # QEMU hxtool .hx file parsing extension
  4. #
  5. # Copyright (c) 2020 Linaro
  6. #
  7. # This work is licensed under the terms of the GNU GPLv2 or later.
  8. # See the COPYING file in the top-level directory.
  9. """hxtool is a Sphinx extension that implements the hxtool-doc directive"""
  10. # The purpose of this extension is to read fragments of rST
  11. # from .hx files, and insert them all into the current document.
  12. # The rST fragments are delimited by SRST/ERST lines.
  13. # The conf.py file must set the hxtool_srctree config value to
  14. # the root of the QEMU source tree.
  15. # Each hxtool-doc:: directive takes one argument which is the
  16. # path of the .hx file to process, relative to the source tree.
  17. import os
  18. import re
  19. from enum import Enum
  20. from docutils import nodes
  21. from docutils.statemachine import ViewList
  22. from docutils.parsers.rst import directives, Directive
  23. from sphinx.errors import ExtensionError
  24. from sphinx.util.nodes import nested_parse_with_titles
  25. import sphinx
  26. # Sphinx up to 1.6 uses AutodocReporter; 1.7 and later
  27. # use switch_source_input. Check borrowed from kerneldoc.py.
  28. Use_SSI = sphinx.__version__[:3] >= '1.7'
  29. if Use_SSI:
  30. from sphinx.util.docutils import switch_source_input
  31. else:
  32. from sphinx.ext.autodoc import AutodocReporter
  33. __version__ = '1.0'
  34. # We parse hx files with a state machine which may be in one of three
  35. # states: reading the C code fragment, inside a texi fragment,
  36. # or inside a rST fragment.
  37. class HxState(Enum):
  38. CTEXT = 1
  39. TEXI = 2
  40. RST = 3
  41. def serror(file, lnum, errtext):
  42. """Raise an exception giving a user-friendly syntax error message"""
  43. raise ExtensionError('%s line %d: syntax error: %s' % (file, lnum, errtext))
  44. def parse_directive(line):
  45. """Return first word of line, if any"""
  46. return re.split('\W', line)[0]
  47. def parse_defheading(file, lnum, line):
  48. """Handle a DEFHEADING directive"""
  49. # The input should be "DEFHEADING(some string)", though note that
  50. # the 'some string' could be the empty string. If the string is
  51. # empty we ignore the directive -- these are used only to add
  52. # blank lines in the plain-text content of the --help output.
  53. #
  54. # Return the heading text
  55. match = re.match(r'DEFHEADING\((.*)\)', line)
  56. if match is None:
  57. serror(file, lnum, "Invalid DEFHEADING line")
  58. return match.group(1)
  59. def parse_archheading(file, lnum, line):
  60. """Handle an ARCHHEADING directive"""
  61. # The input should be "ARCHHEADING(some string, other arg)",
  62. # though note that the 'some string' could be the empty string.
  63. # As with DEFHEADING, empty string ARCHHEADINGs will be ignored.
  64. #
  65. # Return the heading text
  66. match = re.match(r'ARCHHEADING\((.*),.*\)', line)
  67. if match is None:
  68. serror(file, lnum, "Invalid ARCHHEADING line")
  69. return match.group(1)
  70. class HxtoolDocDirective(Directive):
  71. """Extract rST fragments from the specified .hx file"""
  72. required_argument = 1
  73. optional_arguments = 1
  74. option_spec = {
  75. 'hxfile': directives.unchanged_required
  76. }
  77. has_content = False
  78. def run(self):
  79. env = self.state.document.settings.env
  80. hxfile = env.config.hxtool_srctree + '/' + self.arguments[0]
  81. # Tell sphinx of the dependency
  82. env.note_dependency(os.path.abspath(hxfile))
  83. state = HxState.CTEXT
  84. # We build up lines of rST in this ViewList, which we will
  85. # later put into a 'section' node.
  86. rstlist = ViewList()
  87. current_node = None
  88. node_list = []
  89. with open(hxfile) as f:
  90. lines = (l.rstrip() for l in f)
  91. for lnum, line in enumerate(lines, 1):
  92. directive = parse_directive(line)
  93. if directive == 'HXCOMM':
  94. pass
  95. elif directive == 'STEXI':
  96. if state == HxState.RST:
  97. serror(hxfile, lnum, 'expected ERST, found STEXI')
  98. elif state == HxState.TEXI:
  99. serror(hxfile, lnum, 'expected ETEXI, found STEXI')
  100. else:
  101. state = HxState.TEXI
  102. elif directive == 'ETEXI':
  103. if state == HxState.RST:
  104. serror(hxfile, lnum, 'expected ERST, found ETEXI')
  105. elif state == HxState.CTEXT:
  106. serror(hxfile, lnum, 'expected STEXI, found ETEXI')
  107. else:
  108. state = HxState.CTEXT
  109. elif directive == 'SRST':
  110. if state == HxState.RST:
  111. serror(hxfile, lnum, 'expected ERST, found SRST')
  112. elif state == HxState.TEXI:
  113. serror(hxfile, lnum, 'expected ETEXI, found SRST')
  114. else:
  115. state = HxState.RST
  116. elif directive == 'ERST':
  117. if state == HxState.TEXI:
  118. serror(hxfile, lnum, 'expected ETEXI, found ERST')
  119. elif state == HxState.CTEXT:
  120. serror(hxfile, lnum, 'expected SRST, found ERST')
  121. else:
  122. state = HxState.CTEXT
  123. elif directive == 'DEFHEADING' or directive == 'ARCHHEADING':
  124. if directive == 'DEFHEADING':
  125. heading = parse_defheading(hxfile, lnum, line)
  126. else:
  127. heading = parse_archheading(hxfile, lnum, line)
  128. if heading == "":
  129. continue
  130. # Put the accumulated rST into the previous node,
  131. # and then start a fresh section with this heading.
  132. if len(rstlist) > 0:
  133. if current_node is None:
  134. # We had some rST fragments before the first
  135. # DEFHEADING. We don't have a section to put
  136. # these in, so rather than magicing up a section,
  137. # make it a syntax error.
  138. serror(hxfile, lnum,
  139. 'first DEFHEADING must precede all rST text')
  140. self.do_parse(rstlist, current_node)
  141. rstlist = ViewList()
  142. if current_node is not None:
  143. node_list.append(current_node)
  144. section_id = 'hxtool-%d' % env.new_serialno('hxtool')
  145. current_node = nodes.section(ids=[section_id])
  146. current_node += nodes.title(heading, heading)
  147. else:
  148. # Not a directive: put in output if we are in rST fragment
  149. if state == HxState.RST:
  150. # Sphinx counts its lines from 0
  151. rstlist.append(line, hxfile, lnum - 1)
  152. if current_node is None:
  153. # We don't have multiple sections, so just parse the rst
  154. # fragments into a dummy node so we can return the children.
  155. current_node = nodes.section()
  156. self.do_parse(rstlist, current_node)
  157. return current_node.children
  158. else:
  159. # Put the remaining accumulated rST into the last section, and
  160. # return all the sections.
  161. if len(rstlist) > 0:
  162. self.do_parse(rstlist, current_node)
  163. node_list.append(current_node)
  164. return node_list
  165. # This is from kerneldoc.py -- it works around an API change in
  166. # Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use
  167. # sphinx.util.nodes.nested_parse_with_titles() rather than the
  168. # plain self.state.nested_parse(), and so we can drop the saving
  169. # of title_styles and section_level that kerneldoc.py does,
  170. # because nested_parse_with_titles() does that for us.
  171. def do_parse(self, result, node):
  172. if Use_SSI:
  173. with switch_source_input(self.state, result):
  174. nested_parse_with_titles(self.state, result, node)
  175. else:
  176. save = self.state.memo.reporter
  177. self.state.memo.reporter = AutodocReporter(result, self.state.memo.reporter)
  178. try:
  179. nested_parse_with_titles(self.state, result, node)
  180. finally:
  181. self.state.memo.reporter = save
  182. def setup(app):
  183. """ Register hxtool-doc directive with Sphinx"""
  184. app.add_config_value('hxtool_srctree', None, 'env')
  185. app.add_directive('hxtool-doc', HxtoolDocDirective)
  186. return dict(
  187. version = __version__,
  188. parallel_read_safe = True,
  189. parallel_write_safe = True
  190. )