xref: /openbmc/qemu/docs/sphinx/hxtool.py (revision 959269e910944c03bc13f300d65bf08b060d5d0f)
16803d6e9SPeter Maydell# coding=utf-8
26803d6e9SPeter Maydell#
36803d6e9SPeter Maydell# QEMU hxtool .hx file parsing extension
46803d6e9SPeter Maydell#
56803d6e9SPeter Maydell# Copyright (c) 2020 Linaro
66803d6e9SPeter Maydell#
76803d6e9SPeter Maydell# This work is licensed under the terms of the GNU GPLv2 or later.
86803d6e9SPeter Maydell# See the COPYING file in the top-level directory.
96803d6e9SPeter Maydell"""hxtool is a Sphinx extension that implements the hxtool-doc directive"""
106803d6e9SPeter Maydell
116803d6e9SPeter Maydell# The purpose of this extension is to read fragments of rST
126803d6e9SPeter Maydell# from .hx files, and insert them all into the current document.
136803d6e9SPeter Maydell# The rST fragments are delimited by SRST/ERST lines.
146803d6e9SPeter Maydell# The conf.py file must set the hxtool_srctree config value to
156803d6e9SPeter Maydell# the root of the QEMU source tree.
166803d6e9SPeter Maydell# Each hxtool-doc:: directive takes one argument which is the
176803d6e9SPeter Maydell# path of the .hx file to process, relative to the source tree.
186803d6e9SPeter Maydell
196803d6e9SPeter Maydellimport os
206803d6e9SPeter Maydellimport re
216803d6e9SPeter Maydellfrom enum import Enum
226803d6e9SPeter Maydell
236803d6e9SPeter Maydellfrom docutils import nodes
246803d6e9SPeter Maydellfrom docutils.statemachine import ViewList
256803d6e9SPeter Maydellfrom docutils.parsers.rst import directives, Directive
266803d6e9SPeter Maydellfrom sphinx.errors import ExtensionError
27*dd23f9ecSJohn Snowfrom sphinx.util.docutils import switch_source_input
286803d6e9SPeter Maydellfrom sphinx.util.nodes import nested_parse_with_titles
296803d6e9SPeter Maydellimport sphinx
306803d6e9SPeter Maydell
316803d6e9SPeter Maydell
326803d6e9SPeter Maydell__version__ = '1.0'
336803d6e9SPeter Maydell
3480a046c5SPeter Maydell# We parse hx files with a state machine which may be in one of two
3580a046c5SPeter Maydell# states: reading the C code fragment, or inside a rST fragment.
366803d6e9SPeter Maydellclass HxState(Enum):
376803d6e9SPeter Maydell    CTEXT = 1
3880a046c5SPeter Maydell    RST = 2
396803d6e9SPeter Maydell
406803d6e9SPeter Maydelldef serror(file, lnum, errtext):
416803d6e9SPeter Maydell    """Raise an exception giving a user-friendly syntax error message"""
426803d6e9SPeter Maydell    raise ExtensionError('%s line %d: syntax error: %s' % (file, lnum, errtext))
436803d6e9SPeter Maydell
446803d6e9SPeter Maydelldef parse_directive(line):
456803d6e9SPeter Maydell    """Return first word of line, if any"""
46e4b6532cSPaolo Bonzini    return re.split(r'\W', line)[0]
476803d6e9SPeter Maydell
486803d6e9SPeter Maydelldef parse_defheading(file, lnum, line):
496803d6e9SPeter Maydell    """Handle a DEFHEADING directive"""
506803d6e9SPeter Maydell    # The input should be "DEFHEADING(some string)", though note that
516803d6e9SPeter Maydell    # the 'some string' could be the empty string. If the string is
526803d6e9SPeter Maydell    # empty we ignore the directive -- these are used only to add
536803d6e9SPeter Maydell    # blank lines in the plain-text content of the --help output.
546803d6e9SPeter Maydell    #
55705f48ccSPeter Maydell    # Return the heading text. We strip out any trailing ':' for
56705f48ccSPeter Maydell    # consistency with other headings in the rST documentation.
57705f48ccSPeter Maydell    match = re.match(r'DEFHEADING\((.*?):?\)', line)
586803d6e9SPeter Maydell    if match is None:
596803d6e9SPeter Maydell        serror(file, lnum, "Invalid DEFHEADING line")
606803d6e9SPeter Maydell    return match.group(1)
616803d6e9SPeter Maydell
626803d6e9SPeter Maydelldef parse_archheading(file, lnum, line):
636803d6e9SPeter Maydell    """Handle an ARCHHEADING directive"""
646803d6e9SPeter Maydell    # The input should be "ARCHHEADING(some string, other arg)",
656803d6e9SPeter Maydell    # though note that the 'some string' could be the empty string.
666803d6e9SPeter Maydell    # As with DEFHEADING, empty string ARCHHEADINGs will be ignored.
676803d6e9SPeter Maydell    #
68705f48ccSPeter Maydell    # Return the heading text. We strip out any trailing ':' for
69705f48ccSPeter Maydell    # consistency with other headings in the rST documentation.
70705f48ccSPeter Maydell    match = re.match(r'ARCHHEADING\((.*?):?,.*\)', line)
716803d6e9SPeter Maydell    if match is None:
726803d6e9SPeter Maydell        serror(file, lnum, "Invalid ARCHHEADING line")
736803d6e9SPeter Maydell    return match.group(1)
746803d6e9SPeter Maydell
751eeb432aSDavid Woodhousedef parse_srst(file, lnum, line):
761eeb432aSDavid Woodhouse    """Handle an SRST directive"""
771eeb432aSDavid Woodhouse    # The input should be either "SRST", or "SRST(label)".
781eeb432aSDavid Woodhouse    match = re.match(r'SRST(\((.*?)\))?', line)
791eeb432aSDavid Woodhouse    if match is None:
801eeb432aSDavid Woodhouse        serror(file, lnum, "Invalid SRST line")
811eeb432aSDavid Woodhouse    return match.group(2)
821eeb432aSDavid Woodhouse
836803d6e9SPeter Maydellclass HxtoolDocDirective(Directive):
846803d6e9SPeter Maydell    """Extract rST fragments from the specified .hx file"""
856803d6e9SPeter Maydell    required_argument = 1
866803d6e9SPeter Maydell    optional_arguments = 1
876803d6e9SPeter Maydell    option_spec = {
886803d6e9SPeter Maydell        'hxfile': directives.unchanged_required
896803d6e9SPeter Maydell    }
906803d6e9SPeter Maydell    has_content = False
916803d6e9SPeter Maydell
926803d6e9SPeter Maydell    def run(self):
936803d6e9SPeter Maydell        env = self.state.document.settings.env
946803d6e9SPeter Maydell        hxfile = env.config.hxtool_srctree + '/' + self.arguments[0]
956803d6e9SPeter Maydell
966803d6e9SPeter Maydell        # Tell sphinx of the dependency
976803d6e9SPeter Maydell        env.note_dependency(os.path.abspath(hxfile))
986803d6e9SPeter Maydell
996803d6e9SPeter Maydell        state = HxState.CTEXT
1006803d6e9SPeter Maydell        # We build up lines of rST in this ViewList, which we will
1016803d6e9SPeter Maydell        # later put into a 'section' node.
1026803d6e9SPeter Maydell        rstlist = ViewList()
1036803d6e9SPeter Maydell        current_node = None
1046803d6e9SPeter Maydell        node_list = []
1056803d6e9SPeter Maydell
1066803d6e9SPeter Maydell        with open(hxfile) as f:
1076803d6e9SPeter Maydell            lines = (l.rstrip() for l in f)
1086803d6e9SPeter Maydell            for lnum, line in enumerate(lines, 1):
1096803d6e9SPeter Maydell                directive = parse_directive(line)
1106803d6e9SPeter Maydell
1116803d6e9SPeter Maydell                if directive == 'HXCOMM':
1126803d6e9SPeter Maydell                    pass
1136803d6e9SPeter Maydell                elif directive == 'SRST':
1146803d6e9SPeter Maydell                    if state == HxState.RST:
1156803d6e9SPeter Maydell                        serror(hxfile, lnum, 'expected ERST, found SRST')
1166803d6e9SPeter Maydell                    else:
1176803d6e9SPeter Maydell                        state = HxState.RST
1181eeb432aSDavid Woodhouse                        label = parse_srst(hxfile, lnum, line)
1191eeb432aSDavid Woodhouse                        if label:
1201eeb432aSDavid Woodhouse                            rstlist.append("", hxfile, lnum - 1)
1211eeb432aSDavid Woodhouse                            # Build label as _DOCNAME-HXNAME-LABEL
1221eeb432aSDavid Woodhouse                            hx = os.path.splitext(os.path.basename(hxfile))[0]
1231eeb432aSDavid Woodhouse                            refline = ".. _" + env.docname + "-" + hx + \
1241eeb432aSDavid Woodhouse                                "-" + label + ":"
1251eeb432aSDavid Woodhouse                            rstlist.append(refline, hxfile, lnum - 1)
1266803d6e9SPeter Maydell                elif directive == 'ERST':
12780a046c5SPeter Maydell                    if state == HxState.CTEXT:
1286803d6e9SPeter Maydell                        serror(hxfile, lnum, 'expected SRST, found ERST')
1296803d6e9SPeter Maydell                    else:
1306803d6e9SPeter Maydell                        state = HxState.CTEXT
1316803d6e9SPeter Maydell                elif directive == 'DEFHEADING' or directive == 'ARCHHEADING':
1326803d6e9SPeter Maydell                    if directive == 'DEFHEADING':
1336803d6e9SPeter Maydell                        heading = parse_defheading(hxfile, lnum, line)
1346803d6e9SPeter Maydell                    else:
1356803d6e9SPeter Maydell                        heading = parse_archheading(hxfile, lnum, line)
1366803d6e9SPeter Maydell                    if heading == "":
1376803d6e9SPeter Maydell                        continue
1386803d6e9SPeter Maydell                    # Put the accumulated rST into the previous node,
1396803d6e9SPeter Maydell                    # and then start a fresh section with this heading.
1406803d6e9SPeter Maydell                    if len(rstlist) > 0:
1416803d6e9SPeter Maydell                        if current_node is None:
1426803d6e9SPeter Maydell                            # We had some rST fragments before the first
1436803d6e9SPeter Maydell                            # DEFHEADING. We don't have a section to put
1446803d6e9SPeter Maydell                            # these in, so rather than magicing up a section,
1456803d6e9SPeter Maydell                            # make it a syntax error.
1466803d6e9SPeter Maydell                            serror(hxfile, lnum,
1476803d6e9SPeter Maydell                                   'first DEFHEADING must precede all rST text')
1486803d6e9SPeter Maydell                        self.do_parse(rstlist, current_node)
1496803d6e9SPeter Maydell                        rstlist = ViewList()
1506803d6e9SPeter Maydell                    if current_node is not None:
1516803d6e9SPeter Maydell                        node_list.append(current_node)
1526803d6e9SPeter Maydell                    section_id = 'hxtool-%d' % env.new_serialno('hxtool')
1536803d6e9SPeter Maydell                    current_node = nodes.section(ids=[section_id])
1546803d6e9SPeter Maydell                    current_node += nodes.title(heading, heading)
1556803d6e9SPeter Maydell                else:
1566803d6e9SPeter Maydell                    # Not a directive: put in output if we are in rST fragment
1576803d6e9SPeter Maydell                    if state == HxState.RST:
1586803d6e9SPeter Maydell                        # Sphinx counts its lines from 0
1596803d6e9SPeter Maydell                        rstlist.append(line, hxfile, lnum - 1)
1606803d6e9SPeter Maydell
1616803d6e9SPeter Maydell        if current_node is None:
1626803d6e9SPeter Maydell            # We don't have multiple sections, so just parse the rst
1636803d6e9SPeter Maydell            # fragments into a dummy node so we can return the children.
1646803d6e9SPeter Maydell            current_node = nodes.section()
1656803d6e9SPeter Maydell            self.do_parse(rstlist, current_node)
1666803d6e9SPeter Maydell            return current_node.children
1676803d6e9SPeter Maydell        else:
1686803d6e9SPeter Maydell            # Put the remaining accumulated rST into the last section, and
1696803d6e9SPeter Maydell            # return all the sections.
1706803d6e9SPeter Maydell            if len(rstlist) > 0:
1716803d6e9SPeter Maydell                self.do_parse(rstlist, current_node)
1726803d6e9SPeter Maydell            node_list.append(current_node)
1736803d6e9SPeter Maydell            return node_list
1746803d6e9SPeter Maydell
1756803d6e9SPeter Maydell    # This is from kerneldoc.py -- it works around an API change in
1766803d6e9SPeter Maydell    # Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use
1776803d6e9SPeter Maydell    # sphinx.util.nodes.nested_parse_with_titles() rather than the
1786803d6e9SPeter Maydell    # plain self.state.nested_parse(), and so we can drop the saving
1796803d6e9SPeter Maydell    # of title_styles and section_level that kerneldoc.py does,
1806803d6e9SPeter Maydell    # because nested_parse_with_titles() does that for us.
1816803d6e9SPeter Maydell    def do_parse(self, result, node):
1826803d6e9SPeter Maydell        with switch_source_input(self.state, result):
1836803d6e9SPeter Maydell            nested_parse_with_titles(self.state, result, node)
184*dd23f9ecSJohn Snow
1856803d6e9SPeter Maydell
1866803d6e9SPeter Maydelldef setup(app):
1876803d6e9SPeter Maydell    """ Register hxtool-doc directive with Sphinx"""
1886803d6e9SPeter Maydell    app.add_config_value('hxtool_srctree', None, 'env')
1896803d6e9SPeter Maydell    app.add_directive('hxtool-doc', HxtoolDocDirective)
1906803d6e9SPeter Maydell
1916803d6e9SPeter Maydell    return dict(
1926803d6e9SPeter Maydell        version = __version__,
1936803d6e9SPeter Maydell        parallel_read_safe = True,
1946803d6e9SPeter Maydell        parallel_write_safe = True
1956803d6e9SPeter Maydell    )
196