Ugly hack, gets the job done. Likely many simplifications can be made as a result, but I didn't make any of them. There are some inconsistencies with human-readable vs ENUM_NAMES in error messages in this patch, but it appears to work anyway.
Consider this patch more of a rough idea and not anything approximating the kind of code you'd want to see from an Enterprise Linux Senior Software Engineer. Signed-off-by: John Snow <[email protected]> --- scripts/qapi/parser.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 9e8dfc67208..1a7856bf213 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -545,19 +545,25 @@ def get_doc(self) -> 'QAPIDoc': self.accept(False) line = self.get_doc_line() have_tagged = False - no_more_tags = False + last_section = QAPIDoc.Kind.INTRO def _tag_check(what: str) -> None: + nonlocal last_section + if what in ('TODO', 'Since'): return - if no_more_tags: + this_section = QAPIDoc.Kind.from_string(what) + if this_section.value < last_section.value: raise QAPIParseError( self, - f"'{what}' section cannot appear after plain " - "paragraphs that follow other tagged sections\n" - "Move this section up above the plain paragraph(s)." + f"'{what}' section cannot appear after " + f"'{last_section!s}' 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" ) + last_section = this_section while line is not None: # Blank lines @@ -571,7 +577,7 @@ def _tag_check(what: str) -> None: if doc.features: raise QAPIParseError( self, "duplicated 'Features:' line") - _tag_check("Features") + _tag_check("FEATURE") self.accept(False) line = self.get_doc_line() while line == '': @@ -589,7 +595,7 @@ def _tag_check(what: str) -> None: self, 'feature descriptions expected') have_tagged = True elif line == 'Details:': - _tag_check("Details") + _tag_check("DETAILS") self.accept(False) line = self.get_doc_line() while line == '': @@ -598,6 +604,7 @@ def _tag_check(what: str) -> None: have_tagged = True elif match := self._match_at_name_colon(line): # description + _tag_check("MEMBER") if have_tagged: raise QAPIParseError( self, @@ -654,13 +661,10 @@ def _tag_check(what: str) -> None: line = self.get_doc_indented(doc) have_tagged = True else: - # plain paragraph - if have_tagged: - no_more_tags = True - # Paragraphs before tagged sections are "intro" paragraphs. # Any appearing after are "detail" paragraphs. intro = not have_tagged + _tag_check("INTRO" if intro else "DETAILS") doc.ensure_untagged_section(self.info, intro) doc.append_line(line) line = self.get_doc_paragraph(doc) @@ -703,14 +707,17 @@ class QAPIDoc: """ class Kind(enum.Enum): + # 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 - DETAILS = 7 + SINCE = 7 @staticmethod def from_string(kind: str) -> 'QAPIDoc.Kind': -- 2.53.0
