* [PATCH v2 01/13] tests/qapi: generate output in source order
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
@ 2026-04-29 19:25 ` John Snow
2026-05-04 9:50 ` Markus Armbruster
2026-04-29 19:26 ` [PATCH v2 02/13] qapi/docs: remove unused QAPIDoc subsection members John Snow
` (11 subsequent siblings)
12 siblings, 1 reply; 29+ messages in thread
From: John Snow @ 2026-04-29 19:25 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
Rewrite the test doc generator to produce output in source order instead
of arbitrarily by section name.
This patch removes our last use of the "body" field, which has an effect
on how the first plaintext section of each test documented is printed:
we now print "section=Plain" followed by the section text instead of
"body=[...]".
This patch is motivated by a desire to move the QAPIDoc API away from
named fields for specific sections in a bid to force all users to simply
iterate through all_sections in order, instead - and to remove the named
subsections.
Signed-off-by: John Snow <jsnow@redhat.com>
---
tests/qapi-schema/doc-good.out | 26 +++++++++++++-------------
tests/qapi-schema/test-qapi.py | 15 ++++++++-------
2 files changed, 21 insertions(+), 20 deletions(-)
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index 04a55072646..e2be6f96bbf 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -54,15 +54,15 @@ event EVT_BOXED Object
boxed=True
feature feat3
doc freeform
- body=
+ section=Plain
*******
Section
*******
doc freeform
- body=
+ section=Plain
Just text, no heading.
doc freeform
- body=
+ section=Plain
Subsection
==========
@@ -106,7 +106,7 @@ Examples:
- *verbatim*
- {braces}
doc symbol=Enum
- body=
+ section=Plain
arg=one
The _one_ {and only}, description on the same line
@@ -119,13 +119,13 @@ a member feature
section=Plain
@two is undocumented
doc symbol=Base
- body=
+ section=Plain
arg=base1
description starts on a new line,
minimally indented
doc symbol=Variant1
- body=
+ section=Plain
A paragraph
Another paragraph
@@ -138,15 +138,15 @@ a feature
feature=member-feat
a member feature
doc symbol=Variant2
- body=
+ section=Plain
doc symbol=Object
- body=
+ section=Plain
feature=union-feat1
a feature
doc symbol=Alternate
- body=
+ section=Plain
arg=i
description starts on the same line
@@ -157,11 +157,11 @@ description starts on the same line
feature=alt-feat
a feature
doc freeform
- body=
+ section=Plain
Another subsection
==================
doc symbol=cmd
- body=
+ section=Plain
arg=arg1
description starts on a new line,
@@ -210,7 +210,7 @@ Note::
section=Since
2.10
doc symbol=cmd-boxed
- body=
+ section=Plain
If you're bored enough to read this, go see a video of boxed cats
feature=cmd-feat1
a feature
@@ -223,7 +223,7 @@ another feature
<- ... has no title ...
doc symbol=EVT_BOXED
- body=
+ section=Plain
feature=feat3
a feature
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index cf7fb8a6df5..27885147b4d 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -19,6 +19,7 @@
from io import StringIO
from qapi.error import QAPIError
+from qapi.parser import QAPIDoc
from qapi.schema import QAPISchema, QAPISchemaVisitor
@@ -116,13 +117,13 @@ def test_frontend(fname):
print('doc symbol=%s' % doc.symbol)
else:
print('doc freeform')
- print(' body=\n%s' % doc.body.text)
- for arg, section in doc.args.items():
- print(' arg=%s\n%s' % (arg, section.text))
- for feat, section in doc.features.items():
- print(' feature=%s\n%s' % (feat, section.text))
- for section in doc.sections:
- print(' section=%s\n%s' % (section.kind, section.text))
+ for section in doc.all_sections:
+ if section.kind == QAPIDoc.Kind.MEMBER:
+ print(' arg=%s\n%s' % (section.name, section.text))
+ elif section.kind == QAPIDoc.Kind.FEATURE:
+ print(' feature=%s\n%s' % (section.name, section.text))
+ else:
+ print(' section=%s\n%s' % (section.kind, section.text))
def open_test_result(dir_name, file_name, update):
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v2 01/13] tests/qapi: generate output in source order
2026-04-29 19:25 ` [PATCH v2 01/13] tests/qapi: generate output in source order John Snow
@ 2026-05-04 9:50 ` Markus Armbruster
2026-05-04 17:55 ` John Snow
0 siblings, 1 reply; 29+ messages in thread
From: Markus Armbruster @ 2026-05-04 9:50 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
John Snow <jsnow@redhat.com> writes:
> Rewrite the test doc generator to produce output in source order instead
> of arbitrarily by section name.
Yes, please! Looks like leftovers from commit 8d789c8cdb8 (docs/sphinx:
remove legacy QAPI manual generator).
>
> This patch removes our last use of the "body" field, which has an effect
> on how the first plaintext section of each test documented is printed:
What do you mean by "test documented"? The sentence makes sense to me
if I drop "documented".
> we now print "section=Plain" followed by the section text instead of
> "body=[...]".
>
> This patch is motivated by a desire to move the QAPIDoc API away from
> named fields for specific sections in a bid to force all users to simply
> iterate through all_sections in order, instead - and to remove the named
> subsections.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> tests/qapi-schema/doc-good.out | 26 +++++++++++++-------------
> tests/qapi-schema/test-qapi.py | 15 ++++++++-------
> 2 files changed, 21 insertions(+), 20 deletions(-)
>
> diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
> index 04a55072646..e2be6f96bbf 100644
> --- a/tests/qapi-schema/doc-good.out
> +++ b/tests/qapi-schema/doc-good.out
> @@ -54,15 +54,15 @@ event EVT_BOXED Object
> boxed=True
> feature feat3
> doc freeform
> - body=
> + section=Plain
> *******
> Section
> *******
> doc freeform
> - body=
> + section=Plain
> Just text, no heading.
> doc freeform
> - body=
> + section=Plain
> Subsection
> ==========
>
> @@ -106,7 +106,7 @@ Examples:
> - *verbatim*
> - {braces}
> doc symbol=Enum
> - body=
> + section=Plain
>
> arg=one
> The _one_ {and only}, description on the same line
> @@ -119,13 +119,13 @@ a member feature
> section=Plain
> @two is undocumented
> doc symbol=Base
> - body=
> + section=Plain
>
> arg=base1
> description starts on a new line,
> minimally indented
> doc symbol=Variant1
> - body=
> + section=Plain
> A paragraph
>
> Another paragraph
> @@ -138,15 +138,15 @@ a feature
> feature=member-feat
> a member feature
> doc symbol=Variant2
> - body=
> + section=Plain
>
> doc symbol=Object
> - body=
> + section=Plain
>
> feature=union-feat1
> a feature
> doc symbol=Alternate
> - body=
> + section=Plain
>
> arg=i
> description starts on the same line
> @@ -157,11 +157,11 @@ description starts on the same line
> feature=alt-feat
> a feature
> doc freeform
> - body=
> + section=Plain
> Another subsection
> ==================
> doc symbol=cmd
> - body=
> + section=Plain
>
> arg=arg1
> description starts on a new line,
> @@ -210,7 +210,7 @@ Note::
> section=Since
> 2.10
> doc symbol=cmd-boxed
> - body=
> + section=Plain
> If you're bored enough to read this, go see a video of boxed cats
> feature=cmd-feat1
> a feature
> @@ -223,7 +223,7 @@ another feature
>
> <- ... has no title ...
> doc symbol=EVT_BOXED
> - body=
> + section=Plain
>
> feature=feat3
> a feature
> diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
> index cf7fb8a6df5..27885147b4d 100755
> --- a/tests/qapi-schema/test-qapi.py
> +++ b/tests/qapi-schema/test-qapi.py
> @@ -19,6 +19,7 @@
> from io import StringIO
>
> from qapi.error import QAPIError
> +from qapi.parser import QAPIDoc
> from qapi.schema import QAPISchema, QAPISchemaVisitor
>
>
> @@ -116,13 +117,13 @@ def test_frontend(fname):
> print('doc symbol=%s' % doc.symbol)
> else:
> print('doc freeform')
> - print(' body=\n%s' % doc.body.text)
> - for arg, section in doc.args.items():
> - print(' arg=%s\n%s' % (arg, section.text))
> - for feat, section in doc.features.items():
> - print(' feature=%s\n%s' % (feat, section.text))
> - for section in doc.sections:
> - print(' section=%s\n%s' % (section.kind, section.text))
> + for section in doc.all_sections:
> + if section.kind == QAPIDoc.Kind.MEMBER:
> + print(' arg=%s\n%s' % (section.name, section.text))
> + elif section.kind == QAPIDoc.Kind.FEATURE:
> + print(' feature=%s\n%s' % (section.name, section.text))
> + else:
> + print(' section=%s\n%s' % (section.kind, section.text))
>
>
> def open_test_result(dir_name, file_name, update):
The loop body could be dumbed down further:
if isinstance(section, QAPIDoc.ArgSection):
print(' %s=%s' % (section.kind, section.name))
else:
print(' %s' % section.kind)
print(section.text)
Expected output changes a bit more then. I don't mind.
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v2 01/13] tests/qapi: generate output in source order
2026-05-04 9:50 ` Markus Armbruster
@ 2026-05-04 17:55 ` John Snow
0 siblings, 0 replies; 29+ messages in thread
From: John Snow @ 2026-05-04 17:55 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
On Mon, May 4, 2026 at 5:50 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> John Snow <jsnow@redhat.com> writes:
>
> > Rewrite the test doc generator to produce output in source order instead
> > of arbitrarily by section name.
>
> Yes, please! Looks like leftovers from commit 8d789c8cdb8 (docs/sphinx:
> remove legacy QAPI manual generator).
> >
> > This patch removes our last use of the "body" field, which has an effect
> > on how the first plaintext section of each test documented is printed:
>
> What do you mean by "test documented"? The sentence makes sense to me
> if I drop "documented".
Thinko. Probably meant "each test documentation section" and short-circuited.
>
> > we now print "section=Plain" followed by the section text instead of
> > "body=[...]".
> >
> > This patch is motivated by a desire to move the QAPIDoc API away from
> > named fields for specific sections in a bid to force all users to simply
> > iterate through all_sections in order, instead - and to remove the named
> > subsections.
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > tests/qapi-schema/doc-good.out | 26 +++++++++++++-------------
> > tests/qapi-schema/test-qapi.py | 15 ++++++++-------
> > 2 files changed, 21 insertions(+), 20 deletions(-)
> >
> > diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
> > index 04a55072646..e2be6f96bbf 100644
> > --- a/tests/qapi-schema/doc-good.out
> > +++ b/tests/qapi-schema/doc-good.out
> > @@ -54,15 +54,15 @@ event EVT_BOXED Object
> > boxed=True
> > feature feat3
> > doc freeform
> > - body=
> > + section=Plain
> > *******
> > Section
> > *******
> > doc freeform
> > - body=
> > + section=Plain
> > Just text, no heading.
> > doc freeform
> > - body=
> > + section=Plain
> > Subsection
> > ==========
> >
> > @@ -106,7 +106,7 @@ Examples:
> > - *verbatim*
> > - {braces}
> > doc symbol=Enum
> > - body=
> > + section=Plain
> >
> > arg=one
> > The _one_ {and only}, description on the same line
> > @@ -119,13 +119,13 @@ a member feature
> > section=Plain
> > @two is undocumented
> > doc symbol=Base
> > - body=
> > + section=Plain
> >
> > arg=base1
> > description starts on a new line,
> > minimally indented
> > doc symbol=Variant1
> > - body=
> > + section=Plain
> > A paragraph
> >
> > Another paragraph
> > @@ -138,15 +138,15 @@ a feature
> > feature=member-feat
> > a member feature
> > doc symbol=Variant2
> > - body=
> > + section=Plain
> >
> > doc symbol=Object
> > - body=
> > + section=Plain
> >
> > feature=union-feat1
> > a feature
> > doc symbol=Alternate
> > - body=
> > + section=Plain
> >
> > arg=i
> > description starts on the same line
> > @@ -157,11 +157,11 @@ description starts on the same line
> > feature=alt-feat
> > a feature
> > doc freeform
> > - body=
> > + section=Plain
> > Another subsection
> > ==================
> > doc symbol=cmd
> > - body=
> > + section=Plain
> >
> > arg=arg1
> > description starts on a new line,
> > @@ -210,7 +210,7 @@ Note::
> > section=Since
> > 2.10
> > doc symbol=cmd-boxed
> > - body=
> > + section=Plain
> > If you're bored enough to read this, go see a video of boxed cats
> > feature=cmd-feat1
> > a feature
> > @@ -223,7 +223,7 @@ another feature
> >
> > <- ... has no title ...
> > doc symbol=EVT_BOXED
> > - body=
> > + section=Plain
> >
> > feature=feat3
> > a feature
> > diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
> > index cf7fb8a6df5..27885147b4d 100755
> > --- a/tests/qapi-schema/test-qapi.py
> > +++ b/tests/qapi-schema/test-qapi.py
> > @@ -19,6 +19,7 @@
> > from io import StringIO
> >
> > from qapi.error import QAPIError
> > +from qapi.parser import QAPIDoc
> > from qapi.schema import QAPISchema, QAPISchemaVisitor
> >
> >
> > @@ -116,13 +117,13 @@ def test_frontend(fname):
> > print('doc symbol=%s' % doc.symbol)
> > else:
> > print('doc freeform')
> > - print(' body=\n%s' % doc.body.text)
> > - for arg, section in doc.args.items():
> > - print(' arg=%s\n%s' % (arg, section.text))
> > - for feat, section in doc.features.items():
> > - print(' feature=%s\n%s' % (feat, section.text))
> > - for section in doc.sections:
> > - print(' section=%s\n%s' % (section.kind, section.text))
> > + for section in doc.all_sections:
> > + if section.kind == QAPIDoc.Kind.MEMBER:
> > + print(' arg=%s\n%s' % (section.name, section.text))
> > + elif section.kind == QAPIDoc.Kind.FEATURE:
> > + print(' feature=%s\n%s' % (section.name, section.text))
> > + else:
> > + print(' section=%s\n%s' % (section.kind, section.text))
> >
> >
> > def open_test_result(dir_name, file_name, update):
>
> The loop body could be dumbed down further:
>
> if isinstance(section, QAPIDoc.ArgSection):
> print(' %s=%s' % (section.kind, section.name))
> else:
> print(' %s' % section.kind)
> print(section.text)
>
> Expected output changes a bit more then. I don't mind.
Sure, why not. Got other changes to make anyway, ...
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v2 02/13] qapi/docs: remove unused QAPIDoc subsection members
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
2026-04-29 19:25 ` [PATCH v2 01/13] tests/qapi: generate output in source order John Snow
@ 2026-04-29 19:26 ` John Snow
2026-05-04 9:51 ` Markus Armbruster
2026-04-29 19:26 ` [PATCH v2 03/13] qapi/docs: make remaining subsection members "private" John Snow
` (10 subsequent siblings)
12 siblings, 1 reply; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
All users of the QAPIDoc object should be iterating over all_sections
and not grabbing arbitrary subsections, if possible. Remove the 'body'
and 'sections' members, as they are no longer used.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/parser.py | 6 ------
1 file changed, 6 deletions(-)
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index c3cf33904ef..b33edbba74f 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -732,8 +732,6 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None):
self.all_sections: List[QAPIDoc.Section] = [
QAPIDoc.Section(info, QAPIDoc.Kind.PLAIN)
]
- # the body section
- self.body: Optional[QAPIDoc.Section] = self.all_sections[0]
# dicts mapping parameter/feature names to their description
self.args: Dict[str, QAPIDoc.ArgSection] = {}
self.features: Dict[str, QAPIDoc.ArgSection] = {}
@@ -742,8 +740,6 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None):
self.errors: Optional[QAPIDoc.Section] = None
# "Since" section
self.since: Optional[QAPIDoc.Section] = None
- # sections other than .body, .args, .features
- self.sections: List[QAPIDoc.Section] = []
def end(self) -> None:
for section in self.all_sections:
@@ -766,7 +762,6 @@ def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
# start new section
section = self.Section(info, kind)
- self.sections.append(section)
self.all_sections.append(section)
def new_tagged_section(
@@ -790,7 +785,6 @@ def new_tagged_section(
raise QAPISemError(
info, "duplicated '%s' section" % kind)
self.since = section
- self.sections.append(section)
self.all_sections.append(section)
def _new_description(
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v2 02/13] qapi/docs: remove unused QAPIDoc subsection members
2026-04-29 19:26 ` [PATCH v2 02/13] qapi/docs: remove unused QAPIDoc subsection members John Snow
@ 2026-05-04 9:51 ` Markus Armbruster
0 siblings, 0 replies; 29+ messages in thread
From: Markus Armbruster @ 2026-05-04 9:51 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
John Snow <jsnow@redhat.com> writes:
> All users of the QAPIDoc object should be iterating over all_sections
> and not grabbing arbitrary subsections, if possible. Remove the 'body'
> and 'sections' members, as they are no longer used.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v2 03/13] qapi/docs: make remaining subsection members "private"
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
2026-04-29 19:25 ` [PATCH v2 01/13] tests/qapi: generate output in source order John Snow
2026-04-29 19:26 ` [PATCH v2 02/13] qapi/docs: remove unused QAPIDoc subsection members John Snow
@ 2026-04-29 19:26 ` John Snow
2026-05-04 9:54 ` Markus Armbruster
2026-04-29 19:26 ` [PATCH v2 04/13] qapi/docs: add "Intro" section John Snow
` (9 subsequent siblings)
12 siblings, 1 reply; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
These fields are used to provide error checking and internal logistics
and should not be used by a user of the library to directly access
documentation sections, so make them private.
The "since" field alone is left public, as the qapidoc generator does
use this field to pull that section out of the regular flow of the
document.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/parser.py | 54 +++++++++++++++++++++++-------------------
1 file changed, 29 insertions(+), 25 deletions(-)
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index b33edbba74f..da4756a7424 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -555,7 +555,7 @@ def get_doc(self) -> 'QAPIDoc':
break
# Non-blank line, first of a section
if line == 'Features:':
- if doc.features:
+ if doc.has_features:
raise QAPIParseError(
self, "duplicated 'Features:' line")
self.accept(False)
@@ -570,7 +570,7 @@ def get_doc(self) -> 'QAPIDoc':
if text:
doc.append_line(text)
line = self.get_doc_indented(doc)
- if not doc.features:
+ if not doc.has_features:
raise QAPIParseError(
self, 'feature descriptions expected')
no_more_args = True
@@ -733,14 +733,18 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None):
QAPIDoc.Section(info, QAPIDoc.Kind.PLAIN)
]
# dicts mapping parameter/feature names to their description
- self.args: Dict[str, QAPIDoc.ArgSection] = {}
- self.features: Dict[str, QAPIDoc.ArgSection] = {}
+ self._args: Dict[str, QAPIDoc.ArgSection] = {}
+ self._features: Dict[str, QAPIDoc.ArgSection] = {}
# a command's "Returns" and "Errors" section
- self.returns: Optional[QAPIDoc.Section] = None
- self.errors: Optional[QAPIDoc.Section] = None
+ self._returns: Optional[QAPIDoc.Section] = None
+ self._errors: Optional[QAPIDoc.Section] = None
# "Since" section
self.since: Optional[QAPIDoc.Section] = None
+ @property
+ def has_features(self) -> bool:
+ return bool(self._features)
+
def end(self) -> None:
for section in self.all_sections:
section.text = section.text.strip('\n')
@@ -771,15 +775,15 @@ def new_tagged_section(
) -> None:
section = self.Section(info, kind)
if kind == QAPIDoc.Kind.RETURNS:
- if self.returns:
+ if self._returns:
raise QAPISemError(
info, "duplicated '%s' section" % kind)
- self.returns = section
+ self._returns = section
elif kind == QAPIDoc.Kind.ERRORS:
- if self.errors:
+ if self._errors:
raise QAPISemError(
info, "duplicated '%s' section" % kind)
- self.errors = section
+ self._errors = section
elif kind == QAPIDoc.Kind.SINCE:
if self.since:
raise QAPISemError(
@@ -803,16 +807,16 @@ def _new_description(
desc[name] = section
def new_argument(self, info: QAPISourceInfo, name: str) -> None:
- self._new_description(info, name, QAPIDoc.Kind.MEMBER, self.args)
+ self._new_description(info, name, QAPIDoc.Kind.MEMBER, self._args)
def new_feature(self, info: QAPISourceInfo, name: str) -> None:
- self._new_description(info, name, QAPIDoc.Kind.FEATURE, self.features)
+ self._new_description(info, name, QAPIDoc.Kind.FEATURE, self._features)
def append_line(self, line: str) -> None:
self.all_sections[-1].append_line(line)
def connect_member(self, member: 'QAPISchemaMember') -> None:
- if member.name not in self.args:
+ if member.name not in self._args:
assert member.info
if self.symbol not in member.info.pragma.documentation_exceptions:
raise QAPISemError(member.info,
@@ -823,7 +827,7 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
section = QAPIDoc.ArgSection(
self.info, QAPIDoc.Kind.MEMBER, member.name)
- self.args[member.name] = section
+ self._args[member.name] = section
# Determine where to insert stub doc - it should go at the
# end of the members section(s), if any. Note that index 0
@@ -835,14 +839,14 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
index += 1
self.all_sections.insert(index, section)
- self.args[member.name].connect(member)
+ self._args[member.name].connect(member)
def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
- if feature.name not in self.features:
+ if feature.name not in self._features:
raise QAPISemError(feature.info,
"feature '%s' lacks documentation"
% feature.name)
- self.features[feature.name].connect(feature)
+ self._features[feature.name].connect(feature)
def ensure_returns(self, info: QAPISourceInfo) -> None:
@@ -883,18 +887,18 @@ def _insert_near_kind(
def check_expr(self, expr: QAPIExpression) -> None:
if 'command' in expr:
- if self.returns and 'returns' not in expr:
+ if self._returns and 'returns' not in expr:
raise QAPISemError(
- self.returns.info,
+ self._returns.info,
"'Returns' section, but command doesn't return anything")
else:
- if self.returns:
+ if self._returns:
raise QAPISemError(
- self.returns.info,
+ self._returns.info,
"'Returns' section is only valid for commands")
- if self.errors:
+ if self._errors:
raise QAPISemError(
- self.errors.info,
+ self._errors.info,
"'Errors' section is only valid for commands")
def check(self) -> None:
@@ -914,5 +918,5 @@ def check_args_section(
"do" if len(bogus) > 1 else "does"
))
- check_args_section(self.args, 'member')
- check_args_section(self.features, 'feature')
+ check_args_section(self._args, 'member')
+ check_args_section(self._features, 'feature')
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v2 03/13] qapi/docs: make remaining subsection members "private"
2026-04-29 19:26 ` [PATCH v2 03/13] qapi/docs: make remaining subsection members "private" John Snow
@ 2026-05-04 9:54 ` Markus Armbruster
2026-05-04 18:07 ` John Snow
0 siblings, 1 reply; 29+ messages in thread
From: Markus Armbruster @ 2026-05-04 9:54 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
John Snow <jsnow@redhat.com> writes:
> These fields are used to provide error checking and internal logistics
> and should not be used by a user of the library to directly access
> documentation sections, so make them private.
>
> The "since" field alone is left public, as the qapidoc generator does
> use this field to pull that section out of the regular flow of the
> document.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> scripts/qapi/parser.py | 54 +++++++++++++++++++++++-------------------
> 1 file changed, 29 insertions(+), 25 deletions(-)
>
> diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
> index b33edbba74f..da4756a7424 100644
> --- a/scripts/qapi/parser.py
> +++ b/scripts/qapi/parser.py
> @@ -555,7 +555,7 @@ def get_doc(self) -> 'QAPIDoc':
> break
> # Non-blank line, first of a section
> if line == 'Features:':
> - if doc.features:
> + if doc.has_features:
> raise QAPIParseError(
> self, "duplicated 'Features:' line")
> self.accept(False)
> @@ -570,7 +570,7 @@ def get_doc(self) -> 'QAPIDoc':
> if text:
> doc.append_line(text)
> line = self.get_doc_indented(doc)
> - if not doc.features:
> + if not doc.has_features:
> raise QAPIParseError(
> self, 'feature descriptions expected')
> no_more_args = True
These two hunks along with the one adding .has_features() below make the
patch not purely mechanical. Separate patch, please.
Have you considered using a local bool variable instead?
> @@ -733,14 +733,18 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None):
> QAPIDoc.Section(info, QAPIDoc.Kind.PLAIN)
> ]
> # dicts mapping parameter/feature names to their description
> - self.args: Dict[str, QAPIDoc.ArgSection] = {}
> - self.features: Dict[str, QAPIDoc.ArgSection] = {}
> + self._args: Dict[str, QAPIDoc.ArgSection] = {}
> + self._features: Dict[str, QAPIDoc.ArgSection] = {}
> # a command's "Returns" and "Errors" section
> - self.returns: Optional[QAPIDoc.Section] = None
> - self.errors: Optional[QAPIDoc.Section] = None
> + self._returns: Optional[QAPIDoc.Section] = None
> + self._errors: Optional[QAPIDoc.Section] = None
> # "Since" section
> self.since: Optional[QAPIDoc.Section] = None
>
> + @property
> + def has_features(self) -> bool:
> + return bool(self._features)
> +
> def end(self) -> None:
> for section in self.all_sections:
> section.text = section.text.strip('\n')
> @@ -771,15 +775,15 @@ def new_tagged_section(
> ) -> None:
> section = self.Section(info, kind)
> if kind == QAPIDoc.Kind.RETURNS:
> - if self.returns:
> + if self._returns:
> raise QAPISemError(
> info, "duplicated '%s' section" % kind)
> - self.returns = section
> + self._returns = section
> elif kind == QAPIDoc.Kind.ERRORS:
> - if self.errors:
> + if self._errors:
> raise QAPISemError(
> info, "duplicated '%s' section" % kind)
> - self.errors = section
> + self._errors = section
> elif kind == QAPIDoc.Kind.SINCE:
> if self.since:
> raise QAPISemError(
> @@ -803,16 +807,16 @@ def _new_description(
> desc[name] = section
>
> def new_argument(self, info: QAPISourceInfo, name: str) -> None:
> - self._new_description(info, name, QAPIDoc.Kind.MEMBER, self.args)
> + self._new_description(info, name, QAPIDoc.Kind.MEMBER, self._args)
>
> def new_feature(self, info: QAPISourceInfo, name: str) -> None:
> - self._new_description(info, name, QAPIDoc.Kind.FEATURE, self.features)
> + self._new_description(info, name, QAPIDoc.Kind.FEATURE, self._features)
>
> def append_line(self, line: str) -> None:
> self.all_sections[-1].append_line(line)
>
> def connect_member(self, member: 'QAPISchemaMember') -> None:
> - if member.name not in self.args:
> + if member.name not in self._args:
> assert member.info
> if self.symbol not in member.info.pragma.documentation_exceptions:
> raise QAPISemError(member.info,
> @@ -823,7 +827,7 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
>
> section = QAPIDoc.ArgSection(
> self.info, QAPIDoc.Kind.MEMBER, member.name)
> - self.args[member.name] = section
> + self._args[member.name] = section
>
> # Determine where to insert stub doc - it should go at the
> # end of the members section(s), if any. Note that index 0
> @@ -835,14 +839,14 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
> index += 1
> self.all_sections.insert(index, section)
>
> - self.args[member.name].connect(member)
> + self._args[member.name].connect(member)
>
> def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
> - if feature.name not in self.features:
> + if feature.name not in self._features:
> raise QAPISemError(feature.info,
> "feature '%s' lacks documentation"
> % feature.name)
> - self.features[feature.name].connect(feature)
> + self._features[feature.name].connect(feature)
>
> def ensure_returns(self, info: QAPISourceInfo) -> None:
>
> @@ -883,18 +887,18 @@ def _insert_near_kind(
>
> def check_expr(self, expr: QAPIExpression) -> None:
> if 'command' in expr:
> - if self.returns and 'returns' not in expr:
> + if self._returns and 'returns' not in expr:
> raise QAPISemError(
> - self.returns.info,
> + self._returns.info,
> "'Returns' section, but command doesn't return anything")
> else:
> - if self.returns:
> + if self._returns:
> raise QAPISemError(
> - self.returns.info,
> + self._returns.info,
> "'Returns' section is only valid for commands")
> - if self.errors:
> + if self._errors:
> raise QAPISemError(
> - self.errors.info,
> + self._errors.info,
> "'Errors' section is only valid for commands")
>
> def check(self) -> None:
> @@ -914,5 +918,5 @@ def check_args_section(
> "do" if len(bogus) > 1 else "does"
> ))
>
> - check_args_section(self.args, 'member')
> - check_args_section(self.features, 'feature')
> + check_args_section(self._args, 'member')
> + check_args_section(self._features, 'feature')
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v2 03/13] qapi/docs: make remaining subsection members "private"
2026-05-04 9:54 ` Markus Armbruster
@ 2026-05-04 18:07 ` John Snow
0 siblings, 0 replies; 29+ messages in thread
From: John Snow @ 2026-05-04 18:07 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
On Mon, May 4, 2026 at 5:54 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> John Snow <jsnow@redhat.com> writes:
>
> > These fields are used to provide error checking and internal logistics
> > and should not be used by a user of the library to directly access
> > documentation sections, so make them private.
> >
> > The "since" field alone is left public, as the qapidoc generator does
> > use this field to pull that section out of the regular flow of the
> > document.
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > scripts/qapi/parser.py | 54 +++++++++++++++++++++++-------------------
> > 1 file changed, 29 insertions(+), 25 deletions(-)
> >
> > diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
> > index b33edbba74f..da4756a7424 100644
> > --- a/scripts/qapi/parser.py
> > +++ b/scripts/qapi/parser.py
> > @@ -555,7 +555,7 @@ def get_doc(self) -> 'QAPIDoc':
> > break
> > # Non-blank line, first of a section
> > if line == 'Features:':
> > - if doc.features:
> > + if doc.has_features:
> > raise QAPIParseError(
> > self, "duplicated 'Features:' line")
> > self.accept(False)
> > @@ -570,7 +570,7 @@ def get_doc(self) -> 'QAPIDoc':
> > if text:
> > doc.append_line(text)
> > line = self.get_doc_indented(doc)
> > - if not doc.features:
> > + if not doc.has_features:
> > raise QAPIParseError(
> > self, 'feature descriptions expected')
> > no_more_args = True
>
> These two hunks along with the one adding .has_features() below make the
> patch not purely mechanical. Separate patch, please.
And here I thought I already split this all to the Nth degree. :)
>
> Have you considered using a local bool variable instead?
I prefer having less state and more logic, to avoid having to keep
related state synchronized.
>
> > @@ -733,14 +733,18 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None):
> > QAPIDoc.Section(info, QAPIDoc.Kind.PLAIN)
> > ]
> > # dicts mapping parameter/feature names to their description
> > - self.args: Dict[str, QAPIDoc.ArgSection] = {}
> > - self.features: Dict[str, QAPIDoc.ArgSection] = {}
> > + self._args: Dict[str, QAPIDoc.ArgSection] = {}
> > + self._features: Dict[str, QAPIDoc.ArgSection] = {}
> > # a command's "Returns" and "Errors" section
> > - self.returns: Optional[QAPIDoc.Section] = None
> > - self.errors: Optional[QAPIDoc.Section] = None
> > + self._returns: Optional[QAPIDoc.Section] = None
> > + self._errors: Optional[QAPIDoc.Section] = None
> > # "Since" section
> > self.since: Optional[QAPIDoc.Section] = None
> >
> > + @property
> > + def has_features(self) -> bool:
> > + return bool(self._features)
> > +
> > def end(self) -> None:
> > for section in self.all_sections:
> > section.text = section.text.strip('\n')
> > @@ -771,15 +775,15 @@ def new_tagged_section(
> > ) -> None:
> > section = self.Section(info, kind)
> > if kind == QAPIDoc.Kind.RETURNS:
> > - if self.returns:
> > + if self._returns:
> > raise QAPISemError(
> > info, "duplicated '%s' section" % kind)
> > - self.returns = section
> > + self._returns = section
> > elif kind == QAPIDoc.Kind.ERRORS:
> > - if self.errors:
> > + if self._errors:
> > raise QAPISemError(
> > info, "duplicated '%s' section" % kind)
> > - self.errors = section
> > + self._errors = section
> > elif kind == QAPIDoc.Kind.SINCE:
> > if self.since:
> > raise QAPISemError(
> > @@ -803,16 +807,16 @@ def _new_description(
> > desc[name] = section
> >
> > def new_argument(self, info: QAPISourceInfo, name: str) -> None:
> > - self._new_description(info, name, QAPIDoc.Kind.MEMBER, self.args)
> > + self._new_description(info, name, QAPIDoc.Kind.MEMBER, self._args)
> >
> > def new_feature(self, info: QAPISourceInfo, name: str) -> None:
> > - self._new_description(info, name, QAPIDoc.Kind.FEATURE, self.features)
> > + self._new_description(info, name, QAPIDoc.Kind.FEATURE, self._features)
> >
> > def append_line(self, line: str) -> None:
> > self.all_sections[-1].append_line(line)
> >
> > def connect_member(self, member: 'QAPISchemaMember') -> None:
> > - if member.name not in self.args:
> > + if member.name not in self._args:
> > assert member.info
> > if self.symbol not in member.info.pragma.documentation_exceptions:
> > raise QAPISemError(member.info,
> > @@ -823,7 +827,7 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
> >
> > section = QAPIDoc.ArgSection(
> > self.info, QAPIDoc.Kind.MEMBER, member.name)
> > - self.args[member.name] = section
> > + self._args[member.name] = section
> >
> > # Determine where to insert stub doc - it should go at the
> > # end of the members section(s), if any. Note that index 0
> > @@ -835,14 +839,14 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
> > index += 1
> > self.all_sections.insert(index, section)
> >
> > - self.args[member.name].connect(member)
> > + self._args[member.name].connect(member)
> >
> > def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
> > - if feature.name not in self.features:
> > + if feature.name not in self._features:
> > raise QAPISemError(feature.info,
> > "feature '%s' lacks documentation"
> > % feature.name)
> > - self.features[feature.name].connect(feature)
> > + self._features[feature.name].connect(feature)
> >
> > def ensure_returns(self, info: QAPISourceInfo) -> None:
> >
> > @@ -883,18 +887,18 @@ def _insert_near_kind(
> >
> > def check_expr(self, expr: QAPIExpression) -> None:
> > if 'command' in expr:
> > - if self.returns and 'returns' not in expr:
> > + if self._returns and 'returns' not in expr:
> > raise QAPISemError(
> > - self.returns.info,
> > + self._returns.info,
> > "'Returns' section, but command doesn't return anything")
> > else:
> > - if self.returns:
> > + if self._returns:
> > raise QAPISemError(
> > - self.returns.info,
> > + self._returns.info,
> > "'Returns' section is only valid for commands")
> > - if self.errors:
> > + if self._errors:
> > raise QAPISemError(
> > - self.errors.info,
> > + self._errors.info,
> > "'Errors' section is only valid for commands")
> >
> > def check(self) -> None:
> > @@ -914,5 +918,5 @@ def check_args_section(
> > "do" if len(bogus) > 1 else "does"
> > ))
> >
> > - check_args_section(self.args, 'member')
> > - check_args_section(self.features, 'feature')
> > + check_args_section(self._args, 'member')
> > + check_args_section(self._features, 'feature')
>
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v2 04/13] qapi/docs: add "Intro" section
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (2 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 03/13] qapi/docs: make remaining subsection members "private" John Snow
@ 2026-04-29 19:26 ` John Snow
2026-04-29 19:26 ` [PATCH v2 05/13] qapi/docs: adjust stub member insertion algorithm John Snow
` (8 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
This patch adds an "Intro" doc section, intended to eventually replace
the "Plain" doc section alongside a forthcoming "Details" section.
For now, this section is not actually instantiated or used, but
subsequent patches will slowly convert the leading introductory sections
of QAPIDoc documentation to use the new Intro section.
A main motivation of this series of changes is to more explicitly
delineate the "Introductory" documentation for each QAPI definition for
the sake of the inliner. When inlining members, examples, and details
from multiple QAPIDoc sections, we will want to omit the "Introductory"
text from inlined definitions while keeping notes, caution boxes,
examples, and so on.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 2 +-
scripts/qapi/parser.py | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index c2f09bac16c..1f7c15b7075 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -368,7 +368,7 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
for i, section in enumerate(sections):
section.text = self.reformat_arobase(section.text)
- if section.kind == QAPIDoc.Kind.PLAIN:
+ if section.kind.name in ("PLAIN", "INTRO"):
self.visit_paragraph(section)
elif section.kind == QAPIDoc.Kind.MEMBER:
assert isinstance(section, QAPIDoc.ArgSection)
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index da4756a7424..97e7dacb0fd 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -631,7 +631,7 @@ def get_doc(self) -> 'QAPIDoc':
line = self.get_doc_indented(doc)
no_more_args = True
else:
- # plain paragraph
+ # plain paragraph(s)
doc.ensure_untagged_section(self.info)
doc.append_line(line)
line = self.get_doc_paragraph(doc)
@@ -681,6 +681,7 @@ class Kind(enum.Enum):
ERRORS = 4
SINCE = 5
TODO = 6
+ INTRO = 7
@staticmethod
def from_string(kind: str) -> 'QAPIDoc.Kind':
@@ -748,7 +749,7 @@ def has_features(self) -> bool:
def end(self) -> None:
for section in self.all_sections:
section.text = section.text.strip('\n')
- if section.kind != QAPIDoc.Kind.PLAIN and section.text == '':
+ if not (section.kind.name in ("INTRO", "PLAIN") or section.text):
raise QAPISemError(
section.info, "text required after '%s:'" % section.kind)
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v2 05/13] qapi/docs: adjust stub member insertion algorithm
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (3 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 04/13] qapi/docs: add "Intro" section John Snow
@ 2026-04-29 19:26 ` John Snow
2026-05-04 11:20 ` Markus Armbruster
2026-04-29 19:26 ` [PATCH v2 06/13] qapi/docs: remove implicit Plain section John Snow
` (7 subsequent siblings)
12 siblings, 1 reply; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
A forthcoming patch removes the implicit PLAIN section that always
starts a QAPIDoc section list. Further future changes begin converting
"PLAIN" sections to "INTRO" sections. To accommodate this, the insertion
algorithm that places stub and dummy members must be adjusted to cope.
This algorithm can handle zero-or-more PLAIN *or* INTRO sections at the
beginning of a QAPIDoc object.
Since we have three places that need to insert stub members, take the
opportunity to unify and deduplicate this code.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 36 ++++++++---------
scripts/qapi/parser.py | 90 +++++++++++++++++++++++++++---------------
2 files changed, 75 insertions(+), 51 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 1f7c15b7075..70ab9cdc214 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -349,30 +349,32 @@ def _get_target(
)
def visit_sections(self, ent: QAPISchemaDefinition) -> None:
+ # Generate a placeholder right after the member section(s) which
+ # will be used to generate documentation for "The members of..."
+ # pointers in the rendered document.
+ # This is a temporary hack until the inliner is merged.
+ if ent.doc:
+ ent.doc.append_member_stub(
+ QAPIDoc.ArgSection(
+ ent.doc.info, QAPIDoc.Kind.MEMBER, "q_dummy"
+ )
+ )
+
sections = ent.doc.all_sections if ent.doc else []
- # Determine the index location at which we should generate
- # documentation for "The members of ..." pointers. This should
- # go at the end of the members section(s) if any. Note that
- # index 0 is assumed to be a plain intro section, even if it is
- # empty; and that a members section if present will always
- # immediately follow the opening PLAIN section.
- gen_index = 1
- if len(sections) > 1:
- while sections[gen_index].kind == QAPIDoc.Kind.MEMBER:
- gen_index += 1
- if gen_index >= len(sections):
- break
-
# Add sections in source order:
- for i, section in enumerate(sections):
+ for section in sections:
section.text = self.reformat_arobase(section.text)
if section.kind.name in ("PLAIN", "INTRO"):
self.visit_paragraph(section)
elif section.kind == QAPIDoc.Kind.MEMBER:
assert isinstance(section, QAPIDoc.ArgSection)
- self.visit_member(section)
+ if section.name == "q_dummy":
+ # Generate "The members of ..." entries if necessary
+ self._insert_member_pointer(ent)
+ else:
+ self.visit_member(section)
elif section.kind == QAPIDoc.Kind.FEATURE:
assert isinstance(section, QAPIDoc.ArgSection)
self.visit_feature(section)
@@ -386,10 +388,6 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
else:
assert False
- # Generate "The members of ..." entries if necessary:
- if i == gen_index - 1:
- self._insert_member_pointer(ent)
-
self.ensure_blank_line()
# Transmogrification core methods
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 97e7dacb0fd..b21796b3e80 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -816,6 +816,58 @@ def new_feature(self, info: QAPISourceInfo, name: str) -> None:
def append_line(self, line: str) -> None:
self.all_sections[-1].append_line(line)
+ def _insert_near_kind(
+ self,
+ kind: 'QAPIDoc.Kind',
+ new_sect: 'QAPIDoc.Section',
+ after: bool = False,
+ ) -> bool:
+ """Insert or append a new doc section at a specific point."""
+ for idx, sect in enumerate(reversed(self.all_sections)):
+ if sect.kind == kind:
+ pos = len(self.all_sections) - idx - 1
+ if after:
+ pos += 1
+ self.all_sections.insert(pos, new_sect)
+ return True
+ return False
+
+ def _insert_after_intro(
+ self,
+ section: 'QAPIDoc.Section',
+ ) -> None:
+ """
+ Insert a section immediately after the intro section.
+
+ While we convert PLAIN sections to INTRO sections, all
+ contiguous INTRO/PLAIN sections at the start of a QAPIDoc
+ section list are treated as "the intro".
+
+ Once INTRO conversion is complete, this helper will no longer be
+ needed and ``_insert_near_kind(QAPIDoc.Kind.INTRO, ...)`` will
+ be sufficient.
+ """
+ index = 0
+ for index, ref_section in enumerate(self.all_sections):
+ if ref_section.kind.name in ("PLAIN", "INTRO"):
+ continue
+ break
+ else:
+ index += 1
+
+ self.all_sections.insert(index, section)
+
+ def append_member_stub(self, stub: 'QAPIDoc.Section') -> None:
+
+ """
+ Append a stub section after any Member sections.
+ """
+ if self._insert_near_kind(QAPIDoc.Kind.MEMBER, stub, True):
+ return
+
+ # No MEMBER sections present. Insert after INTRO/PLAIN sections.
+ self._insert_after_intro(stub)
+
def connect_member(self, member: 'QAPISchemaMember') -> None:
if member.name not in self._args:
assert member.info
@@ -825,20 +877,10 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
% (member.role, member.name))
# Insert stub documentation section for missing member docs.
# TODO: drop when undocumented members are outlawed
-
- section = QAPIDoc.ArgSection(
+ stub_section = QAPIDoc.ArgSection(
self.info, QAPIDoc.Kind.MEMBER, member.name)
- self._args[member.name] = section
-
- # Determine where to insert stub doc - it should go at the
- # end of the members section(s), if any. Note that index 0
- # is assumed to be an untagged intro section, even if it is
- # empty.
- index = 1
- if len(self.all_sections) > 1:
- while self.all_sections[index].kind == QAPIDoc.Kind.MEMBER:
- index += 1
- self.all_sections.insert(index, section)
+ self._args[member.name] = stub_section
+ self.append_member_stub(stub_section)
self._args[member.name].connect(member)
@@ -851,27 +893,13 @@ def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
def ensure_returns(self, info: QAPISourceInfo) -> None:
- def _insert_near_kind(
- kind: QAPIDoc.Kind,
- new_sect: QAPIDoc.Section,
- after: bool = False,
- ) -> bool:
- for idx, sect in enumerate(reversed(self.all_sections)):
- if sect.kind == kind:
- pos = len(self.all_sections) - idx - 1
- if after:
- pos += 1
- self.all_sections.insert(pos, new_sect)
- return True
- return False
-
if any(s.kind == QAPIDoc.Kind.RETURNS for s in self.all_sections):
return
# Stub "Returns" section for undocumented returns value
stub = QAPIDoc.Section(info, QAPIDoc.Kind.RETURNS)
- if any(_insert_near_kind(kind, stub, after) for kind, after in (
+ if any(self._insert_near_kind(kind, stub, after) for kind, after in (
# 1. If arguments, right after those.
(QAPIDoc.Kind.MEMBER, True),
# 2. Elif errors, right *before* those.
@@ -881,10 +909,8 @@ def _insert_near_kind(
)):
return
- # Otherwise, it should go right after the intro. The intro
- # is always the first section and is always present (even
- # when empty), so we can insert directly at index=1 blindly.
- self.all_sections.insert(1, stub)
+ # Otherwise, it should go right after the intro.
+ self._insert_after_intro(stub)
def check_expr(self, expr: QAPIExpression) -> None:
if 'command' in expr:
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v2 05/13] qapi/docs: adjust stub member insertion algorithm
2026-04-29 19:26 ` [PATCH v2 05/13] qapi/docs: adjust stub member insertion algorithm John Snow
@ 2026-05-04 11:20 ` Markus Armbruster
2026-05-04 18:30 ` John Snow
2026-05-04 18:33 ` John Snow
0 siblings, 2 replies; 29+ messages in thread
From: Markus Armbruster @ 2026-05-04 11:20 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
I'm feeling dense again. Please be patient with me.
John Snow <jsnow@redhat.com> writes:
> A forthcoming patch removes the implicit PLAIN section that always
> starts a QAPIDoc section list. Further future changes begin converting
> "PLAIN" sections to "INTRO" sections. To accommodate this, the insertion
> algorithm that places stub and dummy members must be adjusted to cope.
What are the stub and dummy members?
> This algorithm can handle zero-or-more PLAIN *or* INTRO sections at the
> beginning of a QAPIDoc object.
The revised algorithm, I presume.
What's the structure of its valid input before and after this patch?
> Since we have three places that need to insert stub members, take the
> opportunity to unify and deduplicate this code.
Three? I can only see two: Transmogrifier.visit_sections() and
QAPIDoc.connect_member().
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 36 ++++++++---------
> scripts/qapi/parser.py | 90 +++++++++++++++++++++++++++---------------
> 2 files changed, 75 insertions(+), 51 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 1f7c15b7075..70ab9cdc214 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -349,30 +349,32 @@ def _get_target(
> )
>
> def visit_sections(self, ent: QAPISchemaDefinition) -> None:
> + # Generate a placeholder right after the member section(s) which
> + # will be used to generate documentation for "The members of..."
> + # pointers in the rendered document.
> + # This is a temporary hack until the inliner is merged.
> + if ent.doc:
> + ent.doc.append_member_stub(
> + QAPIDoc.ArgSection(
> + ent.doc.info, QAPIDoc.Kind.MEMBER, "q_dummy"
> + )
> + )
> +
This hack is of the nastier sort: passing a QAPISchema to the doc
generator modifies it.
Would it be possible to add this dummy always in QAPISchema? Any
drawbacks?
[Remainder left for later...]
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 05/13] qapi/docs: adjust stub member insertion algorithm
2026-05-04 11:20 ` Markus Armbruster
@ 2026-05-04 18:30 ` John Snow
2026-05-05 13:54 ` Markus Armbruster
2026-05-04 18:33 ` John Snow
1 sibling, 1 reply; 29+ messages in thread
From: John Snow @ 2026-05-04 18:30 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
On Mon, May 4, 2026 at 7:20 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> I'm feeling dense again. Please be patient with me.
It's okay, this is a complex one. This is part of the reason for
pursuing strict ordering to begin with: the insertion algorithm is
complex and ugly.
>
> John Snow <jsnow@redhat.com> writes:
>
> > A forthcoming patch removes the implicit PLAIN section that always
> > starts a QAPIDoc section list. Further future changes begin converting
> > "PLAIN" sections to "INTRO" sections. To accommodate this, the insertion
> > algorithm that places stub and dummy members must be adjusted to cope.
>
> What are the stub and dummy members?
"stub" - undocumented members.
"dummy" - placeholder section named q_dummy that causes "The members
of ..." references to be printed in the rendered documentation.
>
> > This algorithm can handle zero-or-more PLAIN *or* INTRO sections at the
> > beginning of a QAPIDoc object.
>
> The revised algorithm, I presume.
>
> What's the structure of its valid input before and after this patch?
Before: Plain EverythingElse?
After: (Plain* | Intro*) EverythingElse?
Where EverythingElse may never start with "Intro", and may contain
"Plain" but not as the first token when following a Plain section from
the prior production. (Because contiguous plain sections are merged by
the parser into one section.)
This is to allow a gradual conversion. Once everything is fully
converted, and especially after "details" is introduced and the strict
ordering is enforced, this is tightened down considerably to: Intro?
EverythingElse?
where EverythingElse may no longer contain Plain (It is removed), may
never contain Intro, and may only contain one Details section in the
appropriate position (Near the end, before Since.)
More or less: this patch removes the assumption that every QAPIDoc
starts with exactly one Plain section and allows it to cope with any
number of Intro/Plain sections at the beginning, but makes no change
to what the parser actually produces or accepts. In effect, we go
from:
Plain EverythingElse
to, in the next patch:
Plain? EverythingElse
then as the conversion continues, one of these two:
Intro Plain? EverythingElse? (Without converted intro)
Intro EverythingElse? (With converted intro)
... This entire insertion algorithm gets to be removed once we enforce
strict ordering, because we can insert directly to the correct
position in the list thereafter. So, it is only a temporary complexity
that exists for the sake of gradual conversion and piecemeal list
review of each QAPI module.
>
> > Since we have three places that need to insert stub members, take the
> > opportunity to unify and deduplicate this code.
>
> Three? I can only see two: Transmogrifier.visit_sections() and
> QAPIDoc.connect_member().
Bad wording on my part again. ensure_returns also inserts stub
*sections*, not "members", sorry.
>
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > docs/sphinx/qapidoc.py | 36 ++++++++---------
> > scripts/qapi/parser.py | 90 +++++++++++++++++++++++++++---------------
> > 2 files changed, 75 insertions(+), 51 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index 1f7c15b7075..70ab9cdc214 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -349,30 +349,32 @@ def _get_target(
> > )
> >
> > def visit_sections(self, ent: QAPISchemaDefinition) -> None:
> > + # Generate a placeholder right after the member section(s) which
> > + # will be used to generate documentation for "The members of..."
> > + # pointers in the rendered document.
> > + # This is a temporary hack until the inliner is merged.
> > + if ent.doc:
> > + ent.doc.append_member_stub(
> > + QAPIDoc.ArgSection(
> > + ent.doc.info, QAPIDoc.Kind.MEMBER, "q_dummy"
> > + )
> > + )
> > +
>
> This hack is of the nastier sort: passing a QAPISchema to the doc
> generator modifies it.
>
> Would it be possible to add this dummy always in QAPISchema? Any
> drawbacks?
Well...
I didn't like the idea of generating "q_dummy" stubs inside the
parser, as it is an implementation detail of qapidoc. Though as you
note, this leaks it back out anyway.
Here's my argument: the entire "q_dummy" thing goes away with the
inliner anyway, which is what I am actively working towards, and this
ugliness goes away entirely either way: we do not need q_dummy, we do
not need member pointer stubs, we will not need to modify the caller's
section list.
I found this easier to do, despite the ugliness. Also consider that in
this case, we are building an isolated schema directly inside of the
Sphinx process anyway, so we are spiritually already "modifying our
own copy" - i.e. there's no chance that this stuff leaks out into
other users of the Schema. I think that's actually quite appropriate
and unlikely to cause unintended consequences.
>
> [Remainder left for later...]
>
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 05/13] qapi/docs: adjust stub member insertion algorithm
2026-05-04 18:30 ` John Snow
@ 2026-05-05 13:54 ` Markus Armbruster
0 siblings, 0 replies; 29+ messages in thread
From: Markus Armbruster @ 2026-05-05 13:54 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
John Snow <jsnow@redhat.com> writes:
> On Mon, May 4, 2026 at 7:20 AM Markus Armbruster <armbru@redhat.com> wrote:
>>
>> I'm feeling dense again. Please be patient with me.
>
> It's okay, this is a complex one. This is part of the reason for
> pursuing strict ordering to begin with: the insertion algorithm is
> complex and ugly.
>
>>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > A forthcoming patch removes the implicit PLAIN section that always
>> > starts a QAPIDoc section list. Further future changes begin converting
>> > "PLAIN" sections to "INTRO" sections. To accommodate this, the insertion
>> > algorithm that places stub and dummy members must be adjusted to cope.
>>
>> What are the stub and dummy members?
>
> "stub" - undocumented members.
> "dummy" - placeholder section named q_dummy that causes "The members
> of ..." references to be printed in the rendered documentation.
Got it, thanks.
I figure synthesized "Returns:" can also be effected.
>> > This algorithm can handle zero-or-more PLAIN *or* INTRO sections at the
>> > beginning of a QAPIDoc object.
>>
>> The revised algorithm, I presume.
>>
>> What's the structure of its valid input before and after this patch?
>
> Before: Plain EverythingElse?
> After: (Plain* | Intro*) EverythingElse?
>
> Where EverythingElse may never start with "Intro", and may contain
> "Plain" but not as the first token when following a Plain section from
> the prior production. (Because contiguous plain sections are merged by
> the parser into one section.)
>
> This is to allow a gradual conversion. Once everything is fully
> converted, and especially after "details" is introduced and the strict
> ordering is enforced, this is tightened down considerably to: Intro?
> EverythingElse?
>
> where EverythingElse may no longer contain Plain (It is removed), may
> never contain Intro, and may only contain one Details section in the
> appropriate position (Near the end, before Since.)
>
> More or less: this patch removes the assumption that every QAPIDoc
> starts with exactly one Plain section and allows it to cope with any
> number of Intro/Plain sections at the beginning, but makes no change
> to what the parser actually produces or accepts. In effect, we go
> from:
>
> Plain EverythingElse
>
> to, in the next patch:
>
> Plain? EverythingElse
>
> then as the conversion continues, one of these two:
>
> Intro Plain? EverythingElse? (Without converted intro)
> Intro EverythingElse? (With converted intro)
>
> ... This entire insertion algorithm gets to be removed once we enforce
> strict ordering, because we can insert directly to the correct
> position in the list thereafter. So, it is only a temporary complexity
> that exists for the sake of gradual conversion and piecemeal list
> review of each QAPI module.
Would working this into the commit message make sense?
>> > Since we have three places that need to insert stub members, take the
>> > opportunity to unify and deduplicate this code.
>>
>> Three? I can only see two: Transmogrifier.visit_sections() and
>> QAPIDoc.connect_member().
>
> Bad wording on my part again. ensure_returns also inserts stub
> *sections*, not "members", sorry.
>
>>
>> >
>> > Signed-off-by: John Snow <jsnow@redhat.com>
>> > ---
>> > docs/sphinx/qapidoc.py | 36 ++++++++---------
>> > scripts/qapi/parser.py | 90 +++++++++++++++++++++++++++---------------
>> > 2 files changed, 75 insertions(+), 51 deletions(-)
>> >
>> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
>> > index 1f7c15b7075..70ab9cdc214 100644
>> > --- a/docs/sphinx/qapidoc.py
>> > +++ b/docs/sphinx/qapidoc.py
>> > @@ -349,30 +349,32 @@ def _get_target(
>> > )
>> >
>> > def visit_sections(self, ent: QAPISchemaDefinition) -> None:
>> > + # Generate a placeholder right after the member section(s) which
>> > + # will be used to generate documentation for "The members of..."
>> > + # pointers in the rendered document.
>> > + # This is a temporary hack until the inliner is merged.
>> > + if ent.doc:
>> > + ent.doc.append_member_stub(
>> > + QAPIDoc.ArgSection(
>> > + ent.doc.info, QAPIDoc.Kind.MEMBER, "q_dummy"
>> > + )
>> > + )
>> > +
>>
>> This hack is of the nastier sort: passing a QAPISchema to the doc
>> generator modifies it.
>>
>> Would it be possible to add this dummy always in QAPISchema? Any
>> drawbacks?
>
> Well...
>
> I didn't like the idea of generating "q_dummy" stubs inside the
> parser, as it is an implementation detail of qapidoc. Though as you
> note, this leaks it back out anyway.
>
> Here's my argument: the entire "q_dummy" thing goes away with the
> inliner anyway, which is what I am actively working towards, and this
> ugliness goes away entirely either way: we do not need q_dummy, we do
> not need member pointer stubs, we will not need to modify the caller's
> section list.
>
> I found this easier to do, despite the ugliness. Also consider that in
> this case, we are building an isolated schema directly inside of the
> Sphinx process anyway, so we are spiritually already "modifying our
> own copy" - i.e. there's no chance that this stuff leaks out into
> other users of the Schema. I think that's actually quite appropriate
> and unlikely to cause unintended consequences.
I think this is really a QAPIDoc defect.
The doc parser knows where the (empty) arguments are.
QAPIDoc also knows, but makes it hard to retrieve.
The old code searches QAPIDoc.all_sections to rediscover the spot.
Moderately ugly, relies on the well-known possible sequence of sections
in all_sections.
Your patch factors out the search, runs it once, and hacks up
.all_sections to store the result.
If we want the result stored in .all_sections (or anywhere in QAPIDoc,
really), why not simply store it when we know it?
Sometimes I wish QAPIDoc had a structure more useful than flat list of
sections...
>> [Remainder left for later...]
>>
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 05/13] qapi/docs: adjust stub member insertion algorithm
2026-05-04 11:20 ` Markus Armbruster
2026-05-04 18:30 ` John Snow
@ 2026-05-04 18:33 ` John Snow
1 sibling, 0 replies; 29+ messages in thread
From: John Snow @ 2026-05-04 18:33 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
On Mon, May 4, 2026 at 7:20 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> I'm feeling dense again. Please be patient with me.
>
> John Snow <jsnow@redhat.com> writes:
>
> > A forthcoming patch removes the implicit PLAIN section that always
> > starts a QAPIDoc section list. Further future changes begin converting
> > "PLAIN" sections to "INTRO" sections. To accommodate this, the insertion
> > algorithm that places stub and dummy members must be adjusted to cope.
>
> What are the stub and dummy members?
>
> > This algorithm can handle zero-or-more PLAIN *or* INTRO sections at the
> > beginning of a QAPIDoc object.
>
> The revised algorithm, I presume.
>
> What's the structure of its valid input before and after this patch?
Addendum thought: this algorithm is more complex/flexible than it
needs to be with regards to what possible inputs may be, but this is
to allow it to cope with a changing parser during the gradual
conversion.
Take heart: the .ir outputs do not change at all with this algorithm
change, so you can feel assured that it Does The Right Thing, even if
it looks hideous.
--js
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v2 06/13] qapi/docs: remove implicit Plain section
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (4 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 05/13] qapi/docs: adjust stub member insertion algorithm John Snow
@ 2026-04-29 19:26 ` John Snow
2026-04-29 19:26 ` [PATCH v2 07/13] qapi/docs: add "Intro" section parsing John Snow
` (6 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
Prior to this patch, we always instantiate an empty Plain
section. Removing this allows us to gradually phase out the "Plain"
section in favor of "Intro" and "Details" sections while keeping "Plain"
around for the interim churn during the series - meaning that we don't
actually know at __init__ time which type of section we'll have first.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/parser.py | 4 +---
tests/qapi-schema/doc-good.out | 14 --------------
2 files changed, 1 insertion(+), 17 deletions(-)
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index b21796b3e80..6612f471bb8 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -730,9 +730,7 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None):
# definition doc's symbol, None for free-form doc
self.symbol: Optional[str] = symbol
# the sections in textual order
- self.all_sections: List[QAPIDoc.Section] = [
- QAPIDoc.Section(info, QAPIDoc.Kind.PLAIN)
- ]
+ self.all_sections: List[QAPIDoc.Section] = []
# dicts mapping parameter/feature names to their description
self._args: Dict[str, QAPIDoc.ArgSection] = {}
self._features: Dict[str, QAPIDoc.ArgSection] = {}
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index e2be6f96bbf..6fcc8175cfe 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -106,8 +106,6 @@ Examples:
- *verbatim*
- {braces}
doc symbol=Enum
- section=Plain
-
arg=one
The _one_ {and only}, description on the same line
arg=two
@@ -119,8 +117,6 @@ a member feature
section=Plain
@two is undocumented
doc symbol=Base
- section=Plain
-
arg=base1
description starts on a new line,
minimally indented
@@ -138,16 +134,10 @@ a feature
feature=member-feat
a member feature
doc symbol=Variant2
- section=Plain
-
doc symbol=Object
- section=Plain
-
feature=union-feat1
a feature
doc symbol=Alternate
- section=Plain
-
arg=i
description starts on the same line
remainder indented the same
@@ -161,8 +151,6 @@ doc freeform
Another subsection
==================
doc symbol=cmd
- section=Plain
-
arg=arg1
description starts on a new line,
indented
@@ -223,7 +211,5 @@ another feature
<- ... has no title ...
doc symbol=EVT_BOXED
- section=Plain
-
feature=feat3
a feature
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v2 07/13] qapi/docs: add "Intro" section parsing
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (5 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 06/13] qapi/docs: remove implicit Plain section John Snow
@ 2026-04-29 19:26 ` John Snow
2026-05-04 11:57 ` Markus Armbruster
2026-04-29 19:26 ` [PATCH v2 08/13] qapi/docs: Add rendering for INTRO sections John Snow
` (5 subsequent siblings)
12 siblings, 1 reply; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
Add parsing for explicit Intro section syntax.
A side effect of this patch is that we will always create an empty Intro
section, similar to how we used to have an empty Plain section. The
tests are adjusted accordingly, rendered document output does not change
at all.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/devel/qapi-code-gen.rst | 17 +++++----
scripts/qapi/parser.py | 47 ++++++++++++++++++-------
tests/qapi-schema/doc-good.out | 18 ++++++++++
tests/qapi-schema/doc-missing-colon.err | 2 +-
4 files changed, 64 insertions(+), 20 deletions(-)
diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst
index 3a632b4a648..a8175934d52 100644
--- a/docs/devel/qapi-code-gen.rst
+++ b/docs/devel/qapi-code-gen.rst
@@ -862,7 +862,7 @@ documentation comment.
If the documentation comment starts like ::
##
- # @SYMBOL:
+ # @SYMBOL: [...]
it documents the definition of SYMBOL, else it's free-form
documentation.
@@ -984,11 +984,11 @@ definition it documents.
When documentation is required (see pragma_ 'doc-required'), every
definition must have documentation.
-Definition documentation starts with a line naming the definition,
-followed by an optional overview, a description of each argument (for
-commands and events), member (for structs and unions), branch (for
-alternates), or value (for enums), a description of each feature (if
-any), and finally optional tagged sections.
+Definition documentation starts with a description naming the definition
+with an optional overview, a description of each argument (for commands
+and events), member (for structs and unions), branch (for alternates),
+or value (for enums), a description of each feature (if any), and
+finally optional tagged sections.
Descriptions start with '\@name:'. The description text must be
indented like this::
@@ -996,6 +996,11 @@ indented like this::
# @name: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
# do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+Definition descriptions are special: the optional introductory overview
+describing the definition will not be inlined when referenced by other
+definitions (such as when using 'base' to include members from another
+definition), while other descriptions and tagged sections will be.
+
.. FIXME The parser accepts these things in almost any order.
.. FIXME union branches should be described, too.
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 6612f471bb8..c23fd26aaa7 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -24,6 +24,7 @@
Match,
Optional,
Set,
+ Tuple,
Union,
)
@@ -524,6 +525,30 @@ def get_doc_paragraph(self, doc: 'QAPIDoc') -> Optional[str]:
return line
doc.append_line(line)
+ def _get_doc_intro(
+ self,
+ line: str,
+ info: QAPISourceInfo
+ ) -> Tuple['QAPIDoc', Optional[str]]:
+ match = self._match_at_name_colon(line)
+ if not match:
+ raise QAPIParseError(self, "@name must end with ':'")
+
+ # Invalid names are not checked here, but the name
+ # provided *must* match the following definition,
+ # which *is* validated in expr.py.
+ symbol = match.group(1)
+ if not symbol:
+ raise QAPIParseError(self, "name required after '@'")
+ doc = QAPIDoc(info, symbol)
+
+ doc.ensure_untagged_section(info, QAPIDoc.Kind.INTRO)
+ text = line[match.end():]
+ if text:
+ doc.append_line(text)
+
+ return doc, self.get_doc_indented(doc)
+
def get_doc(self) -> 'QAPIDoc':
if self.val != '##':
raise QAPIParseError(
@@ -532,18 +557,9 @@ def get_doc(self) -> 'QAPIDoc':
self.accept(False)
line = self.get_doc_line()
if line is not None and line.startswith('@'):
+
# Definition documentation
- if not line.endswith(':'):
- raise QAPIParseError(self, "line should end with ':'")
- # Invalid names are not checked here, but the name
- # provided *must* match the following definition,
- # which *is* validated in expr.py.
- symbol = line[1:-1]
- if not symbol:
- raise QAPIParseError(self, "name required after '@'")
- doc = QAPIDoc(info, symbol)
- self.accept(False)
- line = self.get_doc_line()
+ doc, line = self._get_doc_intro(line, info)
no_more_args = False
while line is not None:
@@ -751,8 +767,13 @@ def end(self) -> None:
raise QAPISemError(
section.info, "text required after '%s:'" % section.kind)
- def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
- kind = QAPIDoc.Kind.PLAIN
+ def ensure_untagged_section(
+ self,
+ info: QAPISourceInfo,
+ kind: Optional['QAPIDoc.Kind'] = None,
+ ) -> None:
+ if kind is None:
+ kind = QAPIDoc.Kind.PLAIN
if self.all_sections and self.all_sections[-1].kind == kind:
# extend current section
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index 6fcc8175cfe..bc89853765f 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -106,6 +106,8 @@ Examples:
- *verbatim*
- {braces}
doc symbol=Enum
+ section=Intro
+
arg=one
The _one_ {and only}, description on the same line
arg=two
@@ -117,10 +119,14 @@ a member feature
section=Plain
@two is undocumented
doc symbol=Base
+ section=Intro
+
arg=base1
description starts on a new line,
minimally indented
doc symbol=Variant1
+ section=Intro
+
section=Plain
A paragraph
@@ -134,10 +140,16 @@ a feature
feature=member-feat
a member feature
doc symbol=Variant2
+ section=Intro
+
doc symbol=Object
+ section=Intro
+
feature=union-feat1
a feature
doc symbol=Alternate
+ section=Intro
+
arg=i
description starts on the same line
remainder indented the same
@@ -151,6 +163,8 @@ doc freeform
Another subsection
==================
doc symbol=cmd
+ section=Intro
+
arg=arg1
description starts on a new line,
indented
@@ -198,6 +212,8 @@ Note::
section=Since
2.10
doc symbol=cmd-boxed
+ section=Intro
+
section=Plain
If you're bored enough to read this, go see a video of boxed cats
feature=cmd-feat1
@@ -211,5 +227,7 @@ another feature
<- ... has no title ...
doc symbol=EVT_BOXED
+ section=Intro
+
feature=feat3
a feature
diff --git a/tests/qapi-schema/doc-missing-colon.err b/tests/qapi-schema/doc-missing-colon.err
index cbcea007153..bd5862b30f3 100644
--- a/tests/qapi-schema/doc-missing-colon.err
+++ b/tests/qapi-schema/doc-missing-colon.err
@@ -1 +1 @@
-doc-missing-colon.json:4:1: line should end with ':'
+doc-missing-colon.json:4:1: @name must end with ':'
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v2 07/13] qapi/docs: add "Intro" section parsing
2026-04-29 19:26 ` [PATCH v2 07/13] qapi/docs: add "Intro" section parsing John Snow
@ 2026-05-04 11:57 ` Markus Armbruster
2026-05-04 18:44 ` John Snow
0 siblings, 1 reply; 29+ messages in thread
From: Markus Armbruster @ 2026-05-04 11:57 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
John Snow <jsnow@redhat.com> writes:
> Add parsing for explicit Intro section syntax.
>
> A side effect of this patch is that we will always create an empty Intro
> section, similar to how we used to have an empty Plain section. The
> tests are adjusted accordingly, rendered document output does not change
> at all.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/devel/qapi-code-gen.rst | 17 +++++----
> scripts/qapi/parser.py | 47 ++++++++++++++++++-------
> tests/qapi-schema/doc-good.out | 18 ++++++++++
> tests/qapi-schema/doc-missing-colon.err | 2 +-
> 4 files changed, 64 insertions(+), 20 deletions(-)
>
> diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst
> index 3a632b4a648..a8175934d52 100644
> --- a/docs/devel/qapi-code-gen.rst
> +++ b/docs/devel/qapi-code-gen.rst
> @@ -862,7 +862,7 @@ documentation comment.
> If the documentation comment starts like ::
>
> ##
> - # @SYMBOL:
> + # @SYMBOL: [...]
>
> it documents the definition of SYMBOL, else it's free-form
> documentation.
> @@ -984,11 +984,11 @@ definition it documents.
> When documentation is required (see pragma_ 'doc-required'), every
> definition must have documentation.
>
> -Definition documentation starts with a line naming the definition,
> -followed by an optional overview, a description of each argument (for
> -commands and events), member (for structs and unions), branch (for
> -alternates), or value (for enums), a description of each feature (if
> -any), and finally optional tagged sections.
> +Definition documentation starts with a description naming the definition
> +with an optional overview, a description of each argument (for commands
> +and events), member (for structs and unions), branch (for alternates),
> +or value (for enums), a description of each feature (if any), and
> +finally optional tagged sections.
>
> Descriptions start with '\@name:'. The description text must be
> indented like this::
> @@ -996,6 +996,11 @@ indented like this::
> # @name: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
> # do eiusmod tempor incididunt ut labore et dolore magna aliqua.
>
> +Definition descriptions are special: the optional introductory overview
> +describing the definition will not be inlined when referenced by other
> +definitions (such as when using 'base' to include members from another
> +definition), while other descriptions and tagged sections will be.
> +
Isn't this premature? We're not inlining anything just yet.
> .. FIXME The parser accepts these things in almost any order.
>
> .. FIXME union branches should be described, too.
> diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
> index 6612f471bb8..c23fd26aaa7 100644
> --- a/scripts/qapi/parser.py
> +++ b/scripts/qapi/parser.py
> @@ -24,6 +24,7 @@
> Match,
> Optional,
> Set,
> + Tuple,
> Union,
> )
>
> @@ -524,6 +525,30 @@ def get_doc_paragraph(self, doc: 'QAPIDoc') -> Optional[str]:
> return line
> doc.append_line(line)
>
> + def _get_doc_intro(
> + self,
> + line: str,
> + info: QAPISourceInfo
> + ) -> Tuple['QAPIDoc', Optional[str]]:
> + match = self._match_at_name_colon(line)
> + if not match:
> + raise QAPIParseError(self, "@name must end with ':'")
> +
> + # Invalid names are not checked here, but the name
> + # provided *must* match the following definition,
> + # which *is* validated in expr.py.
> + symbol = match.group(1)
> + if not symbol:
> + raise QAPIParseError(self, "name required after '@'")
> + doc = QAPIDoc(info, symbol)
> +
Drop the blank line. For what it's worth, you do it already in PATCH
13.
> + doc.ensure_untagged_section(info, QAPIDoc.Kind.INTRO)
Hmm.
.ensure_untagged_section() extends the current section if we have one
and it is of kind .INTRO, else creates a new one of kind .INTRO.
But there is no current section here! Why not simply create one?
> + text = line[match.end():]
> + if text:
> + doc.append_line(text)
> +
> + return doc, self.get_doc_indented(doc)
> +
> def get_doc(self) -> 'QAPIDoc':
> if self.val != '##':
> raise QAPIParseError(
> @@ -532,18 +557,9 @@ def get_doc(self) -> 'QAPIDoc':
> self.accept(False)
> line = self.get_doc_line()
> if line is not None and line.startswith('@'):
> +
Drop the blank line, please.
> # Definition documentation
> - if not line.endswith(':'):
> - raise QAPIParseError(self, "line should end with ':'")
> - # Invalid names are not checked here, but the name
> - # provided *must* match the following definition,
> - # which *is* validated in expr.py.
> - symbol = line[1:-1]
> - if not symbol:
> - raise QAPIParseError(self, "name required after '@'")
> - doc = QAPIDoc(info, symbol)
> - self.accept(False)
> - line = self.get_doc_line()
> + doc, line = self._get_doc_intro(line, info)
> no_more_args = False
>
> while line is not None:
> @@ -751,8 +767,13 @@ def end(self) -> None:
> raise QAPISemError(
> section.info, "text required after '%s:'" % section.kind)
>
> - def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
> - kind = QAPIDoc.Kind.PLAIN
> + def ensure_untagged_section(
> + self,
> + info: QAPISourceInfo,
> + kind: Optional['QAPIDoc.Kind'] = None,
Why not = QAPIDoc.Kind.PLAIN here, so ...
> + ) -> None:
> + if kind is None:
> + kind = QAPIDoc.Kind.PLAIN
... you don't need this?
>
> if self.all_sections and self.all_sections[-1].kind == kind:
> # extend current section
Hmm.
QAPIDoc.ensure_untagged_section() extends the current section if we have
one and it's "untagged", else creates a new "untagged" section.
In the beginning, there was just one kind of "untagged" section. Its
encoding eventually became "Section.kind is QAPIDoc.Kind.PLAIN". So
what the function does was clear enough.
This patch adds a second kind: QAPIDoc.Kind.INTRO.
.ensure_untagged_section() now takes a kind argument. It ensures *that*
kind of section.
I think the function name becomes misleading. It can now ensure *any*
kind of section, not just "untagged".
Rename to ensure_section() and make the @kind argument mandatory?
> diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
> index 6fcc8175cfe..bc89853765f 100644
> --- a/tests/qapi-schema/doc-good.out
> +++ b/tests/qapi-schema/doc-good.out
> @@ -106,6 +106,8 @@ Examples:
> - *verbatim*
> - {braces}
> doc symbol=Enum
> + section=Intro
> +
> arg=one
> The _one_ {and only}, description on the same line
> arg=two
> @@ -117,10 +119,14 @@ a member feature
> section=Plain
> @two is undocumented
> doc symbol=Base
> + section=Intro
> +
> arg=base1
> description starts on a new line,
> minimally indented
> doc symbol=Variant1
> + section=Intro
> +
> section=Plain
> A paragraph
>
> @@ -134,10 +140,16 @@ a feature
> feature=member-feat
> a member feature
> doc symbol=Variant2
> + section=Intro
> +
> doc symbol=Object
> + section=Intro
> +
> feature=union-feat1
> a feature
> doc symbol=Alternate
> + section=Intro
> +
> arg=i
> description starts on the same line
> remainder indented the same
> @@ -151,6 +163,8 @@ doc freeform
> Another subsection
> ==================
> doc symbol=cmd
> + section=Intro
> +
> arg=arg1
> description starts on a new line,
> indented
> @@ -198,6 +212,8 @@ Note::
> section=Since
> 2.10
> doc symbol=cmd-boxed
> + section=Intro
> +
> section=Plain
> If you're bored enough to read this, go see a video of boxed cats
> feature=cmd-feat1
> @@ -211,5 +227,7 @@ another feature
>
> <- ... has no title ...
> doc symbol=EVT_BOXED
> + section=Intro
> +
> feature=feat3
> a feature
> diff --git a/tests/qapi-schema/doc-missing-colon.err b/tests/qapi-schema/doc-missing-colon.err
> index cbcea007153..bd5862b30f3 100644
> --- a/tests/qapi-schema/doc-missing-colon.err
> +++ b/tests/qapi-schema/doc-missing-colon.err
> @@ -1 +1 @@
> -doc-missing-colon.json:4:1: line should end with ':'
> +doc-missing-colon.json:4:1: @name must end with ':'
Let's not change this error message in this patch. For what it's worth,
PATCH 13 changes it right back.
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v2 07/13] qapi/docs: add "Intro" section parsing
2026-05-04 11:57 ` Markus Armbruster
@ 2026-05-04 18:44 ` John Snow
2026-05-05 13:30 ` Markus Armbruster
0 siblings, 1 reply; 29+ messages in thread
From: John Snow @ 2026-05-04 18:44 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
On Mon, May 4, 2026 at 7:57 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> John Snow <jsnow@redhat.com> writes:
>
> > Add parsing for explicit Intro section syntax.
> >
> > A side effect of this patch is that we will always create an empty Intro
> > section, similar to how we used to have an empty Plain section. The
> > tests are adjusted accordingly, rendered document output does not change
> > at all.
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > docs/devel/qapi-code-gen.rst | 17 +++++----
> > scripts/qapi/parser.py | 47 ++++++++++++++++++-------
> > tests/qapi-schema/doc-good.out | 18 ++++++++++
> > tests/qapi-schema/doc-missing-colon.err | 2 +-
> > 4 files changed, 64 insertions(+), 20 deletions(-)
> >
> > diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst
> > index 3a632b4a648..a8175934d52 100644
> > --- a/docs/devel/qapi-code-gen.rst
> > +++ b/docs/devel/qapi-code-gen.rst
> > @@ -862,7 +862,7 @@ documentation comment.
> > If the documentation comment starts like ::
> >
> > ##
> > - # @SYMBOL:
> > + # @SYMBOL: [...]
> >
> > it documents the definition of SYMBOL, else it's free-form
> > documentation.
> > @@ -984,11 +984,11 @@ definition it documents.
> > When documentation is required (see pragma_ 'doc-required'), every
> > definition must have documentation.
> >
> > -Definition documentation starts with a line naming the definition,
> > -followed by an optional overview, a description of each argument (for
> > -commands and events), member (for structs and unions), branch (for
> > -alternates), or value (for enums), a description of each feature (if
> > -any), and finally optional tagged sections.
> > +Definition documentation starts with a description naming the definition
> > +with an optional overview, a description of each argument (for commands
> > +and events), member (for structs and unions), branch (for alternates),
> > +or value (for enums), a description of each feature (if any), and
> > +finally optional tagged sections.
> >
> > Descriptions start with '\@name:'. The description text must be
> > indented like this::
> > @@ -996,6 +996,11 @@ indented like this::
> > # @name: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
> > # do eiusmod tempor incididunt ut labore et dolore magna aliqua.
> >
> > +Definition descriptions are special: the optional introductory overview
> > +describing the definition will not be inlined when referenced by other
> > +definitions (such as when using 'base' to include members from another
> > +definition), while other descriptions and tagged sections will be.
> > +
>
> Isn't this premature? We're not inlining anything just yet.
It is, but I felt compelled to justify it holistically instead of
playing "dumb" with the documentation and pretending it didn't exist.
Feel free to suggest better phrasing/rationale here...
>
> > .. FIXME The parser accepts these things in almost any order.
> >
> > .. FIXME union branches should be described, too.
> > diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
> > index 6612f471bb8..c23fd26aaa7 100644
> > --- a/scripts/qapi/parser.py
> > +++ b/scripts/qapi/parser.py
> > @@ -24,6 +24,7 @@
> > Match,
> > Optional,
> > Set,
> > + Tuple,
> > Union,
> > )
> >
> > @@ -524,6 +525,30 @@ def get_doc_paragraph(self, doc: 'QAPIDoc') -> Optional[str]:
> > return line
> > doc.append_line(line)
> >
> > + def _get_doc_intro(
> > + self,
> > + line: str,
> > + info: QAPISourceInfo
> > + ) -> Tuple['QAPIDoc', Optional[str]]:
> > + match = self._match_at_name_colon(line)
> > + if not match:
> > + raise QAPIParseError(self, "@name must end with ':'")
> > +
> > + # Invalid names are not checked here, but the name
> > + # provided *must* match the following definition,
> > + # which *is* validated in expr.py.
> > + symbol = match.group(1)
> > + if not symbol:
> > + raise QAPIParseError(self, "name required after '@'")
> > + doc = QAPIDoc(info, symbol)
> > +
>
> Drop the blank line. For what it's worth, you do it already in PATCH
> 13.
>
> > + doc.ensure_untagged_section(info, QAPIDoc.Kind.INTRO)
>
> Hmm.
>
> .ensure_untagged_section() extends the current section if we have one
> and it is of kind .INTRO, else creates a new one of kind .INTRO.
>
> But there is no current section here! Why not simply create one?
Leftover. I originally wanted to create this conditionally, but ran
into troubles doing so and gave up and made it unconditional, but
still using the conditional constructor.
>
> > + text = line[match.end():]
> > + if text:
> > + doc.append_line(text)
> > +
> > + return doc, self.get_doc_indented(doc)
> > +
> > def get_doc(self) -> 'QAPIDoc':
> > if self.val != '##':
> > raise QAPIParseError(
> > @@ -532,18 +557,9 @@ def get_doc(self) -> 'QAPIDoc':
> > self.accept(False)
> > line = self.get_doc_line()
> > if line is not None and line.startswith('@'):
> > +
>
> Drop the blank line, please.
>
> > # Definition documentation
> > - if not line.endswith(':'):
> > - raise QAPIParseError(self, "line should end with ':'")
> > - # Invalid names are not checked here, but the name
> > - # provided *must* match the following definition,
> > - # which *is* validated in expr.py.
> > - symbol = line[1:-1]
> > - if not symbol:
> > - raise QAPIParseError(self, "name required after '@'")
> > - doc = QAPIDoc(info, symbol)
> > - self.accept(False)
> > - line = self.get_doc_line()
> > + doc, line = self._get_doc_intro(line, info)
> > no_more_args = False
> >
> > while line is not None:
> > @@ -751,8 +767,13 @@ def end(self) -> None:
> > raise QAPISemError(
> > section.info, "text required after '%s:'" % section.kind)
> >
> > - def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
> > - kind = QAPIDoc.Kind.PLAIN
> > + def ensure_untagged_section(
> > + self,
> > + info: QAPISourceInfo,
> > + kind: Optional['QAPIDoc.Kind'] = None,
>
> Why not = QAPIDoc.Kind.PLAIN here, so ...
>
> > + ) -> None:
> > + if kind is None:
> > + kind = QAPIDoc.Kind.PLAIN
>
> ... you don't need this?
Because at the time this function definition is parsed, that
definition does not exist yet :)
>
> >
> > if self.all_sections and self.all_sections[-1].kind == kind:
> > # extend current section
>
> Hmm.
>
> QAPIDoc.ensure_untagged_section() extends the current section if we have
> one and it's "untagged", else creates a new "untagged" section.
>
> In the beginning, there was just one kind of "untagged" section. Its
> encoding eventually became "Section.kind is QAPIDoc.Kind.PLAIN". So
> what the function does was clear enough.
>
> This patch adds a second kind: QAPIDoc.Kind.INTRO.
> .ensure_untagged_section() now takes a kind argument. It ensures *that*
> kind of section.
>
> I think the function name becomes misleading. It can now ensure *any*
> kind of section, not just "untagged".
>
> Rename to ensure_section() and make the @kind argument mandatory?
Sure. I agree "untagged" has kind of long since lost its meaning, and
I'm kind of mumbly-brained about what these terms mean anyway, so I
was somewhat hesitant to rename it and wanted to see if I could get
away with not sweeping that corner of the room.
No such luck. Rats.
>
> > diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
> > index 6fcc8175cfe..bc89853765f 100644
> > --- a/tests/qapi-schema/doc-good.out
> > +++ b/tests/qapi-schema/doc-good.out
> > @@ -106,6 +106,8 @@ Examples:
> > - *verbatim*
> > - {braces}
> > doc symbol=Enum
> > + section=Intro
> > +
> > arg=one
> > The _one_ {and only}, description on the same line
> > arg=two
> > @@ -117,10 +119,14 @@ a member feature
> > section=Plain
> > @two is undocumented
> > doc symbol=Base
> > + section=Intro
> > +
> > arg=base1
> > description starts on a new line,
> > minimally indented
> > doc symbol=Variant1
> > + section=Intro
> > +
> > section=Plain
> > A paragraph
> >
> > @@ -134,10 +140,16 @@ a feature
> > feature=member-feat
> > a member feature
> > doc symbol=Variant2
> > + section=Intro
> > +
> > doc symbol=Object
> > + section=Intro
> > +
> > feature=union-feat1
> > a feature
> > doc symbol=Alternate
> > + section=Intro
> > +
> > arg=i
> > description starts on the same line
> > remainder indented the same
> > @@ -151,6 +163,8 @@ doc freeform
> > Another subsection
> > ==================
> > doc symbol=cmd
> > + section=Intro
> > +
> > arg=arg1
> > description starts on a new line,
> > indented
> > @@ -198,6 +212,8 @@ Note::
> > section=Since
> > 2.10
> > doc symbol=cmd-boxed
> > + section=Intro
> > +
> > section=Plain
> > If you're bored enough to read this, go see a video of boxed cats
> > feature=cmd-feat1
> > @@ -211,5 +227,7 @@ another feature
> >
> > <- ... has no title ...
> > doc symbol=EVT_BOXED
> > + section=Intro
> > +
> > feature=feat3
> > a feature
> > diff --git a/tests/qapi-schema/doc-missing-colon.err b/tests/qapi-schema/doc-missing-colon.err
> > index cbcea007153..bd5862b30f3 100644
> > --- a/tests/qapi-schema/doc-missing-colon.err
> > +++ b/tests/qapi-schema/doc-missing-colon.err
> > @@ -1 +1 @@
> > -doc-missing-colon.json:4:1: line should end with ':'
> > +doc-missing-colon.json:4:1: @name must end with ':'
>
> Let's not change this error message in this patch. For what it's worth,
> PATCH 13 changes it right back.
That one is an RFC! This patch was my original proposal and 13 is
"Here's the what-if you asked for."
I will definitely merge the PATCH 13 changes to their proper positions
if we conclude that's the direction we are going in, but that was not
clear to me when I sent this series. I wanted to highlight to you that
making the newline "optional" was actually proactive code changes and
not that the reverse was true; i.e. we already forbid the @name: line
from having anything else present on it.
So before getting to your review of later patches, it still isn't
clear to me if you want patch 13 or not, so I can't squash things for
consistency yet as we haven't made a choice.
--js
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v2 07/13] qapi/docs: add "Intro" section parsing
2026-05-04 18:44 ` John Snow
@ 2026-05-05 13:30 ` Markus Armbruster
0 siblings, 0 replies; 29+ messages in thread
From: Markus Armbruster @ 2026-05-05 13:30 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
John Snow <jsnow@redhat.com> writes:
> On Mon, May 4, 2026 at 7:57 AM Markus Armbruster <armbru@redhat.com> wrote:
>>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > Add parsing for explicit Intro section syntax.
>> >
>> > A side effect of this patch is that we will always create an empty Intro
>> > section, similar to how we used to have an empty Plain section. The
>> > tests are adjusted accordingly, rendered document output does not change
>> > at all.
>> >
>> > Signed-off-by: John Snow <jsnow@redhat.com>
>> > ---
>> > docs/devel/qapi-code-gen.rst | 17 +++++----
>> > scripts/qapi/parser.py | 47 ++++++++++++++++++-------
>> > tests/qapi-schema/doc-good.out | 18 ++++++++++
>> > tests/qapi-schema/doc-missing-colon.err | 2 +-
>> > 4 files changed, 64 insertions(+), 20 deletions(-)
>> >
>> > diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst
>> > index 3a632b4a648..a8175934d52 100644
>> > --- a/docs/devel/qapi-code-gen.rst
>> > +++ b/docs/devel/qapi-code-gen.rst
>> > @@ -862,7 +862,7 @@ documentation comment.
>> > If the documentation comment starts like ::
>> >
>> > ##
>> > - # @SYMBOL:
>> > + # @SYMBOL: [...]
>> >
>> > it documents the definition of SYMBOL, else it's free-form
>> > documentation.
>> > @@ -984,11 +984,11 @@ definition it documents.
>> > When documentation is required (see pragma_ 'doc-required'), every
>> > definition must have documentation.
>> >
>> > -Definition documentation starts with a line naming the definition,
>> > -followed by an optional overview, a description of each argument (for
>> > -commands and events), member (for structs and unions), branch (for
>> > -alternates), or value (for enums), a description of each feature (if
>> > -any), and finally optional tagged sections.
>> > +Definition documentation starts with a description naming the definition
>> > +with an optional overview, a description of each argument (for commands
>> > +and events), member (for structs and unions), branch (for alternates),
>> > +or value (for enums), a description of each feature (if any), and
>> > +finally optional tagged sections.
>> >
>> > Descriptions start with '\@name:'. The description text must be
>> > indented like this::
>> > @@ -996,6 +996,11 @@ indented like this::
>> > # @name: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
>> > # do eiusmod tempor incididunt ut labore et dolore magna aliqua.
>> >
>> > +Definition descriptions are special: the optional introductory overview
>> > +describing the definition will not be inlined when referenced by other
>> > +definitions (such as when using 'base' to include members from another
>> > +definition), while other descriptions and tagged sections will be.
>> > +
>>
>> Isn't this premature? We're not inlining anything just yet.
>
> It is, but I felt compelled to justify it holistically instead of
> playing "dumb" with the documentation and pretending it didn't exist.
> Feel free to suggest better phrasing/rationale here...
I think we have two reasons for clearly delineated "intro":
1. The existing one: we need to know where to insert synthesized stuff.
Unclear when there is only a "plain" section. We abuse TODO to mark
the spot then. Your "qapi: enforce section ordering" series makes
this worse.
We synthesize missing member documentation, references to base and
branch types, and omitted "Returns". docs/devel/qapi-code-gen.rst
mostly fails to document this, but that's not this patch's fault.
2. The future one: we need to know what not to inline. This is actually
exactly the text up to the point where we would insert synthesized
stuff if we had any. So any solution for 1. will also solve 2.
I think 1. is sufficient rationale for this patch.
In the commit message, you can mention both if you like. Just be clear
that 2. is about the future.
If you want rationale in qapi-code-gen.rst: best to stick to 1 there to
avoid confusion.
>> > .. FIXME The parser accepts these things in almost any order.
>> >
>> > .. FIXME union branches should be described, too.
>> > diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
>> > index 6612f471bb8..c23fd26aaa7 100644
>> > --- a/scripts/qapi/parser.py
>> > +++ b/scripts/qapi/parser.py
>> > @@ -24,6 +24,7 @@
>> > Match,
>> > Optional,
>> > Set,
>> > + Tuple,
>> > Union,
>> > )
>> >
>> > @@ -524,6 +525,30 @@ def get_doc_paragraph(self, doc: 'QAPIDoc') -> Optional[str]:
>> > return line
>> > doc.append_line(line)
>> >
>> > + def _get_doc_intro(
>> > + self,
>> > + line: str,
>> > + info: QAPISourceInfo
>> > + ) -> Tuple['QAPIDoc', Optional[str]]:
>> > + match = self._match_at_name_colon(line)
>> > + if not match:
>> > + raise QAPIParseError(self, "@name must end with ':'")
>> > +
>> > + # Invalid names are not checked here, but the name
>> > + # provided *must* match the following definition,
>> > + # which *is* validated in expr.py.
>> > + symbol = match.group(1)
>> > + if not symbol:
>> > + raise QAPIParseError(self, "name required after '@'")
>> > + doc = QAPIDoc(info, symbol)
>> > +
>>
>> Drop the blank line. For what it's worth, you do it already in PATCH
>> 13.
>>
>> > + doc.ensure_untagged_section(info, QAPIDoc.Kind.INTRO)
>>
>> Hmm.
>>
>> .ensure_untagged_section() extends the current section if we have one
>> and it is of kind .INTRO, else creates a new one of kind .INTRO.
>>
>> But there is no current section here! Why not simply create one?
>
> Leftover. I originally wanted to create this conditionally, but ran
> into troubles doing so and gave up and made it unconditional, but
> still using the conditional constructor.
>
>>
>> > + text = line[match.end():]
>> > + if text:
>> > + doc.append_line(text)
>> > +
>> > + return doc, self.get_doc_indented(doc)
>> > +
>> > def get_doc(self) -> 'QAPIDoc':
>> > if self.val != '##':
>> > raise QAPIParseError(
>> > @@ -532,18 +557,9 @@ def get_doc(self) -> 'QAPIDoc':
>> > self.accept(False)
>> > line = self.get_doc_line()
>> > if line is not None and line.startswith('@'):
>> > +
>>
>> Drop the blank line, please.
>>
>> > # Definition documentation
>> > - if not line.endswith(':'):
>> > - raise QAPIParseError(self, "line should end with ':'")
>> > - # Invalid names are not checked here, but the name
>> > - # provided *must* match the following definition,
>> > - # which *is* validated in expr.py.
>> > - symbol = line[1:-1]
>> > - if not symbol:
>> > - raise QAPIParseError(self, "name required after '@'")
>> > - doc = QAPIDoc(info, symbol)
>> > - self.accept(False)
>> > - line = self.get_doc_line()
>> > + doc, line = self._get_doc_intro(line, info)
>> > no_more_args = False
>> >
>> > while line is not None:
>> > @@ -751,8 +767,13 @@ def end(self) -> None:
>> > raise QAPISemError(
>> > section.info, "text required after '%s:'" % section.kind)
>> >
>> > - def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
>> > - kind = QAPIDoc.Kind.PLAIN
>> > + def ensure_untagged_section(
>> > + self,
>> > + info: QAPISourceInfo,
>> > + kind: Optional['QAPIDoc.Kind'] = None,
>>
>> Why not = QAPIDoc.Kind.PLAIN here, so ...
>>
>> > + ) -> None:
>> > + if kind is None:
>> > + kind = QAPIDoc.Kind.PLAIN
>>
>> ... you don't need this?
>
> Because at the time this function definition is parsed, that
> definition does not exist yet :)
Are you telling me that kind = QAPIDoc.Kind.Plain works as assignment in
the function body, but not as default in the argument list?
Aisde: I'm quite unhappy about the entanglement between QAPISchemaParser
and QAPIDoc. The latter should be in its own .py.
>
>>
>> >
>> > if self.all_sections and self.all_sections[-1].kind == kind:
>> > # extend current section
>>
>> Hmm.
>>
>> QAPIDoc.ensure_untagged_section() extends the current section if we have
>> one and it's "untagged", else creates a new "untagged" section.
>>
>> In the beginning, there was just one kind of "untagged" section. Its
>> encoding eventually became "Section.kind is QAPIDoc.Kind.PLAIN". So
>> what the function does was clear enough.
>>
>> This patch adds a second kind: QAPIDoc.Kind.INTRO.
>> .ensure_untagged_section() now takes a kind argument. It ensures *that*
>> kind of section.
>>
>> I think the function name becomes misleading. It can now ensure *any*
>> kind of section, not just "untagged".
>>
>> Rename to ensure_section() and make the @kind argument mandatory?
>
> Sure. I agree "untagged" has kind of long since lost its meaning, and
> I'm kind of mumbly-brained about what these terms mean anyway, so I
> was somewhat hesitant to rename it and wanted to see if I could get
> away with not sweeping that corner of the room.
>
> No such luck. Rats.
No rest for the wicked! ;)
>> > diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
>> > index 6fcc8175cfe..bc89853765f 100644
>> > --- a/tests/qapi-schema/doc-good.out
>> > +++ b/tests/qapi-schema/doc-good.out
>> > @@ -106,6 +106,8 @@ Examples:
>> > - *verbatim*
>> > - {braces}
>> > doc symbol=Enum
>> > + section=Intro
>> > +
>> > arg=one
>> > The _one_ {and only}, description on the same line
>> > arg=two
>> > @@ -117,10 +119,14 @@ a member feature
>> > section=Plain
>> > @two is undocumented
>> > doc symbol=Base
>> > + section=Intro
>> > +
>> > arg=base1
>> > description starts on a new line,
>> > minimally indented
>> > doc symbol=Variant1
>> > + section=Intro
>> > +
>> > section=Plain
>> > A paragraph
>> >
>> > @@ -134,10 +140,16 @@ a feature
>> > feature=member-feat
>> > a member feature
>> > doc symbol=Variant2
>> > + section=Intro
>> > +
>> > doc symbol=Object
>> > + section=Intro
>> > +
>> > feature=union-feat1
>> > a feature
>> > doc symbol=Alternate
>> > + section=Intro
>> > +
>> > arg=i
>> > description starts on the same line
>> > remainder indented the same
>> > @@ -151,6 +163,8 @@ doc freeform
>> > Another subsection
>> > ==================
>> > doc symbol=cmd
>> > + section=Intro
>> > +
>> > arg=arg1
>> > description starts on a new line,
>> > indented
>> > @@ -198,6 +212,8 @@ Note::
>> > section=Since
>> > 2.10
>> > doc symbol=cmd-boxed
>> > + section=Intro
>> > +
>> > section=Plain
>> > If you're bored enough to read this, go see a video of boxed cats
>> > feature=cmd-feat1
>> > @@ -211,5 +227,7 @@ another feature
>> >
>> > <- ... has no title ...
>> > doc symbol=EVT_BOXED
>> > + section=Intro
>> > +
>> > feature=feat3
>> > a feature
>> > diff --git a/tests/qapi-schema/doc-missing-colon.err b/tests/qapi-schema/doc-missing-colon.err
>> > index cbcea007153..bd5862b30f3 100644
>> > --- a/tests/qapi-schema/doc-missing-colon.err
>> > +++ b/tests/qapi-schema/doc-missing-colon.err
>> > @@ -1 +1 @@
>> > -doc-missing-colon.json:4:1: line should end with ':'
>> > +doc-missing-colon.json:4:1: @name must end with ':'
>>
>> Let's not change this error message in this patch. For what it's worth,
>> PATCH 13 changes it right back.
>
> That one is an RFC! This patch was my original proposal and 13 is
> "Here's the what-if you asked for."
>
> I will definitely merge the PATCH 13 changes to their proper positions
> if we conclude that's the direction we are going in, but that was not
> clear to me when I sent this series. I wanted to highlight to you that
> making the newline "optional" was actually proactive code changes and
> not that the reverse was true; i.e. we already forbid the @name: line
> from having anything else present on it.
>
> So before getting to your review of later patches, it still isn't
> clear to me if you want patch 13 or not, so I can't squash things for
> consistency yet as we haven't made a choice.
Understood.
You offer two styles for definition descriptions. Let me show them both
together with a member description:
##
# @name: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
# do eiusmod tempor incididunt ut labore et dolore magna aliqua.
#
# @member1: Ut enim ad minim veniam, quis nostrud exercitation ullamco
# laboris nisi ut aliquip ex ea commodo consequat.
or
##
# @name:
# Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
# do eiusmod tempor incididunt ut labore et dolore magna aliqua.
#
# @member1: Ut enim ad minim veniam, quis nostrud exercitation ullamco
# laboris nisi ut aliquip ex ea commodo consequat.
The latter makes the defined name (here: "name") stand out a bit more.
It also makes the definition description (@name: ...) differ from member
descriptions (@member: ...). This could be viewed as a feature or as a
pointless complication.
I lean towards feature. The lack of visual distinction between
definition description and member descriptions bothers me more in the
first style than in the second style.
I think this is something we should discuss in a 1:1.
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v2 08/13] qapi/docs: Add rendering for INTRO sections
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (6 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 07/13] qapi/docs: add "Intro" section parsing John Snow
@ 2026-04-29 19:26 ` John Snow
2026-05-04 12:05 ` Markus Armbruster
2026-04-29 19:26 ` [PATCH v2 09/13] qapi: convert intro sections for accelerator.json John Snow
` (4 subsequent siblings)
12 siblings, 1 reply; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
The only real difference here is that we need to dedent all but the
first line so that it renders correctly. We don't need to do this for
members and features because they are always rendered as part of a field
list directive which expects indented lines - Undecorated plaintext
doesn't, so we chop the indent off.
This does not reflow the text or mess with the source info in any way,
so "blame" for error messages should be unchanged.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 70ab9cdc214..6b8e4ecd76a 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -35,6 +35,7 @@
from pathlib import Path
import re
import sys
+import textwrap
from typing import TYPE_CHECKING
from docutils import nodes
@@ -150,8 +151,14 @@ def add_lines(
self,
content: str,
info: QAPISourceInfo,
+ dedent: bool = False,
) -> None:
lines = content.splitlines(True)
+
+ if dedent:
+ txt = "".join(lines[1:])
+ lines[1:] = textwrap.dedent(txt).splitlines(True)
+
for i, line in enumerate(lines):
self.add_line_raw(line, info.fname, info.line + i)
@@ -223,13 +230,14 @@ def reformat_arobase(text: str) -> str:
# Transmogrification helpers
- def visit_paragraph(self, section: QAPIDoc.Section) -> None:
+ def visit_text(self, section: QAPIDoc.Section) -> None:
# Squelch empty paragraphs.
if not section.text:
return
+ dedent = bool(section.kind == QAPIDoc.Kind.INTRO)
self.ensure_blank_line()
- self.add_lines(section.text, section.info)
+ self.add_lines(section.text, section.info, dedent)
self.ensure_blank_line()
def visit_member(self, section: QAPIDoc.ArgSection) -> None:
@@ -367,7 +375,7 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
section.text = self.reformat_arobase(section.text)
if section.kind.name in ("PLAIN", "INTRO"):
- self.visit_paragraph(section)
+ self.visit_text(section)
elif section.kind == QAPIDoc.Kind.MEMBER:
assert isinstance(section, QAPIDoc.ArgSection)
if section.name == "q_dummy":
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v2 08/13] qapi/docs: Add rendering for INTRO sections
2026-04-29 19:26 ` [PATCH v2 08/13] qapi/docs: Add rendering for INTRO sections John Snow
@ 2026-05-04 12:05 ` Markus Armbruster
2026-05-12 21:13 ` John Snow
0 siblings, 1 reply; 29+ messages in thread
From: Markus Armbruster @ 2026-05-04 12:05 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
John Snow <jsnow@redhat.com> writes:
> The only real difference here is that we need to dedent all but the
> first line so that it renders correctly. We don't need to do this for
> members and features because they are always rendered as part of a field
> list directive which expects indented lines - Undecorated plaintext
> doesn't, so we chop the indent off.
>
> This does not reflow the text or mess with the source info in any way,
> so "blame" for error messages should be unchanged.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 14 +++++++++++---
> 1 file changed, 11 insertions(+), 3 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 70ab9cdc214..6b8e4ecd76a 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -35,6 +35,7 @@
> from pathlib import Path
> import re
> import sys
> +import textwrap
> from typing import TYPE_CHECKING
>
> from docutils import nodes
> @@ -150,8 +151,14 @@ def add_lines(
> self,
> content: str,
> info: QAPISourceInfo,
> + dedent: bool = False,
> ) -> None:
> lines = content.splitlines(True)
> +
> + if dedent:
> + txt = "".join(lines[1:])
> + lines[1:] = textwrap.dedent(txt).splitlines(True)
> +
Sure you want to wrap and not just remove indentation?
> for i, line in enumerate(lines):
> self.add_line_raw(line, info.fname, info.line + i)
>
> @@ -223,13 +230,14 @@ def reformat_arobase(text: str) -> str:
>
> # Transmogrification helpers
>
> - def visit_paragraph(self, section: QAPIDoc.Section) -> None:
> + def visit_text(self, section: QAPIDoc.Section) -> None:
This is what gets used for sections of kind PLAIN and INTRO. Can we
come up with a better term for these than "text"? Your turf, so I
*will* take no for an answer.
> # Squelch empty paragraphs.
> if not section.text:
> return
>
> + dedent = bool(section.kind == QAPIDoc.Kind.INTRO)
> self.ensure_blank_line()
> - self.add_lines(section.text, section.info)
> + self.add_lines(section.text, section.info, dedent)
> self.ensure_blank_line()
>
> def visit_member(self, section: QAPIDoc.ArgSection) -> None:
> @@ -367,7 +375,7 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
> section.text = self.reformat_arobase(section.text)
>
> if section.kind.name in ("PLAIN", "INTRO"):
> - self.visit_paragraph(section)
> + self.visit_text(section)
> elif section.kind == QAPIDoc.Kind.MEMBER:
> assert isinstance(section, QAPIDoc.ArgSection)
> if section.name == "q_dummy":
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v2 08/13] qapi/docs: Add rendering for INTRO sections
2026-05-04 12:05 ` Markus Armbruster
@ 2026-05-12 21:13 ` John Snow
2026-05-13 6:25 ` Markus Armbruster
0 siblings, 1 reply; 29+ messages in thread
From: John Snow @ 2026-05-12 21:13 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
On Mon, May 4, 2026 at 8:05 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> John Snow <jsnow@redhat.com> writes:
>
> > The only real difference here is that we need to dedent all but the
> > first line so that it renders correctly. We don't need to do this for
> > members and features because they are always rendered as part of a field
> > list directive which expects indented lines - Undecorated plaintext
> > doesn't, so we chop the indent off.
> >
> > This does not reflow the text or mess with the source info in any way,
> > so "blame" for error messages should be unchanged.
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > docs/sphinx/qapidoc.py | 14 +++++++++++---
> > 1 file changed, 11 insertions(+), 3 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index 70ab9cdc214..6b8e4ecd76a 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -35,6 +35,7 @@
> > from pathlib import Path
> > import re
> > import sys
> > +import textwrap
> > from typing import TYPE_CHECKING
> >
> > from docutils import nodes
> > @@ -150,8 +151,14 @@ def add_lines(
> > self,
> > content: str,
> > info: QAPISourceInfo,
> > + dedent: bool = False,
> > ) -> None:
> > lines = content.splitlines(True)
> > +
> > + if dedent:
> > + txt = "".join(lines[1:])
> > + lines[1:] = textwrap.dedent(txt).splitlines(True)
> > +
>
> Sure you want to wrap and not just remove indentation?
"textwrap" is the module, "dedent" is the function. This really is
just removing indentation. It just happens to be bundled with the
"textwrap" stdlib module.
>
> > for i, line in enumerate(lines):
> > self.add_line_raw(line, info.fname, info.line + i)
> >
> > @@ -223,13 +230,14 @@ def reformat_arobase(text: str) -> str:
> >
> > # Transmogrification helpers
> >
> > - def visit_paragraph(self, section: QAPIDoc.Section) -> None:
> > + def visit_text(self, section: QAPIDoc.Section) -> None:
>
> This is what gets used for sections of kind PLAIN and INTRO. Can we
> come up with a better term for these than "text"? Your turf, so I
> *will* take no for an answer.
"patches welcome" - I was just avoiding the use of "paragraph" for
something that might be multiple paragraphs. Once the literal "PLAIN"
section is removed (in favor of semantic successors INTRO and
DETAILS), we can use "visit_plaintext" to refer to the general
category, but currently it's semantically overloaded.
How about I just do "visit_plaintext" and we suffer with the slight
ambiguity for the interim? not married to "text", "plaintext" or
anything else, just don't have better ideas and naming is one of the
two hard problems in CS.
--js
>
> > # Squelch empty paragraphs.
> > if not section.text:
> > return
> >
> > + dedent = bool(section.kind == QAPIDoc.Kind.INTRO)
> > self.ensure_blank_line()
> > - self.add_lines(section.text, section.info)
> > + self.add_lines(section.text, section.info, dedent)
> > self.ensure_blank_line()
> >
> > def visit_member(self, section: QAPIDoc.ArgSection) -> None:
> > @@ -367,7 +375,7 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
> > section.text = self.reformat_arobase(section.text)
> >
> > if section.kind.name in ("PLAIN", "INTRO"):
> > - self.visit_paragraph(section)
> > + self.visit_text(section)
> > elif section.kind == QAPIDoc.Kind.MEMBER:
> > assert isinstance(section, QAPIDoc.ArgSection)
> > if section.name == "q_dummy":
>
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v2 08/13] qapi/docs: Add rendering for INTRO sections
2026-05-12 21:13 ` John Snow
@ 2026-05-13 6:25 ` Markus Armbruster
0 siblings, 0 replies; 29+ messages in thread
From: Markus Armbruster @ 2026-05-13 6:25 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, Paolo Bonzini, Mauro Carvalho Chehab,
Michael S. Tsirkin, Peter Maydell, Pierrick Bouvier,
Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé
John Snow <jsnow@redhat.com> writes:
> On Mon, May 4, 2026 at 8:05 AM Markus Armbruster <armbru@redhat.com> wrote:
>>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > The only real difference here is that we need to dedent all but the
>> > first line so that it renders correctly. We don't need to do this for
>> > members and features because they are always rendered as part of a field
>> > list directive which expects indented lines - Undecorated plaintext
>> > doesn't, so we chop the indent off.
>> >
>> > This does not reflow the text or mess with the source info in any way,
>> > so "blame" for error messages should be unchanged.
>> >
>> > Signed-off-by: John Snow <jsnow@redhat.com>
>> > ---
>> > docs/sphinx/qapidoc.py | 14 +++++++++++---
>> > 1 file changed, 11 insertions(+), 3 deletions(-)
>> >
>> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
>> > index 70ab9cdc214..6b8e4ecd76a 100644
>> > --- a/docs/sphinx/qapidoc.py
>> > +++ b/docs/sphinx/qapidoc.py
>> > @@ -35,6 +35,7 @@
>> > from pathlib import Path
>> > import re
>> > import sys
>> > +import textwrap
>> > from typing import TYPE_CHECKING
>> >
>> > from docutils import nodes
>> > @@ -150,8 +151,14 @@ def add_lines(
>> > self,
>> > content: str,
>> > info: QAPISourceInfo,
>> > + dedent: bool = False,
>> > ) -> None:
>> > lines = content.splitlines(True)
>> > +
>> > + if dedent:
>> > + txt = "".join(lines[1:])
>> > + lines[1:] = textwrap.dedent(txt).splitlines(True)
>> > +
>>
>> Sure you want to wrap and not just remove indentation?
>
> "textwrap" is the module, "dedent" is the function. This really is
> just removing indentation. It just happens to be bundled with the
> "textwrap" stdlib module.
Basic reading comprehension fail, my apologies.
>> > for i, line in enumerate(lines):
>> > self.add_line_raw(line, info.fname, info.line + i)
>> >
>> > @@ -223,13 +230,14 @@ def reformat_arobase(text: str) -> str:
>> >
>> > # Transmogrification helpers
>> >
>> > - def visit_paragraph(self, section: QAPIDoc.Section) -> None:
>> > + def visit_text(self, section: QAPIDoc.Section) -> None:
>>
>> This is what gets used for sections of kind PLAIN and INTRO. Can we
>> come up with a better term for these than "text"? Your turf, so I
>> *will* take no for an answer.
>
> "patches welcome" - I was just avoiding the use of "paragraph" for
> something that might be multiple paragraphs.
Could use "paragraphs".
> Once the literal "PLAIN"
> section is removed (in favor of semantic successors INTRO and
> DETAILS), we can use "visit_plaintext" to refer to the general
> category, but currently it's semantically overloaded.
>
> How about I just do "visit_plaintext" and we suffer with the slight
> ambiguity for the interim? not married to "text", "plaintext" or
> anything else, just don't have better ideas and naming is one of the
> two hard problems in CS.
The temporary ambiguity feels tolerable.
"plaintext" is probably clearer than "paragraphs" once it's no longer
ambiguous.
Thanks!
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v2 09/13] qapi: convert intro sections for accelerator.json
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (7 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 08/13] qapi/docs: Add rendering for INTRO sections John Snow
@ 2026-04-29 19:26 ` John Snow
2026-04-29 19:26 ` [PATCH v2 10/13] qapi: convert intro sections for acpi-hest.json John Snow
` (3 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
Signed-off-by: John Snow <jsnow@redhat.com>
---
qapi/accelerator.json | 24 ++++++------------------
1 file changed, 6 insertions(+), 18 deletions(-)
diff --git a/qapi/accelerator.json b/qapi/accelerator.json
index 0cf5e0f9d94..71047538ee1 100644
--- a/qapi/accelerator.json
+++ b/qapi/accelerator.json
@@ -12,9 +12,7 @@
{ 'include': 'common.json' }
##
-# @KvmInfo:
-#
-# Information about support for KVM acceleration
+# @KvmInfo: Information about support for KVM acceleration
#
# @enabled: true if KVM acceleration is active
#
@@ -25,9 +23,7 @@
{ 'struct': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} }
##
-# @query-kvm:
-#
-# Return information about KVM acceleration
+# @query-kvm: Return information about KVM acceleration
#
# Since: 0.14
#
@@ -39,9 +35,7 @@
{ 'command': 'query-kvm', 'returns': 'KvmInfo' }
##
-# @x-accel-stats:
-#
-# Query accelerator statistics
+# @x-accel-stats: Query accelerator statistics
#
# Features:
#
@@ -56,9 +50,7 @@
'features': [ 'unstable' ] }
##
-# @Accelerator:
-#
-# Information about support for MSHV acceleration
+# @Accelerator: Information about support for MSHV acceleration
#
# @hvf: Apple Hypervisor.framework
#
@@ -81,9 +73,7 @@
{ 'enum': 'Accelerator', 'data': ['hvf', 'kvm', 'mshv', 'nvmm', 'qtest', 'tcg', 'whpx', 'xen'] }
##
-# @AcceleratorInfo:
-#
-# Information about support for various accelerators
+# @AcceleratorInfo: Information about support for various accelerators
#
# @enabled: the accelerator that is in use
#
@@ -95,9 +85,7 @@
{ 'struct': 'AcceleratorInfo', 'data': {'enabled': 'Accelerator', 'present': ['Accelerator']} }
##
-# @query-accelerators:
-#
-# Return information about accelerators
+# @query-accelerators: Return information about accelerators
#
# Returns: @AcceleratorInfo
#
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v2 10/13] qapi: convert intro sections for acpi-hest.json
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (8 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 09/13] qapi: convert intro sections for accelerator.json John Snow
@ 2026-04-29 19:26 ` John Snow
2026-04-29 19:26 ` [PATCH v2 11/13] qapi: convert intro sections for acpi.json John Snow
` (2 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
Signed-off-by: John Snow <jsnow@redhat.com>
---
qapi/acpi-hest.json | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/qapi/acpi-hest.json b/qapi/acpi-hest.json
index a01f1dee095..6a48e80082e 100644
--- a/qapi/acpi-hest.json
+++ b/qapi/acpi-hest.json
@@ -12,9 +12,8 @@
##
##
-# @inject-ghes-v2-error:
-#
-# Inject an error with additional ACPI 6.1 GHESv2 error information
+# @inject-ghes-v2-error: Inject an error with additional ACPI 6.1
+# GHESv2 error information
#
# @cper: contains a base64 encoded string with raw data for a single
# CPER record with Generic Error Status Block, Generic Error Data
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v2 11/13] qapi: convert intro sections for acpi.json
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (9 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 10/13] qapi: convert intro sections for acpi-hest.json John Snow
@ 2026-04-29 19:26 ` John Snow
2026-04-29 19:26 ` [PATCH v2 12/13] qapi: convert intro sections for audio.json John Snow
2026-04-29 19:26 ` [PATCH v2 13/13] rfc: intro starts on next line John Snow
12 siblings, 0 replies; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
Signed-off-by: John Snow <jsnow@redhat.com>
---
qapi/acpi.json | 23 ++++++++---------------
1 file changed, 8 insertions(+), 15 deletions(-)
diff --git a/qapi/acpi.json b/qapi/acpi.json
index 906b3687a55..ea6ad2cd45f 100644
--- a/qapi/acpi.json
+++ b/qapi/acpi.json
@@ -12,9 +12,8 @@
##
##
-# @AcpiTableOptions:
-#
-# Specify an ACPI table on the command line to load.
+# @AcpiTableOptions: Specify an ACPI table on the
+# command line to load.
#
# At most one of @file and @data can be specified. The list of files
# specified by any one of them is loaded and concatenated in order.
@@ -80,11 +79,9 @@
{ 'enum': 'ACPISlotType', 'data': [ 'DIMM', 'CPU' ] }
##
-# @ACPIOSTInfo:
-#
-# OSPM Status Indication for a device. For description of possible
-# values of @source and @status fields see "_OST (OSPM Status
-# Indication)" chapter of ACPI5.0 spec.
+# @ACPIOSTInfo: OSPM Status Indication for a device. For description
+# of possible values of @source and @status fields see "_OST (OSPM
+# Status Indication)" chapter of ACPI5.0 spec.
#
# @device: device ID associated with slot
#
@@ -106,10 +103,8 @@
'status': 'int' } }
##
-# @query-acpi-ospm-status:
-#
-# Return a list of `ACPIOSTInfo` for devices that support status
-# reporting via ACPI _OST method.
+# @query-acpi-ospm-status: Return a list of `ACPIOSTInfo` for devices
+# that support status reporting via ACPI _OST method.
#
# Since: 2.1
#
@@ -125,9 +120,7 @@
{ 'command': 'query-acpi-ospm-status', 'returns': ['ACPIOSTInfo'] }
##
-# @ACPI_DEVICE_OST:
-#
-# Emitted when guest executes ACPI _OST method.
+# @ACPI_DEVICE_OST: Emitted when guest executes ACPI _OST method.
#
# @info: OSPM Status Indication
#
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v2 12/13] qapi: convert intro sections for audio.json
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (10 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 11/13] qapi: convert intro sections for acpi.json John Snow
@ 2026-04-29 19:26 ` John Snow
2026-04-29 19:26 ` [PATCH v2 13/13] rfc: intro starts on next line John Snow
12 siblings, 0 replies; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
Signed-off-by: John Snow <jsnow@redhat.com>
---
qapi/audio.json | 112 ++++++++++++++----------------------------------
1 file changed, 32 insertions(+), 80 deletions(-)
diff --git a/qapi/audio.json b/qapi/audio.json
index 2df87b97101..62d64eabf1c 100644
--- a/qapi/audio.json
+++ b/qapi/audio.json
@@ -13,10 +13,8 @@
##
##
-# @AudiodevPerDirectionOptions:
-#
-# General audio backend options that are used for both playback and
-# recording.
+# @AudiodevPerDirectionOptions: General audio backend options that are
+# used for both playback and recording.
#
# @mixing-engine: use QEMU's mixing engine to mix all streams inside
# QEMU and convert audio formats when not supported by the
@@ -52,9 +50,7 @@
'*buffer-length': 'uint32' } }
##
-# @AudiodevGenericOptions:
-#
-# Generic driver-specific options.
+# @AudiodevGenericOptions: Generic driver-specific options.
#
# @in: options of the capture stream
#
@@ -68,9 +64,7 @@
'*out': 'AudiodevPerDirectionOptions' } }
##
-# @AudiodevDBusOptions:
-#
-# Options of the D-Bus audio backend.
+# @AudiodevDBusOptions: Options of the D-Bus audio backend.
#
# @in: options of the capture stream
#
@@ -88,10 +82,8 @@
'*nsamples': 'uint32'} }
##
-# @AudiodevAlsaPerDirectionOptions:
-#
-# Options of the ALSA backend that are used for both playback and
-# recording.
+# @AudiodevAlsaPerDirectionOptions: Options of the ALSA backend that
+# are used for both playback and recording.
#
# @dev: the name of the ALSA device to use (default 'default')
#
@@ -110,9 +102,7 @@
'*try-poll': 'bool' } }
##
-# @AudiodevAlsaOptions:
-#
-# Options of the ALSA audio backend.
+# @AudiodevAlsaOptions: Options of the ALSA audio backend.
#
# @in: options of the capture stream
#
@@ -129,9 +119,7 @@
'*threshold': 'uint32' } }
##
-# @AudiodevSndioOptions:
-#
-# Options of the sndio audio backend.
+# @AudiodevSndioOptions: Options of the sndio audio backend.
#
# @in: options of the capture stream
#
@@ -151,10 +139,8 @@
'*latency': 'uint32'} }
##
-# @AudiodevCoreaudioPerDirectionOptions:
-#
-# Options of the Core Audio backend that are used for both playback
-# and recording.
+# @AudiodevCoreaudioPerDirectionOptions: Options of the Core Audio
+# backend that are used for both playback and recording.
#
# @buffer-count: number of buffers
#
@@ -166,9 +152,7 @@
'*buffer-count': 'uint32' } }
##
-# @AudiodevCoreaudioOptions:
-#
-# Options of the coreaudio audio backend.
+# @AudiodevCoreaudioOptions: Options of the coreaudio audio backend.
#
# @in: options of the capture stream
#
@@ -182,9 +166,7 @@
'*out': 'AudiodevCoreaudioPerDirectionOptions' } }
##
-# @AudiodevDsoundOptions:
-#
-# Options of the DirectSound audio backend.
+# @AudiodevDsoundOptions: Options of the DirectSound audio backend.
#
# @in: options of the capture stream
#
@@ -202,10 +184,8 @@
'*latency': 'uint32' } }
##
-# @AudiodevJackPerDirectionOptions:
-#
-# Options of the JACK backend that are used for both playback and
-# recording.
+# @AudiodevJackPerDirectionOptions: Options of the JACK backend that
+# are used for both playback and recording.
#
# @server-name: select from among several possible concurrent server
# instances (default: environment variable $JACK_DEFAULT_SERVER if
@@ -236,9 +216,7 @@
'*exact-name': 'bool' } }
##
-# @AudiodevJackOptions:
-#
-# Options of the JACK audio backend.
+# @AudiodevJackOptions: Options of the JACK audio backend.
#
# @in: options of the capture stream
#
@@ -252,10 +230,8 @@
'*out': 'AudiodevJackPerDirectionOptions' } }
##
-# @AudiodevOssPerDirectionOptions:
-#
-# Options of the OSS backend that are used for both playback and
-# recording.
+# @AudiodevOssPerDirectionOptions: Options of the OSS backend that are
+# used for both playback and recording.
#
# @dev: file name of the OSS device (default '/dev/dsp')
#
@@ -274,9 +250,7 @@
'*try-poll': 'bool' } }
##
-# @AudiodevOssOptions:
-#
-# Options of the OSS audio backend.
+# @AudiodevOssOptions: Options of the OSS audio backend.
#
# @in: options of the capture stream
#
@@ -304,10 +278,8 @@
'*dsp-policy': 'uint32' } }
##
-# @AudiodevPaPerDirectionOptions:
-#
-# Options of the Pulseaudio backend that are used for both playback
-# and recording.
+# @AudiodevPaPerDirectionOptions: Options of the Pulseaudio backend
+# that are used for both playback and recording.
#
# @name: name of the sink/source to use
#
@@ -329,9 +301,7 @@
'*latency': 'uint32' } }
##
-# @AudiodevPaOptions:
-#
-# Options of the PulseAudio audio backend.
+# @AudiodevPaOptions: Options of the PulseAudio audio backend.
#
# @in: options of the capture stream
#
@@ -348,10 +318,8 @@
'*server': 'str' } }
##
-# @AudiodevPipewirePerDirectionOptions:
-#
-# Options of the PipeWire backend that are used for both playback and
-# recording.
+# @AudiodevPipewirePerDirectionOptions: Options of the PipeWire
+# backend that are used for both playback and recording.
#
# @name: name of the sink/source to use
#
@@ -373,9 +341,7 @@
'*latency': 'uint32' } }
##
-# @AudiodevPipewireOptions:
-#
-# Options of the PipeWire audio backend.
+# @AudiodevPipewireOptions: Options of the PipeWire audio backend.
#
# @in: options of the capture stream
#
@@ -389,10 +355,8 @@
'*out': 'AudiodevPipewirePerDirectionOptions' } }
##
-# @AudiodevSdlPerDirectionOptions:
-#
-# Options of the SDL audio backend that are used for both playback and
-# recording.
+# @AudiodevSdlPerDirectionOptions: Options of the SDL audio backend
+# that are used for both playback and recording.
#
# @buffer-count: number of buffers (default 4)
#
@@ -404,9 +368,7 @@
'*buffer-count': 'uint32' } }
##
-# @AudiodevSdlOptions:
-#
-# Options of the SDL audio backend.
+# @AudiodevSdlOptions: Options of the SDL audio backend.
#
# @in: options of the recording stream
#
@@ -420,9 +382,7 @@
'*out': 'AudiodevSdlPerDirectionOptions' } }
##
-# @AudiodevWavOptions:
-#
-# Options of the wav audio backend.
+# @AudiodevWavOptions: Options of the wav audio backend.
#
# @in: options of the capture stream
#
@@ -439,9 +399,7 @@
'*path': 'str' } }
##
-# @AudioFormat:
-#
-# An enumeration of possible audio formats.
+# @AudioFormat: An enumeration of possible audio formats.
#
# @u8: unsigned 8 bit integer
#
@@ -463,9 +421,7 @@
'data': [ 'u8', 's8', 'u16', 's16', 'u32', 's32', 'f32' ] }
##
-# @AudiodevDriver:
-#
-# An enumeration of possible audio backend drivers.
+# @AudiodevDriver: An enumeration of possible audio backend drivers.
#
# @jack: JACK audio backend (since 5.1)
#
@@ -487,9 +443,7 @@
'wav' ] }
##
-# @Audiodev:
-#
-# Options of an audio backend.
+# @Audiodev: Options of an audio backend.
#
# @id: identifier of the backend
#
@@ -533,9 +487,7 @@
'wav': 'AudiodevWavOptions' } }
##
-# @query-audiodevs:
-#
-# Return information about audiodev configuration
+# @query-audiodevs: Return information about audiodev configuration
#
# Since: 8.0
##
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v2 13/13] rfc: intro starts on next line
2026-04-29 19:25 [PATCH v2 00/13] qapi: add formal "intro" section John Snow
` (11 preceding siblings ...)
2026-04-29 19:26 ` [PATCH v2 12/13] qapi: convert intro sections for audio.json John Snow
@ 2026-04-29 19:26 ` John Snow
12 siblings, 0 replies; 29+ messages in thread
From: John Snow @ 2026-04-29 19:26 UTC (permalink / raw)
To: qemu-devel
Cc: Michael Roth, Eric Blake, Marc-André Lureau,
Richard Henderson, John Snow, Paolo Bonzini,
Mauro Carvalho Chehab, Michael S. Tsirkin, Peter Maydell,
Pierrick Bouvier, Igor Mammedov, Gerd Hoffmann, Ani Sinha,
Philippe Mathieu-Daudé, Markus Armbruster
Revert some parser changes that had allowed the intro section to start
on the same line as the definition name. If we go this route, the
changes need to be back-squashed into earlier commits and the
qapi-code-gen.rst documentation needs to be rewritten and reworded a
little bit.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 5 +-
qapi/accelerator.json | 18 +++--
qapi/acpi-hest.json | 5 +-
qapi/acpi.json | 13 ++--
qapi/audio.json | 88 ++++++++++++++++---------
scripts/qapi/parser.py | 12 +---
tests/qapi-schema/doc-missing-colon.err | 2 +-
7 files changed, 86 insertions(+), 57 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 6b8e4ecd76a..985954164b0 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -156,8 +156,9 @@ def add_lines(
lines = content.splitlines(True)
if dedent:
- txt = "".join(lines[1:])
- lines[1:] = textwrap.dedent(txt).splitlines(True)
+ lines = textwrap.dedent(content).splitlines(True)
+ else:
+ lines = content.splitlines(True)
for i, line in enumerate(lines):
self.add_line_raw(line, info.fname, info.line + i)
diff --git a/qapi/accelerator.json b/qapi/accelerator.json
index 71047538ee1..05dbef51c5d 100644
--- a/qapi/accelerator.json
+++ b/qapi/accelerator.json
@@ -12,7 +12,8 @@
{ 'include': 'common.json' }
##
-# @KvmInfo: Information about support for KVM acceleration
+# @KvmInfo:
+# Information about support for KVM acceleration
#
# @enabled: true if KVM acceleration is active
#
@@ -23,7 +24,8 @@
{ 'struct': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} }
##
-# @query-kvm: Return information about KVM acceleration
+# @query-kvm:
+# Return information about KVM acceleration
#
# Since: 0.14
#
@@ -35,7 +37,8 @@
{ 'command': 'query-kvm', 'returns': 'KvmInfo' }
##
-# @x-accel-stats: Query accelerator statistics
+# @x-accel-stats:
+# Query accelerator statistics
#
# Features:
#
@@ -50,7 +53,8 @@
'features': [ 'unstable' ] }
##
-# @Accelerator: Information about support for MSHV acceleration
+# @Accelerator:
+# Information about support for MSHV acceleration
#
# @hvf: Apple Hypervisor.framework
#
@@ -73,7 +77,8 @@
{ 'enum': 'Accelerator', 'data': ['hvf', 'kvm', 'mshv', 'nvmm', 'qtest', 'tcg', 'whpx', 'xen'] }
##
-# @AcceleratorInfo: Information about support for various accelerators
+# @AcceleratorInfo:
+# Information about support for various accelerators
#
# @enabled: the accelerator that is in use
#
@@ -85,7 +90,8 @@
{ 'struct': 'AcceleratorInfo', 'data': {'enabled': 'Accelerator', 'present': ['Accelerator']} }
##
-# @query-accelerators: Return information about accelerators
+# @query-accelerators:
+# Return information about accelerators
#
# Returns: @AcceleratorInfo
#
diff --git a/qapi/acpi-hest.json b/qapi/acpi-hest.json
index 6a48e80082e..8db804df197 100644
--- a/qapi/acpi-hest.json
+++ b/qapi/acpi-hest.json
@@ -12,8 +12,9 @@
##
##
-# @inject-ghes-v2-error: Inject an error with additional ACPI 6.1
-# GHESv2 error information
+# @inject-ghes-v2-error:
+# Inject an error with additional ACPI 6.1 GHESv2 error
+# information
#
# @cper: contains a base64 encoded string with raw data for a single
# CPER record with Generic Error Status Block, Generic Error Data
diff --git a/qapi/acpi.json b/qapi/acpi.json
index ea6ad2cd45f..82e82d7cf89 100644
--- a/qapi/acpi.json
+++ b/qapi/acpi.json
@@ -12,8 +12,8 @@
##
##
-# @AcpiTableOptions: Specify an ACPI table on the
-# command line to load.
+# @AcpiTableOptions:
+# Specify an ACPI table on the command line to load.
#
# At most one of @file and @data can be specified. The list of files
# specified by any one of them is loaded and concatenated in order.
@@ -79,7 +79,8 @@
{ 'enum': 'ACPISlotType', 'data': [ 'DIMM', 'CPU' ] }
##
-# @ACPIOSTInfo: OSPM Status Indication for a device. For description
+# @ACPIOSTInfo:
+# OSPM Status Indication for a device. For description
# of possible values of @source and @status fields see "_OST (OSPM
# Status Indication)" chapter of ACPI5.0 spec.
#
@@ -103,7 +104,8 @@
'status': 'int' } }
##
-# @query-acpi-ospm-status: Return a list of `ACPIOSTInfo` for devices
+# @query-acpi-ospm-status:
+# Return a list of `ACPIOSTInfo` for devices
# that support status reporting via ACPI _OST method.
#
# Since: 2.1
@@ -120,7 +122,8 @@
{ 'command': 'query-acpi-ospm-status', 'returns': ['ACPIOSTInfo'] }
##
-# @ACPI_DEVICE_OST: Emitted when guest executes ACPI _OST method.
+# @ACPI_DEVICE_OST:
+# Emitted when guest executes ACPI _OST method.
#
# @info: OSPM Status Indication
#
diff --git a/qapi/audio.json b/qapi/audio.json
index 62d64eabf1c..72190f20993 100644
--- a/qapi/audio.json
+++ b/qapi/audio.json
@@ -13,8 +13,9 @@
##
##
-# @AudiodevPerDirectionOptions: General audio backend options that are
-# used for both playback and recording.
+# @AudiodevPerDirectionOptions:
+# General audio backend options that are used for both playback
+# and recording.
#
# @mixing-engine: use QEMU's mixing engine to mix all streams inside
# QEMU and convert audio formats when not supported by the
@@ -50,7 +51,8 @@
'*buffer-length': 'uint32' } }
##
-# @AudiodevGenericOptions: Generic driver-specific options.
+# @AudiodevGenericOptions:
+# Generic driver-specific options.
#
# @in: options of the capture stream
#
@@ -64,7 +66,8 @@
'*out': 'AudiodevPerDirectionOptions' } }
##
-# @AudiodevDBusOptions: Options of the D-Bus audio backend.
+# @AudiodevDBusOptions:
+# Options of the D-Bus audio backend.
#
# @in: options of the capture stream
#
@@ -82,8 +85,9 @@
'*nsamples': 'uint32'} }
##
-# @AudiodevAlsaPerDirectionOptions: Options of the ALSA backend that
-# are used for both playback and recording.
+# @AudiodevAlsaPerDirectionOptions:
+# Options of the ALSA backend that are used for both playback and
+# recording.
#
# @dev: the name of the ALSA device to use (default 'default')
#
@@ -102,7 +106,8 @@
'*try-poll': 'bool' } }
##
-# @AudiodevAlsaOptions: Options of the ALSA audio backend.
+# @AudiodevAlsaOptions:
+# Options of the ALSA audio backend.
#
# @in: options of the capture stream
#
@@ -119,7 +124,8 @@
'*threshold': 'uint32' } }
##
-# @AudiodevSndioOptions: Options of the sndio audio backend.
+# @AudiodevSndioOptions:
+# Options of the sndio audio backend.
#
# @in: options of the capture stream
#
@@ -139,8 +145,9 @@
'*latency': 'uint32'} }
##
-# @AudiodevCoreaudioPerDirectionOptions: Options of the Core Audio
-# backend that are used for both playback and recording.
+# @AudiodevCoreaudioPerDirectionOptions:
+# Options of the Core Audio backend that are used for both
+# playback and recording.
#
# @buffer-count: number of buffers
#
@@ -152,7 +159,8 @@
'*buffer-count': 'uint32' } }
##
-# @AudiodevCoreaudioOptions: Options of the coreaudio audio backend.
+# @AudiodevCoreaudioOptions:
+# Options of the coreaudio audio backend.
#
# @in: options of the capture stream
#
@@ -166,7 +174,8 @@
'*out': 'AudiodevCoreaudioPerDirectionOptions' } }
##
-# @AudiodevDsoundOptions: Options of the DirectSound audio backend.
+# @AudiodevDsoundOptions:
+# Options of the DirectSound audio backend.
#
# @in: options of the capture stream
#
@@ -184,8 +193,9 @@
'*latency': 'uint32' } }
##
-# @AudiodevJackPerDirectionOptions: Options of the JACK backend that
-# are used for both playback and recording.
+# @AudiodevJackPerDirectionOptions:
+# Options of the JACK backend that are used for both playback and
+# recording.
#
# @server-name: select from among several possible concurrent server
# instances (default: environment variable $JACK_DEFAULT_SERVER if
@@ -216,7 +226,8 @@
'*exact-name': 'bool' } }
##
-# @AudiodevJackOptions: Options of the JACK audio backend.
+# @AudiodevJackOptions:
+# Options of the JACK audio backend.
#
# @in: options of the capture stream
#
@@ -230,8 +241,9 @@
'*out': 'AudiodevJackPerDirectionOptions' } }
##
-# @AudiodevOssPerDirectionOptions: Options of the OSS backend that are
-# used for both playback and recording.
+# @AudiodevOssPerDirectionOptions:
+# Options of the OSS backend that are used for both playback and
+# recording.
#
# @dev: file name of the OSS device (default '/dev/dsp')
#
@@ -250,7 +262,8 @@
'*try-poll': 'bool' } }
##
-# @AudiodevOssOptions: Options of the OSS audio backend.
+# @AudiodevOssOptions:
+# Options of the OSS audio backend.
#
# @in: options of the capture stream
#
@@ -278,8 +291,9 @@
'*dsp-policy': 'uint32' } }
##
-# @AudiodevPaPerDirectionOptions: Options of the Pulseaudio backend
-# that are used for both playback and recording.
+# @AudiodevPaPerDirectionOptions:
+# Options of the Pulseaudio backend that are used for both
+# playback and recording.
#
# @name: name of the sink/source to use
#
@@ -301,7 +315,8 @@
'*latency': 'uint32' } }
##
-# @AudiodevPaOptions: Options of the PulseAudio audio backend.
+# @AudiodevPaOptions:
+# Options of the PulseAudio audio backend.
#
# @in: options of the capture stream
#
@@ -318,8 +333,9 @@
'*server': 'str' } }
##
-# @AudiodevPipewirePerDirectionOptions: Options of the PipeWire
-# backend that are used for both playback and recording.
+# @AudiodevPipewirePerDirectionOptions:
+# Options of the PipeWire backend that are used for both playback
+# and recording.
#
# @name: name of the sink/source to use
#
@@ -341,7 +357,8 @@
'*latency': 'uint32' } }
##
-# @AudiodevPipewireOptions: Options of the PipeWire audio backend.
+# @AudiodevPipewireOptions:
+# Options of the PipeWire audio backend.
#
# @in: options of the capture stream
#
@@ -355,8 +372,9 @@
'*out': 'AudiodevPipewirePerDirectionOptions' } }
##
-# @AudiodevSdlPerDirectionOptions: Options of the SDL audio backend
-# that are used for both playback and recording.
+# @AudiodevSdlPerDirectionOptions:
+# Options of the SDL audio backend that are used for both playback
+# and recording.
#
# @buffer-count: number of buffers (default 4)
#
@@ -368,7 +386,8 @@
'*buffer-count': 'uint32' } }
##
-# @AudiodevSdlOptions: Options of the SDL audio backend.
+# @AudiodevSdlOptions:
+# Options of the SDL audio backend.
#
# @in: options of the recording stream
#
@@ -382,7 +401,8 @@
'*out': 'AudiodevSdlPerDirectionOptions' } }
##
-# @AudiodevWavOptions: Options of the wav audio backend.
+# @AudiodevWavOptions:
+# Options of the wav audio backend.
#
# @in: options of the capture stream
#
@@ -399,7 +419,8 @@
'*path': 'str' } }
##
-# @AudioFormat: An enumeration of possible audio formats.
+# @AudioFormat:
+# An enumeration of possible audio formats.
#
# @u8: unsigned 8 bit integer
#
@@ -421,7 +442,8 @@
'data': [ 'u8', 's8', 'u16', 's16', 'u32', 's32', 'f32' ] }
##
-# @AudiodevDriver: An enumeration of possible audio backend drivers.
+# @AudiodevDriver:
+# An enumeration of possible audio backend drivers.
#
# @jack: JACK audio backend (since 5.1)
#
@@ -443,7 +465,8 @@
'wav' ] }
##
-# @Audiodev: Options of an audio backend.
+# @Audiodev:
+# Options of an audio backend.
#
# @id: identifier of the backend
#
@@ -487,7 +510,8 @@
'wav': 'AudiodevWavOptions' } }
##
-# @query-audiodevs: Return information about audiodev configuration
+# @query-audiodevs:
+# Return information about audiodev configuration
#
# Since: 8.0
##
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index c23fd26aaa7..8ff2a2c4c0b 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -530,23 +530,17 @@ def _get_doc_intro(
line: str,
info: QAPISourceInfo
) -> Tuple['QAPIDoc', Optional[str]]:
- match = self._match_at_name_colon(line)
- if not match:
- raise QAPIParseError(self, "@name must end with ':'")
+ if not line.endswith(':'):
+ raise QAPIParseError(self, "line should end with ':'")
# Invalid names are not checked here, but the name
# provided *must* match the following definition,
# which *is* validated in expr.py.
- symbol = match.group(1)
+ symbol = line[1:-1]
if not symbol:
raise QAPIParseError(self, "name required after '@'")
doc = QAPIDoc(info, symbol)
-
doc.ensure_untagged_section(info, QAPIDoc.Kind.INTRO)
- text = line[match.end():]
- if text:
- doc.append_line(text)
-
return doc, self.get_doc_indented(doc)
def get_doc(self) -> 'QAPIDoc':
diff --git a/tests/qapi-schema/doc-missing-colon.err b/tests/qapi-schema/doc-missing-colon.err
index bd5862b30f3..cbcea007153 100644
--- a/tests/qapi-schema/doc-missing-colon.err
+++ b/tests/qapi-schema/doc-missing-colon.err
@@ -1 +1 @@
-doc-missing-colon.json:4:1: @name must end with ':'
+doc-missing-colon.json:4:1: line should end with ':'
--
2.54.0
^ permalink raw reply related [flat|nested] 29+ messages in thread