All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
From: John Snow <jsnow@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>,
	"Markus Armbruster" <armbru@redhat.com>,
	"Victor Toso de Carvalho" <victortoso@redhat.com>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	"Paolo Bonzini" <pbonzini@redhat.com>,
	"John Snow" <jsnow@redhat.com>
Subject: [PATCH 17/27] docs/qapi-domain: add qapi:union and qapi:branch directives
Date: Fri, 19 Apr 2024 00:38:05 -0400	[thread overview]
Message-ID: <20240419043820.178731-18-jsnow@redhat.com> (raw)
In-Reply-To: <20240419043820.178731-1-jsnow@redhat.com>

Adds the .. qapi:union:: directive, object, and :qapi:union:`name`
cross-referencing role.

In order to support discriminated branches of unions, a new qapi:branch
directive is created whose only purpose is to create a dynamically named
field list section based on the name of the branch key and value.

Because the label for these branch sections is dynamically generated,
this patch allows the use of either :memb: or :arg: in branches; they
are simply aliases and do not do anything different.

(This is to allow the directive to be used for either Commands or Unions
as needed; i.e. for commands whose argument type is a discriminated
union.)

For example:

.. qapi:union:: foo-union

   :memb foo-enum key: Discriminator field
   :memb foo-type fieldname: Some base type field that is always present

   .. qapi:branch:: key value1

      :memb type name: lorem ipsum ...

   .. qapi:branch:: key value2

      :memb type2 name2: dolor sit amet ...

In order to support this syntax, the root QAPIObject class needs to
perform some post-processing on field lists to merge adjecent field
lists. because each branch directive will return a separate field list,
and we want to combine them into one unified list for proper rendering.

NOTE: Technically, the branch directive will allow you to put arbitrary
ReST besides field lists inside of it, but it almost certainly won't do
anything you'd consider useful. I don't recommend it, but
programmatically forbidding it is expensive. Recommendation: "Don't!"

... and now ...

RFC: The branches abuse the field list system to create new categories
of field list entries that are dynamically generated. At the moment, I
do not have code to apply appropriate formatting to these headings, but
would like to apply ``literal`` syntax at a minimum to the enum values.

The other idea I have is to create a "blank" members value that doesn't
add a "title" (field label) on the left column and instead adds a
horizontal info bar into the arguments list in the right, but the
problem with this approach is that I won't be able to get rid of the "*
..." list bullet syntax, so it may look odd. Otherwise, a benefit to
this syntax is that it would allow us to write a longer explanation,
e.g. "When ``{key}`` is ``{value}``".

It's possible I could split the argument list into several nested lists,
but I haven't experimented in that direction yet - it's also likely that
Sphinx will want to append a ":" even to blank section titles, so this
may prove tricky to do within the constraints of Sphinx's existing Field
list system. (I believe the HTML formatter is responsible for appending
the colons.)

I'm open to suggestions, but wrestling with Sphinx, html and css is time
consuming (for me, at least), so ultimately, I'm afraid I must say:
***PATCHES WELCOME***.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 docs/qapi/index.rst        | 37 ++++++++++++++
 docs/sphinx/qapi-domain.py | 98 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 134 insertions(+), 1 deletion(-)

diff --git a/docs/qapi/index.rst b/docs/qapi/index.rst
index b07e6e9e2e3..d37ceac497f 100644
--- a/docs/qapi/index.rst
+++ b/docs/qapi/index.rst
@@ -103,6 +103,18 @@ Explicit cross-referencing syntax for QAPI modules is available with
    :arg baz: Missing a type.
    :feat unstable: More than unstable, this command doesn't even exist!
    :arg no-descr:
+   :arg BitmapSyncMode discrim: How about branches in commands?
+
+   .. qapi:branch:: discrim on-success
+
+      :arg str foobar: This is an argument that belongs to a tagged union branch.
+      :arg int? foobaz: This is another argument belonging to the same branch.
+
+   .. qapi:branch:: discrim never
+
+      :arg str barfoo: This is an argument that belongs to a *different* tagged union branch.
+      :arg int64 zzxyz: And this is another argument belonging to that same branch.
+
    :feat hallucination: This command is a figment of your imagination.
    :error CommandNotFound: When you try to use this command, because it
       isn't real.
@@ -190,3 +202,28 @@ Explicit cross-referencing syntax for QAPI modules is available with
      operations.  0 means unlimited.  If max-chunk is non-zero then it
      should not be less than job cluster size which is calculated as
      maximum of target image cluster size and 64k.  Default 0.
