qapi/qapidoc: add rendering for INTRO sections

Amend the qapidoc generator to handle and render INTRO sections.

The only real difference here from other sections is that we need to
dedent the text so it renders correctly. Members and Features are also
indented, but do not require a dedent() because they are always used
in tandem with an rST construct that forms the start of a new indented
block; there is coincidental harmony.

Plaintext sections, however, do not start their own block and thus
need to be dedented to prevent accidentally rendering them as a
blockquote or a syntax error.

This dedent transformation on the text does not reflow the text, so
source line information remains accurate, and the "blame" chain of
custody for sphinx rST parsing error messages continues to be correct
even through this transformation.

Signed-off-by: John Snow <jsnow@redhat.com>
Message-ID: <20260611042332.482979-13-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message tweaked]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
John Snow
2026-06-11 00:23:31 -04:00
committed by Markus Armbruster
parent 26b9673e0e
commit a5635d6181

View File

@@ -35,6 +35,7 @@ import os
from pathlib import Path from pathlib import Path
import re import re
import sys import sys
import textwrap
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from docutils import nodes from docutils import nodes
@@ -150,8 +151,15 @@ class Transmogrifier:
self, self,
content: str, content: str,
info: QAPISourceInfo, info: QAPISourceInfo,
dedent: bool = False,
) -> None: ) -> None:
lines = content.splitlines(True) lines = content.splitlines(True)
if dedent:
lines = textwrap.dedent(content).splitlines(True)
else:
lines = content.splitlines(True)
for i, line in enumerate(lines): for i, line in enumerate(lines):
self.add_line_raw(line, info.fname, info.line + i) self.add_line_raw(line, info.fname, info.line + i)
@@ -223,13 +231,16 @@ class Transmogrifier:
# Transmogrification helpers # Transmogrification helpers
def visit_paragraph(self, section: QAPIDoc.Section) -> None: def visit_plaintext(self, section: QAPIDoc.Section) -> None:
# Squelch empty paragraphs. # Squelch empty paragraphs.
if not section.text: if not section.text:
return return
# Intro sections, which are indented in QAPI source, need to
# be dedented to avoid accidental block quotes in ReST syntax.
dedent = bool(section.kind == QAPIDoc.Kind.INTRO)
self.ensure_blank_line() self.ensure_blank_line()
self.add_lines(section.text, section.info) self.add_lines(section.text, section.info, dedent)
self.ensure_blank_line() self.ensure_blank_line()
def visit_member(self, section: QAPIDoc.ArgSection) -> None: def visit_member(self, section: QAPIDoc.ArgSection) -> None:
@@ -373,7 +384,7 @@ class Transmogrifier:
section.text = self.reformat_arobase(section.text) section.text = self.reformat_arobase(section.text)
if section.kind.name in ("PLAIN", "INTRO"): if section.kind.name in ("PLAIN", "INTRO"):
self.visit_paragraph(section) self.visit_plaintext(section)
elif section.kind == QAPIDoc.Kind.MEMBER: elif section.kind == QAPIDoc.Kind.MEMBER:
assert isinstance(section, QAPIDoc.ArgSection) assert isinstance(section, QAPIDoc.ArgSection)
if section.name == "q_dummy": if section.name == "q_dummy":