John Snow <[email protected]> writes:
> Enforce the relative ordering of "Intro", "Members", "Features",
> "Errors" and "Returns" sections.
>
> Signed-off-by: John Snow <[email protected]>
> ---
> docs/devel/qapi-code-gen.rst | 19 +++++++++++------
> scripts/qapi/parser.py | 41 ++++++++++++++++++++++++++++++------
> 2 files changed, 48 insertions(+), 12 deletions(-)
>
> diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst
> index 3ac1c2dae18..29e9c5a3da6 100644
> --- a/docs/devel/qapi-code-gen.rst
> +++ b/docs/devel/qapi-code-gen.rst
> @@ -985,12 +985,19 @@ When documentation is required (see pragma_
> 'doc-required'), every
> definition must have documentation.
>
> Definition documentation starts with a description naming the
> -definition with an optional indented overview (the "intro"),
> -:ref:`descriptions <doc-descrip>` of each argument (for commands and
> -events), member (for structs and unions), branch (for alternates), or
> -value (for enums), :ref:`feature descriptions <doc-feat>` of each
> -feature (if any), and finally optional :ref:`tagged sections
> -<doc-tagged>`.
> +definition with an optional indented overview (the "intro").
> +
> +The introduction is followed by :ref:`descriptions <doc-descrip>` of
> +each argument (for commands and events), member (for structs and
> +unions), branch (for alternates), or value (for enums).
> +
> +:ref:`Tagged sections <doc-tagged>` follow in order: Returns (for
> +commands), Errors (for commands), and lastly :ref:`feature
> +descriptions <doc-feat>` if applicable.
*Some* of the tagged sections do.
Does this document still need the concept "tagged section"?
> +
> +Definition documentation concludes with rST-formatted detail
> +paragraphs (including examples, notes, &c), and finally an optional
> +"Since" tagged section at the very end.
>
> .. _doc-descrip:
>
> diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
> index fe284a3f866..94a60a23482 100644
> --- a/scripts/qapi/parser.py
> +++ b/scripts/qapi/parser.py
> @@ -545,6 +545,28 @@ def get_doc(self) -> 'QAPIDoc':
> doc.all_sections.append(QAPIDoc.Section(info,
> QAPIDoc.Kind.INTRO))
> line = self.get_doc_indented(doc)
> no_more_args = False
> + last_ordered_section = QAPIDoc.Kind.INTRO
> +
> + def _tag_check(this: Union['QAPIDoc.Kind', str]) -> None:
> + nonlocal last_ordered_section
> + if isinstance(this, str):
> + this = QAPIDoc.Kind.from_string(this)
Simpler, I think: require a .Kind argument, and do the conversion from
string in the one caller that needs it.
> +
> + if this in (QAPIDoc.Kind.TODO, QAPIDoc.Kind.SINCE,
> + QAPIDoc.Kind.DETAILS):
> + return
> +
> + if this.value < last_ordered_section.value:
> + raise QAPIParseError(
> + self,
> + f"'{this}' section cannot appear after "
> + f"'{last_ordered_section}' section, please re-order "
> + "the sections and adjust phrasing as necessary to "
> + "ensure consistent documentation flow between the "
> + "source code and the rendered HTML manual"
> + )
That's a loooong error message. It actually consists of the error
proper and advice on how to correct it. Please use
"<error>\n<advice>"
like we do in expr.py's check_keys(), parser.py's
._validate_doc_line_format(), and schema.py's ._def_definition(). Yes,
the embedded newline is a bit of a hack.
> +
> + last_ordered_section = this
>
> while line is not None:
> # Blank lines
> @@ -558,6 +580,7 @@ def get_doc(self) -> 'QAPIDoc':
> if doc.has_features:
> raise QAPIParseError(
> self, "duplicated 'Features:' line")
> + _tag_check(QAPIDoc.Kind.FEATURE)
> self.accept(False)
> line = self.get_doc_line()
> while line == '':
> @@ -581,6 +604,7 @@ def get_doc(self) -> 'QAPIDoc':
> self,
> "description of '@%s:' follows a section"
> % match.group(1))
> + _tag_check(QAPIDoc.Kind.MEMBER)
> while (line is not None
> and (match := self._match_at_name_colon(line))):
> doc.new_argument(self.info, match.group(1))
> @@ -621,6 +645,7 @@ def get_doc(self) -> 'QAPIDoc':
> )
> raise QAPIParseError(self, emsg)
>
> + _tag_check(match.group(1))
This is the caller that needs it.
> doc.new_tagged_section(
> self.info,
> QAPIDoc.Kind.from_string(match.group(1))
> @@ -632,6 +657,7 @@ def get_doc(self) -> 'QAPIDoc':
> no_more_args = True
> else:
> # plain paragraph(s)
> + _tag_check(QAPIDoc.Kind.DETAILS)
> doc.ensure_untagged_section(self.info)
> doc.append_line(line)
> line = self.get_doc_paragraph(doc)
> @@ -674,14 +700,17 @@ class QAPIDoc:
> """
>
> class Kind(enum.Enum):
> - DETAILS = 0
> + # The order here is the order in which sections must appear in
> + # source code; with the exception of 'TODO' which may appear
> + # anywhere but is treated as a comment.
> + INTRO = 0
> MEMBER = 1
> - FEATURE = 2
> - RETURNS = 3
> - ERRORS = 4
> - SINCE = 5
> + RETURNS = 2
> + ERRORS = 3
> + FEATURE = 4
> + DETAILS = 5
> TODO = 6
> - INTRO = 7
> + SINCE = 7
>
> @staticmethod
> def from_string(kind: str) -> 'QAPIDoc.Kind':
Missing: negative test for the new error.