From: John Snow <jsnow@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Michael Roth" <michael.roth@amd.com>,
"Alex Bennée" <alex.bennee@linaro.org>,
"Philippe Mathieu-Daudé" <philmd@linaro.org>,
"Peter Maydell" <peter.maydell@linaro.org>,
"Thomas Huth" <thuth@redhat.com>,
"Daniel P. Berrangé" <berrange@redhat.com>,
"Markus Armbruster" <armbru@redhat.com>,
"John Snow" <jsnow@redhat.com>
Subject: [PATCH 27/57] docs/qapi-domain: add XREF compatibility goop for Sphinx < 4.1
Date: Tue, 4 Mar 2025 22:45:36 -0500 [thread overview]
Message-ID: <20250305034610.960147-28-jsnow@redhat.com> (raw)
In-Reply-To: <20250305034610.960147-1-jsnow@redhat.com>
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 | 129 ++++++++++++++++++++++++++++++++++++-
docs/sphinx/qapi_domain.py | 24 ++++---
2 files changed, 142 insertions(+), 11 deletions(-)
diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py
index 1f9f1f42ef7..7e8d72c9b1f 100644
--- a/docs/sphinx/compat.py
+++ b/docs/sphinx/compat.py
@@ -2,14 +2,32 @@
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.docutils import SphinxDirective, switch_source_input
+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.nodes import nested_parse_with_titles
+from sphinx.util.typing import TextlikeNode
+
+
+MAKE_XREF_WORKAROUND = sphinx.version_info[:3] < (4, 1, 0)
SpaceNode: Callable[[str], Node]
@@ -46,3 +64,110 @@ def nested_parse(directive: SphinxDirective, content_node: Element) -> None:
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 753e07024f5..d3487c5dfeb 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -26,7 +26,14 @@
from docutils import nodes
from docutils.parsers.rst import directives
-from compat import KeywordNode, SpaceNode, nested_parse
+from compat import (
+ CompatField,
+ CompatGroupedField,
+ CompatTypedField,
+ KeywordNode,
+ SpaceNode,
+ nested_parse,
+)
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.directives import ObjectDescription
@@ -39,7 +46,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.docutils import SphinxDirective
from sphinx.util.nodes import make_id, make_refnode
@@ -187,7 +193,7 @@ class QAPIObject(ObjectDescription[Signature]):
doc_field_types = [
# :feat name: descr
- GroupedField(
+ CompatGroupedField(
"feature",
label=_("Features"),
names=("feat",),
@@ -444,7 +450,7 @@ class QAPICommand(QAPIObject):
doc_field_types.extend(
[
# :arg TypeName ArgName: descr
- TypedField(
+ CompatTypedField(
"argument",
label=_("Arguments"),
names=("arg",),
@@ -452,14 +458,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=_("Returns"),
rolename="type",
@@ -477,7 +483,7 @@ class QAPIEnum(QAPIObject):
doc_field_types.extend(
[
# :value name: descr
- GroupedField(
+ CompatGroupedField(
"value",
label=_("Values"),
names=("value",),
@@ -494,7 +500,7 @@ class QAPIAlternate(QAPIObject):
doc_field_types.extend(
[
# :choice type name: descr
- TypedField(
+ CompatTypedField(
"choice",
label=_("Choices"),
names=("choice",),
@@ -512,7 +518,7 @@ class QAPIObjectWithMembers(QAPIObject):
doc_field_types.extend(
[
# :member type name: descr
- TypedField(
+ CompatTypedField(
"member",
label=_("Members"),
names=("memb",),
--
2.48.1
next prev parent reply other threads:[~2025-03-05 3:48 UTC|newest]
Thread overview: 129+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-05 3:45 [PATCH 00/57] docs: Add new QAPI transmogrifier John Snow
2025-03-05 3:45 ` [PATCH 01/57] do-not-merge John Snow
2025-03-05 3:45 ` [PATCH 02/57] qapi: shush pylint up John Snow
2025-03-05 6:28 ` Markus Armbruster
2025-03-05 15:31 ` John Snow
2025-03-05 3:45 ` [PATCH 03/57] docs/sphinx: create QAPI domain extension stub John Snow
2025-03-05 3:45 ` [PATCH 04/57] docs/sphinx: add compat.py module and nested_parse helper John Snow
2025-03-07 5:46 ` Markus Armbruster
2025-03-07 20:08 ` John Snow
2025-03-05 3:45 ` [PATCH 05/57] docs/qapi-domain: add qapi:module directive John Snow
2025-03-05 3:45 ` [PATCH 06/57] docs/qapi-domain: add QAPI domain object registry John Snow
2025-03-05 3:45 ` [PATCH 07/57] docs/qapi-domain: add QAPI index John Snow
2025-03-05 3:45 ` [PATCH 08/57] docs/qapi-domain: add resolve_any_xref() John Snow
2025-03-05 3:45 ` [PATCH 09/57] docs/qapi-domain: add QAPI xref roles John Snow
2025-03-07 10:08 ` Markus Armbruster
2025-03-07 21:23 ` John Snow
2025-03-05 3:45 ` [PATCH 10/57] docs/qapi-domain: add compatibility node classes John Snow
2025-03-05 3:45 ` [PATCH 11/57] docs/qapi-domain: add qapi:command directive John Snow
2025-03-07 6:33 ` Markus Armbruster
2025-03-07 22:39 ` John Snow
2025-03-05 3:45 ` [PATCH 12/57] docs/qapi-domain: add :since: directive option John Snow
2025-03-07 6:59 ` Markus Armbruster
2025-03-07 22:43 ` John Snow
2025-03-05 3:45 ` [PATCH 13/57] docs/qapi-domain: add "Arguments:" field lists John Snow
2025-03-07 7:46 ` Markus Armbruster
2025-03-07 22:48 ` John Snow
2025-03-05 3:45 ` [PATCH 14/57] docs/qapi-domain: add "Features:" " John Snow
2025-03-05 3:45 ` [PATCH 15/57] docs/qapi-domain: add "Errors:" " John Snow
2025-03-07 7:48 ` Markus Armbruster
2025-03-07 22:50 ` John Snow
2025-03-08 6:49 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 16/57] docs/qapi-domain: add "Returns:" " John Snow
2025-03-07 7:58 ` Markus Armbruster
2025-03-07 22:58 ` John Snow
2025-03-08 6:57 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 17/57] docs/qapi-domain: add qapi:enum directive John Snow
2025-03-05 3:45 ` [PATCH 18/57] docs/qapi-domain: add qapi:alternate directive John Snow
2025-03-07 10:18 ` Markus Armbruster
2025-03-07 23:02 ` John Snow
2025-03-08 6:58 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 19/57] docs/qapi-domain: add qapi:event directive John Snow
2025-03-07 10:26 ` Markus Armbruster
2025-03-07 23:06 ` John Snow
2025-03-08 7:00 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 20/57] docs/qapi-domain: add qapi:object directive John Snow
2025-03-05 3:45 ` [PATCH 21/57] docs/qapi-domain: add :deprecated: directive option John Snow
2025-03-05 9:13 ` Markus Armbruster
2025-03-05 15:34 ` John Snow
2025-03-06 7:22 ` Markus Armbruster
2025-03-07 10:47 ` Markus Armbruster
2025-03-08 18:24 ` John Snow
2025-03-05 3:45 ` [PATCH 22/57] docs/qapi-domain: add :unstable: " John Snow
2025-03-05 3:45 ` [PATCH 23/57] docs/qapi-domain: add :ifcond: " John Snow
2025-03-07 10:57 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 24/57] docs/qapi-domain: add warnings for malformed field lists John Snow
2025-03-05 3:45 ` [PATCH 25/57] docs/qapi-domain: add type cross-refs to " John Snow
2025-03-05 3:45 ` [PATCH 26/57] docs/qapi-domain: add CSS styling John Snow
2025-03-05 3:45 ` John Snow [this message]
2025-03-05 3:45 ` [PATCH 28/57] docs/qapi-domain: warn when QAPI domain xrefs fail to resolve John Snow
2025-03-05 3:45 ` [PATCH 29/57] docs/qapi-domain: Fix error context reporting in Sphinx 5.x and 6.x John Snow
2025-03-05 3:45 ` [PATCH 30/57] qapi/parser: adjust info location for doc body section John Snow
2025-03-05 10:10 ` Markus Armbruster
2025-03-06 3:42 ` John Snow
2025-03-06 6:53 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 31/57] qapi: expand tags to all doc sections John Snow
2025-03-05 10:16 ` Markus Armbruster
2025-03-06 3:58 ` John Snow
2025-03-06 7:42 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 32/57] qapi/schema: add __repr__ to QAPIDoc.Section John Snow
2025-03-05 3:45 ` [PATCH 33/57] docs/qapidoc: add transmogrifier stub John Snow
2025-03-05 3:45 ` [PATCH 34/57] docs/qapidoc: split old implementation into qapidoc_legacy.py John Snow
2025-03-05 3:45 ` [PATCH 35/57] docs/qapidoc: Fix static typing on qapidoc.py John Snow
2025-03-07 11:59 ` Markus Armbruster
2025-03-08 18:32 ` John Snow
2025-03-09 5:37 ` Markus Armbruster
2025-03-09 6:46 ` John Snow
2025-03-05 3:45 ` [PATCH 36/57] do-not-merge John Snow
2025-03-05 3:45 ` [PATCH 37/57] docs/qapidoc: add transmogrifier class stub John Snow
2025-03-05 3:45 ` [PATCH 38/57] docs/qapidoc: add visit_module() method John Snow
2025-03-05 3:45 ` [PATCH 39/57] qapi/source: allow multi-line QAPISourceInfo advancing John Snow
2025-03-05 10:35 ` Markus Armbruster
2025-03-06 3:13 ` John Snow
2025-03-06 7:01 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 40/57] docs/qapidoc: add visit_freeform() method John Snow
2025-03-07 12:10 ` Markus Armbruster
2025-03-08 8:02 ` John Snow
2025-03-05 3:45 ` [PATCH 41/57] docs/qapidoc: add preamble() method John Snow
2025-03-07 12:13 ` Markus Armbruster
2025-03-08 8:03 ` John Snow
2025-03-05 3:45 ` [PATCH 42/57] docs/qapidoc: add visit_paragraph() method John Snow
2025-03-05 3:45 ` [PATCH 43/57] docs/qapidoc: add visit_errors() method John Snow
2025-03-07 12:14 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 44/57] docs/qapidoc: add format_type() method John Snow
2025-03-05 3:45 ` [PATCH 45/57] docs/qapidoc: add add_field() and generate_field() helper methods John Snow
2025-03-05 3:45 ` [PATCH 46/57] docs/qapidoc: add visit_feature() method John Snow
2025-03-07 12:16 ` Markus Armbruster
2025-03-05 3:45 ` [PATCH 47/57] docs/qapidoc: prepare to record entity being transmogrified John Snow
2025-03-05 3:45 ` [PATCH 48/57] docs/qapidoc: add visit_returns() method John Snow
2025-03-07 12:18 ` Markus Armbruster
2025-03-08 8:06 ` John Snow
2025-03-05 3:45 ` [PATCH 49/57] docs/qapidoc: add visit_member() method John Snow
2025-03-07 12:24 ` Markus Armbruster
2025-03-08 8:07 ` John Snow
2025-03-05 3:45 ` [PATCH 50/57] docs/qapidoc: add visit_sections() method John Snow
2025-03-07 12:25 ` Markus Armbruster
2025-03-08 8:10 ` John Snow
2025-03-08 9:20 ` Markus Armbruster
2025-03-05 3:46 ` [PATCH 51/57] docs/qapidoc: add visit_entity() John Snow
2025-03-07 12:26 ` Markus Armbruster
2025-03-08 8:12 ` John Snow
2025-03-05 3:46 ` [PATCH 52/57] docs/qapidoc: implement transmogrify() method John Snow
2025-03-05 3:46 ` [PATCH 53/57] docs: disambiguate cross-references John Snow
2025-03-05 3:46 ` [PATCH 54/57] docs/qapidoc: add transmogrifier test document John Snow
2025-03-09 7:19 ` Markus Armbruster
2025-03-05 3:46 ` [PATCH 55/57] docs/qapidoc: process @foo into ``foo`` John Snow
2025-03-05 3:46 ` [PATCH 56/57] docs/qapidoc: add intermediate output debugger John Snow
2025-03-07 12:34 ` Markus Armbruster
2025-03-08 8:13 ` John Snow
2025-03-08 9:21 ` Markus Armbruster
2025-03-05 3:46 ` [PATCH 57/57] docs/qapidoc: Add "the members of" pointers John Snow
2025-03-07 12:37 ` Markus Armbruster
2025-03-08 8:18 ` John Snow
2025-03-05 11:31 ` [PATCH 00/57] docs: Add new QAPI transmogrifier Markus Armbruster
2025-03-05 15:40 ` John Snow
2025-03-06 7:19 ` Markus Armbruster
2025-03-06 10:58 ` Markus Armbruster
2025-03-06 12:35 ` Markus Armbruster
2025-03-06 13:27 ` John Snow
2025-03-07 18:19 ` Markus Armbruster
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250305034610.960147-28-jsnow@redhat.com \
--to=jsnow@redhat.com \
--cc=alex.bennee@linaro.org \
--cc=armbru@redhat.com \
--cc=berrange@redhat.com \
--cc=michael.roth@amd.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@linaro.org \
--cc=qemu-devel@nongnu.org \
--cc=thuth@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is 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).