Let docutils know the location of `docutils.conf` for Sphinx

This commit is contained in:
Takeshi KOMIYA 2018-03-29 22:45:20 +09:00
parent d80801deeb
commit 81946e423a
6 changed files with 63 additions and 26 deletions

View File

@ -23,6 +23,8 @@ Incompatible changes
:py:meth:`.Sphinx.add_transform()` :py:meth:`.Sphinx.add_transform()`
* #4827: All ``substitution_definition`` nodes are removed from doctree on * #4827: All ``substitution_definition`` nodes are removed from doctree on
reading phase reading phase
* ``docutils.conf`` on ``$HOME`` and ``/etc`` directories are ignored. Only
``docutils.conf`` on confdir is refered.
Deprecated Deprecated
---------- ----------

View File

@ -293,7 +293,8 @@ def build_main(argv=sys.argv[1:]): # type: ignore
app = None app = None
try: try:
with patch_docutils(), docutils_namespace(): confdir = args.confdir or args.sourcedir
with patch_docutils(confdir), docutils_namespace():
app = Sphinx(args.sourcedir, args.confdir, args.outputdir, app = Sphinx(args.sourcedir, args.confdir, args.outputdir,
args.doctreedir, args.builder, confoverrides, status, args.doctreedir, args.builder, confoverrides, status,
warning, args.freshenv, args.warningiserror, warning, args.freshenv, args.warningiserror,

View File

@ -19,7 +19,6 @@ from collections import defaultdict
from copy import copy from copy import copy
from os import path from os import path
from docutils.frontend import OptionParser
from docutils.utils import Reporter, get_source_line from docutils.utils import Reporter, get_source_line
from six import BytesIO, class_types, next from six import BytesIO, class_types, next
from six.moves import cPickle as pickle from six.moves import cPickle as pickle
@ -561,9 +560,8 @@ class BuildEnvironment(object):
"""Parse a file and add/update inventory entries for the doctree.""" """Parse a file and add/update inventory entries for the doctree."""
self.prepare_settings(docname) self.prepare_settings(docname)
docutilsconf = path.join(self.srcdir, 'docutils.conf') # Add confdir/docutils.conf to dependencies list if exists
# read docutils.conf from source dir, not from current dir docutilsconf = path.join(self.app.confdir, 'docutils.conf')
OptionParser.standard_config_files[1] = docutilsconf
if path.isfile(docutilsconf): if path.isfile(docutilsconf):
self.note_dependency(docutilsconf) self.note_dependency(docutilsconf)

View File

@ -179,7 +179,8 @@ class BuildDoc(Command):
app = None app = None
try: try:
with patch_docutils(), docutils_namespace(): confdir = self.config_dir or self.source_dir
with patch_docutils(confdir), docutils_namespace():
app = Sphinx(self.source_dir, self.config_dir, app = Sphinx(self.source_dir, self.config_dir,
builder_target_dir, self.doctree_dir, builder_target_dir, self.doctree_dir,
builder, confoverrides, status_stream, builder, confoverrides, status_stream,

View File

@ -10,16 +10,17 @@
""" """
from __future__ import absolute_import from __future__ import absolute_import
import os
import re import re
import types import types
import warnings import warnings
from contextlib import contextmanager from contextlib import contextmanager
from copy import copy from copy import copy
from distutils.version import LooseVersion from distutils.version import LooseVersion
from os import path
import docutils import docutils
from docutils import nodes from docutils import nodes
from docutils.languages import get_language
from docutils.parsers.rst import Directive, directives, roles, convert_directive_function from docutils.parsers.rst import Directive, directives, roles, convert_directive_function
from docutils.statemachine import StateMachine from docutils.statemachine import StateMachine
from docutils.utils import Reporter from docutils.utils import Reporter
@ -34,7 +35,7 @@ report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(
if False: if False:
# For type annotation # For type annotation
from typing import Any, Callable, Generator, Iterator, List, Set, Tuple # NOQA from typing import Any, Callable, Generator, List, Set, Tuple # NOQA
from docutils.statemachine import State, ViewList # NOQA from docutils.statemachine import State, ViewList # NOQA
from sphinx.config import Config # NOQA from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA from sphinx.environment import BuildEnvironment # NOQA
@ -47,7 +48,7 @@ additional_nodes = set() # type: Set[nodes.Node]
@contextmanager @contextmanager
def docutils_namespace(): def docutils_namespace():
# type: () -> Iterator[None] # type: () -> Generator[None, None, None]
"""Create namespace for reST parsers.""" """Create namespace for reST parsers."""
try: try:
_directives = copy(directives._directives) _directives = copy(directives._directives)
@ -94,29 +95,53 @@ def unregister_node(node):
delattr(nodes.SparseNodeVisitor, 'depart_' + node.__name__) delattr(nodes.SparseNodeVisitor, 'depart_' + node.__name__)
def patched_get_language(language_code, reporter=None): @contextmanager
# type: (unicode, Reporter) -> Any def patched_get_language():
"""A wrapper for docutils.languages.get_language(). # type: () -> Generator[None, None, None]
"""Patch docutils.languages.get_language() temporarily.
This ignores the second argument ``reporter`` to suppress warnings. This ignores the second argument ``reporter`` to suppress warnings.
refs: https://github.com/sphinx-doc/sphinx/issues/3788 refs: https://github.com/sphinx-doc/sphinx/issues/3788
""" """
return get_language(language_code) from docutils.languages import get_language
def patched_get_language(language_code, reporter=None):
# type: (unicode, Reporter) -> Any
return get_language(language_code)
@contextmanager
def patch_docutils():
# type: () -> Iterator[None]
"""Patch to docutils temporarily."""
try: try:
docutils.languages.get_language = patched_get_language docutils.languages.get_language = patched_get_language
yield yield
finally: finally:
# restore original implementations # restore original implementations
docutils.languages.get_language = get_language docutils.languages.get_language = get_language
@contextmanager
def using_user_docutils_conf(confdir):
# type: (unicode) -> Generator[None, None, None]
"""Let docutils know the location of ``docutils.conf`` for Sphinx."""
try:
docutilsconfig = os.environ.get('DOCUTILSCONFIG', None)
if confdir:
os.environ['DOCUTILSCONFIG'] = path.join(path.abspath(confdir), 'docutils.conf') # type: ignore # NOQA
yield
finally:
if docutilsconfig is None:
os.environ.pop('DOCUTILSCONFIG')
else:
os.environ['DOCUTILSCONFIG'] = docutilsconfig
@contextmanager
def patch_docutils(confdir=None):
# type: (unicode) -> Generator[None, None, None]
"""Patch to docutils temporarily."""
with patched_get_language(), using_user_docutils_conf(confdir):
yield
class ElementLookupError(Exception): class ElementLookupError(Exception):
pass pass
@ -257,7 +282,7 @@ def directive_helper(obj, has_content=None, argument_spec=None, **option_spec):
@contextmanager @contextmanager
def switch_source_input(state, content): def switch_source_input(state, content):
# type: (State, ViewList) -> Generator # type: (State, ViewList) -> Generator[None, None, None]
"""Switch current source input of state temporarily.""" """Switch current source input of state temporarily."""
try: try:
# remember the original ``get_source_and_line()`` method # remember the original ``get_source_and_line()`` method

View File

@ -15,6 +15,7 @@ import sys
import pytest import pytest
from sphinx.testing.path import path from sphinx.testing.path import path
from sphinx.util.docutils import patch_docutils
def regex_count(expr, result): def regex_count(expr, result):
@ -23,7 +24,9 @@ def regex_count(expr, result):
@pytest.mark.sphinx('html', testroot='docutilsconf', freshenv=True, docutilsconf='') @pytest.mark.sphinx('html', testroot='docutilsconf', freshenv=True, docutilsconf='')
def test_html_with_default_docutilsconf(app, status, warning): def test_html_with_default_docutilsconf(app, status, warning):
app.builder.build(['contents']) with patch_docutils(app.confdir):
app.builder.build(['contents'])
result = (app.outdir / 'contents.html').text(encoding='utf-8') result = (app.outdir / 'contents.html').text(encoding='utf-8')
assert regex_count(r'<th class="field-name">', result) == 1 assert regex_count(r'<th class="field-name">', result) == 1
@ -39,7 +42,9 @@ def test_html_with_default_docutilsconf(app, status, warning):
'\n') '\n')
) )
def test_html_with_docutilsconf(app, status, warning): def test_html_with_docutilsconf(app, status, warning):
app.builder.build(['contents']) with patch_docutils(app.confdir):
app.builder.build(['contents'])
result = (app.outdir / 'contents.html').text(encoding='utf-8') result = (app.outdir / 'contents.html').text(encoding='utf-8')
assert regex_count(r'<th class="field-name">', result) == 0 assert regex_count(r'<th class="field-name">', result) == 0
@ -50,25 +55,29 @@ def test_html_with_docutilsconf(app, status, warning):
@pytest.mark.sphinx('html', testroot='docutilsconf') @pytest.mark.sphinx('html', testroot='docutilsconf')
def test_html(app, status, warning): def test_html(app, status, warning):
app.builder.build(['contents']) with patch_docutils(app.confdir):
app.builder.build(['contents'])
assert warning.getvalue() == '' assert warning.getvalue() == ''
@pytest.mark.sphinx('latex', testroot='docutilsconf') @pytest.mark.sphinx('latex', testroot='docutilsconf')
def test_latex(app, status, warning): def test_latex(app, status, warning):
app.builder.build(['contents']) with patch_docutils(app.confdir):
app.builder.build(['contents'])
assert warning.getvalue() == '' assert warning.getvalue() == ''
@pytest.mark.sphinx('man', testroot='docutilsconf') @pytest.mark.sphinx('man', testroot='docutilsconf')
def test_man(app, status, warning): def test_man(app, status, warning):
app.builder.build(['contents']) with patch_docutils(app.confdir):
app.builder.build(['contents'])
assert warning.getvalue() == '' assert warning.getvalue() == ''
@pytest.mark.sphinx('texinfo', testroot='docutilsconf') @pytest.mark.sphinx('texinfo', testroot='docutilsconf')
def test_texinfo(app, status, warning): def test_texinfo(app, status, warning):
app.builder.build(['contents']) with patch_docutils(app.confdir):
app.builder.build(['contents'])
@pytest.mark.sphinx('html', testroot='docutilsconf', @pytest.mark.sphinx('html', testroot='docutilsconf',
@ -87,4 +96,5 @@ def test_docutils_source_link_with_nonascii_file(app, status, warning):
'nonascii filename not supported on this filesystem encoding: ' 'nonascii filename not supported on this filesystem encoding: '
'%s', FILESYSTEMENCODING) '%s', FILESYSTEMENCODING)
app.builder.build_all() with patch_docutils(app.confdir):
app.builder.build_all()