+
+.. qapi:union:: RbdEncryptionOptions
+   :since: 6.1
+
+   :memb RbdImageEncryptionFormat format: Encryption format.
+   :memb RbdEncryptionOptions? parent: Parent image encryption options
+      (for cloned images).  Can be left unspecified if this cloned image
+      is encrypted using the same format and secret as its parent image
+      (i.e. not explicitly formatted) or if its parent image is not
+      encrypted.  (Since 8.0)
+
+   .. qapi:branch:: format luks
+
+      :memb str key-secret: ID of a QCryptoSecret object providing a
+         passphrase for unlocking the encryption
+
+   .. qapi:branch:: format luks2
+
+      :memb str key-secret: ID of a QCryptoSecret object providing a
+         passphrase for unlocking the encryption
+
+   .. qapi:branch:: format luks-any
+
+      :memb str key-secret: ID of a QCryptoSecret object providing a
+         passphrase for unlocking the encryption
diff --git a/docs/sphinx/qapi-domain.py b/docs/sphinx/qapi-domain.py
index b46faeaceef..f0094300c03 100644
--- a/docs/sphinx/qapi-domain.py
+++ b/docs/sphinx/qapi-domain.py
@@ -33,7 +33,12 @@
 from sphinx.locale import _, __
 from sphinx.roles import XRefRole
 from sphinx.util import logging
-from sphinx.util.docfields import GroupedField, TypedField
+from sphinx.util.docfields import (
+    DocFieldTransformer,
+    Field,
+    GroupedField,
+    TypedField,
+)
 from sphinx.util.docutils import SphinxDirective, switch_source_input
 from sphinx.util.nodes import (
     make_id,
@@ -52,6 +57,8 @@
 
 logger = logging.getLogger(__name__)
 
+quack = cast  # O:-)
+
 
 class ObjectEntry(NamedTuple):
     docname: str
@@ -242,6 +249,30 @@ def add_target_and_index(
                     ("single", indextext, node_id, "", None)
                 )
 
+    def _merge_adjoining_field_lists(self, contentnode: addnodes.desc_content) -> None:
+        # Take any adjacent field lists and glue them together into
+        # one list for further processing by Sphinx. This is done so
+        # that field lists declared in nested directives can be
+        # flattened into non-nested field lists.
+
+        first_list = None
+        delete_queue: List[nodes.field_list] = []
+        for child in contentnode:
+            if isinstance(child, nodes.field_list):
+                if not first_list:
+                    first_list = child
+                else:
+                    first_list += child.children
+                    delete_queue.append(child)
+            else:
+                first_list = None
+
+        for child in delete_queue:
+            contentnode.remove(child)
+
+    def transform_content(self, contentnode: addnodes.desc_content) -> None:
+        self._merge_adjoining_field_lists(contentnode)
+
     def _toc_entry_name(self, sig_node: desc_signature) -> str:
         # This controls the name in the TOC and on the sidebar.
 
@@ -349,6 +380,12 @@ class QAPIStruct(QAPIObjectWithMembers):
     pass
 
 
+class QAPIUnion(QAPIObjectWithMembers):
+    """Description of a QAPI Union."""
+
+    pass
+
+
 class QAPIModule(SphinxDirective):
     """
     Directive to mark description of a new module.
@@ -439,6 +476,59 @@ def run(self) -> List[Node]:
         return ret
 
 
+class Branch(SphinxDirective):
+    """
+    Nested directive which only serves to introduce temporary
+    metadata but return its parsed content nodes unaltered otherwise.
+
+    Technically, you can put whatever you want in here, but doing so may
+    prevent proper merging of adjacent field lists.
+    """
+
+    doc_field_types: List[Field] = []
+    has_content = True
+    required_arguments = 2
+    optional_arguments = 0
+    domain = "qapi"
+
+    def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]:
+        ret = {}
+        for field in self.doc_field_types:
+            for name in field.names:
+                ret[name] = (field, False)
+
+            if field.is_typed:
+                typed_field = cast(TypedField, field)
+                for name in typed_field.typenames:
+                    ret[name] = (field, True)
+        return ret
+
+    def run(self) -> list[Node]:
+        discrim = self.arguments[0].strip()
+        value = self.arguments[1].strip()
+
+        # The label name is dynamically generated per-instance instead
+        # of per-class to incorporate the branch conditions as a label
+        # name.
+        self.doc_field_types = [
+            TypedField(
+                "branch-arg-or-memb",
+                label=f"[{discrim} = {value}]",
+                # In a branch, we don't actually use the name of the
+                # field name to generate the label; so allow either-or.
+                names=("arg", "memb"),
+            ),
+        ]
+
+        content_node: addnodes.desc_content = addnodes.desc_content()
+        _nested_parse(self, content_node)
+        # DocFieldTransformer usually expects ObjectDescription, but... quack!
+        transformer = DocFieldTransformer(quack(ObjectDescription, self))
+        transformer.transform_all(content_node)
+
+        return content_node.children
+
+
 class QAPIIndex(Index):
     """
     Index subclass to provide the QAPI definition index.
@@ -505,6 +595,7 @@ class QAPIDomain(Domain):
         "enum": ObjType(_("enum"), "enum", "obj", "type"),
         "struct": ObjType(_("struct"), "struct", "obj", "type"),
         "alternate": ObjType(_("alternate"), "alt", "obj", "type"),
+        "union": ObjType(_("union"), "union", "obj", "type"),
     }
 
     # Each of these provides a ReST directive,
