mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-07-29 13:23:54 -06:00
docs/qapidoc: Add "the members of" pointers
Add "the members of ..." pointers to Members and Arguments lists where appropriate, with clickable cross-references - so it's a slight improvement over the old system :) This patch is meant to be a temporary solution until we can review and merge the inliner. The implementation of this patch is a little bit of a hack: Sphinx is not designed to allow you to mix fields of different "type"; i.e. mixing member descriptions and free-form text under the same heading. To accomplish this with a minimum of hackery, we technically document a "dummy field" and then just strip off the documentation for that dummy field in a post-processing step. We use the "q_dummy" variable for this purpose, then strip it back out before final processing. If this processing step should fail, you'll see warnings for a bad cross-reference. (So if you don't see any, it must be working!) Signed-off-by: John Snow <jsnow@redhat.com> Message-ID: <20250311034303.75779-58-jsnow@redhat.com> Acked-by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
parent
7f6f24aaf5
commit
1884492e64
2 changed files with 77 additions and 3 deletions
|
@ -433,6 +433,24 @@ class QAPIObject(QAPIDescription):
|
||||||
self._validate_field(field)
|
self._validate_field(field)
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialTypedField(CompatTypedField):
|
||||||
|
def make_field(self, *args: Any, **kwargs: Any) -> nodes.field:
|
||||||
|
ret = super().make_field(*args, **kwargs)
|
||||||
|
|
||||||
|
# Look for the characteristic " -- " text node that Sphinx
|
||||||
|
# inserts for each TypedField entry ...
|
||||||
|
for node in ret.traverse(lambda n: str(n) == " -- "):
|
||||||
|
par = node.parent
|
||||||
|
if par.children[0].astext() != "q_dummy":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If the first node's text is q_dummy, this is a dummy
|
||||||
|
# field we want to strip down to just its contents.
|
||||||
|
del par.children[:-1]
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class QAPICommand(QAPIObject):
|
class QAPICommand(QAPIObject):
|
||||||
"""Description of a QAPI Command."""
|
"""Description of a QAPI Command."""
|
||||||
|
|
||||||
|
@ -440,7 +458,7 @@ class QAPICommand(QAPIObject):
|
||||||
doc_field_types.extend(
|
doc_field_types.extend(
|
||||||
[
|
[
|
||||||
# :arg TypeName ArgName: descr
|
# :arg TypeName ArgName: descr
|
||||||
CompatTypedField(
|
SpecialTypedField(
|
||||||
"argument",
|
"argument",
|
||||||
label=_("Arguments"),
|
label=_("Arguments"),
|
||||||
names=("arg",),
|
names=("arg",),
|
||||||
|
@ -508,7 +526,7 @@ class QAPIObjectWithMembers(QAPIObject):
|
||||||
doc_field_types.extend(
|
doc_field_types.extend(
|
||||||
[
|
[
|
||||||
# :member type name: descr
|
# :member type name: descr
|
||||||
CompatTypedField(
|
SpecialTypedField(
|
||||||
"member",
|
"member",
|
||||||
label=_("Members"),
|
label=_("Members"),
|
||||||
names=("memb",),
|
names=("memb",),
|
||||||
|
|
|
@ -47,8 +47,10 @@ from qapi.schema import (
|
||||||
QAPISchemaCommand,
|
QAPISchemaCommand,
|
||||||
QAPISchemaDefinition,
|
QAPISchemaDefinition,
|
||||||
QAPISchemaEnumMember,
|
QAPISchemaEnumMember,
|
||||||
|
QAPISchemaEvent,
|
||||||
QAPISchemaFeature,
|
QAPISchemaFeature,
|
||||||
QAPISchemaMember,
|
QAPISchemaMember,
|
||||||
|
QAPISchemaObjectType,
|
||||||
QAPISchemaObjectTypeMember,
|
QAPISchemaObjectTypeMember,
|
||||||
QAPISchemaType,
|
QAPISchemaType,
|
||||||
QAPISchemaVisitor,
|
QAPISchemaVisitor,
|
||||||
|
@ -298,11 +300,61 @@ class Transmogrifier:
|
||||||
|
|
||||||
self.ensure_blank_line()
|
self.ensure_blank_line()
|
||||||
|
|
||||||
|
def _insert_member_pointer(self, ent: QAPISchemaDefinition) -> None:
|
||||||
|
|
||||||
|
def _get_target(
|
||||||
|
ent: QAPISchemaDefinition,
|
||||||
|
) -> Optional[QAPISchemaDefinition]:
|
||||||
|
if isinstance(ent, (QAPISchemaCommand, QAPISchemaEvent)):
|
||||||
|
return ent.arg_type
|
||||||
|
if isinstance(ent, QAPISchemaObjectType):
|
||||||
|
return ent.base
|
||||||
|
return None
|
||||||
|
|
||||||
|
target = _get_target(ent)
|
||||||
|
if target is not None and not target.is_implicit():
|
||||||
|
assert ent.info
|
||||||
|
self.add_field(
|
||||||
|
self.member_field_type,
|
||||||
|
"q_dummy",
|
||||||
|
f"The members of :qapi:type:`{target.name}`.",
|
||||||
|
ent.info,
|
||||||
|
"q_dummy",
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(ent, QAPISchemaObjectType) and ent.branches is not None:
|
||||||
|
for variant in ent.branches.variants:
|
||||||
|
if variant.type.name == "q_empty":
|
||||||
|
continue
|
||||||
|
assert ent.info
|
||||||
|
self.add_field(
|
||||||
|
self.member_field_type,
|
||||||
|
"q_dummy",
|
||||||
|
f" When ``{ent.branches.tag_member.name}`` is "
|
||||||
|
f"``{variant.name}``: "
|
||||||
|
f"The members of :qapi:type:`{variant.type.name}`.",
|
||||||
|
ent.info,
|
||||||
|
"q_dummy",
|
||||||
|
)
|
||||||
|
|
||||||
def visit_sections(self, ent: QAPISchemaDefinition) -> None:
|
def visit_sections(self, ent: QAPISchemaDefinition) -> None:
|
||||||
sections = ent.doc.all_sections if ent.doc else []
|
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:
|
# Add sections in source order:
|
||||||
for section in sections:
|
for i, section in enumerate(sections):
|
||||||
# @var is translated to ``var``:
|
# @var is translated to ``var``:
|
||||||
section.text = re.sub(r"@([\w-]+)", r"``\1``", section.text)
|
section.text = re.sub(r"@([\w-]+)", r"``\1``", section.text)
|
||||||
|
|
||||||
|
@ -324,6 +376,10 @@ class Transmogrifier:
|
||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
# Generate "The members of ..." entries if necessary:
|
||||||
|
if i == gen_index - 1:
|
||||||
|
self._insert_member_pointer(ent)
|
||||||
|
|
||||||
self.ensure_blank_line()
|
self.ensure_blank_line()
|
||||||
|
|
||||||
# Transmogrification core methods
|
# Transmogrification core methods
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue