Skip to content

Commit

Permalink
Increase minimum Docutils to 0.18
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner committed Jun 16, 2022
1 parent f57a844 commit b6fe58d
Show file tree
Hide file tree
Showing 13 changed files with 25 additions and 144 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Expand Up @@ -11,7 +11,7 @@ jobs:
matrix:
include:
- python: "3.8"
docutils: du17
docutils: du18
- python: "3.9"
docutils: du18
- python: "3.10"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -20,7 +20,7 @@
'sphinxcontrib-qthelp',
'Jinja2>=2.3',
'Pygments>=2.8.0',
'docutils>=0.17,<0.19',
'docutils>=0.18,<0.19',
'snowballstemmer>=1.1',
'babel>=1.3',
'alabaster>=0.7,<0.8',
Expand Down
29 changes: 12 additions & 17 deletions sphinx/addnodes.py
Expand Up @@ -2,19 +2,24 @@

from typing import TYPE_CHECKING, Any, Dict, List, Sequence

import docutils
from docutils import nodes
from docutils.nodes import Element

from sphinx.deprecation import RemovedInSphinx70Warning, deprecated_alias

if TYPE_CHECKING:
from sphinx.application import Sphinx

try:
from docutils.nodes import meta as docutils_meta # type: ignore
except ImportError:
# docutils-0.17
from docutils.parsers.rst.directives.html import MetaBody
docutils_meta = MetaBody.meta
deprecated_alias('sphinx.addnodes',
{
'meta': nodes.meta, # type: ignore
'docutils_meta': nodes.meta, # type: ignore
},
RemovedInSphinx70Warning,
{
'meta': 'docutils.nodes.meta',
'docutils_meta': 'docutils.nodes.meta',
})


class document(nodes.document):
Expand Down Expand Up @@ -424,13 +429,6 @@ class tabular_col_spec(nodes.Element):
"""Node for specifying tabular columns, used for LaTeX output."""


class meta(nodes.Special, nodes.PreBibliographic, nodes.Element):
"""Node for meta directive -- same as docutils' standard meta node,
but pickleable.
"""
rawcontent = None


# inline nodes

class pending_xref(nodes.Inline, nodes.Element):
Expand Down Expand Up @@ -558,9 +556,6 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_node(literal_strong)
app.add_node(manpage)

if docutils.__version_info__ < (0, 18):
app.add_node(meta)

return {
'version': 'builtin',
'parallel_read_safe': True,
Expand Down
28 changes: 2 additions & 26 deletions sphinx/directives/patches.py
@@ -1,14 +1,14 @@
import os
from os import path
from typing import TYPE_CHECKING, Any, Dict, List, Sequence, cast
from typing import TYPE_CHECKING, Any, Dict, List, cast

from docutils import nodes
from docutils.nodes import Node, make_id
from docutils.parsers.rst import directives
from docutils.parsers.rst.directives import images, tables
from docutils.parsers.rst.directives.misc import Meta as Meta # type: ignore
from docutils.parsers.rst.roles import set_classes

from sphinx import addnodes
from sphinx.directives import optional_int
from sphinx.domains.math import MathDomain
from sphinx.locale import __
Expand All @@ -18,30 +18,6 @@
from sphinx.util.osutil import SEP, os_path, relpath
from sphinx.util.typing import OptionSpec

try:
from docutils.parsers.rst.directives.misc import Meta as Meta # type: ignore
except ImportError:
# 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
6 changes: 1 addition & 5 deletions sphinx/environment/__init__.py
Expand Up @@ -9,9 +9,7 @@
from typing import (TYPE_CHECKING, Any, Callable, Dict, Generator, Iterator, List, Optional,
Set, Tuple, Union)

import docutils
from docutils import nodes
from docutils.nodes import Node

from sphinx import addnodes
from sphinx.config import Config
Expand Down Expand Up @@ -52,8 +50,6 @@
'file_insertion_enabled': True,
'smartquotes_locales': [],
}
if docutils.__version_info__[:2] <= (0, 17):
default_settings['embed_images'] = False

