This patch changes the qapidoc parser to generate stub Return value documentation for any command that has a return value but does not have a "Returns:" doc section.
The stubs include just the type name, which will be rendered with a cross-reference link in the HTML output. The algorithm for where return sections are placed is as follows: 1. If we have arguments, return goes right after them. 2. Else if we have errors, return goes right before them. 3. Else if we have features, return goes right before them. 4. Else return goes right after the intro To facilitate this algorithm, a "TODO:" hack line is used to separate the intro from the remainder of the documentation block in cases where there are no other sections to separate the intro from e.g. examples and additional detail meant to appear below the key sections of interest. Signed-off-by: John Snow <js...@redhat.com> --- docs/sphinx/qapidoc.py | 14 ++++++++------ qapi/machine.json | 2 ++ scripts/qapi/parser.py | 35 +++++++++++++++++++++++++++++++++++ scripts/qapi/schema.py | 3 +++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 8011ac9efaf..77e28a65cfc 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -255,16 +255,18 @@ def visit_feature(self, section: QAPIDoc.ArgSection) -> None: def visit_returns(self, section: QAPIDoc.Section) -> None: assert isinstance(self.entity, QAPISchemaCommand) rtype = self.entity.ret_type - # q_empty can produce None, but we won't be documenting anything - # without an explicit return statement in the doc block, and we - # should not have any such explicit statements when there is no - # return value. + # return statements will not be present (and won't be + # autogenerated) for any command that doesn't return + # *something*, so rtype will always be defined here. assert rtype typ = self.format_type(rtype) assert typ - assert section.text - self.add_field("return", typ, section.text, section.info) + + if section.text: + self.add_field("return", typ, section.text, section.info) + else: + self.add_lines(f":return-nodesc: {typ}", section.info) def visit_errors(self, section: QAPIDoc.Section) -> None: # FIXME: the formatting for errors may be inconsistent and may diff --git a/qapi/machine.json b/qapi/machine.json index f712e7da6d6..e3e0505a4b4 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -1301,6 +1301,8 @@ # Return the amount of initially allocated and present hotpluggable # (if enabled) memory in bytes. # +# TODO: This line is a hack to separate the example from the body +# # .. qmp-example:: # # -> { "execute": "query-memory-size-summary" } diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 949d9e8bff7..aa8f1852c50 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -815,6 +815,41 @@ def connect_feature(self, feature: 'QAPISchemaFeature') -> None: % feature.name) self.features[feature.name].connect(feature) + def ensure_returns(self, info: QAPISourceInfo) -> None: + + def _insert_near_kind( + kind: QAPIDoc.Kind, + new_sect: QAPIDoc.Section, + after: bool = False, + ) -> bool: + for sect in filter(lambda sect: sect.kind == kind, reversed( + self.all_sections)): + idx = self.all_sections.index(sect) + (1 if after else 0) + self.all_sections.insert(idx, 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 ( + # 1. If arguments, right after those. + (QAPIDoc.Kind.MEMBER, True), + # 2. Elif errors, right *before* those. + (QAPIDoc.Kind.ERRORS, False), + # 3. Elif features, right *before* those. + (QAPIDoc.Kind.FEATURE, False), + )): + 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) + def check_expr(self, expr: QAPIExpression) -> None: if 'command' in expr: if self.returns and 'returns' not in expr: diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index cbe3b5aa91e..3abddea3525 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -1062,6 +1062,9 @@ def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None: if self.arg_type and self.arg_type.is_implicit(): self.arg_type.connect_doc(doc) + if self.ret_type and self.info: + doc.ensure_returns(self.info) + def visit(self, visitor: QAPISchemaVisitor) -> None: super().visit(visitor) visitor.visit_command( -- 2.50.0