@@ -516,6 +607,10 @@ class QAPIDomain(Domain):
         "enum": QAPIEnum,
         "struct": QAPIStruct,
         "alternate": QAPIAlternate,
+        "union": QAPIUnion,
+        # This is not an object in its own right;
+        # It's a directive for documenting branch members of Union types.
+        "branch": Branch,
     }
 
     # These are all cross-reference roles; e.g.
@@ -528,6 +623,7 @@ class QAPIDomain(Domain):
         "enum": QAPIXRefRole(),
         "struct": QAPIXRefRole(),
         "alt": QAPIXRefRole(),
+        "union": QAPIXRefRole(),
         "type": QAPIXRefRole(),  # reference any data type (excludes modules, commands, events)
         "obj": QAPIXRefRole(),  # reference *any* type of QAPI object.
     }
-- 
2.44.0



  parent reply	other threads:[~2024-04-19  4:42 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-19  4:37 [PATCH 00/27] Add qapi-domain Sphinx extension John Snow
2024-04-19  4:37 ` [PATCH 01/27] docs/sphinx: create QAPI domain extension stub John Snow
2024-04-19  4:37 ` [PATCH 02/27] docs/qapi-domain: add qapi:module directive John Snow
2024-04-19  4:37 ` [PATCH 03/27] docs/qapi-module: add QAPI domain object registry John Snow
2024-04-19  4:37 ` [PATCH 04/27] docs/qapi-domain: add QAPI index John Snow
2024-04-19  4:37 ` [PATCH 05/27] docs/qapi-domain: add resolve_any_xref() John Snow
2024-04-19  4:37 ` [PATCH 06/27] docs/qapi-domain: add QAPI xref roles John Snow
2024-04-19  4:37 ` [PATCH 07/27] docs/qapi-domain: add qapi:command directive John Snow
2024-04-19  4:37 ` [PATCH 08/27] docs/qapi-domain: add :since: directive option John Snow
2024-04-19  4:37 ` [PATCH 09/27] docs/qapi-domain: add "Arguments:" field lists John Snow
2024-04-19  4:37 ` [PATCH 10/27] docs/qapi-domain: add "Features:" " John Snow
2024-04-19  4:37 ` [PATCH 11/27] docs/qapi-domain: add "Errors:" " John Snow
2024-04-19  4:38 ` [PATCH 12/27] docs/qapi-domain: add "Returns:" " John Snow
2024-04-19  4:38 ` [PATCH 13/27] docs/qapi-domain: add qapi:enum directive John Snow
2024-04-19  4:38 ` [PATCH 14/27] docs/qapi-domain: add qapi:alternate directive John Snow
2024-04-19  4:38 ` [PATCH 15/27] docs/qapi-domain: add qapi:event directive John Snow
2024-04-19  4:38 ` [PATCH 16/27] docs/qapi-domain: add qapi:struct directive John Snow
2024-04-19  4:38 ` John Snow [this message]
2024-04-19  4:38 ` [PATCH 18/27] docs/qapi-domain: add :deprecated: directive option John Snow
2024-04-19  4:38 ` [PATCH 19/27] docs/qapi-domain: add :unstable: " John Snow
2024-04-19  4:38 ` [PATCH 20/27] docs/qapi-domain: add :ifcond: " John Snow
2024-04-19  4:38 ` [PATCH 21/27] docs/qapi-domain: RFC patch - add malformed field list entries John Snow
2024-04-19  4:38 ` [PATCH 22/27] docs/qapi-domain: add warnings for malformed field lists John Snow
2024-04-19  4:38 ` [PATCH 23/27] docs/qapi-domain: RFC patch - delete " John Snow
2024-04-19  4:38 ` [PATCH 24/27] docs/qapi-domain: add type cross-refs to " John Snow
2024-04-19 16:58   ` John Snow
2024-04-19  4:38 ` [PATCH 25/27] docs/qapi-domain: implement error context reporting fix John Snow
2024-04-19  4:38 ` [PATCH 26/27] docs/qapi-domain: RFC patch - Add one last sample command John Snow
2024-04-19  4:38 ` [PATCH 27/27] docs/qapi-domain: add CSS styling John Snow
2024-04-19 14:45 ` [PATCH 00/27] Add qapi-domain Sphinx extension Markus Armbruster
2024-04-19 15:10   ` Markus Armbruster
2024-04-19 16:31   ` John Snow
2024-04-22  9:19     ` Markus Armbruster
2024-04-22 16:38       ` John Snow
2024-04-23  1:56         ` John Snow
2024-04-23  7:48           ` Markus Armbruster
2024-04-23 18:32             ` John Snow
2024-04-24 14:13               ` Markus Armbruster
2024-04-23  7:19         ` Markus Armbruster

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240419043820.178731-18-jsnow@redhat.com \
    --to=jsnow@redhat.com \
    --cc=armbru@redhat.com \
    --cc=marcandre.lureau@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-devel@nongnu.org \
    --cc=victortoso@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.