summary refs log tree commit diff stats
path: root/docs/sphinx/qapidoc.py
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2024-07-18 10:06:35 +1000
committerRichard Henderson <richard.henderson@linaro.org>2024-07-18 10:06:35 +1000
commite6485190f77e6166cde8dc799e4e52e607b6f61d (patch)
treef3c34b5f28f9ed0ce8d2e39b834ae388c2541321 /docs/sphinx/qapidoc.py
parent58ee924b97d1c0898555647a31820c5a20d55a73 (diff)
parent3c5f6114d9ffc70bc9b1a7cc0dddd72a911f966d (diff)
downloadfocaccia-qemu-e6485190f77e6166cde8dc799e4e52e607b6f61d.tar.gz
focaccia-qemu-e6485190f77e6166cde8dc799e4e52e607b6f61d.zip
Merge tag 'pull-qapi-2024-07-17' of https://repo.or.cz/qemu/armbru into staging
QAPI patches patches for 2024-07-17

# -----BEGIN PGP SIGNATURE-----
#
# iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAmaXoUESHGFybWJydUBy
# ZWRoYXQuY29tAAoJEDhwtADrkYZTatYP/jPlsmx8S6X397COQf6Wd4oEFQEMo/FS
# tWFiHWenPUZ56U3O3lDNIw+5URbhF4aUpxhLGg6cmkrOwK0zPjARI2UNnUnZvPtN
# EHf//KJOpYLsSdkIlIW2nYzB27ps0DRf5PgOGdOOdW32Nuq93FLx7ChDgbpmrijc
# HzByyJIn1QEv/G0aOMLCuTPA7LpGjCAd2a/LjWYpSXB3WGizrS2Rrat7oJYUl8Rz
# mAPgdiE0aH2yWHOTcWabKfN4AsIHCnv7qNOZkumoWpZ0XULbgyK1OO05ju3jRSrB
# 0WiwiE8pEhHz7qstKGcjS1c7pPuId64ubm3RAZ1RUqVvA5TZGucwuYiuQHUVX6jH
# BGzpfojISFzIfTiKemyfqBb1gjXjxT6OIlCtmlJSUCJohb70f0fhX3vniyhzyl1d
# fFTM0jMbmBX89e/o3j6ZXa7anafYNDh5TjTK4BYeAXRqe+jZpvDJUrwu1OZIq1cd
# Wr1RR8qaawpfjD5r9SXu1mX5MPCX4SmNVNoQ7N4ruWjpVojQNmuCRW9yLPIv3yTH
# c5ESND4zdvceW5EF9f5GSIFwnIdGqnUwJyBMcULGoCxz1HougQmGR4bhqSkEl6RD
# GRK+bj3pLdj9f/en62mE6+f5rkEJye3Y5fJ5dn9+Ld09PeUtY59YKnJGg896g55V
# /pGOUWf3L4iY
# =E0F5
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 17 Jul 2024 08:47:29 PM AEST
# gpg:                using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653
# gpg:                issuer "armbru@redhat.com"
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full]
# gpg:                 aka "Markus Armbruster <armbru@pond.sub.org>" [full]

* tag 'pull-qapi-2024-07-17' of https://repo.or.cz/qemu/armbru:
  qapi: remove "Example" doc section
  qapi: convert "Example" sections with longer prose
  qapi: convert "Example" sections with titles
  qapi: convert "Example" sections without titles
  docs/sphinx: add CSS styling for qmp-example directive
  docs/qapidoc: add QMP highlighting to annotated qmp-example blocks
  docs/qapidoc: create qmp-example directive
  docs/qapidoc: factor out do_parse()
  qapi/ui: Drop note on naming of SpiceQueryMouseMode
  qapi/sockets: Move deprecation note out of SocketAddress doc comment
  qapi/machine: Clarify query-uuid value when none has been specified
  qapi/machine: Clean up documentation around CpuInstanceProperties
  qapi/pci: Clean up documentation around PciDeviceClass
  qapi/qom: Document feature unstable of @x-vfio-user-server

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Diffstat (limited to 'docs/sphinx/qapidoc.py')
-rw-r--r--docs/sphinx/qapidoc.py128
1 files changed, 117 insertions, 11 deletions
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 62b39833ca..738b2450fb 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -26,7 +26,9 @@ https://www.sphinx-doc.org/en/master/development/index.html
 
 import os
 import re
+import sys
 import textwrap
+from typing import List
 
 from docutils import nodes
 from docutils.parsers.rst import Directive, directives
@@ -35,6 +37,8 @@ from qapi.error import QAPIError, QAPISemError
 from qapi.gen import QAPISchemaVisitor
 from qapi.schema import QAPISchema
 
