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 <[email protected]> --- 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
