This commit adds a stubbed version of QAPICommand that utilizes the
QAPIObject class, the qapi:command directive, the :qapi:cmd:
cross-reference role, and the "command" object type in the QAPI object
registry.
This commit also adds the aforementioned generic QAPIObject class for
use in documenting various QAPI entities in the Sphinx ecosystem.
They don't do anything *particularly* interesting yet, but that will
come in forthcoming commits.
Note: some versions of mypy get a little confused over the difference
between class and instance variables; because sphinx's ObjectDescription
does not declare option_spec as a ClassVar (even though it's obvious
that it is), mypy may produce this error:
qapi-domain.py:125: error: Cannot override instance variable (previously
declared on base class "ObjectDescription") with class variable [misc]
I can't control that; so silence the error with a pragma.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 146 ++++++++++++++++++++++++++++++++++++-
1 file changed, 144 insertions(+), 2 deletions(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 104bae709f3..6168c23936f 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -21,9 +21,10 @@
from docutils import nodes
from docutils.parsers.rst import directives
-from compat import nested_parse
+from compat import KeywordNode, SpaceNode, nested_parse
from sphinx import addnodes
-from sphinx.addnodes import pending_xref
+from sphinx.addnodes import desc_signature, pending_xref
+from sphinx.directives import ObjectDescription
from sphinx.domains import (
Domain,
Index,
@@ -103,6 +104,144 @@ def process_link(
return title, target
+# Alias for the return of handle_signature(), which is used in several places.
+# (In the Python domain, this is Tuple[str, str] instead.)
+Signature = str
+
+
+class QAPIObject(ObjectDescription[Signature]):
+ """
+ Description of a generic QAPI object.
+
+ It's not used directly, but is instead subclassed by specific directives.
+ """
+
+ # Inherit some standard options from Sphinx's ObjectDescription
+ option_spec: OptionSpec = ( # type:ignore[misc]
+ ObjectDescription.option_spec.copy()
+ )
+ option_spec.update(
+ {
+ # Borrowed from the Python domain:
+ "module": directives.unchanged, # Override contextual module name
+ }
+ )
+
+ def get_signature_prefix(self) -> List[nodes.Node]:
+ """Returns a prefix to put before the object name in the signature."""
+ assert self.objtype
+ return [
+ KeywordNode("", self.objtype.title()),
+ SpaceNode(" "),
+ ]
+
+ def get_signature_suffix(self) -> list[nodes.Node]:
+ """Returns a suffix to put after the object name in the signature."""
+ return []
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> Signature:
+ """
+ Transform a QAPI definition name into RST nodes.
+
+ This method was originally intended for handling function
+ signatures. In the QAPI domain, however, we only pass the
+ definition name as the directive argument and handle everything
+ else in the content body with field lists.
+
+ As such, the only argument here is "sig", which is just the QAPI
+ definition name.
+ """
+ modname = self.options.get(
+ "module", self.env.ref_context.get("qapi:module")
+ )
+
+ signode["fullname"] = sig
+ signode["module"] = modname
+ sig_prefix = self.get_signature_prefix()
+ if sig_prefix:
+ signode += addnodes.desc_annotation(
+ str(sig_prefix), "", *sig_prefix
+ )
+ signode += addnodes.desc_name(sig, sig)
+ signode += self.get_signature_suffix()
+
+ return sig
+
+ def _object_hierarchy_parts(
+ self, sig_node: desc_signature
+ ) -> Tuple[str, ...]:
+ if "fullname" not in sig_node:
+ return ()
+ modname = sig_node.get("module")
+ fullname = sig_node["fullname"]
+
+ if modname:
+ return (modname, *fullname.split("."))
+
+ return tuple(fullname.split("."))
+
+ def get_index_text(self, modname: str, name: Signature) -> str:
+ """Return the text for the index entry of the object."""
+ # We don't care about the module name here:
+ # pylint: disable=unused-argument
+
+ # NB: this is used for the global index, not the QAPI index.
+ return f"{name} (QMP {self.objtype})"
+
+ def add_target_and_index(
+ self, name: Signature, sig: str, signode: desc_signature
+ ) -> None:
+ # Called by ObjectDescription.run with the result of
+ # handle_signature; name is the return value of handle_signature
+ # where sig is the original argument to handle_signature. In our
+ # case, they're the same for now.
+ assert self.objtype
+
+ modname = self.options.get(
+ "module", self.env.ref_context.get("qapi:module")
+ )
+ # Here, sphinx decides to prepend the module name. OK.
+ fullname = (modname + "." if modname else "") + name
+ node_id = make_id(self.env, self.state.document, "", fullname)
+ signode["ids"].append(node_id)
+ self.state.document.note_explicit_target(signode)
+
+ domain = cast(QAPIDomain, self.env.get_domain("qapi"))
+ domain.note_object(fullname, self.objtype, node_id, location=signode)
+
+ if "no-index-entry" not in self.options:
+ indextext = self.get_index_text(modname, name)
+ assert self.indexnode is not None
+ if indextext:
+ self.indexnode["entries"].append(
+ ("single", indextext, node_id, "", None)
+ )
+
+ def _toc_entry_name(self, sig_node: desc_signature) -> str:
+ # This controls the name in the TOC and on the sidebar.
+
+ # This is the return type of _object_hierarchy_parts().
+ toc_parts = cast(Tuple[str, ...], sig_node.get("_toc_parts", ()))
+ if not toc_parts:
+ return ""
+
+ config = self.env.app.config
+ *parents, name = toc_parts
+ if config.toc_object_entries_show_parents == "domain":
+ return sig_node.get("fullname", name)
+ if config.toc_object_entries_show_parents == "hide":
+ return name
+ if config.toc_object_entries_show_parents == "all":
+ return ".".join(parents + [name])
+ return ""
+
+
+class QAPICommand(QAPIObject):
+ """Description of a QAPI Command."""
+
+ # Nothing unique for now! Changed in later commits O:-)
+
+
class QAPIModule(SphinxDirective):
"""
Directive to mark description of a new module.
@@ -260,12 +399,14 @@ class QAPIDomain(Domain):
# for each object type.
object_types: Dict[str, ObjType] = {
"module": ObjType(_("module"), "mod", "any"),
+ "command": ObjType(_("command"), "cmd", "any"),
}
# Each of these provides a rST directive,
# e.g. .. qapi:module:: block-core
directives = {
"module": QAPIModule,
+ "command": QAPICommand,
}
# These are all cross-reference roles; e.g.
@@ -273,6 +414,7 @@ class QAPIDomain(Domain):
# the object_types table values above.
roles = {
"mod": QAPIXRefRole(),
+ "cmd": QAPIXRefRole(),
"any": QAPIXRefRole(), # reference *any* type of QAPI object.
}
--
2.48.1