Skip to content

Commit 1884492

Browse files
jnsnowMarkus Armbruster
authored andcommitted
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 <[email protected]> Message-ID: <[email protected]> Acked-by: Markus Armbruster <[email protected]> Signed-off-by: Markus Armbruster <[email protected]>
1 parent 7f6f24a commit 1884492

File tree

2 files changed

+77
-3
lines changed

2 files changed

+77
-3
lines changed

docs/sphinx/qapi_domain.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,14 +433,32 @@ def transform_content(self, content_node: addnodes.desc_content) -> None:
433433
self._validate_field(field)
434434

435435

436+
class SpecialTypedField(CompatTypedField):
437+
def make_field(self, *args: Any, **kwargs: Any) -> nodes.field:
438+
ret = super().make_field(*args, **kwargs)
439+
440+
# Look for the characteristic " -- " text node that Sphinx
441+
# inserts for each TypedField entry ...
442+
for node in ret.traverse(lambda n: str(n) == " -- "):
443+
par = node.parent
444+
if par.children[0].astext() != "q_dummy":
445+
continue
446+
447+
# If the first node's text is q_dummy, this is a dummy
448+
# field we want to strip down to just its contents.
449+
del par.children[:-1]
450+
451+
return ret
452+
453+
436454
class QAPICommand(QAPIObject):
437455
"""Description of a QAPI Command."""
438456

439457
doc_field_types = QAPIObject.doc_field_types.copy()
440458
doc_field_types.extend(
441459
[
442460
# :arg TypeName ArgName: descr
443-
CompatTypedField(
461+
SpecialTypedField(
444462
"argument",
445463
label=_("Arguments"),
446464
names=("arg",),
@@ -508,7 +526,7 @@ class QAPIObjectWithMembers(QAPIObject):
508526
doc_field_types.extend(
509527
[
510528
# :member type name: descr
511-
CompatTypedField(
529+
SpecialTypedField(
512530
"member",
513531
label=_("Members"),
514532
names=("memb",),

docs/sphinx/qapidoc.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@
4747
QAPISchemaCommand,
4848
QAPISchemaDefinition,
4949
QAPISchemaEnumMember,
50+
QAPISchemaEvent,
5051
QAPISchemaFeature,
5152
QAPISchemaMember,
53+
QAPISchemaObjectType,
5254
QAPISchemaObjectTypeMember,
5355
QAPISchemaType,
5456
QAPISchemaVisitor,
@@ -298,11 +300,61 @@ def preamble(self, ent: QAPISchemaDefinition) -> None:
298300

299301
self.ensure_blank_line()
300302

303+
def _insert_member_pointer(self, ent: QAPISchemaDefinition) -> None:
304+
305+
def _get_target(
306+
ent: QAPISchemaDefinition,
307+
) -> Optional[QAPISchemaDefinition]:
308+
if isinstance(ent, (QAPISchemaCommand, QAPISchemaEvent)):
309+
return ent.arg_type
310+
if isinstance(ent, QAPISchemaObjectType):
311+
return ent.base
312+
return None
313+
314+
target = _get_target(ent)
315+
if target is not None and not target.is_implicit():
316+
assert ent.info
317+
self.add_field(
318+
self.member_field_type,
319+
"q_dummy",
320+
f"The members of :qapi:type:`{target.name}`.",
321+
ent.info,
322+
"q_dummy",
323+
)
324+
325+
if isinstance(ent, QAPISchemaObjectType) and ent.branches is not None:
326+
for variant in ent.branches.variants:
327+
if variant.type.name == "q_empty":
328+
continue
329+
assert ent.info
330+
self.add_field(
331+
self.member_field_type,
332+
"q_dummy",
333+
f" When ``{ent.branches.tag_member.name}`` is "
334+
f"``{variant.name}``: "
335+
f"The members of :qapi:type:`{variant.type.name}`.",
336+
ent.info,
337+
"q_dummy",
338+
)
339+
301340
def visit_sections(self, ent: QAPISchemaDefinition) -> None:
302341
sections = ent.doc.all_sections if ent.doc else []
303342

343+
# Determine the index location at which we should generate
344+
# documentation for "The members of ..." pointers. This should
345+
# go at the end of the members section(s) if any. Note that
346+
# index 0 is assumed to be a plain intro section, even if it is
347+
# empty; and that a members section if present will always
348+
# immediately follow the opening PLAIN section.
349+
gen_index = 1
350+
if len(sections) > 1:
351+
while sections[gen_index].kind == QAPIDoc.Kind.MEMBER:
352+
gen_index += 1
353+
if gen_index >= len(sections):
354+
break
355+
304356
# Add sections in source order:
305-
for section in sections:
357+
for i, section in enumerate(sections):
306358
# @var is translated to ``var``:
307359
section.text = re.sub(r"@([\w-]+)", r"``\1``", section.text)
308360

@@ -324,6 +376,10 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
324376
else:
325377
assert False
326378

379+
# Generate "The members of ..." entries if necessary:
380+
if i == gen_index - 1:
381+
self._insert_member_pointer(ent)
382+
327383
self.ensure_blank_line()
328384

329385
# Transmogrification core methods

0 commit comments

Comments
 (0)