On Fri, Jun 28, 2024, 9:24 AM Markus Armbruster <armbru@redhat.com> wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > This is a directive that creates a syntactic sugar for creating
> > "Example" boxes very similar to the ones already used in the bitmaps.rst
> > document, please see e.g.
> >
> https://www.qemu.org/docs/master/interop/bitmaps.html#creation-block-dirty-bitmap-add
> >
> > In its simplest form, when a custom title is not needed or wanted, and
> > the example body is *solely* a QMP example:
> >
> > ```
> > .. qmp-example::
> >
> > {body}
> > ```
> >
> > is syntactic sugar for:
> >
> > ```
> > .. admonition:: Example:
> >
> > .. code-block:: QMP
> >
> > {body}
> > ```
> >
> > When a custom, plaintext title that describes the example is desired,
> > this form:
> >
> > ```
> > .. qmp-example::
> > :title: Defrobnification
> >
> > {body}
> > ```
> >
> > Is syntactic sugar for:
> >
> > ```
> > .. admonition:: Example: Defrobnification
> >
> > .. code-block:: QMP
> >
> > {body}
> > ```
> >
> > Lastly, when Examples are multi-step processes that require non-QMP
> > exposition, have lengthy titles, or otherwise involve prose with rST
> > markup (lists, cross-references, etc), the most complex form:
> >
> > ```
> > .. qmp-example::
> > :annotated:
> >
> > This example shows how to use `foo-command`::
> >
> > {body}
> > ```
> >
> > Is desugared to:
> >
> > ```
> > .. admonition:: Example:
> >
> > This example shows how to use `foo-command`::
> >
> > {body}
> >
> > For more information, please see `frobnozz`.
> > ```
>
^ Whoops, added prose in the desugar block without modifying the original.
> Can we combine the latter two? Like this:
>
> .. qmp-example::
> :title: Defrobnification
> :annotated:
>
> This example shows how to use `foo-command`::
>
> {body}
>
Yes! I only didn't use that form in the series because splitting longer
Examples into title and prose felt like an editorial decision, but
absolutely you can use both.
> > The primary benefit here being documentation source consistently using
> > the same directive for all forms of examples to ensure consistent visual
> > styling, and ensuring all relevant prose is visually grouped alongside
> > the code literal block.
> >
> > Note that as of this commit, the code-block rST syntax "::" does not
> > apply QMP highlighting; you would need to use ".. code-block:: QMP". The
> > very next commit changes this behavior to assume all "::" code blocks
> > within this directive are QMP blocks.
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > docs/sphinx/qapidoc.py | 60 ++++++++++++++++++++++++++++++++++++++++--
> > 1 file changed, 58 insertions(+), 2 deletions(-)
>
> No tests? Hmm, I see you convert existing tests in PATCH 19-21. While
> that works, test coverage now would make it easier to see how each patch
> affects doc generator output.
>
Mmm. Do you want me to move the test changes up to this patch ... ?
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index 43dd99e21e6..a2fa05fc491 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -27,16 +27,19 @@
> > import os
> > import re
> > import textwrap
> > +from typing import List
> >
> > from docutils import nodes
> > -from docutils.parsers.rst import Directive, directives
> > +from docutils.parsers.rst import directives
> > from docutils.statemachine import ViewList
> > from qapi.error import QAPIError, QAPISemError
> > from qapi.gen import QAPISchemaVisitor
> > from qapi.schema import QAPISchema
> >
> > import sphinx
> > +from sphinx.directives.code import CodeBlock
> > from sphinx.errors import ExtensionError
> > +from sphinx.util.docutils import SphinxDirective
> > from sphinx.util.nodes import nested_parse_with_titles
> >
> >
> > @@ -494,7 +497,7 @@ def visit_module(self, name):
> > super().visit_module(name)
> >
> >
> > -class NestedDirective(Directive):
> > +class NestedDirective(SphinxDirective):
>
> What is this about?
>
Hmm. Strictly it's for access to sphinx configuration which I use only in
the next patch, but practically I suspect if I don't change it *here* that
the multiple inheritance from CodeBlock (which is a SphinxDirective) would
possibly be stranger.
I can try delaying that change by a patch and see if it hurts anything ...
> > def run(self):
> > raise NotImplementedError
> >
> > @@ -567,10 +570,63 @@ def run(self):
> > raise ExtensionError(str(err)) from err
> >
> >
> > +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 instead. 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 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]:
> > + content_node: nodes.Element = nodes.section()
> > + self.do_parse(self.content, content_node)
> > + 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__,
>
>