Re: [PATCH 08/11] docs/qapi-domain: add namespaced index support

2025-03-14 Thread Markus Armbruster
John Snow  writes:

> Generate an index-per-namespace for the QAPI domain. Due to a limitation
> with Sphinx's architecture, these indices must be defined during setup
> time and cannot be dynamically created on-demand when a namespace
> directive is encountered.
>
> Owing to that limitation, add a configuration value to conf.py that
> specifies which QAPI namespaces we'll generate indices for.
>
> Indices will be named after their namespace, e.g. the "QMP" namespace
> will generate to "qapi-qmp-index.html" and can be referenced using
> `qapi-qmp-index`.
>
> Signed-off-by: John Snow 

I'm going to state my understanding of a few things.  Please correct
misunderstandings, if any.

Before this patch, the QAPI domain creates a single index, and it's
named "QAPI Index".

It has "everything".

This patch adds one index per namespace.  There are none, yet.  The next
patch will add namespace "QMP", and the last patch adds "QGA" and "QSD".

Each of these indexes will have exactly the stuff in that namespace.

The QAPI Index continues to have everything.

The patch adding namespace "QMP" replaces the QEMU QMP Reference
Manual's link to the QAPI Index by a link to the QMP reference (the QAPI
index is no longer linked from anywhere).  At that time, their contents
is still identical, but that stops when the last patch enables the
transmogrifier more widely, so the replacement is clearly necessary.

Since we put everything QAPI in a namespace, the QAPI index has no
entries that aren't also in a namespace's index.

Since QSD's schema is a subset of QMP's, everything in the QSD index and
also in the QMP index.

The QAPI index has everything...  does it link to QMP's copy or QSD's
copy?  I checked block-commit, and it appears to link to QMP's.




[PATCH 08/11] docs/qapi-domain: add namespaced index support

2025-03-13 Thread John Snow
Generate an index-per-namespace for the QAPI domain. Due to a limitation
with Sphinx's architecture, these indices must be defined during setup
time and cannot be dynamically created on-demand when a namespace
directive is encountered.

Owing to that limitation, add a configuration value to conf.py that
specifies which QAPI namespaces we'll generate indices for.

Indices will be named after their namespace, e.g. the "QMP" namespace
will generate to "qapi-qmp-index.html" and can be referenced using
`qapi-qmp-index`.

Signed-off-by: John Snow 
---
 docs/conf.py   |  3 +++
 docs/sphinx/qapi_domain.py | 51 +++---
 2 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/docs/conf.py b/docs/conf.py
index a3f9fa63d94..175491148c3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -161,6 +161,9 @@
 "see also",
 }
 
+# Due to a limitation in Sphinx, we need to know which indices to
+# generate in advance. Adding a namespace here allows that generation.
+qapi_namespaces = set()
 
 # -- Options for HTML output --
 
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index a8a85a2de36..c94af5719ca 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -8,11 +8,13 @@
 from __future__ import annotations
 
 import re
+import types
 from typing import (
 TYPE_CHECKING,
 List,
 NamedTuple,
 Tuple,
+Type,
 cast,
 )
 
@@ -669,6 +671,7 @@ class QAPIIndex(Index):
 name = "index"
 localname = _("QAPI Index")
 shortname = _("QAPI Index")
+namespace = ""
 
 def generate(
 self,
@@ -678,25 +681,20 @@ def generate(
 content: Dict[str, List[IndexEntry]] = {}
 collapse = False
 
-# list of all object (name, ObjectEntry) pairs, sorted by name
-# (ignoring the module)
-objects = sorted(
-self.domain.objects.items(),
-key=lambda x: x[0].split(".")[-1].lower(),
-)
-
-for objname, obj in objects:
+for objname, obj in self.domain.objects.items():
 if docnames and obj.docname not in docnames:
 continue
 
-# Strip the module name out:
-objname = objname.split(".")[-1]
+ns, _mod, name = QAPIDescription.split_fqn(objname)
+
+if self.namespace != ns:
+continue
 
 # Add an alphabetical entry:
-entries = content.setdefault(objname[0].upper(), [])
+entries = content.setdefault(name[0].upper(), [])
 entries.append(
 IndexEntry(
-objname, 0, obj.docname, obj.node_id, obj.objtype, "", ""
+name, 0, obj.docname, obj.node_id, obj.objtype, "", ""
 )
 )
 
@@ -704,10 +702,14 @@ def generate(
 category = obj.objtype.title() + "s"
 entries = content.setdefault(category, [])
 entries.append(
-IndexEntry(objname, 0, obj.docname, obj.node_id, "", "", "")
+IndexEntry(name, 0, obj.docname, obj.node_id, "", "", "")
 )
 
-# alphabetically sort categories; type names first, ABC entries last.
+# Sort entries within each category alphabetically
+for category in content:
+content[category] = sorted(content[category])
+
+# Sort the categories themselves; type names first, ABC entries last.
 sorted_content = sorted(
 content.items(),
 key=lambda x: (len(x[0]) == 1, x[0]),
@@ -780,6 +782,21 @@ def objects(self) -> Dict[str, ObjectEntry]:
 ret = self.data.setdefault("objects", {})
 return ret  # type: ignore[no-any-return]
 
+def setup(self) -> None:
+namespaces = set(self.env.app.config.qapi_namespaces)
+for namespace in namespaces:
+new_index: Type[QAPIIndex] = types.new_class(
+f"{namespace}Index", bases=(QAPIIndex,)
+)
+new_index.name = f"{namespace.lower()}-index"
+new_index.localname = _(f"{namespace} Index")
+new_index.shortname = _(f"{namespace} Index")
+new_index.namespace = namespace
+
+self.indices.append(new_index)
+
+super().setup()
+
 def note_object(
 self,
 name: str,
@@ -1019,6 +1036,12 @@ def setup(app: Sphinx) -> Dict[str, Any]:
 "env",  # Setting impacts parsing phase
 types=set,
 )
+app.add_config_value(
+"qapi_namespaces",
+set(),
+"env",
+types=set,
+)
 app.add_domain(QAPIDomain)
 
 return {
-- 
2.48.1