On Fri, Jun 28, 2024, 9:24 AM Markus Armbruster <arm...@redhat.com> wrote:

> John Snow <js...@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 <js...@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__,
>
>

Reply via email to