Skip to content

Commit c994609

Browse files
committed
⬆️ Allow docutils 0.22
1 parent a9e529f commit c994609

11 files changed

Lines changed: 124 additions & 24 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474
strategy:
7575
fail-fast: false
7676
matrix:
77-
docutils-version: ["0.20", "0.21"]
77+
docutils-version: ["0.20", "0.21", "0.22"]
7878

7979
steps:
8080
- name: Checkout source

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ keywords = [
3434
]
3535
requires-python = ">=3.11"
3636
dependencies = [
37-
"docutils>=0.20,<0.22",
37+
"docutils>=0.20,<0.23",
3838
"jinja2", # required for substitutions, but let sphinx choose version
3939
"markdown-it-py~=4.0",
4040
"mdit-py-plugins~=0.5",
@@ -146,6 +146,7 @@ disallow_any_generics = false
146146
[tool.pytest.ini_options]
147147
filterwarnings = [
148148
"ignore:.*The default for the setting.*:FutureWarning",
149+
"ignore:.*:PendingDeprecationWarning",
149150
]
150151

151152
[tool.coverage.run]

tests/conftest.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Shared pytest configuration for all tests."""
2+
3+
import re
4+
5+
from docutils import __version_info__ as docutils_version_info
6+
7+
# docutils 0.22+ serializes boolean attributes as "1"/"0" instead of "True"/"False"
8+
DOCUTILS_0_22_PLUS = docutils_version_info >= (0, 22)
9+
10+
11+
def normalize_doctree_xml(text: str) -> str:
12+
"""Normalize docutils XML output for cross-version compatibility.
13+
14+
In docutils 0.22+, boolean attributes are serialized as "1"/"0"
15+
instead of "True"/"False". This function normalizes to the new format
16+
for consistent test fixtures.
17+
"""
18+
if DOCUTILS_0_22_PLUS:
19+
# Normalize new format (True/False) to old format (1/0)
20+
# Only replace when it's clearly a boolean attribute value
21+
# Pattern: attribute="True" or attribute="False"
22+
attrs = [
23+
"force",
24+
"glob",
25+
"hidden",
26+
"id_link",
27+
"includehidden",
28+
"inline",
29+
"internal",
30+
"is_div",
31+
"linenos",
32+
"multi_line_parameter_list",
33+
"multi_line_trailing_comma",
34+
"no-contents-entry",
35+
"no-index",
36+
"no-index-entry",
37+
"no-typesetting",
38+
"no-wrap",
39+
"nocontentsentry",
40+
"noindex",
41+
"noindexentry",
42+
"nowrap",
43+
"refexplicit",
44+
"refspecific",
45+
"refwarn",
46+
"sorted",
47+
"titlesonly",
48+
"toctree",
49+
"translatable",
50+
]
51+
text = re.sub(rf' ({"|".join(attrs)})="1"', r' \1="True"', text)
52+
text = re.sub(rf' ({"|".join(attrs)})="0"', r' \1="False"', text)
53+
# numbered is changed in math_block, but not in toctree, so we have to be more precise
54+
text = re.sub(r' numbered="1" xml:space', r' numbered="True" xml:space', text)
55+
return text

tests/test_html/test_html_to_nodes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from myst_parser.config.main import MdParserConfig
88
from myst_parser.mdit_to_docutils.html_to_nodes import html_to_nodes
9+
from tests.conftest import normalize_doctree_xml
910

1011
FIXTURE_PATH = Path(__file__).parent
1112

@@ -32,4 +33,4 @@ def _run_directive(name: str, first_line: str, content: str, position: int):
3233
def test_html_to_nodes(file_params, mock_renderer):
3334
output = nodes.container()
3435
output += html_to_nodes(file_params.content, line_number=0, renderer=mock_renderer)
35-
file_params.assert_expected(output.pformat(), rstrip=True)
36+
file_params.assert_expected(normalize_doctree_xml(output.pformat()), rstrip=True)

tests/test_renderers/test_fixtures_docutils.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import Any
1212

1313
import pytest
14+
from conftest import normalize_doctree_xml
1415
from docutils.core import Publisher, publish_doctree
1516
from pytest_param_files import ParamTestData
1617

@@ -37,7 +38,9 @@ def _apply_transforms(self):
3738
)
3839

3940
# in docutils 0.18 footnote ids have changed
40-
outcome = doctree.pformat().replace('"footnote-reference-1"', '"id1"')
41+
outcome = normalize_doctree_xml(doctree.pformat()).replace(
42+
'"footnote-reference-1"', '"id1"'
43+
)
4144
outcome = outcome.replace(' language=""', "")
4245
file_params.assert_expected(outcome, rstrip_lines=True)
4346

@@ -54,7 +57,7 @@ def test_link_resolution(file_params: ParamTestData):
5457
parser=Parser(),
5558
settings_overrides=settings,
5659
)
57-
outcome = doctree.pformat()
60+
outcome = normalize_doctree_xml(doctree.pformat())
5861
if report_stream.getvalue().strip():
5962
outcome += "\n\n" + report_stream.getvalue().strip()
6063
file_params.assert_expected(outcome, rstrip_lines=True)
@@ -75,7 +78,9 @@ def _apply_transforms(self):
7578
parser=Parser(),
7679
)
7780

78-
file_params.assert_expected(doctree.pformat(), rstrip_lines=True)
81+
file_params.assert_expected(
82+
normalize_doctree_xml(doctree.pformat()), rstrip_lines=True
83+
)
7984

8085

8186
@pytest.mark.param_file(FIXTURE_PATH / "docutil_directives.md")
@@ -95,7 +100,9 @@ def _apply_transforms(self):
95100
parser=Parser(),
96101
)
97102

98-
file_params.assert_expected(doctree.pformat(), rstrip_lines=True)
103+
file_params.assert_expected(
104+
normalize_doctree_xml(doctree.pformat()), rstrip_lines=True
105+
)
99106

100107

101108
@pytest.mark.param_file(FIXTURE_PATH / "docutil_syntax_extensions.txt")
@@ -109,7 +116,9 @@ def test_syntax_extensions(file_params: ParamTestData):
109116
parser=Parser(),
110117
settings_overrides=settings,
111118
)
112-
file_params.assert_expected(doctree.pformat(), rstrip_lines=True)
119+
file_params.assert_expected(
120+
normalize_doctree_xml(doctree.pformat()), rstrip_lines=True
121+
)
113122

114123

115124
def settings_from_cmdline(cmdline: str | None) -> dict[str, Any]:

tests/test_renderers/test_fixtures_sphinx.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from sphinx_pytest.plugin import CreateDoctree
1818

1919
from myst_parser.mdit_to_docutils.sphinx_ import SphinxRenderer
20+
from tests.conftest import normalize_doctree_xml
2021

2122
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
2223

@@ -36,7 +37,7 @@ def _apply_transforms(self):
3637
monkeypatch.setattr(SphinxTransformer, "apply_transforms", _apply_transforms)
3738

3839
result = sphinx_doctree(file_params.content, "index.md")
39-
pformat = result.pformat("index")
40+
pformat = normalize_doctree_xml(result.pformat("index"))
4041
replacements = {
4142
# changed in docutils 0.20.1
4243
'<literal classes="code" language="">': '<literal classes="code">',
@@ -58,7 +59,7 @@ def test_link_resolution(file_params: ParamTestData, sphinx_doctree: CreateDoctr
5859
sphinx_doctree.srcdir.joinpath("test.txt").touch()
5960
sphinx_doctree.srcdir.joinpath("other.rst").write_text(":orphan:\n\nTest\n====")
6061
result = sphinx_doctree(file_params.content, "index.md")
61-
outcome = result.pformat("index")
62+
outcome = normalize_doctree_xml(result.pformat("index"))
6263
if result.warnings.strip():
6364
outcome += "\n\n" + result.warnings.strip()
6465
file_params.assert_expected(outcome, rstrip_lines=True)
@@ -80,7 +81,9 @@ def settings_from_json(string: str | None):
8081
def test_tables(file_params: ParamTestData, sphinx_doctree_no_tr: CreateDoctree):
8182
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
8283
result = sphinx_doctree_no_tr(file_params.content, "index.md")
83-
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
84+
file_params.assert_expected(
85+
normalize_doctree_xml(result.pformat("index")), rstrip_lines=True
86+
)
8487

8588

8689
@pytest.mark.param_file(FIXTURE_PATH / "directive_options.md")
@@ -89,7 +92,9 @@ def test_directive_options(
8992
):
9093
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
9194
result = sphinx_doctree_no_tr(file_params.content, "index.md")
92-
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
95+
file_params.assert_expected(
96+
normalize_doctree_xml(result.pformat("index")), rstrip_lines=True
97+
)
9398

9499

95100
@pytest.mark.param_file(FIXTURE_PATH / "sphinx_directives.md")
@@ -102,7 +107,9 @@ def test_sphinx_directives(
102107
pytest.skip(file_params.title)
103108

104109
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
105-
pformat = sphinx_doctree_no_tr(file_params.content, "index.md").pformat("index")
110+
pformat = normalize_doctree_xml(
111+
sphinx_doctree_no_tr(file_params.content, "index.md").pformat("index")
112+
)
106113
# see https://github.com/executablebooks/MyST-Parser/issues/522
107114
if sys.maxsize == 2147483647:
108115
pformat = pformat.replace('"2147483647"', '"9223372036854775807"')
@@ -115,7 +122,9 @@ def test_sphinx_roles(file_params: ParamTestData, sphinx_doctree_no_tr: CreateDo
115122
pytest.skip(file_params.title)
116123

117124
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
118-
pformat = sphinx_doctree_no_tr(file_params.content, "index.md").pformat("index")
125+
pformat = normalize_doctree_xml(
126+
sphinx_doctree_no_tr(file_params.content, "index.md").pformat("index")
127+
)
119128
# sphinx 3 adds a parent key
120129
pformat = re.sub('cpp:parent_key="[^"]*"', 'cpp:parent_key=""', pformat)
121130
# sphinx >= 4.5.0 adds a trailing slash to PEP URLs,
@@ -136,7 +145,9 @@ def test_dollarmath(file_params: ParamTestData, sphinx_doctree_no_tr: CreateDoct
136145
{"extensions": ["myst_parser"], "myst_enable_extensions": ["dollarmath"]}
137146
)
138147
result = sphinx_doctree_no_tr(file_params.content, "index.md")
139-
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
148+
file_params.assert_expected(
149+
normalize_doctree_xml(result.pformat("index")), rstrip_lines=True
150+
)
140151

141152

142153
@pytest.mark.param_file(FIXTURE_PATH / "amsmath.md")
@@ -148,7 +159,9 @@ def test_amsmath(
148159
{"extensions": ["myst_parser"], "myst_enable_extensions": ["amsmath"]}
149160
)
150161
result = sphinx_doctree_no_tr(file_params.content, "index.md")
151-
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
162+
file_params.assert_expected(
163+
normalize_doctree_xml(result.pformat("index")), rstrip_lines=True
164+
)
152165

153166

154167
@pytest.mark.param_file(FIXTURE_PATH / "containers.md")
@@ -160,7 +173,9 @@ def test_containers(
160173
{"extensions": ["myst_parser"], "myst_enable_extensions": ["colon_fence"]}
161174
)
162175
result = sphinx_doctree_no_tr(file_params.content, "index.md")
163-
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
176+
file_params.assert_expected(
177+
normalize_doctree_xml(result.pformat("index")), rstrip_lines=True
178+
)
164179

165180

166181
@pytest.mark.param_file(FIXTURE_PATH / "eval_rst.md")
@@ -169,7 +184,9 @@ def test_evalrst_elements(
169184
):
170185
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
171186
result = sphinx_doctree_no_tr(file_params.content, "index.md")
172-
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
187+
file_params.assert_expected(
188+
normalize_doctree_xml(result.pformat("index")), rstrip_lines=True
189+
)
173190

174191

175192
@pytest.mark.param_file(FIXTURE_PATH / "definition_lists.md")
@@ -180,7 +197,9 @@ def test_definition_lists(
180197
{"extensions": ["myst_parser"], "myst_enable_extensions": ["deflist"]}
181198
)
182199
result = sphinx_doctree_no_tr(file_params.content, "index.md")
183-
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
200+
file_params.assert_expected(
201+
normalize_doctree_xml(result.pformat("index")), rstrip_lines=True
202+
)
184203

185204

186205
@pytest.mark.param_file(FIXTURE_PATH / "attributes.md")
@@ -192,4 +211,6 @@ def test_attributes(file_params: ParamTestData, sphinx_doctree_no_tr: CreateDoct
192211
}
193212
)
194213
result = sphinx_doctree_no_tr(file_params.content, "index.md")
195-
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
214+
file_params.assert_expected(
215+
normalize_doctree_xml(result.pformat("index")), rstrip_lines=True
216+
)

tests/test_renderers/test_include_directive.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pathlib import Path
44

55
import pytest
6+
from conftest import normalize_doctree_xml
67
from docutils.core import publish_doctree
78

89
from myst_parser.parsers.docutils_ import Parser
@@ -24,7 +25,11 @@ def test_render(file_params, tmp_path, monkeypatch):
2425
)
2526

2627
doctree["source"] = "tmpdir/test.md"
27-
output = doctree.pformat().replace(str(tmp_path) + os.sep, "tmpdir/").rstrip()
28+
output = (
29+
normalize_doctree_xml(doctree.pformat())
30+
.replace(str(tmp_path) + os.sep, "tmpdir/")
31+
.rstrip()
32+
)
2833

2934
file_params.assert_expected(output, rstrip=True)
3035

tests/test_renderers/test_myst_config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pathlib import Path
66

77
import pytest
8+
from conftest import normalize_doctree_xml
89
from docutils.core import Publisher, publish_string
910
from pytest_param_files import ParamTestData
1011

@@ -36,6 +37,7 @@ def test_cmdline(file_params: ParamTestData):
3637
writer_name="pseudoxml",
3738
settings_overrides=settings,
3839
)
40+
output = normalize_doctree_xml(output)
3941
warnings = report_stream.getvalue()
4042
if warnings:
4143
output += "\n" + warnings

tests/test_renderers/test_myst_refs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
22

33
import pytest
4+
from conftest import normalize_doctree_xml
45
from sphinx.util.console import strip_colors
56
from sphinx_pytest.plugin import CreateDoctree
67

@@ -55,7 +56,7 @@ def test_parse(
5556

5657
doctree["source"] = "root/index.md"
5758
doctree.attributes.pop("translation_progress", None)
58-
outcome = doctree.pformat()
59+
outcome = normalize_doctree_xml(doctree.pformat())
5960
if result.warnings.strip():
6061
outcome += "\n\n" + strip_colors(result.warnings.strip())
6162
file_regression.check(outcome, basename=test_name, extension=".xml")

tests/test_sphinx/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def test_basic(app, status, warning, get_sphinx_app_output):
4141
from docutils import nodes
4242

4343
from myst_parser._compat import findall
44+
from tests.conftest import normalize_doctree_xml
4445

4546
SOURCE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "sourcedirs"))
4647

@@ -125,7 +126,7 @@ def read(
125126
node.attributes.pop("translated", None)
126127

127128
if regress:
128-
text = doctree.pformat() # type: str
129+
text = normalize_doctree_xml(doctree.pformat()) # type: str
129130
for find, rep in (replace or {}).items():
130131
text = text.replace(find, rep)
131132
if rstrip_lines:

0 commit comments

Comments
 (0)