* [PATCH v2 01/62] do-not-merge
2025-03-09 8:31 John Snow
@ 2025-03-09 8:31 ` John Snow
0 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:31 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Eric Blake, Markus Armbruster, Michael Roth,
Thomas Huth, Daniel P. Berrangé, Alex Bennée,
Philippe Mathieu-Daudé, John Snow
Ad-hoc linting scripts to scrub down the new docs/sphinx files. Should
work with a reasonably modern mypy/pylint/etc, and Sphinx 8.2.0. Older
versions of Sphinx ought to still work at runtime, but may not type
check correctly.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi-lint.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
create mode 100755 scripts/qapi-lint.sh
diff --git a/scripts/qapi-lint.sh b/scripts/qapi-lint.sh
new file mode 100755
index 00000000000..738490352cd
--- /dev/null
+++ b/scripts/qapi-lint.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+set -e
+
+if [[ -f qapi/.flake8 ]]; then
+ echo "flake8 --config=qapi/.flake8 qapi/"
+ flake8 --config=qapi/.flake8 qapi/
+fi
+if [[ -f qapi/pylintrc ]]; then
+ echo "pylint --rcfile=qapi/pylintrc qapi/"
+ pylint --rcfile=qapi/pylintrc qapi/
+fi
+if [[ -f qapi/mypy.ini ]]; then
+ echo "mypy --config-file=qapi/mypy.ini qapi/"
+ mypy --config-file=qapi/mypy.ini qapi/
+fi
+
+if [[ -f qapi/.isort.cfg ]]; then
+ pushd qapi
+ echo "isort -c ."
+ isort -c .
+ popd
+fi
+
+if [[ -f ../docs/sphinx/qapi_domain.py ]]; then
+ files="qapi_domain.py"
+fi
+if [[ -f ../docs/sphinx/compat.py ]]; then
+ files="${files} compat.py"
+fi
+if [[ -f ../docs/sphinx/collapse.py ]]; then
+ files="${files} collapse.py"
+fi
+
+if [[ -f ../docs/sphinx/qapi_domain.py ]]; then
+ pushd ../docs/sphinx
+
+ set -x
+ mypy --strict $files
+ flake8 --max-line-length=80 $files qapidoc.py
+ isort -c $files qapidoc.py
+ black --line-length 80 --check $files qapidoc.py
+ PYTHONPATH=../../scripts/ pylint \
+ --rc-file ../../scripts/qapi/pylintrc \
+ $files qapidoc.py
+ set +x
+
+ popd
+fi
+
+pushd ../build
+#make -j13
+make check-qapi-schema
+rm -rf docs/
+#make docs
+#make sphinxdocs
+time pyvenv/bin/sphinx-build -v -j 8 -b html -d docs/manual.p/ ../docs/ docs/manual/;
+popd
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 00/62] docs: Add new QAPI transmogrifier
@ 2025-03-09 8:34 John Snow
2025-03-09 8:34 ` [PATCH v2 01/62] do-not-merge John Snow
` (62 more replies)
0 siblings, 63 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This series is a "minimum viable" version of the new QAPI documentation
system. It does the bare minimum under the new framework, saving nice
features for later.
Patches 3-31 implement the qapi_domain extension.
Patches 32-59 implement the qapidoc "Transmogrifier".
Known shortcomings in this series:
- Some ifcond information is still not displayed.
- No QAPI namespace support ... yet. So we can't enable it for QMP, QGA
and QSD simultaneously just yet. It's nearly complete.
- Didn't finish addressing all of Markus' feedback, but needed to get
another spin out on the list ASAP.
v2:
- Did you know that my computer just shut off the moment I started
sending the patchset? Just a hard off. Wow!
- Refactored QAPIObject class so that QAPIModule uses more of
Sphinx's ObjectDescription class, which means less fooling around with
re-parsing Sphinx standard options.
- Removed test document, flipped switch on the real QMP manual.
- Undocumented members have been re-added. I think that's new for this version!
- Removed some inliner branch code that snuck into the "validate info
fields" patch.
- Lots of Markus' feedback, and misc changes.
"v1":
- @foo is processed into ``foo`
- "The members of ..." messages have been temporarily re-added until we
can smooth over the inliner.
- This series runs under Sphinx 3.4.3 to Sphinx 8.2.0 inclusive. It
truly is a Christmas miracle. (please clap)
- This series now fully type checks and lint checks under Sphinx 8.2.0,
but may not type check under earlier Sphinx versions. Achieving this
alone, nevermind in conjunction with the above, was a literal
herculean labor.
John Snow (62):
do-not-merge
qapi: shush pylint up
docs/sphinx: create QAPI domain extension stub
docs/sphinx: add compat.py module and nested_parse helper
docs/qapi-domain: add QAPI domain object registry
docs/qapi-domain: add QAPI index
docs/qapi-domain: add resolve_any_xref()
docs/qapi-domain: add QAPI xref roles
docs/qapi-domain: add compatibility node classes
docs/qapi-domain: Add ObjectDescription abstract class
docs/qapi-domain: add qapi:module directive
docs/qapi-domain: add QAPIObject class
docs/qapi-domain: add qapi:command directive
docs/qapi-domain: add :since: directive option
docs/qapi-domain: add "Arguments:" field lists
docs/qapi-domain: add "Features:" field lists
docs/qapi-domain: add "Errors:" field lists
docs/qapi-domain: add "Return:" field lists
docs/qapi-domain: add qapi:enum directive
docs/qapi-domain: add qapi:alternate directive
docs/qapi-domain: add qapi:event directive
docs/qapi-domain: add qapi:object directive
docs/qapi-domain: add :deprecated: directive option
docs/qapi-domain: add :unstable: directive option
docs/qapi-domain: add :ifcond: directive option
docs/qapi-domain: add warnings for malformed field lists
docs/qapi-domain: add type cross-refs to field lists
docs/qapi-domain: add CSS styling
docs/qapi-domain: add XREF compatibility goop for Sphinx < 4.1
docs/qapi-domain: warn when QAPI domain xrefs fail to resolve
docs/qapi-domain: Fix error context reporting in Sphinx 5.x and 6.x
qapi/parser: adjust info location for doc body section
qapi: expand tags to all doc sections
qapi/schema: add __repr__ to QAPIDoc.Section
docs/qapidoc: add transmogrifier stub
docs/qapidoc: split old implementation into qapidoc_legacy.py
docs/qapidoc: Fix static typing on qapidoc.py
do-not-merge
docs/qapidoc: add transmogrifier class stub
docs/qapidoc: add visit_module() method
qapi/source: allow multi-line QAPISourceInfo advancing
docs/qapidoc: add visit_freeform() method
docs/qapidoc: add preamble() method
docs/qapidoc: add visit_paragraph() method
docs/qapidoc: add visit_errors() method
docs/qapidoc: add format_type() method
docs/qapidoc: add add_field() and generate_field() helper methods
docs/qapidoc: add visit_feature() method
docs/qapidoc: prepare to record entity being transmogrified
docs/qapidoc: add visit_returns() method
docs/qapidoc: add visit_member() method
docs/qapidoc: add visit_sections() method
docs/qapidoc: add visit_entity()
docs/qapidoc: implement transmogrify() method
docs/qapidoc: process @foo into ``foo``
docs/qapidoc: add intermediate output debugger
docs/qapidoc: Add "the members of" pointers
docs/qapidoc: generate entries for undocumented members
qapi/parser: add undocumented stub members to all_sections
docs: disambiguate cross-references
docs: enable qapidoc transmogrifier for QEMU QMP Reference
docs: add qapi-domain syntax documentation
docs/conf.py | 18 +-
docs/devel/codebase.rst | 6 +-
docs/devel/index-build.rst | 1 +
docs/devel/qapi-domain.rst | 670 +++++++++++++++++
docs/glossary.rst | 10 +-
docs/interop/qemu-qmp-ref.rst | 1 +
docs/sphinx-static/theme_overrides.css | 98 ++-
docs/sphinx/compat.py | 230 ++++++
docs/sphinx/qapi_domain.py | 926 ++++++++++++++++++++++++
docs/sphinx/qapidoc.py | 948 ++++++++++++++-----------
docs/sphinx/qapidoc_legacy.py | 440 ++++++++++++
qapi/qapi-schema.json | 2 +
scripts/qapi-lint.sh | 57 ++
scripts/qapi/backend.py | 2 +
scripts/qapi/main.py | 8 +-
scripts/qapi/parser.py | 121 +++-
scripts/qapi/source.py | 4 +-
tests/qapi-schema/doc-good.out | 10 +-
tests/qapi-schema/test-qapi.py | 2 +-
19 files changed, 3069 insertions(+), 485 deletions(-)
create mode 100644 docs/devel/qapi-domain.rst
create mode 100644 docs/sphinx/compat.py
create mode 100644 docs/sphinx/qapi_domain.py
create mode 100644 docs/sphinx/qapidoc_legacy.py
create mode 100755 scripts/qapi-lint.sh
--
2.48.1
^ permalink raw reply [flat|nested] 101+ messages in thread
* [PATCH v2 01/62] do-not-merge
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-09 8:34 ` [PATCH v2 02/62] qapi: shush pylint up John Snow
` (61 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Ad-hoc linting scripts to scrub down the new docs/sphinx files. Should
work with a reasonably modern mypy/pylint/etc, and Sphinx 8.2.0. Older
versions of Sphinx ought to still work at runtime, but may not type
check correctly.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi-lint.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
create mode 100755 scripts/qapi-lint.sh
diff --git a/scripts/qapi-lint.sh b/scripts/qapi-lint.sh
new file mode 100755
index 00000000000..738490352cd
--- /dev/null
+++ b/scripts/qapi-lint.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+set -e
+
+if [[ -f qapi/.flake8 ]]; then
+ echo "flake8 --config=qapi/.flake8 qapi/"
+ flake8 --config=qapi/.flake8 qapi/
+fi
+if [[ -f qapi/pylintrc ]]; then
+ echo "pylint --rcfile=qapi/pylintrc qapi/"
+ pylint --rcfile=qapi/pylintrc qapi/
+fi
+if [[ -f qapi/mypy.ini ]]; then
+ echo "mypy --config-file=qapi/mypy.ini qapi/"
+ mypy --config-file=qapi/mypy.ini qapi/
+fi
+
+if [[ -f qapi/.isort.cfg ]]; then
+ pushd qapi
+ echo "isort -c ."
+ isort -c .
+ popd
+fi
+
+if [[ -f ../docs/sphinx/qapi_domain.py ]]; then
+ files="qapi_domain.py"
+fi
+if [[ -f ../docs/sphinx/compat.py ]]; then
+ files="${files} compat.py"
+fi
+if [[ -f ../docs/sphinx/collapse.py ]]; then
+ files="${files} collapse.py"
+fi
+
+if [[ -f ../docs/sphinx/qapi_domain.py ]]; then
+ pushd ../docs/sphinx
+
+ set -x
+ mypy --strict $files
+ flake8 --max-line-length=80 $files qapidoc.py
+ isort -c $files qapidoc.py
+ black --line-length 80 --check $files qapidoc.py
+ PYTHONPATH=../../scripts/ pylint \
+ --rc-file ../../scripts/qapi/pylintrc \
+ $files qapidoc.py
+ set +x
+
+ popd
+fi
+
+pushd ../build
+#make -j13
+make check-qapi-schema
+rm -rf docs/
+#make docs
+#make sphinxdocs
+time pyvenv/bin/sphinx-build -v -j 8 -b html -d docs/manual.p/ ../docs/ docs/manual/;
+popd
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 02/62] qapi: shush pylint up
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
2025-03-09 8:34 ` [PATCH v2 01/62] do-not-merge John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-09 19:41 ` Markus Armbruster
2025-03-09 8:34 ` [PATCH v2 03/62] docs/sphinx: create QAPI domain extension stub John Snow
` (60 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Shhhhh!
This patch is RFC quality, I wasn't in the mood to actually solve
problems so much as I was in the mood to continue working on the Sphinx
rework. Plus, I don't think the code I am patching has hit origin/master
yet ...
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/backend.py | 2 ++
scripts/qapi/main.py | 8 +++-----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/scripts/qapi/backend.py b/scripts/qapi/backend.py
index 14e60aa67af..49ae6ecdd33 100644
--- a/scripts/qapi/backend.py
+++ b/scripts/qapi/backend.py
@@ -13,6 +13,7 @@
class QAPIBackend(ABC):
+ # pylint: disable=too-few-public-methods
@abstractmethod
def generate(self,
@@ -36,6 +37,7 @@ def generate(self,
class QAPICBackend(QAPIBackend):
+ # pylint: disable=too-few-public-methods
def generate(self,
schema: QAPISchema,
diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py
index 5b4679abcf1..01155373bd0 100644
--- a/scripts/qapi/main.py
+++ b/scripts/qapi/main.py
@@ -38,8 +38,7 @@ def create_backend(path: str) -> QAPIBackend:
try:
mod = import_module(module_path)
except Exception as ex:
- print(f"unable to import '{module_path}': {ex}", file=sys.stderr)
- sys.exit(1)
+ raise QAPIError(f"unable to import '{module_path}': {ex}") from ex
try:
klass = getattr(mod, class_name)
@@ -51,9 +50,8 @@ def create_backend(path: str) -> QAPIBackend:
try:
backend = klass()
except Exception as ex:
- print(f"backend '{path}' cannot be instantiated: {ex}",
- file=sys.stderr)
- sys.exit(1)
+ raise QAPIError(
+ f"backend '{path}' cannot be instantiated: {ex}") from ex
if not isinstance(backend, QAPIBackend):
print(f"backend '{path}' must be an instance of QAPIBackend",
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 03/62] docs/sphinx: create QAPI domain extension stub
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
2025-03-09 8:34 ` [PATCH v2 01/62] do-not-merge John Snow
2025-03-09 8:34 ` [PATCH v2 02/62] qapi: shush pylint up John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-09 8:34 ` [PATCH v2 04/62] docs/sphinx: add compat.py module and nested_parse helper John Snow
` (59 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
A Sphinx domain is a collection of directive and role extensions meant
to facilitate the documentation of a specific language. For instance,
Sphinx ships with "python" and "cpp" domains. This patch introduces a
stub for the "qapi" language domain.
Please see https://www.sphinx-doc.org/en/master/usage/domains/index.html
for more information.
This stub doesn't really do anything yet, we'll get to it brick-by-brick
in the forthcoming commits to keep the series breezy and the git history
informative.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/conf.py | 9 +++++-
docs/sphinx/qapi_domain.py | 56 ++++++++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+), 1 deletion(-)
create mode 100644 docs/sphinx/qapi_domain.py
diff --git a/docs/conf.py b/docs/conf.py
index 31bb9a37893..49d9de894c0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -60,7 +60,14 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
-extensions = ['kerneldoc', 'qmp_lexer', 'hxtool', 'depfile', 'qapidoc']
+extensions = [
+ 'depfile',
+ 'hxtool',
+ 'kerneldoc',
+ 'qapi_domain',
+ 'qapidoc',
+ 'qmp_lexer',
+]
if sphinx.version_info[:3] > (4, 0, 0):
tags.add('sphinx4')
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
new file mode 100644
index 00000000000..a1983d94440
--- /dev/null
+++ b/docs/sphinx/qapi_domain.py
@@ -0,0 +1,56 @@
+"""
+QAPI domain extension.
+"""
+
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ AbstractSet,
+ Any,
+ Dict,
+ Tuple,
+)
+
+from sphinx.domains import Domain, ObjType
+from sphinx.util import logging
+
+
+if TYPE_CHECKING:
+ from sphinx.application import Sphinx
+
+logger = logging.getLogger(__name__)
+
+
+class QAPIDomain(Domain):
+ """QAPI language domain."""
+
+ name = "qapi"
+ label = "QAPI"
+
+ object_types: Dict[str, ObjType] = {}
+ directives = {}
+ roles = {}
+ initial_data: Dict[str, Dict[str, Tuple[Any]]] = {}
+ indices = []
+
+ def merge_domaindata(
+ self, docnames: AbstractSet[str], otherdata: Dict[str, Any]
+ ) -> None:
+ pass
+
+ def resolve_any_xref(self, *args: Any, **kwargs: Any) -> Any:
+ # pylint: disable=unused-argument
+ return []
+
+
+def setup(app: Sphinx) -> Dict[str, Any]:
+ app.setup_extension("sphinx.directives")
+ app.add_domain(QAPIDomain)
+
+ return {
+ "version": "1.0",
+ "env_version": 1,
+ "parallel_read_safe": True,
+ "parallel_write_safe": True,
+ }
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 04/62] docs/sphinx: add compat.py module and nested_parse helper
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (2 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 03/62] docs/sphinx: create QAPI domain extension stub John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-09 8:34 ` [PATCH v2 05/62] docs/qapi-domain: add QAPI domain object registry John Snow
` (58 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Create a compat module that handles sphinx cross-version compatibility
issues. For the inaugural function, add a nested_parse() helper that
handles differences in line number tracking for nested directive body
parsing.
Spoilers: there are more cross-version hacks to come throughout the
series.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/compat.py | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
create mode 100644 docs/sphinx/compat.py
diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py
new file mode 100644
index 00000000000..39b859a25e3
--- /dev/null
+++ b/docs/sphinx/compat.py
@@ -0,0 +1,35 @@
+"""
+Sphinx cross-version compatibility goop
+"""
+
+from docutils.nodes import Element
+
+from sphinx.util import nodes
+from sphinx.util.docutils import SphinxDirective, switch_source_input
+
+
+def nested_parse_with_titles(
+ directive: SphinxDirective, content_node: Element
+) -> None:
+ """
+ This helper preserves error parsing context across sphinx versions.
+ """
+
+ # necessary so that the child nodes get the right source/line set
+ content_node.document = directive.state.document
+
+ try:
+ # Modern sphinx (6.2.0+) supports proper offsetting for
+ # nested parse error context management
+ nodes.nested_parse_with_titles(
+ directive.state,
+ directive.content,
+ content_node,
+ content_offset=directive.content_offset,
+ )
+ except TypeError:
+ # No content_offset argument. Fall back to SSI method.
+ with switch_source_input(directive.state, directive.content):
+ nodes.nested_parse_with_titles(
+ directive.state, directive.content, content_node
+ )
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 05/62] docs/qapi-domain: add QAPI domain object registry
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (3 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 04/62] docs/sphinx: add compat.py module and nested_parse helper John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-09 8:34 ` [PATCH v2 06/62] docs/qapi-domain: add QAPI index John Snow
` (57 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This is the first step towards QAPI domain cross-references and a QAPI
reference index.
This patch just creates the object registry, and updates the
merge_domaindata stub method now that we have actual data we may need to
merge.
Note that how to handle merge conflict resolution is unhandled, as the
Sphinx python domain itself does not handle it either. I do not know how
to intentionally trigger it, so I've left an assertion instead if it
should ever come up ...
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 77 +++++++++++++++++++++++++++++++++++++-
1 file changed, 75 insertions(+), 2 deletions(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index a1983d94440..f3ece42bc2a 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -9,10 +9,12 @@
AbstractSet,
Any,
Dict,
+ NamedTuple,
Tuple,
)
from sphinx.domains import Domain, ObjType
+from sphinx.locale import __
from sphinx.util import logging
@@ -22,22 +24,93 @@
logger = logging.getLogger(__name__)
+class ObjectEntry(NamedTuple):
+ docname: str
+ node_id: str
+ objtype: str
+ aliased: bool
+
+
class QAPIDomain(Domain):
"""QAPI language domain."""
name = "qapi"
label = "QAPI"
+ # This table associates cross-reference object types (key) with an
+ # ObjType instance, which defines the valid cross-reference roles
+ # for each object type.
+
+ # Actual table entries for module, command, event, etc will come in
+ # forthcoming commits.
object_types: Dict[str, ObjType] = {}
+
directives = {}
roles = {}
- initial_data: Dict[str, Dict[str, Tuple[Any]]] = {}
+
+ # Moved into the data property at runtime;
+ # this is the internal index of reference-able objects.
+ initial_data: Dict[str, Dict[str, Tuple[Any]]] = {
+ "objects": {}, # fullname -> ObjectEntry
+ }
+
indices = []
+ @property
+ def objects(self) -> Dict[str, ObjectEntry]:
+ ret = self.data.setdefault("objects", {})
+ return ret # type: ignore[no-any-return]
+
+ def note_object(
+ self,
+ name: str,
+ objtype: str,
+ node_id: str,
+ aliased: bool = False,
+ location: Any = None,
+ ) -> None:
+ """Note a QAPI object for cross reference."""
+ if name in self.objects:
+ other = self.objects[name]
+ if other.aliased and aliased is False:
+ # The original definition found. Override it!
+ pass
+ elif other.aliased is False and aliased:
+ # The original definition is already registered.
+ return
+ else:
+ # duplicated
+ logger.warning(
+ __(
+ "duplicate object description of %s, "
+ "other instance in %s, use :no-index: for one of them"
+ ),
+ name,
+ other.docname,
+ location=location,
+ )
+ self.objects[name] = ObjectEntry(
+ self.env.docname, node_id, objtype, aliased
+ )
+
+ def clear_doc(self, docname: str) -> None:
+ for fullname, obj in list(self.objects.items()):
+ if obj.docname == docname:
+ del self.objects[fullname]
+
def merge_domaindata(
self, docnames: AbstractSet[str], otherdata: Dict[str, Any]
) -> None:
- pass
+ for fullname, obj in otherdata["objects"].items():
+ if obj.docname in docnames:
+ # Sphinx's own python domain doesn't appear to bother to
+ # check for collisions. Assert they don't happen and
+ # we'll fix it if/when the case arises.
+ assert fullname not in self.objects, (
+ "bug - collision on merge?"
+ f" {fullname=} {obj=} {self.objects[fullname]=}"
+ )
+ self.objects[fullname] = obj
def resolve_any_xref(self, *args: Any, **kwargs: Any) -> Any:
# pylint: disable=unused-argument
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 06/62] docs/qapi-domain: add QAPI index
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (4 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 05/62] docs/qapi-domain: add QAPI domain object registry John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-09 8:34 ` [PATCH v2 07/62] docs/qapi-domain: add resolve_any_xref() John Snow
` (56 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Use the QAPI object registry to generate a special index just for QAPI
definitions. The index can show entries both by definition type and all
together, alphabetically.
The index can be linked from anywhere in the QEMU manual by using the
reference `qapi-index`.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 73 ++++++++++++++++++++++++++++++++++++--
1 file changed, 70 insertions(+), 3 deletions(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index f3ece42bc2a..3e7718d32d1 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -9,12 +9,20 @@
AbstractSet,
Any,
Dict,
+ Iterable,
+ List,
NamedTuple,
+ Optional,
Tuple,
)
-from sphinx.domains import Domain, ObjType
-from sphinx.locale import __
+from sphinx.domains import (
+ Domain,
+ Index,
+ IndexEntry,
+ ObjType,
+)
+from sphinx.locale import _, __
from sphinx.util import logging
@@ -31,6 +39,62 @@ class ObjectEntry(NamedTuple):
aliased: bool
+class QAPIIndex(Index):
+ """
+ Index subclass to provide the QAPI definition index.
+ """
+
+ # pylint: disable=too-few-public-methods
+
+ name = "index"
+ localname = _("QAPI Index")
+ shortname = _("QAPI Index")
+
+ def generate(
+ self,
+ docnames: Optional[Iterable[str]] = None,
+ ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
+ assert isinstance(self.domain, QAPIDomain)
+ 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:
+ if docnames and obj.docname not in docnames:
+ continue
+
+ # Strip the module name out:
+ objname = objname.split(".")[-1]
+
+ # Add an alphabetical entry:
+ entries = content.setdefault(objname[0].upper(), [])
+ entries.append(
+ IndexEntry(
+ objname, 0, obj.docname, obj.node_id, obj.objtype, "", ""
+ )
+ )
+
+ # Add a categorical entry:
+ category = obj.objtype.title() + "s"
+ entries = content.setdefault(category, [])
+ entries.append(
+ IndexEntry(objname, 0, obj.docname, obj.node_id, "", "", "")
+ )
+
+ # alphabetically sort categories; type names first, ABC entries last.
+ sorted_content = sorted(
+ content.items(),
+ key=lambda x: (len(x[0]) == 1, x[0]),
+ )
+ return sorted_content, collapse
+
+
class QAPIDomain(Domain):
"""QAPI language domain."""
@@ -54,7 +118,10 @@ class QAPIDomain(Domain):
"objects": {}, # fullname -> ObjectEntry
}
- indices = []
+ # Index pages to generate; each entry is an Index class.
+ indices = [
+ QAPIIndex,
+ ]
@property
def objects(self) -> Dict[str, ObjectEntry]:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 07/62] docs/qapi-domain: add resolve_any_xref()
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (5 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 06/62] docs/qapi-domain: add QAPI index John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-09 8:34 ` [PATCH v2 08/62] docs/qapi-domain: add QAPI xref roles John Snow
` (55 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add the ability to resolve cross-references using the `any`
cross-reference syntax. Adding QAPI-specific cross-reference roles will
be added in a forthcoming commit, and will share the same find_obj()
helper.
(There's less code needed for the generic cross-reference resolver, so
it comes first in this series.)
Once again, this code is based very heavily on sphinx.domains.python.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 96 ++++++++++++++++++++++++++++++++++++--
1 file changed, 93 insertions(+), 3 deletions(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 3e7718d32d1..f05c2cadf06 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -16,6 +16,9 @@
Tuple,
)
+from docutils import nodes
+
+from sphinx.addnodes import pending_xref
from sphinx.domains import (
Domain,
Index,
@@ -24,10 +27,15 @@
)
from sphinx.locale import _, __
from sphinx.util import logging
+from sphinx.util.nodes import make_refnode
if TYPE_CHECKING:
+ from docutils.nodes import Element
+
from sphinx.application import Sphinx
+ from sphinx.builders import Builder
+ from sphinx.environment import BuildEnvironment
logger = logging.getLogger(__name__)
@@ -179,9 +187,91 @@ def merge_domaindata(
)
self.objects[fullname] = obj
- def resolve_any_xref(self, *args: Any, **kwargs: Any) -> Any:
- # pylint: disable=unused-argument
- return []
+ def find_obj(
+ self, modname: str, name: str, typ: Optional[str]
+ ) -> list[tuple[str, ObjectEntry]]:
+ """
+ Find a QAPI object for "name", perhaps using the given module.
+
+ Returns a list of (name, object entry) tuples.
+
+ :param modname: The current module context (if any!)
+ under which we are searching.
+ :param name: The name of the x-ref to resolve;
+ may or may not include a leading module.
+ :param type: The role name of the x-ref we're resolving, if provided.
+ (This is absent for "any" lookups.)
+ """
+ if not name:
+ return []
+
+ names: list[str] = []
+ matches: list[tuple[str, ObjectEntry]] = []
+
+ fullname = name
+ if "." in fullname:
+ # We're searching for a fully qualified reference;
+ # ignore the contextual module.
+ pass
+ elif modname:
+ # We're searching for something from somewhere;
+ # try searching the current module first.
+ # e.g. :qapi:cmd:`query-block` or `query-block` is being searched.
+ fullname = f"{modname}.{name}"
+
+ if typ is None:
+ # type isn't specified, this is a generic xref.
+ # search *all* qapi-specific object types.
+ objtypes: List[str] = list(self.object_types)
+ else:
+ # type is specified and will be a role (e.g. obj, mod, cmd)
+ # convert this to eligible object types (e.g. command, module)
+ # using the QAPIDomain.object_types table.
+ objtypes = self.objtypes_for_role(typ, [])
+
+ if name in self.objects and self.objects[name].objtype in objtypes:
+ names = [name]
+ elif (
+ fullname in self.objects
+ and self.objects[fullname].objtype in objtypes
+ ):
+ names = [fullname]
+ else:
+ # exact match wasn't found; e.g. we are searching for
+ # `query-block` from a different (or no) module.
+ searchname = "." + name
+ names = [
+ oname
+ for oname in self.objects
+ if oname.endswith(searchname)
+ and self.objects[oname].objtype in objtypes
+ ]
+
+ matches = [(oname, self.objects[oname]) for oname in names]
+ if len(matches) > 1:
+ matches = [m for m in matches if not m[1].aliased]
+ return matches
+
+ def resolve_any_xref(
+ self,
+ env: BuildEnvironment,
+ fromdocname: str,
+ builder: Builder,
+ target: str,
+ node: pending_xref,
+ contnode: Element,
+ ) -> List[Tuple[str, nodes.reference]]:
+ results: List[Tuple[str, nodes.reference]] = []
+ matches = self.find_obj(node.get("qapi:module"), target, None)
+ for name, obj in matches:
+ rolename = self.role_for_objtype(obj.objtype)
+ assert rolename is not None
+ role = f"qapi:{rolename}"
+ refnode = make_refnode(
+ builder, fromdocname, obj.docname, obj.node_id, contnode, name
+ )
+ results.append((role, refnode))
+ return results
def setup(app: Sphinx) -> Dict[str, Any]:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 08/62] docs/qapi-domain: add QAPI xref roles
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (6 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 07/62] docs/qapi-domain: add resolve_any_xref() John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-09 8:34 ` [PATCH v2 09/62] docs/qapi-domain: add compatibility node classes John Snow
` (54 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add domain-specific cross-reference syntax. As of this commit, that
means new :qapi:any:`block-core` referencing syntax.
The :any: role will find anything registered to the QAPI domain,
including modules, commands, events, etc.
Creating the cross-references is powered by the QAPIXRefRole class;
resolving them is handled by QAPIDomain.resolve_xref().
QAPIXrefRole is based heavily on Sphinx's own PyXrefRole, with
modifications necessary for QAPI features.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 88 +++++++++++++++++++++++++++++++++++++-
1 file changed, 87 insertions(+), 1 deletion(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index f05c2cadf06..49d42c0921c 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -26,6 +26,7 @@
ObjType,
)
from sphinx.locale import _, __
+from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.nodes import make_refnode
@@ -47,6 +48,54 @@ class ObjectEntry(NamedTuple):
aliased: bool
+class QAPIXRefRole(XRefRole):
+
+ def process_link(
+ self,
+ env: BuildEnvironment,
+ refnode: Element,
+ has_explicit_title: bool,
+ title: str,
+ target: str,
+ ) -> tuple[str, str]:
+ refnode["qapi:module"] = env.ref_context.get("qapi:module")
+
+ # Cross-references that begin with a tilde adjust the title to
+ # only show the reference without a leading module, even if one
+ # was provided. This is a Sphinx-standard syntax; give it
+ # priority over QAPI-specific type markup below.
+ hide_module = False
+ if target.startswith("~"):
+ hide_module = True
+ target = target[1:]
+
+ # Type names that end with "?" are considered optional
+ # arguments and should be documented as such, but it's not
+ # part of the xref itself.
+ if target.endswith("?"):
+ refnode["qapi:optional"] = True
+ target = target[:-1]
+
+ # Type names wrapped in brackets denote lists. strip the
+ # brackets and remember to add them back later.
+ if target.startswith("[") and target.endswith("]"):
+ refnode["qapi:array"] = True
+ target = target[1:-1]
+
+ if has_explicit_title:
+ # Don't mess with the title at all if it was explicitly set.
+ # Explicit title syntax for references is e.g.
+ # :qapi:type:`target <explicit title>`
+ # and this explicit title overrides everything else here.
+ return title, target
+
+ title = target
+ if hide_module:
+ title = target.split(".")[-1]
+
+ return title, target
+
+
class QAPIIndex(Index):
"""
Index subclass to provide the QAPI definition index.
@@ -118,7 +167,13 @@ class QAPIDomain(Domain):
object_types: Dict[str, ObjType] = {}
directives = {}
- roles = {}
+
+ # These are all cross-reference roles; e.g.
+ # :qapi:cmd:`query-block`. The keys correlate to the names used in
+ # the object_types table values above.
+ roles = {
+ "any": QAPIXRefRole(), # reference *any* type of QAPI object.
+ }
# Moved into the data property at runtime;
# this is the internal index of reference-able objects.
@@ -252,6 +307,37 @@ def find_obj(
matches = [m for m in matches if not m[1].aliased]
return matches
+ def resolve_xref(
+ self,
+ env: BuildEnvironment,
+ fromdocname: str,
+ builder: Builder,
+ typ: str,
+ target: str,
+ node: pending_xref,
+ contnode: Element,
+ ) -> nodes.reference | None:
+ modname = node.get("qapi:module")
+ matches = self.find_obj(modname, target, typ)
+
+ if not matches:
+ return None
+
+ if len(matches) > 1:
+ logger.warning(
+ __("more than one target found for cross-reference %r: %s"),
+ target,
+ ", ".join(match[0] for match in matches),
+ type="ref",
+ subtype="qapi",
+ location=node,
+ )
+
+ name, obj = matches[0]
+ return make_refnode(
+ builder, fromdocname, obj.docname, obj.node_id, contnode, name
+ )
+
def resolve_any_xref(
self,
env: BuildEnvironment,
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 09/62] docs/qapi-domain: add compatibility node classes
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (7 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 08/62] docs/qapi-domain: add QAPI xref roles John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-09 8:34 ` [PATCH v2 10/62] docs/qapi-domain: Add ObjectDescription abstract class John Snow
` (53 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Sphinx prior to v4.0 uses different classes for rendering elements of
documentation objects; add some compatibility classes to use the right
node classes conditionally.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/compat.py | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py
index 39b859a25e3..6bc698c5ada 100644
--- a/docs/sphinx/compat.py
+++ b/docs/sphinx/compat.py
@@ -2,12 +2,27 @@
Sphinx cross-version compatibility goop
"""
-from docutils.nodes import Element
+from typing import Callable
+from docutils.nodes import Element, Node, Text
+
+import sphinx
+from sphinx import addnodes
from sphinx.util import nodes
from sphinx.util.docutils import SphinxDirective, switch_source_input
+SpaceNode: Callable[[str], Node]
+KeywordNode: Callable[[str, str], Node]
+
+if sphinx.version_info[:3] >= (4, 0, 0):
+ SpaceNode = addnodes.desc_sig_space
+ KeywordNode = addnodes.desc_sig_keyword
+else:
+ SpaceNode = Text
+ KeywordNode = addnodes.desc_annotation
+
+
def nested_parse_with_titles(
directive: SphinxDirective, content_node: Element
) -> None:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 10/62] docs/qapi-domain: Add ObjectDescription abstract class
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (8 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 09/62] docs/qapi-domain: add compatibility node classes John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-10 9:15 ` Markus Armbruster
2025-03-09 8:34 ` [PATCH v2 11/62] docs/qapi-domain: add qapi:module directive John Snow
` (52 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This class is a generic, top-level directive for documenting some kind
of QAPI thingamajig that we expect to go into the Index. This class
doesn't do much by itself, and it isn't yet associated with any
particular directive.
Only handle_signature() is defined in the base class; get_index_text and
add_target_and_index are new methods defined here; they are based
heavily on the layout and format of the Python domain's general object
class.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 65 ++++++++++++++++++++++++++++++++++++--
1 file changed, 63 insertions(+), 2 deletions(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 49d42c0921c..0365891f354 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -14,11 +14,13 @@
NamedTuple,
Optional,
Tuple,
+ cast,
)
from docutils import nodes
-from sphinx.addnodes import pending_xref
+from sphinx.addnodes import desc_signature, pending_xref
+from sphinx.directives import ObjectDescription
from sphinx.domains import (
Domain,
Index,
@@ -28,7 +30,7 @@
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
-from sphinx.util.nodes import make_refnode
+from sphinx.util.nodes import make_id, make_refnode
if TYPE_CHECKING:
@@ -96,6 +98,65 @@ def process_link(
return title, target
+Signature = str
+
+
+class QAPIDescription(ObjectDescription[Signature]):
+ """
+ Generic QAPI description.
+
+ Abstract class, not instantiated directly.
+ """
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> Signature:
+ # Do nothing. The return value here is the "name" of the entity
+ # being documented; for QAPI, this is the same as the
+ # "signature", which is just a name.
+
+ # Normally this method must also populate signode with nodes to
+ # render the signature; here we do nothing instead.
+ return sig
+
+ def get_index_text(self, name: Signature) -> Tuple[str, str]:
+ """Return the text for the index entry of the object."""
+
+ # NB: this is used for the global index, not the QAPI index.
+ return ("single", f"{name} (QMP {self.objtype})")
+
+ def add_target_and_index(
+ self, name: Signature, sig: str, signode: desc_signature
+ ) -> None:
+ # name is the return value of handle_signature.
+ # sig is the original, raw text argument to handle_signature.
+ # For QAPI, these are identical, currently.
+
+ assert self.objtype
+
+ # If we're documenting a module, don't include the module as
+ # part of the FQN.
+ modname = ""
+ if self.objtype != "module":
+ modname = self.options.get(
+ "module", self.env.ref_context.get("qapi:module")
+ )
+ fullname = (modname + "." if modname else "") + name
+
+ node_id = make_id(self.env, self.state.document, self.objtype, fullname)
+ signode["ids"].append(node_id)
+
+ self.state.document.note_explicit_target(signode)
+ domain = cast(QAPIDomain, self.env.get_domain("qapi"))
+ domain.note_object(fullname, self.objtype, node_id, location=signode)
+
+ if "no-index-entry" not in self.options:
+ arity, indextext = self.get_index_text(name)
+ assert self.indexnode is not None
+ if indextext:
+ self.indexnode["entries"].append(
+ (arity, indextext, node_id, "", None)
+ )
+
+
class QAPIIndex(Index):
"""
Index subclass to provide the QAPI definition index.
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 11/62] docs/qapi-domain: add qapi:module directive
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (9 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 10/62] docs/qapi-domain: Add ObjectDescription abstract class John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-10 9:47 ` Markus Armbruster
2025-03-09 8:34 ` [PATCH v2 12/62] docs/qapi-domain: add QAPIObject class John Snow
` (51 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This adds the qapi:module directive, which just notes the current module
being documented and performs a nested parse of the content block, if
present.
This code is based pretty heavily on Sphinx's PyModule directive, but
with unnecessary features excised.
For example:
.. qapi:module:: block-core
Hello, and welcome to block-core!
=================================
lorem ipsum, dolor sit amet ...
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 71 ++++++++++++++++++++++++++++++++++----
1 file changed, 65 insertions(+), 6 deletions(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 0365891f354..a445150ae59 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -19,6 +19,7 @@
from docutils import nodes
+from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.directives import ObjectDescription
from sphinx.domains import (
@@ -34,7 +35,7 @@
if TYPE_CHECKING:
- from docutils.nodes import Element
+ from docutils.nodes import Element, Node
from sphinx.application import Sphinx
from sphinx.builders import Builder
@@ -157,6 +158,60 @@ def add_target_and_index(
)
+class QAPIModule(QAPIDescription):
+ """
+ Directive to mark description of a new module.
+
+ This directive doesn't generate any special formatting, and is just
+ a pass-through for the content body. Named section titles are
+ allowed in the content body.
+
+ Use this directive to create entries for the QAPI module in the
+ global index and the qapi index; as well as to associate subsequent
+ definitions with the module they are defined in for purposes of
+ search and QAPI index organization.
+
+ :arg: The name of the module.
+ :opt no-index: Don't add cross-reference targets or index entries.
+ :opt no-typesetting: Don't render the content body (but preserve any
+ cross-reference target IDs in the squelched output.)
+
+ Example::
+
+ .. qapi:module:: block-core
+ :no-index:
+ :no-typesetting:
+
+ Lorem ipsum, dolor sit amet ...
+ """
+
+ def run(self) -> List[Node]:
+ modname = self.arguments[0].strip()
+ self.env.ref_context["qapi:module"] = modname
+ ret = super().run()
+
+ # ObjectDescription always creates a visible signature bar. We
+ # want module items to be "invisible", however.
+
+ # Extract the content body of the directive:
+ assert isinstance(ret[-1], addnodes.desc)
+ desc_node = ret.pop(-1)
+ assert isinstance(desc_node.children[1], addnodes.desc_content)
+ ret.extend(desc_node.children[1].children)
+
+ # Re-home node_ids so anchor refs still work:
+ node_ids: List[str]
+ if node_ids := [
+ node_id
+ for el in desc_node.children[0].traverse(nodes.Element)
+ for node_id in cast(List[str], el.get("ids", ()))
+ ]:
+ target_node = nodes.target(ids=node_ids)
+ ret.insert(1, target_node)
+
+ return ret
+
+
class QAPIIndex(Index):
"""
Index subclass to provide the QAPI definition index.
@@ -222,17 +277,21 @@ class QAPIDomain(Domain):
# This table associates cross-reference object types (key) with an
# ObjType instance, which defines the valid cross-reference roles
# for each object type.
+ object_types: Dict[str, ObjType] = {
+ "module": ObjType(_("module"), "mod", "any"),
+ }
- # Actual table entries for module, command, event, etc will come in
- # forthcoming commits.
- object_types: Dict[str, ObjType] = {}
-
- directives = {}
+ # Each of these provides a rST directive,
+ # e.g. .. qapi:module:: block-core
+ directives = {
+ "module": QAPIModule,
+ }
# These are all cross-reference roles; e.g.
# :qapi:cmd:`query-block`. The keys correlate to the names used in
# the object_types table values above.
roles = {
+ "mod": QAPIXRefRole(),
"any": QAPIXRefRole(), # reference *any* type of QAPI object.
}
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 12/62] docs/qapi-domain: add QAPIObject class
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (10 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 11/62] docs/qapi-domain: add qapi:module directive John Snow
@ 2025-03-09 8:34 ` John Snow
2025-03-10 9:46 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 13/62] docs/qapi-domain: add qapi:command directive John Snow
` (50 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:34 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 95 ++++++++++++++++++++++++++++++++++++++
1 file changed, 95 insertions(+)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index a445150ae59..183d9a2766f 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -18,7 +18,9 @@
)
from docutils import nodes
+from docutils.parsers.rst import directives
+from compat import KeywordNode, SpaceNode
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.directives import ObjectDescription
@@ -40,6 +42,7 @@
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
+ from sphinx.util.typing import OptionSpec
logger = logging.getLogger(__name__)
@@ -99,6 +102,8 @@ def process_link(
return title, target
+# Alias for the return of handle_signature(), which is used in several places.
+# (In the Python domain, this is Tuple[str, str] instead.)
Signature = str
@@ -158,6 +163,96 @@ def add_target_and_index(
)
+class QAPIObject(QAPIDescription):
+ """
+ Description of a generic QAPI object.
+
+ It's not used directly, but is instead subclassed by specific directives.
+ """
+
+ # Inherit some standard options from Sphinx's ObjectDescription
+ option_spec: OptionSpec = ( # type:ignore[misc]
+ ObjectDescription.option_spec.copy()
+ )
+ option_spec.update(
+ {
+ # Borrowed from the Python domain:
+ "module": directives.unchanged, # Override contextual module name
+ }
+ )
+
+ def get_signature_prefix(self) -> List[nodes.Node]:
+ """Returns a prefix to put before the object name in the signature."""
+ assert self.objtype
+ return [
+ KeywordNode("", self.objtype.title()),
+ SpaceNode(" "),
+ ]
+
+ def get_signature_suffix(self) -> List[nodes.Node]:
+ """Returns a suffix to put after the object name in the signature."""
+ return []
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> Signature:
+ """
+ Transform a QAPI definition name into RST nodes.
+
+ This method was originally intended for handling function
+ signatures. In the QAPI domain, however, we only pass the
+ definition name as the directive argument and handle everything
+ else in the content body with field lists.
+
+ As such, the only argument here is "sig", which is just the QAPI
+ definition name.
+ """
+ modname = self.options.get(
+ "module", self.env.ref_context.get("qapi:module")
+ )
+
+ signode["fullname"] = sig
+ signode["module"] = modname
+ sig_prefix = self.get_signature_prefix()
+ if sig_prefix:
+ signode += addnodes.desc_annotation(
+ str(sig_prefix), "", *sig_prefix
+ )
+ signode += addnodes.desc_name(sig, sig)
+ signode += self.get_signature_suffix()
+
+ return sig
+
+ def _object_hierarchy_parts(
+ self, sig_node: desc_signature
+ ) -> Tuple[str, ...]:
+ if "fullname" not in sig_node:
+ return ()
+ modname = sig_node.get("module")
+ fullname = sig_node["fullname"]
+
+ if modname:
+ return (modname, *fullname.split("."))
+
+ return tuple(fullname.split("."))
+
+ def _toc_entry_name(self, sig_node: desc_signature) -> str:
+ # This controls the name in the TOC and on the sidebar.
+
+ # This is the return type of _object_hierarchy_parts().
+ toc_parts = cast(Tuple[str, ...], sig_node.get("_toc_parts", ()))
+ if not toc_parts:
+ return ""
+
+ config = self.env.app.config
+ *parents, name = toc_parts
+ if config.toc_object_entries_show_parents == "domain":
+ return sig_node.get("fullname", name)
+ if config.toc_object_entries_show_parents == "hide":
+ return name
+ if config.toc_object_entries_show_parents == "all":
+ return ".".join(parents + [name])
+ return ""
+
+
class QAPIModule(QAPIDescription):
"""
Directive to mark description of a new module.
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 13/62] docs/qapi-domain: add qapi:command directive
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (11 preceding siblings ...)
2025-03-09 8:34 ` [PATCH v2 12/62] docs/qapi-domain: add QAPIObject class John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 14/62] docs/qapi-domain: add :since: directive option John Snow
` (49 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This commit adds a stubbed version of QAPICommand that utilizes the
QAPIObject class, the qapi:command directive, the :qapi:cmd:
cross-reference role, and the "command" object type in the QAPI object
registry.
They don't do anything *particularly* interesting yet, but that will
come in forthcoming commits.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 183d9a2766f..b3928b1b0cf 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -253,6 +253,12 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str:
return ""
+class QAPICommand(QAPIObject):
+ """Description of a QAPI Command."""
+
+ # Nothing unique for now! Changed in later commits O:-)
+
+
class QAPIModule(QAPIDescription):
"""
Directive to mark description of a new module.
@@ -374,12 +380,14 @@ class QAPIDomain(Domain):
# for each object type.
object_types: Dict[str, ObjType] = {
"module": ObjType(_("module"), "mod", "any"),
+ "command": ObjType(_("command"), "cmd", "any"),
}
# Each of these provides a rST directive,
# e.g. .. qapi:module:: block-core
directives = {
"module": QAPIModule,
+ "command": QAPICommand,
}
# These are all cross-reference roles; e.g.
@@ -387,6 +395,7 @@ class QAPIDomain(Domain):
# the object_types table values above.
roles = {
"mod": QAPIXRefRole(),
+ "cmd": QAPIXRefRole(),
"any": QAPIXRefRole(), # reference *any* type of QAPI object.
}
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 14/62] docs/qapi-domain: add :since: directive option
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (12 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 13/62] docs/qapi-domain: add qapi:command directive John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 15/62] docs/qapi-domain: add "Arguments:" field lists John Snow
` (48 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add a little special markup for registering "Since:" information. Adding
it as an option instead of generic content lets us hoist the information
into the Signature bar, optionally put it in the index, etc.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index b3928b1b0cf..51a15714bf0 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -178,6 +178,8 @@ class QAPIObject(QAPIDescription):
{
# Borrowed from the Python domain:
"module": directives.unchanged, # Override contextual module name
+ # These are QAPI originals:
+ "since": directives.unchanged,
}
)
@@ -191,7 +193,17 @@ def get_signature_prefix(self) -> List[nodes.Node]:
def get_signature_suffix(self) -> List[nodes.Node]:
"""Returns a suffix to put after the object name in the signature."""
- return []
+ ret: List[nodes.Node] = []
+
+ if "since" in self.options:
+ ret += [
+ SpaceNode(" "),
+ addnodes.desc_sig_element(
+ "", f"(Since: {self.options['since']})"
+ ),
+ ]
+
+ return ret
def handle_signature(self, sig: str, signode: desc_signature) -> Signature:
"""
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 15/62] docs/qapi-domain: add "Arguments:" field lists
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (13 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 14/62] docs/qapi-domain: add :since: directive option John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 20:37 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 16/62] docs/qapi-domain: add "Features:" " John Snow
` (47 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This adds special rendering for Sphinx's typed field lists.
This patch does not add any QAPI-aware markup, rendering, or
cross-referencing for the type names, yet. That feature requires a
subclass to TypedField which will happen in its own commit quite a bit
later in this series; after all the basic fields and objects have been
established first.
The syntax for this field is:
:arg type name: description
description cont'd
You can omit the type or the description, but you cannot omit the name
-- if you do so, it degenerates into a "normal field list" entry, and
probably isn't what you want.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 51a15714bf0..c0a1a1f9ee8 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -33,6 +33,7 @@
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
+from sphinx.util.docfields import TypedField
from sphinx.util.nodes import make_id, make_refnode
@@ -268,7 +269,18 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str:
class QAPICommand(QAPIObject):
"""Description of a QAPI Command."""
- # Nothing unique for now! Changed in later commits O:-)
+ doc_field_types = QAPIObject.doc_field_types.copy()
+ doc_field_types.extend(
+ [
+ # :arg TypeName ArgName: descr
+ TypedField(
+ "argument",
+ label=_("Arguments"),
+ names=("arg",),
+ can_collapse=False,
+ ),
+ ]
+ )
class QAPIModule(QAPIDescription):
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 16/62] docs/qapi-domain: add "Features:" field lists
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (14 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 15/62] docs/qapi-domain: add "Arguments:" field lists John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 20:42 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 17/62] docs/qapi-domain: add "Errors:" " John Snow
` (46 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add support for Features field lists. There is no QAPI-specific
functionality here, but this could be changed if desired (if we wanted
the feature names to link somewhere, for instance.)
This feature list doesn't have any restrictions, so it can be used to
document object-wide features or per-member features as deemed
appropriate. It's essentially free-form text.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index c0a1a1f9ee8..21d487514ec 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -33,7 +33,7 @@
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
-from sphinx.util.docfields import TypedField
+from sphinx.util.docfields import GroupedField, TypedField
from sphinx.util.nodes import make_id, make_refnode
@@ -184,6 +184,16 @@ class QAPIObject(QAPIDescription):
}
)
+ doc_field_types = [
+ # :feat name: descr
+ GroupedField(
+ "feature",
+ label=_("Features"),
+ names=("feat",),
+ can_collapse=False,
+ ),
+ ]
+
def get_signature_prefix(self) -> List[nodes.Node]:
"""Returns a prefix to put before the object name in the signature."""
assert self.objtype
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 17/62] docs/qapi-domain: add "Errors:" field lists
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (15 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 16/62] docs/qapi-domain: add "Features:" " John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 18/62] docs/qapi-domain: add "Return:" " John Snow
` (45 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
``:error: descr`` can now be used to document error conditions. The
format of the description is not defined here; so the ability to name
specific types is left to the document writer.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 21d487514ec..d018fcd4f7c 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -33,7 +33,7 @@
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 Field, GroupedField, TypedField
from sphinx.util.nodes import make_id, make_refnode
@@ -289,6 +289,13 @@ class QAPICommand(QAPIObject):
names=("arg",),
can_collapse=False,
),
+ # :error: descr
+ Field(
+ "error",
+ label=_("Errors"),
+ names=("error", "errors"),
+ has_arg=False,
+ ),
]
)
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 18/62] docs/qapi-domain: add "Return:" field lists
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (16 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 17/62] docs/qapi-domain: add "Errors:" " John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 20:46 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 19/62] docs/qapi-domain: add qapi:enum directive John Snow
` (44 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add "Return:" field list syntax to QAPI Commands.
Like "Arguments:" and "Errors:", the type name isn't currently processed
for cross-referencing, but this will be addressed in a forthcoming
commit.
This patch adds "Return" as a GroupedField, which means that multiple
return values can be annotated - this is only done because Sphinx does
not seemingly (Maybe I missed it?) support mandatory type arguments to
Ungrouped fields. Because we want to cross-reference this type
information later, we want to make the type argument mandatory. As a
result, you can technically add multiple :return: fields, though I'm not
aware of any circumstance in which you'd need or want
to. Recommendation: "Don't do that, then."
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index d018fcd4f7c..8a364ff75ea 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -296,6 +296,13 @@ class QAPICommand(QAPIObject):
names=("error", "errors"),
has_arg=False,
),
+ # :returns TypeName: descr
+ GroupedField(
+ "returnvalue",
+ label=_("Return"),
+ names=("return",),
+ can_collapse=True,
+ ),
]
)
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 19/62] docs/qapi-domain: add qapi:enum directive
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (17 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 18/62] docs/qapi-domain: add "Return:" " John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 20/62] docs/qapi-domain: add qapi:alternate directive John Snow
` (43 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add the .. qapi:enum:: directive, object, and :qapi:enum:`name`
cross-reference role.
Add the :value name: field list for documenting Enum values.
Of note, also introduce a new "type" role that is intended to be used by
other QAPI object directives to cross-reference arbitrary QAPI type
names, but will exclude commands, events, and modules from
consideration.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 8a364ff75ea..0eb8c6fce55 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -307,6 +307,23 @@ class QAPICommand(QAPIObject):
)
+class QAPIEnum(QAPIObject):
+ """Description of a QAPI Enum."""
+
+ doc_field_types = QAPIObject.doc_field_types.copy()
+ doc_field_types.extend(
+ [
+ # :value name: descr
+ GroupedField(
+ "value",
+ label=_("Values"),
+ names=("value",),
+ can_collapse=False,
+ )
+ ]
+ )
+
+
class QAPIModule(QAPIDescription):
"""
Directive to mark description of a new module.
@@ -426,9 +443,14 @@ class QAPIDomain(Domain):
# This table associates cross-reference object types (key) with an
# ObjType instance, which defines the valid cross-reference roles
# for each object type.
+ #
+ # e.g., the :qapi:type: cross-reference role can refer to enum,
+ # struct, union, or alternate objects; but :qapi:obj: can refer to
+ # anything. Each object also gets its own targeted cross-reference role.
object_types: Dict[str, ObjType] = {
"module": ObjType(_("module"), "mod", "any"),
"command": ObjType(_("command"), "cmd", "any"),
+ "enum": ObjType(_("enum"), "enum", "type", "any"),
}
# Each of these provides a rST directive,
@@ -436,6 +458,7 @@ class QAPIDomain(Domain):
directives = {
"module": QAPIModule,
"command": QAPICommand,
+ "enum": QAPIEnum,
}
# These are all cross-reference roles; e.g.
@@ -444,6 +467,9 @@ class QAPIDomain(Domain):
roles = {
"mod": QAPIXRefRole(),
"cmd": QAPIXRefRole(),
+ "enum": QAPIXRefRole(),
+ # reference any data type (excludes modules, commands, events)
+ "type": QAPIXRefRole(),
"any": QAPIXRefRole(), # reference *any* type of QAPI object.
}
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 20/62] docs/qapi-domain: add qapi:alternate directive
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (18 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 19/62] docs/qapi-domain: add qapi:enum directive John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 21/62] docs/qapi-domain: add qapi:event directive John Snow
` (42 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add the .. qapi:alternate:: directive, object, and qapi:alt:`name`
cross-reference role.
Add the "Alternatives:" field list for describing alternate choices. Like
other field lists that reference QAPI types, a forthcoming commit will
add cross-referencing support to this field.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 0eb8c6fce55..8b9055c6b5b 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -324,6 +324,23 @@ class QAPIEnum(QAPIObject):
)
+class QAPIAlternate(QAPIObject):
+ """Description of a QAPI Alternate."""
+
+ doc_field_types = QAPIObject.doc_field_types.copy()
+ doc_field_types.extend(
+ [
+ # :alt type name: descr
+ TypedField(
+ "alternative",
+ label=_("Alternatives"),
+ names=("alt",),
+ can_collapse=False,
+ ),
+ ]
+ )
+
+
class QAPIModule(QAPIDescription):
"""
Directive to mark description of a new module.
@@ -451,6 +468,7 @@ class QAPIDomain(Domain):
"module": ObjType(_("module"), "mod", "any"),
"command": ObjType(_("command"), "cmd", "any"),
"enum": ObjType(_("enum"), "enum", "type", "any"),
+ "alternate": ObjType(_("alternate"), "alt", "type", "any"),
}
# Each of these provides a rST directive,
@@ -459,6 +477,7 @@ class QAPIDomain(Domain):
"module": QAPIModule,
"command": QAPICommand,
"enum": QAPIEnum,
+ "alternate": QAPIAlternate,
}
# These are all cross-reference roles; e.g.
@@ -468,6 +487,7 @@ class QAPIDomain(Domain):
"mod": QAPIXRefRole(),
"cmd": QAPIXRefRole(),
"enum": QAPIXRefRole(),
+ "alt": QAPIXRefRole(),
# reference any data type (excludes modules, commands, events)
"type": QAPIXRefRole(),
"any": QAPIXRefRole(), # reference *any* type of QAPI object.
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 21/62] docs/qapi-domain: add qapi:event directive
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (19 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 20/62] docs/qapi-domain: add qapi:alternate directive John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 22/62] docs/qapi-domain: add qapi:object directive John Snow
` (41 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Adds the .. qapi:event:: directive, object, and :qapi:event:`name`
cross-referencing role.
Adds the :memb type name: field list syntax for documenting event data
members. As this syntax and phrasing will be shared with Structs and
Unions as well, add the field list definition to a shared abstract
class.
As per usual, QAPI cross-referencing for types in the member field list
will be added in a forthcoming commit.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 8b9055c6b5b..e5beae1a528 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -341,6 +341,27 @@ class QAPIAlternate(QAPIObject):
)
+class QAPIObjectWithMembers(QAPIObject):
+ """Base class for Events/Structs/Unions"""
+
+ doc_field_types = QAPIObject.doc_field_types.copy()
+ doc_field_types.extend(
+ [
+ # :member type name: descr
+ TypedField(
+ "member",
+ label=_("Members"),
+ names=("memb",),
+ can_collapse=False,
+ ),
+ ]
+ )
+
+
+class QAPIEvent(QAPIObjectWithMembers):
+ """Description of a QAPI Event."""
+
+
class QAPIModule(QAPIDescription):
"""
Directive to mark description of a new module.
@@ -467,6 +488,7 @@ class QAPIDomain(Domain):
object_types: Dict[str, ObjType] = {
"module": ObjType(_("module"), "mod", "any"),
"command": ObjType(_("command"), "cmd", "any"),
+ "event": ObjType(_("event"), "event", "any"),
"enum": ObjType(_("enum"), "enum", "type", "any"),
"alternate": ObjType(_("alternate"), "alt", "type", "any"),
}
@@ -476,6 +498,7 @@ class QAPIDomain(Domain):
directives = {
"module": QAPIModule,
"command": QAPICommand,
+ "event": QAPIEvent,
"enum": QAPIEnum,
"alternate": QAPIAlternate,
}
@@ -486,6 +509,7 @@ class QAPIDomain(Domain):
roles = {
"mod": QAPIXRefRole(),
"cmd": QAPIXRefRole(),
+ "event": QAPIXRefRole(),
"enum": QAPIXRefRole(),
"alt": QAPIXRefRole(),
# reference any data type (excludes modules, commands, events)
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 22/62] docs/qapi-domain: add qapi:object directive
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (20 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 21/62] docs/qapi-domain: add qapi:event directive John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 23/62] docs/qapi-domain: add :deprecated: directive option John Snow
` (40 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Adds the .. qapi:object:: directive, object, and :qapi:obj:`name`
cross-referencing role. This directive is meant to document both structs
and unions.
As per usual, QAPI cross-referencing for types in the member field list
will be added in a forthcoming commit.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index e5beae1a528..e84affaaec2 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -362,6 +362,10 @@ class QAPIEvent(QAPIObjectWithMembers):
"""Description of a QAPI Event."""
+class QAPIJSONObject(QAPIObjectWithMembers):
+ """Description of a QAPI Object: structs and unions."""
+
+
class QAPIModule(QAPIDescription):
"""
Directive to mark description of a new module.
@@ -490,6 +494,7 @@ class QAPIDomain(Domain):
"command": ObjType(_("command"), "cmd", "any"),
"event": ObjType(_("event"), "event", "any"),
"enum": ObjType(_("enum"), "enum", "type", "any"),
+ "object": ObjType(_("object"), "obj", "type", "any"),
"alternate": ObjType(_("alternate"), "alt", "type", "any"),
}
@@ -500,6 +505,7 @@ class QAPIDomain(Domain):
"command": QAPICommand,
"event": QAPIEvent,
"enum": QAPIEnum,
+ "object": QAPIJSONObject,
"alternate": QAPIAlternate,
}
@@ -511,6 +517,7 @@ class QAPIDomain(Domain):
"cmd": QAPIXRefRole(),
"event": QAPIXRefRole(),
"enum": QAPIXRefRole(),
+ "obj": QAPIXRefRole(), # specifically structs and unions.
"alt": QAPIXRefRole(),
# reference any data type (excludes modules, commands, events)
"type": QAPIXRefRole(),
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 23/62] docs/qapi-domain: add :deprecated: directive option
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (21 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 22/62] docs/qapi-domain: add qapi:object directive John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 20:51 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 24/62] docs/qapi-domain: add :unstable: " John Snow
` (39 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow, Harmonie Snow
Although "deprecated" is a feature (and *will* appear in the features
list), add a special :deprecated: option to generate an eye-catch that
makes this information very hard to miss.
(The intent is to modify qapidoc.py to add this option whenever it
detects that the features list attached to a definition contains the
"deprecated" entry.)
-
RFC: Technically, this object-level option is un-needed and could be
replaced with a standard content-level directive that e.g. qapidoc.py
could insert at the beginning of the content block. I've done it here as
an option to demonstrate how it would be possible to do.
It's a matter of taste for "where" we feel like implementing it.
One benefit of doing it this way is that we can create a single
containing box to set CSS style options controlling the flow of multiple
infoboxes. The other way to achieve that would be to create a directive
that allows us to set multiple options instead, e.g.:
.. qapi:infoboxes:: deprecated unstable
or possibly:
.. qapi:infoboxes::
:deprecated:
:unstable:
For now, I've left these as top-level QAPI object options. "Hey, it works."
P.S., I outsourced the CSS ;)
Signed-off-by: Harmonie Snow <harmonie@gmail.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx-static/theme_overrides.css | 25 +++++++++++++++++++++++++
docs/sphinx/qapi_domain.py | 26 ++++++++++++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css
index 965ecac54fd..3765cab1b20 100644
--- a/docs/sphinx-static/theme_overrides.css
+++ b/docs/sphinx-static/theme_overrides.css
@@ -208,3 +208,28 @@ div[class^="highlight"] pre {
color: inherit;
}
}
+
+/* QAPI domain theming */
+
+.qapi-infopips {
+ margin-bottom: 1em;
+}
+
+.qapi-infopip {
+ display: inline-block;
+ padding: 0em 0.5em 0em 0.5em;
+ margin: 0.25em;
+}
+
+.qapi-deprecated {
+ background-color: #fffef5;
+ border: solid #fff176 6px;
+ font-weight: bold;
+ padding: 8px;
+ border-radius: 15px;
+ margin: 5px;
+}
+
+.qapi-deprecated::before {
+ content: '⚠️ ';
+}
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index e84affaaec2..482f4bcde3b 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -181,6 +181,7 @@ class QAPIObject(QAPIDescription):
"module": directives.unchanged, # Override contextual module name
# These are QAPI originals:
"since": directives.unchanged,
+ "deprecated": directives.flag,
}
)
@@ -257,6 +258,31 @@ def _object_hierarchy_parts(
return tuple(fullname.split("."))
+ def _add_infopips(self, contentnode: addnodes.desc_content) -> None:
+ # Add various eye-catches and things that go below the signature
+ # bar, but precede the user-defined content.
+ infopips = nodes.container()
+ infopips.attributes["classes"].append("qapi-infopips")
+
+ def _add_pip(source: str, content: str, classname: str) -> None:
+ node = nodes.container(source)
+ node.append(nodes.Text(content))
+ node.attributes["classes"].extend(["qapi-infopip", classname])
+ infopips.append(node)
+
+ if "deprecated" in self.options:
+ _add_pip(
+ ":deprecated:",
+ f"This {self.objtype} is deprecated.",
+ "qapi-deprecated",
+ )
+
+ if infopips.children:
+ contentnode.insert(0, infopips)
+
+ def transform_content(self, content_node: addnodes.desc_content) -> None:
+ self._add_infopips(content_node)
+
def _toc_entry_name(self, sig_node: desc_signature) -> str:
# This controls the name in the TOC and on the sidebar.
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 24/62] docs/qapi-domain: add :unstable: directive option
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (22 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 23/62] docs/qapi-domain: add :deprecated: directive option John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-10 9:54 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 25/62] docs/qapi-domain: add :ifcond: " John Snow
` (38 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow, Harmonie Snow
Although "unstable" is a feature (and *will* appear in the features
list), add a special :unstable: option to generate an eye-catch that
makes this information very hard to miss.
(The intent is to modify qapidoc.py to add this option whenever it
detects that the features list attached to a definition contains the
"unstable" entry.)
RFC: Same comments as last patch.
Signed-off-by: Harmonie Snow <harmonie@gmail.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx-static/theme_overrides.css | 6 +++++-
docs/sphinx/qapi_domain.py | 8 ++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css
index 3765cab1b20..5f58f1d5246 100644
--- a/docs/sphinx-static/theme_overrides.css
+++ b/docs/sphinx-static/theme_overrides.css
@@ -221,7 +221,7 @@ div[class^="highlight"] pre {
margin: 0.25em;
}
-.qapi-deprecated {
+.qapi-deprecated,.qapi-unstable {
background-color: #fffef5;
border: solid #fff176 6px;
font-weight: bold;
@@ -230,6 +230,10 @@ div[class^="highlight"] pre {
margin: 5px;
}
+.qapi-unstable::before {
+ content: '🚧 ';
+}
+
.qapi-deprecated::before {
content: '⚠️ ';
}
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 482f4bcde3b..4fc9608e170 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -182,6 +182,7 @@ class QAPIObject(QAPIDescription):
# These are QAPI originals:
"since": directives.unchanged,
"deprecated": directives.flag,
+ "unstable": directives.flag,
}
)
@@ -277,6 +278,13 @@ def _add_pip(source: str, content: str, classname: str) -> None:
"qapi-deprecated",
)
+ if "unstable" in self.options:
+ _add_pip(
+ ":unstable:",
+ f"This {self.objtype} is unstable/experimental.",
+ "qapi-unstable",
+ )
+
if infopips.children:
contentnode.insert(0, infopips)
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 25/62] docs/qapi-domain: add :ifcond: directive option
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (23 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 24/62] docs/qapi-domain: add :unstable: " John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 26/62] docs/qapi-domain: add warnings for malformed field lists John Snow
` (37 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow, Harmonie Snow
Add a special :ifcond: option that allows us to annotate the
definition-level conditionals.
The syntax of the argument is currently undefined, but it's possible we
can apply better formatting in the future. Currently, we just display
the ifcond string as preformatted text.
Signed-off-by: Harmonie Snow <harmonie@gmail.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx-static/theme_overrides.css | 13 +++++++++++++
docs/sphinx/qapi_domain.py | 23 +++++++++++++++++++++--
2 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css
index 5f58f1d5246..3fd326613d9 100644
--- a/docs/sphinx-static/theme_overrides.css
+++ b/docs/sphinx-static/theme_overrides.css
@@ -237,3 +237,16 @@ div[class^="highlight"] pre {
.qapi-deprecated::before {
content: '⚠️ ';
}
+
+.qapi-ifcond::before {
+ /* gaze ye into the crystal ball to determine feature availability */
+ content: '🔮 ';
+}
+
+.qapi-ifcond {
+ background-color: #f9f5ff;
+ border: solid #dac2ff 6px;
+ padding: 8px;
+ border-radius: 15px;
+ margin: 5px;
+}
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 4fc9608e170..6f599495e45 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -14,6 +14,7 @@
NamedTuple,
Optional,
Tuple,
+ Union,
cast,
)
@@ -181,6 +182,7 @@ class QAPIObject(QAPIDescription):
"module": directives.unchanged, # Override contextual module name
# These are QAPI originals:
"since": directives.unchanged,
+ "ifcond": directives.unchanged,
"deprecated": directives.flag,
"unstable": directives.flag,
}
@@ -265,9 +267,14 @@ def _add_infopips(self, contentnode: addnodes.desc_content) -> None:
infopips = nodes.container()
infopips.attributes["classes"].append("qapi-infopips")
- def _add_pip(source: str, content: str, classname: str) -> None:
+ def _add_pip(
+ source: str, content: Union[str, List[nodes.Node]], classname: str
+ ) -> None:
node = nodes.container(source)
- node.append(nodes.Text(content))
+ if isinstance(content, str):
+ node.append(nodes.Text(content))
+ else:
+ node.extend(content)
node.attributes["classes"].extend(["qapi-infopip", classname])
infopips.append(node)
@@ -285,6 +292,18 @@ def _add_pip(source: str, content: str, classname: str) -> None:
"qapi-unstable",
)
+ if self.options.get("ifcond", ""):
+ ifcond = self.options["ifcond"]
+ _add_pip(
+ f":ifcond: {ifcond}",
+ [
+ nodes.emphasis("", "Availability"),
+ nodes.Text(": "),
+ nodes.literal(ifcond, ifcond),
+ ],
+ "qapi-ifcond",
+ )
+
if infopips.children:
contentnode.insert(0, infopips)
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 26/62] docs/qapi-domain: add warnings for malformed field lists
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (24 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 25/62] docs/qapi-domain: add :ifcond: " John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 27/62] docs/qapi-domain: add type cross-refs to " John Snow
` (36 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Normally, Sphinx will silently fall back to its standard field list
processing if it doesn't match one of your defined fields. A lot of the
time, that's not what we want - we want to be warned if we goof
something up.
For instance, the canonical argument field list form is:
:arg type name: descr
This form is captured by Sphinx and transformed so that the field label
will become "Arguments:". It's possible to omit the type name and descr
and still have it be processed correctly. However, if you omit the type
name, Sphinx no longer recognizes it:
:arg: this is not recognized.
This will turn into an arbitrary field list entry whose label is "Arg:",
and it otherwise silently fails. You may also see failures for doing
things like using :values: instead of :value:, or :errors: instead of
:error:, and so on. It's also case sensitive, and easy to trip up.
Add a validator that guarantees all field list entries that are the
direct child of an ObjectDescription use only recognized forms of field
lists, and emit a warning (treated as error by default in most build
configurations) whenever we detect one that is goofed up.
However, there's still benefit to allowing arbitrary fields -- they are
after all not a Sphinx invention, but perfectly normal docutils
syntax. Create an allow list for known spellings we don't mind letting
through, but warn against anything else.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/conf.py | 9 +++++
docs/sphinx/qapi_domain.py | 74 ++++++++++++++++++++++++++++++++++++++
2 files changed, 83 insertions(+)
diff --git a/docs/conf.py b/docs/conf.py
index 49d9de894c0..a3f9fa63d94 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -153,6 +153,15 @@
with open(os.path.join(qemu_docdir, 'defs.rst.inc')) as f:
rst_epilog += f.read()
+
+# Normally, the QAPI domain is picky about what field lists you use to
+# describe a QAPI entity. If you'd like to use arbitrary additional
+# fields in source documentation, add them here.
+qapi_allowed_fields = {
+ "see also",
+}
+
+
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 6f599495e45..659b57d1b33 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -49,6 +49,19 @@
logger = logging.getLogger(__name__)
+def _unpack_field(
+ field: nodes.Node,
+) -> Tuple[nodes.field_name, nodes.field_body]:
+ """
+ docutils helper: unpack a field node in a type-safe manner.
+ """
+ assert isinstance(field, nodes.field)
+ assert len(field.children) == 2
+ assert isinstance(field.children[0], nodes.field_name)
+ assert isinstance(field.children[1], nodes.field_body)
+ return (field.children[0], field.children[1])
+
+
class ObjectEntry(NamedTuple):
docname: str
node_id: str
@@ -307,9 +320,64 @@ def _add_pip(
if infopips.children:
contentnode.insert(0, infopips)
+ def _validate_field(self, field: nodes.field) -> None:
+ """Validate field lists in this QAPI Object Description."""
+ name, _ = _unpack_field(field)
+ allowed_fields = set(self.env.app.config.qapi_allowed_fields)
+
+ field_label = name.astext()
+ if field_label in allowed_fields:
+ # Explicitly allowed field list name, OK.
+ return
+
+ try:
+ # split into field type and argument (if provided)
+ # e.g. `:arg type name: descr` is
+ # field_type = "arg", field_arg = "type name".
+ field_type, field_arg = field_label.split(None, 1)
+ except ValueError:
+ # No arguments provided
+ field_type = field_label
+ field_arg = ""
+
+ typemap = self.get_field_type_map()
+ if field_type in typemap:
+ # This is a special docfield, yet-to-be-processed. Catch
+ # correct names, but incorrect arguments. This mismatch WILL
+ # cause Sphinx to render this field incorrectly (without a
+ # warning), which is never what we want.
+ typedesc = typemap[field_type][0]
+ if typedesc.has_arg != bool(field_arg):
+ msg = f"docfield field list type {field_type!r} "
+ if typedesc.has_arg:
+ msg += "requires an argument."
+ else:
+ msg += "takes no arguments."
+ logger.warning(msg, location=field)
+ else:
+ # This is unrecognized entirely. It's valid rST to use
+ # arbitrary fields, but let's ensure the documentation
+ # writer has done this intentionally.
+ valid = ", ".join(sorted(set(typemap) | allowed_fields))
+ msg = (
+ f"Unrecognized field list name {field_label!r}.\n"
+ f"Valid fields for qapi:{self.objtype} are: {valid}\n"
+ "\n"
+ "If this usage is intentional, please add it to "
+ "'qapi_allowed_fields' in docs/conf.py."
+ )
+ logger.warning(msg, location=field)
+
def transform_content(self, content_node: addnodes.desc_content) -> None:
self._add_infopips(content_node)
+ # Validate field lists.
+ for child in content_node:
+ if isinstance(child, nodes.field_list):
+ for field in child.children:
+ assert isinstance(field, nodes.field)
+ self._validate_field(field)
+
def _toc_entry_name(self, sig_node: desc_signature) -> str:
# This controls the name in the TOC and on the sidebar.
@@ -764,6 +832,12 @@ def resolve_any_xref(
def setup(app: Sphinx) -> Dict[str, Any]:
app.setup_extension("sphinx.directives")
+ app.add_config_value(
+ "qapi_allowed_fields",
+ set(),
+ "env", # Setting impacts parsing phase
+ types=set,
+ )
app.add_domain(QAPIDomain)
return {
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 27/62] docs/qapi-domain: add type cross-refs to field lists
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (25 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 26/62] docs/qapi-domain: add warnings for malformed field lists John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 28/62] docs/qapi-domain: add CSS styling John Snow
` (35 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This commit, finally, adds cross-referencing support to various field
lists; modeled tightly after Sphinx's own Python domain code.
Cross-referencing support is added to type names provided to :arg:,
:memb:, :returns: and :choice:.
:feat:, :error: and :value:, which do not take type names, do not
support this syntax.
The general syntax is simple:
:arg TypeName ArgName: Lorem Ipsum ...
The domain will transform TypeName into :qapi:type:`TypeName` in this
basic case, and also apply the ``literal`` decoration to indicate that
this is a type cross-reference.
For optional arguments, the special "?" suffix is used. Because "*" has
special meaning in rST that would cause parsing errors, we elect to use
"?" instead. The special syntax processing strips this character from
the end of any type name argument and will append ", optional" to the
rendered output, applying the cross-reference only to the actual type
name.
The intent here is that the actual syntax in doc-blocks need not change;
but e.g. qapidoc.py will need to process and transform "@arg foo lorem
ipsum" into ":arg type? foo: lorem ipsum" based on the schema
information. Therefore, nobody should ever actually witness this
intermediate syntax unless they are writing manual documentation or the
doc transmogrifier breaks.
For array arguments, type names can similarly be surrounded by "[]",
which are stripped off and then re-appended outside of the
cross-reference.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 659b57d1b33..4f4bf328cc7 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -2,6 +2,9 @@
QAPI domain extension.
"""
+# The best laid plans of mice and men, ...
+# pylint: disable=too-many-lines
+
from __future__ import annotations
from typing import (
@@ -116,6 +119,28 @@ def process_link(
return title, target
+ def result_nodes(
+ self,
+ document: nodes.document,
+ env: BuildEnvironment,
+ node: Element,
+ is_ref: bool,
+ ) -> Tuple[List[nodes.Node], List[nodes.system_message]]:
+
+ # node here is the pending_xref node (or whatever nodeclass was
+ # configured at XRefRole class instantiation time).
+ results: List[nodes.Node] = [node]
+
+ if node.get("qapi:array"):
+ results.insert(0, nodes.literal("[", "["))
+ results.append(nodes.literal("]", "]"))
+
+ if node.get("qapi:optional"):
+ results.append(nodes.Text(", "))
+ results.append(nodes.emphasis("?", "optional"))
+
+ return results, []
+
# Alias for the return of handle_signature(), which is used in several places.
# (In the Python domain, this is Tuple[str, str] instead.)
@@ -408,6 +433,7 @@ class QAPICommand(QAPIObject):
"argument",
label=_("Arguments"),
names=("arg",),
+ typerolename="type",
can_collapse=False,
),
# :error: descr
@@ -421,6 +447,7 @@ class QAPICommand(QAPIObject):
GroupedField(
"returnvalue",
label=_("Return"),
+ rolename="type",
names=("return",),
can_collapse=True,
),
@@ -456,6 +483,7 @@ class QAPIAlternate(QAPIObject):
"alternative",
label=_("Alternatives"),
names=("alt",),
+ typerolename="type",
can_collapse=False,
),
]
@@ -473,6 +501,7 @@ class QAPIObjectWithMembers(QAPIObject):
"member",
label=_("Members"),
names=("memb",),
+ typerolename="type",
can_collapse=False,
),
]
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 28/62] docs/qapi-domain: add CSS styling
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (26 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 27/62] docs/qapi-domain: add type cross-refs to " John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-10 9:47 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 29/62] docs/qapi-domain: add XREF compatibility goop for Sphinx < 4.1 John Snow
` (34 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow, Harmonie Snow
Improve the general look and feel of generated QAPI docs.
Attempt to limit line lengths to offer a more comfortable measure on
maximized windows, and improve some margin and spacing for field lists.
Signed-off-by: Harmonie Snow <harmonie@gmail.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx-static/theme_overrides.css | 56 +++++++++++++++++++++++++-
1 file changed, 54 insertions(+), 2 deletions(-)
diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css
index 3fd326613d9..92f395054a8 100644
--- a/docs/sphinx-static/theme_overrides.css
+++ b/docs/sphinx-static/theme_overrides.css
@@ -18,8 +18,8 @@ h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend {
.rst-content dl:not(.docutils) dt {
border-top: none;
- border-left: solid 3px #ccc;
- background-color: #f0f0f0;
+ border-left: solid 5px #bcc6d2;
+ background-color: #eaedf1;
color: black;
}
@@ -211,6 +211,18 @@ div[class^="highlight"] pre {
/* QAPI domain theming */
+/* most content in a qapi object definition should not eclipse about
+ 80ch, but nested field lists are explicitly exempt due to their
+ two-column nature */
+.qapi dd *:not(dl) {
+ max-width: 80ch;
+}
+
+/* but the content column itself should still be less than ~80ch. */
+.qapi .field-list dd {
+ max-width: 80ch;
+}
+
.qapi-infopips {
margin-bottom: 1em;
}
@@ -250,3 +262,43 @@ div[class^="highlight"] pre {
border-radius: 15px;
margin: 5px;
}
+
+/* code blocks */
+.qapi div[class^="highlight"] {
+ width: fit-content;
+ background-color: #fffafd;
+ border: 2px solid #ffe1f3;
+}
+
+/* note, warning, etc. */
+.qapi .admonition {
+ width: fit-content;
+}
+
+/* pad the top of the field-list so the text doesn't start directly at
+ the top border; primarily for the field list labels, but adjust the
+ field bodies as well for parity. */
+dl.field-list > dt:first-of-type, dl.field-list > dd:first-of-type {
+ padding-top: 0.3em;
+}
+
+dl.field-list > dt:last-of-type, dl.field-list > dd:last-of-type {
+ padding-bottom: 0.3em;
+}
+
+/* pad the field list labels so they don't crash into the border */
+dl.field-list > dt {
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+}
+
+/* Add a little padding between field list sections */
+dl.field-list > dd:not(:last-child) {
+ padding-bottom: 1em;
+}
+
+/* Sphinx 3.x: unresolved xrefs */
+.rst-content *:not(a) > code.xref {
+ font-weight: 400;
+ color: #333333;
+}
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 29/62] docs/qapi-domain: add XREF compatibility goop for Sphinx < 4.1
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (27 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 28/62] docs/qapi-domain: add CSS styling John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 30/62] docs/qapi-domain: warn when QAPI domain xrefs fail to resolve John Snow
` (33 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Sphinx < 4.1 handles cross-references ... differently. Factor out and
isolate the compatibility goop we need to make cross references work
properly in old versions of Sphinx.
Yes, it's ugly. Yes, it works. No, I don't want to talk about
it.
Understand that this patch exists because of the overflowing love in my
heart.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/compat.py | 136 +++++++++++++++++++++++++++++++++++--
docs/sphinx/qapi_domain.py | 23 ++++---
2 files changed, 144 insertions(+), 15 deletions(-)
diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py
index 6bc698c5ada..f068d70388d 100644
--- a/docs/sphinx/compat.py
+++ b/docs/sphinx/compat.py
@@ -2,14 +2,31 @@
Sphinx cross-version compatibility goop
"""
-from typing import Callable
+import re
+from typing import (
+ Any,
+ Callable,
+ Optional,
+ Type,
+)
+from docutils import nodes
from docutils.nodes import Element, Node, Text
import sphinx
-from sphinx import addnodes
-from sphinx.util import nodes
-from sphinx.util.docutils import SphinxDirective, switch_source_input
+from sphinx import addnodes, util
+from sphinx.environment import BuildEnvironment
+from sphinx.roles import XRefRole
+from sphinx.util import docfields
+from sphinx.util.docutils import (
+ ReferenceRole,
+ SphinxDirective,
+ switch_source_input,
+)
+from sphinx.util.typing import TextlikeNode
+
+
+MAKE_XREF_WORKAROUND = sphinx.version_info[:3] < (4, 1, 0)
SpaceNode: Callable[[str], Node]
@@ -36,7 +53,7 @@ def nested_parse_with_titles(
try:
# Modern sphinx (6.2.0+) supports proper offsetting for
# nested parse error context management
- nodes.nested_parse_with_titles(
+ util.nodes.nested_parse_with_titles(
directive.state,
directive.content,
content_node,
@@ -45,6 +62,113 @@ def nested_parse_with_titles(
except TypeError:
# No content_offset argument. Fall back to SSI method.
with switch_source_input(directive.state, directive.content):
- nodes.nested_parse_with_titles(
+ util.nodes.nested_parse_with_titles(
directive.state, directive.content, content_node
)
+
+
+# ###########################################
+# xref compatibility hacks for Sphinx < 4.1 #
+# ###########################################
+
+# When we require >= Sphinx 4.1, the following function and the
+# subsequent 3 compatibility classes can be removed. Anywhere in
+# qapi_domain that uses one of these Compat* types can be switched to
+# using the garden-variety lib-provided classes with no trickery.
+
+
+def _compat_make_xref( # pylint: disable=unused-argument
+ self: sphinx.util.docfields.Field,
+ rolename: str,
+ domain: str,
+ target: str,
+ innernode: Type[TextlikeNode] = addnodes.literal_emphasis,
+ contnode: Optional[Node] = None,
+ env: Optional[BuildEnvironment] = None,
+ inliner: Any = None,
+ location: Any = None,
+) -> Node:
+ """
+ Compatibility workaround for Sphinx versions prior to 4.1.0.
+
+ Older sphinx versions do not use the domain's XRefRole for parsing
+ and formatting cross-references, so we need to perform this magick
+ ourselves to avoid needing to write the parser/formatter in two
+ separate places.
+
+ This workaround isn't brick-for-brick compatible with modern Sphinx
+ versions, because we do not have access to the parent directive's
+ state during this parsing like we do in more modern versions.
+
+ It's no worse than what pre-Sphinx 4.1.0 does, so... oh well!
+ """
+
+ # Yes, this function is gross. Pre-4.1 support is a miracle.
+ # pylint: disable=too-many-locals
+
+ assert env
+ # Note: Sphinx's own code ignores the type warning here, too.
+ if not rolename:
+ return contnode or innernode(target, target) # type: ignore[call-arg]
+
+ # Get the role instance, but don't *execute it* - we lack the
+ # correct state to do so. Instead, we'll just use its public
+ # methods to do our reference formatting, and emulate the rest.
+ role = env.get_domain(domain).roles[rolename]
+ assert isinstance(role, XRefRole)
+
+ # XRefRole features not supported by this compatibility shim;
+ # these were not supported in Sphinx 3.x either, so nothing of
+ # value is really lost.
+ assert not target.startswith("!")
+ assert not re.match(ReferenceRole.explicit_title_re, target)
+ assert not role.lowercase
+ assert not role.fix_parens
+
+ # Code below based mostly on sphinx.roles.XRefRole; run() and
+ # create_xref_node()
+ options = {
+ "refdoc": env.docname,
+ "refdomain": domain,
+ "reftype": rolename,
+ "refexplicit": False,
+ "refwarn": role.warn_dangling,
+ }
+ refnode = role.nodeclass(target, **options)
+ title, target = role.process_link(env, refnode, False, target, target)
+ refnode["reftarget"] = target
+ classes = ["xref", domain, f"{domain}-{rolename}"]
+ refnode += role.innernodeclass(target, title, classes=classes)
+
+ # This is the very gross part of the hack. Normally,
+ # result_nodes takes a document object to which we would pass
+ # self.inliner.document. Prior to Sphinx 4.1, we don't *have* an
+ # inliner to pass, so we have nothing to pass here. However, the
+ # actual implementation of role.result_nodes in this case
+ # doesn't actually use that argument, so this winds up being
+ # ... fine. Rest easy at night knowing this code only runs under
+ # old versions of Sphinx, so at least it won't change in the
+ # future on us and lead to surprising new failures.
+ # Gross, I know.
+ result_nodes, _messages = role.result_nodes(
+ None, # type: ignore
+ env,
+ refnode,
+ is_ref=True,
+ )
+ return nodes.inline(target, "", *result_nodes)
+
+
+class CompatField(docfields.Field):
+ if MAKE_XREF_WORKAROUND:
+ make_xref = _compat_make_xref
+
+
+class CompatGroupedField(docfields.GroupedField):
+ if MAKE_XREF_WORKAROUND:
+ make_xref = _compat_make_xref
+
+
+class CompatTypedField(docfields.TypedField):
+ if MAKE_XREF_WORKAROUND:
+ make_xref = _compat_make_xref
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 4f4bf328cc7..2649fa8c1db 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -24,7 +24,13 @@
from docutils import nodes
from docutils.parsers.rst import directives
-from compat import KeywordNode, SpaceNode
+from compat import (
+ CompatField,
+ CompatGroupedField,
+ CompatTypedField,
+ KeywordNode,
+ SpaceNode,
+)
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.directives import ObjectDescription
@@ -37,7 +43,6 @@
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
-from sphinx.util.docfields import Field, GroupedField, TypedField
from sphinx.util.nodes import make_id, make_refnode
@@ -228,7 +233,7 @@ class QAPIObject(QAPIDescription):
doc_field_types = [
# :feat name: descr
- GroupedField(
+ CompatGroupedField(
"feature",
label=_("Features"),
names=("feat",),
@@ -429,7 +434,7 @@ class QAPICommand(QAPIObject):
doc_field_types.extend(
[
# :arg TypeName ArgName: descr
- TypedField(
+ CompatTypedField(
"argument",
label=_("Arguments"),
names=("arg",),
@@ -437,14 +442,14 @@ class QAPICommand(QAPIObject):
can_collapse=False,
),
# :error: descr
- Field(
+ CompatField(
"error",
label=_("Errors"),
names=("error", "errors"),
has_arg=False,
),
# :returns TypeName: descr
- GroupedField(
+ CompatGroupedField(
"returnvalue",
label=_("Return"),
rolename="type",
@@ -462,7 +467,7 @@ class QAPIEnum(QAPIObject):
doc_field_types.extend(
[
# :value name: descr
- GroupedField(
+ CompatGroupedField(
"value",
label=_("Values"),
names=("value",),
@@ -479,7 +484,7 @@ class QAPIAlternate(QAPIObject):
doc_field_types.extend(
[
# :alt type name: descr
- TypedField(
+ CompatTypedField(
"alternative",
label=_("Alternatives"),
names=("alt",),
@@ -497,7 +502,7 @@ class QAPIObjectWithMembers(QAPIObject):
doc_field_types.extend(
[
# :member type name: descr
- TypedField(
+ CompatTypedField(
"member",
label=_("Members"),
names=("memb",),
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 30/62] docs/qapi-domain: warn when QAPI domain xrefs fail to resolve
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (28 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 29/62] docs/qapi-domain: add XREF compatibility goop for Sphinx < 4.1 John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 31/62] docs/qapi-domain: Fix error context reporting in Sphinx 5.x and 6.x John Snow
` (32 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This patch adds a warning (which is a build failure under our current
build settings) whenever a QAPI cross-reference fails to resolve.
This applies to any cross-references of the form :qapi:{role}:`foo`,
which covers all of the automatically generated references by the qapi
domain, and any such references that are manually written into the
documentation rst files.
Cross-references of the form `foo` do not use this system, but are
already configured to issue a warning (Again, a build failure) if the
cross-reference isn't found anywhere.
Adds warnings that look like the following:
docs/qapi/index.rst:48: WARNING: qapi:type reference target not found: 'footype' [ref.qapi]
docs/qapi/index.rst:50: WARNING: qapi:mod reference target not found: 'foomod' [ref.qapi]
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 2649fa8c1db..f8fc51d7d4f 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -825,6 +825,29 @@ def resolve_xref(
matches = self.find_obj(modname, target, typ)
if not matches:
+ # Normally, we could pass warn_dangling=True to QAPIXRefRole(),
+ # but that will trigger on references to these built-in types,
+ # which we'd like to ignore instead.
+
+ # Take care of that warning here instead, so long as the
+ # reference isn't to one of our built-in core types.
+ if target not in (
+ "string",
+ "number",
+ "int",
+ "boolean",
+ "null",
+ "value",
+ "q_empty",
+ ):
+ logger.warning(
+ __("qapi:%s reference target not found: %r"),
+ typ,
+ target,
+ type="ref",
+ subtype="qapi",
+ location=node,
+ )
return None
if len(matches) > 1:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 31/62] docs/qapi-domain: Fix error context reporting in Sphinx 5.x and 6.x
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (29 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 30/62] docs/qapi-domain: warn when QAPI domain xrefs fail to resolve John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 32/62] qapi/parser: adjust info location for doc body section John Snow
` (31 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Sphinx 5.3.0 to Sphinx 6.2.0 has a bug where nested content in an
ObjectDescription content block has its error position reported
incorrectly due to an oversight when they added nested section support
to this directive.
(This bug is present in Sphinx's own Python and C domains; test it
yourself by creating a py:func directive and creating a syntax error in
the directive's content block. The reporting will be incorrect.)
To avoid overriding and re-implementing the entirety of the run()
method, a workaround is employed where we parse the content block
ourselves in before_content(), then null the content block to make
Sphinx's own parsing a no-op. Then, in transform_content (which occurs
after Sphinx's nested parse), we simply swap our own parsed content tree
back in for Sphinx's.
It appears a little tricky, but it's the nicest solution I can find.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/compat.py | 56 ++++++++++++++++++++++++++++++++++++++
docs/sphinx/qapi_domain.py | 15 ++++++----
2 files changed, 65 insertions(+), 6 deletions(-)
diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py
index f068d70388d..9cf7fe006e4 100644
--- a/docs/sphinx/compat.py
+++ b/docs/sphinx/compat.py
@@ -4,6 +4,7 @@
import re
from typing import (
+ TYPE_CHECKING,
Any,
Callable,
Optional,
@@ -12,9 +13,11 @@
from docutils import nodes
from docutils.nodes import Element, Node, Text
+from docutils.statemachine import StringList
import sphinx
from sphinx import addnodes, util
+from sphinx.directives import ObjectDescription
from sphinx.environment import BuildEnvironment
from sphinx.roles import XRefRole
from sphinx.util import docfields
@@ -172,3 +175,56 @@ class CompatGroupedField(docfields.GroupedField):
class CompatTypedField(docfields.TypedField):
if MAKE_XREF_WORKAROUND:
make_xref = _compat_make_xref
+
+
+# ################################################################
+# Nested parsing error location fix for Sphinx 5.3.0 < x < 6.2.0 #
+# ################################################################
+
+# When we require Sphinx 4.x, the TYPE_CHECKING hack where we avoid
+# subscripting ObjectDescription at runtime can be removed in favor of
+# just always subscripting the class.
+
+# When we require Sphinx > 6.2.0, the rest of this compatibility hack
+# can be dropped and QAPIObject can just inherit directly from
+# ObjectDescription[Signature].
+
+SOURCE_LOCATION_FIX = (5, 3, 0) <= sphinx.version_info[:3] < (6, 2, 0)
+
+Signature = str
+
+
+if TYPE_CHECKING:
+ _BaseClass = ObjectDescription[Signature]
+else:
+ _BaseClass = ObjectDescription
+
+
+class ParserFix(_BaseClass):
+
+ _temp_content: StringList
+ _temp_offset: int
+ _temp_node: Optional[addnodes.desc_content]
+
+ def before_content(self) -> None:
+ # Work around a sphinx bug and parse the content ourselves.
+ self._temp_content = self.content
+ self._temp_offset = self.content_offset
+ self._temp_node = None
+
+ if SOURCE_LOCATION_FIX:
+ self._temp_node = addnodes.desc_content()
+ self.state.nested_parse(
+ self.content, self.content_offset, self._temp_node
+ )
+ # Sphinx will try to parse the content block itself,
+ # Give it nothingness to parse instead.
+ self.content = StringList()
+ self.content_offset = 0
+
+ def transform_content(self, content_node: addnodes.desc_content) -> None:
+ # Sphinx workaround: Inject our parsed content and restore state.
+ if self._temp_node:
+ content_node += self._temp_node.children
+ self.content = self._temp_content
+ self.content_offset = self._temp_offset
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index f8fc51d7d4f..ca5e878c8ad 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -29,6 +29,8 @@
CompatGroupedField,
CompatTypedField,
KeywordNode,
+ ParserFix,
+ Signature,
SpaceNode,
)
from sphinx import addnodes
@@ -147,12 +149,7 @@ def result_nodes(
return results, []
-# Alias for the return of handle_signature(), which is used in several places.
-# (In the Python domain, this is Tuple[str, str] instead.)
-Signature = str
-
-
-class QAPIDescription(ObjectDescription[Signature]):
+class QAPIDescription(ParserFix):
"""
Generic QAPI description.
@@ -399,6 +396,10 @@ def _validate_field(self, field: nodes.field) -> None:
logger.warning(msg, location=field)
def transform_content(self, content_node: addnodes.desc_content) -> None:
+ # This hook runs after before_content and the nested parse, but
+ # before the DocFieldTransformer is executed.
+ super().transform_content(content_node)
+
self._add_infopips(content_node)
# Validate field lists.
@@ -514,10 +515,12 @@ class QAPIObjectWithMembers(QAPIObject):
class QAPIEvent(QAPIObjectWithMembers):
+ # pylint: disable=too-many-ancestors
"""Description of a QAPI Event."""
class QAPIJSONObject(QAPIObjectWithMembers):
+ # pylint: disable=too-many-ancestors
"""Description of a QAPI Object: structs and unions."""
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 32/62] qapi/parser: adjust info location for doc body section
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (30 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 31/62] docs/qapi-domain: Fix error context reporting in Sphinx 5.x and 6.x John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 20:53 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 33/62] qapi: expand tags to all doc sections John Snow
` (30 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Instead of using the info object for the doc block as a whole (which
always points to the very first line of the block), update the info
pointer for each call to ensure_untagged_section when the existing
section is otherwise empty. This way, Sphinx error information will
match precisely to where the text actually starts.
For example, this patch will move the info pointer for the "Hello!"
untagged section ...
> ## <-- from here ...
> # Hello! <-- ... to here.
> ##
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/parser.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 64f0bb824ae..97def9f0e4f 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -686,7 +686,11 @@ def end(self) -> None:
def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
if self.all_sections and not self.all_sections[-1].tag:
# extend current section
- self.all_sections[-1].text += '\n'
+ section = self.all_sections[-1]
+ if not section.text:
+ # Section is empty so far; update info to start *here*.
+ section.info = info
+ section.text += '\n'
return
# start new section
section = self.Section(info)
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 33/62] qapi: expand tags to all doc sections
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (31 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 32/62] qapi/parser: adjust info location for doc body section John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 20:57 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 34/62] qapi/schema: add __repr__ to QAPIDoc.Section John Snow
` (29 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This patch adds an explicit section "kind" to all QAPIDoc
sections. Members/Features are now explicitly marked as such, with the
name now being stored in a dedicated "name" field (which qapidoc.py was
not actually using anyway.)
The qapi-schema tests are updated to account for the new section names;
mostly "TODO" becomes "Todo" and `None` becomes "Plain".
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 7 +--
scripts/qapi/parser.py | 97 ++++++++++++++++++++++++----------
tests/qapi-schema/doc-good.out | 10 ++--
tests/qapi-schema/test-qapi.py | 2 +-
4 files changed, 80 insertions(+), 36 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 61997fd21af..d622398f1da 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -35,6 +35,7 @@
from docutils.statemachine import ViewList
from qapi.error import QAPIError, QAPISemError
from qapi.gen import QAPISchemaVisitor
+from qapi.parser import QAPIDoc
from qapi.schema import QAPISchema
from sphinx import addnodes
@@ -258,11 +259,11 @@ def _nodes_for_sections(self, doc):
"""Return list of doctree nodes for additional sections"""
nodelist = []
for section in doc.sections:
- if section.tag and section.tag == 'TODO':
+ if section.kind == QAPIDoc.Kind.TODO:
# Hide TODO: sections
continue
- if not section.tag:
+ if section.kind == QAPIDoc.Kind.PLAIN:
# Sphinx cannot handle sectionless titles;
# Instead, just append the results to the prior section.
container = nodes.container()
@@ -270,7 +271,7 @@ def _nodes_for_sections(self, doc):
nodelist += container.children
continue
- snode = self._make_section(section.tag)
+ snode = self._make_section(section.kind.name.title())
self._parse_text_into_node(dedent(section.text), snode)
nodelist.append(snode)
return nodelist
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 97def9f0e4f..94d5322f8af 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -14,6 +14,7 @@
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
+import enum
import os
import re
from typing import (
@@ -574,7 +575,10 @@ def get_doc(self) -> 'QAPIDoc':
)
raise QAPIParseError(self, emsg)
- doc.new_tagged_section(self.info, match.group(1))
+ doc.new_tagged_section(
+ self.info,
+ QAPIDoc.Kind.from_string(match.group(1))
+ )
text = line[match.end():]
if text:
doc.append_line(text)
@@ -585,7 +589,7 @@ def get_doc(self) -> 'QAPIDoc':
self,
"unexpected '=' markup in definition documentation")
else:
- # tag-less paragraph
+ # plain paragraph
doc.ensure_untagged_section(self.info)
doc.append_line(line)
line = self.get_doc_paragraph(doc)
@@ -634,14 +638,33 @@ class QAPIDoc:
Free-form documentation blocks consist only of a body section.
"""
+ class Kind(enum.Enum):
+ PLAIN = 0
+ MEMBER = 1
+ FEATURE = 2
+ RETURNS = 3
+ ERRORS = 4
+ SINCE = 5
+ TODO = 6
+
+ @staticmethod
+ def from_string(kind: str) -> 'QAPIDoc.Kind':
+ return QAPIDoc.Kind[kind.upper()]
+
+ def __str__(self) -> str:
+ return self.name.title()
+
class Section:
# pylint: disable=too-few-public-methods
- def __init__(self, info: QAPISourceInfo,
- tag: Optional[str] = None):
+ def __init__(
+ self,
+ info: QAPISourceInfo,
+ kind: 'QAPIDoc.Kind',
+ ):
# section source info, i.e. where it begins
self.info = info
- # section tag, if any ('Returns', '@name', ...)
- self.tag = tag
+ # section kind
+ self.kind = kind
# section text without tag
self.text = ''
@@ -649,8 +672,14 @@ def append_line(self, line: str) -> None:
self.text += line + '\n'
class ArgSection(Section):
- def __init__(self, info: QAPISourceInfo, tag: str):
- super().__init__(info, tag)
+ def __init__(
+ self,
+ info: QAPISourceInfo,
+ kind: 'QAPIDoc.Kind',
+ name: str
+ ):
+ super().__init__(info, kind)
+ self.name = name
self.member: Optional['QAPISchemaMember'] = None
def connect(self, member: 'QAPISchemaMember') -> None:
@@ -662,7 +691,9 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None):
# definition doc's symbol, None for free-form doc
self.symbol: Optional[str] = symbol
# the sections in textual order
- self.all_sections: List[QAPIDoc.Section] = [QAPIDoc.Section(info)]
+ self.all_sections: List[QAPIDoc.Section] = [
+ QAPIDoc.Section(info, QAPIDoc.Kind.PLAIN)
+ ]
# the body section
self.body: Optional[QAPIDoc.Section] = self.all_sections[0]
# dicts mapping parameter/feature names to their description
@@ -679,12 +710,14 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None):
def end(self) -> None:
for section in self.all_sections:
section.text = section.text.strip('\n')
- if section.tag is not None and section.text == '':
+ if section.kind != QAPIDoc.Kind.PLAIN and section.text == '':
raise QAPISemError(
- section.info, "text required after '%s:'" % section.tag)
+ section.info, "text required after '%s:'" % section.kind)
def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
- if self.all_sections and not self.all_sections[-1].tag:
+ kind = QAPIDoc.Kind.PLAIN
+
+ if self.all_sections and self.all_sections[-1].kind == kind:
# extend current section
section = self.all_sections[-1]
if not section.text:
@@ -692,46 +725,56 @@ def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
section.info = info
section.text += '\n'
return
+
# start new section
- section = self.Section(info)
+ section = self.Section(info, kind)
self.sections.append(section)
self.all_sections.append(section)
- def new_tagged_section(self, info: QAPISourceInfo, tag: str) -> None:
- section = self.Section(info, tag)
- if tag == 'Returns':
+ def new_tagged_section(
+ self,
+ info: QAPISourceInfo,
+ kind: 'QAPIDoc.Kind',
+ ) -> None:
+ section = self.Section(info, kind)
+ if kind == QAPIDoc.Kind.RETURNS:
if self.returns:
raise QAPISemError(
- info, "duplicated '%s' section" % tag)
+ info, "duplicated '%s' section" % kind)
self.returns = section
- elif tag == 'Errors':
+ elif kind == QAPIDoc.Kind.ERRORS:
if self.errors:
raise QAPISemError(
- info, "duplicated '%s' section" % tag)
+ info, "duplicated '%s' section" % kind)
self.errors = section
- elif tag == 'Since':
+ elif kind == QAPIDoc.Kind.SINCE:
if self.since:
raise QAPISemError(
- info, "duplicated '%s' section" % tag)
+ info, "duplicated '%s' section" % kind)
self.since = section
self.sections.append(section)
self.all_sections.append(section)
- def _new_description(self, info: QAPISourceInfo, name: str,
- desc: Dict[str, ArgSection]) -> None:
+ def _new_description(
+ self,
+ info: QAPISourceInfo,
+ name: str,
+ kind: 'QAPIDoc.Kind',
+ desc: Dict[str, ArgSection]
+ ) -> None:
if not name:
raise QAPISemError(info, "invalid parameter name")
if name in desc:
raise QAPISemError(info, "'%s' parameter name duplicated" % name)
- section = self.ArgSection(info, '@' + name)
+ section = self.ArgSection(info, kind, name)
self.all_sections.append(section)
desc[name] = section
def new_argument(self, info: QAPISourceInfo, name: str) -> None:
- self._new_description(info, name, self.args)
+ self._new_description(info, name, QAPIDoc.Kind.MEMBER, self.args)
def new_feature(self, info: QAPISourceInfo, name: str) -> None:
- self._new_description(info, name, self.features)
+ self._new_description(info, name, QAPIDoc.Kind.FEATURE, self.features)
def append_line(self, line: str) -> None:
self.all_sections[-1].append_line(line)
@@ -744,7 +787,7 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
"%s '%s' lacks documentation"
% (member.role, member.name))
self.args[member.name] = QAPIDoc.ArgSection(
- self.info, '@' + member.name)
+ self.info, QAPIDoc.Kind.MEMBER, member.name)
self.args[member.name].connect(member)
def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index 0a9da3efdeb..5773f1dd6d6 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -113,7 +113,7 @@ The _one_ {and only}, description on the same line
Also _one_ {and only}
feature=enum-member-feat
a member feature
- section=None
+ section=Plain
@two is undocumented
doc symbol=Base
body=
@@ -171,15 +171,15 @@ description starts on the same line
a feature
feature=cmd-feat2
another feature
- section=None
+ section=Plain
.. note:: @arg3 is undocumented
section=Returns
@Object
section=Errors
some
- section=TODO
+ section=Todo
frobnicate
- section=None
+ section=Plain
.. admonition:: Notes
- Lorem ipsum dolor sit amet
@@ -212,7 +212,7 @@ If you're bored enough to read this, go see a video of boxed cats
a feature
feature=cmd-feat2
another feature
- section=None
+ section=Plain
.. qmp-example::
-> "this example"
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 8fe951c8803..4be930228cc 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -122,7 +122,7 @@ def test_frontend(fname):
for feat, section in doc.features.items():
print(' feature=%s\n%s' % (feat, section.text))
for section in doc.sections:
- print(' section=%s\n%s' % (section.tag, section.text))
+ print(' section=%s\n%s' % (section.kind, section.text))
def open_test_result(dir_name, file_name, update):
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 34/62] qapi/schema: add __repr__ to QAPIDoc.Section
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (32 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 33/62] qapi: expand tags to all doc sections John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 35/62] docs/qapidoc: add transmogrifier stub John Snow
` (28 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Makes debugging far more pleasant when you can just print(section) and
get something reasonable to display.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/parser.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 94d5322f8af..11c11bb09e5 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -668,6 +668,9 @@ def __init__(
# section text without tag
self.text = ''
+ def __repr__(self) -> str:
+ return f"<QAPIDoc.Section kind={self.kind!r} text={self.text!r}>"
+
def append_line(self, line: str) -> None:
self.text += line + '\n'
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 35/62] docs/qapidoc: add transmogrifier stub
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (33 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 34/62] qapi/schema: add __repr__ to QAPIDoc.Section John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 36/62] docs/qapidoc: split old implementation into qapidoc_legacy.py John Snow
` (27 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This commit adds a stubbed option to the qapi-doc directive that opts-in
to the new rST generator; the implementation of which will follow in
subsequent commits.
Once all QAPI documents have been converted, this option and the old
qapidoc implementation can be dropped.
Note that moving code outside of the try...except block has no impact
because the code moved outside of that block does not ever raise a
QAPIError.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 41 ++++++++++++++++++++++++++++-------------
1 file changed, 28 insertions(+), 13 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index d622398f1da..dc72f3fd3f3 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -452,9 +452,9 @@ def _parse_text_into_node(self, doctext, node):
rstlist.append("", self._cur_doc.info.fname, self._cur_doc.info.line)
self._sphinx_directive.do_parse(rstlist, node)
- def get_document_nodes(self):
- """Return the list of docutils nodes which make up the document"""
- return self._top_node.children
+ def get_document_node(self):
+ """Return the root docutils node which makes up the document"""
+ return self._top_node
# Turn the black formatter on for the rest of the file.
@@ -503,7 +503,10 @@ class QAPIDocDirective(NestedDirective):
required_argument = 1
optional_arguments = 1
- option_spec = {"qapifile": directives.unchanged_required}
+ option_spec = {
+ "qapifile": directives.unchanged_required,
+ "transmogrify": directives.flag,
+ }
has_content = False
def new_serialno(self):
@@ -511,10 +514,24 @@ def new_serialno(self):
env = self.state.document.settings.env
return "qapidoc-%d" % env.new_serialno("qapidoc")
+ def transmogrify(self, schema) -> nodes.Element:
+ raise NotImplementedError
+
+ def legacy(self, schema) -> nodes.Element:
+ vis = QAPISchemaGenRSTVisitor(self)
+ vis.visit_begin(schema)
+ for doc in schema.docs:
+ if doc.symbol:
+ vis.symbol(doc, schema.lookup_entity(doc.symbol))
+ else:
+ vis.freeform(doc)
+ return vis.get_document_node()
+
def run(self):
env = self.state.document.settings.env
qapifile = env.config.qapidoc_srctree + "/" + self.arguments[0]
qapidir = os.path.dirname(qapifile)
+ transmogrify = "transmogrify" in self.options
try:
schema = QAPISchema(qapifile)
@@ -522,20 +539,18 @@ def run(self):
# First tell Sphinx about all the schema files that the
# output documentation depends on (including 'qapifile' itself)
schema.visit(QAPISchemaGenDepVisitor(env, qapidir))
-
- vis = QAPISchemaGenRSTVisitor(self)
- vis.visit_begin(schema)
- for doc in schema.docs:
- if doc.symbol:
- vis.symbol(doc, schema.lookup_entity(doc.symbol))
- else:
- vis.freeform(doc)
- return vis.get_document_nodes()
except QAPIError as err:
# Launder QAPI parse errors into Sphinx extension errors
# so they are displayed nicely to the user
raise ExtensionError(str(err)) from err
+ if transmogrify:
+ contentnode = self.transmogrify(schema)
+ else:
+ contentnode = self.legacy(schema)
+
+ return contentnode.children
+
class QMPExample(CodeBlock, NestedDirective):
"""
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 36/62] docs/qapidoc: split old implementation into qapidoc_legacy.py
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (34 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 35/62] docs/qapidoc: add transmogrifier stub John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 37/62] docs/qapidoc: Fix static typing on qapidoc.py John Snow
` (26 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This is being done primarily to be able to type check and delint the new
implementation without needing to worry about fixing up the old
implementation.
I'm adding the new implementation into the existing file instead of into
a new file so that when the dust settles, qapidoc.py will contain the
full history of development on this generative module.
This patch *should* be pure motion, give or take the import statements.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 420 +-------------------------------
docs/sphinx/qapidoc_legacy.py | 439 ++++++++++++++++++++++++++++++++++
2 files changed, 441 insertions(+), 418 deletions(-)
create mode 100644 docs/sphinx/qapidoc_legacy.py
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index dc72f3fd3f3..f4abf42e7bf 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -25,19 +25,16 @@
"""
import os
-import re
import sys
-import textwrap
from typing import List
from docutils import nodes
from docutils.parsers.rst import Directive, directives
-from docutils.statemachine import ViewList
-from qapi.error import QAPIError, QAPISemError
+from qapi.error import QAPIError
from qapi.gen import QAPISchemaVisitor
-from qapi.parser import QAPIDoc
from qapi.schema import QAPISchema
+from qapidoc_legacy import QAPISchemaGenRSTVisitor
from sphinx import addnodes
from sphinx.directives.code import CodeBlock
from sphinx.errors import ExtensionError
@@ -48,419 +45,6 @@
__version__ = "1.0"
-def dedent(text: str) -> str:
- # Adjust indentation to make description text parse as paragraph.
-
- lines = text.splitlines(True)
- if re.match(r"\s+", lines[0]):
- # First line is indented; description started on the line after
- # the name. dedent the whole block.
- return textwrap.dedent(text)
-
- # Descr started on same line. Dedent line 2+.
- return lines[0] + textwrap.dedent("".join(lines[1:]))
-
-
-# Disable black auto-formatter until re-enabled:
-# fmt: off
-
-
-class QAPISchemaGenRSTVisitor(QAPISchemaVisitor):
- """A QAPI schema visitor which generates docutils/Sphinx nodes
-
- This class builds up a tree of docutils/Sphinx nodes corresponding
- to documentation for the various QAPI objects. To use it, first
- create a QAPISchemaGenRSTVisitor object, and call its
- visit_begin() method. Then you can call one of the two methods
- 'freeform' (to add documentation for a freeform documentation
- chunk) or 'symbol' (to add documentation for a QAPI symbol). These
- will cause the visitor to build up the tree of document
- nodes. Once you've added all the documentation via 'freeform' and
- 'symbol' method calls, you can call 'get_document_nodes' to get
- the final list of document nodes (in a form suitable for returning
- from a Sphinx directive's 'run' method).
- """
- def __init__(self, sphinx_directive):
- self._cur_doc = None
- self._sphinx_directive = sphinx_directive
- self._top_node = nodes.section()
- self._active_headings = [self._top_node]
-
- def _make_dlitem(self, term, defn):
- """Return a dlitem node with the specified term and definition.
-
- term should be a list of Text and literal nodes.
- defn should be one of:
- - a string, which will be handed to _parse_text_into_node
- - a list of Text and literal nodes, which will be put into
- a paragraph node
- """
- dlitem = nodes.definition_list_item()
- dlterm = nodes.term('', '', *term)
- dlitem += dlterm
- if defn:
- dldef = nodes.definition()
- if isinstance(defn, list):
- dldef += nodes.paragraph('', '', *defn)
- else:
- self._parse_text_into_node(defn, dldef)
- dlitem += dldef
- return dlitem
-
- def _make_section(self, title):
- """Return a section node with optional title"""
- section = nodes.section(ids=[self._sphinx_directive.new_serialno()])
- if title:
- section += nodes.title(title, title)
- return section
-
- def _nodes_for_ifcond(self, ifcond, with_if=True):
- """Return list of Text, literal nodes for the ifcond
-
- Return a list which gives text like ' (If: condition)'.
- If with_if is False, we don't return the "(If: " and ")".
- """
-
- doc = ifcond.docgen()
- if not doc:
- return []
- doc = nodes.literal('', doc)
- if not with_if:
- return [doc]
-
- nodelist = [nodes.Text(' ('), nodes.strong('', 'If: ')]
- nodelist.append(doc)
- nodelist.append(nodes.Text(')'))
- return nodelist
-
- def _nodes_for_one_member(self, member):
- """Return list of Text, literal nodes for this member
-
- Return a list of doctree nodes which give text like
- 'name: type (optional) (If: ...)' suitable for use as the
- 'term' part of a definition list item.
- """
- term = [nodes.literal('', member.name)]
- if member.type.doc_type():
- term.append(nodes.Text(': '))
- term.append(nodes.literal('', member.type.doc_type()))
- if member.optional:
- term.append(nodes.Text(' (optional)'))
- if member.ifcond.is_present():
- term.extend(self._nodes_for_ifcond(member.ifcond))
- return term
-
- def _nodes_for_variant_when(self, branches, variant):
- """Return list of Text, literal nodes for variant 'when' clause
-
- Return a list of doctree nodes which give text like
- 'when tagname is variant (If: ...)' suitable for use in
- the 'branches' part of a definition list.
- """
- term = [nodes.Text(' when '),
- nodes.literal('', branches.tag_member.name),
- nodes.Text(' is '),
- nodes.literal('', '"%s"' % variant.name)]
- if variant.ifcond.is_present():
- term.extend(self._nodes_for_ifcond(variant.ifcond))
- return term
-
- def _nodes_for_members(self, doc, what, base=None, branches=None):
- """Return list of doctree nodes for the table of members"""
- dlnode = nodes.definition_list()
- for section in doc.args.values():
- term = self._nodes_for_one_member(section.member)
- # TODO drop fallbacks when undocumented members are outlawed
- if section.text:
- defn = dedent(section.text)
- else:
- defn = [nodes.Text('Not documented')]
-
- dlnode += self._make_dlitem(term, defn)
-
- if base:
- dlnode += self._make_dlitem([nodes.Text('The members of '),
- nodes.literal('', base.doc_type())],
- None)
-
- if branches:
- for v in branches.variants:
- if v.type.name == 'q_empty':
- continue
- assert not v.type.is_implicit()
- term = [nodes.Text('The members of '),
- nodes.literal('', v.type.doc_type())]
- term.extend(self._nodes_for_variant_when(branches, v))
- dlnode += self._make_dlitem(term, None)
-
- if not dlnode.children:
- return []
-
- section = self._make_section(what)
- section += dlnode
- return [section]
-
- def _nodes_for_enum_values(self, doc):
- """Return list of doctree nodes for the table of enum values"""
- seen_item = False
- dlnode = nodes.definition_list()
- for section in doc.args.values():
- termtext = [nodes.literal('', section.member.name)]
- if section.member.ifcond.is_present():
- termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
- # TODO drop fallbacks when undocumented members are outlawed
- if section.text:
- defn = dedent(section.text)
- else:
- defn = [nodes.Text('Not documented')]
-
- dlnode += self._make_dlitem(termtext, defn)
- seen_item = True
-
- if not seen_item:
- return []
-
- section = self._make_section('Values')
- section += dlnode
- return [section]
-
- def _nodes_for_arguments(self, doc, arg_type):
- """Return list of doctree nodes for the arguments section"""
- if arg_type and not arg_type.is_implicit():
- assert not doc.args
- section = self._make_section('Arguments')
- dlnode = nodes.definition_list()
- dlnode += self._make_dlitem(
- [nodes.Text('The members of '),
- nodes.literal('', arg_type.name)],
- None)
- section += dlnode
- return [section]
-
- return self._nodes_for_members(doc, 'Arguments')
-
- def _nodes_for_features(self, doc):
- """Return list of doctree nodes for the table of features"""
- seen_item = False
- dlnode = nodes.definition_list()
- for section in doc.features.values():
- dlnode += self._make_dlitem(
- [nodes.literal('', section.member.name)], dedent(section.text))
- seen_item = True
-
- if not seen_item:
- return []
-
- section = self._make_section('Features')
- section += dlnode
- return [section]
-
- def _nodes_for_sections(self, doc):
- """Return list of doctree nodes for additional sections"""
- nodelist = []
- for section in doc.sections:
- if section.kind == QAPIDoc.Kind.TODO:
- # Hide TODO: sections
- continue
-
- if section.kind == QAPIDoc.Kind.PLAIN:
- # Sphinx cannot handle sectionless titles;
- # Instead, just append the results to the prior section.
- container = nodes.container()
- self._parse_text_into_node(section.text, container)
- nodelist += container.children
- continue
-
- snode = self._make_section(section.kind.name.title())
- self._parse_text_into_node(dedent(section.text), snode)
- nodelist.append(snode)
- return nodelist
-
- def _nodes_for_if_section(self, ifcond):
- """Return list of doctree nodes for the "If" section"""
- nodelist = []
- if ifcond.is_present():
- snode = self._make_section('If')
- snode += nodes.paragraph(
- '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
- )
- nodelist.append(snode)
- return nodelist
-
- def _add_doc(self, typ, sections):
- """Add documentation for a command/object/enum...
-
- We assume we're documenting the thing defined in self._cur_doc.
- typ is the type of thing being added ("Command", "Object", etc)
-
- sections is a list of nodes for sections to add to the definition.
- """
-
- doc = self._cur_doc
- snode = nodes.section(ids=[self._sphinx_directive.new_serialno()])
- snode += nodes.title('', '', *[nodes.literal(doc.symbol, doc.symbol),
- nodes.Text(' (' + typ + ')')])
- self._parse_text_into_node(doc.body.text, snode)
- for s in sections:
- if s is not None:
- snode += s
- self._add_node_to_current_heading(snode)
-
- def visit_enum_type(self, name, info, ifcond, features, members, prefix):
- doc = self._cur_doc
- self._add_doc('Enum',
- self._nodes_for_enum_values(doc)
- + self._nodes_for_features(doc)
- + self._nodes_for_sections(doc)
- + self._nodes_for_if_section(ifcond))
-
- def visit_object_type(self, name, info, ifcond, features,
- base, members, branches):
- doc = self._cur_doc
- if base and base.is_implicit():
- base = None
- self._add_doc('Object',
- self._nodes_for_members(doc, 'Members', base, branches)
- + self._nodes_for_features(doc)
- + self._nodes_for_sections(doc)
- + self._nodes_for_if_section(ifcond))
-
- def visit_alternate_type(self, name, info, ifcond, features,
- alternatives):
- doc = self._cur_doc
- self._add_doc('Alternate',
- self._nodes_for_members(doc, 'Members')
- + self._nodes_for_features(doc)
- + self._nodes_for_sections(doc)
- + self._nodes_for_if_section(ifcond))
-
- def visit_command(self, name, info, ifcond, features, arg_type,
- ret_type, gen, success_response, boxed, allow_oob,
- allow_preconfig, coroutine):
- doc = self._cur_doc
- self._add_doc('Command',
- self._nodes_for_arguments(doc, arg_type)
- + self._nodes_for_features(doc)
- + self._nodes_for_sections(doc)
- + self._nodes_for_if_section(ifcond))
-
- def visit_event(self, name, info, ifcond, features, arg_type, boxed):
- doc = self._cur_doc
- self._add_doc('Event',
- self._nodes_for_arguments(doc, arg_type)
- + self._nodes_for_features(doc)
- + self._nodes_for_sections(doc)
- + self._nodes_for_if_section(ifcond))
-
- def symbol(self, doc, entity):
- """Add documentation for one symbol to the document tree
-
- This is the main entry point which causes us to add documentation
- nodes for a symbol (which could be a 'command', 'object', 'event',
- etc). We do this by calling 'visit' on the schema entity, which
- will then call back into one of our visit_* methods, depending
- on what kind of thing this symbol is.
- """
- self._cur_doc = doc
- entity.visit(self)
- self._cur_doc = None
-
- def _start_new_heading(self, heading, level):
- """Start a new heading at the specified heading level
-
- Create a new section whose title is 'heading' and which is placed
- in the docutils node tree as a child of the most recent level-1
- heading. Subsequent document sections (commands, freeform doc chunks,
- etc) will be placed as children of this new heading section.
- """
- if len(self._active_headings) < level:
- raise QAPISemError(self._cur_doc.info,
- 'Level %d subheading found outside a '
- 'level %d heading'
- % (level, level - 1))
- snode = self._make_section(heading)
- self._active_headings[level - 1] += snode
- self._active_headings = self._active_headings[:level]
- self._active_headings.append(snode)
- return snode
-
- def _add_node_to_current_heading(self, node):
- """Add the node to whatever the current active heading is"""
- self._active_headings[-1] += node
-
- def freeform(self, doc):
- """Add a piece of 'freeform' documentation to the document tree
-
- A 'freeform' document chunk doesn't relate to any particular
- symbol (for instance, it could be an introduction).
-
- If the freeform document starts with a line of the form
- '= Heading text', this is a section or subsection heading, with
- the heading level indicated by the number of '=' signs.
- """
-
- # QAPIDoc documentation says free-form documentation blocks
- # must have only a body section, nothing else.
- assert not doc.sections
- assert not doc.args
- assert not doc.features
- self._cur_doc = doc
-
- text = doc.body.text
- if re.match(r'=+ ', text):
- # Section/subsection heading (if present, will always be
- # the first line of the block)
- (heading, _, text) = text.partition('\n')
- (leader, _, heading) = heading.partition(' ')
- node = self._start_new_heading(heading, len(leader))
- if text == '':
- return
- else:
- node = nodes.container()
-
- self._parse_text_into_node(text, node)
- self._cur_doc = None
-
- def _parse_text_into_node(self, doctext, node):
- """Parse a chunk of QAPI-doc-format text into the node
-
- The doc comment can contain most inline rST markup, including
- bulleted and enumerated lists.
- As an extra permitted piece of markup, @var will be turned
- into ``var``.
- """
-
- # Handle the "@var means ``var`` case
- doctext = re.sub(r'@([\w-]+)', r'``\1``', doctext)
-
- rstlist = ViewList()
- for line in doctext.splitlines():
- # The reported line number will always be that of the start line
- # of the doc comment, rather than the actual location of the error.
- # Being more precise would require overhaul of the QAPIDoc class
- # to track lines more exactly within all the sub-parts of the doc
- # comment, as well as counting lines here.
- rstlist.append(line, self._cur_doc.info.fname,
- self._cur_doc.info.line)
- # Append a blank line -- in some cases rST syntax errors get
- # attributed to the line after one with actual text, and if there
- # isn't anything in the ViewList corresponding to that then Sphinx
- # 1.6's AutodocReporter will then misidentify the source/line location
- # in the error message (usually attributing it to the top-level
- # .rst file rather than the offending .json file). The extra blank
- # line won't affect the rendered output.
- rstlist.append("", self._cur_doc.info.fname, self._cur_doc.info.line)
- self._sphinx_directive.do_parse(rstlist, node)
-
- def get_document_node(self):
- """Return the root docutils node which makes up the document"""
- return self._top_node
-
-
-# Turn the black formatter on for the rest of the file.
-# fmt: on
-
-
class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
"""A QAPI schema visitor which adds Sphinx dependencies each module
diff --git a/docs/sphinx/qapidoc_legacy.py b/docs/sphinx/qapidoc_legacy.py
new file mode 100644
index 00000000000..679f38356b1
--- /dev/null
+++ b/docs/sphinx/qapidoc_legacy.py
@@ -0,0 +1,439 @@
+# coding=utf-8
+#
+# QEMU qapidoc QAPI file parsing extension
+#
+# Copyright (c) 2020 Linaro
+#
+# This work is licensed under the terms of the GNU GPLv2 or later.
+# See the COPYING file in the top-level directory.
+
+"""
+qapidoc is a Sphinx extension that implements the qapi-doc directive
+
+The purpose of this extension is to read the documentation comments
+in QAPI schema files, and insert them all into the current document.
+
+It implements one new rST directive, "qapi-doc::".
+Each qapi-doc:: directive takes one argument, which is the
+pathname of the schema file to process, relative to the source tree.
+
+The docs/conf.py file must set the qapidoc_srctree config value to
+the root of the QEMU source tree.
+
+The Sphinx documentation on writing extensions is at:
+https://www.sphinx-doc.org/en/master/development/index.html
+"""
+
+import re
+import textwrap
+
+from docutils import nodes
+from docutils.statemachine import ViewList
+from qapi.error import QAPISemError
+from qapi.gen import QAPISchemaVisitor
+from qapi.parser import QAPIDoc
+
+
+def dedent(text: str) -> str:
+ # Adjust indentation to make description text parse as paragraph.
+
+ lines = text.splitlines(True)
+ if re.match(r"\s+", lines[0]):
+ # First line is indented; description started on the line after
+ # the name. dedent the whole block.
+ return textwrap.dedent(text)
+
+ # Descr started on same line. Dedent line 2+.
+ return lines[0] + textwrap.dedent("".join(lines[1:]))
+
+
+class QAPISchemaGenRSTVisitor(QAPISchemaVisitor):
+ """A QAPI schema visitor which generates docutils/Sphinx nodes
+
+ This class builds up a tree of docutils/Sphinx nodes corresponding
+ to documentation for the various QAPI objects. To use it, first
+ create a QAPISchemaGenRSTVisitor object, and call its
+ visit_begin() method. Then you can call one of the two methods
+ 'freeform' (to add documentation for a freeform documentation
+ chunk) or 'symbol' (to add documentation for a QAPI symbol). These
+ will cause the visitor to build up the tree of document
+ nodes. Once you've added all the documentation via 'freeform' and
+ 'symbol' method calls, you can call 'get_document_nodes' to get
+ the final list of document nodes (in a form suitable for returning
+ from a Sphinx directive's 'run' method).
+ """
+ def __init__(self, sphinx_directive):
+ self._cur_doc = None
+ self._sphinx_directive = sphinx_directive
+ self._top_node = nodes.section()
+ self._active_headings = [self._top_node]
+
+ def _make_dlitem(self, term, defn):
+ """Return a dlitem node with the specified term and definition.
+
+ term should be a list of Text and literal nodes.
+ defn should be one of:
+ - a string, which will be handed to _parse_text_into_node
+ - a list of Text and literal nodes, which will be put into
+ a paragraph node
+ """
+ dlitem = nodes.definition_list_item()
+ dlterm = nodes.term('', '', *term)
+ dlitem += dlterm
+ if defn:
+ dldef = nodes.definition()
+ if isinstance(defn, list):
+ dldef += nodes.paragraph('', '', *defn)
+ else:
+ self._parse_text_into_node(defn, dldef)
+ dlitem += dldef
+ return dlitem
+
+ def _make_section(self, title):
+ """Return a section node with optional title"""
+ section = nodes.section(ids=[self._sphinx_directive.new_serialno()])
+ if title:
+ section += nodes.title(title, title)
+ return section
+
+ def _nodes_for_ifcond(self, ifcond, with_if=True):
+ """Return list of Text, literal nodes for the ifcond
+
+ Return a list which gives text like ' (If: condition)'.
+ If with_if is False, we don't return the "(If: " and ")".
+ """
+
+ doc = ifcond.docgen()
+ if not doc:
+ return []
+ doc = nodes.literal('', doc)
+ if not with_if:
+ return [doc]
+
+ nodelist = [nodes.Text(' ('), nodes.strong('', 'If: ')]
+ nodelist.append(doc)
+ nodelist.append(nodes.Text(')'))
+ return nodelist
+
+ def _nodes_for_one_member(self, member):
+ """Return list of Text, literal nodes for this member
+
+ Return a list of doctree nodes which give text like
+ 'name: type (optional) (If: ...)' suitable for use as the
+ 'term' part of a definition list item.
+ """
+ term = [nodes.literal('', member.name)]
+ if member.type.doc_type():
+ term.append(nodes.Text(': '))
+ term.append(nodes.literal('', member.type.doc_type()))
+ if member.optional:
+ term.append(nodes.Text(' (optional)'))
+ if member.ifcond.is_present():
+ term.extend(self._nodes_for_ifcond(member.ifcond))
+ return term
+
+ def _nodes_for_variant_when(self, branches, variant):
+ """Return list of Text, literal nodes for variant 'when' clause
+
+ Return a list of doctree nodes which give text like
+ 'when tagname is variant (If: ...)' suitable for use in
+ the 'branches' part of a definition list.
+ """
+ term = [nodes.Text(' when '),
+ nodes.literal('', branches.tag_member.name),
+ nodes.Text(' is '),
+ nodes.literal('', '"%s"' % variant.name)]
+ if variant.ifcond.is_present():
+ term.extend(self._nodes_for_ifcond(variant.ifcond))
+ return term
+
+ def _nodes_for_members(self, doc, what, base=None, branches=None):
+ """Return list of doctree nodes for the table of members"""
+ dlnode = nodes.definition_list()
+ for section in doc.args.values():
+ term = self._nodes_for_one_member(section.member)
+ # TODO drop fallbacks when undocumented members are outlawed
+ if section.text:
+ defn = dedent(section.text)
+ else:
+ defn = [nodes.Text('Not documented')]
+
+ dlnode += self._make_dlitem(term, defn)
+
+ if base:
+ dlnode += self._make_dlitem([nodes.Text('The members of '),
+ nodes.literal('', base.doc_type())],
+ None)
+
+ if branches:
+ for v in branches.variants:
+ if v.type.name == 'q_empty':
+ continue
+ assert not v.type.is_implicit()
+ term = [nodes.Text('The members of '),
+ nodes.literal('', v.type.doc_type())]
+ term.extend(self._nodes_for_variant_when(branches, v))
+ dlnode += self._make_dlitem(term, None)
+
+ if not dlnode.children:
+ return []
+
+ section = self._make_section(what)
+ section += dlnode
+ return [section]
+
+ def _nodes_for_enum_values(self, doc):
+ """Return list of doctree nodes for the table of enum values"""
+ seen_item = False
+ dlnode = nodes.definition_list()
+ for section in doc.args.values():
+ termtext = [nodes.literal('', section.member.name)]
+ if section.member.ifcond.is_present():
+ termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
+ # TODO drop fallbacks when undocumented members are outlawed
+ if section.text:
+ defn = dedent(section.text)
+ else:
+ defn = [nodes.Text('Not documented')]
+
+ dlnode += self._make_dlitem(termtext, defn)
+ seen_item = True
+
+ if not seen_item:
+ return []
+
+ section = self._make_section('Values')
+ section += dlnode
+ return [section]
+
+ def _nodes_for_arguments(self, doc, arg_type):
+ """Return list of doctree nodes for the arguments section"""
+ if arg_type and not arg_type.is_implicit():
+ assert not doc.args
+ section = self._make_section('Arguments')
+ dlnode = nodes.definition_list()
+ dlnode += self._make_dlitem(
+ [nodes.Text('The members of '),
+ nodes.literal('', arg_type.name)],
+ None)
+ section += dlnode
+ return [section]
+
+ return self._nodes_for_members(doc, 'Arguments')
+
+ def _nodes_for_features(self, doc):
+ """Return list of doctree nodes for the table of features"""
+ seen_item = False
+ dlnode = nodes.definition_list()
+ for section in doc.features.values():
+ dlnode += self._make_dlitem(
+ [nodes.literal('', section.member.name)], dedent(section.text))
+ seen_item = True
+
+ if not seen_item:
+ return []
+
+ section = self._make_section('Features')
+ section += dlnode
+ return [section]
+
+ def _nodes_for_sections(self, doc):
+ """Return list of doctree nodes for additional sections"""
+ nodelist = []
+ for section in doc.sections:
+ if section.kind == QAPIDoc.Kind.TODO:
+ # Hide TODO: sections
+ continue
+
+ if section.kind == QAPIDoc.Kind.PLAIN:
+ # Sphinx cannot handle sectionless titles;
+ # Instead, just append the results to the prior section.
+ container = nodes.container()
+ self._parse_text_into_node(section.text, container)
+ nodelist += container.children
+ continue
+
+ snode = self._make_section(section.kind.name.title())
+ self._parse_text_into_node(dedent(section.text), snode)
+ nodelist.append(snode)
+ return nodelist
+
+ def _nodes_for_if_section(self, ifcond):
+ """Return list of doctree nodes for the "If" section"""
+ nodelist = []
+ if ifcond.is_present():
+ snode = self._make_section('If')
+ snode += nodes.paragraph(
+ '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
+ )
+ nodelist.append(snode)
+ return nodelist
+
+ def _add_doc(self, typ, sections):
+ """Add documentation for a command/object/enum...
+
+ We assume we're documenting the thing defined in self._cur_doc.
+ typ is the type of thing being added ("Command", "Object", etc)
+
+ sections is a list of nodes for sections to add to the definition.
+ """
+
+ doc = self._cur_doc
+ snode = nodes.section(ids=[self._sphinx_directive.new_serialno()])
+ snode += nodes.title('', '', *[nodes.literal(doc.symbol, doc.symbol),
+ nodes.Text(' (' + typ + ')')])
+ self._parse_text_into_node(doc.body.text, snode)
+ for s in sections:
+ if s is not None:
+ snode += s
+ self._add_node_to_current_heading(snode)
+
+ def visit_enum_type(self, name, info, ifcond, features, members, prefix):
+ doc = self._cur_doc
+ self._add_doc('Enum',
+ self._nodes_for_enum_values(doc)
+ + self._nodes_for_features(doc)
+ + self._nodes_for_sections(doc)
+ + self._nodes_for_if_section(ifcond))
+
+ def visit_object_type(self, name, info, ifcond, features,
+ base, members, branches):
+ doc = self._cur_doc
+ if base and base.is_implicit():
+ base = None
+ self._add_doc('Object',
+ self._nodes_for_members(doc, 'Members', base, branches)
+ + self._nodes_for_features(doc)
+ + self._nodes_for_sections(doc)
+ + self._nodes_for_if_section(ifcond))
+
+ def visit_alternate_type(self, name, info, ifcond, features,
+ alternatives):
+ doc = self._cur_doc
+ self._add_doc('Alternate',
+ self._nodes_for_members(doc, 'Members')
+ + self._nodes_for_features(doc)
+ + self._nodes_for_sections(doc)
+ + self._nodes_for_if_section(ifcond))
+
+ def visit_command(self, name, info, ifcond, features, arg_type,
+ ret_type, gen, success_response, boxed, allow_oob,
+ allow_preconfig, coroutine):
+ doc = self._cur_doc
+ self._add_doc('Command',
+ self._nodes_for_arguments(doc, arg_type)
+ + self._nodes_for_features(doc)
+ + self._nodes_for_sections(doc)
+ + self._nodes_for_if_section(ifcond))
+
+ def visit_event(self, name, info, ifcond, features, arg_type, boxed):
+ doc = self._cur_doc
+ self._add_doc('Event',
+ self._nodes_for_arguments(doc, arg_type)
+ + self._nodes_for_features(doc)
+ + self._nodes_for_sections(doc)
+ + self._nodes_for_if_section(ifcond))
+
+ def symbol(self, doc, entity):
+ """Add documentation for one symbol to the document tree
+
+ This is the main entry point which causes us to add documentation
+ nodes for a symbol (which could be a 'command', 'object', 'event',
+ etc). We do this by calling 'visit' on the schema entity, which
+ will then call back into one of our visit_* methods, depending
+ on what kind of thing this symbol is.
+ """
+ self._cur_doc = doc
+ entity.visit(self)
+ self._cur_doc = None
+
+ def _start_new_heading(self, heading, level):
+ """Start a new heading at the specified heading level
+
+ Create a new section whose title is 'heading' and which is placed
+ in the docutils node tree as a child of the most recent level-1
+ heading. Subsequent document sections (commands, freeform doc chunks,
+ etc) will be placed as children of this new heading section.
+ """
+ if len(self._active_headings) < level:
+ raise QAPISemError(self._cur_doc.info,
+ 'Level %d subheading found outside a '
+ 'level %d heading'
+ % (level, level - 1))
+ snode = self._make_section(heading)
+ self._active_headings[level - 1] += snode
+ self._active_headings = self._active_headings[:level]
+ self._active_headings.append(snode)
+ return snode
+
+ def _add_node_to_current_heading(self, node):
+ """Add the node to whatever the current active heading is"""
+ self._active_headings[-1] += node
+
+ def freeform(self, doc):
+ """Add a piece of 'freeform' documentation to the document tree
+
+ A 'freeform' document chunk doesn't relate to any particular
+ symbol (for instance, it could be an introduction).
+
+ If the freeform document starts with a line of the form
+ '= Heading text', this is a section or subsection heading, with
+ the heading level indicated by the number of '=' signs.
+ """
+
+ # QAPIDoc documentation says free-form documentation blocks
+ # must have only a body section, nothing else.
+ assert not doc.sections
+ assert not doc.args
+ assert not doc.features
+ self._cur_doc = doc
+
+ text = doc.body.text
+ if re.match(r'=+ ', text):
+ # Section/subsection heading (if present, will always be
+ # the first line of the block)
+ (heading, _, text) = text.partition('\n')
+ (leader, _, heading) = heading.partition(' ')
+ node = self._start_new_heading(heading, len(leader))
+ if text == '':
+ return
+ else:
+ node = nodes.container()
+
+ self._parse_text_into_node(text, node)
+ self._cur_doc = None
+
+ def _parse_text_into_node(self, doctext, node):
+ """Parse a chunk of QAPI-doc-format text into the node
+
+ The doc comment can contain most inline rST markup, including
+ bulleted and enumerated lists.
+ As an extra permitted piece of markup, @var will be turned
+ into ``var``.
+ """
+
+ # Handle the "@var means ``var`` case
+ doctext = re.sub(r'@([\w-]+)', r'``\1``', doctext)
+
+ rstlist = ViewList()
+ for line in doctext.splitlines():
+ # The reported line number will always be that of the start line
+ # of the doc comment, rather than the actual location of the error.
+ # Being more precise would require overhaul of the QAPIDoc class
+ # to track lines more exactly within all the sub-parts of the doc
+ # comment, as well as counting lines here.
+ rstlist.append(line, self._cur_doc.info.fname,
+ self._cur_doc.info.line)
+ # Append a blank line -- in some cases rST syntax errors get
+ # attributed to the line after one with actual text, and if there
+ # isn't anything in the ViewList corresponding to that then Sphinx
+ # 1.6's AutodocReporter will then misidentify the source/line location
+ # in the error message (usually attributing it to the top-level
+ # .rst file rather than the offending .json file). The extra blank
+ # line won't affect the rendered output.
+ rstlist.append("", self._cur_doc.info.fname, self._cur_doc.info.line)
+ self._sphinx_directive.do_parse(rstlist, node)
+
+ def get_document_node(self):
+ """Return the root docutils node which makes up the document"""
+ return self._top_node
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 37/62] docs/qapidoc: Fix static typing on qapidoc.py
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (35 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 36/62] docs/qapidoc: split old implementation into qapidoc_legacy.py John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 38/62] do-not-merge John Snow
` (25 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Now that the legacy code is factored out, fix up the typing on the
remaining code in qapidoc.py. Add a type ignore to qapi_legacy.py to
prevent the errors there from bleeding out into qapidoc.py.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 40 ++++++++++++++++++++++-------------
docs/sphinx/qapidoc_legacy.py | 1 +
2 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index f4abf42e7bf..5246832b68c 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -24,17 +24,18 @@
https://www.sphinx-doc.org/en/master/development/index.html
"""
+from __future__ import annotations
+
import os
import sys
-from typing import List
+from typing import TYPE_CHECKING
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from qapi.error import QAPIError
-from qapi.gen import QAPISchemaVisitor
-from qapi.schema import QAPISchema
+from qapi.schema import QAPISchema, QAPISchemaVisitor
-from qapidoc_legacy import QAPISchemaGenRSTVisitor
+from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore
from sphinx import addnodes
from sphinx.directives.code import CodeBlock
from sphinx.errors import ExtensionError
@@ -42,6 +43,15 @@
from sphinx.util.nodes import nested_parse_with_titles
+if TYPE_CHECKING:
+ from typing import Any, List, Sequence
+
+ from docutils.statemachine import StringList
+
+ from sphinx.application import Sphinx
+ from sphinx.util.typing import ExtensionMetadata
+
+
__version__ = "1.0"
@@ -53,11 +63,11 @@ class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
schema file associated with each module in the QAPI input.
"""
- def __init__(self, env, qapidir):
+ def __init__(self, env: Any, qapidir: str) -> None:
self._env = env
self._qapidir = qapidir
- def visit_module(self, name):
+ def visit_module(self, name: str) -> None:
if name != "./builtin":
qapifile = self._qapidir + "/" + name
self._env.note_dependency(os.path.abspath(qapifile))
@@ -65,10 +75,10 @@ def visit_module(self, name):
class NestedDirective(Directive):
- def run(self):
+ def run(self) -> Sequence[nodes.Node]:
raise NotImplementedError
- def do_parse(self, rstlist, node):
+ def do_parse(self, rstlist: StringList, node: nodes.Node) -> None:
"""
Parse rST source lines and add them to the specified node
@@ -93,15 +103,15 @@ class QAPIDocDirective(NestedDirective):
}
has_content = False
- def new_serialno(self):
+ def new_serialno(self) -> str:
"""Return a unique new ID string suitable for use as a node's ID"""
env = self.state.document.settings.env
return "qapidoc-%d" % env.new_serialno("qapidoc")
- def transmogrify(self, schema) -> nodes.Element:
+ def transmogrify(self, schema: QAPISchema) -> nodes.Element:
raise NotImplementedError
- def legacy(self, schema) -> nodes.Element:
+ def legacy(self, schema: QAPISchema) -> nodes.Element:
vis = QAPISchemaGenRSTVisitor(self)
vis.visit_begin(schema)
for doc in schema.docs:
@@ -109,9 +119,9 @@ def legacy(self, schema) -> nodes.Element:
vis.symbol(doc, schema.lookup_entity(doc.symbol))
else:
vis.freeform(doc)
- return vis.get_document_node()
+ return vis.get_document_node() # type: ignore
- def run(self):
+ def run(self) -> Sequence[nodes.Node]:
env = self.state.document.settings.env
qapifile = env.config.qapidoc_srctree + "/" + self.arguments[0]
qapidir = os.path.dirname(qapifile)
@@ -185,7 +195,7 @@ def _highlightlang(self) -> addnodes.highlightlang:
)
return node
- def admonition_wrap(self, *content) -> List[nodes.Node]:
+ def admonition_wrap(self, *content: nodes.Node) -> List[nodes.Node]:
title = "Example:"
if "title" in self.options:
title = f"{title} {self.options['title']}"
@@ -231,7 +241,7 @@ def run(self) -> List[nodes.Node]:
return self.admonition_wrap(*content_nodes)
-def setup(app):
+def setup(app: Sphinx) -> ExtensionMetadata:
"""Register qapi-doc directive with Sphinx"""
app.add_config_value("qapidoc_srctree", None, "env")
app.add_directive("qapi-doc", QAPIDocDirective)
diff --git a/docs/sphinx/qapidoc_legacy.py b/docs/sphinx/qapidoc_legacy.py
index 679f38356b1..13520f4c26b 100644
--- a/docs/sphinx/qapidoc_legacy.py
+++ b/docs/sphinx/qapidoc_legacy.py
@@ -1,4 +1,5 @@
# coding=utf-8
+# type: ignore
#
# QEMU qapidoc QAPI file parsing extension
#
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 38/62] do-not-merge
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (36 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 37/62] docs/qapidoc: Fix static typing on qapidoc.py John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 39/62] docs/qapidoc: add transmogrifier class stub John Snow
` (24 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add strict typing to qapidoc.py for the remainder of this series.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi-lint.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/qapi-lint.sh b/scripts/qapi-lint.sh
index 738490352cd..f19c766c7cf 100755
--- a/scripts/qapi-lint.sh
+++ b/scripts/qapi-lint.sh
@@ -35,7 +35,7 @@ if [[ -f ../docs/sphinx/qapi_domain.py ]]; then
pushd ../docs/sphinx
set -x
- mypy --strict $files
+ PYTHONPATH=../../scripts/ mypy --follow-untyped-imports --strict $files qapidoc.py
flake8 --max-line-length=80 $files qapidoc.py
isort -c $files qapidoc.py
black --line-length 80 --check $files qapidoc.py
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 39/62] docs/qapidoc: add transmogrifier class stub
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (37 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 38/62] do-not-merge John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 40/62] docs/qapidoc: add visit_module() method John Snow
` (23 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add the beginnings of the Transmogrifier class by adding the rST
conversion helpers that will be used to build the virtual rST document.
This version of the class does not actually "do anything" yet; each
individual feature is added one-at-a-time in the forthcoming commits.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 73 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 70 insertions(+), 3 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 5246832b68c..c243bb6faaa 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -26,14 +26,17 @@
from __future__ import annotations
+from contextlib import contextmanager
import os
import sys
from typing import TYPE_CHECKING
from docutils import nodes
from docutils.parsers.rst import Directive, directives
+from docutils.statemachine import StringList
from qapi.error import QAPIError
from qapi.schema import QAPISchema, QAPISchemaVisitor
+from qapi.source import QAPISourceInfo
from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore
from sphinx import addnodes
@@ -44,9 +47,12 @@
if TYPE_CHECKING:
- from typing import Any, List, Sequence
-
- from docutils.statemachine import StringList
+ from typing import (
+ Any,
+ Generator,
+ List,
+ Sequence,
+ )
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata
@@ -55,6 +61,67 @@
__version__ = "1.0"
+class Transmogrifier:
+ def __init__(self) -> None:
+ self._result = StringList()
+ self.indent = 0
+
+ # General-purpose rST generation functions
+
+ def get_indent(self) -> str:
+ return " " * self.indent
+
+ @contextmanager
+ def indented(self) -> Generator[None]:
+ self.indent += 1
+ try:
+ yield
+ finally:
+ self.indent -= 1
+
+ def add_line_raw(self, line: str, source: str, *lineno: int) -> None:
+ """Append one line of generated reST to the output."""
+
+ # NB: Sphinx uses zero-indexed lines; subtract one.
+ lineno = tuple((n - 1 for n in lineno))
+
+ if line.strip():
+ # not a blank line
+ self._result.append(
+ self.get_indent() + line.rstrip("\n"), source, *lineno
+ )
+ else:
+ self._result.append("", source, *lineno)
+
+ def add_line(self, content: str, info: QAPISourceInfo) -> None:
+ # NB: We *require* an info object; this works out OK because we
+ # don't document built-in objects that don't have
+ # one. Everything else should.
+ self.add_line_raw(content, info.fname, info.line)
+
+ def add_lines(
+ self,
+ content: str,
+ info: QAPISourceInfo,
+ ) -> None:
+ lines = content.splitlines(True)
+ for i, line in enumerate(lines):
+ self.add_line_raw(line, info.fname, info.line + i)
+
+ def ensure_blank_line(self) -> None:
+ # Empty document -- no blank line required.
+ if not self._result:
+ return
+
+ # Last line isn't blank, add one.
+ if self._result[-1].strip(): # pylint: disable=no-member
+ fname, line = self._result.info(-1)
+ assert isinstance(line, int)
+ # New blank line is credited to one-after the current last line.
+ # +2: correct for zero/one index, then increment by one.
+ self.add_line_raw("", fname, line + 2)
+
+
class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
"""A QAPI schema visitor which adds Sphinx dependencies each module
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 40/62] docs/qapidoc: add visit_module() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (38 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 39/62] docs/qapidoc: add transmogrifier class stub John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 41/62] qapi/source: allow multi-line QAPISourceInfo advancing John Snow
` (22 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This method annotates the start of a new module, crediting the source
location to the first line of the module file.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index c243bb6faaa..6de8c900543 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -28,6 +28,7 @@
from contextlib import contextmanager
import os
+from pathlib import Path
import sys
from typing import TYPE_CHECKING
@@ -121,6 +122,14 @@ def ensure_blank_line(self) -> None:
# +2: correct for zero/one index, then increment by one.
self.add_line_raw("", fname, line + 2)
+ # Transmogrification core methods
+
+ def visit_module(self, path: str) -> None:
+ name = Path(path).stem
+ # module directives are credited to the first line of a module file.
+ self.add_line_raw(f".. qapi:module:: {name}", path, 1)
+ self.ensure_blank_line()
+
class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
"""A QAPI schema visitor which adds Sphinx dependencies each module
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 41/62] qapi/source: allow multi-line QAPISourceInfo advancing
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (39 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 40/62] docs/qapidoc: add visit_module() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 20:59 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 42/62] docs/qapidoc: add visit_freeform() method John Snow
` (21 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This is for the sake of the new rST generator (the "transmogrifier") so
we can advance multiple lines on occasion while keeping the
generated<-->source mappings accurate.
next_line now simply takes an optional n parameter which chooses the
number of lines to advance.
RFC: Here's the exorbitant detail on why I want this:
This is used mainly when converting section syntax in free-form
documentation to more traditional rST section header syntax, which
does not always line up 1:1 for line counts.
For example:
```
##
# = Section <-- Info is pointing here, "L1"
#
# Lorem Ipsum
##
```
would be transformed to rST as:
```
======= <-- L1
Section <-- L1
======= <-- L1
<-- L2
Lorem Ipsum <-- L3
```
After consuming the single "Section" line from the source, we want to
advance the source pointer to the next non-empty line which requires
jumping by more than one line.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
scripts/qapi/source.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/qapi/source.py b/scripts/qapi/source.py
index 7b379fdc925..ffdc3f482ac 100644
--- a/scripts/qapi/source.py
+++ b/scripts/qapi/source.py
@@ -47,9 +47,9 @@ def set_defn(self, meta: str, name: str) -> None:
self.defn_meta = meta
self.defn_name = name
- def next_line(self: T) -> T:
+ def next_line(self: T, n: int = 1) -> T:
info = copy.copy(self)
- info.line += 1
+ info.line += n
return info
def loc(self) -> str:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 42/62] docs/qapidoc: add visit_freeform() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (40 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 41/62] qapi/source: allow multi-line QAPISourceInfo advancing John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:01 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 43/62] docs/qapidoc: add preamble() method John Snow
` (20 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 50 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 6de8c900543..cf5dbb0133d 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -29,6 +29,7 @@
from contextlib import contextmanager
import os
from pathlib import Path
+import re
import sys
from typing import TYPE_CHECKING
@@ -55,6 +56,8 @@
Sequence,
)
+ from qapi.parser import QAPIDoc
+
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata
@@ -130,6 +133,53 @@ def visit_module(self, path: str) -> None:
self.add_line_raw(f".. qapi:module:: {name}", path, 1)
self.ensure_blank_line()
+ def visit_freeform(self, doc: QAPIDoc) -> None:
+ # TODO: Once the old qapidoc transformer is deprecated, freeform
+ # sections can be updated to pure rST, and this transformed removed.
+ #
+ # For now, translate our micro-format into rST. Code adapted
+ # from Peter Maydell's freeform().
+
+ assert len(doc.all_sections) == 1, doc.all_sections
+ body = doc.all_sections[0]
+ text = body.text
+ info = doc.info
+
+ if re.match(r"=+ ", text):
+ # Section/subsection heading (if present, will always be the
+ # first line of the block)
+ (heading, _, text) = text.partition("\n")
+ (leader, _, heading) = heading.partition(" ")
+ level = len(leader) + 1 # Implicit +1 for heading in .rST stub
+
+ # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#sections
+ markers = {
+ 1: "#",
+ 2: "*",
+ 3: "=",
+ 4: "-",
+ 5: "^",
+ 6: '"',
+ }
+ overline = level <= 2
+ marker = markers[level]
+
+ self.ensure_blank_line()
+ # This credits all 2 or 3 lines to the single source line.
+ if overline:
+ self.add_line(marker * len(heading), info)
+ self.add_line(heading, info)
+ self.add_line(marker * len(heading), info)
+ self.ensure_blank_line()
+
+ # Eat blank line(s) and advance info
+ trimmed = text.lstrip("\n")
+ text = trimmed
+ info = info.next_line(len(text) - len(trimmed) + 1)
+
+ self.add_lines(text, info)
+ self.ensure_blank_line()
+
class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
"""A QAPI schema visitor which adds Sphinx dependencies each module
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 43/62] docs/qapidoc: add preamble() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (41 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 42/62] docs/qapidoc: add visit_freeform() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:03 ` Markus Armbruster
2025-03-10 9:46 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 44/62] docs/qapidoc: add visit_paragraph() method John Snow
` (19 subsequent siblings)
62 siblings, 2 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This method adds the options/preamble to each definition block. Notably,
:since: and :ifcond: are added, as are any "special features" such as
:deprecated: and :unstable:.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 41 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 38 insertions(+), 3 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index cf5dbb0133d..d8bf0073dfa 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -37,7 +37,12 @@
from docutils.parsers.rst import Directive, directives
from docutils.statemachine import StringList
from qapi.error import QAPIError
-from qapi.schema import QAPISchema, QAPISchemaVisitor
+from qapi.parser import QAPIDoc
+from qapi.schema import (
+ QAPISchema,
+ QAPISchemaDefinition,
+ QAPISchemaVisitor,
+)
from qapi.source import QAPISourceInfo
from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore
@@ -56,8 +61,6 @@
Sequence,
)
- from qapi.parser import QAPIDoc
-
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata
@@ -125,6 +128,38 @@ def ensure_blank_line(self) -> None:
# +2: correct for zero/one index, then increment by one.
self.add_line_raw("", fname, line + 2)
+ # Transmogrification helpers
+
+ def preamble(self, ent: QAPISchemaDefinition) -> None:
+ """
+ Generate option lines for qapi entity directives.
+ """
+ if ent.doc and ent.doc.since:
+ assert ent.doc.since.kind == QAPIDoc.Kind.SINCE
+ # Generated from the entity's docblock; info location is exact.
+ self.add_line(f":since: {ent.doc.since.text}", ent.doc.since.info)
+
+ if ent.ifcond.is_present():
+ doc = ent.ifcond.docgen()
+ assert ent.info
+ # Generated from entity definition; info location is approximate.
+ self.add_line(f":ifcond: {doc}", ent.info)
+
+ # Hoist special features such as :deprecated: and :unstable:
+ # into the options block for the entity. If, in the future, new
+ # special features are added, qapi-domain will chirp about
+ # unrecognized options and fail until they are handled in
+ # qapi-domain.
+ for feat in ent.features:
+ if feat.is_special():
+ # FIXME: handle ifcond if present. How to display that
+ # information is TBD.
+ # Generated from entity def; info location is approximate.
+ assert feat.info
+ self.add_line(f":{feat.name}:", feat.info)
+
+ self.ensure_blank_line()
+
# Transmogrification core methods
def visit_module(self, path: str) -> None:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 44/62] docs/qapidoc: add visit_paragraph() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (42 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 43/62] docs/qapidoc: add preamble() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 45/62] docs/qapidoc: add visit_errors() method John Snow
` (18 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This transforms "formerly known as untagged sections" into our pure
intermediate rST format. These sections are already pure rST, so this
method doesn't do a whole lot except ensure appropriate newlines.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index d8bf0073dfa..b96445f0802 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -130,6 +130,15 @@ def ensure_blank_line(self) -> None:
# Transmogrification helpers
+ def visit_paragraph(self, section: QAPIDoc.Section) -> None:
+ # Squelch empty paragraphs.
+ if not section.text:
+ return
+
+ self.ensure_blank_line()
+ self.add_lines(section.text, section.info)
+ self.ensure_blank_line()
+
def preamble(self, ent: QAPISchemaDefinition) -> None:
"""
Generate option lines for qapi entity directives.
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 45/62] docs/qapidoc: add visit_errors() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (43 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 44/62] docs/qapidoc: add visit_paragraph() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:05 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 46/62] docs/qapidoc: add format_type() method John Snow
` (17 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Notably, this method does not currently address the formatting issues
present with the "errors" section in QAPIDoc and just vomits the text
verbatim into the rST doc, with somewhat inconsistent results.
To be addressed in a future revision.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index b96445f0802..14feafe866e 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -139,6 +139,12 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None:
self.add_lines(section.text, section.info)
self.ensure_blank_line()
+ def visit_errors(self, section: QAPIDoc.Section) -> None:
+ # FIXME: the formatting for errors may be inconsistent and may
+ # or may not require different newline placement to ensure
+ # proper rendering as a nested list.
+ self.add_lines(f":error:\n{section.text}", section.info)
+
def preamble(self, ent: QAPISchemaDefinition) -> None:
"""
Generate option lines for qapi entity directives.
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 46/62] docs/qapidoc: add format_type() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (44 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 45/62] docs/qapidoc: add visit_errors() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 47/62] docs/qapidoc: add add_field() and generate_field() helper methods John Snow
` (16 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This method is responsible for generating a type name for a given member
with the correct annotations for the QAPI domain. Features and enums do
not *have* types, so they return None. Everything else returns the type
name with a "?" suffix if that type is optional, and ensconced in
[brackets] if it's an array type.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 14feafe866e..0f895a3624a 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -40,7 +40,13 @@
from qapi.parser import QAPIDoc
from qapi.schema import (
QAPISchema,
+ QAPISchemaArrayType,
QAPISchemaDefinition,
+ QAPISchemaEnumMember,
+ QAPISchemaFeature,
+ QAPISchemaMember,
+ QAPISchemaObjectTypeMember,
+ QAPISchemaType,
QAPISchemaVisitor,
)
from qapi.source import QAPISourceInfo
@@ -58,7 +64,9 @@
Any,
Generator,
List,
+ Optional,
Sequence,
+ Union,
)
from sphinx.application import Sphinx
@@ -128,6 +136,30 @@ def ensure_blank_line(self) -> None:
# +2: correct for zero/one index, then increment by one.
self.add_line_raw("", fname, line + 2)
+ def format_type(
+ self, ent: Union[QAPISchemaDefinition | QAPISchemaMember]
+ ) -> Optional[str]:
+ if isinstance(ent, (QAPISchemaEnumMember, QAPISchemaFeature)):
+ return None
+
+ qapi_type = ent
+ optional = False
+ if isinstance(ent, QAPISchemaObjectTypeMember):
+ qapi_type = ent.type
+ optional = ent.optional
+
+ if isinstance(qapi_type, QAPISchemaArrayType):
+ ret = f"[{qapi_type.element_type.doc_type()}]"
+ else:
+ assert isinstance(qapi_type, QAPISchemaType)
+ tmp = qapi_type.doc_type()
+ assert tmp
+ ret = tmp
+ if optional:
+ ret += "?"
+
+ return ret
+
# Transmogrification helpers
def visit_paragraph(self, section: QAPIDoc.Section) -> None:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 47/62] docs/qapidoc: add add_field() and generate_field() helper methods
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (45 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 46/62] docs/qapidoc: add format_type() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 48/62] docs/qapidoc: add visit_feature() method John Snow
` (15 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
These are simple rST generation methods that assist in getting the types
and formatting correct for a field list entry. add_field() is a more
raw, direct call while generate_field() is intended to be used for
generating the correct field from a member object.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 0f895a3624a..b87ce288837 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -136,6 +136,20 @@ def ensure_blank_line(self) -> None:
# +2: correct for zero/one index, then increment by one.
self.add_line_raw("", fname, line + 2)
+ def add_field(
+ self,
+ kind: str,
+ name: str,
+ body: str,
+ info: QAPISourceInfo,
+ typ: Optional[str] = None,
+ ) -> None:
+ if typ:
+ text = f":{kind} {typ} {name}: {body}"
+ else:
+ text = f":{kind} {name}: {body}"
+ self.add_lines(text, info)
+
def format_type(
self, ent: Union[QAPISchemaDefinition | QAPISchemaMember]
) -> Optional[str]:
@@ -160,6 +174,16 @@ def format_type(
return ret
+ def generate_field(
+ self,
+ kind: str,
+ member: QAPISchemaMember,
+ body: str,
+ info: QAPISourceInfo,
+ ) -> None:
+ typ = self.format_type(member)
+ self.add_field(kind, member.name, body, info, typ)
+
# Transmogrification helpers
def visit_paragraph(self, section: QAPIDoc.Section) -> None:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 48/62] docs/qapidoc: add visit_feature() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (46 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 47/62] docs/qapidoc: add add_field() and generate_field() helper methods John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:06 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 49/62] docs/qapidoc: prepare to record entity being transmogrified John Snow
` (14 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This adds a simple ":feat name: lorem ipsum ..." line to the generated
rST document, so at the moment it's only for "top level" features.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index b87ce288837..eaea19af7ac 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -195,6 +195,15 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None:
self.add_lines(section.text, section.info)
self.ensure_blank_line()
+ def visit_feature(self, section: QAPIDoc.ArgSection) -> None:
+ # FIXME - ifcond for features is not handled at all yet!
+ # Proposal: decorate the right-hand column with some graphical
+ # element to indicate conditional availability?
+ assert section.text # Guaranteed by parser.py
+ assert section.member
+
+ self.generate_field("feat", section.member, section.text, section.info)
+
def visit_errors(self, section: QAPIDoc.Section) -> None:
# FIXME: the formatting for errors may be inconsistent and may
# or may not require different newline placement to ensure
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 49/62] docs/qapidoc: prepare to record entity being transmogrified
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (47 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 48/62] docs/qapidoc: add visit_feature() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 50/62] docs/qapidoc: add visit_returns() method John Snow
` (13 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Prepare to keep a record of which entity we're working on documenting
for the purposes of being able to change certain generative features
conditionally and create stronger assertions.
If you find yourself asking: "Wait, but where does the current entity
actually get recorded?!", you're right! That part comes with the
visit_entity() implementation, which gets added later.
This patch is front-loaded for the sake of type checking in the
forthcoming commits before visit_entity() is ready to be added.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index eaea19af7ac..834f12ba6e9 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -78,9 +78,15 @@
class Transmogrifier:
def __init__(self) -> None:
+ self._curr_ent: Optional[QAPISchemaDefinition] = None
self._result = StringList()
self.indent = 0
+ @property
+ def entity(self) -> QAPISchemaDefinition:
+ assert self._curr_ent is not None
+ return self._curr_ent
+
# General-purpose rST generation functions
def get_indent(self) -> str:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 50/62] docs/qapidoc: add visit_returns() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (48 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 49/62] docs/qapidoc: prepare to record entity being transmogrified John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:08 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 51/62] docs/qapidoc: add visit_member() method John Snow
` (12 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Generates :returns: fields for explicit returns statements. Note that
this does not presently handle undocumented returns, which is handled in
a later commit.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 834f12ba6e9..85e7367ad79 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -41,6 +41,7 @@
from qapi.schema import (
QAPISchema,
QAPISchemaArrayType,
+ QAPISchemaCommand,
QAPISchemaDefinition,
QAPISchemaEnumMember,
QAPISchemaFeature,
@@ -210,6 +211,20 @@ def visit_feature(self, section: QAPIDoc.ArgSection) -> None:
self.generate_field("feat", section.member, section.text, section.info)
+ def visit_returns(self, section: QAPIDoc.Section) -> None:
+ assert isinstance(self.entity, QAPISchemaCommand)
+ rtype = self.entity.ret_type
+ # q_empty can produce None, but we won't be documenting anything
+ # without an explicit return statement in the doc block, and we
+ # should not have any such explicit statements when there is no
+ # return value.
+ assert rtype
+
+ typ = self.format_type(rtype)
+ assert typ
+ assert section.text # We don't expect empty returns sections.
+ self.add_field("return", typ, section.text, section.info)
+
def visit_errors(self, section: QAPIDoc.Section) -> None:
# FIXME: the formatting for errors may be inconsistent and may
# or may not require different newline placement to ensure
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 51/62] docs/qapidoc: add visit_member() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (49 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 50/62] docs/qapidoc: add visit_returns() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:09 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 52/62] docs/qapidoc: add visit_sections() method John Snow
` (11 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This method is used for generating the "members" of a wide variety of
things, including structs, unions, enums, alternates, etc. The field
name it uses to do so is dependent on the type of entity the "member"
belongs to.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 85e7367ad79..db11c2ae933 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -78,6 +78,16 @@
class Transmogrifier:
+ # Field names used for different entity types:
+ field_types = {
+ "enum": "value",
+ "struct": "memb",
+ "union": "memb",
+ "event": "memb",
+ "command": "arg",
+ "alternate": "alt",
+ }
+
def __init__(self) -> None:
self._curr_ent: Optional[QAPISchemaDefinition] = None
self._result = StringList()
@@ -88,6 +98,10 @@ def entity(self) -> QAPISchemaDefinition:
assert self._curr_ent is not None
return self._curr_ent
+ @property
+ def member_field_type(self) -> str:
+ return self.field_types[self.entity.meta]
+
# General-purpose rST generation functions
def get_indent(self) -> str:
@@ -202,6 +216,19 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None:
self.add_lines(section.text, section.info)
self.ensure_blank_line()
+ def visit_member(self, section: QAPIDoc.ArgSection) -> None:
+ # TODO: ifcond for members
+ # TODO?: features for members (documented at entity-level,
+ # but sometimes defined per-member. Should we add such
+ # information to member descriptions when we can?)
+ assert section.text and section.member
+ self.generate_field(
+ self.member_field_type,
+ section.member,
+ section.text,
+ section.info,
+ )
+
def visit_feature(self, section: QAPIDoc.ArgSection) -> None:
# FIXME - ifcond for features is not handled at all yet!
# Proposal: decorate the right-hand column with some graphical
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 52/62] docs/qapidoc: add visit_sections() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (50 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 51/62] docs/qapidoc: add visit_member() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:10 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 53/62] docs/qapidoc: add visit_entity() John Snow
` (10 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Implement the actual main dispatch method that processes and handles the
list of doc sections for a given QAPI entity.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index db11c2ae933..78bec91271f 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -288,6 +288,31 @@ def preamble(self, ent: QAPISchemaDefinition) -> None:
self.ensure_blank_line()
+ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
+ sections = ent.doc.all_sections if ent.doc else []
+
+ # Add sections *in the order they are documented*:
+ for section in sections:
+ if section.kind == QAPIDoc.Kind.PLAIN:
+ self.visit_paragraph(section)
+ elif section.kind == QAPIDoc.Kind.MEMBER:
+ assert isinstance(section, QAPIDoc.ArgSection)
+ self.visit_member(section)
+ elif section.kind == QAPIDoc.Kind.FEATURE:
+ assert isinstance(section, QAPIDoc.ArgSection)
+ self.visit_feature(section)
+ elif section.kind in (QAPIDoc.Kind.SINCE, QAPIDoc.Kind.TODO):
+ # Since is handled in preamble, TODO is skipped intentionally.
+ pass
+ elif section.kind == QAPIDoc.Kind.RETURNS:
+ self.visit_returns(section)
+ elif section.kind == QAPIDoc.Kind.ERRORS:
+ self.visit_errors(section)
+ else:
+ assert False
+
+ self.ensure_blank_line()
+
# Transmogrification core methods
def visit_module(self, path: str) -> None:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 53/62] docs/qapidoc: add visit_entity()
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (51 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 52/62] docs/qapidoc: add visit_sections() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 54/62] docs/qapidoc: implement transmogrify() method John Snow
` (9 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Finally, the core entry method for a qapi entity.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 78bec91271f..5fd8a7c1fc1 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -78,6 +78,8 @@
class Transmogrifier:
+ # pylint: disable=too-many-public-methods
+
# Field names used for different entity types:
field_types = {
"enum": "value",
@@ -368,6 +370,25 @@ def visit_freeform(self, doc: QAPIDoc) -> None:
self.add_lines(text, info)
self.ensure_blank_line()
+ def visit_entity(self, ent: QAPISchemaDefinition) -> None:
+ assert ent.info
+
+ try:
+ self._curr_ent = ent
+
+ # Squish structs and unions together into an "object" directive.
+ meta = ent.meta
+ if meta in ("struct", "union"):
+ meta = "object"
+
+ # This line gets credited to the start of the /definition/.
+ self.add_line(f".. qapi:{meta}:: {ent.name}", ent.info)
+ with self.indented():
+ self.preamble(ent)
+ self.visit_sections(ent)
+ finally:
+ self._curr_ent = None
+
class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
"""A QAPI schema visitor which adds Sphinx dependencies each module
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 54/62] docs/qapidoc: implement transmogrify() method
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (52 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 53/62] docs/qapidoc: add visit_entity() John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-10 12:42 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 55/62] docs/qapidoc: process @foo into ``foo`` John Snow
` (8 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This is the true top-level processor for the new transmogrifier;
responsible both for generating the intermediate rST and then running
the nested parse on that generated document to produce the final
docutils tree that is then - very finally - postprocessed by sphinx for
final rendering to HTML &c.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 49 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 48 insertions(+), 1 deletion(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 5fd8a7c1fc1..11b8fca21ec 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -2,6 +2,7 @@
#
# QEMU qapidoc QAPI file parsing extension
#
+# Copyright (c) 2024 Red Hat
# Copyright (c) 2020 Linaro
#
# This work is licensed under the terms of the GNU GPLv2 or later.
@@ -24,6 +25,8 @@
https://www.sphinx-doc.org/en/master/development/index.html
"""
+# pylint: disable=too-many-lines
+
from __future__ import annotations
from contextlib import contextmanager
@@ -56,6 +59,7 @@
from sphinx import addnodes
from sphinx.directives.code import CodeBlock
from sphinx.errors import ExtensionError
+from sphinx.util import logging
from sphinx.util.docutils import switch_source_input
from sphinx.util.nodes import nested_parse_with_titles
@@ -76,6 +80,8 @@
__version__ = "1.0"
+logger = logging.getLogger(__name__)
+
class Transmogrifier:
# pylint: disable=too-many-public-methods
@@ -95,6 +101,10 @@ def __init__(self) -> None:
self._result = StringList()
self.indent = 0
+ @property
+ def result(self) -> StringList:
+ return self._result
+
@property
def entity(self) -> QAPISchemaDefinition:
assert self._curr_ent is not None
@@ -444,7 +454,43 @@ def new_serialno(self) -> str:
return "qapidoc-%d" % env.new_serialno("qapidoc")
def transmogrify(self, schema: QAPISchema) -> nodes.Element:
- raise NotImplementedError
+ logger.info("Transmogrifying QAPI to rST ...")
+ vis = Transmogrifier()
+ modules = set()
+
+ for doc in schema.docs:
+ module_source = doc.info.fname
+ if module_source not in modules:
+ vis.visit_module(module_source)
+ modules.add(module_source)
+
+ if doc.symbol:
+ ent = schema.lookup_entity(doc.symbol)
+ assert isinstance(ent, QAPISchemaDefinition)
+ vis.visit_entity(ent)
+ else:
+ vis.visit_freeform(doc)
+
+ logger.info("Transmogrification complete.")
+
+ contentnode = nodes.section()
+ content = vis.result
+ titles_allowed = True
+
+ logger.info("Transmogrifier running nested parse ...")
+ with switch_source_input(self.state, content):
+ if titles_allowed:
+ node: nodes.Element = nodes.section()
+ node.document = self.state.document
+ nested_parse_with_titles(self.state, content, contentnode)
+ else:
+ node = nodes.paragraph()
+ node.document = self.state.document
+ self.state.nested_parse(content, 0, contentnode)
+ logger.info("Transmogrifier's nested parse completed.")
+ sys.stdout.flush()
+
+ return contentnode
def legacy(self, schema: QAPISchema) -> nodes.Element:
vis = QAPISchemaGenRSTVisitor(self)
@@ -578,6 +624,7 @@ def run(self) -> List[nodes.Node]:
def setup(app: Sphinx) -> ExtensionMetadata:
"""Register qapi-doc directive with Sphinx"""
+ app.setup_extension("qapi_domain")
app.add_config_value("qapidoc_srctree", None, "env")
app.add_directive("qapi-doc", QAPIDocDirective)
app.add_directive("qmp-example", QMPExample)
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 55/62] docs/qapidoc: process @foo into ``foo``
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (53 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 54/62] docs/qapidoc: implement transmogrify() method John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 56/62] docs/qapidoc: add intermediate output debugger John Snow
` (7 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add support for the special QAPI doc syntax to process @references as
``preformatted text``. At the moment, there are no actual
cross-references for individual members, so there is nothing to link
against. For now, process it identically to how we did in the old
qapidoc system.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 11b8fca21ec..43091b1fabc 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -305,6 +305,9 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
# Add sections *in the order they are documented*:
for section in sections:
+ # @var is translated to ``var``:
+ section.text = re.sub(r"@([\w-]+)", r"``\1``", section.text)
+
if section.kind == QAPIDoc.Kind.PLAIN:
self.visit_paragraph(section)
elif section.kind == QAPIDoc.Kind.MEMBER:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 56/62] docs/qapidoc: add intermediate output debugger
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (54 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 55/62] docs/qapidoc: process @foo into ``foo`` John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:17 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 57/62] docs/qapidoc: Add "the members of" pointers John Snow
` (6 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add debugging output for the qapidoc transmogrifier - setting DEBUG=1
will produce .ir files (one for each qapidoc directive) that write the
generated rst file to disk to allow for easy debugging and verification
of the generated document.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 41 +++++++++++++++++++++++++++++++++++++----
1 file changed, 37 insertions(+), 4 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 43091b1fabc..e8719fac4c1 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -37,7 +37,7 @@
from typing import TYPE_CHECKING
from docutils import nodes
-from docutils.parsers.rst import Directive, directives
+from docutils.parsers.rst import directives
from docutils.statemachine import StringList
from qapi.error import QAPIError
from qapi.parser import QAPIDoc
@@ -60,7 +60,7 @@
from sphinx.directives.code import CodeBlock
from sphinx.errors import ExtensionError
from sphinx.util import logging
-from sphinx.util.docutils import switch_source_input
+from sphinx.util.docutils import SphinxDirective, switch_source_input
from sphinx.util.nodes import nested_parse_with_titles
@@ -422,7 +422,7 @@ def visit_module(self, name: str) -> None:
super().visit_module(name)
-class NestedDirective(Directive):
+class NestedDirective(SphinxDirective):
def run(self) -> Sequence[nodes.Node]:
raise NotImplementedError
@@ -491,10 +491,43 @@ def transmogrify(self, schema: QAPISchema) -> nodes.Element:
node.document = self.state.document
self.state.nested_parse(content, 0, contentnode)
logger.info("Transmogrifier's nested parse completed.")
+
+ if self.env.app.verbosity >= 2 or os.environ.get("DEBUG"):
+ argname = "_".join(Path(self.arguments[0]).parts)
+ name = Path(argname).stem + ".ir"
+ self.write_intermediate(content, name)
+
sys.stdout.flush()
-
return contentnode
+ def write_intermediate(self, content: StringList, filename: str) -> None:
+ logger.info(
+ "writing intermediate rST for '%s' to '%s'",
+ self.arguments[0],
+ filename,
+ )
+
+ srctree = Path(self.env.app.config.qapidoc_srctree).resolve()
+ outlines = []
+ lcol_width = 0
+
+ for i, line in enumerate(content):
+ src, lineno = content.info(i)
+ srcpath = Path(src).resolve()
+ srcpath = srcpath.relative_to(srctree)
+
+ lcol = f"{srcpath}:{lineno:04d}"
+ lcol_width = max(lcol_width, len(lcol))
+ outlines.append((lcol, line))
+
+ with open(filename, "w", encoding="UTF-8") as outfile:
+ for lcol, rcol in outlines:
+ outfile.write(lcol.rjust(lcol_width))
+ outfile.write(" |")
+ if rcol:
+ outfile.write(f" {rcol}")
+ outfile.write("\n")
+
def legacy(self, schema: QAPISchema) -> nodes.Element:
vis = QAPISchemaGenRSTVisitor(self)
vis.visit_begin(schema)
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 57/62] docs/qapidoc: Add "the members of" pointers
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (55 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 56/62] docs/qapidoc: add intermediate output debugger John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 58/62] docs/qapidoc: generate entries for undocumented members John Snow
` (5 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Add "the members of ..." pointers to Members and Arguments lists where
appropriate, with clickable cross-references - so it's a slight
improvement over the old system :)
This patch is meant to be a temporary solution until we can review and
merge the inliner.
The implementation of this patch is a little bit of a hack: Sphinx is
not designed to allow you to mix fields of different "type"; i.e. mixing
member descriptions and free-form text under the same heading. To
accomplish this with a minimum of hackery, we technically document a
"dummy field" and then just strip off the documentation for that dummy
field in a post-processing step. We use the "q_dummy" variable for this
purpose, then strip it back out before final processing. If this
processing step should fail, you'll see warnings for a bad
cross-reference. (So if you don't see any, it must be working!)
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapi_domain.py | 22 +++++++++++++--
docs/sphinx/qapidoc.py | 58 +++++++++++++++++++++++++++++++++++++-
2 files changed, 77 insertions(+), 3 deletions(-)
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index ca5e878c8ad..c2c98a80994 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -428,6 +428,24 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str:
return ""
+class SpecialTypedField(CompatTypedField):
+ def make_field(self, *args: Any, **kwargs: Any) -> nodes.field:
+ ret = super().make_field(*args, **kwargs)
+
+ # Look for the characteristic " -- " text node that Sphinx
+ # inserts for each TypedField entry ...
+ for node in ret.traverse(lambda n: str(n) == " -- "):
+ par = node.parent
+ if par.children[0].astext() != "q_dummy":
+ continue
+
+ # If the first node's text is q_dummy, this is a dummy
+ # field we want to strip down to just its contents.
+ del par.children[:-1]
+
+ return ret
+
+
class QAPICommand(QAPIObject):
"""Description of a QAPI Command."""
@@ -435,7 +453,7 @@ class QAPICommand(QAPIObject):
doc_field_types.extend(
[
# :arg TypeName ArgName: descr
- CompatTypedField(
+ SpecialTypedField(
"argument",
label=_("Arguments"),
names=("arg",),
@@ -503,7 +521,7 @@ class QAPIObjectWithMembers(QAPIObject):
doc_field_types.extend(
[
# :member type name: descr
- CompatTypedField(
+ SpecialTypedField(
"member",
label=_("Members"),
names=("memb",),
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index e8719fac4c1..96ac9f8620e 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -47,8 +47,10 @@
QAPISchemaCommand,
QAPISchemaDefinition,
QAPISchemaEnumMember,
+ QAPISchemaEvent,
QAPISchemaFeature,
QAPISchemaMember,
+ QAPISchemaObjectType,
QAPISchemaObjectTypeMember,
QAPISchemaType,
QAPISchemaVisitor,
@@ -300,11 +302,61 @@ def preamble(self, ent: QAPISchemaDefinition) -> None:
self.ensure_blank_line()
+ def _insert_member_pointer(self, ent: QAPISchemaDefinition) -> None:
+
+ def _get_target(
+ ent: QAPISchemaDefinition,
+ ) -> Optional[QAPISchemaDefinition]:
+ if isinstance(ent, (QAPISchemaCommand, QAPISchemaEvent)):
+ return ent.arg_type
+ if isinstance(ent, QAPISchemaObjectType):
+ return ent.base
+ return None
+
+ target = _get_target(ent)
+ if target is not None and not target.is_implicit():
+ assert ent.info
+ self.add_field(
+ self.member_field_type,
+ "q_dummy",
+ f"The members of :qapi:type:`{target.name}`.",
+ ent.info,
+ "q_dummy",
+ )
+
+ if isinstance(ent, QAPISchemaObjectType) and ent.branches is not None:
+ for variant in ent.branches.variants:
+ if variant.type.name == "q_empty":
+ continue
+ assert ent.info
+ self.add_field(
+ self.member_field_type,
+ "q_dummy",
+ f" When ``{ent.branches.tag_member.name}`` is "
+ f"``{variant.name}``: "
+ f"The members of :qapi:type:`{variant.type.name}`.",
+ ent.info,
+ "q_dummy",
+ )
+
def visit_sections(self, ent: QAPISchemaDefinition) -> None:
sections = ent.doc.all_sections if ent.doc else []
+ # Determine the index location at which we should generate
+ # documentation for "The members of ..." pointers. This should
+ # go at the end of the members section(s) if any. Note that
+ # index 0 is assumed to be a plain intro section, even if it is
+ # empty; and that a members section if present will always
+ # immediately follow the opening PLAIN section.
+ gen_index = 1
+ if len(sections) > 1:
+ while sections[gen_index].kind == QAPIDoc.Kind.MEMBER:
+ gen_index += 1
+ if gen_index >= len(sections):
+ break
+
# Add sections *in the order they are documented*:
- for section in sections:
+ for i, section in enumerate(sections):
# @var is translated to ``var``:
section.text = re.sub(r"@([\w-]+)", r"``\1``", section.text)
@@ -326,6 +378,10 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
else:
assert False
+ # Generate "The members of ..." entries if necessary:
+ if i == gen_index - 1:
+ self._insert_member_pointer(ent)
+
self.ensure_blank_line()
# Transmogrification core methods
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 58/62] docs/qapidoc: generate entries for undocumented members
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (56 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 57/62] docs/qapidoc: Add "the members of" pointers John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-10 10:08 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 59/62] qapi/parser: add undocumented stub members to all_sections John Snow
` (4 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Presently, we never have any empty text entries for members. The next
patch will explicitly generate such sections, so enable support for it
in advance.
The parser will generate placeholder sections to indicate undocumented
members, but it's the qapidoc generator that's responsible for deciding
what to do with that stub section.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 96ac9f8620e..fec48fd01e1 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -235,11 +235,11 @@ def visit_member(self, section: QAPIDoc.ArgSection) -> None:
# TODO?: features for members (documented at entity-level,
# but sometimes defined per-member. Should we add such
# information to member descriptions when we can?)
- assert section.text and section.member
+ assert section.member
self.generate_field(
self.member_field_type,
section.member,
- section.text,
+ section.text if section.text else "(Not Documented.)",
section.info,
)
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 59/62] qapi/parser: add undocumented stub members to all_sections
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (57 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 58/62] docs/qapidoc: generate entries for undocumented members John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-10 10:19 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 60/62] docs: disambiguate cross-references John Snow
` (3 subsequent siblings)
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
This helps simplify the new doc generator if it doesn't have to check
for undocumented members, it can just blindly operate on a sequence of
QAPIDoc.Section instances.
NB: If there is no existing 'member' section, these undocumented stub
members will be inserted directly after the leading section.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/parser.py | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 11c11bb09e5..58cb9f41ae8 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -789,8 +789,21 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
raise QAPISemError(member.info,
"%s '%s' lacks documentation"
% (member.role, member.name))
- self.args[member.name] = QAPIDoc.ArgSection(
+ section = QAPIDoc.ArgSection(
self.info, QAPIDoc.Kind.MEMBER, member.name)
+ self.args[member.name] = section
+
+ # Insert stub documentation section for missing member docs.
+ # Determine where to insert stub doc - it should go at the
+ # end of the members section(s), if any. Note that index 0
+ # is assumed to be an untagged intro section, even if it is
+ # empty.
+ index = 1
+ if len(self.all_sections) > 1:
+ while self.all_sections[index].kind == QAPIDoc.Kind.MEMBER:
+ index += 1
+ self.all_sections.insert(index, section)
+
self.args[member.name].connect(member)
def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 60/62] docs: disambiguate cross-references
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (58 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 59/62] qapi/parser: add undocumented stub members to all_sections John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 8:35 ` [PATCH v2 61/62] docs: enable qapidoc transmogrifier for QEMU QMP Reference John Snow
` (2 subsequent siblings)
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
The next patch will engage the qapidoc transmogrifier, which creates a
lot of cross-reference targets. Some of the existing targets
("migration", "qom", "replay") will become ambiguous as a result. Nail
them down more explicitly to prevent ambiguous cross-reference warnings.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/devel/codebase.rst | 6 +++---
docs/glossary.rst | 10 +++++-----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/docs/devel/codebase.rst b/docs/devel/codebase.rst
index 4039875ee04..1b09953197b 100644
--- a/docs/devel/codebase.rst
+++ b/docs/devel/codebase.rst
@@ -23,7 +23,7 @@ Some of the main QEMU subsystems are:
- `Devices<device-emulation>` & Board models
- `Documentation <documentation-root>`
- `GDB support<GDB usage>`
-- `Migration<migration>`
+- :ref:`Migration<migration>`
- `Monitor<QEMU monitor>`
- :ref:`QOM (QEMU Object Model)<qom>`
- `System mode<System emulation>`
@@ -112,7 +112,7 @@ yet, so sometimes the source code is all you have.
* `libdecnumber <https://gitlab.com/qemu-project/qemu/-/tree/master/libdecnumber>`_:
Import of gcc library, used to implement decimal number arithmetic.
* `migration <https://gitlab.com/qemu-project/qemu/-/tree/master/migration>`__:
- `Migration framework <migration>`.
+ :ref:`Migration framework <migration>`.
* `monitor <https://gitlab.com/qemu-project/qemu/-/tree/master/monitor>`_:
`Monitor <QEMU monitor>` implementation (HMP & QMP).
* `nbd <https://gitlab.com/qemu-project/qemu/-/tree/master/nbd>`_:
@@ -193,7 +193,7 @@ yet, so sometimes the source code is all you have.
- `lcitool <https://gitlab.com/qemu-project/qemu/-/tree/master/tests/lcitool>`_:
Generate dockerfiles for CI containers.
- `migration <https://gitlab.com/qemu-project/qemu/-/tree/master/tests/migration>`_:
- Test scripts and data for `Migration framework <migration>`.
+ Test scripts and data for :ref:`Migration framework <migration>`.
- `multiboot <https://gitlab.com/qemu-project/qemu/-/tree/master/tests/multiboot>`_:
Test multiboot functionality for x86_64/i386.
- `qapi-schema <https://gitlab.com/qemu-project/qemu/-/tree/master/tests/qapi-schema>`_:
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 693d9855dd1..4fa044bfb6e 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -120,7 +120,7 @@ Migration
---------
QEMU can save and restore the execution of a virtual machine between different
-host systems. This is provided by the `Migration framework<migration>`.
+host systems. This is provided by the :ref:`Migration framework<migration>`.
NBD
---
@@ -212,14 +212,14 @@ machine emulator and virtualizer.
QOM
---
-`QEMU Object Model <qom>` is an object oriented API used to define various
-devices and hardware in the QEMU codebase.
+:ref:`QEMU Object Model <qom>` is an object oriented API used to define
+various devices and hardware in the QEMU codebase.
Record/replay
-------------
-`Record/replay <replay>` is a feature of QEMU allowing to have a deterministic
-and reproducible execution of a virtual machine.
+:ref:`Record/replay <replay>` is a feature of QEMU allowing to have a
+deterministic and reproducible execution of a virtual machine.
Rust
----
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 61/62] docs: enable qapidoc transmogrifier for QEMU QMP Reference
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (59 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 60/62] docs: disambiguate cross-references John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:13 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 62/62] docs: add qapi-domain syntax documentation John Snow
2025-03-09 21:19 ` [PATCH v2 00/62] docs: Add new QAPI transmogrifier Markus Armbruster
62 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
We are not enabling the transmogrifier for QSD or QGA yet because we
don't (yet) have a way to create separate indices, and all of the
definitions will bleed together, which isn't so nice.
For now, QMP is better than nothing at all!
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/interop/qemu-qmp-ref.rst | 1 +
qapi/qapi-schema.json | 2 ++
2 files changed, 3 insertions(+)
diff --git a/docs/interop/qemu-qmp-ref.rst b/docs/interop/qemu-qmp-ref.rst
index f94614a0b2f..e95eeac45e2 100644
--- a/docs/interop/qemu-qmp-ref.rst
+++ b/docs/interop/qemu-qmp-ref.rst
@@ -7,3 +7,4 @@ QEMU QMP Reference Manual
:depth: 3
.. qapi-doc:: qapi/qapi-schema.json
+ :transmogrify:
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index 2877aff73d0..4475e81cc3e 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -5,6 +5,8 @@
#
# This document describes all commands currently supported by QMP.
#
+# For locating a particular item, please see the `qapi-index`.
+#
# Most of the time their usage is exactly the same as in the user
# Monitor, this means that any other document which also describe
# commands (the manpage, QEMU's manual, etc) can and should be
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* [PATCH v2 62/62] docs: add qapi-domain syntax documentation
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (60 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 61/62] docs: enable qapidoc transmogrifier for QEMU QMP Reference John Snow
@ 2025-03-09 8:35 ` John Snow
2025-03-09 21:19 ` [PATCH v2 00/62] docs: Add new QAPI transmogrifier Markus Armbruster
62 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-09 8:35 UTC (permalink / raw)
To: qemu-devel
Cc: Markus Armbruster, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, John Snow
Who documents the documentation?
Me, I guess.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/devel/index-build.rst | 1 +
docs/devel/qapi-domain.rst | 670 +++++++++++++++++++++++++++++++++++++
2 files changed, 671 insertions(+)
create mode 100644 docs/devel/qapi-domain.rst
diff --git a/docs/devel/index-build.rst b/docs/devel/index-build.rst
index 0745c81a264..3f3cb21b9b4 100644
--- a/docs/devel/index-build.rst
+++ b/docs/devel/index-build.rst
@@ -12,4 +12,5 @@ some of the basics if you are adding new files and targets to the build.
kconfig
docs
qapi-code-gen
+ qapi-domain
control-flow-integrity
diff --git a/docs/devel/qapi-domain.rst b/docs/devel/qapi-domain.rst
new file mode 100644
index 00000000000..1475870ca6c
--- /dev/null
+++ b/docs/devel/qapi-domain.rst
@@ -0,0 +1,670 @@
+======================
+The Sphinx QAPI Domain
+======================
+
+An extension to the `rST syntax
+<https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_
+in Sphinx is provided by the QAPI Domain, located in
+``docs/sphinx/qapi_domain.py``. This extension is analogous to the
+`Python Domain
+<https://www.sphinx-doc.org/en/master/usage/domains/python.html>`_
+included with Sphinx, but provides special directives and roles
+speciically for annotating and documenting QAPI definitions
+specifically.
+
+A `Domain
+<https://www.sphinx-doc.org/en/master/usage/domains/index.html>`_
+provides a set of special rST directives and cross-referencing roles to
+Sphinx for understanding rST markup written to document a specific
+language. By itself, this QAPI extension is only sufficient to parse rST
+markup written by hand; the `autodoc
+<https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html>`_
+functionality is provided elsewhere, in ``docs/sphinx/qapidoc.py``, by
+the "Transmogrifier".
+
+It is not expected that any developer nor documentation writer would
+never need to write *nor* read these special rST forms. However, in the
+event that something needs to be debugged, knowing the syntax of the
+domain is quite handy. This reference may also be useful as a guide for
+understanding the QAPI Domain extension code itself. Although most of
+these forms will not be needed for documentation writing purposes,
+understanding the cross-referencing syntax *will* be helpful when
+writing rST documentation elsewhere, or for enriching the body of
+QAPIDoc blocks themselves.
+
+
+Concepts
+========
+
+The QAPI Domain itself provides no mechanisms for reading the QAPI
+Schema or generating documentation from code that exists. It is merely
+the rST syntax used to describe things. For instance, the Sphinx Python
+domain adds syntax like ``:py:func:`` for describing Python functions in
+documentation, but it's the autodoc module that is responsible for
+reading python code and generating such syntax. QAPI is analagous here:
+qapidoc.py is responsible for reading the QAPI Schema and generating rST
+syntax, and qapi_domain.py is responsible for translating that special
+syntax and providing APIs for Sphinx internals.
+
+In other words:
+
+qapi_domain.py adds syntax like ``.. qapi:command::`` to Sphinx, and
+qapidoc.py transforms the documentation in ``qapi/*.json`` into rST
+using directives defined by the domain.
+
+Or even shorter:
+
+``:py:`` is to ``:qapi:`` as *autodoc* is to *qapidoc*.
+
+
+Info Field Lists
+================
+
+`Field lists
+<https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#field-lists>`_
+are a standard syntax in reStructuredText. Sphinx `extends that syntax
+<https://www.sphinx-doc.org/en/master/usage/domains/python.html#info-field-lists>`_
+to give certain field list entries special meaning and parsing to, for
+example, add cross-references. The QAPI Domain takes advantage of this
+field list extension to document things like Arguments, Members, Values,
+and so on.
+
+The special parsing and handling of info field lists in Sphinx is provided by
+three main classes; Field, GroupedField, and TypedField. The behavior
+and formatting for each configured field list entry in the domain
+changes depending on which class is used.
+
+Field:
+ * Creates an ungrouped field: i.e., each entry will create its own
+ section and they will not be combined.
+ * May *optionally* support an argument.
+ * May apply cross-reference roles to *either* the argument *or* the
+ content body, both, or neither.
+
+This is used primarily for entries which are not expected to be
+repeated, i.e., items that may only show up at most once. The QAPI
+domain uses this class for "Errors" section.
+
+GroupedField:
+ * Creates a grouped field: i.e. multiple adjacent entries will be
+ merged into one section, and the content will form a bulleted list.
+ * *Must* take an argument.
+ * May optionally apply a cross-reference role to the argument, but not
+ the body.
+ * Can be configured to remove the bulleted list if there is only a
+ single entry.
+ * All items will be generated with the form: "argument -- body"
+
+This is used for entries which are expected to be repeated, but aren't
+expected to have two arguments, i.e. types without names, or names
+without types. The QAPI domain uses this class for features, returns,
+and enum values.
+
+TypedField:
+ * Creates a grouped, typed field. Multiple adjacent entres will be
+ merged into one section, and the content will form a bulleted list.
+ * *Must* take at least one argument, but supports up to two -
+ nominally, a name and a type.
+ * May optionally apply a cross-reference role to the type or the name
+ argument, but not the body.
+ * Can be configured to remove the bulleted list if there is only a
+ single entry.
+ * All items will be generated with the form "name (type) -- body"
+
+This is used for entries that are expected to be repeated and will have
+a name, a type, and a description. The QAPI domain uses this class for
+arguments, alternatives, and members. Wherever type names are referenced
+below, They must be a valid, documented type that will be
+cross-referenced in the HTML output; or one of the built-in JSON types
+(string, number, int, boolean, null, value, q_empty).
+
+
+``:feat:``
+----------
+
+Document a feature attached to a QAPI definition.
+
+:availability: This field list is available in the body of Command,
+ Event, Enum, Object and Alternate directives.
+:syntax: ``:feat name: Lorem ipsum, dolor sit amet...``
+:type: `sphinx.util.docfields.GroupedField
+ <https://pydoc.dev/sphinx/latest/sphinx.util.docfields.GroupedField.html?private=1>`_
+
+Example::
+
+ .. qapi:object:: BlockdevOptionsVirtioBlkVhostVdpa
+ :since: 7.2
+ :ifcond: CONFIG_BLKIO
+
+ Driver specific block device options for the virtio-blk-vhost-vdpa
+ backend.
+
+ :memb string path: path to the vhost-vdpa character device.
+ :feat fdset: Member ``path`` supports the special "/dev/fdset/N" path
+ (since 8.1)
+
+
+``:arg:``
+---------
+
+Document an argument to a QAPI command.
+
+:availability: This field list is only available in the body of the
+ Command directive.
+:syntax: ``:arg type name: description``
+:type: `sphinx.util.docfields.TypedField
+ <https://pydoc.dev/sphinx/latest/sphinx.util.docfields.TypedField.html?private=1>`_
+
+
+Example::
+
+ .. qapi:command:: job-pause
+ :since: 3.0
+
+ Pause an active job.
+
+ This command returns immediately after marking the active job for
+ pausing. Pausing an already paused job is an error.
+
+ The job will pause as soon as possible, which means transitioning
+ into the PAUSED state if it was RUNNING, or into STANDBY if it was
+ READY. The corresponding JOB_STATUS_CHANGE event will be emitted.
+
+ Cancelling a paused job automatically resumes it.
+
+ :arg string id: The job identifier.
+
+
+``:error:``
+-----------
+
+Document the error condition(s) of a QAPI command.
+
+:availability: This field list is only available in the body of the
+ Command directive.
+:syntax: ``:error: Lorem ipsum dolor sit amet ...``
+:type: `sphinx.util.docfields.Field
+ <https://pydoc.dev/sphinx/latest/sphinx.util.docfields.Field.html?private=1>`_
+
+The format of the :errors: field list description is free-form rST. The
+alternative spelling ":errors:" is also permitted, but strictly
+analogous.
+
+Example::
+
+ .. qapi:command:: block-job-set-speed
+ :since: 1.1
+
+ Set maximum speed for a background block operation.
+
+ This command can only be issued when there is an active block job.
+
+ Throttling can be disabled by setting the speed to 0.
+
+ :arg string device: The job identifier. This used to be a device
+ name (hence the name of the parameter), but since QEMU 2.7 it
+ can have other values.
+ :arg int speed: the maximum speed, in bytes per second, or 0 for
+ unlimited. Defaults to 0.
+ :error:
+ - If no background operation is active on this device,
+ DeviceNotActive
+
+
+``:return:``
+-------------
+
+Document the return type(s) and value(s) of a QAPI command.
+
+:availability: This field list is only available in the body of the
+ Command directive.
+:syntax: ``:return type: Lorem ipsum dolor sit amet ...``
+:type: `sphinx.util.docfields.GroupedField
+ <https://pydoc.dev/sphinx/latest/sphinx.util.docfields.GroupedField.html?private=1>`_
+
+
+Example::
+
+ .. qapi:command:: query-replay
+ :since: 5.2
+
+ Retrieve the record/replay information. It includes current
+ instruction count which may be used for ``replay-break`` and
+ ``replay-seek`` commands.
+
+ :return ReplayInfo: record/replay information.
+
+ .. qmp-example::
+
+ -> { "execute": "query-replay" }
+ <- { "return": {
+ "mode": "play", "filename": "log.rr", "icount": 220414 }
+ }
+
+
+``:value:``
+-----------
+
+Document a possible value for a QAPI enum.
+
+:availability: This field list is only available in the body of the Enum
+ directive.
+:syntax: ``:value name: Lorem ipsum, dolor sit amet ...``
+:type: `sphinx.util.docfields.GroupedField
+ <https://pydoc.dev/sphinx/latest/sphinx.util.docfields.GroupedField.html?private=1>`_
+
+Example::
+
+ .. qapi:enum:: QapiErrorClass
+ :since: 1.2
+
+ QEMU error classes
+
+ :value GenericError: this is used for errors that don't require a specific
+ error class. This should be the default case for most errors
+ :value CommandNotFound: the requested command has not been found
+ :value DeviceNotActive: a device has failed to be become active
+ :value DeviceNotFound: the requested device has not been found
+ :value KVMMissingCap: the requested operation can't be fulfilled because a
+ required KVM capability is missing
+
+
+``:alt:``
+------------
+
+Document a possible branch for a QAPI alternate.
+
+:availability: This field list is only available in the body of the
+ Alternate directive.
+:syntax: ``:alt type name: Lorem ipsum, dolor sit amet ...``
+:type: `sphinx.util.docfields.TypedField
+ <https://pydoc.dev/sphinx/latest/sphinx.util.docfields.TypedField.html?private=1>`_
+
+As a limitation of Sphinx, we must document the "name" of the branch in
+addition to the type, even though this information is not visible on the
+wire in the QMP protocol format. This limitation *may* be lifted at a
+future date.
+
+Example::
+
+ .. qapi:alternate:: StrOrNull
+ :since: 2.10
+
+ This is a string value or the explicit lack of a string (null
+ pointer in C). Intended for cases when 'optional absent' already
+ has a different meaning.
+
+ :alt string s: the string value
+ :alt null n: no string value
+
+
+``:memb:``
+----------
+
+Document a member of an Event or Object.
+
+:availability: This field list is available in the body of Event or
+ Object directives.
+:syntax: ``:memb type name: Lorem ipsum, dolor sit amet ...``
+:type: `sphinx.util.docfields.TypedField
+ <https://pydoc.dev/sphinx/latest/sphinx.util.docfields.TypedField.html?private=1>`_
+
+This is fundamentally the same as ``:arg:`` and ``:alt:``, but uses the
+"Members" phrasing for Events and Objects (Structs and Unions).
+
+Example::
+
+ .. qapi:event:: JOB_STATUS_CHANGE
+ :since: 3.0
+
+ Emitted when a job transitions to a different status.
+
+ :memb string id: The job identifier
+ :memb JobStatus status: The new job status
+
+
+Arbitrary field lists
+---------------------
+
+Other field list names, while valid rST syntax, are prohibited inside of
+QAPI directives to help prevent accidental misspellings of info field
+list names. If you want to add a new arbitrary "non-value-added" field
+list to QAPI documentation, you must add the field name to the allow
+list in ``docs/conf.py``
+
+For example::
+
+ qapi_allowed_fields = {
+ "see also",
+ }
+
+Will allow you to add arbitrary field lists in QAPI directives::
+
+ .. qapi:command:: x-fake-command
+
+ :see also: Lorem ipsum, dolor sit amet ...
+
+
+Cross-references
+================
+
+Cross-reference `roles
+<https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html>`_
+in the QAPI domain are modeled closely after the `Python
+cross-referencing syntax
+<https://www.sphinx-doc.org/en/master/usage/domains/python.html#cross-referencing-python-objects>`_.
+
+QAPI definitions can be referenced using the standard `any
+<https://www.sphinx-doc.org/en/master/usage/referencing.html#role-any>`_
+role cross-reference syntax, such as with ```query-blockstats```. In
+the event that disambiguation is needed, cross-references can also be
+written using a number of explicit cross-reference roles:
+
+* ``:qapi:mod:`block-core``` -- Reference a QAPI module. The link will
+ take you to the beginning of that section in the documentation.
+* ``:qapi:cmd:`query-block``` -- Reference a QAPI command.
+* ``:qapi:event:`JOB_STATUS_CHANGE``` -- Reference a QAPI event.
+* ``:qapi:enum:`QapiErrorClass``` -- Reference a QAPI enum.
+* ``:qapi:obj:`BlockdevOptionsVirtioBlkVhostVdpa`` -- Reference a QAPI
+ object (struct or union)
+* ``:qapi:alt:`StrOrNull``` -- Reference a QAPI alternate.
+* ``:qapi:type:`BlockDirtyInfo``` -- Reference *any* QAPI type; this
+ excludes modules, commands, and events.
+* ``:qapi:any:`block-job-set-speed``` -- Reference absolutely any QAPI entity.
+
+Type arguments in info field lists are converted into references as if
+you had used the ``:qapi:type:`` role. All of the special syntax below
+applies to both info field lists and standalone explicit
+cross-references.
+
+
+Type decorations
+----------------
+
+Type names in references can be surrounded by brackets, like
+``[typename]``, to indicate an array of that type. The cross-reference
+will apply only to the type name between the brackets. For example;
+``:qapi:type:`[Qcow2BitmapInfoFlags]``` renders to:
+:qapi:type:`[Qcow2BitmapInfoFlags]`
+
+To indicate an optional argument/member in a field list, the type name
+can be suffixed with ``?``. The cross-reference will be transformed to
+"type, Optional" with the link applying only to the type name. For
+example; ``:qapi:type:`BitmapSyncMode?``` renders to:
+:qapi:type:`BitmapSyncMode?`
+
+
+Namespaces
+----------
+
+Mimicking the `Python domain target specification syntax
+<https://www.sphinx-doc.org/en/master/usage/domains/python.html#target-specification>`_,
+QAPI allows you to specify the fully qualified path for a data
+type. QAPI enforces globally unique names, so it's unlikely you'll need
+this specific feature, but it may be extended in the near future to
+allow referencing identically named commands and data types from
+different utilities; i.e. QEMU Storage Daemon vs QMP.
+
+* A module can be explicitly provided;
+ ``:qapi:type:`block-core.BitmapSyncMode``` will render to:
+ :qapi:type:`block-core.BitmapSyncMode`
+* If you don't want to display the "fully qualified" name, it can be
+ prefixed with a tilde; ``:qapi:type:`~block-core.BitmapSyncMode```
+ will render to: :qapi:type:`~block-core.BitmapSyncMode`
+
+
+Custom link text
+----------------
+
+The name of a cross-reference link can be explicitly overridden like
+`most stock Sphinx references
+<https://www.sphinx-doc.org/en/master/usage/referencing.html#syntax>`_
+using the ``custom text <target>`` syntax.
+
+For example, ``:qapi:cmd:`Merge dirty bitmaps
+<block-dirty-bitmap-merge>``` will render as: :qapi:cmd:`Merge dirty
+bitmaps <block-dirty-bitmap-merge>`
+
+
+Directives
+==========
+
+The QAPI domain adds a number of custom directives for documenting
+various QAPI/QMP entities. The syntax is plain rST, and follows this
+general format::
+
+ .. qapi:directive:: argument
+ :option:
+ :another-option: with an argument
+
+ Content body, arbitrary rST is allowed here.
+
+
+Sphinx standard options
+-----------------------
+
+All QAPI directives inherit a number of `standard options
+<https://www.sphinx-doc.org/en/master/usage/domains/index.html#basic-markup>`_
+from Sphinx's ObjectDescription class.
+
+The dashed spellings of the below options were added in Sphinx 7.2, the
+undashed spellings are currently retained as aliases, but will be
+removed in a future version.
+
+* ``:no-index:`` and ``:noindex:`` -- Do not add this item into the
+ Index, and do not make it available for cross-referencing.
+* ``no-index-entry:`` and ``:noindexentry:`` -- Do not add this item
+ into the Index, but allow it to be cross-referenced.
+* ``no-contents-entry`` and ``:nocontentsentry:`` -- Exclude this item
+ from the Table of Contents.
+* ``no-typesetting`` -- Create TOC, Index and cross-referencing
+ entities, but don't actually display the content.
+
+
+QAPI standard options
+---------------------
+
+All QAPI directives -- *except* for module -- support these common options.
+
+* ``:module: modname`` -- Borrowed from the Python domain, this option allows
+ you to override the module association of a given definition.
+* ``:since: x.y`` -- Allows the documenting of "Since" information, which is
+ displayed in the signature bar.
+* ``:ifcond: CONDITION`` -- Allows the documenting of conditional availability
+ information, which is displayed in an eyecatch just below the
+ signature bar.
+* ``:deprecated:`` -- Adds an eyecatch just below the signature bar that
+ advertises that this definition is deprecated and should be avoided.
+* ``:unstable:`` -- Adds an eyecatch just below the signature bar that
+ advertises that this definition is unstable and should not be used in
+ production code.
+
+
+qapi:module
+-----------
+
+The ``qapi:module`` directive marks the start of a QAPI module. It may have
+a content body, but it can be omitted. All subsequent QAPI directives
+are associated with the most recent module; this effects their "fully
+qualified" name, but has no other effect.
+
+Example::
+
+ .. qapi:module:: block-core
+
+ Welcome to the block-core module!
+
+Will be rendered as:
+
+.. qapi:module:: block-core
+ :noindex:
+
+ Welcome to the block-core module!
+
+
+qapi:command
+------------
+
+This directive documents a QMP command. It may use any of the standard
+Sphinx or QAPI options, and the documentation body may contain
+``:arg:``, ``:feat:``, ``:error:``, or ``:return:`` info field list
+entries.
+
+Example::
+
+ .. qapi:command:: x-fake-command
+ :since: 42.0
+ :unstable:
+
+ This command is fake, so it can't hurt you!
+
+ :arg int foo: Your favorite number.
+ :arg string? bar: Your favorite season.
+ :return [string]: A lovely computer-written poem for you.
+
+
+Will be rendered as:
+
+ .. qapi:command:: x-fake-command
+ :noindex:
+ :since: 42.0
+ :unstable:
+
+ This command is fake, so it can't hurt you!
+
+ :arg int foo: Your favorite number.
+ :arg string? bar: Your favorite season.
+ :return [string]: A lovely computer-written poem for you.
+
+
+qapi:event
+----------
+
+This directive documents a QMP event. It may use any of the standard
+Sphinx or QAPI options, and the documentation body may contain
+``:memb:`` or ``:feat:`` info field list entries.
+
+Example::
+
+ .. qapi:event:: COMPUTER_IS_RUINED
+ :since: 0.1
+ :deprecated:
+
+ This event is emitted when your computer is *extremely* ruined.
+
+ :memb string reason: Diagnostics as to what caused your computer to
+ be ruined.
+ :feat sadness: When present, the diagnostic message will also
+ explain how sad the computer is as a result of your wrongdoings.
+
+Will be rendered as:
+
+.. qapi:event:: COMPUTER_IS_RUINED
+ :noindex:
+ :since: 0.1
+ :deprecated:
+
+ This event is emitted when your computer is *extremely* ruined.
+
+ :memb string reason: Diagnostics as to what caused your computer to
+ be ruined.
+ :feat sadness: When present, the diagnostic message will also explain
+ how sad the computer is as a result of your wrongdoings.
+
+
+qapi:enum
+---------
+
+This directive documents a QAPI enum. It may use any of the standard
+Sphinx or QAPI options, and the documentation body may contain
+``:value:`` or ``:feat:`` info field list entries.
+
+Example::
+
+ .. qapi:enum:: Mood
+ :ifcond: LIB_PERSONALITY
+
+ This enum represents your virtual machine's current mood!
+
+ :value Happy: Your VM is content and well-fed.
+ :value Hungry: Your VM needs food.
+ :value Melancholic: Your VM is experiencing existential angst.
+ :value Petulant: Your VM is throwing a temper tantrum.
+
+Will be rendered as:
+
+.. qapi:enum:: Mood
+ :noindex:
+ :ifcond: LIB_PERSONALITY
+
+ This enum represents your virtual machine's current mood!
+
+ :value Happy: Your VM is content and well-fed.
+ :value Hungry: Your VM needs food.
+ :value Melancholic: Your VM is experiencing existential angst.
+ :value Petulant: Your VM is throwing a temper tantrum.
+
+
+qapi:object
+-----------
+
+This directive documents a QAPI structure or union and represents a QMP
+object. It may use any of the standard Sphinx or QAPI options, and the
+documentation body may contain ``:memb:`` or ``:feat:`` info field list
+entries.
+
+Example::
+
+ .. qapi:object:: BigBlobOfStuff
+
+ This object has a bunch of disparate and unrelated things in it.
+
+ :memb int Birthday: Your birthday, represented in seconds since the
+ UNIX epoch.
+ :memb [string] Fav-Foods: A list of your favorite foods.
+ :memb boolean? Bizarre-Docs: True if the documentation reference
+ should be strange.
+
+Will be rendered as:
+
+.. qapi:object:: BigBlobOfStuff
+ :noindex:
+
+ This object has a bunch of disparate and unrelated things in it.
+
+ :memb int Birthday: Your birthday, represented in seconds since the
+ UNIX epoch.
+ :memb [string] Fav-Foods: A list of your favorite foods.
+ :memb boolean? Bizarre-Docs: True if the documentation reference
+ should be strange.
+
+
+qapi:alternate
+--------------
+
+This directive documents a QAPI alternate. It may use any of the
+standard Sphinx or QAPI options, and the documentation body may contain
+``:alt:`` or ``:feat:`` info field list entries.
+
+Example::
+
+ .. qapi:alternate:: ErrorCode
+
+ This alternate represents an Error Code from the VM.
+
+ :alt int ec: An error code, like the type you're used to.
+ :alt string em: An expletive-laced error message, if your
+ computer is feeling particularly cranky and tired of your
+ antics.
+
+Will be rendered as:
+
+.. qapi:alternate:: ErrorCode
+ :noindex:
+
+ This alternate represents an Error Code from the VM.
+
+ :alt int ec: An error code, like the type you're used to.
+ :alt string em: An expletive-laced error message, if your
+ computer is feeling particularly cranky and tired of your
+ antics.
--
2.48.1
^ permalink raw reply related [flat|nested] 101+ messages in thread
* Re: [PATCH v2 02/62] qapi: shush pylint up
2025-03-09 8:34 ` [PATCH v2 02/62] qapi: shush pylint up John Snow
@ 2025-03-09 19:41 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 19:41 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Shhhhh!
>
> This patch is RFC quality, I wasn't in the mood to actually solve
> problems so much as I was in the mood to continue working on the Sphinx
> rework.
Does this patch leave anything in need of cleanup? If yes, mark the
spots with TODO comments, please. If no, drop the sentence above?
> Plus, I don't think the code I am patching has hit origin/master
> yet ...
This is no longer correct.
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> scripts/qapi/backend.py | 2 ++
> scripts/qapi/main.py | 8 +++-----
> 2 files changed, 5 insertions(+), 5 deletions(-)
>
> diff --git a/scripts/qapi/backend.py b/scripts/qapi/backend.py
> index 14e60aa67af..49ae6ecdd33 100644
> --- a/scripts/qapi/backend.py
> +++ b/scripts/qapi/backend.py
> @@ -13,6 +13,7 @@
>
>
> class QAPIBackend(ABC):
> + # pylint: disable=too-few-public-methods
>
> @abstractmethod
> def generate(self,
> @@ -36,6 +37,7 @@ def generate(self,
>
>
> class QAPICBackend(QAPIBackend):
> + # pylint: disable=too-few-public-methods
>
> def generate(self,
> schema: QAPISchema,
> diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py
> index 5b4679abcf1..01155373bd0 100644
> --- a/scripts/qapi/main.py
> +++ b/scripts/qapi/main.py
> @@ -38,8 +38,7 @@ def create_backend(path: str) -> QAPIBackend:
> try:
> mod = import_module(module_path)
> except Exception as ex:
> - print(f"unable to import '{module_path}': {ex}", file=sys.stderr)
> - sys.exit(1)
> + raise QAPIError(f"unable to import '{module_path}': {ex}") from ex
>
> try:
> klass = getattr(mod, class_name)
> @@ -51,9 +50,8 @@ def create_backend(path: str) -> QAPIBackend:
> try:
> backend = klass()
> except Exception as ex:
> - print(f"backend '{path}' cannot be instantiated: {ex}",
> - file=sys.stderr)
> - sys.exit(1)
> + raise QAPIError(
> + f"backend '{path}' cannot be instantiated: {ex}") from ex
>
> if not isinstance(backend, QAPIBackend):
> print(f"backend '{path}' must be an instance of QAPIBackend",
Missed in my review of the patch that added this code: the caller
catches QAPIError, and returns non-zero exit code on catch. The
caller's caller passes the exit code to sys.exit(). Leaving the
sys.exit() to the callers is cleaner.
However, you convert only two out of five error paths from sys.exit() to
raise. All or nothing, please.
Maybe split the patch into a "# pylint:" and a "raise QAPIError" part?
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 15/62] docs/qapi-domain: add "Arguments:" field lists
2025-03-09 8:35 ` [PATCH v2 15/62] docs/qapi-domain: add "Arguments:" field lists John Snow
@ 2025-03-09 20:37 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 20:37 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This adds special rendering for Sphinx's typed field lists.
>
> This patch does not add any QAPI-aware markup, rendering, or
> cross-referencing for the type names, yet. That feature requires a
> subclass to TypedField which will happen in its own commit quite a bit
> later in this series; after all the basic fields and objects have been
> established first.
>
> The syntax for this field is:
>
> :arg type name: description
> description cont'd
>
> You can omit the type or the description, but you cannot omit the name
> -- if you do so, it degenerates into a "normal field list" entry, and
> probably isn't what you want.
Suggest "You can omit the type or the description. You should not omit
the name ..."
>
> Signed-off-by: John Snow <jsnow@redhat.com>
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 16/62] docs/qapi-domain: add "Features:" field lists
2025-03-09 8:35 ` [PATCH v2 16/62] docs/qapi-domain: add "Features:" " John Snow
@ 2025-03-09 20:42 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 20:42 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Add support for Features field lists. There is no QAPI-specific
> functionality here, but this could be changed if desired (if we wanted
> the feature names to link somewhere, for instance.)
>
> This feature list doesn't have any restrictions, so it can be used to
> document object-wide features or per-member features as deemed
> appropriate. It's essentially free-form text.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
Mention the new syntax :feat, like the previous patch does for :arg?
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 18/62] docs/qapi-domain: add "Return:" field lists
2025-03-09 8:35 ` [PATCH v2 18/62] docs/qapi-domain: add "Return:" " John Snow
@ 2025-03-09 20:46 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 20:46 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Add "Return:" field list syntax to QAPI Commands.
>
> Like "Arguments:" and "Errors:", the type name isn't currently processed
> for cross-referencing, but this will be addressed in a forthcoming
> commit.
>
> This patch adds "Return" as a GroupedField, which means that multiple
> return values can be annotated - this is only done because Sphinx does
> not seemingly (Maybe I missed it?) support mandatory type arguments to
> Ungrouped fields. Because we want to cross-reference this type
> information later, we want to make the type argument mandatory. As a
> result, you can technically add multiple :return: fields, though I'm not
> aware of any circumstance in which you'd need or want
> to. Recommendation: "Don't do that, then."
>
> Signed-off-by: John Snow <jsnow@redhat.com>
Mention the new syntax :return like PATCH 15 does for :arg?
> ---
> docs/sphinx/qapi_domain.py | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
> index d018fcd4f7c..8a364ff75ea 100644
> --- a/docs/sphinx/qapi_domain.py
> +++ b/docs/sphinx/qapi_domain.py
> @@ -296,6 +296,13 @@ class QAPICommand(QAPIObject):
> names=("error", "errors"),
> has_arg=False,
> ),
> + # :returns TypeName: descr
:return I believe.
> + GroupedField(
> + "returnvalue",
> + label=_("Return"),
> + names=("return",),
> + can_collapse=True,
> + ),
> ]
> )
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 23/62] docs/qapi-domain: add :deprecated: directive option
2025-03-09 8:35 ` [PATCH v2 23/62] docs/qapi-domain: add :deprecated: directive option John Snow
@ 2025-03-09 20:51 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 20:51 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, Harmonie Snow
John Snow <jsnow@redhat.com> writes:
> Although "deprecated" is a feature (and *will* appear in the features
> list), add a special :deprecated: option to generate an eye-catch that
> makes this information very hard to miss.
>
> (The intent is to modify qapidoc.py to add this option whenever it
> detects that the features list attached to a definition contains the
> "deprecated" entry.)
>
> -
>
> RFC: Technically, this object-level option is un-needed and could be
> replaced with a standard content-level directive that e.g. qapidoc.py
> could insert at the beginning of the content block. I've done it here as
> an option to demonstrate how it would be possible to do.
>
> It's a matter of taste for "where" we feel like implementing it.
>
> One benefit of doing it this way is that we can create a single
> containing box to set CSS style options controlling the flow of multiple
> infoboxes. The other way to achieve that would be to create a directive
> that allows us to set multiple options instead, e.g.:
>
> .. qapi:infoboxes:: deprecated unstable
>
> or possibly:
>
> .. qapi:infoboxes::
> :deprecated:
> :unstable:
>
> For now, I've left these as top-level QAPI object options. "Hey, it works."
I think we agreed to delete the text from RFC: until here.
> P.S., I outsourced the CSS ;)
>
> Signed-off-by: Harmonie Snow <harmonie@gmail.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 32/62] qapi/parser: adjust info location for doc body section
2025-03-09 8:35 ` [PATCH v2 32/62] qapi/parser: adjust info location for doc body section John Snow
@ 2025-03-09 20:53 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 20:53 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Instead of using the info object for the doc block as a whole (which
> always points to the very first line of the block), update the info
> pointer for each call to ensure_untagged_section when the existing
> section is otherwise empty. This way, Sphinx error information will
> match precisely to where the text actually starts.
>
> For example, this patch will move the info pointer for the "Hello!"
> untagged section ...
>
>> ## <-- from here ...
>> # Hello! <-- ... to here.
>> ##
>
> Signed-off-by: John Snow <jsnow@redhat.com>
Here's my attempt to improve the commit message:
qapi/parser: adjust info location for doc body section
Instead of using the info object for the doc block as a whole (which
always points to the very first line of the block), update the info
pointer for each call to ensure_untagged_section when the existing
section is otherwise empty. This way, Sphinx error information will
match precisely to where the text actually starts.
For example, this patch will move the info pointer for the "Hello!"
untagged section ...
## <-- from here ...
# Hello! <-- ... to here.
##
This doesn't seem to improve error reporting now. It will with the
QAPI doc transmogrifier I'm about to add.
If I stick bad rST into qapi/block-core.json like this
##
# @SnapshotInfo:
#
+# rST syntax error: *ahh!
+#
# @id: unique snapshot id
#
# @name: user chosen name
the existing code's error message will point to the beginning of the
doc comment, which is less than helpful. The transmogrifier's
message will point to the erroneous line, but to accomplish this, it
needs this patch.
What do you think?
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 33/62] qapi: expand tags to all doc sections
2025-03-09 8:35 ` [PATCH v2 33/62] qapi: expand tags to all doc sections John Snow
@ 2025-03-09 20:57 ` Markus Armbruster
2025-03-10 11:44 ` Markus Armbruster
0 siblings, 1 reply; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 20:57 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This patch adds an explicit section "kind" to all QAPIDoc
> sections. Members/Features are now explicitly marked as such, with the
> name now being stored in a dedicated "name" field (which qapidoc.py was
> not actually using anyway.)
>
> The qapi-schema tests are updated to account for the new section names;
> mostly "TODO" becomes "Todo" and `None` becomes "Plain".
>
> Signed-off-by: John Snow <jsnow@redhat.com>
I think the commit message could be clearer. But I'm too tired for
wordsmithing. I intend to have another look at it tomorrow.
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 41/62] qapi/source: allow multi-line QAPISourceInfo advancing
2025-03-09 8:35 ` [PATCH v2 41/62] qapi/source: allow multi-line QAPISourceInfo advancing John Snow
@ 2025-03-09 20:59 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 20:59 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This is for the sake of the new rST generator (the "transmogrifier") so
> we can advance multiple lines on occasion while keeping the
> generated<-->source mappings accurate.
>
> next_line now simply takes an optional n parameter which chooses the
> number of lines to advance.
>
> RFC: Here's the exorbitant detail on why I want this:
>
> This is used mainly when converting section syntax in free-form
> documentation to more traditional rST section header syntax, which
> does not always line up 1:1 for line counts.
Obvious way to resolve the RFC:
The next patch will use this when converting ...
>
> For example:
>
> ```
> ##
> # = Section <-- Info is pointing here, "L1"
> #
> # Lorem Ipsum
> ##
> ```
>
> would be transformed to rST as:
>
> ```
> ======= <-- L1
> Section <-- L1
> ======= <-- L1
> <-- L2
> Lorem Ipsum <-- L3
> ```
>
> After consuming the single "Section" line from the source, we want to
> advance the source pointer to the next non-empty line which requires
> jumping by more than one line.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> Reviewed-by: Markus Armbruster <armbru@redhat.com>
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 42/62] docs/qapidoc: add visit_freeform() method
2025-03-09 8:35 ` [PATCH v2 42/62] docs/qapidoc: add visit_freeform() method John Snow
@ 2025-03-09 21:01 ` Markus Armbruster
2025-03-10 21:11 ` John Snow
0 siblings, 1 reply; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:01 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 50 ++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 50 insertions(+)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 6de8c900543..cf5dbb0133d 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -29,6 +29,7 @@
> from contextlib import contextmanager
> import os
> from pathlib import Path
> +import re
> import sys
> from typing import TYPE_CHECKING
>
> @@ -55,6 +56,8 @@
> Sequence,
> )
>
> + from qapi.parser import QAPIDoc
> +
> from sphinx.application import Sphinx
> from sphinx.util.typing import ExtensionMetadata
>
> @@ -130,6 +133,53 @@ def visit_module(self, path: str) -> None:
> self.add_line_raw(f".. qapi:module:: {name}", path, 1)
> self.ensure_blank_line()
>
> + def visit_freeform(self, doc: QAPIDoc) -> None:
> + # TODO: Once the old qapidoc transformer is deprecated, freeform
> + # sections can be updated to pure rST, and this transformed removed.
> + #
> + # For now, translate our micro-format into rST. Code adapted
> + # from Peter Maydell's freeform().
> +
> + assert len(doc.all_sections) == 1, doc.all_sections
> + body = doc.all_sections[0]
> + text = body.text
> + info = doc.info
> +
> + if re.match(r"=+ ", text):
> + # Section/subsection heading (if present, will always be the
> + # first line of the block)
> + (heading, _, text) = text.partition("\n")
> + (leader, _, heading) = heading.partition(" ")
> + level = len(leader) + 1 # Implicit +1 for heading in .rST stub
I find "in .rST stub" less than clear. Could we use the filename?
> +
> + # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#sections
> + markers = {
> + 1: "#",
> + 2: "*",
> + 3: "=",
> + 4: "-",
> + 5: "^",
> + 6: '"',
> + }
Suggest
markers = '#*=-^".
> + overline = level <= 2
> + marker = markers[level]
> +
> + self.ensure_blank_line()
> + # This credits all 2 or 3 lines to the single source line.
> + if overline:
> + self.add_line(marker * len(heading), info)
> + self.add_line(heading, info)
> + self.add_line(marker * len(heading), info)
> + self.ensure_blank_line()
> +
> + # Eat blank line(s) and advance info
> + trimmed = text.lstrip("\n")
> + text = trimmed
> + info = info.next_line(len(text) - len(trimmed) + 1)
> +
> + self.add_lines(text, info)
> + self.ensure_blank_line()
> +
>
> class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
> """A QAPI schema visitor which adds Sphinx dependencies each module
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 43/62] docs/qapidoc: add preamble() method
2025-03-09 8:35 ` [PATCH v2 43/62] docs/qapidoc: add preamble() method John Snow
@ 2025-03-09 21:03 ` Markus Armbruster
2025-03-10 21:05 ` John Snow
2025-03-10 9:46 ` Markus Armbruster
1 sibling, 1 reply; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:03 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Markus Armbruster, Philippe Mathieu-Daudé,
Michael Roth, Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This method adds the options/preamble to each definition block. Notably,
> :since: and :ifcond: are added, as are any "special features" such as
> :deprecated: and :unstable:.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 41 ++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 38 insertions(+), 3 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index cf5dbb0133d..d8bf0073dfa 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -37,7 +37,12 @@
> from docutils.parsers.rst import Directive, directives
> from docutils.statemachine import StringList
> from qapi.error import QAPIError
> -from qapi.schema import QAPISchema, QAPISchemaVisitor
> +from qapi.parser import QAPIDoc
> +from qapi.schema import (
> + QAPISchema,
> + QAPISchemaDefinition,
> + QAPISchemaVisitor,
> +)
> from qapi.source import QAPISourceInfo
>
> from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore
> @@ -56,8 +61,6 @@
> Sequence,
> )
>
> - from qapi.parser import QAPIDoc
> -
Accident?
> from sphinx.application import Sphinx
> from sphinx.util.typing import ExtensionMetadata
>
> @@ -125,6 +128,38 @@ def ensure_blank_line(self) -> None:
> # +2: correct for zero/one index, then increment by one.
> self.add_line_raw("", fname, line + 2)
>
> + # Transmogrification helpers
> +
> + def preamble(self, ent: QAPISchemaDefinition) -> None:
> + """
> + Generate option lines for qapi entity directives.
> + """
> + if ent.doc and ent.doc.since:
> + assert ent.doc.since.kind == QAPIDoc.Kind.SINCE
> + # Generated from the entity's docblock; info location is exact.
> + self.add_line(f":since: {ent.doc.since.text}", ent.doc.since.info)
Break the line aftee the comma?
> +
> + if ent.ifcond.is_present():
> + doc = ent.ifcond.docgen()
> + assert ent.info
> + # Generated from entity definition; info location is approximate.
> + self.add_line(f":ifcond: {doc}", ent.info)
> +
> + # Hoist special features such as :deprecated: and :unstable:
> + # into the options block for the entity. If, in the future, new
> + # special features are added, qapi-domain will chirp about
> + # unrecognized options and fail until they are handled in
> + # qapi-domain.
> + for feat in ent.features:
> + if feat.is_special():
> + # FIXME: handle ifcond if present. How to display that
If I remember correctly, you wanted to mention this FIXME in the commit
message.
> + # information is TBD.
> + # Generated from entity def; info location is approximate.
> + assert feat.info
> + self.add_line(f":{feat.name}:", feat.info)
> +
> + self.ensure_blank_line()
> +
> # Transmogrification core methods
>
> def visit_module(self, path: str) -> None:
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 45/62] docs/qapidoc: add visit_errors() method
2025-03-09 8:35 ` [PATCH v2 45/62] docs/qapidoc: add visit_errors() method John Snow
@ 2025-03-09 21:05 ` Markus Armbruster
2025-03-10 21:17 ` John Snow
0 siblings, 1 reply; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:05 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Markus Armbruster, Philippe Mathieu-Daudé,
Michael Roth, Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Notably, this method does not currently address the formatting issues
> present with the "errors" section in QAPIDoc and just vomits the text
> verbatim into the rST doc, with somewhat inconsistent results.
>
> To be addressed in a future revision.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index b96445f0802..14feafe866e 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -139,6 +139,12 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None:
> self.add_lines(section.text, section.info)
> self.ensure_blank_line()
>
> + def visit_errors(self, section: QAPIDoc.Section) -> None:
> + # FIXME: the formatting for errors may be inconsistent and may
If I remember correctly, you wanted to mention this FIXME in the commit
message.
> + # or may not require different newline placement to ensure
> + # proper rendering as a nested list.
> + self.add_lines(f":error:\n{section.text}", section.info)
> +
> def preamble(self, ent: QAPISchemaDefinition) -> None:
> """
> Generate option lines for qapi entity directives.
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 48/62] docs/qapidoc: add visit_feature() method
2025-03-09 8:35 ` [PATCH v2 48/62] docs/qapidoc: add visit_feature() method John Snow
@ 2025-03-09 21:06 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:06 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This adds a simple ":feat name: lorem ipsum ..." line to the generated
> rST document, so at the moment it's only for "top level" features.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index b87ce288837..eaea19af7ac 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -195,6 +195,15 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None:
> self.add_lines(section.text, section.info)
> self.ensure_blank_line()
>
> + def visit_feature(self, section: QAPIDoc.ArgSection) -> None:
> + # FIXME - ifcond for features is not handled at all yet!
> + # Proposal: decorate the right-hand column with some graphical
> + # element to indicate conditional availability?
If I remember correctly, you wanted to mention this FIXME in the commit
message.
> + assert section.text # Guaranteed by parser.py
> + assert section.member
> +
> + self.generate_field("feat", section.member, section.text, section.info)
> +
> def visit_errors(self, section: QAPIDoc.Section) -> None:
> # FIXME: the formatting for errors may be inconsistent and may
> # or may not require different newline placement to ensure
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 50/62] docs/qapidoc: add visit_returns() method
2025-03-09 8:35 ` [PATCH v2 50/62] docs/qapidoc: add visit_returns() method John Snow
@ 2025-03-09 21:08 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:08 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Generates :returns: fields for explicit returns statements. Note that
> this does not presently handle undocumented returns, which is handled in
> a later commit.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 15 +++++++++++++++
> 1 file changed, 15 insertions(+)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 834f12ba6e9..85e7367ad79 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -41,6 +41,7 @@
> from qapi.schema import (
> QAPISchema,
> QAPISchemaArrayType,
> + QAPISchemaCommand,
> QAPISchemaDefinition,
> QAPISchemaEnumMember,
> QAPISchemaFeature,
> @@ -210,6 +211,20 @@ def visit_feature(self, section: QAPIDoc.ArgSection) -> None:
>
> self.generate_field("feat", section.member, section.text, section.info)
>
> + def visit_returns(self, section: QAPIDoc.Section) -> None:
> + assert isinstance(self.entity, QAPISchemaCommand)
> + rtype = self.entity.ret_type
> + # q_empty can produce None, but we won't be documenting anything
> + # without an explicit return statement in the doc block, and we
> + # should not have any such explicit statements when there is no
> + # return value.
> + assert rtype
> +
> + typ = self.format_type(rtype)
> + assert typ
> + assert section.text # We don't expect empty returns sections.
If I remember correctly, you wanted to drop this comment.
> + self.add_field("return", typ, section.text, section.info)
> +
> def visit_errors(self, section: QAPIDoc.Section) -> None:
> # FIXME: the formatting for errors may be inconsistent and may
> # or may not require different newline placement to ensure
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 51/62] docs/qapidoc: add visit_member() method
2025-03-09 8:35 ` [PATCH v2 51/62] docs/qapidoc: add visit_member() method John Snow
@ 2025-03-09 21:09 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:09 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Markus Armbruster, Philippe Mathieu-Daudé,
Michael Roth, Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This method is used for generating the "members" of a wide variety of
> things, including structs, unions, enums, alternates, etc. The field
> name it uses to do so is dependent on the type of entity the "member"
> belongs to.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 27 +++++++++++++++++++++++++++
> 1 file changed, 27 insertions(+)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 85e7367ad79..db11c2ae933 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -78,6 +78,16 @@
>
>
> class Transmogrifier:
> + # Field names used for different entity types:
> + field_types = {
> + "enum": "value",
> + "struct": "memb",
> + "union": "memb",
> + "event": "memb",
> + "command": "arg",
> + "alternate": "alt",
> + }
> +
> def __init__(self) -> None:
> self._curr_ent: Optional[QAPISchemaDefinition] = None
> self._result = StringList()
> @@ -88,6 +98,10 @@ def entity(self) -> QAPISchemaDefinition:
> assert self._curr_ent is not None
> return self._curr_ent
>
> + @property
> + def member_field_type(self) -> str:
> + return self.field_types[self.entity.meta]
> +
> # General-purpose rST generation functions
>
> def get_indent(self) -> str:
> @@ -202,6 +216,19 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None:
> self.add_lines(section.text, section.info)
> self.ensure_blank_line()
>
> + def visit_member(self, section: QAPIDoc.ArgSection) -> None:
> + # TODO: ifcond for members
Make it FIXME, and ideally mention it in the commit message.
> + # TODO?: features for members (documented at entity-level,
> + # but sometimes defined per-member. Should we add such
> + # information to member descriptions when we can?)
Let's drop the '?' in 'TODO?:'.
> + assert section.text and section.member
> + self.generate_field(
> + self.member_field_type,
> + section.member,
> + section.text,
> + section.info,
> + )
> +
> def visit_feature(self, section: QAPIDoc.ArgSection) -> None:
> # FIXME - ifcond for features is not handled at all yet!
> # Proposal: decorate the right-hand column with some graphical
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 52/62] docs/qapidoc: add visit_sections() method
2025-03-09 8:35 ` [PATCH v2 52/62] docs/qapidoc: add visit_sections() method John Snow
@ 2025-03-09 21:10 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:10 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Implement the actual main dispatch method that processes and handles the
> list of doc sections for a given QAPI entity.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 25 +++++++++++++++++++++++++
> 1 file changed, 25 insertions(+)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index db11c2ae933..78bec91271f 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -288,6 +288,31 @@ def preamble(self, ent: QAPISchemaDefinition) -> None:
>
> self.ensure_blank_line()
>
> + def visit_sections(self, ent: QAPISchemaDefinition) -> None:
> + sections = ent.doc.all_sections if ent.doc else []
> +
> + # Add sections *in the order they are documented*:
Would
# Add sections in source order
be clearer?
> + for section in sections:
> + if section.kind == QAPIDoc.Kind.PLAIN:
> + self.visit_paragraph(section)
> + elif section.kind == QAPIDoc.Kind.MEMBER:
> + assert isinstance(section, QAPIDoc.ArgSection)
> + self.visit_member(section)
> + elif section.kind == QAPIDoc.Kind.FEATURE:
> + assert isinstance(section, QAPIDoc.ArgSection)
> + self.visit_feature(section)
> + elif section.kind in (QAPIDoc.Kind.SINCE, QAPIDoc.Kind.TODO):
> + # Since is handled in preamble, TODO is skipped intentionally.
> + pass
> + elif section.kind == QAPIDoc.Kind.RETURNS:
> + self.visit_returns(section)
> + elif section.kind == QAPIDoc.Kind.ERRORS:
> + self.visit_errors(section)
> + else:
> + assert False
> +
> + self.ensure_blank_line()
> +
> # Transmogrification core methods
>
> def visit_module(self, path: str) -> None:
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 61/62] docs: enable qapidoc transmogrifier for QEMU QMP Reference
2025-03-09 8:35 ` [PATCH v2 61/62] docs: enable qapidoc transmogrifier for QEMU QMP Reference John Snow
@ 2025-03-09 21:13 ` Markus Armbruster
2025-03-10 21:33 ` John Snow
0 siblings, 1 reply; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:13 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> We are not enabling the transmogrifier for QSD or QGA yet because we
> don't (yet) have a way to create separate indices, and all of the
> definitions will bleed together, which isn't so nice.
>
> For now, QMP is better than nothing at all!
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/interop/qemu-qmp-ref.rst | 1 +
> qapi/qapi-schema.json | 2 ++
> 2 files changed, 3 insertions(+)
>
> diff --git a/docs/interop/qemu-qmp-ref.rst b/docs/interop/qemu-qmp-ref.rst
> index f94614a0b2f..e95eeac45e2 100644
> --- a/docs/interop/qemu-qmp-ref.rst
> +++ b/docs/interop/qemu-qmp-ref.rst
> @@ -7,3 +7,4 @@ QEMU QMP Reference Manual
> :depth: 3
>
> .. qapi-doc:: qapi/qapi-schema.json
> + :transmogrify:
> diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
> index 2877aff73d0..4475e81cc3e 100644
> --- a/qapi/qapi-schema.json
> +++ b/qapi/qapi-schema.json
> @@ -5,6 +5,8 @@
> #
> # This document describes all commands currently supported by QMP.
> #
> +# For locating a particular item, please see the `qapi-index`.
> +#
```qapi-index``` becomes a link in HTML. The link takes me to an index
page. Two observations:
* The index page appears not to be linked from the navigation thingie on
the left. Searching for "QAPI Index" there doesn't find it, either.
* The index is structured into sections titled Alternates, Commands |
Enums, Events, Modules, Objects, A, ... Z. As I scrolled down
quickly, the transition from Objects to A confused me briefly: I
didn't immediately understand that A, ... Z contains the union of
everything above sorted into letter buckets.
> # Most of the time their usage is exactly the same as in the user
> # Monitor, this means that any other document which also describe
> # commands (the manpage, QEMU's manual, etc) can and should be
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 56/62] docs/qapidoc: add intermediate output debugger
2025-03-09 8:35 ` [PATCH v2 56/62] docs/qapidoc: add intermediate output debugger John Snow
@ 2025-03-09 21:17 ` Markus Armbruster
2025-03-10 21:26 ` John Snow
0 siblings, 1 reply; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:17 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Add debugging output for the qapidoc transmogrifier - setting DEBUG=1
> will produce .ir files (one for each qapidoc directive) that write the
> generated rst file to disk to allow for easy debugging and verification
> of the generated document.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
You wrote you intend to advertise this debugging feature in the
transmogrifier doc and/or extend the doc-writing section of the qapi
code gen doc. If you did this, pointer, please. If not, no worries, it
can be done at a later time.
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 00/62] docs: Add new QAPI transmogrifier
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
` (61 preceding siblings ...)
2025-03-09 8:35 ` [PATCH v2 62/62] docs: add qapi-domain syntax documentation John Snow
@ 2025-03-09 21:19 ` Markus Armbruster
62 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-09 21:19 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
Would you be willing to serve as maintainer of (a subset of)
docs/sphinx/? If yes, please make it official by patching MAINTAINERS.
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 10/62] docs/qapi-domain: Add ObjectDescription abstract class
2025-03-09 8:34 ` [PATCH v2 10/62] docs/qapi-domain: Add ObjectDescription abstract class John Snow
@ 2025-03-10 9:15 ` Markus Armbruster
2025-03-10 21:12 ` John Snow
0 siblings, 1 reply; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 9:15 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This class is a generic, top-level directive for documenting some kind
> of QAPI thingamajig that we expect to go into the Index. This class
> doesn't do much by itself, and it isn't yet associated with any
> particular directive.
>
> Only handle_signature() is defined in the base class; get_index_text and
> add_target_and_index are new methods defined here; they are based
> heavily on the layout and format of the Python domain's general object
> class.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapi_domain.py | 65 ++++++++++++++++++++++++++++++++++++--
> 1 file changed, 63 insertions(+), 2 deletions(-)
>
> diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
> index 49d42c0921c..0365891f354 100644
> --- a/docs/sphinx/qapi_domain.py
> +++ b/docs/sphinx/qapi_domain.py
> @@ -14,11 +14,13 @@
> NamedTuple,
> Optional,
> Tuple,
> + cast,
> )
>
> from docutils import nodes
>
> -from sphinx.addnodes import pending_xref
> +from sphinx.addnodes import desc_signature, pending_xref
> +from sphinx.directives import ObjectDescription
> from sphinx.domains import (
> Domain,
> Index,
> @@ -28,7 +30,7 @@
> from sphinx.locale import _, __
> from sphinx.roles import XRefRole
> from sphinx.util import logging
> -from sphinx.util.nodes import make_refnode
> +from sphinx.util.nodes import make_id, make_refnode
>
>
> if TYPE_CHECKING:
> @@ -96,6 +98,65 @@ def process_link(
> return title, target
>
>
> +Signature = str
> +
> +
> +class QAPIDescription(ObjectDescription[Signature]):
> + """
> + Generic QAPI description.
> +
> + Abstract class, not instantiated directly.
> + """
> +
> + def handle_signature(self, sig: str, signode: desc_signature) -> Signature:
> + # Do nothing. The return value here is the "name" of the entity
> + # being documented; for QAPI, this is the same as the
> + # "signature", which is just a name.
> +
> + # Normally this method must also populate signode with nodes to
> + # render the signature; here we do nothing instead.
> + return sig
> +
> + def get_index_text(self, name: Signature) -> Tuple[str, str]:
> + """Return the text for the index entry of the object."""
> +
> + # NB: this is used for the global index, not the QAPI index.
> + return ("single", f"{name} (QMP {self.objtype})")
> +
> + def add_target_and_index(
> + self, name: Signature, sig: str, signode: desc_signature
> + ) -> None:
> + # name is the return value of handle_signature.
> + # sig is the original, raw text argument to handle_signature.
> + # For QAPI, these are identical, currently.
> +
> + assert self.objtype
> +
> + # If we're documenting a module, don't include the module as
> + # part of the FQN.
> + modname = ""
> + if self.objtype != "module":
> + modname = self.options.get(
> + "module", self.env.ref_context.get("qapi:module")
> + )
> + fullname = (modname + "." if modname else "") + name
> +
> + node_id = make_id(self.env, self.state.document, self.objtype, fullname)
pycodestyle-3 points out:
docs/sphinx/qapi_domain.py:144:80: E501 line too long (80 > 79 characters)
> + signode["ids"].append(node_id)
> +
> + self.state.document.note_explicit_target(signode)
> + domain = cast(QAPIDomain, self.env.get_domain("qapi"))
> + domain.note_object(fullname, self.objtype, node_id, location=signode)
This one's pushing it, too :)
> +
> + if "no-index-entry" not in self.options:
> + arity, indextext = self.get_index_text(name)
> + assert self.indexnode is not None
> + if indextext:
> + self.indexnode["entries"].append(
> + (arity, indextext, node_id, "", None)
> + )
> +
> +
> class QAPIIndex(Index):
> """
> Index subclass to provide the QAPI definition index.
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 12/62] docs/qapi-domain: add QAPIObject class
2025-03-09 8:34 ` [PATCH v2 12/62] docs/qapi-domain: add QAPIObject class John Snow
@ 2025-03-10 9:46 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 9:46 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Markus Armbruster, Philippe Mathieu-Daudé,
Michael Roth, Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapi_domain.py | 95 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 95 insertions(+)
>
> diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
> index a445150ae59..183d9a2766f 100644
> --- a/docs/sphinx/qapi_domain.py
> +++ b/docs/sphinx/qapi_domain.py
> @@ -18,7 +18,9 @@
> )
>
> from docutils import nodes
> +from docutils.parsers.rst import directives
>
> +from compat import KeywordNode, SpaceNode
> from sphinx import addnodes
> from sphinx.addnodes import desc_signature, pending_xref
> from sphinx.directives import ObjectDescription
> @@ -40,6 +42,7 @@
> from sphinx.application import Sphinx
> from sphinx.builders import Builder
> from sphinx.environment import BuildEnvironment
> + from sphinx.util.typing import OptionSpec
>
> logger = logging.getLogger(__name__)
>
> @@ -99,6 +102,8 @@ def process_link(
> return title, target
>
>
> +# Alias for the return of handle_signature(), which is used in several places.
> +# (In the Python domain, this is Tuple[str, str] instead.)
> Signature = str
>
>
> @@ -158,6 +163,96 @@ def add_target_and_index(
> )
>
>
> +class QAPIObject(QAPIDescription):
> + """
> + Description of a generic QAPI object.
> +
> + It's not used directly, but is instead subclassed by specific directives.
> + """
> +
> + # Inherit some standard options from Sphinx's ObjectDescription
> + option_spec: OptionSpec = ( # type:ignore[misc]
> + ObjectDescription.option_spec.copy()
> + )
> + option_spec.update(
> + {
> + # Borrowed from the Python domain:
> + "module": directives.unchanged, # Override contextual module name
> + }
> + )
> +
> + def get_signature_prefix(self) -> List[nodes.Node]:
> + """Returns a prefix to put before the object name in the signature."""
Recommend imperative mood: Return a prefix
> + assert self.objtype
> + return [
> + KeywordNode("", self.objtype.title()),
> + SpaceNode(" "),
> + ]
> +
> + def get_signature_suffix(self) -> List[nodes.Node]:
> + """Returns a suffix to put after the object name in the signature."""
Likewise.
> + return []
> +
> + def handle_signature(self, sig: str, signode: desc_signature) -> Signature:
> + """
> + Transform a QAPI definition name into RST nodes.
Like you do here.
> +
> + This method was originally intended for handling function
> + signatures. In the QAPI domain, however, we only pass the
> + definition name as the directive argument and handle everything
> + else in the content body with field lists.
> +
> + As such, the only argument here is "sig", which is just the QAPI
> + definition name.
> + """
> + modname = self.options.get(
> + "module", self.env.ref_context.get("qapi:module")
> + )
> +
> + signode["fullname"] = sig
> + signode["module"] = modname
> + sig_prefix = self.get_signature_prefix()
> + if sig_prefix:
> + signode += addnodes.desc_annotation(
> + str(sig_prefix), "", *sig_prefix
> + )
> + signode += addnodes.desc_name(sig, sig)
> + signode += self.get_signature_suffix()
> +
> + return sig
> +
> + def _object_hierarchy_parts(
> + self, sig_node: desc_signature
> + ) -> Tuple[str, ...]:
> + if "fullname" not in sig_node:
> + return ()
> + modname = sig_node.get("module")
> + fullname = sig_node["fullname"]
> +
> + if modname:
> + return (modname, *fullname.split("."))
> +
> + return tuple(fullname.split("."))
> +
> + def _toc_entry_name(self, sig_node: desc_signature) -> str:
> + # This controls the name in the TOC and on the sidebar.
> +
> + # This is the return type of _object_hierarchy_parts().
> + toc_parts = cast(Tuple[str, ...], sig_node.get("_toc_parts", ()))
> + if not toc_parts:
> + return ""
> +
> + config = self.env.app.config
> + *parents, name = toc_parts
> + if config.toc_object_entries_show_parents == "domain":
> + return sig_node.get("fullname", name)
> + if config.toc_object_entries_show_parents == "hide":
> + return name
> + if config.toc_object_entries_show_parents == "all":
> + return ".".join(parents + [name])
> + return ""
> +
> +
> class QAPIModule(QAPIDescription):
> """
> Directive to mark description of a new module.
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 43/62] docs/qapidoc: add preamble() method
2025-03-09 8:35 ` [PATCH v2 43/62] docs/qapidoc: add preamble() method John Snow
2025-03-09 21:03 ` Markus Armbruster
@ 2025-03-10 9:46 ` Markus Armbruster
2025-03-10 11:24 ` Markus Armbruster
1 sibling, 1 reply; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 9:46 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Markus Armbruster, Philippe Mathieu-Daudé,
Michael Roth, Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This method adds the options/preamble to each definition block. Notably,
> :since: and :ifcond: are added, as are any "special features" such as
> :deprecated: and :unstable:.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 41 ++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 38 insertions(+), 3 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index cf5dbb0133d..d8bf0073dfa 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -37,7 +37,12 @@
> from docutils.parsers.rst import Directive, directives
> from docutils.statemachine import StringList
> from qapi.error import QAPIError
> -from qapi.schema import QAPISchema, QAPISchemaVisitor
> +from qapi.parser import QAPIDoc
> +from qapi.schema import (
> + QAPISchema,
> + QAPISchemaDefinition,
> + QAPISchemaVisitor,
> +)
> from qapi.source import QAPISourceInfo
>
> from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore
> @@ -56,8 +61,6 @@
> Sequence,
> )
>
> - from qapi.parser import QAPIDoc
> -
> from sphinx.application import Sphinx
> from sphinx.util.typing import ExtensionMetadata
>
> @@ -125,6 +128,38 @@ def ensure_blank_line(self) -> None:
> # +2: correct for zero/one index, then increment by one.
> self.add_line_raw("", fname, line + 2)
>
> + # Transmogrification helpers
> +
> + def preamble(self, ent: QAPISchemaDefinition) -> None:
> + """
> + Generate option lines for qapi entity directives.
QAPI index unless there's a reason for lower case.
> + """
[...]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 28/62] docs/qapi-domain: add CSS styling
2025-03-09 8:35 ` [PATCH v2 28/62] docs/qapi-domain: add CSS styling John Snow
@ 2025-03-10 9:47 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 9:47 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, Harmonie Snow
John Snow <jsnow@redhat.com> writes:
> Improve the general look and feel of generated QAPI docs.
>
> Attempt to limit line lengths to offer a more comfortable measure on
> maximized windows, and improve some margin and spacing for field lists.
>
> Signed-off-by: Harmonie Snow <harmonie@gmail.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx-static/theme_overrides.css | 56 +++++++++++++++++++++++++-
> 1 file changed, 54 insertions(+), 2 deletions(-)
>
> diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css
> index 3fd326613d9..92f395054a8 100644
> --- a/docs/sphinx-static/theme_overrides.css
> +++ b/docs/sphinx-static/theme_overrides.css
> @@ -18,8 +18,8 @@ h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend {
>
> .rst-content dl:not(.docutils) dt {
> border-top: none;
> - border-left: solid 3px #ccc;
> - background-color: #f0f0f0;
> + border-left: solid 5px #bcc6d2;
> + background-color: #eaedf1;
> color: black;
> }
>
> @@ -211,6 +211,18 @@ div[class^="highlight"] pre {
>
> /* QAPI domain theming */
>
> +/* most content in a qapi object definition should not eclipse about
QAPI unless there's a reason for lower case.
> + 80ch, but nested field lists are explicitly exempt due to their
> + two-column nature */
> +.qapi dd *:not(dl) {
> + max-width: 80ch;
> +}
> +
> +/* but the content column itself should still be less than ~80ch. */
> +.qapi .field-list dd {
> + max-width: 80ch;
> +}
> +
> .qapi-infopips {
> margin-bottom: 1em;
> }
[...]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 11/62] docs/qapi-domain: add qapi:module directive
2025-03-09 8:34 ` [PATCH v2 11/62] docs/qapi-domain: add qapi:module directive John Snow
@ 2025-03-10 9:47 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 9:47 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This adds the qapi:module directive, which just notes the current module
> being documented and performs a nested parse of the content block, if
> present.
>
> This code is based pretty heavily on Sphinx's PyModule directive, but
> with unnecessary features excised.
>
> For example:
>
> .. qapi:module:: block-core
>
> Hello, and welcome to block-core!
> =================================
>
> lorem ipsum, dolor sit amet ...
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapi_domain.py | 71 ++++++++++++++++++++++++++++++++++----
> 1 file changed, 65 insertions(+), 6 deletions(-)
>
> diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
> index 0365891f354..a445150ae59 100644
> --- a/docs/sphinx/qapi_domain.py
> +++ b/docs/sphinx/qapi_domain.py
> @@ -19,6 +19,7 @@
>
> from docutils import nodes
>
> +from sphinx import addnodes
> from sphinx.addnodes import desc_signature, pending_xref
> from sphinx.directives import ObjectDescription
> from sphinx.domains import (
> @@ -34,7 +35,7 @@
>
>
> if TYPE_CHECKING:
> - from docutils.nodes import Element
> + from docutils.nodes import Element, Node
>
> from sphinx.application import Sphinx
> from sphinx.builders import Builder
> @@ -157,6 +158,60 @@ def add_target_and_index(
> )
>
>
> +class QAPIModule(QAPIDescription):
> + """
> + Directive to mark description of a new module.
> +
> + This directive doesn't generate any special formatting, and is just
> + a pass-through for the content body. Named section titles are
> + allowed in the content body.
> +
> + Use this directive to create entries for the QAPI module in the
> + global index and the qapi index; as well as to associate subsequent
QAPI unless there's a reason for lower case.
> + definitions with the module they are defined in for purposes of
> + search and QAPI index organization.
> +
> + :arg: The name of the module.
> + :opt no-index: Don't add cross-reference targets or index entries.
> + :opt no-typesetting: Don't render the content body (but preserve any
> + cross-reference target IDs in the squelched output.)
> +
> + Example::
> +
> + .. qapi:module:: block-core
> + :no-index:
> + :no-typesetting:
> +
> + Lorem ipsum, dolor sit amet ...
> + """
[...]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 24/62] docs/qapi-domain: add :unstable: directive option
2025-03-09 8:35 ` [PATCH v2 24/62] docs/qapi-domain: add :unstable: " John Snow
@ 2025-03-10 9:54 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 9:54 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell, Harmonie Snow
John Snow <jsnow@redhat.com> writes:
> Although "unstable" is a feature (and *will* appear in the features
> list), add a special :unstable: option to generate an eye-catch that
> makes this information very hard to miss.
>
> (The intent is to modify qapidoc.py to add this option whenever it
> detects that the features list attached to a definition contains the
> "unstable" entry.)
>
> RFC: Same comments as last patch.
I think we agreed to drop the RFC: part from the last patch's commit
message. Also drop it here, then.
> Signed-off-by: Harmonie Snow <harmonie@gmail.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 58/62] docs/qapidoc: generate entries for undocumented members
2025-03-09 8:35 ` [PATCH v2 58/62] docs/qapidoc: generate entries for undocumented members John Snow
@ 2025-03-10 10:08 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 10:08 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> Presently, we never have any empty text entries for members. The next
> patch will explicitly generate such sections, so enable support for it
> in advance.
>
> The parser will generate placeholder sections to indicate undocumented
> members, but it's the qapidoc generator that's responsible for deciding
> what to do with that stub section.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 96ac9f8620e..fec48fd01e1 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -235,11 +235,11 @@ def visit_member(self, section: QAPIDoc.ArgSection) -> None:
> # TODO?: features for members (documented at entity-level,
> # but sometimes defined per-member. Should we add such
> # information to member descriptions when we can?)
> - assert section.text and section.member
> + assert section.member
> self.generate_field(
> self.member_field_type,
> section.member,
> - section.text,
> + section.text if section.text else "(Not Documented.)",
The old generator uses just "Not documented". I don't mind the period,
but the parenthesis looks awkward:
Members:
* limits ("ThrottleLimits", optional) -- limits to apply
for this throttle group
* x-iops-total ("int", optional) -- (Not Documented.)
The old generator has a comment
# TODO drop fallbacks when undocumented members are outlawed
Let's have it here as well.
> section.info,
> )
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 59/62] qapi/parser: add undocumented stub members to all_sections
2025-03-09 8:35 ` [PATCH v2 59/62] qapi/parser: add undocumented stub members to all_sections John Snow
@ 2025-03-10 10:19 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 10:19 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This helps simplify the new doc generator if it doesn't have to check
> for undocumented members, it can just blindly operate on a sequence of
> QAPIDoc.Section instances.
>
> NB: If there is no existing 'member' section, these undocumented stub
> members will be inserted directly after the leading section.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> scripts/qapi/parser.py | 15 ++++++++++++++-
> 1 file changed, 14 insertions(+), 1 deletion(-)
>
> diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
> index 11c11bb09e5..58cb9f41ae8 100644
> --- a/scripts/qapi/parser.py
> +++ b/scripts/qapi/parser.py
> @@ -789,8 +789,21 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
> raise QAPISemError(member.info,
> "%s '%s' lacks documentation"
> % (member.role, member.name))
> - self.args[member.name] = QAPIDoc.ArgSection(
> + section = QAPIDoc.ArgSection(
> self.info, QAPIDoc.Kind.MEMBER, member.name)
> + self.args[member.name] = section
> +
> + # Insert stub documentation section for missing member docs.
> + # Determine where to insert stub doc - it should go at the
> + # end of the members section(s), if any. Note that index 0
> + # is assumed to be an untagged intro section, even if it is
> + # empty.
Please add
# TODO drop when undocumented members are outlawed
> + index = 1
> + if len(self.all_sections) > 1:
> + while self.all_sections[index].kind == QAPIDoc.Kind.MEMBER:
> + index += 1
> + self.all_sections.insert(index, section)
> +
> self.args[member.name].connect(member)
>
> def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 43/62] docs/qapidoc: add preamble() method
2025-03-10 9:46 ` Markus Armbruster
@ 2025-03-10 11:24 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 11:24 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
Markus Armbruster <armbru@redhat.com> writes:
> John Snow <jsnow@redhat.com> writes:
>
>> This method adds the options/preamble to each definition block. Notably,
>> :since: and :ifcond: are added, as are any "special features" such as
>> :deprecated: and :unstable:.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> docs/sphinx/qapidoc.py | 41 ++++++++++++++++++++++++++++++++++++++---
>> 1 file changed, 38 insertions(+), 3 deletions(-)
>>
>> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
>> index cf5dbb0133d..d8bf0073dfa 100644
>> --- a/docs/sphinx/qapidoc.py
>> +++ b/docs/sphinx/qapidoc.py
>> @@ -37,7 +37,12 @@
>> from docutils.parsers.rst import Directive, directives
>> from docutils.statemachine import StringList
>> from qapi.error import QAPIError
>> -from qapi.schema import QAPISchema, QAPISchemaVisitor
>> +from qapi.parser import QAPIDoc
>> +from qapi.schema import (
>> + QAPISchema,
>> + QAPISchemaDefinition,
>> + QAPISchemaVisitor,
>> +)
>> from qapi.source import QAPISourceInfo
>>
>> from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore
>> @@ -56,8 +61,6 @@
>> Sequence,
>> )
>>
>> - from qapi.parser import QAPIDoc
>> -
>> from sphinx.application import Sphinx
>> from sphinx.util.typing import ExtensionMetadata
>>
>> @@ -125,6 +128,38 @@ def ensure_blank_line(self) -> None:
>> # +2: correct for zero/one index, then increment by one.
>> self.add_line_raw("", fname, line + 2)
>>
>> + # Transmogrification helpers
>> +
>> + def preamble(self, ent: QAPISchemaDefinition) -> None:
>> + """
>> + Generate option lines for qapi entity directives.
>
> QAPI index unless there's a reason for lower case.
Pasto; I mean QAPI entity, of course.
>> + """
>
> [...]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 33/62] qapi: expand tags to all doc sections
2025-03-09 20:57 ` Markus Armbruster
@ 2025-03-10 11:44 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 11:44 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
Markus Armbruster <armbru@redhat.com> writes:
> John Snow <jsnow@redhat.com> writes:
>
>> This patch adds an explicit section "kind" to all QAPIDoc
>> sections. Members/Features are now explicitly marked as such, with the
>> name now being stored in a dedicated "name" field (which qapidoc.py was
>> not actually using anyway.)
>>
>> The qapi-schema tests are updated to account for the new section names;
>> mostly "TODO" becomes "Todo" and `None` becomes "Plain".
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>
> I think the commit message could be clearer. But I'm too tired for
> wordsmithing. I intend to have another look at it tomorrow.
qapi: Clean up encoding of section kinds
We have several kinds of sections, and to tell them apart, we use
Section attribute @tag and also the section object's Python type:
type @tag
untagged Section None
@foo: ArgSection 'foo'
Returns: Section 'Returns'
Errors: Section 'Errors'
Since: Section 'Since'
TODO: Section 'TODO'
Note:
* @foo can be a member or a feature description, depending on context.
* tag == 'Since' can be a Since: section or a member or feature
description. If it's a Section, it's the former, and if it's an
ArgSection, it's the latter.
Clean this up as follows. Move the member or feature name to new
ArgSection attribute @name, and replace @tag by enum @kind like this:
type kind name
untagged Section PLAIN
@foo: ArgSection MEMBER 'foo' if member or argument
ArgSection FEATURE 'foo' if feature
Returns: Section RETURNS
Errors: Section ERRORS
Since: Section SINCE
TODO: Section TODO
The qapi-schema tests are updated to account for the new section names;
"TODO" becomes "Todo" and `None` becomes "Plain" there.
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 54/62] docs/qapidoc: implement transmogrify() method
2025-03-09 8:35 ` [PATCH v2 54/62] docs/qapidoc: implement transmogrify() method John Snow
@ 2025-03-10 12:42 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-10 12:42 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> This is the true top-level processor for the new transmogrifier;
> responsible both for generating the intermediate rST and then running
> the nested parse on that generated document to produce the final
> docutils tree that is then - very finally - postprocessed by sphinx for
> final rendering to HTML &c.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 49 +++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 48 insertions(+), 1 deletion(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 5fd8a7c1fc1..11b8fca21ec 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -2,6 +2,7 @@
> #
> # QEMU qapidoc QAPI file parsing extension
> #
> +# Copyright (c) 2024 Red Hat
2025
> # Copyright (c) 2020 Linaro
> #
> # This work is licensed under the terms of the GNU GPLv2 or later.
[...]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 43/62] docs/qapidoc: add preamble() method
2025-03-09 21:03 ` Markus Armbruster
@ 2025-03-10 21:05 ` John Snow
2025-03-10 21:06 ` John Snow
0 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-10 21:05 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
[-- Attachment #1: Type: text/plain, Size: 3530 bytes --]
On Sun, Mar 9, 2025 at 5:03 PM Markus Armbruster <armbru@redhat.com> wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > This method adds the options/preamble to each definition block. Notably,
> > :since: and :ifcond: are added, as are any "special features" such as
> > :deprecated: and :unstable:.
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > docs/sphinx/qapidoc.py | 41 ++++++++++++++++++++++++++++++++++++++---
> > 1 file changed, 38 insertions(+), 3 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index cf5dbb0133d..d8bf0073dfa 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -37,7 +37,12 @@
> > from docutils.parsers.rst import Directive, directives
> > from docutils.statemachine import StringList
> > from qapi.error import QAPIError
> > -from qapi.schema import QAPISchema, QAPISchemaVisitor
> > +from qapi.parser import QAPIDoc
> > +from qapi.schema import (
> > + QAPISchema,
> > + QAPISchemaDefinition,
> > + QAPISchemaVisitor,
> > +)
> > from qapi.source import QAPISourceInfo
> >
> > from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore
> > @@ -56,8 +61,6 @@
> > Sequence,
> > )
> >
> > - from qapi.parser import QAPIDoc
> > -
>
> Accident?
>
I don't know. isort decided to move it and none of my tooling complains
about it.
>
> > from sphinx.application import Sphinx
> > from sphinx.util.typing import ExtensionMetadata
> >
> > @@ -125,6 +128,38 @@ def ensure_blank_line(self) -> None:
> > # +2: correct for zero/one index, then increment by one.
> > self.add_line_raw("", fname, line + 2)
> >
> > + # Transmogrification helpers
> > +
> > + def preamble(self, ent: QAPISchemaDefinition) -> None:
> > + """
> > + Generate option lines for qapi entity directives.
> > + """
> > + if ent.doc and ent.doc.since:
> > + assert ent.doc.since.kind == QAPIDoc.Kind.SINCE
> > + # Generated from the entity's docblock; info location is
> exact.
> > + self.add_line(f":since: {ent.doc.since.text}",
> ent.doc.since.info)
>
> Break the line aftee the comma?
>
> > +
> > + if ent.ifcond.is_present():
> > + doc = ent.ifcond.docgen()
> > + assert ent.info
> > + # Generated from entity definition; info location is
> approximate.
> > + self.add_line(f":ifcond: {doc}", ent.info)
> > +
> > + # Hoist special features such as :deprecated: and :unstable:
> > + # into the options block for the entity. If, in the future, new
> > + # special features are added, qapi-domain will chirp about
> > + # unrecognized options and fail until they are handled in
> > + # qapi-domain.
> > + for feat in ent.features:
> > + if feat.is_special():
> > + # FIXME: handle ifcond if present. How to display that
>
> If I remember correctly, you wanted to mention this FIXME in the commit
> message.
>
> > + # information is TBD.
> > + # Generated from entity def; info location is
> approximate.
> > + assert feat.info
> > + self.add_line(f":{feat.name}:", feat.info)
> > +
> > + self.ensure_blank_line()
> > +
> > # Transmogrification core methods
> >
> > def visit_module(self, path: str) -> None:
>
>
[-- Attachment #2: Type: text/html, Size: 5222 bytes --]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 43/62] docs/qapidoc: add preamble() method
2025-03-10 21:05 ` John Snow
@ 2025-03-10 21:06 ` John Snow
0 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-10 21:06 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
[-- Attachment #1: Type: text/plain, Size: 3816 bytes --]
On Mon, Mar 10, 2025 at 5:05 PM John Snow <jsnow@redhat.com> wrote:
>
>
> On Sun, Mar 9, 2025 at 5:03 PM Markus Armbruster <armbru@redhat.com>
> wrote:
>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > This method adds the options/preamble to each definition block. Notably,
>> > :since: and :ifcond: are added, as are any "special features" such as
>> > :deprecated: and :unstable:.
>> >
>> > Signed-off-by: John Snow <jsnow@redhat.com>
>> > ---
>> > docs/sphinx/qapidoc.py | 41 ++++++++++++++++++++++++++++++++++++++---
>> > 1 file changed, 38 insertions(+), 3 deletions(-)
>> >
>> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
>> > index cf5dbb0133d..d8bf0073dfa 100644
>> > --- a/docs/sphinx/qapidoc.py
>> > +++ b/docs/sphinx/qapidoc.py
>> > @@ -37,7 +37,12 @@
>> > from docutils.parsers.rst import Directive, directives
>> > from docutils.statemachine import StringList
>> > from qapi.error import QAPIError
>> > -from qapi.schema import QAPISchema, QAPISchemaVisitor
>> > +from qapi.parser import QAPIDoc
>> > +from qapi.schema import (
>> > + QAPISchema,
>> > + QAPISchemaDefinition,
>> > + QAPISchemaVisitor,
>> > +)
>> > from qapi.source import QAPISourceInfo
>> >
>> > from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore
>> > @@ -56,8 +61,6 @@
>> > Sequence,
>> > )
>> >
>> > - from qapi.parser import QAPIDoc
>> > -
>>
>> Accident?
>>
>
> I don't know. isort decided to move it and none of my tooling complains
> about it.
>
Ah, no, it's because it becomes used at runtime. Promoted from "type hint
only".
--js
>
>
>>
>> > from sphinx.application import Sphinx
>> > from sphinx.util.typing import ExtensionMetadata
>> >
>> > @@ -125,6 +128,38 @@ def ensure_blank_line(self) -> None:
>> > # +2: correct for zero/one index, then increment by one.
>> > self.add_line_raw("", fname, line + 2)
>> >
>> > + # Transmogrification helpers
>> > +
>> > + def preamble(self, ent: QAPISchemaDefinition) -> None:
>> > + """
>> > + Generate option lines for qapi entity directives.
>> > + """
>> > + if ent.doc and ent.doc.since:
>> > + assert ent.doc.since.kind == QAPIDoc.Kind.SINCE
>> > + # Generated from the entity's docblock; info location is
>> exact.
>> > + self.add_line(f":since: {ent.doc.since.text}",
>> ent.doc.since.info)
>>
>> Break the line aftee the comma?
>>
>> > +
>> > + if ent.ifcond.is_present():
>> > + doc = ent.ifcond.docgen()
>> > + assert ent.info
>> > + # Generated from entity definition; info location is
>> approximate.
>> > + self.add_line(f":ifcond: {doc}", ent.info)
>> > +
>> > + # Hoist special features such as :deprecated: and :unstable:
>> > + # into the options block for the entity. If, in the future, new
>> > + # special features are added, qapi-domain will chirp about
>> > + # unrecognized options and fail until they are handled in
>> > + # qapi-domain.
>> > + for feat in ent.features:
>> > + if feat.is_special():
>> > + # FIXME: handle ifcond if present. How to display that
>>
>> If I remember correctly, you wanted to mention this FIXME in the commit
>> message.
>>
>> > + # information is TBD.
>> > + # Generated from entity def; info location is
>> approximate.
>> > + assert feat.info
>> > + self.add_line(f":{feat.name}:", feat.info)
>> > +
>> > + self.ensure_blank_line()
>> > +
>> > # Transmogrification core methods
>> >
>> > def visit_module(self, path: str) -> None:
>>
>>
[-- Attachment #2: Type: text/html, Size: 5961 bytes --]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 42/62] docs/qapidoc: add visit_freeform() method
2025-03-09 21:01 ` Markus Armbruster
@ 2025-03-10 21:11 ` John Snow
0 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-10 21:11 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
[-- Attachment #1: Type: text/plain, Size: 3519 bytes --]
On Sun, Mar 9, 2025 at 5:01 PM Markus Armbruster <armbru@redhat.com> wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > docs/sphinx/qapidoc.py | 50 ++++++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 50 insertions(+)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index 6de8c900543..cf5dbb0133d 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -29,6 +29,7 @@
> > from contextlib import contextmanager
> > import os
> > from pathlib import Path
> > +import re
> > import sys
> > from typing import TYPE_CHECKING
> >
> > @@ -55,6 +56,8 @@
> > Sequence,
> > )
> >
> > + from qapi.parser import QAPIDoc
> > +
> > from sphinx.application import Sphinx
> > from sphinx.util.typing import ExtensionMetadata
> >
> > @@ -130,6 +133,53 @@ def visit_module(self, path: str) -> None:
> > self.add_line_raw(f".. qapi:module:: {name}", path, 1)
> > self.ensure_blank_line()
> >
> > + def visit_freeform(self, doc: QAPIDoc) -> None:
> > + # TODO: Once the old qapidoc transformer is deprecated, freeform
> > + # sections can be updated to pure rST, and this transformed
> removed.
> > + #
> > + # For now, translate our micro-format into rST. Code adapted
> > + # from Peter Maydell's freeform().
> > +
> > + assert len(doc.all_sections) == 1, doc.all_sections
> > + body = doc.all_sections[0]
> > + text = body.text
> > + info = doc.info
> > +
> > + if re.match(r"=+ ", text):
> > + # Section/subsection heading (if present, will always be the
> > + # first line of the block)
> > + (heading, _, text) = text.partition("\n")
> > + (leader, _, heading) = heading.partition(" ")
> > + level = len(leader) + 1 # Implicit +1 for heading in .rST
> stub
>
> I find "in .rST stub" less than clear. Could we use the filename?
>
No, because the filename is "wherever the qapi-doc directive is written". I
adjusted the comment and will re-spin.
>
> > +
> > + #
> https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#sections
> > + markers = {
> > + 1: "#",
> > + 2: "*",
> > + 3: "=",
> > + 4: "-",
> > + 5: "^",
> > + 6: '"',
> > + }
>
> Suggest
>
> markers = '#*=-^".
>
Done.
>
> > + overline = level <= 2
> > + marker = markers[level]
> > +
> > + self.ensure_blank_line()
> > + # This credits all 2 or 3 lines to the single source line.
> > + if overline:
> > + self.add_line(marker * len(heading), info)
> > + self.add_line(heading, info)
> > + self.add_line(marker * len(heading), info)
> > + self.ensure_blank_line()
> > +
> > + # Eat blank line(s) and advance info
> > + trimmed = text.lstrip("\n")
> > + text = trimmed
> > + info = info.next_line(len(text) - len(trimmed) + 1)
> > +
> > + self.add_lines(text, info)
> > + self.ensure_blank_line()
> > +
> >
> > class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
> > """A QAPI schema visitor which adds Sphinx dependencies each module
>
>
[-- Attachment #2: Type: text/html, Size: 5352 bytes --]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 10/62] docs/qapi-domain: Add ObjectDescription abstract class
2025-03-10 9:15 ` Markus Armbruster
@ 2025-03-10 21:12 ` John Snow
0 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-10 21:12 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
[-- Attachment #1: Type: text/plain, Size: 4709 bytes --]
On Mon, Mar 10, 2025 at 5:15 AM Markus Armbruster <armbru@redhat.com> wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > This class is a generic, top-level directive for documenting some kind
> > of QAPI thingamajig that we expect to go into the Index. This class
> > doesn't do much by itself, and it isn't yet associated with any
> > particular directive.
> >
> > Only handle_signature() is defined in the base class; get_index_text and
> > add_target_and_index are new methods defined here; they are based
> > heavily on the layout and format of the Python domain's general object
> > class.
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > docs/sphinx/qapi_domain.py | 65 ++++++++++++++++++++++++++++++++++++--
> > 1 file changed, 63 insertions(+), 2 deletions(-)
> >
> > diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
> > index 49d42c0921c..0365891f354 100644
> > --- a/docs/sphinx/qapi_domain.py
> > +++ b/docs/sphinx/qapi_domain.py
> > @@ -14,11 +14,13 @@
> > NamedTuple,
> > Optional,
> > Tuple,
> > + cast,
> > )
> >
> > from docutils import nodes
> >
> > -from sphinx.addnodes import pending_xref
> > +from sphinx.addnodes import desc_signature, pending_xref
> > +from sphinx.directives import ObjectDescription
> > from sphinx.domains import (
> > Domain,
> > Index,
> > @@ -28,7 +30,7 @@
> > from sphinx.locale import _, __
> > from sphinx.roles import XRefRole
> > from sphinx.util import logging
> > -from sphinx.util.nodes import make_refnode
> > +from sphinx.util.nodes import make_id, make_refnode
> >
> >
> > if TYPE_CHECKING:
> > @@ -96,6 +98,65 @@ def process_link(
> > return title, target
> >
> >
> > +Signature = str
> > +
> > +
> > +class QAPIDescription(ObjectDescription[Signature]):
> > + """
> > + Generic QAPI description.
> > +
> > + Abstract class, not instantiated directly.
> > + """
> > +
> > + def handle_signature(self, sig: str, signode: desc_signature) ->
> Signature:
> > + # Do nothing. The return value here is the "name" of the entity
> > + # being documented; for QAPI, this is the same as the
> > + # "signature", which is just a name.
> > +
> > + # Normally this method must also populate signode with nodes to
> > + # render the signature; here we do nothing instead.
> > + return sig
> > +
> > + def get_index_text(self, name: Signature) -> Tuple[str, str]:
> > + """Return the text for the index entry of the object."""
> > +
> > + # NB: this is used for the global index, not the QAPI index.
> > + return ("single", f"{name} (QMP {self.objtype})")
> > +
> > + def add_target_and_index(
> > + self, name: Signature, sig: str, signode: desc_signature
> > + ) -> None:
> > + # name is the return value of handle_signature.
> > + # sig is the original, raw text argument to handle_signature.
> > + # For QAPI, these are identical, currently.
> > +
> > + assert self.objtype
> > +
> > + # If we're documenting a module, don't include the module as
> > + # part of the FQN.
> > + modname = ""
> > + if self.objtype != "module":
> > + modname = self.options.get(
> > + "module", self.env.ref_context.get("qapi:module")
> > + )
> > + fullname = (modname + "." if modname else "") + name
> > +
> > + node_id = make_id(self.env, self.state.document, self.objtype,
> fullname)
>
> pycodestyle-3 points out:
>
> docs/sphinx/qapi_domain.py:144:80: E501 line too long (80 > 79
> characters)
>
Adjusted my black config to aim for 79 chars instead of 80 and it fixes
this one.
>
> > + signode["ids"].append(node_id)
> > +
> > + self.state.document.note_explicit_target(signode)
> > + domain = cast(QAPIDomain, self.env.get_domain("qapi"))
> > + domain.note_object(fullname, self.objtype, node_id,
> location=signode)
>
> This one's pushing it, too :)
>
black left this one alone, though. For consistency I'm just doing whatever
black tells me.
>
> > +
> > + if "no-index-entry" not in self.options:
> > + arity, indextext = self.get_index_text(name)
> > + assert self.indexnode is not None
> > + if indextext:
> > + self.indexnode["entries"].append(
> > + (arity, indextext, node_id, "", None)
> > + )
> > +
> > +
> > class QAPIIndex(Index):
> > """
> > Index subclass to provide the QAPI definition index.
>
>
[-- Attachment #2: Type: text/html, Size: 6537 bytes --]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 45/62] docs/qapidoc: add visit_errors() method
2025-03-09 21:05 ` Markus Armbruster
@ 2025-03-10 21:17 ` John Snow
2025-03-11 6:08 ` Markus Armbruster
0 siblings, 1 reply; 101+ messages in thread
From: John Snow @ 2025-03-10 21:17 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
[-- Attachment #1: Type: text/plain, Size: 1718 bytes --]
On Sun, Mar 9, 2025 at 5:05 PM Markus Armbruster <armbru@redhat.com> wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > Notably, this method does not currently address the formatting issues
> > present with the "errors" section in QAPIDoc and just vomits the text
> > verbatim into the rST doc, with somewhat inconsistent results.
> >
> > To be addressed in a future revision.
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > docs/sphinx/qapidoc.py | 6 ++++++
> > 1 file changed, 6 insertions(+)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index b96445f0802..14feafe866e 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -139,6 +139,12 @@ def visit_paragraph(self, section: QAPIDoc.Section)
> -> None:
> > self.add_lines(section.text, section.info)
> > self.ensure_blank_line()
> >
> > + def visit_errors(self, section: QAPIDoc.Section) -> None:
> > + # FIXME: the formatting for errors may be inconsistent and may
>
> If I remember correctly, you wanted to mention this FIXME in the commit
> message.
>
Well, that's what the entire commit message is already about! Both of these
places are talking about the visual misalignment and enforcing the source
formatting of errors to fix the visual alignment problems.
>
> > + # or may not require different newline placement to ensure
> > + # proper rendering as a nested list.
> > + self.add_lines(f":error:\n{section.text}", section.info)
> > +
> > def preamble(self, ent: QAPISchemaDefinition) -> None:
> > """
> > Generate option lines for qapi entity directives.
>
>
[-- Attachment #2: Type: text/html, Size: 2702 bytes --]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 56/62] docs/qapidoc: add intermediate output debugger
2025-03-09 21:17 ` Markus Armbruster
@ 2025-03-10 21:26 ` John Snow
0 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-10 21:26 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
[-- Attachment #1: Type: text/plain, Size: 728 bytes --]
On Sun, Mar 9, 2025 at 5:17 PM Markus Armbruster <armbru@redhat.com> wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > Add debugging output for the qapidoc transmogrifier - setting DEBUG=1
> > will produce .ir files (one for each qapidoc directive) that write the
> > generated rst file to disk to allow for easy debugging and verification
> > of the generated document.
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
>
> You wrote you intend to advertise this debugging feature in the
> transmogrifier doc and/or extend the doc-writing section of the qapi
> code gen doc. If you did this, pointer, please. If not, no worries, it
> can be done at a later time.
Still working on the doc. For rc1.
[-- Attachment #2: Type: text/html, Size: 1239 bytes --]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 61/62] docs: enable qapidoc transmogrifier for QEMU QMP Reference
2025-03-09 21:13 ` Markus Armbruster
@ 2025-03-10 21:33 ` John Snow
0 siblings, 0 replies; 101+ messages in thread
From: John Snow @ 2025-03-10 21:33 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
[-- Attachment #1: Type: text/plain, Size: 2716 bytes --]
On Sun, Mar 9, 2025 at 5:13 PM Markus Armbruster <armbru@redhat.com> wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > We are not enabling the transmogrifier for QSD or QGA yet because we
> > don't (yet) have a way to create separate indices, and all of the
> > definitions will bleed together, which isn't so nice.
> >
> > For now, QMP is better than nothing at all!
> >
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > docs/interop/qemu-qmp-ref.rst | 1 +
> > qapi/qapi-schema.json | 2 ++
> > 2 files changed, 3 insertions(+)
> >
> > diff --git a/docs/interop/qemu-qmp-ref.rst
> b/docs/interop/qemu-qmp-ref.rst
> > index f94614a0b2f..e95eeac45e2 100644
> > --- a/docs/interop/qemu-qmp-ref.rst
> > +++ b/docs/interop/qemu-qmp-ref.rst
> > @@ -7,3 +7,4 @@ QEMU QMP Reference Manual
> > :depth: 3
> >
> > .. qapi-doc:: qapi/qapi-schema.json
> > + :transmogrify:
> > diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
> > index 2877aff73d0..4475e81cc3e 100644
> > --- a/qapi/qapi-schema.json
> > +++ b/qapi/qapi-schema.json
> > @@ -5,6 +5,8 @@
> > #
> > # This document describes all commands currently supported by QMP.
> > #
> > +# For locating a particular item, please see the `qapi-index`.
> > +#
>
> ```qapi-index``` becomes a link in HTML. The link takes me to an index
> page. Two observations:
>
> * The index page appears not to be linked from the navigation thingie on
> the left. Searching for "QAPI Index" there doesn't find it, either.
>
We'll have to find a way to add it, and where. I don't actually know how
right away. We can discuss this on the namespace patches.
>
> * The index is structured into sections titled Alternates, Commands |
> Enums, Events, Modules, Objects, A, ... Z. As I scrolled down
> quickly, the transition from Objects to A confused me briefly: I
> didn't immediately understand that A, ... Z contains the union of
> everything above sorted into letter buckets.
>
Yes, it's weird. I don't have a lot of control over how the index is laid
out, but felt that it was very useful to have both by type (especially to
see commands and events all in one place) and a "classic" alphabetical
reference.
I don't think I can make any meaningful adjustments to this before rc0, I
apologize. Let's revisit soon on the namespace patches, because each
namespace will need its own index and it's a good time to discuss how to
lay them out.
>
> > # Most of the time their usage is exactly the same as in the user
> > # Monitor, this means that any other document which also describe
> > # commands (the manpage, QEMU's manual, etc) can and should be
>
>
[-- Attachment #2: Type: text/html, Size: 3801 bytes --]
^ permalink raw reply [flat|nested] 101+ messages in thread
* Re: [PATCH v2 45/62] docs/qapidoc: add visit_errors() method
2025-03-10 21:17 ` John Snow
@ 2025-03-11 6:08 ` Markus Armbruster
0 siblings, 0 replies; 101+ messages in thread
From: Markus Armbruster @ 2025-03-11 6:08 UTC (permalink / raw)
To: John Snow
Cc: qemu-devel, Philippe Mathieu-Daudé, Michael Roth,
Daniel P. Berrangé, Eric Blake, Thomas Huth,
Alex Bennée, Peter Maydell
John Snow <jsnow@redhat.com> writes:
> On Sun, Mar 9, 2025 at 5:05 PM Markus Armbruster <armbru@redhat.com> wrote:
>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > Notably, this method does not currently address the formatting issues
>> > present with the "errors" section in QAPIDoc and just vomits the text
>> > verbatim into the rST doc, with somewhat inconsistent results.
>> >
>> > To be addressed in a future revision.
>> >
>> > Signed-off-by: John Snow <jsnow@redhat.com>
>> > ---
>> > docs/sphinx/qapidoc.py | 6 ++++++
>> > 1 file changed, 6 insertions(+)
>> >
>> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
>> > index b96445f0802..14feafe866e 100644
>> > --- a/docs/sphinx/qapidoc.py
>> > +++ b/docs/sphinx/qapidoc.py
>> > @@ -139,6 +139,12 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None:
>> > self.add_lines(section.text, section.info)
>> > self.ensure_blank_line()
>> >
>> > + def visit_errors(self, section: QAPIDoc.Section) -> None:
>> > + # FIXME: the formatting for errors may be inconsistent and may
>>
>> If I remember correctly, you wanted to mention this FIXME in the commit
>> message.
>>
>
> Well, that's what the entire commit message is already about! Both of these
> places are talking about the visual misalignment and enforcing the source
> formatting of errors to fix the visual alignment problems.
Alright. If the patch was larger, I'd ask you to replace
To be addressed in a future revision.
by something like
Marked FIXME in the code.
Both tell me there's something in need of fixing, but only the latter
tells me how to find the spot in need.
[...]
^ permalink raw reply [flat|nested] 101+ messages in thread
end of thread, other threads:[~2025-03-11 6:09 UTC | newest]
Thread overview: 101+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-03-09 8:34 [PATCH v2 00/62] docs: Add new QAPI transmogrifier John Snow
2025-03-09 8:34 ` [PATCH v2 01/62] do-not-merge John Snow
2025-03-09 8:34 ` [PATCH v2 02/62] qapi: shush pylint up John Snow
2025-03-09 19:41 ` Markus Armbruster
2025-03-09 8:34 ` [PATCH v2 03/62] docs/sphinx: create QAPI domain extension stub John Snow
2025-03-09 8:34 ` [PATCH v2 04/62] docs/sphinx: add compat.py module and nested_parse helper John Snow
2025-03-09 8:34 ` [PATCH v2 05/62] docs/qapi-domain: add QAPI domain object registry John Snow
2025-03-09 8:34 ` [PATCH v2 06/62] docs/qapi-domain: add QAPI index John Snow
2025-03-09 8:34 ` [PATCH v2 07/62] docs/qapi-domain: add resolve_any_xref() John Snow
2025-03-09 8:34 ` [PATCH v2 08/62] docs/qapi-domain: add QAPI xref roles John Snow
2025-03-09 8:34 ` [PATCH v2 09/62] docs/qapi-domain: add compatibility node classes John Snow
2025-03-09 8:34 ` [PATCH v2 10/62] docs/qapi-domain: Add ObjectDescription abstract class John Snow
2025-03-10 9:15 ` Markus Armbruster
2025-03-10 21:12 ` John Snow
2025-03-09 8:34 ` [PATCH v2 11/62] docs/qapi-domain: add qapi:module directive John Snow
2025-03-10 9:47 ` Markus Armbruster
2025-03-09 8:34 ` [PATCH v2 12/62] docs/qapi-domain: add QAPIObject class John Snow
2025-03-10 9:46 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 13/62] docs/qapi-domain: add qapi:command directive John Snow
2025-03-09 8:35 ` [PATCH v2 14/62] docs/qapi-domain: add :since: directive option John Snow
2025-03-09 8:35 ` [PATCH v2 15/62] docs/qapi-domain: add "Arguments:" field lists John Snow
2025-03-09 20:37 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 16/62] docs/qapi-domain: add "Features:" " John Snow
2025-03-09 20:42 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 17/62] docs/qapi-domain: add "Errors:" " John Snow
2025-03-09 8:35 ` [PATCH v2 18/62] docs/qapi-domain: add "Return:" " John Snow
2025-03-09 20:46 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 19/62] docs/qapi-domain: add qapi:enum directive John Snow
2025-03-09 8:35 ` [PATCH v2 20/62] docs/qapi-domain: add qapi:alternate directive John Snow
2025-03-09 8:35 ` [PATCH v2 21/62] docs/qapi-domain: add qapi:event directive John Snow
2025-03-09 8:35 ` [PATCH v2 22/62] docs/qapi-domain: add qapi:object directive John Snow
2025-03-09 8:35 ` [PATCH v2 23/62] docs/qapi-domain: add :deprecated: directive option John Snow
2025-03-09 20:51 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 24/62] docs/qapi-domain: add :unstable: " John Snow
2025-03-10 9:54 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 25/62] docs/qapi-domain: add :ifcond: " John Snow
2025-03-09 8:35 ` [PATCH v2 26/62] docs/qapi-domain: add warnings for malformed field lists John Snow
2025-03-09 8:35 ` [PATCH v2 27/62] docs/qapi-domain: add type cross-refs to " John Snow
2025-03-09 8:35 ` [PATCH v2 28/62] docs/qapi-domain: add CSS styling John Snow
2025-03-10 9:47 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 29/62] docs/qapi-domain: add XREF compatibility goop for Sphinx < 4.1 John Snow
2025-03-09 8:35 ` [PATCH v2 30/62] docs/qapi-domain: warn when QAPI domain xrefs fail to resolve John Snow
2025-03-09 8:35 ` [PATCH v2 31/62] docs/qapi-domain: Fix error context reporting in Sphinx 5.x and 6.x John Snow
2025-03-09 8:35 ` [PATCH v2 32/62] qapi/parser: adjust info location for doc body section John Snow
2025-03-09 20:53 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 33/62] qapi: expand tags to all doc sections John Snow
2025-03-09 20:57 ` Markus Armbruster
2025-03-10 11:44 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 34/62] qapi/schema: add __repr__ to QAPIDoc.Section John Snow
2025-03-09 8:35 ` [PATCH v2 35/62] docs/qapidoc: add transmogrifier stub John Snow
2025-03-09 8:35 ` [PATCH v2 36/62] docs/qapidoc: split old implementation into qapidoc_legacy.py John Snow
2025-03-09 8:35 ` [PATCH v2 37/62] docs/qapidoc: Fix static typing on qapidoc.py John Snow
2025-03-09 8:35 ` [PATCH v2 38/62] do-not-merge John Snow
2025-03-09 8:35 ` [PATCH v2 39/62] docs/qapidoc: add transmogrifier class stub John Snow
2025-03-09 8:35 ` [PATCH v2 40/62] docs/qapidoc: add visit_module() method John Snow
2025-03-09 8:35 ` [PATCH v2 41/62] qapi/source: allow multi-line QAPISourceInfo advancing John Snow
2025-03-09 20:59 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 42/62] docs/qapidoc: add visit_freeform() method John Snow
2025-03-09 21:01 ` Markus Armbruster
2025-03-10 21:11 ` John Snow
2025-03-09 8:35 ` [PATCH v2 43/62] docs/qapidoc: add preamble() method John Snow
2025-03-09 21:03 ` Markus Armbruster
2025-03-10 21:05 ` John Snow
2025-03-10 21:06 ` John Snow
2025-03-10 9:46 ` Markus Armbruster
2025-03-10 11:24 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 44/62] docs/qapidoc: add visit_paragraph() method John Snow
2025-03-09 8:35 ` [PATCH v2 45/62] docs/qapidoc: add visit_errors() method John Snow
2025-03-09 21:05 ` Markus Armbruster
2025-03-10 21:17 ` John Snow
2025-03-11 6:08 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 46/62] docs/qapidoc: add format_type() method John Snow
2025-03-09 8:35 ` [PATCH v2 47/62] docs/qapidoc: add add_field() and generate_field() helper methods John Snow
2025-03-09 8:35 ` [PATCH v2 48/62] docs/qapidoc: add visit_feature() method John Snow
2025-03-09 21:06 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 49/62] docs/qapidoc: prepare to record entity being transmogrified John Snow
2025-03-09 8:35 ` [PATCH v2 50/62] docs/qapidoc: add visit_returns() method John Snow
2025-03-09 21:08 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 51/62] docs/qapidoc: add visit_member() method John Snow
2025-03-09 21:09 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 52/62] docs/qapidoc: add visit_sections() method John Snow
2025-03-09 21:10 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 53/62] docs/qapidoc: add visit_entity() John Snow
2025-03-09 8:35 ` [PATCH v2 54/62] docs/qapidoc: implement transmogrify() method John Snow
2025-03-10 12:42 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 55/62] docs/qapidoc: process @foo into ``foo`` John Snow
2025-03-09 8:35 ` [PATCH v2 56/62] docs/qapidoc: add intermediate output debugger John Snow
2025-03-09 21:17 ` Markus Armbruster
2025-03-10 21:26 ` John Snow
2025-03-09 8:35 ` [PATCH v2 57/62] docs/qapidoc: Add "the members of" pointers John Snow
2025-03-09 8:35 ` [PATCH v2 58/62] docs/qapidoc: generate entries for undocumented members John Snow
2025-03-10 10:08 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 59/62] qapi/parser: add undocumented stub members to all_sections John Snow
2025-03-10 10:19 ` Markus Armbruster
2025-03-09 8:35 ` [PATCH v2 60/62] docs: disambiguate cross-references John Snow
2025-03-09 8:35 ` [PATCH v2 61/62] docs: enable qapidoc transmogrifier for QEMU QMP Reference John Snow
2025-03-09 21:13 ` Markus Armbruster
2025-03-10 21:33 ` John Snow
2025-03-09 8:35 ` [PATCH v2 62/62] docs: add qapi-domain syntax documentation John Snow
2025-03-09 21:19 ` [PATCH v2 00/62] docs: Add new QAPI transmogrifier Markus Armbruster
-- strict thread matches above, loose matches on Subject: below --
2025-03-09 8:31 John Snow
2025-03-09 8:31 ` [PATCH v2 01/62] do-not-merge John Snow
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).