# This is increased every time an environment attribute is added
# or changed to properly invalidate pickle files.
Expand Down Expand Up @@ -537,7 +533,7 @@ def get_and_resolve_doctree(self, docname: str, builder: "Builder",

def resolve_toctree(self, docname: str, builder: "Builder", toctree: addnodes.toctree,
prune: bool = True, maxdepth: int = 0, titles_only: bool = False,
collapse: bool = False, includehidden: bool = False) -> Node:
collapse: bool = False, includehidden: bool = False) -> nodes.Node:
"""Resolve a *toctree* node into individual bullet lists with titles
as items, returning None (if no containing titles are found) or
a new node.
Expand Down
4 changes: 2 additions & 2 deletions sphinx/search/__init__.py
Expand Up @@ -187,8 +187,8 @@ def __init__(self, document: nodes.document, lang: SearchLanguage) -> None:
self.lang = lang

def is_meta_keywords(self, node: Element) -> bool:
if (isinstance(node, (addnodes.meta, addnodes.docutils_meta)) and
node.get('name') == 'keywords'):
if (isinstance(node, nodes.meta) # type: ignore
and node.get('name') == 'keywords'):
meta_lang = node.get('lang')
if meta_lang is None: # lang not specified
return True
Expand Down
9 changes: 2 additions & 7 deletions sphinx/transforms/i18n.py
Expand Up @@ -19,7 +19,7 @@
from sphinx.util import get_filetype, logging, split_index_msg
from sphinx.util.i18n import docname_to_domain
from sphinx.util.nodes import (IMAGE_TYPE_NODES, LITERAL_TYPE_NODES, NodeMatcher,
extract_messages, is_pending_meta, traverse_translatable_index)
extract_messages, traverse_translatable_index)

if TYPE_CHECKING:
from sphinx.application import Sphinx
Expand Down Expand Up @@ -253,12 +253,7 @@ def apply(self, **kwargs: Any) -> None:
continue

# update meta nodes
if isinstance(node, nodes.pending) and is_pending_meta(node):
# docutils-0.17
node.details['nodes'][0]['content'] = msgstr
continue
elif isinstance(node, addnodes.docutils_meta):
# docutils-0.18+
if isinstance(node, nodes.meta): # type: ignore
node['content'] = msgstr
continue

Expand Down
10 changes: 0 additions & 10 deletions sphinx/util/docutils.py
Expand Up @@ -549,16 +549,6 @@ def unknown_visit(self, node: Node) -> None:
logger.warning(__('unknown node type: %r'), node, location=node)


# Node.findall() is a new interface to traverse a doctree since docutils-0.18.
# This applies a patch docutils-0.17 to be available Node.findall()
# method to use it from our codebase.
if docutils.__version_info__[:2] <= (0, 17):
def findall(self, *args, **kwargs):
return iter(self.traverse(*args, **kwargs))

Node.findall = findall # type: ignore


# cache a vanilla instance of nodes.document
# Used in new_document() function
__document_cache__: Optional[nodes.document] = None
Expand Down
26 changes: 2 additions & 24 deletions sphinx/util/nodes.py
Expand Up @@ -182,14 +182,6 @@ def apply_source_workaround(node: Element) -> None:
)


def is_pending_meta(node: Node) -> bool:
if (isinstance(node, nodes.pending) and
isinstance(node.details.get('nodes', [None])[0], addnodes.meta)):
return True
else:
return False


def is_translatable(node: Node) -> bool:
if isinstance(node, addnodes.translatable):
return True
Expand Down Expand Up @@ -225,11 +217,7 @@ def is_translatable(node: Node) -> bool:
return False
return True

if is_pending_meta(node) or isinstance(node, addnodes.meta):
# docutils-0.17
return True
elif isinstance(node, addnodes.docutils_meta):
# docutils-0.18+
if isinstance(node, nodes.meta): # type: ignore
return True

return False
Expand All @@ -244,9 +232,6 @@ def is_translatable(node: Node) -> bool:
IMAGE_TYPE_NODES = (
nodes.image,
)
META_TYPE_NODES = (
addnodes.meta,
)


def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]:
Expand All @@ -267,14 +252,7 @@ def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]:
msg = '.. image:: %s' % node['uri']
else:
msg = ''
elif isinstance(node, META_TYPE_NODES):
# docutils-0.17
msg = node.rawcontent
elif isinstance(node, nodes.pending) and is_pending_meta(node):
# docutils-0.17
msg = node.details['nodes'][0].rawcontent
elif isinstance(node, addnodes.docutils_meta):
# docutils-0.18+
elif isinstance(node, nodes.meta): # type: ignore
msg = node["content"]
else:
msg = node.rawsource.replace('\n', ' ').strip()
Expand Down
36 changes: 1 addition & 35 deletions tests/test_build_html.py
Expand Up @@ -5,7 +5,6 @@
from itertools import chain, cycle
from unittest.mock import ANY, call, patch

import docutils
import pygments
import pytest
from html5lib import HTMLParser
Expand Down Expand Up @@ -402,39 +401,6 @@ def test_html5_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.17 or below is required.')
@pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [
(".//dt[@class='label']/span[@class='brackets']", r'Ref1'),
(".//dt[@class='label']", ''),
],
'footnote.html': [
(".//a[@class='footnote-reference brackets'][@href='#id9'][@id='id1']", r"1"),
(".//a[@class='footnote-reference brackets'][@href='#id10'][@id='id2']", r"2"),
(".//a[@class='footnote-reference brackets'][@href='#foo'][@id='id3']", r"3"),
(".//a[@class='reference internal'][@href='#bar'][@id='id4']/span", r"\[bar\]"),
(".//a[@class='reference internal'][@href='#baz-qux'][@id='id5']/span", r"\[baz_qux\]"),
(".//a[@class='footnote-reference brackets'][@href='#id11'][@id='id6']", r"4"),
(".//a[@class='footnote-reference brackets'][@href='#id12'][@id='id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id1']", r"1"),
(".//a[@class='fn-backref'][@href='#id2']", r"2"),
(".//a[@class='fn-backref'][@href='#id3']", r"3"),
(".//a[@class='fn-backref'][@href='#id4']", r"bar"),
(".//a[@class='fn-backref'][@href='#id5']", r"baz_qux"),
(".//a[@class='fn-backref'][@href='#id6']", r"4"),
(".//a[@class='fn-backref'][@href='#id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id8']", r"6"),
],
}))
@pytest.mark.sphinx('html')
@pytest.mark.test_params(shared_result='test_build_html_output_docutils17')
def test_docutils17_output(app, cached_etree_parse, fname, expect):
app.build()
print(app.outdir / fname)
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)


@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 All @@ -460,7 +426,7 @@ def test_docutils17_output(app, cached_etree_parse, fname, expect):
}))
@pytest.mark.sphinx('html')
@pytest.mark.test_params(shared_result='test_build_html_output_docutils18')
def test_docutils18_output(app, cached_etree_parse, fname, expect):
def test_docutils_output(app, cached_etree_parse, fname, expect):
app.build()
print(app.outdir / fname)
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
Expand Down
6 changes: 1 addition & 5 deletions tests/test_smartquotes.py
@@ -1,6 +1,5 @@
"""Test smart quotes."""

import docutils
import pytest
from html5lib import HTMLParser

Expand Down Expand Up @@ -44,10 +43,7 @@ def test_man_builder(app, status, warning):
app.build()

content = (app.outdir / 'python.1').read_text(encoding='utf8')
if docutils.__version_info__ > (0, 18):
assert r'\-\- \(dqSphinx\(dq is a tool that makes it easy ...' in content
else:
assert r'\-\- "Sphinx" is a tool that makes it easy ...' in content
assert r'\-\- \(dqSphinx\(dq is a tool that makes it easy ...' in content


@pytest.mark.sphinx(buildername='latex', testroot='smartquotes', freshenv=True)
Expand Down
10 changes: 0 additions & 10 deletions tests/test_versioning.py
Expand Up @@ -4,17 +4,9 @@

import pytest

from sphinx import addnodes
from sphinx.testing.util import SphinxTestApp
from sphinx.versioning import add_uids, get_ratio, merge_doctrees

try:
from docutils.nodes import meta
except ImportError:
# docutils-0.18.0 or older
from docutils.parsers.rst.directives.html import MetaBody
meta = MetaBody.meta

app = original = original_uids = None


Expand Down Expand Up @@ -62,8 +54,6 @@ def test_picklablility():
copy.settings.warning_stream = None
copy.settings.env = None
copy.settings.record_dependencies = None
for metanode in copy.findall(meta):
metanode.__class__ = addnodes.meta
loaded = pickle.loads(pickle.dumps(copy, pickle.HIGHEST_PROTOCOL))
assert all(getattr(n, 'uid', False) for n in loaded.findall(is_paragraph))

Expand Down
1 change: 0 additions & 1 deletion tox.ini
Expand Up @@ -18,7 +18,6 @@ description =
py{36,37,38,39,310}: Run unit tests against {envname}.
du{17,18,19}: Run unit tests with the given version of docutils.
deps =
du17: docutils==0.17.*
du18: docutils==0.18.*
du19: docutils==0.19.*
extras =
Expand Down

0 comments on commit b6fe58d

Please sign in to comment.