This makes docs/sphinx/compat.py unnecessary. The accepted range of the sphinx_rtd_theme version was also changed to >=1.2 for compatibility with sphinx>=6.2.
Signed-off-by: Akihiko Odaki <akihiko.od...@daynix.com> --- docs/sphinx/compat.py | 230 --------------------------------------------- docs/sphinx/qapi_domain.py | 33 +++---- pythondeps.toml | 4 +- 3 files changed, 17 insertions(+), 250 deletions(-) diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py deleted file mode 100644 index 9cf7fe006e44..000000000000 --- a/docs/sphinx/compat.py +++ /dev/null @@ -1,230 +0,0 @@ -""" -Sphinx cross-version compatibility goop -""" - -import re -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Optional, - Type, -) - -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 -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] -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: - """ - 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 - util.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): - 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 - - -# ################################################################ -# 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 c94af5719ca0..29e006c9cf0f 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -21,14 +21,10 @@ from docutils import nodes from docutils.parsers.rst import directives -from compat import ( - CompatField, - CompatGroupedField, - CompatTypedField, - KeywordNode, - ParserFix, - Signature, - SpaceNode, +from sphinx.util.docfields import ( + Field, + GroupedField, + TypedField, ) from sphinx import addnodes from sphinx.directives import ObjectDescription @@ -64,6 +60,7 @@ from sphinx.util.typing import OptionSpec +Signature = str logger = logging.getLogger(__name__) @@ -158,7 +155,7 @@ def result_nodes( return results, [] -class QAPIDescription(ParserFix): +class QAPIDescription(ObjectDescription[Signature]): """ Generic QAPI description. @@ -312,7 +309,7 @@ class QAPIObject(QAPIDescription): doc_field_types = [ # :feat name: descr - CompatGroupedField( + GroupedField( "feature", label=_("Features"), names=("feat",), @@ -324,8 +321,8 @@ def get_signature_prefix(self) -> List[nodes.Node]: """Return a prefix to put before the object name in the signature.""" assert self.objtype return [ - KeywordNode("", self.objtype.title()), - SpaceNode(" "), + addnodes.desc_sig_keyword("", self.objtype.title()), + addnodes.desc_sig_space(" "), ] def get_signature_suffix(self) -> List[nodes.Node]: @@ -334,7 +331,7 @@ def get_signature_suffix(self) -> List[nodes.Node]: if "since" in self.options: ret += [ - SpaceNode(" "), + addnodes.desc_sig_space(" "), addnodes.desc_sig_element( "", f"(Since: {self.options['since']})" ), @@ -482,7 +479,7 @@ def transform_content(self, content_node: addnodes.desc_content) -> None: self._validate_field(field) -class SpecialTypedField(CompatTypedField): +class SpecialTypedField(TypedField): def make_field(self, *args: Any, **kwargs: Any) -> nodes.field: ret = super().make_field(*args, **kwargs) @@ -515,14 +512,14 @@ class QAPICommand(QAPIObject): can_collapse=False, ), # :error: descr - CompatField( + Field( "error", label=_("Errors"), names=("error", "errors"), has_arg=False, ), # :return TypeName: descr - CompatGroupedField( + GroupedField( "returnvalue", label=_("Return"), rolename="type", @@ -540,7 +537,7 @@ class QAPIEnum(QAPIObject): doc_field_types.extend( [ # :value name: descr - CompatGroupedField( + GroupedField( "value", label=_("Values"), names=("value",), @@ -557,7 +554,7 @@ class QAPIAlternate(QAPIObject): doc_field_types.extend( [ # :alt type name: descr - CompatTypedField( + TypedField( "alternative", label=_("Alternatives"), names=("alt",), diff --git a/pythondeps.toml b/pythondeps.toml index 4f6b6f01e0d0..0d8f5ae24a24 100644 --- a/pythondeps.toml +++ b/pythondeps.toml @@ -24,8 +24,8 @@ pycotap = { accepted = ">=1.1.0", installed = "1.3.1" } [docs] # Please keep the installed versions in sync with docs/requirements.txt -sphinx = { accepted = ">=3.4.3", installed = "6.2.1", canary = "sphinx-build" } -sphinx_rtd_theme = { accepted = ">=0.5", installed = "1.2.2" } +sphinx = { accepted = ">=6.2", installed = "6.2.1", canary = "sphinx-build" } +sphinx_rtd_theme = { accepted = ">=1.2", installed = "1.2.2" } [avocado] # Note that qemu.git/python/ is always implicitly installed. -- 2.49.0