xref: /openbmc/qemu/docs/sphinx/qapidoc_legacy.py (revision 94d689d0c6f23dc3129e8432c496ccb866788dbf)
17640410dSJohn Snow# coding=utf-8
2*45d483a8SJohn Snow# type: ignore
37640410dSJohn Snow#
47640410dSJohn Snow# QEMU qapidoc QAPI file parsing extension
57640410dSJohn Snow#
67640410dSJohn Snow# Copyright (c) 2020 Linaro
77640410dSJohn Snow#
87640410dSJohn Snow# This work is licensed under the terms of the GNU GPLv2 or later.
97640410dSJohn Snow# See the COPYING file in the top-level directory.
107640410dSJohn Snow
117640410dSJohn Snow"""
127640410dSJohn Snowqapidoc is a Sphinx extension that implements the qapi-doc directive
137640410dSJohn Snow
147640410dSJohn SnowThe purpose of this extension is to read the documentation comments
157640410dSJohn Snowin QAPI schema files, and insert them all into the current document.
167640410dSJohn Snow
177640410dSJohn SnowIt implements one new rST directive, "qapi-doc::".
187640410dSJohn SnowEach qapi-doc:: directive takes one argument, which is the
197640410dSJohn Snowpathname of the schema file to process, relative to the source tree.
207640410dSJohn Snow
217640410dSJohn SnowThe docs/conf.py file must set the qapidoc_srctree config value to
227640410dSJohn Snowthe root of the QEMU source tree.
237640410dSJohn Snow
247640410dSJohn SnowThe Sphinx documentation on writing extensions is at:
257640410dSJohn Snowhttps://www.sphinx-doc.org/en/master/development/index.html
267640410dSJohn Snow"""
277640410dSJohn Snow
287640410dSJohn Snowimport re
297640410dSJohn Snowimport textwrap
307640410dSJohn Snow
317640410dSJohn Snowfrom docutils import nodes
327640410dSJohn Snowfrom docutils.statemachine import ViewList
337640410dSJohn Snowfrom qapi.error import QAPISemError
347640410dSJohn Snowfrom qapi.gen import QAPISchemaVisitor
357640410dSJohn Snowfrom qapi.parser import QAPIDoc
367640410dSJohn Snow
377640410dSJohn Snow
387640410dSJohn Snowdef dedent(text: str) -> str:
397640410dSJohn Snow    # Adjust indentation to make description text parse as paragraph.
407640410dSJohn Snow
417640410dSJohn Snow    lines = text.splitlines(True)
427640410dSJohn Snow    if re.match(r"\s+", lines[0]):
437640410dSJohn Snow        # First line is indented; description started on the line after
447640410dSJohn Snow        # the name. dedent the whole block.
457640410dSJohn Snow        return textwrap.dedent(text)
467640410dSJohn Snow
477640410dSJohn Snow    # Descr started on same line. Dedent line 2+.
487640410dSJohn Snow    return lines[0] + textwrap.dedent("".join(lines[1:]))
497640410dSJohn Snow
507640410dSJohn Snow
517640410dSJohn Snowclass QAPISchemaGenRSTVisitor(QAPISchemaVisitor):
527640410dSJohn Snow    """A QAPI schema visitor which generates docutils/Sphinx nodes
537640410dSJohn Snow
547640410dSJohn Snow    This class builds up a tree of docutils/Sphinx nodes corresponding
557640410dSJohn Snow    to documentation for the various QAPI objects. To use it, first
567640410dSJohn Snow    create a QAPISchemaGenRSTVisitor object, and call its
577640410dSJohn Snow    visit_begin() method.  Then you can call one of the two methods
587640410dSJohn Snow    'freeform' (to add documentation for a freeform documentation
597640410dSJohn Snow    chunk) or 'symbol' (to add documentation for a QAPI symbol). These
607640410dSJohn Snow    will cause the visitor to build up the tree of document
617640410dSJohn Snow    nodes. Once you've added all the documentation via 'freeform' and
627640410dSJohn Snow    'symbol' method calls, you can call 'get_document_nodes' to get
637640410dSJohn Snow    the final list of document nodes (in a form suitable for returning
647640410dSJohn Snow    from a Sphinx directive's 'run' method).
657640410dSJohn Snow    """
667640410dSJohn Snow    def __init__(self, sphinx_directive):
677640410dSJohn Snow        self._cur_doc = None
687640410dSJohn Snow        self._sphinx_directive = sphinx_directive
697640410dSJohn Snow        self._top_node = nodes.section()
707640410dSJohn Snow        self._active_headings = [self._top_node]
717640410dSJohn Snow
727640410dSJohn Snow    def _make_dlitem(self, term, defn):
737640410dSJohn Snow        """Return a dlitem node with the specified term and definition.
747640410dSJohn Snow
757640410dSJohn Snow        term should be a list of Text and literal nodes.
767640410dSJohn Snow        defn should be one of:
777640410dSJohn Snow        - a string, which will be handed to _parse_text_into_node
787640410dSJohn Snow        - a list of Text and literal nodes, which will be put into
797640410dSJohn Snow          a paragraph node
807640410dSJohn Snow        """
817640410dSJohn Snow        dlitem = nodes.definition_list_item()
827640410dSJohn Snow        dlterm = nodes.term('', '', *term)
837640410dSJohn Snow        dlitem += dlterm
847640410dSJohn Snow        if defn:
857640410dSJohn Snow            dldef = nodes.definition()
867640410dSJohn Snow            if isinstance(defn, list):
877640410dSJohn Snow                dldef += nodes.paragraph('', '', *defn)
887640410dSJohn Snow            else:
897640410dSJohn Snow                self._parse_text_into_node(defn, dldef)
907640410dSJohn Snow            dlitem += dldef
917640410dSJohn Snow        return dlitem
927640410dSJohn Snow
937640410dSJohn Snow    def _make_section(self, title):
947640410dSJohn Snow        """Return a section node with optional title"""
957640410dSJohn Snow        section = nodes.section(ids=[self._sphinx_directive.new_serialno()])
967640410dSJohn Snow        if title:
977640410dSJohn Snow            section += nodes.title(title, title)
987640410dSJohn Snow        return section
997640410dSJohn Snow
1007640410dSJohn Snow    def _nodes_for_ifcond(self, ifcond, with_if=True):
1017640410dSJohn Snow        """Return list of Text, literal nodes for the ifcond
1027640410dSJohn Snow
1037640410dSJohn Snow        Return a list which gives text like ' (If: condition)'.
1047640410dSJohn Snow        If with_if is False, we don't return the "(If: " and ")".
1057640410dSJohn Snow        """
1067640410dSJohn Snow
1077640410dSJohn Snow        doc = ifcond.docgen()
1087640410dSJohn Snow        if not doc:
1097640410dSJohn Snow            return []
1107640410dSJohn Snow        doc = nodes.literal('', doc)
1117640410dSJohn Snow        if not with_if:
1127640410dSJohn Snow            return [doc]
1137640410dSJohn Snow
1147640410dSJohn Snow        nodelist = [nodes.Text(' ('), nodes.strong('', 'If: ')]
1157640410dSJohn Snow        nodelist.append(doc)
1167640410dSJohn Snow        nodelist.append(nodes.Text(')'))
1177640410dSJohn Snow        return nodelist
1187640410dSJohn Snow
1197640410dSJohn Snow    def _nodes_for_one_member(self, member):
1207640410dSJohn Snow        """Return list of Text, literal nodes for this member
1217640410dSJohn Snow
1227640410dSJohn Snow        Return a list of doctree nodes which give text like
1237640410dSJohn Snow        'name: type (optional) (If: ...)' suitable for use as the
1247640410dSJohn Snow        'term' part of a definition list item.
1257640410dSJohn Snow        """
1267640410dSJohn Snow        term = [nodes.literal('', member.name)]
1277640410dSJohn Snow        if member.type.doc_type():
1287640410dSJohn Snow            term.append(nodes.Text(': '))
1297640410dSJohn Snow            term.append(nodes.literal('', member.type.doc_type()))
1307640410dSJohn Snow        if member.optional:
1317640410dSJohn Snow            term.append(nodes.Text(' (optional)'))
1327640410dSJohn Snow        if member.ifcond.is_present():
1337640410dSJohn Snow            term.extend(self._nodes_for_ifcond(member.ifcond))
1347640410dSJohn Snow        return term
1357640410dSJohn Snow
1367640410dSJohn Snow    def _nodes_for_variant_when(self, branches, variant):
1377640410dSJohn Snow        """Return list of Text, literal nodes for variant 'when' clause
1387640410dSJohn Snow
1397640410dSJohn Snow        Return a list of doctree nodes which give text like
1407640410dSJohn Snow        'when tagname is variant (If: ...)' suitable for use in
1417640410dSJohn Snow        the 'branches' part of a definition list.
1427640410dSJohn Snow        """
1437640410dSJohn Snow        term = [nodes.Text(' when '),
1447640410dSJohn Snow                nodes.literal('', branches.tag_member.name),
1457640410dSJohn Snow                nodes.Text(' is '),
1467640410dSJohn Snow                nodes.literal('', '"%s"' % variant.name)]
1477640410dSJohn Snow        if variant.ifcond.is_present():
1487640410dSJohn Snow            term.extend(self._nodes_for_ifcond(variant.ifcond))
1497640410dSJohn Snow        return term
1507640410dSJohn Snow
1517640410dSJohn Snow    def _nodes_for_members(self, doc, what, base=None, branches=None):
1527640410dSJohn Snow        """Return list of doctree nodes for the table of members"""
1537640410dSJohn Snow        dlnode = nodes.definition_list()
1547640410dSJohn Snow        for section in doc.args.values():
1557640410dSJohn Snow            term = self._nodes_for_one_member(section.member)
1567640410dSJohn Snow            # TODO drop fallbacks when undocumented members are outlawed
1577640410dSJohn Snow            if section.text:
1587640410dSJohn Snow                defn = dedent(section.text)
1597640410dSJohn Snow            else:
1607640410dSJohn Snow                defn = [nodes.Text('Not documented')]
1617640410dSJohn Snow
1627640410dSJohn Snow            dlnode += self._make_dlitem(term, defn)
1637640410dSJohn Snow
1647640410dSJohn Snow        if base:
1657640410dSJohn Snow            dlnode += self._make_dlitem([nodes.Text('The members of '),
1667640410dSJohn Snow                                         nodes.literal('', base.doc_type())],
1677640410dSJohn Snow                                        None)
1687640410dSJohn Snow
1697640410dSJohn Snow        if branches:
1707640410dSJohn Snow            for v in branches.variants:
1717640410dSJohn Snow                if v.type.name == 'q_empty':
1727640410dSJohn Snow                    continue
1737640410dSJohn Snow                assert not v.type.is_implicit()
1747640410dSJohn Snow                term = [nodes.Text('The members of '),
1757640410dSJohn Snow                        nodes.literal('', v.type.doc_type())]
1767640410dSJohn Snow                term.extend(self._nodes_for_variant_when(branches, v))
1777640410dSJohn Snow                dlnode += self._make_dlitem(term, None)
1787640410dSJohn Snow
1797640410dSJohn Snow        if not dlnode.children:
1807640410dSJohn Snow            return []
1817640410dSJohn Snow
1827640410dSJohn Snow        section = self._make_section(what)
1837640410dSJohn Snow        section += dlnode
1847640410dSJohn Snow        return [section]
1857640410dSJohn Snow
1867640410dSJohn Snow    def _nodes_for_enum_values(self, doc):
1877640410dSJohn Snow        """Return list of doctree nodes for the table of enum values"""
1887640410dSJohn Snow        seen_item = False
1897640410dSJohn Snow        dlnode = nodes.definition_list()
1907640410dSJohn Snow        for section in doc.args.values():
1917640410dSJohn Snow            termtext = [nodes.literal('', section.member.name)]
1927640410dSJohn Snow            if section.member.ifcond.is_present():
1937640410dSJohn Snow                termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
1947640410dSJohn Snow            # TODO drop fallbacks when undocumented members are outlawed
1957640410dSJohn Snow            if section.text:
1967640410dSJohn Snow                defn = dedent(section.text)
1977640410dSJohn Snow            else:
1987640410dSJohn Snow                defn = [nodes.Text('Not documented')]
1997640410dSJohn Snow
2007640410dSJohn Snow            dlnode += self._make_dlitem(termtext, defn)
2017640410dSJohn Snow            seen_item = True
2027640410dSJohn Snow
2037640410dSJohn Snow        if not seen_item:
2047640410dSJohn Snow            return []
2057640410dSJohn Snow
2067640410dSJohn Snow        section = self._make_section('Values')
2077640410dSJohn Snow        section += dlnode
2087640410dSJohn Snow        return [section]
2097640410dSJohn Snow
2107640410dSJohn Snow    def _nodes_for_arguments(self, doc, arg_type):
2117640410dSJohn Snow        """Return list of doctree nodes for the arguments section"""
2127640410dSJohn Snow        if arg_type and not arg_type.is_implicit():
2137640410dSJohn Snow            assert not doc.args
2147640410dSJohn Snow            section = self._make_section('Arguments')
2157640410dSJohn Snow            dlnode = nodes.definition_list()
2167640410dSJohn Snow            dlnode += self._make_dlitem(
2177640410dSJohn Snow                [nodes.Text('The members of '),
2187640410dSJohn Snow                 nodes.literal('', arg_type.name)],
2197640410dSJohn Snow                None)
2207640410dSJohn Snow            section += dlnode
2217640410dSJohn Snow            return [section]
2227640410dSJohn Snow
2237640410dSJohn Snow        return self._nodes_for_members(doc, 'Arguments')
2247640410dSJohn Snow
2257640410dSJohn Snow    def _nodes_for_features(self, doc):
2267640410dSJohn Snow        """Return list of doctree nodes for the table of features"""
2277640410dSJohn Snow        seen_item = False
2287640410dSJohn Snow        dlnode = nodes.definition_list()
2297640410dSJohn Snow        for section in doc.features.values():
2307640410dSJohn Snow            dlnode += self._make_dlitem(
2317640410dSJohn Snow                [nodes.literal('', section.member.name)], dedent(section.text))
2327640410dSJohn Snow            seen_item = True
2337640410dSJohn Snow
2347640410dSJohn Snow        if not seen_item:
2357640410dSJohn Snow            return []
2367640410dSJohn Snow
2377640410dSJohn Snow        section = self._make_section('Features')
2387640410dSJohn Snow        section += dlnode
2397640410dSJohn Snow        return [section]
2407640410dSJohn Snow
2417640410dSJohn Snow    def _nodes_for_sections(self, doc):
2427640410dSJohn Snow        """Return list of doctree nodes for additional sections"""
2437640410dSJohn Snow        nodelist = []
2447640410dSJohn Snow        for section in doc.sections:
2457640410dSJohn Snow            if section.kind == QAPIDoc.Kind.TODO:
2467640410dSJohn Snow                # Hide TODO: sections
2477640410dSJohn Snow                continue
2487640410dSJohn Snow
2497640410dSJohn Snow            if section.kind == QAPIDoc.Kind.PLAIN:
2507640410dSJohn Snow                # Sphinx cannot handle sectionless titles;
2517640410dSJohn Snow                # Instead, just append the results to the prior section.
2527640410dSJohn Snow                container = nodes.container()
2537640410dSJohn Snow                self._parse_text_into_node(section.text, container)
2547640410dSJohn Snow                nodelist += container.children
2557640410dSJohn Snow                continue
2567640410dSJohn Snow
2577640410dSJohn Snow            snode = self._make_section(section.kind.name.title())
2587640410dSJohn Snow            self._parse_text_into_node(dedent(section.text), snode)
2597640410dSJohn Snow            nodelist.append(snode)
2607640410dSJohn Snow        return nodelist
2617640410dSJohn Snow
2627640410dSJohn Snow    def _nodes_for_if_section(self, ifcond):
2637640410dSJohn Snow        """Return list of doctree nodes for the "If" section"""
2647640410dSJohn Snow        nodelist = []
2657640410dSJohn Snow        if ifcond.is_present():
2667640410dSJohn Snow            snode = self._make_section('If')
2677640410dSJohn Snow            snode += nodes.paragraph(
2687640410dSJohn Snow                '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
2697640410dSJohn Snow            )
2707640410dSJohn Snow            nodelist.append(snode)
2717640410dSJohn Snow        return nodelist
2727640410dSJohn Snow
2737640410dSJohn Snow    def _add_doc(self, typ, sections):
2747640410dSJohn Snow        """Add documentation for a command/object/enum...
2757640410dSJohn Snow
2767640410dSJohn Snow        We assume we're documenting the thing defined in self._cur_doc.
2777640410dSJohn Snow        typ is the type of thing being added ("Command", "Object", etc)
2787640410dSJohn Snow
2797640410dSJohn Snow        sections is a list of nodes for sections to add to the definition.
2807640410dSJohn Snow        """
2817640410dSJohn Snow
2827640410dSJohn Snow        doc = self._cur_doc
2837640410dSJohn Snow        snode = nodes.section(ids=[self._sphinx_directive.new_serialno()])
2847640410dSJohn Snow        snode += nodes.title('', '', *[nodes.literal(doc.symbol, doc.symbol),
2857640410dSJohn Snow                                       nodes.Text(' (' + typ + ')')])
2867640410dSJohn Snow        self._parse_text_into_node(doc.body.text, snode)
2877640410dSJohn Snow        for s in sections:
2887640410dSJohn Snow            if s is not None:
2897640410dSJohn Snow                snode += s
2907640410dSJohn Snow        self._add_node_to_current_heading(snode)
2917640410dSJohn Snow
2927640410dSJohn Snow    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
2937640410dSJohn Snow        doc = self._cur_doc
2947640410dSJohn Snow        self._add_doc('Enum',
2957640410dSJohn Snow                      self._nodes_for_enum_values(doc)
2967640410dSJohn Snow                      + self._nodes_for_features(doc)
2977640410dSJohn Snow                      + self._nodes_for_sections(doc)
2987640410dSJohn Snow                      + self._nodes_for_if_section(ifcond))
2997640410dSJohn Snow
3007640410dSJohn Snow    def visit_object_type(self, name, info, ifcond, features,
3017640410dSJohn Snow                          base, members, branches):
3027640410dSJohn Snow        doc = self._cur_doc
3037640410dSJohn Snow        if base and base.is_implicit():
3047640410dSJohn Snow            base = None
3057640410dSJohn Snow        self._add_doc('Object',
3067640410dSJohn Snow                      self._nodes_for_members(doc, 'Members', base, branches)
3077640410dSJohn Snow                      + self._nodes_for_features(doc)
3087640410dSJohn Snow                      + self._nodes_for_sections(doc)
3097640410dSJohn Snow                      + self._nodes_for_if_section(ifcond))
3107640410dSJohn Snow
3117640410dSJohn Snow    def visit_alternate_type(self, name, info, ifcond, features,
3127640410dSJohn Snow                             alternatives):
3137640410dSJohn Snow        doc = self._cur_doc
3147640410dSJohn Snow        self._add_doc('Alternate',
3157640410dSJohn Snow                      self._nodes_for_members(doc, 'Members')
3167640410dSJohn Snow                      + self._nodes_for_features(doc)
3177640410dSJohn Snow                      + self._nodes_for_sections(doc)
3187640410dSJohn Snow                      + self._nodes_for_if_section(ifcond))
3197640410dSJohn Snow
3207640410dSJohn Snow    def visit_command(self, name, info, ifcond, features, arg_type,
3217640410dSJohn Snow                      ret_type, gen, success_response, boxed, allow_oob,
3227640410dSJohn Snow                      allow_preconfig, coroutine):
3237640410dSJohn Snow        doc = self._cur_doc
3247640410dSJohn Snow        self._add_doc('Command',
3257640410dSJohn Snow                      self._nodes_for_arguments(doc, arg_type)
3267640410dSJohn Snow                      + self._nodes_for_features(doc)
3277640410dSJohn Snow                      + self._nodes_for_sections(doc)
3287640410dSJohn Snow                      + self._nodes_for_if_section(ifcond))
3297640410dSJohn Snow
3307640410dSJohn Snow    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
3317640410dSJohn Snow        doc = self._cur_doc
3327640410dSJohn Snow        self._add_doc('Event',
3337640410dSJohn Snow                      self._nodes_for_arguments(doc, arg_type)
3347640410dSJohn Snow                      + self._nodes_for_features(doc)
3357640410dSJohn Snow                      + self._nodes_for_sections(doc)
3367640410dSJohn Snow                      + self._nodes_for_if_section(ifcond))
3377640410dSJohn Snow
3387640410dSJohn Snow    def symbol(self, doc, entity):
3397640410dSJohn Snow        """Add documentation for one symbol to the document tree
3407640410dSJohn Snow
3417640410dSJohn Snow        This is the main entry point which causes us to add documentation
3427640410dSJohn Snow        nodes for a symbol (which could be a 'command', 'object', 'event',
3437640410dSJohn Snow        etc). We do this by calling 'visit' on the schema entity, which
3447640410dSJohn Snow        will then call back into one of our visit_* methods, depending
3457640410dSJohn Snow        on what kind of thing this symbol is.
3467640410dSJohn Snow        """
3477640410dSJohn Snow        self._cur_doc = doc
3487640410dSJohn Snow        entity.visit(self)
3497640410dSJohn Snow        self._cur_doc = None
3507640410dSJohn Snow
3517640410dSJohn Snow    def _start_new_heading(self, heading, level):
3527640410dSJohn Snow        """Start a new heading at the specified heading level
3537640410dSJohn Snow
3547640410dSJohn Snow        Create a new section whose title is 'heading' and which is placed
3557640410dSJohn Snow        in the docutils node tree as a child of the most recent level-1
3567640410dSJohn Snow        heading. Subsequent document sections (commands, freeform doc chunks,
3577640410dSJohn Snow        etc) will be placed as children of this new heading section.
3587640410dSJohn Snow        """
3597640410dSJohn Snow        if len(self._active_headings) < level:
3607640410dSJohn Snow            raise QAPISemError(self._cur_doc.info,
3617640410dSJohn Snow                               'Level %d subheading found outside a '
3627640410dSJohn Snow                               'level %d heading'
3637640410dSJohn Snow                               % (level, level - 1))
3647640410dSJohn Snow        snode = self._make_section(heading)
3657640410dSJohn Snow        self._active_headings[level - 1] += snode
3667640410dSJohn Snow        self._active_headings = self._active_headings[:level]
3677640410dSJohn Snow        self._active_headings.append(snode)
3687640410dSJohn Snow        return snode
3697640410dSJohn Snow
3707640410dSJohn Snow    def _add_node_to_current_heading(self, node):
3717640410dSJohn Snow        """Add the node to whatever the current active heading is"""
3727640410dSJohn Snow        self._active_headings[-1] += node
3737640410dSJohn Snow
3747640410dSJohn Snow    def freeform(self, doc):
3757640410dSJohn Snow        """Add a piece of 'freeform' documentation to the document tree
3767640410dSJohn Snow
3777640410dSJohn Snow        A 'freeform' document chunk doesn't relate to any particular
3787640410dSJohn Snow        symbol (for instance, it could be an introduction).
3797640410dSJohn Snow
3807640410dSJohn Snow        If the freeform document starts with a line of the form
3817640410dSJohn Snow        '= Heading text', this is a section or subsection heading, with
3827640410dSJohn Snow        the heading level indicated by the number of '=' signs.
3837640410dSJohn Snow        """
3847640410dSJohn Snow
3857640410dSJohn Snow        # QAPIDoc documentation says free-form documentation blocks
3867640410dSJohn Snow        # must have only a body section, nothing else.
3877640410dSJohn Snow        assert not doc.sections
3887640410dSJohn Snow        assert not doc.args
3897640410dSJohn Snow        assert not doc.features
3907640410dSJohn Snow        self._cur_doc = doc
3917640410dSJohn Snow
3927640410dSJohn Snow        text = doc.body.text
3937640410dSJohn Snow        if re.match(r'=+ ', text):
3947640410dSJohn Snow            # Section/subsection heading (if present, will always be
3957640410dSJohn Snow            # the first line of the block)
3967640410dSJohn Snow            (heading, _, text) = text.partition('\n')
3977640410dSJohn Snow            (leader, _, heading) = heading.partition(' ')
3987640410dSJohn Snow            node = self._start_new_heading(heading, len(leader))
3997640410dSJohn Snow            if text == '':
4007640410dSJohn Snow                return
4017640410dSJohn Snow        else:
4027640410dSJohn Snow            node = nodes.container()
4037640410dSJohn Snow
4047640410dSJohn Snow        self._parse_text_into_node(text, node)
4057640410dSJohn Snow        self._cur_doc = None
4067640410dSJohn Snow
4077640410dSJohn Snow    def _parse_text_into_node(self, doctext, node):
4087640410dSJohn Snow        """Parse a chunk of QAPI-doc-format text into the node
4097640410dSJohn Snow
4107640410dSJohn Snow        The doc comment can contain most inline rST markup, including
4117640410dSJohn Snow        bulleted and enumerated lists.
4127640410dSJohn Snow        As an extra permitted piece of markup, @var will be turned
4137640410dSJohn Snow        into ``var``.
4147640410dSJohn Snow        """
4157640410dSJohn Snow
4167640410dSJohn Snow        # Handle the "@var means ``var`` case
4177640410dSJohn Snow        doctext = re.sub(r'@([\w-]+)', r'``\1``', doctext)
4187640410dSJohn Snow
4197640410dSJohn Snow        rstlist = ViewList()
4207640410dSJohn Snow        for line in doctext.splitlines():
4217640410dSJohn Snow            # The reported line number will always be that of the start line
4227640410dSJohn Snow            # of the doc comment, rather than the actual location of the error.
4237640410dSJohn Snow            # Being more precise would require overhaul of the QAPIDoc class
4247640410dSJohn Snow            # to track lines more exactly within all the sub-parts of the doc
4257640410dSJohn Snow            # comment, as well as counting lines here.
4267640410dSJohn Snow            rstlist.append(line, self._cur_doc.info.fname,
4277640410dSJohn Snow                           self._cur_doc.info.line)
4287640410dSJohn Snow        # Append a blank line -- in some cases rST syntax errors get
4297640410dSJohn Snow        # attributed to the line after one with actual text, and if there
4307640410dSJohn Snow        # isn't anything in the ViewList corresponding to that then Sphinx
4317640410dSJohn Snow        # 1.6's AutodocReporter will then misidentify the source/line location
4327640410dSJohn Snow        # in the error message (usually attributing it to the top-level
4337640410dSJohn Snow        # .rst file rather than the offending .json file). The extra blank
4347640410dSJohn Snow        # line won't affect the rendered output.
4357640410dSJohn Snow        rstlist.append("", self._cur_doc.info.fname, self._cur_doc.info.line)
4367640410dSJohn Snow        self._sphinx_directive.do_parse(rstlist, node)
4377640410dSJohn Snow
4387640410dSJohn Snow    def get_document_node(self):
4397640410dSJohn Snow        """Return the root docutils node which makes up the document"""
4407640410dSJohn Snow        return self._top_node
441