+from sphinx import addnodes
+from sphinx.directives.code import CodeBlock
 from sphinx.errors import ExtensionError
 from sphinx.util.docutils import switch_source_input
 from sphinx.util.nodes import nested_parse_with_titles
@@ -481,7 +485,25 @@ class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
         super().visit_module(name)
 
 
-class QAPIDocDirective(Directive):
+class NestedDirective(Directive):
+    def run(self):
+        raise NotImplementedError
+
+    def do_parse(self, rstlist, node):
+        """
+        Parse rST source lines and add them to the specified node
+
+        Take the list of rST source lines rstlist, parse them as
+        rST, and add the resulting docutils nodes as children of node.
+        The nodes are parsed in a way that allows them to include
+        subheadings (titles) without confusing the rendering of
+        anything else.
+        """
+        with switch_source_input(self.state, rstlist):
+            nested_parse_with_titles(self.state, rstlist, node)
+
+
+class QAPIDocDirective(NestedDirective):
     """Extract documentation from the specified QAPI .json file"""
 
     required_argument = 1
@@ -519,23 +541,107 @@ class QAPIDocDirective(Directive):
             # so they are displayed nicely to the user
             raise ExtensionError(str(err)) from err
 
-    def do_parse(self, rstlist, node):
-        """Parse rST source lines and add them to the specified node
 
-        Take the list of rST source lines rstlist, parse them as
-        rST, and add the resulting docutils nodes as children of node.
-        The nodes are parsed in a way that allows them to include
-        subheadings (titles) without confusing the rendering of
-        anything else.
-        """
-        with switch_source_input(self.state, rstlist):
-            nested_parse_with_titles(self.state, rstlist, node)
+class QMPExample(CodeBlock, NestedDirective):
+    """
+    Custom admonition for QMP code examples.
+
+    When the :annotated: option is present, the body of this directive
+    is parsed as normal rST, but with any '::' code blocks set to use
+    the QMP lexer. Code blocks must be explicitly written by the user,
+    but this allows for intermingling explanatory paragraphs with
+    arbitrary rST syntax and code blocks for more involved examples.
+
+    When :annotated: is absent, the directive body is treated as a
+    simple standalone QMP code block literal.
+    """
+
+    required_argument = 0
+    optional_arguments = 0
+    has_content = True
+    option_spec = {
+        "annotated": directives.flag,
+        "title": directives.unchanged,
+    }
+
+    def _highlightlang(self) -> addnodes.highlightlang:
+        """Return the current highlightlang setting for the document"""
+        node = None
+        doc = self.state.document
+
+        if hasattr(doc, "findall"):
+            # docutils >= 0.18.1
+            for node in doc.findall(addnodes.highlightlang):
+                pass
+        else:
+            for elem in doc.traverse():
+                if isinstance(elem, addnodes.highlightlang):
+                    node = elem
+
+        if node:
+            return node
+
+        # No explicit directive found, use defaults
+        node = addnodes.highlightlang(
+            lang=self.env.config.highlight_language,
+            force=False,
+            # Yes, Sphinx uses this value to effectively disable line
+            # numbers and not 0 or None or -1 or something. ¯\_(ツ)_/¯
+            linenothreshold=sys.maxsize,
+        )
+        return node
+
+    def admonition_wrap(self, *content) -> List[nodes.Node]:
+        title = "Example:"
+        if "title" in self.options:
+            title = f"{title} {self.options['title']}"
+
+        admon = nodes.admonition(
+            "",
+            nodes.title("", title),
+            *content,
+            classes=["admonition", "admonition-example"],
+        )
+        return [admon]
+
+    def run_annotated(self) -> List[nodes.Node]:
+        lang_node = self._highlightlang()
+
+        content_node: nodes.Element = nodes.section()
+
+        # Configure QMP highlighting for "::" blocks, if needed
+        if lang_node["lang"] != "QMP":
+            content_node += addnodes.highlightlang(
+                lang="QMP",
+                force=False,  # "True" ignores lexing errors
+                linenothreshold=lang_node["linenothreshold"],
+            )
+
+        self.do_parse(self.content, content_node)
+
+        # Restore prior language highlighting, if needed
+        if lang_node["lang"] != "QMP":
+            content_node += addnodes.highlightlang(**lang_node.attributes)
+
+        return content_node.children
+
+    def run(self) -> List[nodes.Node]:
+        annotated = "annotated" in self.options
+
+        if annotated:
+            content_nodes = self.run_annotated()
+        else:
+            self.arguments = ["QMP"]
+            content_nodes = super().run()
+
+        return self.admonition_wrap(*content_nodes)
 
 
 def setup(app):
     """Register qapi-doc directive with Sphinx"""
     app.add_config_value("qapidoc_srctree", None, "env")
     app.add_directive("qapi-doc", QAPIDocDirective)
+    app.add_directive("qmp-example", QMPExample)
 
     return {
         "version": __version__,