|
@@ -0,0 +1,210 @@
|
|
|
+# coding=utf-8
|
|
|
+#
|
|
|
+# QEMU hxtool .hx file parsing extension
|
|
|
+#
|
|
|
+# Copyright (c) 2020 Linaro
|
|
|
+#
|
|
|
+# This work is licensed under the terms of the GNU GPLv2 or later.
|
|
|
+# See the COPYING file in the top-level directory.
|
|
|
+"""hxtool is a Sphinx extension that implements the hxtool-doc directive"""
|
|
|
+
|
|
|
+# The purpose of this extension is to read fragments of rST
|
|
|
+# from .hx files, and insert them all into the current document.
|
|
|
+# The rST fragments are delimited by SRST/ERST lines.
|
|
|
+# The conf.py file must set the hxtool_srctree config value to
|
|
|
+# the root of the QEMU source tree.
|
|
|
+# Each hxtool-doc:: directive takes one argument which is the
|
|
|
+# path of the .hx file to process, relative to the source tree.
|
|
|
+
|
|
|
+import os
|
|
|
+import re
|
|
|
+from enum import Enum
|
|
|
+
|
|
|
+from docutils import nodes
|
|
|
+from docutils.statemachine import ViewList
|
|
|
+from docutils.parsers.rst import directives, Directive
|
|
|
+from sphinx.errors import ExtensionError
|
|
|
+from sphinx.util.nodes import nested_parse_with_titles
|
|
|
+import sphinx
|
|
|
+
|
|
|
+# Sphinx up to 1.6 uses AutodocReporter; 1.7 and later
|
|
|
+# use switch_source_input. Check borrowed from kerneldoc.py.
|
|
|
+Use_SSI = sphinx.__version__[:3] >= '1.7'
|
|
|
+if Use_SSI:
|
|
|
+ from sphinx.util.docutils import switch_source_input
|
|
|
+else:
|
|
|
+ from sphinx.ext.autodoc import AutodocReporter
|
|
|
+
|
|
|
+__version__ = '1.0'
|
|
|
+
|
|
|
+# We parse hx files with a state machine which may be in one of three
|
|
|
+# states: reading the C code fragment, inside a texi fragment,
|
|
|
+# or inside a rST fragment.
|
|
|
+class HxState(Enum):
|
|
|
+ CTEXT = 1
|
|
|
+ TEXI = 2
|
|
|
+ RST = 3
|
|
|
+
|
|
|
+def serror(file, lnum, errtext):
|
|
|
+ """Raise an exception giving a user-friendly syntax error message"""
|
|
|
+ raise ExtensionError('%s line %d: syntax error: %s' % (file, lnum, errtext))
|
|
|
+
|
|
|
+def parse_directive(line):
|
|
|
+ """Return first word of line, if any"""
|
|
|
+ return re.split('\W', line)[0]
|
|
|
+
|
|
|
+def parse_defheading(file, lnum, line):
|
|
|
+ """Handle a DEFHEADING directive"""
|
|
|
+ # The input should be "DEFHEADING(some string)", though note that
|
|
|
+ # the 'some string' could be the empty string. If the string is
|
|
|
+ # empty we ignore the directive -- these are used only to add
|
|
|
+ # blank lines in the plain-text content of the --help output.
|
|
|
+ #
|
|
|
+ # Return the heading text
|
|
|
+ match = re.match(r'DEFHEADING\((.*)\)', line)
|
|
|
+ if match is None:
|
|
|
+ serror(file, lnum, "Invalid DEFHEADING line")
|
|
|
+ return match.group(1)
|
|
|
+
|
|
|
+def parse_archheading(file, lnum, line):
|
|
|
+ """Handle an ARCHHEADING directive"""
|
|
|
+ # The input should be "ARCHHEADING(some string, other arg)",
|
|
|
+ # though note that the 'some string' could be the empty string.
|
|
|
+ # As with DEFHEADING, empty string ARCHHEADINGs will be ignored.
|
|
|
+ #
|
|
|
+ # Return the heading text
|
|
|
+ match = re.match(r'ARCHHEADING\((.*),.*\)', line)
|
|
|
+ if match is None:
|
|
|
+ serror(file, lnum, "Invalid ARCHHEADING line")
|
|
|
+ return match.group(1)
|
|
|
+
|
|
|
+class HxtoolDocDirective(Directive):
|
|
|
+ """Extract rST fragments from the specified .hx file"""
|
|
|
+ required_argument = 1
|
|
|
+ optional_arguments = 1
|
|
|
+ option_spec = {
|
|
|
+ 'hxfile': directives.unchanged_required
|
|
|
+ }
|
|
|
+ has_content = False
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ env = self.state.document.settings.env
|
|
|
+ hxfile = env.config.hxtool_srctree + '/' + self.arguments[0]
|
|
|
+
|
|
|
+ # Tell sphinx of the dependency
|
|
|
+ env.note_dependency(os.path.abspath(hxfile))
|
|
|
+
|
|
|
+ state = HxState.CTEXT
|
|
|
+ # We build up lines of rST in this ViewList, which we will
|
|
|
+ # later put into a 'section' node.
|
|
|
+ rstlist = ViewList()
|
|
|
+ current_node = None
|
|
|
+ node_list = []
|
|
|
+
|
|
|
+ with open(hxfile) as f:
|
|
|
+ lines = (l.rstrip() for l in f)
|
|
|
+ for lnum, line in enumerate(lines, 1):
|
|
|
+ directive = parse_directive(line)
|
|
|
+
|
|
|
+ if directive == 'HXCOMM':
|
|
|
+ pass
|
|
|
+ elif directive == 'STEXI':
|
|
|
+ if state == HxState.RST:
|
|
|
+ serror(hxfile, lnum, 'expected ERST, found STEXI')
|
|
|
+ elif state == HxState.TEXI:
|
|
|
+ serror(hxfile, lnum, 'expected ETEXI, found STEXI')
|
|
|
+ else:
|
|
|
+ state = HxState.TEXI
|
|
|
+ elif directive == 'ETEXI':
|
|
|
+ if state == HxState.RST:
|
|
|
+ serror(hxfile, lnum, 'expected ERST, found ETEXI')
|
|
|
+ elif state == HxState.CTEXT:
|
|
|
+ serror(hxfile, lnum, 'expected STEXI, found ETEXI')
|
|
|
+ else:
|
|
|
+ state = HxState.CTEXT
|
|
|
+ elif directive == 'SRST':
|
|
|
+ if state == HxState.RST:
|
|
|
+ serror(hxfile, lnum, 'expected ERST, found SRST')
|
|
|
+ elif state == HxState.TEXI:
|
|
|
+ serror(hxfile, lnum, 'expected ETEXI, found SRST')
|
|
|
+ else:
|
|
|
+ state = HxState.RST
|
|
|
+ elif directive == 'ERST':
|
|
|
+ if state == HxState.TEXI:
|
|
|
+ serror(hxfile, lnum, 'expected ETEXI, found ERST')
|
|
|
+ elif state == HxState.CTEXT:
|
|
|
+ serror(hxfile, lnum, 'expected SRST, found ERST')
|
|
|
+ else:
|
|
|
+ state = HxState.CTEXT
|
|
|
+ elif directive == 'DEFHEADING' or directive == 'ARCHHEADING':
|
|
|
+ if directive == 'DEFHEADING':
|
|
|
+ heading = parse_defheading(hxfile, lnum, line)
|
|
|
+ else:
|
|
|
+ heading = parse_archheading(hxfile, lnum, line)
|
|
|
+ if heading == "":
|
|
|
+ continue
|
|
|
+ # Put the accumulated rST into the previous node,
|
|
|
+ # and then start a fresh section with this heading.
|
|
|
+ if len(rstlist) > 0:
|
|
|
+ if current_node is None:
|
|
|
+ # We had some rST fragments before the first
|
|
|
+ # DEFHEADING. We don't have a section to put
|
|
|
+ # these in, so rather than magicing up a section,
|
|
|
+ # make it a syntax error.
|
|
|
+ serror(hxfile, lnum,
|
|
|
+ 'first DEFHEADING must precede all rST text')
|
|
|
+ self.do_parse(rstlist, current_node)
|
|
|
+ rstlist = ViewList()
|
|
|
+ if current_node is not None:
|
|
|
+ node_list.append(current_node)
|
|
|
+ section_id = 'hxtool-%d' % env.new_serialno('hxtool')
|
|
|
+ current_node = nodes.section(ids=[section_id])
|
|
|
+ current_node += nodes.title(heading, heading)
|
|
|
+ else:
|
|
|
+ # Not a directive: put in output if we are in rST fragment
|
|
|
+ if state == HxState.RST:
|
|
|
+ # Sphinx counts its lines from 0
|
|
|
+ rstlist.append(line, hxfile, lnum - 1)
|
|
|
+
|
|
|
+ if current_node is None:
|
|
|
+ # We don't have multiple sections, so just parse the rst
|
|
|
+ # fragments into a dummy node so we can return the children.
|
|
|
+ current_node = nodes.section()
|
|
|
+ self.do_parse(rstlist, current_node)
|
|
|
+ return current_node.children
|
|
|
+ else:
|
|
|
+ # Put the remaining accumulated rST into the last section, and
|
|
|
+ # return all the sections.
|
|
|
+ if len(rstlist) > 0:
|
|
|
+ self.do_parse(rstlist, current_node)
|
|
|
+ node_list.append(current_node)
|
|
|
+ return node_list
|
|
|
+
|
|
|
+ # This is from kerneldoc.py -- it works around an API change in
|
|
|
+ # Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use
|
|
|
+ # sphinx.util.nodes.nested_parse_with_titles() rather than the
|
|
|
+ # plain self.state.nested_parse(), and so we can drop the saving
|
|
|
+ # of title_styles and section_level that kerneldoc.py does,
|
|
|
+ # because nested_parse_with_titles() does that for us.
|
|
|
+ def do_parse(self, result, node):
|
|
|
+ if Use_SSI:
|
|
|
+ with switch_source_input(self.state, result):
|
|
|
+ nested_parse_with_titles(self.state, result, node)
|
|
|
+ else:
|
|
|
+ save = self.state.memo.reporter
|
|
|
+ self.state.memo.reporter = AutodocReporter(result, self.state.memo.reporter)
|
|
|
+ try:
|
|
|
+ nested_parse_with_titles(self.state, result, node)
|
|
|
+ finally:
|
|
|
+ self.state.memo.reporter = save
|
|
|
+
|
|
|
+def setup(app):
|
|
|
+ """ Register hxtool-doc directive with Sphinx"""
|
|
|
+ app.add_config_value('hxtool_srctree', None, 'env')
|
|
|
+ app.add_directive('hxtool-doc', HxtoolDocDirective)
|
|
|
+
|
|
|
+ return dict(
|
|
|
+ version = __version__,
|
|
|
+ parallel_read_safe = True,
|
|
|
+ parallel_write_safe = True
|
|
|
+ )
|