Fix tests for Docutils 1.0 pre-releases

This commit is contained in:
Adam Turner committed 2026-10-19 17:05:16 -07:00
1 parent 378a332a8f
commit d19cbb5083
14 files changed
+163 -26

No files matched your search

+4 -2
View File
@@ -58,6 +58,7 @@ from inspect import Parameter
from types import ModuleType
from typing import TYPE_CHECKING, cast
import docutils
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst.states import RSTStateMachine, state_classes
@@ -398,8 +399,9 @@ class Autosummary(SphinxDirective):
table.append(real_table)
group = nodes.tgroup('', cols=2)
real_table.append(group)
group.append(nodes.colspec('', colwidth=10))
group.append(nodes.colspec('', colwidth=90))
for width in (10, 90):
colwidth = str(width) if docutils.__version_info__[:2] >= (1, 0) else width
group.append(nodes.colspec('', colwidth=colwidth))
body = nodes.tbody('')
group.append(body)
+2 -1
View File
@@ -24,6 +24,7 @@ from sphinx.builders import Builder
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import _is_doctest_block
from sphinx.util.osutil import relpath
if TYPE_CHECKING:
@@ -661,4 +662,4 @@ def _condition_default(node: Node) -> bool:
def _condition_with_doctest(node: Node) -> bool:
return _condition_default(node) or isinstance(node, nodes.doctest_block)
return _condition_default(node) or _is_doctest_block(node)
+5 -2
View File
@@ -5,9 +5,10 @@ from __future__ import annotations
import warnings
from typing import TYPE_CHECKING
import docutils
from docutils.io import FileInput
from docutils.readers import standalone
from docutils.transforms.references import DanglingReferences
from docutils.transforms import references
from docutils.writers import UnfilteredWriter
from sphinx.deprecation import RemovedInSphinx10Warning
@@ -52,7 +53,9 @@ class SphinxBaseReader(standalone.Reader): # type: ignore[type-arg]
transforms = super().get_transforms() + self.transforms
# remove transforms which is not needed for Sphinx
unused = [DanglingReferences]
unused: list[type[Transform]] = [references.DanglingReferences]
if docutils.__version_info__[:2] >= (1, 0):
unused.append(references.ReportUnreferencedTargets) # type: ignore[attr-defined]
for transform in unused:
if transform in transforms:
transforms.remove(transform)
+15 -3
View File
@@ -6,6 +6,7 @@ import re
import unicodedata
from typing import TYPE_CHECKING, cast
import docutils
from docutils import nodes
from docutils.transforms import Transform, Transformer
from docutils.transforms.parts import ContentsFilter
@@ -19,7 +20,11 @@ from sphinx.deprecation import _deprecation_warning
from sphinx.locale import _, __
from sphinx.util import logging
from sphinx.util.i18n import format_date
from sphinx.util.nodes import apply_source_workaround, is_smartquotable
from sphinx.util.nodes import (
_is_doctest_block,
apply_source_workaround,
is_smartquotable,
)
if TYPE_CHECKING:
from collections.abc import Iterable, Iterator
@@ -183,7 +188,7 @@ class HandleCodeBlocks(SphinxTransform):
def apply(self, **kwargs: Any) -> None:
# move doctest blocks out of blockquotes
for node in self.document.findall(nodes.block_quote):
if all(isinstance(child, nodes.doctest_block) for child in node.children):
if all(_is_doctest_block(child) for child in node.children):
node.replace_self(node.children)
# combine successive doctest blocks
# for node in self.document.findall(nodes.doctest_block):
@@ -278,6 +283,8 @@ class ExtraTranslatableNodes(SphinxTransform):
return
def is_translatable_node(node: Node) -> TypeIs[nodes.Element]:
if _is_doctest_block(node):
return 'doctest-block' in targets
return isinstance(node, target_nodes)
for node in self.document.findall(is_translatable_node):
@@ -325,13 +332,18 @@ class UnreferencedFootnotesDetector(SphinxTransform):
class DoctestTransform(SphinxTransform):
"""Set "doctest" style to each doctest_block node"""
"""Set the style and highlighting language of doctest blocks."""
default_priority = 500
def apply(self, **kwargs: Any) -> None:
for node in self.document.findall(nodes.doctest_block):
node['classes'].append('doctest')
if docutils.__version_info__[:2] >= (1, 0):
# Docutils 1.0 represents doctests as literal blocks.
for literal in self.document.findall(nodes.literal_block):
if _is_doctest_block(literal):
literal.setdefault('language', 'pycon')
class FilterSystemMessages(SphinxTransform):
+5 -1
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import docutils
from docutils.transforms.references import DanglingReferences
from sphinx.transforms import SphinxTransform
@@ -42,7 +43,10 @@ class SphinxDomains(SphinxTransform):
def setup(app: Sphinx) -> ExtensionMetadata:
app.add_transform(SphinxDanglingReferences)
# Docutils 1.0 splits reference matching and reporting into separate
# transforms, which are already provided by the standalone reader.
if docutils.__version_info__[:2] < (1, 0):
app.add_transform(SphinxDanglingReferences)
app.add_transform(SphinxDomains)
return {
+13 -6
View File
@@ -18,7 +18,7 @@ from docutils.io import FileOutput
from docutils.parsers.rst import Directive, directives, roles
from docutils.readers import standalone
from docutils.statemachine import StateMachine
from docutils.transforms.references import DanglingReferences
from docutils.transforms import references
from docutils.utils import Reporter, unescape
from sphinx.errors import SphinxError
@@ -77,11 +77,18 @@ if TYPE_CHECKING:
) -> tuple[RoleFunction | None, list[system_message]]: ...
_READER_TRANSFORMS = [
transform
for transform in standalone.Reader().get_transforms()
if transform is not DanglingReferences
]
if docutils.__version_info__[:2] >= (1, 0):
_READER_TRANSFORMS = [
transform
for transform in standalone.Reader().get_transforms()
if transform is not references.ReportUnreferencedTargets # type: ignore[attr-defined]
]
else:
_READER_TRANSFORMS = [
transform
for transform in standalone.Reader().get_transforms()
if transform is not references.DanglingReferences
]
additional_nodes: set[type[Element]] = set()
+10
View File
@@ -8,6 +8,7 @@ import unicodedata
from io import StringIO
from typing import TYPE_CHECKING, Any, cast
import docutils
from docutils import nodes
from docutils.nodes import Node
@@ -287,6 +288,15 @@ IMAGE_TYPE_NODES = (
) # fmt: skip
def _is_doctest_block(node: Node) -> bool:
if docutils.__version_info__[:2] >= (1, 0) and isinstance(
node, nodes.literal_block
):
return 'doctest' in node['classes']
# Also accept doctest_block nodes from third-party parsers on Docutils 1.0.
return isinstance(node, nodes.doctest_block)
def _clean_extracted_message(text: str) -> str:
"""Remove trailing backslashes from each line of *text*."""
if '\\' in text:
+10
View File
@@ -7,6 +7,7 @@ import re
import urllib.parse
from typing import TYPE_CHECKING
import docutils
from docutils import nodes
from docutils.writers.html5_polyglot import HTMLTranslator as BaseTranslator
@@ -672,6 +673,15 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def depart_block_quote(self, node: Element) -> None:
self.body.append('</div></blockquote>\n')
if docutils.__version_info__[:2] >= (1, 0):
def visit_attribution(self, node: Element) -> None:
# Keep attributions inside our blockquote wrapper. Docutils 1.0's
# visitor closes the blockquote before emitting the attribution.
prefix, suffix = self.attribution_formats[self.settings.attribution]
self.context.append(suffix)
self.body.append(self.starttag(node, 'p', prefix, CLASS='attribution'))
# overwritten
def visit_literal(self, node: Element) -> None:
if 'kbd' in node['classes']:
+7 -1
View File
@@ -11,6 +11,7 @@ from collections import defaultdict
from pathlib import Path
from typing import TYPE_CHECKING, cast
import docutils
from docutils import nodes, writers
from roman_numerals import RomanNumeral
@@ -1249,7 +1250,12 @@ class LaTeXTranslator(SphinxTranslator):
assert self.table is not None
self.table.colcount += 1
if 'colwidth' in node:
self.table.colwidths.append(node['colwidth'])
if docutils.__version_info__[:2] >= (1, 0):
# breaking change in Docutils 1.0
# https://sourceforge.net/p/docutils/code/10397
self.table.colwidths.append(int(node['colwidth']))
else:
self.table.colwidths.append(node['colwidth'])
if 'stub' in node:
self.table.stubs.append(self.table.colcount - 1)
+7 -1
View File
@@ -7,6 +7,7 @@ import re
import textwrap
from typing import TYPE_CHECKING, cast
import docutils
from docutils import nodes, writers
from sphinx import __display_version__, addnodes
@@ -1041,7 +1042,12 @@ class TexinfoTranslator(SphinxTranslator):
pass
def visit_colspec(self, node: Element) -> None:
self.colwidths.append(node['colwidth'])
if docutils.__version_info__[:2] >= (1, 0):
# breaking change in Docutils 1.0
# https://sourceforge.net/p/docutils/code/10397
self.colwidths.append(int(node['colwidth']))
else:
self.colwidths.append(node['colwidth'])
if len(self.colwidths) != self.n_cols:
return
self.body.append('\n\n@multitable ')
+7 -1
View File
@@ -9,6 +9,7 @@ import textwrap
from itertools import chain, groupby, pairwise
from typing import TYPE_CHECKING, cast
import docutils
from docutils import nodes, writers
from docutils.utils import column_width
@@ -904,7 +905,12 @@ class TextTranslator(SphinxTranslator):
raise nodes.SkipNode
def visit_colspec(self, node: Element) -> None:
self.table.colwidth.append(node['colwidth'])
if docutils.__version_info__[:2] >= (1, 0):
# breaking change in Docutils 1.0
# https://sourceforge.net/p/docutils/code/10397
self.table.colwidth.append(int(node['colwidth']))
else:
self.table.colwidth.append(node['colwidth'])
raise nodes.SkipNode
def visit_tgroup(self, node: Element) -> None:
@@ -5,11 +5,17 @@ from __future__ import annotations
import re
from typing import TYPE_CHECKING
import docutils
import pytest
from docutils import nodes
from tests.test_builders.xpath_util import check_xpath
if docutils.__version_info__[:2] >= (1, 0):
_RFC_1_URL = 'https://datatracker.ietf.org/doc/html/rfc1/'
else:
_RFC_1_URL = 'https://datatracker.ietf.org/doc/html/rfc1.html'
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
from pathlib import Path
@@ -147,14 +153,12 @@ def tail_check(check: str) -> Callable[[Iterable[Element]], Literal[True]]:
),
(
'markup.html',
".//a[@href='https://datatracker.ietf.org/doc/html/rfc1.html']"
"[@class='rfc reference external']/strong",
f".//a[@href='{_RFC_1_URL}'][@class='rfc reference external']/strong",
'RFC 1',
),
(
'markup.html',
".//a[@href='https://datatracker.ietf.org/doc/html/rfc1.html']"
"[@class='rfc reference external']/strong",
f".//a[@href='{_RFC_1_URL}'][@class='rfc reference external']/strong",
'Request for Comments #1',
),
(
+20 -4
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
import re
from types import SimpleNamespace
import docutils
import pytest
from docutils import nodes, utils
from docutils.parsers.rst import Parser as RstParser
@@ -23,6 +24,11 @@ from sphinx.writers.latex import LaTeXTranslator, LaTeXWriter
from tests.utils import extract_node
if docutils.__version_info__[:2] >= (1, 0):
_RFC_2324_URL = 'https://datatracker.ietf.org/doc/html/rfc2324/'
else:
_RFC_2324_URL = 'https://datatracker.ietf.org/doc/html/rfc2324.html'
TYPE_CHECKING = False
if TYPE_CHECKING:
from sphinx.environment import BuildEnvironment
@@ -212,12 +218,12 @@ def rst_to_latex(rst: str, *, app: SphinxTestApp) -> str:
':rfc:`2324`',
(
'<p><span class="target" id="index-0"></span><a class="rfc reference external" '
'href="https://datatracker.ietf.org/doc/html/rfc2324.html"><strong>RFC 2324</strong></a></p>'
f'href="{_RFC_2324_URL}"><strong>RFC 2324</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{RFC@\\spxentry{RFC}!RFC 2324@\\spxentry{RFC 2324}}'
'\\sphinxhref{https://datatracker.ietf.org/doc/html/rfc2324.html}'
rf'\sphinxhref{{{_RFC_2324_URL}}}'
'{\\sphinxstylestrong{RFC 2324}}'
),
),
@@ -226,13 +232,13 @@ def rst_to_latex(rst: str, *, app: SphinxTestApp) -> str:
':rfc:`2324#section-1`',
(
'<p><span class="target" id="index-0"></span><a class="rfc reference external" '
'href="https://datatracker.ietf.org/doc/html/rfc2324.html#section-1">'
f'href="{_RFC_2324_URL}#section-1">'
'<strong>RFC 2324 Section 1</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{RFC@\\spxentry{RFC}!RFC 2324 Section 1@\\spxentry{RFC 2324 Section 1}}'
'\\sphinxhref{https://datatracker.ietf.org/doc/html/rfc2324.html\\#section-1}'
rf'\sphinxhref{{{_RFC_2324_URL}\#section-1}}'
'{\\sphinxstylestrong{RFC 2324 Section 1}}'
),
),
@@ -462,6 +468,16 @@ def test_inline(
assert latex_expected == latex_translated, f'from {rst!r}'
@pytest.mark.sphinx('html', testroot='_blank')
def test_block_quote_attribution(app: SphinxTestApp) -> None:
html = rst_to_html(' A quotation.\n\n -- The author\n', app=app)
assert html == (
'<blockquote>\n<div><p>A quotation.</p>\n'
'<p class="attribution">—The author</p>\n'
'</div></blockquote>'
)
@pytest.mark.parametrize(
('rst', 'html_expected', 'latex_expected'),
[
@@ -0,0 +1,50 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import docutils
import pytest
from docutils import nodes
from sphinx.testing import restructuredtext
if TYPE_CHECKING:
from sphinx.testing.util import SphinxTestApp
@pytest.mark.sphinx('dummy', testroot='_blank')
def test_indented_doctest(app: SphinxTestApp) -> None:
doctree = restructuredtext.parse(app, ' >>> 1 + 1\n 2\n')
assert len(doctree) == 1
node = doctree[0]
if docutils.__version_info__[:2] >= (1, 0):
assert isinstance(node, nodes.literal_block)
assert node['language'] == 'pycon'
else:
assert isinstance(node, nodes.doctest_block)
assert node.astext() == '>>> 1 + 1\n2'
assert 'doctest' in node['classes']
@pytest.mark.parametrize(
('target', 'translate_doctest', 'translate_literal'),
[('doctest-block', True, False), ('literal-block', False, True)],
)
@pytest.mark.sphinx('dummy', testroot='_blank')
def test_doctest_translation(
app: SphinxTestApp,
target: str,
translate_doctest: bool,
translate_literal: bool,
) -> None:
app.config.gettext_additional_targets = [target]
doctree = restructuredtext.parse(
app, '>>> 1 + 1\n2\n\n::\n\n an ordinary literal block\n'
)
doctest, literal = doctree.children
assert isinstance(doctest, nodes.Element)
assert doctest.get('translatable', False) is translate_doctest
assert isinstance(literal, nodes.Element)
assert literal.get('translatable', False) is translate_literal