Increase minimum Docutils to 0.17

This commit is contained in:
Adam Turner
2022-09-27 18:31:47 +01:00
parent 4660b62de0
commit 080517ce55
16 changed files with 95 additions and 211 deletions
+2 -2
View File
@@ -14,9 +14,9 @@ jobs:
matrix: matrix:
include: include:
- python: "3.8" - python: "3.8"
docutils: du16
- python: "3.9"
docutils: du17 docutils: du17
- python: "3.9"
docutils: du18
- python: "3.10" - python: "3.10"
docutils: du18 docutils: du18
- python: "3.10" - python: "3.10"
+2 -1
View File
@@ -5,7 +5,8 @@ Dependencies
------------ ------------
* #10468: Drop Python 3.6 support * #10468: Drop Python 3.6 support
* #10470: Drop Python 3.7 support. Patch by Adam Turner * #10470: Drop Python 3.7, Docutils 0.14, Docutils 0.15, and Docutils 0.16
support. Patch by Adam Turner
Incompatible changes Incompatible changes
-------------------- --------------------
+1 -1
View File
@@ -63,7 +63,7 @@ dependencies = [
"sphinxcontrib-qthelp", "sphinxcontrib-qthelp",
"Jinja2>=3.0", "Jinja2>=3.0",
"Pygments>=2.12", "Pygments>=2.12",
"docutils>=0.14,<0.20", "docutils>=0.17,<0.20",
"snowballstemmer>=2.0", "snowballstemmer>=2.0",
"babel>=2.9", "babel>=2.9",
"alabaster>=0.7,<0.8", "alabaster>=0.7,<0.8",
+3 -14
View File
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
try: try:
from docutils.nodes import meta as docutils_meta # type: ignore from docutils.nodes import meta as docutils_meta # type: ignore
except ImportError: except ImportError:
# docutils-0.17 or older # docutils-0.17
from docutils.parsers.rst.directives.html import MetaBody from docutils.parsers.rst.directives.html import MetaBody
docutils_meta = MetaBody.meta docutils_meta = MetaBody.meta
@@ -29,18 +29,7 @@ class document(nodes.document):
def set_id(self, node: Element, msgnode: Optional[Element] = None, def set_id(self, node: Element, msgnode: Optional[Element] = None,
suggested_prefix: str = '') -> str: suggested_prefix: str = '') -> str:
if docutils.__version_info__ >= (0, 16): return super().set_id(node, msgnode, suggested_prefix) # type: ignore
ret = super().set_id(node, msgnode, suggested_prefix) # type: ignore
else:
ret = super().set_id(node, msgnode)
if docutils.__version_info__ < (0, 17):
# register other node IDs forcedly
for node_id in node['ids']:
if node_id not in self.ids:
self.ids[node_id] = node
return ret
class translatable(nodes.Node): class translatable(nodes.Node):
@@ -198,7 +187,7 @@ class desc_inline(_desc_classes_injector, nodes.Inline, nodes.TextElement):
classes = ['sig', 'sig-inline'] classes = ['sig', 'sig-inline']
def __init__(self, domain: str, *args: Any, **kwargs: Any) -> None: def __init__(self, domain: str, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs, domain=domain)
self['classes'].append(domain) self['classes'].append(domain)
+20 -21
View File
@@ -19,11 +19,29 @@ from sphinx.util.osutil import SEP, os_path, relpath
from sphinx.util.typing import OptionSpec from sphinx.util.typing import OptionSpec
try: try:
from docutils.parsers.rst.directives.misc import Meta as MetaBase # type: ignore from docutils.parsers.rst.directives.misc import Meta as Meta # type: ignore
except ImportError: except ImportError:
# docutils-0.17 or older # docutils-0.17
from docutils.parsers.rst.directives.html import Meta as MetaBase from docutils.parsers.rst.directives.html import Meta as MetaBase
class Meta(MetaBase, SphinxDirective): # type: ignore
def run(self) -> Sequence[Node]: # type: ignore
result = super().run()
for node in result:
# for docutils-0.17. Since docutils-0.18, patching is no longer needed
# because it uses picklable node; ``docutils.nodes.meta``.
if (isinstance(node, nodes.pending) and
isinstance(node.details['nodes'][0], addnodes.docutils_meta)):
meta = node.details['nodes'][0]
meta.source = self.env.doc2path(self.env.docname)
meta.line = self.lineno
meta.rawcontent = meta['content']
# docutils' meta nodes aren't picklable because the class is nested
meta.__class__ = addnodes.meta
return result
if TYPE_CHECKING: if TYPE_CHECKING:
from sphinx.application import Sphinx from sphinx.application import Sphinx
@@ -57,25 +75,6 @@ class Figure(images.Figure):
return [figure_node] return [figure_node]
class Meta(MetaBase, SphinxDirective):
def run(self) -> Sequence[Node]:
result = super().run()
for node in result:
# for docutils-0.17 or older. Since docutils-0.18, patching is no longer needed
# because it uses picklable node; ``docutils.nodes.meta``.
if (isinstance(node, nodes.pending) and
isinstance(node.details['nodes'][0], addnodes.docutils_meta)):
meta = node.details['nodes'][0]
meta.source = self.env.doc2path(self.env.docname)
meta.line = self.lineno
meta.rawcontent = meta['content']
# docutils' meta nodes aren't picklable because the class is nested
meta.__class__ = addnodes.meta
return result
class CSVTable(tables.CSVTable): class CSVTable(tables.CSVTable):
"""The csv-table directive which searches a CSV file from Sphinx project's source """The csv-table directive which searches a CSV file from Sphinx project's source
directory when an absolute path is given via :file: option. directory when an absolute path is given via :file: option.
+2 -7
View File
@@ -4,7 +4,6 @@ import re
import unicodedata import unicodedata
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, cast from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, cast
import docutils
from docutils import nodes from docutils import nodes
from docutils.nodes import Element, Node, Text from docutils.nodes import Element, Node, Text
from docutils.transforms import Transform, Transformer from docutils.transforms import Transform, Transformer
@@ -342,16 +341,12 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform):
# of "Text" nodes (interface to ``smartquotes.educate_tokens()``). # of "Text" nodes (interface to ``smartquotes.educate_tokens()``).
for txtnode in txtnodes: for txtnode in txtnodes:
if is_smartquotable(txtnode): if is_smartquotable(txtnode):
if docutils.__version_info__ >= (0, 16):
# SmartQuotes uses backslash escapes instead of null-escapes # SmartQuotes uses backslash escapes instead of null-escapes
text = re.sub(r'(?<=\x00)([-\\\'".`])', r'\\\1', str(txtnode)) text = re.sub(r'(?<=\x00)([-\\\'".`])', r'\\\1', str(txtnode))
else: yield 'plain', text
text = txtnode.astext()
yield ('plain', text)
else: else:
# skip smart quotes # skip smart quotes
yield ('literal', txtnode.astext()) yield 'literal', txtnode.astext()
class DoctreeReadEvent(SphinxTransform): class DoctreeReadEvent(SphinxTransform):
+1 -1
View File
@@ -255,7 +255,7 @@ class Locale(SphinxTransform):
# update meta nodes # update meta nodes
if isinstance(node, nodes.pending) and is_pending_meta(node): if isinstance(node, nodes.pending) and is_pending_meta(node):
# docutils-0.17 or older # docutils-0.17
node.details['nodes'][0]['content'] = msgstr node.details['nodes'][0]['content'] = msgstr
continue continue
elif isinstance(node, addnodes.docutils_meta): elif isinstance(node, addnodes.docutils_meta):
+1 -1
View File
@@ -591,7 +591,7 @@ class SphinxTranslator(nodes.NodeVisitor):
# Node.findall() is a new interface to traverse a doctree since docutils-0.18. # Node.findall() is a new interface to traverse a doctree since docutils-0.18.
# This applies a patch to docutils up to 0.18 inclusive to provide Node.findall() # This applies a patch to docutils up to 0.18 inclusive to provide Node.findall()
# method to use it from our codebase. # method to use it from our codebase.
if docutils.__version_info__ <= (0, 18): if docutils.__version_info__[:2] <= (0, 17):
def findall(self, *args, **kwargs): def findall(self, *args, **kwargs):
return iter(self.traverse(*args, **kwargs)) return iter(self.traverse(*args, **kwargs))
+3 -19
View File
@@ -226,7 +226,7 @@ def is_translatable(node: Node) -> bool:
return True return True
if is_pending_meta(node) or isinstance(node, addnodes.meta): if is_pending_meta(node) or isinstance(node, addnodes.meta):
# docutils-0.17 or older # docutils-0.17
return True return True
elif isinstance(node, addnodes.docutils_meta): elif isinstance(node, addnodes.docutils_meta):
# docutils-0.18+ # docutils-0.18+
@@ -268,10 +268,10 @@ def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]:
else: else:
msg = '' msg = ''
elif isinstance(node, META_TYPE_NODES): elif isinstance(node, META_TYPE_NODES):
# docutils-0.17 or older # docutils-0.17
msg = node.rawcontent msg = node.rawcontent
elif isinstance(node, nodes.pending) and is_pending_meta(node): elif isinstance(node, nodes.pending) and is_pending_meta(node):
# docutils-0.17 or older # docutils-0.17
msg = node.details['nodes'][0].rawcontent msg = node.details['nodes'][0].rawcontent
elif isinstance(node, addnodes.docutils_meta): elif isinstance(node, addnodes.docutils_meta):
# docutils-0.18+ # docutils-0.18+
@@ -625,19 +625,3 @@ def process_only_nodes(document: Node, tags: "Tags") -> None:
# the only node, so we make sure docutils can transfer the id to # the only node, so we make sure docutils can transfer the id to
# something, even if it's just a comment and will lose the id anyway... # something, even if it's just a comment and will lose the id anyway...
node.replace_self(nodes.comment()) node.replace_self(nodes.comment())
def _new_copy(self: Element) -> Element:
"""monkey-patch Element.copy to copy the rawsource and line
for docutils-0.16 or older versions.
refs: https://sourceforge.net/p/docutils/patches/165/
"""
newnode = self.__class__(self.rawsource, **self.attributes)
if isinstance(self, nodes.Element):
newnode.source = self.source
newnode.line = self.line
return newnode
nodes.Element.copy = _new_copy # type: ignore
-4
View File
@@ -2,7 +2,6 @@
import sys import sys
import docutils
import pytest import pytest
@@ -19,9 +18,6 @@ def test_html_translator(app, status, warning):
# no set_translator() # no set_translator()
translator_class = app.builder.get_translator_class() translator_class = app.builder.get_translator_class()
assert translator_class assert translator_class
if docutils.__version_info__ < (0, 13):
assert translator_class.__name__ == 'HTMLTranslator'
else:
assert translator_class.__name__ == 'HTML5Translator' assert translator_class.__name__ == 'HTML5Translator'
+1 -9
View File
@@ -15,9 +15,6 @@ from sphinx.testing.util import strip_escseq
from sphinx.util import md5 from sphinx.util import md5
from sphinx.util.inventory import InventoryFile from sphinx.util.inventory import InventoryFile
if docutils.__version_info__ < (0, 17):
FIGURE_CAPTION = ".//div[@class='figure align-default']/p[@class='caption']"
else:
FIGURE_CAPTION = ".//figure/figcaption/p" FIGURE_CAPTION = ".//figure/figcaption/p"
@@ -442,7 +439,7 @@ def test_docutils17_output(app, cached_etree_parse, fname, expect):
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
@pytest.mark.skipif(docutils.__version_info__ < (0, 18), reason='docutils-0.18+ is required.') @pytest.mark.skipif(docutils.__version_info__[:2] <= (0, 17), reason='docutils-0.18+ is required.')
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [ 'index.html': [
(".//div[@class='citation']/span", r'Ref1'), (".//div[@class='citation']/span", r'Ref1'),
@@ -1324,11 +1321,6 @@ def test_html_inventory(app):
def test_html_anchor_for_figure(app): def test_html_anchor_for_figure(app):
app.builder.build_all() app.builder.build_all()
content = (app.outdir / 'index.html').read_text(encoding='utf8') content = (app.outdir / 'index.html').read_text(encoding='utf8')
if docutils.__version_info__ < (0, 17):
assert ('<p class="caption"><span class="caption-text">The caption of pic</span>'
'<a class="headerlink" href="#id1" title="Permalink to this image">¶</a></p>'
in content)
else:
assert ('<figcaption>\n<p><span class="caption-text">The caption of pic</span>' assert ('<figcaption>\n<p><span class="caption-text">The caption of pic</span>'
'<a class="headerlink" href="#id1" title="Permalink to this image">¶</a></p>\n</figcaption>' '<a class="headerlink" href="#id1" title="Permalink to this image">¶</a></p>\n</figcaption>'
in content) in content)
-30
View File
@@ -2,7 +2,6 @@
import re import re
import docutils
import pytest import pytest
from sphinx.ext.graphviz import ClickableMapDefinition from sphinx.ext.graphviz import ClickableMapDefinition
@@ -14,11 +13,6 @@ def test_graphviz_png_html(app, status, warning):
app.builder.build_all() app.builder.build_all()
content = (app.outdir / 'index.html').read_text(encoding='utf8') content = (app.outdir / 'index.html').read_text(encoding='utf8')
if docutils.__version_info__ < (0, 17):
html = (r'<div class="figure align-default" .*?>\s*'
r'<div class="graphviz"><img .*?/></div>\s*<p class="caption">'
r'<span class="caption-text">caption of graph</span>.*</p>\s*</div>')
else:
html = (r'<figure class="align-default" .*?>\s*' html = (r'<figure class="align-default" .*?>\s*'
r'<div class="graphviz"><img .*?/></div>\s*<figcaption>\s*' r'<div class="graphviz"><img .*?/></div>\s*<figcaption>\s*'
r'<p><span class="caption-text">caption of graph</span>.*</p>\s*' r'<p><span class="caption-text">caption of graph</span>.*</p>\s*'
@@ -32,11 +26,6 @@ def test_graphviz_png_html(app, status, warning):
'class="graphviz neato-graph" />') 'class="graphviz neato-graph" />')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
if docutils.__version_info__ < (0, 17):
html = (r'<div class="figure align-right" .*?>\s*'
r'<div class="graphviz"><img .*?/></div>\s*<p class="caption">'
r'<span class="caption-text">on <em>right</em></span>.*</p>\s*</div>')
else:
html = (r'<figure class="align-right" .*?>\s*' html = (r'<figure class="align-right" .*?>\s*'
r'<div class="graphviz"><img .*?/></div>\s*<figcaption>\s*' r'<div class="graphviz"><img .*?/></div>\s*<figcaption>\s*'
r'<p><span class="caption-text">on <em>right</em></span>.*</p>\s*' r'<p><span class="caption-text">on <em>right</em></span>.*</p>\s*'
@@ -58,15 +47,6 @@ def test_graphviz_svg_html(app, status, warning):
content = (app.outdir / 'index.html').read_text(encoding='utf8') content = (app.outdir / 'index.html').read_text(encoding='utf8')
if docutils.__version_info__ < (0, 17):
html = (r'<div class=\"figure align-default\" .*?>\n'
r'<div class="graphviz"><object data=\".*\.svg\".*>\n'
r'\s*<p class=\"warning\">digraph foo {\n'
r'bar -&gt; baz\n'
r'}</p></object></div>\n'
r'<p class=\"caption\"><span class=\"caption-text\">'
r'caption of graph</span>.*</p>\n</div>')
else:
html = (r'<figure class=\"align-default\" .*?>\n' html = (r'<figure class=\"align-default\" .*?>\n'
r'<div class="graphviz"><object data=\".*\.svg\".*>\n' r'<div class="graphviz"><object data=\".*\.svg\".*>\n'
r'\s*<p class=\"warning\">digraph foo {\n' r'\s*<p class=\"warning\">digraph foo {\n'
@@ -83,16 +63,6 @@ def test_graphviz_svg_html(app, status, warning):
r' graphviz world') r' graphviz world')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
if docutils.__version_info__ < (0, 17):
html = (r'<div class=\"figure align-right\" .*\>\n'
r'<div class="graphviz"><object data=\".*\.svg\".*>\n'
r'\s*<p class=\"warning\">digraph bar {\n'
r'foo -&gt; bar\n'
r'}</p></object></div>\n'
r'<p class=\"caption\"><span class=\"caption-text\">'
r'on <em>right</em></span>.*</p>\n'
r'</div>')
else:
html = (r'<figure class=\"align-right\" .*\>\n' html = (r'<figure class=\"align-right\" .*\>\n'
r'<div class="graphviz"><object data=\".*\.svg\".*>\n' r'<div class="graphviz"><object data=\".*\.svg\".*>\n'
r'\s*<p class=\"warning\">digraph bar {\n' r'\s*<p class=\"warning\">digraph bar {\n'
-27
View File
@@ -4,7 +4,6 @@ import os
import re import re
import sys import sys
import docutils
import pytest import pytest
from sphinx.ext.inheritance_diagram import (InheritanceDiagram, InheritanceException, from sphinx.ext.inheritance_diagram import (InheritanceDiagram, InheritanceException,
@@ -140,14 +139,6 @@ def test_inheritance_diagram_png_html(app, status, warning):
content = (app.outdir / 'index.html').read_text(encoding='utf8') content = (app.outdir / 'index.html').read_text(encoding='utf8')
if docutils.__version_info__ < (0, 17):
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
'class="inheritance graphviz" /></div>\n<p class="caption">'
'<span class="caption-text">Test Foo!</span><a class="headerlink" href="#id1" '
'title="Permalink to this image">\xb6</a></p>\n</div>\n')
else:
pattern = ('<figure class="align-default" id="id1">\n' pattern = ('<figure class="align-default" id="id1">\n'
'<div class="graphviz">' '<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" ' '<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
@@ -165,16 +156,6 @@ def test_inheritance_diagram_svg_html(app, status, warning):
content = (app.outdir / 'index.html').read_text(encoding='utf8') content = (app.outdir / 'index.html').read_text(encoding='utf8')
if docutils.__version_info__ < (0, 17):
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
'<object data="_images/inheritance-\\w+.svg" '
'type="image/svg\\+xml" class="inheritance graphviz">\n'
'<p class=\"warning\">Inheritance diagram of test.Foo</p>'
'</object></div>\n<p class="caption"><span class="caption-text">'
'Test Foo!</span><a class="headerlink" href="#id1" '
'title="Permalink to this image">\xb6</a></p>\n</div>\n')
else:
pattern = ('<figure class="align-default" id="id1">\n' pattern = ('<figure class="align-default" id="id1">\n'
'<div class="graphviz">' '<div class="graphviz">'
'<object data="_images/inheritance-\\w+.svg" ' '<object data="_images/inheritance-\\w+.svg" '
@@ -216,14 +197,6 @@ def test_inheritance_diagram_latex_alias(app, status, warning):
content = (app.outdir / 'index.html').read_text(encoding='utf8') content = (app.outdir / 'index.html').read_text(encoding='utf8')
if docutils.__version_info__ < (0, 17):
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
'class="inheritance graphviz" /></div>\n<p class="caption">'
'<span class="caption-text">Test Foo!</span><a class="headerlink" href="#id1" '
'title="Permalink to this image">\xb6</a></p>\n</div>\n')
else:
pattern = ('<figure class="align-default" id="id1">\n' pattern = ('<figure class="align-default" id="id1">\n'
'<div class="graphviz">' '<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" ' '<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
-9
View File
@@ -6,7 +6,6 @@ Runs the text builder in the test root.
import os import os
import re import re
import docutils
import pytest import pytest
from babel.messages import mofile, pofile from babel.messages import mofile, pofile
from babel.messages.catalog import Catalog from babel.messages.catalog import Catalog
@@ -1121,10 +1120,6 @@ def test_additional_targets_should_not_be_translated(app):
result = (app.outdir / 'raw.html').read_text(encoding='utf8') result = (app.outdir / 'raw.html').read_text(encoding='utf8')
# raw block should not be translated # raw block should not be translated
if docutils.__version_info__ < (0, 17):
expected_expr = """<iframe src="http://sphinx-doc.org"></iframe></div>"""
assert_count(expected_expr, result, 1)
else:
expected_expr = """<iframe src="http://sphinx-doc.org"></iframe></section>""" expected_expr = """<iframe src="http://sphinx-doc.org"></iframe></section>"""
assert_count(expected_expr, result, 1) assert_count(expected_expr, result, 1)
@@ -1203,10 +1198,6 @@ def test_additional_targets_should_be_translated(app):
result = (app.outdir / 'raw.html').read_text(encoding='utf8') result = (app.outdir / 'raw.html').read_text(encoding='utf8')
# raw block should be translated # raw block should be translated
if docutils.__version_info__ < (0, 17):
expected_expr = """<iframe src="HTTP://SPHINX-DOC.ORG"></iframe></div>"""
assert_count(expected_expr, result, 1)
else:
expected_expr = """<iframe src="HTTP://SPHINX-DOC.ORG"></iframe></section>""" expected_expr = """<iframe src="HTTP://SPHINX-DOC.ORG"></iframe></section>"""
assert_count(expected_expr, result, 1) assert_count(expected_expr, result, 1)
-3
View File
@@ -3,7 +3,6 @@
import re import re
import warnings import warnings
import docutils
import pytest import pytest
from docutils import frontend, nodes, utils from docutils import frontend, nodes, utils
from docutils.parsers.rst import Parser as RstParser from docutils.parsers.rst import Parser as RstParser
@@ -395,8 +394,6 @@ def test_inline(get_verifier, type, rst, html_expected, latex_expected):
None, None,
), ),
]) ])
@pytest.mark.skipif(docutils.__version_info__ < (0, 16),
reason='docutils-0.16 or above is required')
def test_inline_docutils16(get_verifier, type, rst, html_expected, latex_expected): def test_inline_docutils16(get_verifier, type, rst, html_expected, latex_expected):
verifier = get_verifier(type) verifier = get_verifier(type)
verifier(rst, html_expected, latex_expected) verifier(rst, html_expected, latex_expected)
+2 -5
View File
@@ -1,6 +1,6 @@
[tox] [tox]
minversion = 2.4.0 minversion = 2.4.0
envlist = docs,flake8,mypy,twine,py{38,39,310,311},du{14,15,16,17,18,19} envlist = docs,flake8,mypy,twine,py{38,39,310,311},du{17,18,19}
isolated_build = True isolated_build = True
[testenv] [testenv]
@@ -17,11 +17,8 @@ passenv =
TERM TERM
description = description =
py{38,39,310,311}: Run unit tests against {envname}. py{38,39,310,311}: Run unit tests against {envname}.
du{14,15,16,17,18,19}: Run unit tests with the given version of docutils. du{17,18,19}: Run unit tests with the given version of docutils.
deps = deps =
du14: docutils==0.14.*
du15: docutils==0.15.*
du16: docutils==0.16.*
du17: docutils==0.17.* du17: docutils==0.17.*
du18: docutils==0.18.* du18: docutils==0.18.*
du19: docutils==0.19.* du19: docutils==0.19.*