Skip to content

Commit

Permalink
Increase minimum Docutils to 0.17
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner committed Sep 27, 2022
1 parent fa9ba26 commit c621b71
Show file tree
Hide file tree
Showing 15 changed files with 93 additions and 210 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Expand Up @@ -14,9 +14,9 @@ jobs:
matrix:
include:
- python: "3.8"
docutils: du16
- python: "3.9"
docutils: du17
- python: "3.9"
docutils: du18
- python: "3.10"
docutils: du18
- python: "3.10"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Expand Up @@ -62,7 +62,7 @@ dependencies = [
"sphinxcontrib-qthelp",
"Jinja2>=3.0",
"Pygments>=2.12",
"docutils>=0.14,<0.20",
"docutils>=0.17,<0.20",
"snowballstemmer>=2.0",
"babel>=2.9",
"alabaster>=0.7,<0.8",
Expand Down
17 changes: 3 additions & 14 deletions sphinx/addnodes.py
Expand Up @@ -12,7 +12,7 @@
try:
from docutils.nodes import meta as docutils_meta # type: ignore
except ImportError:
# docutils-0.17 or older
# docutils-0.17
from docutils.parsers.rst.directives.html import MetaBody
docutils_meta = MetaBody.meta

Expand All @@ -29,18 +29,7 @@ class document(nodes.document):

def set_id(self, node: Element, msgnode: Optional[Element] = None,
suggested_prefix: str = '') -> str:
if docutils.__version_info__ >= (0, 16):
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
return super().set_id(node, msgnode, suggested_prefix) # type: ignore


class translatable(nodes.Node):
Expand Down Expand Up @@ -198,7 +187,7 @@ class desc_inline(_desc_classes_injector, nodes.Inline, nodes.TextElement):
classes = ['sig', 'sig-inline']

def __init__(self, domain: str, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
super().__init__(*args, **kwargs, domain=domain)
self['classes'].append(domain)


Expand Down
41 changes: 20 additions & 21 deletions sphinx/directives/patches.py
Expand Up @@ -19,11 +19,29 @@
from sphinx.util.typing import OptionSpec

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:
# docutils-0.17 or older
# docutils-0.17
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:
from sphinx.application import Sphinx

Expand Down Expand Up @@ -57,25 +75,6 @@ def run(self) -> List[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):
"""The csv-table directive which searches a CSV file from Sphinx project's source
directory when an absolute path is given via :file: option.
Expand Down
13 changes: 4 additions & 9 deletions sphinx/transforms/__init__.py
Expand Up @@ -4,7 +4,6 @@
import unicodedata
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, cast

import docutils
from docutils import nodes
from docutils.nodes import Element, Node, Text
from docutils.transforms import Transform, Transformer
Expand Down Expand Up @@ -342,16 +341,12 @@ def get_tokens(self, txtnodes: List[Text]) -> Generator[Tuple[str, str], None, N
# of "Text" nodes (interface to ``smartquotes.educate_tokens()``).
for txtnode in txtnodes:
if is_smartquotable(txtnode):
if docutils.__version_info__ >= (0, 16):
# SmartQuotes uses backslash escapes instead of null-escapes
text = re.sub(r'(?<=\x00)([-\\\'".`])', r'\\\1', str(txtnode))
else:
text = txtnode.astext()

yield ('plain', text)
# SmartQuotes uses backslash escapes instead of null-escapes
text = re.sub(r'(?<=\x00)([-\\\'".`])', r'\\\1', str(txtnode))
yield 'plain', text
else:
# skip smart quotes
yield ('literal', txtnode.astext())
yield 'literal', txtnode.astext()


class DoctreeReadEvent(SphinxTransform):
Expand Down
2 changes: 1 addition & 1 deletion sphinx/transforms/i18n.py
Expand Up @@ -255,7 +255,7 @@ def apply(self, **kwargs: Any) -> None:

# update meta nodes
if isinstance(node, nodes.pending) and is_pending_meta(node):
# docutils-0.17 or older
# docutils-0.17
node.details['nodes'][0]['content'] = msgstr
continue
elif isinstance(node, addnodes.docutils_meta):
Expand Down
2 changes: 1 addition & 1 deletion sphinx/util/docutils.py
Expand Up @@ -591,7 +591,7 @@ def unknown_visit(self, node: Node) -> None:
# 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()
# 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):
return iter(self.traverse(*args, **kwargs))

Expand Down
22 changes: 3 additions & 19 deletions sphinx/util/nodes.py
Expand Up @@ -226,7 +226,7 @@ def is_translatable(node: Node) -> bool:
return True

if is_pending_meta(node) or isinstance(node, addnodes.meta):
# docutils-0.17 or older
# docutils-0.17
return True
elif isinstance(node, addnodes.docutils_meta):
# docutils-0.18+
Expand Down Expand Up @@ -268,10 +268,10 @@ def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]:
else:
msg = ''
elif isinstance(node, META_TYPE_NODES):
# docutils-0.17 or older
# docutils-0.17
msg = node.rawcontent
elif isinstance(node, nodes.pending) and is_pending_meta(node):
# docutils-0.17 or older
# docutils-0.17
msg = node.details['nodes'][0].rawcontent
elif isinstance(node, addnodes.docutils_meta):
# docutils-0.18+
Expand Down Expand Up @@ -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
# something, even if it's just a comment and will lose the id anyway...
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
6 changes: 1 addition & 5 deletions tests/test_api_translator.py
Expand Up @@ -2,7 +2,6 @@

import sys

import docutils
import pytest


Expand All @@ -19,10 +18,7 @@ def test_html_translator(app, status, warning):
# no set_translator()
translator_class = app.builder.get_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'


@pytest.mark.sphinx('html', testroot='api-set-translator')
Expand Down
18 changes: 5 additions & 13 deletions tests/test_build_html.py
Expand Up @@ -15,10 +15,7 @@
from sphinx.util import md5
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"


ENV_WARNINGS = """\
Expand Down Expand Up @@ -442,7 +439,7 @@ def test_docutils17_output(app, cached_etree_parse, 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({
'index.html': [
(".//div[@class='citation']/span", r'Ref1'),
Expand Down Expand Up @@ -1324,14 +1321,9 @@ def test_html_inventory(app):
def test_html_anchor_for_figure(app):
app.builder.build_all()
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>'
'<a class="headerlink" href="#id1" title="Permalink to this image">¶</a></p>\n</figcaption>'
in content)
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>'
in content)


@pytest.mark.sphinx('html', testroot='directives-raw')
Expand Down
82 changes: 26 additions & 56 deletions tests/test_ext_graphviz.py
Expand Up @@ -2,7 +2,6 @@

import re

import docutils
import pytest

from sphinx.ext.graphviz import ClickableMapDefinition
Expand All @@ -14,15 +13,10 @@ def test_graphviz_png_html(app, status, warning):
app.builder.build_all()

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*'
r'<div class="graphviz"><img .*?/></div>\s*<figcaption>\s*'
r'<p><span class="caption-text">caption of graph</span>.*</p>\s*'
r'</figcaption>\s*</figure>')
html = (r'<figure class="align-default" .*?>\s*'
r'<div class="graphviz"><img .*?/></div>\s*<figcaption>\s*'
r'<p><span class="caption-text">caption of graph</span>.*</p>\s*'
r'</figcaption>\s*</figure>')
assert re.search(html, content, re.S)

html = 'Hello <div class="graphviz"><img .*?/></div>\n graphviz world'
Expand All @@ -32,15 +26,10 @@ def test_graphviz_png_html(app, status, warning):
'class="graphviz neato-graph" />')
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*'
r'<div class="graphviz"><img .*?/></div>\s*<figcaption>\s*'
r'<p><span class="caption-text">on <em>right</em></span>.*</p>\s*'
r'</figcaption>\s*</figure>')
html = (r'<figure class="align-right" .*?>\s*'
r'<div class="graphviz"><img .*?/></div>\s*<figcaption>\s*'
r'<p><span class="caption-text">on <em>right</em></span>.*</p>\s*'
r'</figcaption>\s*</figure>')
assert re.search(html, content, re.S)

html = (r'<div align=\"center\" class=\"align-center\">'
Expand All @@ -58,50 +47,31 @@ def test_graphviz_svg_html(app, status, warning):

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'
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'<figcaption>\n'
r'<p><span class=\"caption-text\">caption of graph</span>.*</p>\n'
r'</figcaption>\n'
r'</figure>')
html = (r'<figure class=\"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'<figcaption>\n'
r'<p><span class=\"caption-text\">caption of graph</span>.*</p>\n'
r'</figcaption>\n'
r'</figure>')
assert re.search(html, content, re.S)

html = (r'Hello <div class="graphviz"><object.*>\n'
r'\s*<p class=\"warning\">graph</p></object></div>\n'
r' graphviz world')
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'
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'<figcaption>\n'
r'<p><span class=\"caption-text\">on <em>right</em></span>.*</p>\n'
r'</figcaption>\n'
r'</figure>')
html = (r'<figure class=\"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'<figcaption>\n'
r'<p><span class=\"caption-text\">on <em>right</em></span>.*</p>\n'
r'</figcaption>\n'
r'</figure>')
assert re.search(html, content, re.S)

html = (r'<div align=\"center\" class=\"align-center\">'
Expand Down

0 comments on commit c621b71

Please sign in to comment.