aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/devel/qapi-code-gen.rst58
-rw-r--r--docs/sphinx-static/theme_overrides.css49
-rw-r--r--docs/sphinx/qapidoc.py128
3 files changed, 215 insertions, 20 deletions
diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst
index ae97b335cb..583207a8ec 100644
--- a/docs/devel/qapi-code-gen.rst
+++ b/docs/devel/qapi-code-gen.rst
@@ -899,7 +899,7 @@ Documentation markup
~~~~~~~~~~~~~~~~~~~~
Documentation comments can use most rST markup. In particular,
-a ``::`` literal block can be used for examples::
+a ``::`` literal block can be used for pre-formatted text::
# ::
#
@@ -995,8 +995,8 @@ line "Features:", like this::
# @feature: Description text
A tagged section begins with a paragraph that starts with one of the
-following words: "Since:", "Example:"/"Examples:", "Returns:",
-"Errors:", "TODO:". It ends with the start of a new section.
+following words: "Since:", "Returns:", "Errors:", "TODO:". It ends with
+the start of a new section.
The second and subsequent lines of tagged sections must be indented
like this::
@@ -1020,13 +1020,53 @@ detailing a relevant error condition. For example::
A "Since: x.y.z" tagged section lists the release that introduced the
definition.
-An "Example" or "Examples" section is rendered entirely
-as literal fixed-width text. "TODO" sections are not rendered at all
-(they are for developers, not users of QMP). In other sections, the
-text is formatted, and rST markup can be used.
+"TODO" sections are not rendered (they are for developers, not users of
+QMP). In other sections, the text is formatted, and rST markup can be
+used.
+
+QMP Examples can be added by using the ``.. qmp-example::``
+directive. In its simplest form, this can be used to contain a single
+QMP code block which accepts standard JSON syntax with additional server
+directionality indicators (``->`` and ``<-``), and elisions (``...``).
+
+Optionally, a plaintext title may be provided by using the ``:title:``
+directive option. If the title is omitted, the example title will
+default to "Example:".
+
+A simple QMP example::
+
+ # .. qmp-example::
+ # :title: Using query-block
+ #
+ # -> { "execute": "query-block" }
+ # <- { ... }
+
+More complex or multi-step examples where exposition is needed before or
+between QMP code blocks can be created by using the ``:annotated:``
+directive option. When using this option, nested QMP code blocks must be
+entered explicitly with rST's ``::`` syntax.
+
+Highlighting in non-QMP languages can be accomplished by using the
+``.. code-block:: lang`` directive, and non-highlighted text can be
+achieved by omitting the language argument.
For example::
+ # .. qmp-example::
+ # :annotated:
+ # :title: A more complex demonstration
+ #
+ # This is a more complex example that can use
+ # ``arbitrary rST syntax`` in its exposition::
+ #
+ # -> { "execute": "query-block" }
+ # <- { ... }
+ #
+ # Above, lengthy output has been omitted for brevity.
+
+
+Examples of complete definition documentation::
+
##
# @BlockStats:
#
@@ -1058,11 +1098,11 @@ For example::
#
# Since: 0.14
#
- # Example:
+ # .. qmp-example::
#
# -> { "execute": "query-blockstats" }
# <- {
- # ... lots of output ...
+ # ...
# }
##
{ 'command': 'query-blockstats',
diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css
index c70ef95128..965ecac54f 100644
--- a/docs/sphinx-static/theme_overrides.css
+++ b/docs/sphinx-static/theme_overrides.css
@@ -87,6 +87,55 @@ div[class^="highlight"] pre {
padding-bottom: 1px;
}
+/* qmp-example directive styling */
+
+.rst-content .admonition-example {
+ /* do not apply the standard admonition background */
+ background-color: transparent;
+ border: solid #ffd2ed 1px;
+}
+
+.rst-content .admonition-example > .admonition-title:before {
+ content: "▷";
+}
+
+.rst-content .admonition-example > .admonition-title {
+ background-color: #5980a6;
+}
+
+.rst-content .admonition-example > div[class^="highlight"] {
+ /* make code boxes take up the full width of the admonition w/o margin */
+ margin-left: -12px;
+ margin-right: -12px;
+
+ border-top: 1px solid #ffd2ed;
+ border-bottom: 1px solid #ffd2ed;
+ border-left: 0px;
+ border-right: 0px;
+}
+
+.rst-content .admonition-example > div[class^="highlight"]:nth-child(2) {
+ /* If a code box is the second element in an example admonition,
+ * it is the first child after the title. let it sit flush against
+ * the title. */
+ margin-top: -12px;
+ border-top: 0px;
+}
+
+.rst-content .admonition-example > div[class^="highlight"]:last-child {
+ /* If a code box is the final element in an example admonition, don't
+ * render margin below it; let it sit flush with the end of the
+ * admonition box */
+ margin-bottom: -12px;
+ border-bottom: 0px;
+}
+
+.rst-content .admonition-example .highlight {
+ background-color: #fffafd;
+}
+
+/* end qmp-example styling */
+
@media screen {
/* content column
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__,