From ba832143860866c543d4904903a120822d17a26a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 23 Mar 2018 01:04:56 +0900 Subject: [PATCH 01/68] Refactor: Add Config.read() as a constructor To simplify Config.__init__() method, this separates conf.py parsing feature to Config.read() method. This allows to instantiate config object simply. --- CHANGES | 4 ++ doc/extdev/index.rst | 6 +++ sphinx/application.py | 4 +- sphinx/config.py | 85 ++++++++++++++++++++++++------------ tests/test_config.py | 8 ++-- tests/test_directive_code.py | 2 +- 6 files changed, 75 insertions(+), 34 deletions(-) diff --git a/CHANGES b/CHANGES index a1736631d..fc844f139 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,8 @@ Deprecated deprecated * ``sphinx.locale.l_()`` is deprecated * #2157: helper function ``warn()`` for HTML themes is deprecated +* ``Config.__init__()`` has changed; the *dirname*, *filename* and *tags* + argument has been deprecated For more details, see `deprecation APIs list `_ @@ -51,6 +53,8 @@ Features added * helper function ``warning()`` for HTML themes is added * Add ``Domain.enumerable_nodes`` to manage own enumerable nodes for domains (experimental) +* Add ``Config.read()`` classmethod to create a new config object from + configuration file Bugs fixed ---------- diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index a28d01890..92b0a3139 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -113,6 +113,12 @@ The following is a list of deprecated interface. - (will be) Removed - Alternatives + * - ``dirname``, ``filename`` and ``tags`` arguments of + ``Config.__init__()`` + - 1.8 + - 3.0 + - ``Config.read()`` + * - ``warn()`` (template helper function) - 1.8 - 3.0 diff --git a/sphinx/application.py b/sphinx/application.py index 8014460a3..df2fc00ee 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -187,8 +187,8 @@ class Sphinx(object): # read config self.tags = Tags(tags) - self.config = Config(self.confdir, CONFIG_FILENAME, - confoverrides or {}, self.tags) + self.config = Config.read(self.confdir, CONFIG_FILENAME, + confoverrides or {}, self.tags) self.config.check_unicode() # defer checking types until i18n has been initialized diff --git a/sphinx/config.py b/sphinx/config.py index df4a8f184..9323d48d2 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -11,12 +11,14 @@ import re import traceback +import warnings from collections import OrderedDict from os import path, getenv from typing import Any, NamedTuple, Union from six import PY2, PY3, iteritems, string_types, binary_type, text_type, integer_types +from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.errors import ConfigError from sphinx.locale import _, __ from sphinx.util import logging @@ -35,12 +37,6 @@ logger = logging.getLogger(__name__) nonascii_re = re.compile(br'[\x80-\xff]') copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])') -CONFIG_SYNTAX_ERROR = __("There is a syntax error in your configuration file: %s") -if PY3: - CONFIG_SYNTAX_ERROR += __("\nDid you change the syntax from 2.x to 3.x?") -CONFIG_ERROR = __("There is a programable error in your configuration file:\n\n%s") -CONFIG_EXIT_ERROR = __("The configuration file (or one of the modules it imports) " - "called sys.exit()") CONFIG_ENUM_WARNING = __("The config value `{name}` has to be a one of {candidates}, " "but `{current}` is given.") CONFIG_PERMITTED_TYPE_WARNING = __("The config value `{name}' has type `{current.__name__}', " @@ -159,30 +155,27 @@ class Config(object): 'env'), ) # type: Dict[unicode, Tuple] - def __init__(self, dirname, filename, overrides, tags): - # type: (unicode, unicode, Dict, Tags) -> None + def __init__(self, *args): + # type: (Any) -> None + if len(args) == 4: + # old style arguments: (dirname, filename, overrides, tags) + warnings.warn('The argument of Config() class has been changed. ' + 'Use Config.read() to read configuration from conf.py.', + RemovedInSphinx30Warning) + dirname, filename, overrides, tags = args + config = eval_config_file(dirname, filename, tags) + else: + # new style arguments: (config={}, overrides={}) + if len(args) == 0: + config, overrides = {}, {} + elif len(args) == 1: + config, overrides = args[0], {} + else: + config, overrides = args[:2] + self.overrides = overrides self.values = Config.config_values.copy() - config = {} # type: Dict[unicode, Any] - if dirname is not None: - config_file = path.join(dirname, filename) - config['__file__'] = config_file - config['tags'] = tags - with cd(dirname): - # we promise to have the config dir as current dir while the - # config file is executed - try: - execfile_(filename, config) - except SyntaxError as err: - raise ConfigError(CONFIG_SYNTAX_ERROR % err) - except SystemExit: - raise ConfigError(CONFIG_EXIT_ERROR) - except Exception: - raise ConfigError(CONFIG_ERROR % traceback.format_exc()) - self._raw_config = config - # these two must be preinitialized because extensions can add their - # own config values self.setup = config.get('setup', None) # type: Callable if 'extensions' in overrides: @@ -201,6 +194,13 @@ class Config(object): config[k] = copyright_year_re.sub(r'\g<1>%s' % format_date('%Y'), config[k]) + @classmethod + def read(cls, confdir, filename, overrides=None, tags=None): + # type: (unicode, unicode, Dict, Tags) -> Config + """Create a Config object from configuration file.""" + namespace = eval_config_file(confdir, filename, tags) + return Config(namespace, overrides or {}) + def check_types(self): # type: () -> None # check all values for deviation from the default value's type, since @@ -365,6 +365,37 @@ class Config(object): return (value for value in self if value.rebuild in rebuild) # type: ignore +def eval_config_file(confdir, filename, tags): + # type: (unicode, unicode, Tags) -> Dict[unicode, Any] + """Evaluate a config file.""" + if confdir is None: + return {} + + config_path = path.join(confdir, filename) + namespace = {} # type: Dict[unicode, Any] + namespace['__file__'] = config_path + namespace['tags'] = tags + + with cd(confdir): + # during executing config file, current dir is changed to ``confdir``. + try: + execfile_(filename, namespace) + except SyntaxError as err: + msg = __("There is a syntax error in your configuration file: %s") + if PY3: + msg += __("\nDid you change the syntax from 2.x to 3.x?") + raise ConfigError(msg % err) + except SystemExit: + msg = __("The configuration file (or one of the modules it imports) " + "called sys.exit()") + raise ConfigError(msg) + except Exception: + msg = __("There is a programable error in your configuration file:\n\n%s") + raise ConfigError(msg % traceback.format_exc()) + + return namespace + + def convert_source_suffix(app, config): # type: (Sphinx, Config) -> None """This converts old styled source_suffix to new styled one. diff --git a/tests/test_config.py b/tests/test_config.py index be227819e..4775421d5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -96,14 +96,14 @@ def test_errors_warnings(logger, tempdir): # test the error for syntax errors in the config file (tempdir / 'conf.py').write_text(u'project = \n', encoding='ascii') with pytest.raises(ConfigError) as excinfo: - Config(tempdir, 'conf.py', {}, None) + Config.read(tempdir, 'conf.py', {}, None) assert 'conf.py' in str(excinfo.value) # test the automatic conversion of 2.x only code in configs (tempdir / 'conf.py').write_text( u'# -*- coding: utf-8\n\nproject = u"Jägermeister"\n', encoding='utf-8') - cfg = Config(tempdir, 'conf.py', {}, None) + cfg = Config.read(tempdir, 'conf.py', {}, None) cfg.init_values() assert cfg.project == u'Jägermeister' assert logger.called is False @@ -115,7 +115,7 @@ def test_errors_warnings(logger, tempdir): return (tempdir / 'conf.py').write_text( u'# -*- coding: latin-1\nproject = "fooä"\n', encoding='latin-1') - cfg = Config(tempdir, 'conf.py', {}, None) + cfg = Config.read(tempdir, 'conf.py', {}, None) assert logger.warning.called is False cfg.check_unicode() @@ -174,7 +174,7 @@ def test_config_eol(logger, tempdir): configfile = tempdir / 'conf.py' for eol in (b'\n', b'\r\n'): configfile.write_bytes(b'project = "spam"' + eol) - cfg = Config(tempdir, 'conf.py', {}, None) + cfg = Config.read(tempdir, 'conf.py', {}, None) cfg.init_values() assert cfg.project == u'spam' assert logger.called is False diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index 5b48b1d61..cb4a1ef22 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -17,7 +17,7 @@ from sphinx.config import Config from sphinx.directives.code import LiteralIncludeReader from sphinx.testing.util import etree_parse -DUMMY_CONFIG = Config(None, None, {}, '') +DUMMY_CONFIG = Config({}, {}) @pytest.fixture(scope='module') From 1cce27c3021b6f058f0089075fb0c67793d9520d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 23 Mar 2018 01:50:53 +0900 Subject: [PATCH 02/68] Refactor: Move correct-copyright-year to config-inited handler --- sphinx/config.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/sphinx/config.py b/sphinx/config.py index 9323d48d2..0ffa128f3 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -185,15 +185,6 @@ class Config(object): config['extensions'] = overrides.pop('extensions') self.extensions = config.get('extensions', []) # type: List[unicode] - # correct values of copyright year that are not coherent with - # the SOURCE_DATE_EPOCH environment variable (if set) - # See https://reproducible-builds.org/specs/source-date-epoch/ - if getenv('SOURCE_DATE_EPOCH') is not None: - for k in ('copyright', 'epub_copyright'): - if k in config: - config[k] = copyright_year_re.sub(r'\g<1>%s' % format_date('%Y'), - config[k]) - @classmethod def read(cls, confdir, filename, overrides=None, tags=None): # type: (unicode, unicode, Dict, Tags) -> Config @@ -422,9 +413,24 @@ def convert_source_suffix(app, config): "But `%r' is given." % source_suffix)) +def correct_copyright_year(app, config): + # type: (Sphinx, Config) -> None + """correct values of copyright year that are not coherent with + the SOURCE_DATE_EPOCH environment variable (if set) + + See https://reproducible-builds.org/specs/source-date-epoch/ + """ + if getenv('SOURCE_DATE_EPOCH') is not None: + for k in ('copyright', 'epub_copyright'): + if k in config: + replace = r'\g<1>%s' % format_date('%Y') + config[k] = copyright_year_re.sub(replace, config[k]) # type: ignore + + def setup(app): # type: (Sphinx) -> Dict[unicode, Any] app.connect('config-inited', convert_source_suffix) + app.connect('config-inited', correct_copyright_year) return { 'version': 'builtin', From e4ecb97d5ac308cb3a54994661d61d0c82402eb0 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 23 Mar 2018 14:05:04 +0900 Subject: [PATCH 03/68] refactor testcase for config --- tests/roots/test-config/conf.py | 57 +------------ tests/test_config.py | 145 +++++++++++++++++++++----------- 2 files changed, 98 insertions(+), 104 deletions(-) diff --git a/tests/roots/test-config/conf.py b/tests/roots/test-config/conf.py index 4c2ea9fc5..0027d87f2 100644 --- a/tests/roots/test-config/conf.py +++ b/tests/roots/test-config/conf.py @@ -1,54 +1,3 @@ -from sphinx.config import string_classes, ENUM - -value1 = 123 # wrong type -value2 = 123 # lambda with wrong type -value3 = [] # lambda with correct type -value4 = True # child type -value5 = 3 # parent type -value6 = () # other sequence type, also raises -value7 = ['foo'] # explicitly permitted - - -class A(object): - pass - - -class B(A): - pass - - -class C(A): - pass - - -value8 = C() # sibling type - -# both have no default or permissible types -value9 = 'foo' -value10 = 123 -value11 = u'bar' -value12 = u'bar' -value13 = 'bar' -value14 = u'bar' -value15 = 'bar' -value16 = u'bar' - - -def setup(app): - app.add_config_value('value1', 'string', False) - app.add_config_value('value2', lambda conf: [], False) - app.add_config_value('value3', [], False) - app.add_config_value('value4', 100, False) - app.add_config_value('value5', False, False) - app.add_config_value('value6', [], False) - app.add_config_value('value7', 'string', False, [list]) - app.add_config_value('value8', B(), False) - app.add_config_value('value9', None, False) - app.add_config_value('value10', None, False) - app.add_config_value('value11', None, False, [str]) - app.add_config_value('value12', 'string', False) - app.add_config_value('value13', None, False, string_classes) - app.add_config_value('value14', None, False, string_classes) - app.add_config_value('value15', u'unicode', False) - app.add_config_value('value16', u'unicode', False) - app.add_config_value('value17', 'default', False, ENUM('default', 'one', 'two')) +project = 'Sphinx ' +release = '0.6alpha1' +templates_path = ['_templates'] diff --git a/tests/test_config.py b/tests/test_config.py index 4775421d5..c3600ddca 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -11,15 +11,15 @@ """ import mock import pytest -from six import PY3, iteritems +from six import PY3 import sphinx -from sphinx.config import Config +from sphinx.config import Config, ENUM, string_classes from sphinx.errors import ExtensionError, ConfigError, VersionRequirementError from sphinx.testing.path import path -@pytest.mark.sphinx(confoverrides={ +@pytest.mark.sphinx(testroot='config', confoverrides={ 'master_doc': 'master', 'nonexisting_value': 'True', 'latex_elements.docclass': 'scrartcl', @@ -91,6 +91,30 @@ def test_extension_values(app, status, warning): assert 'already present' in str(excinfo.value) +def test_overrides(): + config = Config({'value1': '1', 'value2': 2, 'value6': {'default': 6}}, + {'value2': 999, 'value3': '999', 'value5.attr1': 999, 'value6.attr1': 999, + 'value7': 'abc,def,ghi', 'value8': 'abc,def,ghi'}) + config.add('value1', None, 'env', ()) + config.add('value2', None, 'env', ()) + config.add('value3', 0, 'env', ()) + config.add('value4', 0, 'env', ()) + config.add('value5', {'default': 0}, 'env', ()) + config.add('value6', {'default': 0}, 'env', ()) + config.add('value7', None, 'env', ()) + config.add('value8', [], 'env', ()) + config.init_values() + + assert config.value1 == '1' + assert config.value2 == 999 + assert config.value3 == 999 + assert config.value4 == 0 + assert config.value5 == {'attr1': 999} + assert config.value6 == {'default': 6, 'attr1': 999} + assert config.value7 == 'abc,def,ghi' + assert config.value8 == ['abc', 'def', 'ghi'] + + @mock.patch("sphinx.config.logger") def test_errors_warnings(logger, tempdir): # test the error for syntax errors in the config file @@ -195,60 +219,81 @@ def test_builtin_conf(app, status, warning): 'warning') -# See roots/test-config/conf.py. -TYPECHECK_WARNINGS = { - 'value1': True, - 'value2': True, - 'value3': False, - 'value4': True, - 'value5': False, - 'value6': True, - 'value7': False, - 'value8': False, - 'value9': False, - 'value10': False, - 'value11': False if PY3 else True, - 'value12': False, - 'value13': False, - 'value14': False, - 'value15': False, - 'value16': False, -} +# example classes for type checking +class A(object): + pass -@pytest.mark.parametrize("key,should", iteritems(TYPECHECK_WARNINGS)) -@pytest.mark.sphinx(testroot='config') -def test_check_types(warning, key, should): - warn = warning.getvalue() - if should: - assert key in warn, ( - 'override on "%s" should raise a type warning' % key - ) - else: - assert key not in warn, ( - 'override on "%s" should NOT raise a type warning' % key - ) +class B(A): + pass -@pytest.mark.sphinx(testroot='config') -def test_check_enum(app, status, warning): - assert "The config value `value17` has to be a one of ('default', 'one', 'two'), " \ - not in warning.getvalue() +class C(A): + pass -@pytest.mark.sphinx(testroot='config', confoverrides={'value17': 'invalid'}) -def test_check_enum_failed(app, status, warning): - assert "The config value `value17` has to be a one of ('default', 'one', 'two'), " \ - "but `invalid` is given." in warning.getvalue() +# name, default, annotation, actual, warned +TYPECHECK_WARNINGS = [ + ('value1', 'string', None, 123, True), # wrong type + ('value2', lambda _: [], None, 123, True), # lambda with wrong type + ('value3', lambda _: [], None, [], False), # lambda with correct type + ('value4', 100, None, True, True), # child type + ('value5', False, None, True, False), # parent type + ('value6', [], None, (), True), # other sequence type + ('value7', 'string', [list], ['foo'], False), # explicit type annotation + ('value8', B(), None, C(), False), # sibling type + ('value9', None, None, 'foo', False), # no default or no annotations + ('value10', None, None, 123, False), # no default or no annotations + ('value11', None, [str], u'bar', False if PY3 else True), # str vs unicode + ('value12', 'string', None, u'bar', False), # str vs unicode + ('value13', None, string_classes, 'bar', False), # string_classes + ('value14', None, string_classes, u'bar', False), # string_classes + ('value15', u'unicode', None, 'bar', False), # str vs unicode + ('value16', u'unicode', None, u'bar', False), # str vs unicode +] -@pytest.mark.sphinx(testroot='config', confoverrides={'value17': ['one', 'two']}) -def test_check_enum_for_list(app, status, warning): - assert "The config value `value17` has to be a one of ('default', 'one', 'two'), " \ - not in warning.getvalue() +@mock.patch("sphinx.config.logger") +@pytest.mark.parametrize("name,default,annotation,actual,warned", TYPECHECK_WARNINGS) +def test_check_types(logger, name, default, annotation, actual, warned): + config = Config({name: actual}) + config.add(name, default, 'env', annotation or ()) + config.init_values() + config.check_types() + assert logger.warning.called == warned -@pytest.mark.sphinx(testroot='config', confoverrides={'value17': ['one', 'two', 'invalid']}) -def test_check_enum_for_list_failed(app, status, warning): - assert "The config value `value17` has to be a one of ('default', 'one', 'two'), " \ - "but `['one', 'two', 'invalid']` is given." in warning.getvalue() +@mock.patch("sphinx.config.logger") +def test_check_enum(logger): + config = Config() + config.add('value', 'default', False, ENUM('default', 'one', 'two')) + config.init_values() + config.check_types() + logger.warning.assert_not_called() # not warned + + +@mock.patch("sphinx.config.logger") +def test_check_enum_failed(logger): + config = Config({'value': 'invalid'}) + config.add('value', 'default', False, ENUM('default', 'one', 'two')) + config.init_values() + config.check_types() + logger.warning.assert_called() + + +@mock.patch("sphinx.config.logger") +def test_check_enum_for_list(logger): + config = Config({'value': ['one', 'two']}) + config.add('value', 'default', False, ENUM('default', 'one', 'two')) + config.init_values() + config.check_types() + logger.warning.assert_not_called() # not warned + + +@mock.patch("sphinx.config.logger") +def test_check_enum_for_list_failed(logger): + config = Config({'value': ['one', 'two', 'invalid']}) + config.add('value', 'default', False, ENUM('default', 'one', 'two')) + config.init_values() + config.check_types() + logger.warning.assert_called() From 3d8cb12497dfa9a044e4b4eaa98b39c4000c1915 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 23 Mar 2018 14:26:07 +0900 Subject: [PATCH 04/68] Raise ExtensionError from Config class instead application class --- sphinx/application.py | 6 +----- sphinx/config.py | 7 +++++-- sphinx/ext/autodoc/__init__.py | 2 +- sphinx/ext/jsmath.py | 2 +- tests/roots/test-root/conf.py | 5 +---- tests/roots/test-root/ext.py | 5 ----- tests/test_application.py | 2 +- tests/test_config.py | 22 +++++++++++++--------- 8 files changed, 23 insertions(+), 28 deletions(-) delete mode 100644 tests/roots/test-root/ext.py diff --git a/sphinx/application.py b/sphinx/application.py index df2fc00ee..6b1779016 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -29,9 +29,7 @@ from sphinx import package_dir, locale from sphinx.config import Config from sphinx.deprecation import RemovedInSphinx20Warning, RemovedInSphinx30Warning from sphinx.environment import BuildEnvironment -from sphinx.errors import ( - ApplicationError, ConfigError, ExtensionError, VersionRequirementError -) +from sphinx.errors import ApplicationError, ConfigError, VersionRequirementError from sphinx.events import EventManager from sphinx.locale import __ from sphinx.registry import SphinxComponentRegistry @@ -556,8 +554,6 @@ class Sphinx(object): """ logger.debug('[app] adding config value: %r', (name, default, rebuild) + ((types,) if types else ())) # type: ignore - if name in self.config: - raise ExtensionError(__('Config value %r already present') % name) if rebuild in (False, True): rebuild = rebuild and 'env' or '' self.config.add(name, default, rebuild, types) diff --git a/sphinx/config.py b/sphinx/config.py index 0ffa128f3..7818244e5 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -19,7 +19,7 @@ from typing import Any, NamedTuple, Union from six import PY2, PY3, iteritems, string_types, binary_type, text_type, integer_types from sphinx.deprecation import RemovedInSphinx30Warning -from sphinx.errors import ConfigError +from sphinx.errors import ConfigError, ExtensionError from sphinx.locale import _, __ from sphinx.util import logging from sphinx.util.i18n import format_date @@ -347,7 +347,10 @@ class Config(object): def add(self, name, default, rebuild, types): # type: (unicode, Any, Union[bool, unicode], Any) -> None - self.values[name] = (default, rebuild, types) + if name in self.values: + raise ExtensionError(__('Config value %r already present') % name) + else: + self.values[name] = (default, rebuild, types) def filter(self, rebuild): # type: (Union[unicode, List[unicode]]) -> Iterator[ConfigValue] diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 132116d55..b956fdb7e 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -20,8 +20,8 @@ from docutils.statemachine import ViewList from six import iteritems, itervalues, text_type, class_types, string_types import sphinx -from sphinx.application import ExtensionError from sphinx.deprecation import RemovedInSphinx20Warning +from sphinx.errors import ExtensionError from sphinx.ext.autodoc.importer import mock, import_object, get_object_members from sphinx.ext.autodoc.importer import _MockImporter # to keep compatibility # NOQA from sphinx.ext.autodoc.inspector import format_annotation, formatargspec # to keep compatibility # NOQA diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py index e523b54c8..ffa36969d 100644 --- a/sphinx/ext/jsmath.py +++ b/sphinx/ext/jsmath.py @@ -15,7 +15,7 @@ from typing import TYPE_CHECKING from docutils import nodes import sphinx -from sphinx.application import ExtensionError +from sphinx.errors import ExtensionError from sphinx.ext.mathbase import get_node_equation_number from sphinx.ext.mathbase import setup_math as mathbase_setup from sphinx.locale import _ diff --git a/tests/roots/test-root/conf.py b/tests/roots/test-root/conf.py index 38b17f57a..f96ea8821 100644 --- a/tests/roots/test-root/conf.py +++ b/tests/roots/test-root/conf.py @@ -12,7 +12,7 @@ from sphinx import addnodes sys.path.append(os.path.abspath('.')) extensions = ['sphinx.ext.autodoc', 'sphinx.ext.jsmath', 'sphinx.ext.todo', - 'sphinx.ext.coverage', 'sphinx.ext.extlinks', 'ext'] + 'sphinx.ext.coverage', 'sphinx.ext.extlinks'] jsmath_path = 'dummy.js' @@ -65,8 +65,6 @@ man_pages = [ 'Georg Brandl and someone else', 1), ] -value_from_conf_py = 84 - coverage_c_path = ['special/*.h'] coverage_c_regexes = {'function': r'^PyAPI_FUNC\(.*\)\s+([^_][\w_]+)'} @@ -104,7 +102,6 @@ class ClassDirective(Directive): def setup(app): import parsermod - app.add_config_value('value_from_conf_py', 42, False) app.add_directive('clsdir', ClassDirective) app.add_object_type('userdesc', 'userdescrole', '%s (userdesc)', userdesc_parse, objname='user desc') diff --git a/tests/roots/test-root/ext.py b/tests/roots/test-root/ext.py deleted file mode 100644 index 5665bea90..000000000 --- a/tests/roots/test-root/ext.py +++ /dev/null @@ -1,5 +0,0 @@ -# Test extension module - - -def setup(app): - app.add_config_value('value_from_ext', [], False) diff --git a/tests/test_application.py b/tests/test_application.py index babf95497..aa598b00a 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -11,7 +11,7 @@ import pytest from docutils import nodes -from sphinx.application import ExtensionError +from sphinx.errors import ExtensionError from sphinx.domains import Domain from sphinx.testing.util import strip_escseq from sphinx.util import logging diff --git a/tests/test_config.py b/tests/test_config.py index c3600ddca..e48c1c160 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -74,20 +74,24 @@ def test_core_config(app, status, warning): assert cfg['project'] == cfg.project == 'Sphinx Tests' -def test_extension_values(app, status, warning): - cfg = app.config +def test_extension_values(): + config = Config() - # default value - assert cfg.value_from_ext == [] - # non-default value - assert cfg.value_from_conf_py == 84 + # check standard settings + assert config.master_doc == 'contents' - # no duplicate values allowed + # can't override it by add_config_value() with pytest.raises(ExtensionError) as excinfo: - app.add_config_value('html_title', 'x', True) + config.add('master_doc', 'index', 'env', None) assert 'already present' in str(excinfo.value) + + # add a new config value + config.add('value_from_ext', [], 'env', None) + assert config.value_from_ext == [] + + # can't override it by add_config_value() with pytest.raises(ExtensionError) as excinfo: - app.add_config_value('value_from_ext', 'x', True) + config.add('value_from_ext', [], 'env', None) assert 'already present' in str(excinfo.value) From 78cbf96158ee21d326da4b6e9325d9fe68b8c553 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 23 Mar 2018 20:43:41 +0900 Subject: [PATCH 05/68] Deprecate Config.check_types() --- CHANGES | 1 + doc/extdev/index.rst | 5 ++ sphinx/application.py | 2 - sphinx/builders/latex.py | 2 +- sphinx/config.py | 104 +++++++++++++++++++++------------------ tests/test_config.py | 12 ++--- 6 files changed, 68 insertions(+), 58 deletions(-) diff --git a/CHANGES b/CHANGES index fc844f139..ccab93722 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,7 @@ Deprecated * #2157: helper function ``warn()`` for HTML themes is deprecated * ``Config.__init__()`` has changed; the *dirname*, *filename* and *tags* argument has been deprecated +* ``Config.check_types()`` is deprecated For more details, see `deprecation APIs list `_ diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 92b0a3139..a3b880deb 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -113,6 +113,11 @@ The following is a list of deprecated interface. - (will be) Removed - Alternatives + * - ``Config.check_types()`` + - 1.8 + - 3.0 + - ``sphinx.config.check_confval_types()`` + * - ``dirname``, ``filename`` and ``tags`` arguments of ``Config.__init__()`` - 1.8 diff --git a/sphinx/application.py b/sphinx/application.py index 6b1779016..c2da600f4 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -246,8 +246,6 @@ class Sphinx(object): # create the builder self.builder = self.create_builder(buildername) - # check all configuration values for permissible types - self.config.check_types() # set up the build environment self._init_env(freshenv) # set up the builder diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex.py index e0ce9f235..8425e25e9 100644 --- a/sphinx/builders/latex.py +++ b/sphinx/builders/latex.py @@ -315,7 +315,7 @@ def setup(app): app.add_config_value('latex_appendices', [], None) app.add_config_value('latex_use_latex_multicolumn', False, None) app.add_config_value('latex_toplevel_sectioning', None, None, - ENUM('part', 'chapter', 'section')) + ENUM(None, 'part', 'chapter', 'section')) app.add_config_value('latex_domain_indices', True, None, [list]) app.add_config_value('latex_show_urls', 'no', None) app.add_config_value('latex_show_pagerefs', False, None) diff --git a/sphinx/config.py b/sphinx/config.py index 7818244e5..a6cb6c682 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -37,13 +37,6 @@ logger = logging.getLogger(__name__) nonascii_re = re.compile(br'[\x80-\xff]') copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])') -CONFIG_ENUM_WARNING = __("The config value `{name}` has to be a one of {candidates}, " - "but `{current}` is given.") -CONFIG_PERMITTED_TYPE_WARNING = __("The config value `{name}' has type `{current.__name__}', " - "expected to {permitted}.") -CONFIG_TYPE_WARNING = __("The config value `{name}' has type `{current.__name__}', " - "defaults to `{default.__name__}'.") - if PY3: unicode = str # special alias for static typing... @@ -194,48 +187,9 @@ class Config(object): def check_types(self): # type: () -> None - # check all values for deviation from the default value's type, since - # that can result in TypeErrors all over the place - # NB. since config values might use _() we have to wait with calling - # this method until i18n is initialized - for name in self._raw_config: - if name not in self.values: - continue # we don't know a default value - settings = self.values[name] - default, dummy_rebuild = settings[:2] - permitted = settings[2] if len(settings) == 3 else () - - if hasattr(default, '__call__'): - default = default(self) # could invoke _() - if default is None and not permitted: - continue # neither inferrable nor expliclitly permitted types - current = self[name] - if permitted is Any: - # any type of value is accepted - pass - elif isinstance(permitted, ENUM): - if not permitted.match(current): - logger.warning(CONFIG_ENUM_WARNING.format( - name=name, current=current, candidates=permitted.candidates)) - else: - if type(current) is type(default): - continue - if type(current) in permitted: - continue - - common_bases = (set(type(current).__bases__ + (type(current),)) & - set(type(default).__bases__)) - common_bases.discard(object) - if common_bases: - continue # at least we share a non-trivial base class - - if permitted: - logger.warning(CONFIG_PERMITTED_TYPE_WARNING.format( - name=name, current=type(current), - permitted=str([cls.__name__ for cls in permitted]))) - else: - logger.warning(CONFIG_TYPE_WARNING.format( - name=name, current=type(current), default=type(default))) + warnings.warn('Config.check_types() is deprecated. Use check_confval_types() instead.', + RemovedInSphinx30Warning) + check_confval_types(None, self) def check_unicode(self): # type: () -> None @@ -430,10 +384,62 @@ def correct_copyright_year(app, config): config[k] = copyright_year_re.sub(replace, config[k]) # type: ignore +def check_confval_types(app, config): + # type: (Sphinx, Config) -> None + """check all values for deviation from the default value's type, since + that can result in TypeErrors all over the place NB. + """ + for confval in config: + settings = config.values[confval.name] + default = settings[0] + annotations = settings[2] if len(settings) == 3 else () + + if hasattr(default, '__call__'): + default = default(config) # evaluate default value + if default is None and not annotations: + continue # neither inferrable nor expliclitly annotated types + + if annotations is Any: + # any type of value is accepted + pass + elif isinstance(annotations, ENUM): + if not annotations.match(confval.value): + msg = __("The config value `{name}` has to be a one of {candidates}, " + "but `{current}` is given.") + logger.warning(msg.format(name=confval.name, + current=confval.value, + candidates=annotations.candidates)) + else: + if type(confval.value) is type(default): + continue + if type(confval.value) in annotations: + continue + + common_bases = (set(type(confval.value).__bases__ + (type(confval.value),)) & + set(type(default).__bases__)) + common_bases.discard(object) + if common_bases: + continue # at least we share a non-trivial base class + + if annotations: + msg = __("The config value `{name}' has type `{current.__name__}', " + "expected to {permitted}.") + logger.warning(msg.format(name=confval.name, + current=type(confval.value), + permitted=str([c.__name__ for c in annotations]))) + else: + msg = __("The config value `{name}' has type `{current.__name__}', " + "defaults to `{default.__name__}'.") + logger.warning(msg.format(name=confval.name, + current=type(confval.value), + default=type(default))) + + def setup(app): # type: (Sphinx) -> Dict[unicode, Any] app.connect('config-inited', convert_source_suffix) app.connect('config-inited', correct_copyright_year) + app.connect('config-inited', check_confval_types) return { 'version': 'builtin', diff --git a/tests/test_config.py b/tests/test_config.py index e48c1c160..f3110f6a7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -14,7 +14,7 @@ import pytest from six import PY3 import sphinx -from sphinx.config import Config, ENUM, string_classes +from sphinx.config import Config, ENUM, string_classes, check_confval_types from sphinx.errors import ExtensionError, ConfigError, VersionRequirementError from sphinx.testing.path import path @@ -263,7 +263,7 @@ def test_check_types(logger, name, default, annotation, actual, warned): config = Config({name: actual}) config.add(name, default, 'env', annotation or ()) config.init_values() - config.check_types() + check_confval_types(None, config) assert logger.warning.called == warned @@ -272,7 +272,7 @@ def test_check_enum(logger): config = Config() config.add('value', 'default', False, ENUM('default', 'one', 'two')) config.init_values() - config.check_types() + check_confval_types(None, config) logger.warning.assert_not_called() # not warned @@ -281,7 +281,7 @@ def test_check_enum_failed(logger): config = Config({'value': 'invalid'}) config.add('value', 'default', False, ENUM('default', 'one', 'two')) config.init_values() - config.check_types() + check_confval_types(None, config) logger.warning.assert_called() @@ -290,7 +290,7 @@ def test_check_enum_for_list(logger): config = Config({'value': ['one', 'two']}) config.add('value', 'default', False, ENUM('default', 'one', 'two')) config.init_values() - config.check_types() + check_confval_types(None, config) logger.warning.assert_not_called() # not warned @@ -299,5 +299,5 @@ def test_check_enum_for_list_failed(logger): config = Config({'value': ['one', 'two', 'invalid']}) config.add('value', 'default', False, ENUM('default', 'one', 'two')) config.init_values() - config.check_types() + check_confval_types(None, config) logger.warning.assert_called() From 13455067e822036e4cfe1d73bcaee22b99da3016 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 23 Mar 2018 23:39:14 +0900 Subject: [PATCH 06/68] Fix mypy violations --- sphinx/config.py | 6 +++--- sphinx/ext/ifconfig.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/config.py b/sphinx/config.py index a6cb6c682..ba5e9a6ac 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -28,7 +28,7 @@ from sphinx.util.pycompat import execfile_, NoneType if False: # For type annotation - from typing import Any, Callable, Dict, Iterable, Iterator, List, Tuple, Union # NOQA + from typing import Any, Callable, Dict, Generator, Iterator, List, Tuple, Union # NOQA from sphinx.application import Sphinx # NOQA from sphinx.util.tags import Tags # NOQA @@ -295,7 +295,7 @@ class Config(object): return name in self.values def __iter__(self): - # type: () -> Iterable[ConfigValue] + # type: () -> Generator[ConfigValue, None, None] for name, value in iteritems(self.values): yield ConfigValue(name, getattr(self, name), value[1]) # type: ignore @@ -310,7 +310,7 @@ class Config(object): # type: (Union[unicode, List[unicode]]) -> Iterator[ConfigValue] if isinstance(rebuild, string_types): rebuild = [rebuild] - return (value for value in self if value.rebuild in rebuild) # type: ignore + return (value for value in self if value.rebuild in rebuild) def eval_config_file(confdir, filename, tags): diff --git a/sphinx/ext/ifconfig.py b/sphinx/ext/ifconfig.py index 16042ac3f..864e7a185 100644 --- a/sphinx/ext/ifconfig.py +++ b/sphinx/ext/ifconfig.py @@ -57,7 +57,7 @@ class IfConfig(Directive): def process_ifconfig_nodes(app, doctree, docname): # type: (Sphinx, nodes.Node, unicode) -> None - ns = dict((confval.name, confval.value) for confval in app.config) # type: ignore + ns = dict((confval.name, confval.value) for confval in app.config) ns.update(app.config.__dict__.copy()) ns['builder'] = app.builder.name for node in doctree.traverse(ifconfig): From 2a43b44ec6d3ba4c9ed76ddb42daf7c8b3e161cb Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 27 Mar 2018 01:29:50 +0900 Subject: [PATCH 07/68] Use ``cls`` variable to instantiate --- sphinx/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/config.py b/sphinx/config.py index ba5e9a6ac..407bad31c 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -183,7 +183,7 @@ class Config(object): # type: (unicode, unicode, Dict, Tags) -> Config """Create a Config object from configuration file.""" namespace = eval_config_file(confdir, filename, tags) - return Config(namespace, overrides or {}) + return cls(namespace, overrides or {}) def check_types(self): # type: () -> None From 7aaa4948f563fcaa6d17b008c3618a9eba18fdd3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 27 Mar 2018 01:33:07 +0900 Subject: [PATCH 08/68] The `dirname` argument for eval_config_file() is now required --- sphinx/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/config.py b/sphinx/config.py index 407bad31c..4e3aad53e 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -156,7 +156,10 @@ class Config(object): 'Use Config.read() to read configuration from conf.py.', RemovedInSphinx30Warning) dirname, filename, overrides, tags = args - config = eval_config_file(dirname, filename, tags) + if dirname is None: + config = {} + else: + config = eval_config_file(dirname, filename, tags) else: # new style arguments: (config={}, overrides={}) if len(args) == 0: @@ -316,9 +319,6 @@ class Config(object): def eval_config_file(confdir, filename, tags): # type: (unicode, unicode, Tags) -> Dict[unicode, Any] """Evaluate a config file.""" - if confdir is None: - return {} - config_path = path.join(confdir, filename) namespace = {} # type: Dict[unicode, Any] namespace['__file__'] = config_path From 0df4a121bd5348a2fcfbf70551e887d980447bf9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 28 Mar 2018 00:54:06 +0900 Subject: [PATCH 09/68] Fix mypy violations --- sphinx/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/config.py b/sphinx/config.py index 4e3aad53e..1656aa9a3 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -157,7 +157,7 @@ class Config(object): RemovedInSphinx30Warning) dirname, filename, overrides, tags = args if dirname is None: - config = {} + config = {} # type: Dict[unicode, Any] else: config = eval_config_file(dirname, filename, tags) else: From 181fb1093d451ecc7dac64da9a62761acd1bc7fa Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 28 Mar 2018 00:54:06 +0900 Subject: [PATCH 10/68] Now Config.read() takes a filename instead (dirname, filename) --- sphinx/application.py | 2 +- sphinx/config.py | 17 ++++++++--------- tests/test_config.py | 8 ++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index c2da600f4..92a20e44b 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -185,7 +185,7 @@ class Sphinx(object): # read config self.tags = Tags(tags) - self.config = Config.read(self.confdir, CONFIG_FILENAME, + self.config = Config.read(path.join(self.confdir, CONFIG_FILENAME), confoverrides or {}, self.tags) self.config.check_unicode() # defer checking types until i18n has been initialized diff --git a/sphinx/config.py b/sphinx/config.py index 1656aa9a3..4e6585334 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -159,7 +159,7 @@ class Config(object): if dirname is None: config = {} # type: Dict[unicode, Any] else: - config = eval_config_file(dirname, filename, tags) + config = eval_config_file(path.join(dirname, filename), tags) else: # new style arguments: (config={}, overrides={}) if len(args) == 0: @@ -182,10 +182,10 @@ class Config(object): self.extensions = config.get('extensions', []) # type: List[unicode] @classmethod - def read(cls, confdir, filename, overrides=None, tags=None): - # type: (unicode, unicode, Dict, Tags) -> Config + def read(cls, filename, overrides=None, tags=None): + # type: (unicode, Dict, Tags) -> Config """Create a Config object from configuration file.""" - namespace = eval_config_file(confdir, filename, tags) + namespace = eval_config_file(filename, tags) return cls(namespace, overrides or {}) def check_types(self): @@ -316,15 +316,14 @@ class Config(object): return (value for value in self if value.rebuild in rebuild) -def eval_config_file(confdir, filename, tags): - # type: (unicode, unicode, Tags) -> Dict[unicode, Any] +def eval_config_file(filename, tags): + # type: (unicode, Tags) -> Dict[unicode, Any] """Evaluate a config file.""" - config_path = path.join(confdir, filename) namespace = {} # type: Dict[unicode, Any] - namespace['__file__'] = config_path + namespace['__file__'] = filename namespace['tags'] = tags - with cd(confdir): + with cd(path.dirname(filename)): # during executing config file, current dir is changed to ``confdir``. try: execfile_(filename, namespace) diff --git a/tests/test_config.py b/tests/test_config.py index f3110f6a7..7f27e3ff0 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -124,14 +124,14 @@ def test_errors_warnings(logger, tempdir): # test the error for syntax errors in the config file (tempdir / 'conf.py').write_text(u'project = \n', encoding='ascii') with pytest.raises(ConfigError) as excinfo: - Config.read(tempdir, 'conf.py', {}, None) + Config.read(tempdir / 'conf.py', {}, None) assert 'conf.py' in str(excinfo.value) # test the automatic conversion of 2.x only code in configs (tempdir / 'conf.py').write_text( u'# -*- coding: utf-8\n\nproject = u"Jägermeister"\n', encoding='utf-8') - cfg = Config.read(tempdir, 'conf.py', {}, None) + cfg = Config.read(tempdir / 'conf.py', {}, None) cfg.init_values() assert cfg.project == u'Jägermeister' assert logger.called is False @@ -143,7 +143,7 @@ def test_errors_warnings(logger, tempdir): return (tempdir / 'conf.py').write_text( u'# -*- coding: latin-1\nproject = "fooä"\n', encoding='latin-1') - cfg = Config.read(tempdir, 'conf.py', {}, None) + cfg = Config.read(tempdir / 'conf.py', {}, None) assert logger.warning.called is False cfg.check_unicode() @@ -202,7 +202,7 @@ def test_config_eol(logger, tempdir): configfile = tempdir / 'conf.py' for eol in (b'\n', b'\r\n'): configfile.write_bytes(b'project = "spam"' + eol) - cfg = Config.read(tempdir, 'conf.py', {}, None) + cfg = Config.read(tempdir / 'conf.py', {}, None) cfg.init_values() assert cfg.project == u'spam' assert logger.called is False From 2f1cb1e1840f81e52657d3c270183d8d32c6654a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 28 Mar 2018 00:54:07 +0900 Subject: [PATCH 11/68] Instantiate Config object simply if confdir not given --- sphinx/application.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 92a20e44b..2bdc27b6d 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -185,10 +185,13 @@ class Sphinx(object): # read config self.tags = Tags(tags) - self.config = Config.read(path.join(self.confdir, CONFIG_FILENAME), - confoverrides or {}, self.tags) - self.config.check_unicode() - # defer checking types until i18n has been initialized + if self.confdir is None: + self.config = Config() + else: + self.config = Config.read(path.join(self.confdir, CONFIG_FILENAME), + confoverrides or {}, self.tags) + self.config.check_unicode() + # defer checking types until i18n has been initialized # initialize some limited config variables before initialize i18n and loading # extensions From 00e9e560b11c900b044cfb4945a26761c8d117e8 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 29 Mar 2018 10:24:48 +0900 Subject: [PATCH 12/68] Fix confoverrides parameter was ignored --- sphinx/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/application.py b/sphinx/application.py index 2bdc27b6d..91302e6e0 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -186,7 +186,7 @@ class Sphinx(object): # read config self.tags = Tags(tags) if self.confdir is None: - self.config = Config() + self.config = Config({}, confoverrides or {}) else: self.config = Config.read(path.join(self.confdir, CONFIG_FILENAME), confoverrides or {}, self.tags) From a24601aa24d5d0143a6bf352d4275e9a85e93559 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 29 Mar 2018 10:25:03 +0900 Subject: [PATCH 13/68] Deprecate Config.check_unicode() --- CHANGES | 1 + doc/extdev/index.rst | 5 +++++ sphinx/application.py | 5 ++--- sphinx/config.py | 25 +++++++++++++++++-------- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 11f62c501..cb94800ec 100644 --- a/CHANGES +++ b/CHANGES @@ -36,6 +36,7 @@ Deprecated * ``Config.__init__()`` has changed; the *dirname*, *filename* and *tags* argument has been deprecated * ``Config.check_types()`` is deprecated +* ``Config.check_unicode()`` is deprecated For more details, see `deprecation APIs list `_ diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index bc79394cc..192f6891a 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -113,6 +113,11 @@ The following is a list of deprecated interface. - (will be) Removed - Alternatives + * - ``Config.check_unicode()`` + - 1.8 + - 3.0 + - ``sphinx.config.check_unicode()`` + * - ``Config.check_types()`` - 1.8 - 3.0 diff --git a/sphinx/application.py b/sphinx/application.py index 91302e6e0..eaa5f8ccd 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -26,7 +26,7 @@ from six.moves import cStringIO import sphinx from sphinx import package_dir, locale -from sphinx.config import Config +from sphinx.config import Config, check_unicode from sphinx.deprecation import RemovedInSphinx20Warning, RemovedInSphinx30Warning from sphinx.environment import BuildEnvironment from sphinx.errors import ApplicationError, ConfigError, VersionRequirementError @@ -190,8 +190,7 @@ class Sphinx(object): else: self.config = Config.read(path.join(self.confdir, CONFIG_FILENAME), confoverrides or {}, self.tags) - self.config.check_unicode() - # defer checking types until i18n has been initialized + check_unicode(self.config) # initialize some limited config variables before initialize i18n and loading # extensions diff --git a/sphinx/config.py b/sphinx/config.py index 4e6585334..d1ef82ee4 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -34,7 +34,6 @@ if False: logger = logging.getLogger(__name__) -nonascii_re = re.compile(br'[\x80-\xff]') copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])') if PY3: @@ -196,13 +195,9 @@ class Config(object): def check_unicode(self): # type: () -> None - # check all string values for non-ASCII characters in bytestrings, - # since that can result in UnicodeErrors all over the place - for name, value in iteritems(self._raw_config): - if isinstance(value, binary_type) and nonascii_re.search(value): - logger.warning(__('the config value %r is set to a string with non-ASCII ' - 'characters; this can lead to Unicode errors occurring. ' - 'Please use Unicode strings, e.g. %r.'), name, u'Content') + warnings.warn('Config.check_unicode() is deprecated. Use check_unicode() instead.', + RemovedInSphinx30Warning) + check_unicode(self) def convert_overrides(self, name, value): # type: (unicode, Any) -> Any @@ -434,6 +429,20 @@ def check_confval_types(app, config): default=type(default))) +def check_unicode(config): + # type: (Config) -> None + """check all string values for non-ASCII characters in bytestrings, + since that can result in UnicodeErrors all over the place + """ + nonascii_re = re.compile(br'[\x80-\xff]') + + for name, value in iteritems(config._raw_config): + if isinstance(value, binary_type) and nonascii_re.search(value): + logger.warning(__('the config value %r is set to a string with non-ASCII ' + 'characters; this can lead to Unicode errors occurring. ' + 'Please use Unicode strings, e.g. %r.'), name, u'Content') + + def setup(app): # type: (Sphinx) -> Dict[unicode, Any] app.connect('config-inited', convert_source_suffix) From bc63dc845002a74bc82e5b519e318afe39198dcb Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 1 Apr 2018 20:07:45 +0900 Subject: [PATCH 14/68] Rename Config.read() to Config.from_conf_py() --- CHANGES | 2 +- doc/extdev/index.rst | 2 +- sphinx/application.py | 4 ++-- sphinx/config.py | 4 ++-- tests/test_config.py | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index cb94800ec..e7e191f15 100644 --- a/CHANGES +++ b/CHANGES @@ -56,7 +56,7 @@ Features added * helper function ``warning()`` for HTML themes is added * Add ``Domain.enumerable_nodes`` to manage own enumerable nodes for domains (experimental) -* Add ``Config.read()`` classmethod to create a new config object from +* Add ``Config.from_conf_py()`` classmethod to create a new config object from configuration file Bugs fixed diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 192f6891a..7ca0c6f2e 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -127,7 +127,7 @@ The following is a list of deprecated interface. ``Config.__init__()`` - 1.8 - 3.0 - - ``Config.read()`` + - ``Config.from_conf_py()`` * - ``BuildEnvironment._nitpick_ignore`` - 1.8 diff --git a/sphinx/application.py b/sphinx/application.py index eaa5f8ccd..d84301dd6 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -188,8 +188,8 @@ class Sphinx(object): if self.confdir is None: self.config = Config({}, confoverrides or {}) else: - self.config = Config.read(path.join(self.confdir, CONFIG_FILENAME), - confoverrides or {}, self.tags) + self.config = Config.from_conf_py(path.join(self.confdir, CONFIG_FILENAME), + confoverrides or {}, self.tags) check_unicode(self.config) # initialize some limited config variables before initialize i18n and loading diff --git a/sphinx/config.py b/sphinx/config.py index d1ef82ee4..33f09a85e 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -152,7 +152,7 @@ class Config(object): if len(args) == 4: # old style arguments: (dirname, filename, overrides, tags) warnings.warn('The argument of Config() class has been changed. ' - 'Use Config.read() to read configuration from conf.py.', + 'Use Config.from_conf_py() to read configuration from conf.py.', RemovedInSphinx30Warning) dirname, filename, overrides, tags = args if dirname is None: @@ -181,7 +181,7 @@ class Config(object): self.extensions = config.get('extensions', []) # type: List[unicode] @classmethod - def read(cls, filename, overrides=None, tags=None): + def from_conf_py(cls, filename, overrides=None, tags=None): # type: (unicode, Dict, Tags) -> Config """Create a Config object from configuration file.""" namespace = eval_config_file(filename, tags) diff --git a/tests/test_config.py b/tests/test_config.py index 7f27e3ff0..078a05a2a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -124,14 +124,14 @@ def test_errors_warnings(logger, tempdir): # test the error for syntax errors in the config file (tempdir / 'conf.py').write_text(u'project = \n', encoding='ascii') with pytest.raises(ConfigError) as excinfo: - Config.read(tempdir / 'conf.py', {}, None) + Config.from_conf_py(tempdir / 'conf.py', {}, None) assert 'conf.py' in str(excinfo.value) # test the automatic conversion of 2.x only code in configs (tempdir / 'conf.py').write_text( u'# -*- coding: utf-8\n\nproject = u"Jägermeister"\n', encoding='utf-8') - cfg = Config.read(tempdir / 'conf.py', {}, None) + cfg = Config.from_conf_py(tempdir / 'conf.py', {}, None) cfg.init_values() assert cfg.project == u'Jägermeister' assert logger.called is False @@ -143,7 +143,7 @@ def test_errors_warnings(logger, tempdir): return (tempdir / 'conf.py').write_text( u'# -*- coding: latin-1\nproject = "fooä"\n', encoding='latin-1') - cfg = Config.read(tempdir / 'conf.py', {}, None) + cfg = Config.from_conf_py(tempdir / 'conf.py', {}, None) assert logger.warning.called is False cfg.check_unicode() @@ -202,7 +202,7 @@ def test_config_eol(logger, tempdir): configfile = tempdir / 'conf.py' for eol in (b'\n', b'\r\n'): configfile.write_bytes(b'project = "spam"' + eol) - cfg = Config.read(tempdir / 'conf.py', {}, None) + cfg = Config.from_conf_py(tempdir / 'conf.py', {}, None) cfg.init_values() assert cfg.project == u'spam' assert logger.called is False From 5ab403bf3661345e09a8a772cfa218363ae357a5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 30 Mar 2018 23:57:39 +0900 Subject: [PATCH 15/68] Define priority for transforms --- sphinx/application.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/sphinx/application.py b/sphinx/application.py index d6d66c925..1a59c5a60 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -968,7 +968,30 @@ class Sphinx(object): Add the standard docutils :class:`Transform` subclass *transform* to the list of transforms that are applied after Sphinx parses a reST document. - """ + + .. list-table:: priority range categories for Sphinx transforms + + * - Priority + - Main purpose in Sphinx + * - 0-99 + - Fix invalid nodes by docutils. Translate a doctree. + * - 100-299 + - Preparation + * - 300-399 + - early + * - 400-699 + - main + * - 700-799 + - Post processing. Deadline to modify text and referencing. + * - 800-899 + - Collect referencing and referenced nodes. Domain processing. + * - 900-999 + - Finalize and clean up. + + refs: `Transform Priority Range Categories`__ + + __ http://docutils.sourceforge.net/docs/ref/transforms.html#transform-priority-range-categories + """ # NOQA self.registry.add_transform(transform) def add_post_transform(self, transform): From 390ea6d35a41d44b9b1d1b6fee4232ad6420ba38 Mon Sep 17 00:00:00 2001 From: Gussie Carther <> Date: Sat, 14 Apr 2018 17:51:06 +0900 Subject: [PATCH 16/68] Fix #4420: Docs conflict on use of rst_prolog and rst_epilog --- doc/config.rst | 15 ++++++++++++--- doc/faq.rst | 2 +- doc/usage/restructuredtext/basics.rst | 11 +++++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/doc/config.rst b/doc/config.rst index 46958cde6..1c8ae48e0 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -192,8 +192,9 @@ General configuration .. index:: pair: global; substitutions A string of reStructuredText that will be included at the end of every source - file that is read. This is the right place to add substitutions that should - be available in every file. An example:: + file that is read. This is a possible place to add substitutions that should + be available in every file (another being :confval:`rst_prolog`). An + example:: rst_epilog = """ .. |psf| replace:: Python Software Foundation @@ -203,8 +204,16 @@ General configuration .. confval:: rst_prolog + .. index:: pair: global; substitutions + A string of reStructuredText that will be included at the beginning of every - source file that is read. + source file that is read. This is a possible place to add substitutions that + should be available in every file (another being :confval:`rst_epilog`). An + example:: + + rst_prolog = """ + .. |psf| replace:: Python Software Foundation + """ .. versionadded:: 1.0 diff --git a/doc/faq.rst b/doc/faq.rst index 8798fd5db..270ceb68a 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -23,7 +23,7 @@ How do I... Use themes, see :doc:`theming`. ... add global substitutions or includes? - Add them in the :confval:`rst_epilog` config value. + Add them in the :confval:`rst_prolog` or :confval:`rst_epilog` config value. ... display the whole TOC tree in the sidebar? Use the :data:`toctree` callable in a custom layout template, probably in the diff --git a/doc/usage/restructuredtext/basics.rst b/doc/usage/restructuredtext/basics.rst index 9aa8e1ef7..48ffa5ffb 100644 --- a/doc/usage/restructuredtext/basics.rst +++ b/doc/usage/restructuredtext/basics.rst @@ -508,11 +508,14 @@ or this:: See the :duref:`reST reference for substitutions ` for details. +.. index:: ! pair: global; substitutions + If you want to use some substitutions for all documents, put them into -:confval:`rst_prolog` or put them into a separate file and include it into all -documents you want to use them in, using the :rst:dir:`include` directive. (Be -sure to give the include file a file name extension differing from that of -other source files, to avoid Sphinx finding it as a standalone document.) +:confval:`rst_prolog` or :confval:`rst_epilog` or put them into a separate file +and include it into all documents you want to use them in, using the +:rst:dir:`include` directive. (Be sure to give the include file a file name +extension differing from that of other source files, to avoid Sphinx finding it +as a standalone document.) Sphinx defines some default substitutions, see :ref:`default-substitutions`. From 0104cba2b8da1d2af6ea808ffb24fc82bcf6d7d9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 14 Apr 2018 18:22:31 +0900 Subject: [PATCH 17/68] Convert sphinx.builders.latex to package from module --- sphinx/builders/{latex.py => latex/__init__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sphinx/builders/{latex.py => latex/__init__.py} (100%) diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex/__init__.py similarity index 100% rename from sphinx/builders/latex.py rename to sphinx/builders/latex/__init__.py From 185987a51792b1d8dfa38b98beda07e0621faf53 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 14 Apr 2018 20:13:16 +0900 Subject: [PATCH 18/68] Move ShowUrlsTransform to sphinx.builders.latex.transforms --- sphinx/builders/latex/transforms.py | 125 ++++++++++++++++++++++++++++ sphinx/writers/latex.py | 111 +----------------------- 2 files changed, 127 insertions(+), 109 deletions(-) create mode 100644 sphinx/builders/latex/transforms.py diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py new file mode 100644 index 000000000..250036c06 --- /dev/null +++ b/sphinx/builders/latex/transforms.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +""" + sphinx.builders.latex.transforms + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Transforms for LaTeX builder. + + :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from docutils import nodes +from six import iteritems + +from sphinx import addnodes +from sphinx.transforms import SphinxTransform +from sphinx.util.nodes import traverse_parent + +URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:') + + +class ShowUrlsTransform(SphinxTransform, object): + def __init__(self, document, startnode=None): + # type: (nodes.document, nodes.Node) -> None + super(ShowUrlsTransform, self).__init__(document, startnode) + self.expanded = False + + def apply(self): + # type: () -> None + # replace id_prefix temporarily + id_prefix = self.document.settings.id_prefix + self.document.settings.id_prefix = 'show_urls' + + self.expand_show_urls() + if self.expanded: + self.renumber_footnotes() + + # restore id_prefix + self.document.settings.id_prefix = id_prefix + + def expand_show_urls(self): + # type: () -> None + show_urls = self.document.settings.env.config.latex_show_urls + if show_urls is False or show_urls == 'no': + return + + for node in self.document.traverse(nodes.reference): + uri = node.get('refuri', '') + if uri.startswith(URI_SCHEMES): + if uri.startswith('mailto:'): + uri = uri[7:] + if node.astext() != uri: + index = node.parent.index(node) + if show_urls == 'footnote': + footnote_nodes = self.create_footnote(uri) + for i, fn in enumerate(footnote_nodes): + node.parent.insert(index + i + 1, fn) + + self.expanded = True + else: # all other true values (b/w compat) + textnode = nodes.Text(" (%s)" % uri) + node.parent.insert(index + 1, textnode) + + def create_footnote(self, uri): + # type: (unicode) -> List[Union[nodes.footnote, nodes.footnote_ref]] + label = nodes.label('', '#') + para = nodes.paragraph() + para.append(nodes.reference('', nodes.Text(uri), refuri=uri, nolinkurl=True)) + footnote = nodes.footnote(uri, label, para, auto=1) + footnote['names'].append('#') + self.document.note_autofootnote(footnote) + + label = nodes.Text('#') + footnote_ref = nodes.footnote_reference('[#]_', label, auto=1, + refid=footnote['ids'][0]) + self.document.note_autofootnote_ref(footnote_ref) + footnote.add_backref(footnote_ref['ids'][0]) + + return [footnote, footnote_ref] + + def renumber_footnotes(self): + # type: () -> None + def is_used_number(number): + # type: (unicode) -> bool + for node in self.document.traverse(nodes.footnote): + if not node.get('auto') and number in node['names']: + return True + + return False + + def is_auto_footnote(node): + # type: (nodes.Node) -> bool + return isinstance(node, nodes.footnote) and node.get('auto') + + def footnote_ref_by(node): + # type: (nodes.Node) -> Callable[[nodes.Node], bool] + ids = node['ids'] + parent = list(traverse_parent(node, (nodes.document, addnodes.start_of_file)))[0] + + def is_footnote_ref(node): + # type: (nodes.Node) -> bool + return (isinstance(node, nodes.footnote_reference) and + ids[0] == node['refid'] and + parent in list(traverse_parent(node))) + + return is_footnote_ref + + startnum = 1 + for footnote in self.document.traverse(is_auto_footnote): + while True: + label = str(startnum) + startnum += 1 + if not is_used_number(label): + break + + old_label = footnote[0].astext() + footnote.remove(footnote[0]) + footnote.insert(0, nodes.label('', label)) + if old_label in footnote['names']: + footnote['names'].remove(old_label) + footnote['names'].append(label) + + for footnote_ref in self.document.traverse(footnote_ref_by(footnote)): + footnote_ref.remove(footnote_ref[0]) + footnote_ref += nodes.Text(label) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 0fecd7b8a..24c37b96a 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -23,12 +23,12 @@ from six import itervalues, text_type from sphinx import addnodes from sphinx import highlighting +from sphinx.builders.latex.transforms import URI_SCHEMES, ShowUrlsTransform # NOQA # for compatibility from sphinx.errors import SphinxError from sphinx.locale import admonitionlabels, _ -from sphinx.transforms import SphinxTransform from sphinx.util import split_into, logging from sphinx.util.i18n import format_date -from sphinx.util.nodes import clean_astext, traverse_parent +from sphinx.util.nodes import clean_astext from sphinx.util.template import LaTeXRenderer from sphinx.util.texescape import tex_escape_map, tex_replace_map @@ -47,7 +47,6 @@ BEGIN_DOC = r''' ''' -URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:') LATEXSECTIONNAMES = ["part", "chapter", "section", "subsection", "subsubsection", "paragraph", "subparagraph"] @@ -224,112 +223,6 @@ class ExtBabel(Babel): return language -class ShowUrlsTransform(SphinxTransform, object): - def __init__(self, document, startnode=None): - # type: (nodes.document, nodes.Node) -> None - super(ShowUrlsTransform, self).__init__(document, startnode) - self.expanded = False - - def apply(self): - # type: () -> None - # replace id_prefix temporarily - id_prefix = self.document.settings.id_prefix - self.document.settings.id_prefix = 'show_urls' - - self.expand_show_urls() - if self.expanded: - self.renumber_footnotes() - - # restore id_prefix - self.document.settings.id_prefix = id_prefix - - def expand_show_urls(self): - # type: () -> None - show_urls = self.document.settings.env.config.latex_show_urls - if show_urls is False or show_urls == 'no': - return - - for node in self.document.traverse(nodes.reference): - uri = node.get('refuri', '') - if uri.startswith(URI_SCHEMES): - if uri.startswith('mailto:'): - uri = uri[7:] - if node.astext() != uri: - index = node.parent.index(node) - if show_urls == 'footnote': - footnote_nodes = self.create_footnote(uri) - for i, fn in enumerate(footnote_nodes): - node.parent.insert(index + i + 1, fn) - - self.expanded = True - else: # all other true values (b/w compat) - textnode = nodes.Text(" (%s)" % uri) - node.parent.insert(index + 1, textnode) - - def create_footnote(self, uri): - # type: (unicode) -> List[Union[nodes.footnote, nodes.footnote_ref]] - label = nodes.label('', '#') - para = nodes.paragraph() - para.append(nodes.reference('', nodes.Text(uri), refuri=uri, nolinkurl=True)) - footnote = nodes.footnote(uri, label, para, auto=1) - footnote['names'].append('#') - self.document.note_autofootnote(footnote) - - label = nodes.Text('#') - footnote_ref = nodes.footnote_reference('[#]_', label, auto=1, - refid=footnote['ids'][0]) - self.document.note_autofootnote_ref(footnote_ref) - footnote.add_backref(footnote_ref['ids'][0]) - - return [footnote, footnote_ref] - - def renumber_footnotes(self): - # type: () -> None - def is_used_number(number): - # type: (unicode) -> bool - for node in self.document.traverse(nodes.footnote): - if not node.get('auto') and number in node['names']: - return True - - return False - - def is_auto_footnote(node): - # type: (nodes.Node) -> bool - return isinstance(node, nodes.footnote) and node.get('auto') - - def footnote_ref_by(node): - # type: (nodes.Node) -> Callable[[nodes.Node], bool] - ids = node['ids'] - parent = list(traverse_parent(node, (nodes.document, addnodes.start_of_file)))[0] - - def is_footnote_ref(node): - # type: (nodes.Node) -> bool - return (isinstance(node, nodes.footnote_reference) and - ids[0] == node['refid'] and - parent in list(traverse_parent(node))) - - return is_footnote_ref - - startnum = 1 - for footnote in self.document.traverse(is_auto_footnote): - while True: - label = str(startnum) - startnum += 1 - if not is_used_number(label): - break - - old_label = footnote[0].astext() - footnote.remove(footnote[0]) - footnote.insert(0, nodes.label('', label)) - if old_label in footnote['names']: - footnote['names'].remove(old_label) - footnote['names'].append(label) - - for footnote_ref in self.document.traverse(footnote_ref_by(footnote)): - footnote_ref.remove(footnote_ref[0]) - footnote_ref += nodes.Text(label) - - class Table(object): """A table data""" From 36a8781688e4999f34d96ce44b2b15452c3a59a6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 14 Apr 2018 20:46:01 +0900 Subject: [PATCH 19/68] Fix #4803: latex: too slow in proportion to number of auto numbered footnotes --- CHANGES | 1 + sphinx/builders/latex/transforms.py | 122 ++++++++++++++++++---------- 2 files changed, 80 insertions(+), 43 deletions(-) diff --git a/CHANGES b/CHANGES index 93377cd20..9269d035c 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Bugs fixed * #4817: wrong URLs on warning messages * #4784: latex: :confval:`latex_show_urls` assigns incorrect footnote numbers if hyperlinks exists inside substitutions +* #4803: latex: too slow in proportion to number of auto numbered footnotes Testing -------- diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py index 250036c06..201129989 100644 --- a/sphinx/builders/latex/transforms.py +++ b/sphinx/builders/latex/transforms.py @@ -10,20 +10,25 @@ """ from docutils import nodes -from six import iteritems -from sphinx import addnodes from sphinx.transforms import SphinxTransform -from sphinx.util.nodes import traverse_parent + +if False: + # For type annotation + from typing import Dict, List, Set, Union # NOQA URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:') -class ShowUrlsTransform(SphinxTransform, object): - def __init__(self, document, startnode=None): - # type: (nodes.document, nodes.Node) -> None - super(ShowUrlsTransform, self).__init__(document, startnode) - self.expanded = False +class ShowUrlsTransform(SphinxTransform): + """Expand references to inline text or footnotes. + + For more information, see :confval:`latex_show_urls`. + """ + default_priority = 400 + + # references are expanded to footnotes (or not) + expanded = False def apply(self): # type: () -> None @@ -80,46 +85,77 @@ class ShowUrlsTransform(SphinxTransform, object): def renumber_footnotes(self): # type: () -> None - def is_used_number(number): - # type: (unicode) -> bool - for node in self.document.traverse(nodes.footnote): - if not node.get('auto') and number in node['names']: - return True + collector = FootnoteCollector(self.document) + self.document.walkabout(collector) - return False - - def is_auto_footnote(node): - # type: (nodes.Node) -> bool - return isinstance(node, nodes.footnote) and node.get('auto') - - def footnote_ref_by(node): - # type: (nodes.Node) -> Callable[[nodes.Node], bool] - ids = node['ids'] - parent = list(traverse_parent(node, (nodes.document, addnodes.start_of_file)))[0] - - def is_footnote_ref(node): - # type: (nodes.Node) -> bool - return (isinstance(node, nodes.footnote_reference) and - ids[0] == node['refid'] and - parent in list(traverse_parent(node))) - - return is_footnote_ref - - startnum = 1 - for footnote in self.document.traverse(is_auto_footnote): + num = 0 + for document, footnote in collector.auto_footnotes: + # search unused footnote number while True: - label = str(startnum) - startnum += 1 - if not is_used_number(label): + num += 1 + if str(num) not in collector.used_footnote_numbers: break + # assign new footnote number old_label = footnote[0].astext() - footnote.remove(footnote[0]) - footnote.insert(0, nodes.label('', label)) + footnote[0].replace_self(nodes.label('', str(num))) if old_label in footnote['names']: footnote['names'].remove(old_label) - footnote['names'].append(label) + footnote['names'].append(str(num)) - for footnote_ref in self.document.traverse(footnote_ref_by(footnote)): - footnote_ref.remove(footnote_ref[0]) - footnote_ref += nodes.Text(label) + # update footnote_references by new footnote number + for ref in collector.footnote_refs.get(document, []): + if footnote['ids'][0] == ref['refid']: + ref.remove(ref[0]) + ref += nodes.Text(str(num)) + + +class FootnoteCollector(nodes.NodeVisitor): + """Collect footnotes and footnote references on the document""" + + def __init__(self, document): + # type: (nodes.document) -> None + self.auto_footnotes = [] # type: List[nodes.footnote] + self.used_footnote_numbers = set() # type: Set[unicode] + self.footnote_refs = {} # type: Dict[nodes.Node, List[nodes.footnote_reference]] # NOQA + self.current_document = [] # type: List[nodes.Node] + nodes.NodeVisitor.__init__(self, document) + + def visit_document(self, node): + # type: (nodes.Node) -> None + self.current_document.append(node) + + def depart_document(self, node): + # type: (nodes.Node) -> None + self.current_document.pop() + + def visit_start_of_file(self, node): + # type: (nodes.Node) -> None + self.current_document.append(node) + + def depart_start_of_file(self, node): + # type: (nodes.Node) -> None + self.current_document.pop() + + def unknown_visit(self, node): + # type: (nodes.Node) -> None + pass + + def visit_footnote(self, node): + # type: (nodes.footnote) -> None + document = self.current_document[-1] + if node.get('auto'): + self.auto_footnotes.append((document, node)) + else: + for name in node['names']: + self.used_footnote_numbers.add(name) + + def visit_footnote_reference(self, node): + # type: (nodes.footnote_reference) -> None + document = self.current_document[-1] + footnote_refs = self.footnote_refs.setdefault(document, []) + footnote_refs.append(node) + + def unknown_departure(self, node): + # type: (nodes.Node) -> None + pass From 5a887b1075afe3eb180a786d8a36c600a5857ac0 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 14 Apr 2018 18:15:26 +0900 Subject: [PATCH 20/68] Fix #4828: Allow to override numfig_format partially --- CHANGES | 2 ++ sphinx/config.py | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index d07d4f132..36aedd0ef 100644 --- a/CHANGES +++ b/CHANGES @@ -62,6 +62,8 @@ Features added * Add :confval:`html_css_files` and :confval:`epub_css_files` for adding CSS files from configuration * #4834: Ensure set object descriptions are reproducible. +* #4828: Allow to override :confval:`numfig_format` partially. Full definition + is not needed. Bugs fixed ---------- diff --git a/sphinx/config.py b/sphinx/config.py index df4a8f184..6d965904c 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -144,11 +144,7 @@ class Config(object): nitpick_ignore = ([], None), numfig = (False, 'env'), numfig_secnum_depth = (1, 'env'), - numfig_format = ({'section': _('Section %s'), - 'figure': _('Fig. %s'), - 'table': _('Table %s'), - 'code-block': _('Listing %s')}, - 'env'), + numfig_format = ({}, 'env'), # will be initialized in init_numfig_format() tls_verify = (True, 'env'), tls_cacerts = (None, 'env'), @@ -391,9 +387,23 @@ def convert_source_suffix(app, config): "But `%r' is given." % source_suffix)) +def init_numfig_format(app, config): + # type: (Sphinx, Config) -> None + """Initialize :confval:`numfig_format`.""" + numfig_format = {'section': _('Section %s'), + 'figure': _('Fig. %s'), + 'table': _('Table %s'), + 'code-block': _('Listing %s')} + + # override default labels by configuration + numfig_format.update(config.numfig_format) + config.numfig_format = numfig_format # type: ignore + + def setup(app): # type: (Sphinx) -> Dict[unicode, Any] app.connect('config-inited', convert_source_suffix) + app.connect('config-inited', init_numfig_format) return { 'version': 'builtin', From b1a0e8062fa059a4be6a8ba2a0ebe7372825e590 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 14 Apr 2018 22:36:24 +0900 Subject: [PATCH 21/68] Fix #4767: html: search highlighting breaks mathjax equations --- CHANGES | 1 + sphinx/ext/jsmath.py | 6 +++--- sphinx/ext/mathjax.py | 4 ++-- sphinx/themes/basic/static/doctools.js_t | 4 +++- tests/test_ext_math.py | 22 +++++++++++----------- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 93377cd20..1f78e2139 100644 --- a/CHANGES +++ b/CHANGES @@ -60,6 +60,7 @@ Bugs fixed * #1435: qthelp builder should htmlescape keywords * epub: Fix docTitle elements of toc.ncx is not escaped * #4520: apidoc: Subpackage not in toc (introduced in 1.6.6) now fixed +* #4767: html: search highlighting breaks mathjax equations Release 1.7.1 (released Feb 23, 2018) ===================================== diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py index 05d5dc290..5d8ec876e 100644 --- a/sphinx/ext/jsmath.py +++ b/sphinx/ext/jsmath.py @@ -20,14 +20,14 @@ from sphinx.locale import _ def html_visit_math(self, node): - self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate')) + self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight')) self.body.append(self.encode(node['latex']) + '') raise nodes.SkipNode def html_visit_displaymath(self, node): if node['nowrap']: - self.body.append(self.starttag(node, 'div', CLASS='math notranslate')) + self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight')) self.body.append(self.encode(node['latex'])) self.body.append('') raise nodes.SkipNode @@ -40,7 +40,7 @@ def html_visit_displaymath(self, node): self.body.append('(%s)' % number) self.add_permalink_ref(node, _('Permalink to this equation')) self.body.append('') - self.body.append(self.starttag(node, 'div', CLASS='math notranslate')) + self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight')) else: # but only once! self.body.append('
') diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py index 6c5d0a642..bb9964fa8 100644 --- a/sphinx/ext/mathjax.py +++ b/sphinx/ext/mathjax.py @@ -21,7 +21,7 @@ from sphinx.locale import _ def html_visit_math(self, node): - self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate')) + self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight')) self.body.append(self.builder.config.mathjax_inline[0] + self.encode(node['latex']) + self.builder.config.mathjax_inline[1] + '') @@ -29,7 +29,7 @@ def html_visit_math(self, node): def html_visit_displaymath(self, node): - self.body.append(self.starttag(node, 'div', CLASS='math notranslate')) + self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight')) if node['nowrap']: self.body.append(self.encode(node['latex'])) self.body.append('
') diff --git a/sphinx/themes/basic/static/doctools.js_t b/sphinx/themes/basic/static/doctools.js_t index b261a44f3..1dca47404 100644 --- a/sphinx/themes/basic/static/doctools.js_t +++ b/sphinx/themes/basic/static/doctools.js_t @@ -70,7 +70,9 @@ jQuery.fn.highlightText = function(text, className) { if (node.nodeType === 3) { var val = node.nodeValue; var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { var span; var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); if (isInSVG) { diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py index 755a9e955..28ce094a8 100644 --- a/tests/test_ext_math.py +++ b/tests/test_ext_math.py @@ -35,19 +35,19 @@ def test_jsmath(app, status, warning): app.builder.build_all() content = (app.outdir / 'math.html').text() - assert '
\na^2 + b^2 = c^2
' in content - assert ('
\n\\begin{split}a + 1 < b\\end{split}
' - in content) + assert '
\na^2 + b^2 = c^2
' in content + assert ('
\n\\begin{split}a + 1 < ' + 'b\\end{split}
' in content) assert (u'(1)\xb6' - u'
\ne^{i\\pi} = 1
' - in content) + u'
' + '\ne^{i\\pi} = 1
' in content) assert (u'(2)\xb6' - u'
\n' + u'
\n' u'e^{ix} = \\cos x + i\\sin x
' in content) - assert '
\nn \\in \\mathbb N
' in content - assert '
\na + 1 < b
' in content + assert '
\nn \\in \\mathbb N
' in content + assert '
\na + 1 < b
' in content @pytest.mark.skipif(not has_binary('dvipng'), @@ -91,7 +91,7 @@ def test_mathjax_align(app, status, warning): app.builder.build_all() content = (app.outdir / 'index.html').text() - html = (r'
\s*' + html = (r'
\s*' r'\\\[ \\begin\{align\}\\begin\{aligned\}S \&= \\pi r\^2\\\\' r'V \&= \\frac\{4\}\{3\} \\pi r\^3\\end\{aligned\}\\end\{align\} \\\]
') assert re.search(html, content, re.S) @@ -104,7 +104,7 @@ def test_math_number_all_mathjax(app, status, warning): app.builder.build_all() content = (app.outdir / 'index.html').text() - html = (r'
\s*' + html = (r'
\s*' r'\(1\)\xb6\\\[a\^2\+b\^2=c\^2\\\]
') assert re.search(html, content, re.S) @@ -169,7 +169,7 @@ def test_mathjax_numfig_html(app, status, warning): app.builder.build_all() content = (app.outdir / 'math.html').text() - html = ('
\n' + html = ('
\n' '(1.2)') assert html in content html = ('

Referencing equation Date: Sat, 14 Apr 2018 18:47:11 +0200 Subject: [PATCH 22/68] Fix #4837: (latex with class memoir) Font command \sf is not supported --- CHANGES | 1 + sphinx/writers/latex.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 93377cd20..6962814e4 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Bugs fixed * #4817: wrong URLs on warning messages * #4784: latex: :confval:`latex_show_urls` assigns incorrect footnote numbers if hyperlinks exists inside substitutions +* #4837: latex: Class memoir Error Font command ``\sf`` is not supported Testing -------- diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 0fecd7b8a..aca5d91ab 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -615,7 +615,9 @@ class LaTeXTranslator(nodes.NodeVisitor): if builder.config.language \ and 'fncychap' not in builder.config.latex_elements: # use Sonny style if any language specified - self.elements['fncychap'] = '\\usepackage[Sonny]{fncychap}' + self.elements['fncychap'] = ('\\usepackage[Sonny]{fncychap}' + '\\ChNameVar{\\Large\\sffamily}' + '\\ChTitleVar{\\Large\\sffamily}') self.babel = ExtBabel(builder.config.language) if builder.config.language and not self.babel.is_supported_language(): From 765aec565d667572d336d2a4035473cd19775f4b Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 15 Apr 2018 02:17:36 +0900 Subject: [PATCH 23/68] Change priority of SphinxSmartQuotes --- CHANGES | 2 ++ sphinx/transforms/__init__.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index d07d4f132..bc1072b9f 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,8 @@ Incompatible changes a ``conf.py`` file sphinx-build generates. * The ``gettext_compact`` attribute is removed from ``document.settings`` object. Please use ``config.gettext_compact`` instead. +* The order of smart_quotes is changed. For more detail, please read a + description of :py:meth:`.Sphinx.add_transform()` Deprecated ---------- diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index b32b317bb..4f3d15752 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -343,6 +343,8 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform): refs: sphinx.parsers.RSTParser """ + default_priority = 750 + def apply(self): # type: () -> None if not self.is_available(): From 5e4da90ffa382f16d504f698e43467690d536379 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 15 Apr 2018 02:34:26 +0900 Subject: [PATCH 24/68] Change priority of sphinx-domains --- CHANGES | 4 ++-- sphinx/environment/__init__.py | 6 +----- sphinx/io.py | 4 +++- sphinx/transforms/references.py | 11 +++++++++++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index bc1072b9f..642325847 100644 --- a/CHANGES +++ b/CHANGES @@ -17,8 +17,8 @@ Incompatible changes a ``conf.py`` file sphinx-build generates. * The ``gettext_compact`` attribute is removed from ``document.settings`` object. Please use ``config.gettext_compact`` instead. -* The order of smart_quotes is changed. For more detail, please read a - description of :py:meth:`.Sphinx.add_transform()` +* The processing order of smart_quotes and sphinx domains are changed. For more + details, please read a description of :py:meth:`.Sphinx.add_transform()` Deprecated ---------- diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index b69a5fd32..af76c7ab1 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -21,7 +21,7 @@ from os import path from docutils.frontend import OptionParser from docutils.utils import Reporter, get_source_line -from six import BytesIO, itervalues, class_types, next +from six import BytesIO, class_types, next from six.moves import cPickle as pickle from sphinx import addnodes, versioning @@ -570,10 +570,6 @@ class BuildEnvironment(object): with sphinx_domains(self), rst.default_role(docname, self.config.default_role): doctree = read_doc(self.app, self, self.doc2path(docname)) - # post-processing - for domain in itervalues(self.domains): - domain.process_doc(self, docname, doctree) - # allow extension-specific post-processing if app: app.emit('doctree-read', doctree) diff --git a/sphinx/io.py b/sphinx/io.py index 826da1812..d9b28bed3 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -31,6 +31,7 @@ from sphinx.transforms.compact_bullet_list import RefOnlyBulletListTransform from sphinx.transforms.i18n import ( PreserveTranslatableMessages, Locale, RemoveTranslatableInline, ) +from sphinx.transforms.references import SphinxDomains from sphinx.util import logging from sphinx.util.docutils import LoggingReporter @@ -93,7 +94,8 @@ class SphinxStandaloneReader(SphinxBaseReader): Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, RemoveTranslatableInline, FilterSystemMessages, RefOnlyBulletListTransform, - UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink + UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink, + SphinxDomains, ] # type: List[Transform] def __init__(self, app, *args, **kwargs): diff --git a/sphinx/transforms/references.py b/sphinx/transforms/references.py index affe4012b..1d0fefdc9 100644 --- a/sphinx/transforms/references.py +++ b/sphinx/transforms/references.py @@ -11,6 +11,7 @@ from docutils import nodes from docutils.transforms.references import Substitutions +from six import itervalues from sphinx.transforms import SphinxTransform @@ -28,3 +29,13 @@ class SubstitutionDefinitionsRemover(SphinxTransform): # type: () -> None for node in self.document.traverse(nodes.substitution_definition): node.parent.remove(node) + + +class SphinxDomains(SphinxTransform): + """Collect objects to Sphinx domains for cross references.""" + default_priority = 850 + + def apply(self): + # type: () -> None + for domain in itervalues(self.env.domains): + domain.process_doc(self, self.env.docname, self.document) From af58d7faed65bd6af6a29f6042c6d358e256ea2d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 14 Apr 2018 16:03:30 +0900 Subject: [PATCH 25/68] Remove substitution_definition nodes from doctree on reading phase (refs: #4827) --- CHANGES | 2 ++ sphinx/builders/latex.py | 2 -- sphinx/io.py | 7 +++++-- sphinx/transforms/references.py | 5 +---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index d07d4f132..1dbdc0f7a 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,8 @@ Incompatible changes a ``conf.py`` file sphinx-build generates. * The ``gettext_compact`` attribute is removed from ``document.settings`` object. Please use ``config.gettext_compact`` instead. +* #4827: All ``substitution_definition`` nodes are removed from doctree on + reading phase Deprecated ---------- diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex.py index d0357c6a0..002efcbd3 100644 --- a/sphinx/builders/latex.py +++ b/sphinx/builders/latex.py @@ -25,7 +25,6 @@ from sphinx.environment.adapters.asset import ImageAdapter from sphinx.errors import SphinxError, ConfigError from sphinx.locale import _, __ from sphinx.transforms import SphinxTransformer -from sphinx.transforms.references import SubstitutionDefinitionsRemover from sphinx.util import texescape, logging, status_iterator from sphinx.util.console import bold, darkgreen # type: ignore from sphinx.util.docutils import new_document @@ -217,7 +216,6 @@ class LaTeXBuilder(Builder): # type: (nodes.document) -> None transformer = SphinxTransformer(doctree) transformer.set_environment(self.env) - transformer.add_transforms([SubstitutionDefinitionsRemover]) transformer.apply_transforms() def finish(self): diff --git a/sphinx/io.py b/sphinx/io.py index 826da1812..fb34615ad 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -31,6 +31,7 @@ from sphinx.transforms.compact_bullet_list import RefOnlyBulletListTransform from sphinx.transforms.i18n import ( PreserveTranslatableMessages, Locale, RemoveTranslatableInline, ) +from sphinx.transforms.references import SubstitutionDefinitionsRemover from sphinx.util import logging from sphinx.util.docutils import LoggingReporter @@ -93,7 +94,8 @@ class SphinxStandaloneReader(SphinxBaseReader): Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, RemoveTranslatableInline, FilterSystemMessages, RefOnlyBulletListTransform, - UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink + UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink, + SubstitutionDefinitionsRemover, ] # type: List[Transform] def __init__(self, app, *args, **kwargs): @@ -116,7 +118,8 @@ class SphinxI18nReader(SphinxBaseReader): DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, AutoNumbering, SortIds, RemoveTranslatableInline, FilterSystemMessages, RefOnlyBulletListTransform, - UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink] + UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink, + SubstitutionDefinitionsRemover] def set_lineno_for_reporter(self, lineno): # type: (int) -> None diff --git a/sphinx/transforms/references.py b/sphinx/transforms/references.py index affe4012b..2f4755db8 100644 --- a/sphinx/transforms/references.py +++ b/sphinx/transforms/references.py @@ -16,10 +16,7 @@ from sphinx.transforms import SphinxTransform class SubstitutionDefinitionsRemover(SphinxTransform): - """Remove ``substitution_definition node from doctrees. - - .. note:: In Sphinx-1.7, this transform is only used in LaTeX builder. - """ + """Remove ``substitution_definition node from doctrees.""" # should be invoked after Substitutions process default_priority = Substitutions.default_priority + 1 From d8d1c7ac63bcc3113a1184715ad15a0040eb4bea Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 14 Apr 2018 16:07:03 +0900 Subject: [PATCH 26/68] Remove unused visitor methods --- sphinx/writers/latex.py | 8 -------- sphinx/writers/texinfo.py | 12 ------------ sphinx/writers/text.py | 4 ---- 3 files changed, 24 deletions(-) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 68acc8e9a..b9c70d56b 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -2502,14 +2502,6 @@ class LaTeXTranslator(nodes.NodeVisitor): # type: (nodes.Node) -> None self.body.append('}}$') - def visit_substitution_definition(self, node): - # type: (nodes.Node) -> None - raise nodes.SkipNode - - def visit_substitution_reference(self, node): - # type: (nodes.Node) -> None - raise nodes.SkipNode - def visit_inline(self, node): # type: (nodes.Node) -> None classes = node.get('classes', []) diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index c24ba60bd..e58d659ab 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -1385,18 +1385,6 @@ class TexinfoTranslator(nodes.NodeVisitor): # type: (nodes.Node) -> None pass - def visit_substitution_reference(self, node): - # type: (nodes.Node) -> None - pass - - def depart_substitution_reference(self, node): - # type: (nodes.Node) -> None - pass - - def visit_substitution_definition(self, node): - # type: (nodes.Node) -> None - raise nodes.SkipNode - def visit_system_message(self, node): # type: (nodes.Node) -> None self.body.append('\n@verbatim\n' diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 2bc43a30d..379f06b46 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -987,10 +987,6 @@ class TextTranslator(nodes.NodeVisitor): # type: (nodes.Node) -> None raise nodes.SkipNode - def visit_substitution_definition(self, node): - # type: (nodes.Node) -> None - raise nodes.SkipNode - def visit_pending_xref(self, node): # type: (nodes.Node) -> None pass From aa21d78e42ebbc993f4f5df66755a044a95d52c8 Mon Sep 17 00:00:00 2001 From: jfbu Date: Sat, 14 Apr 2018 19:48:24 +0200 Subject: [PATCH 27/68] Fix again #4837, using better equivalent to deprecated \sf --- CHANGES | 2 +- sphinx/writers/latex.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 6962814e4..fd1e8dcbe 100644 --- a/CHANGES +++ b/CHANGES @@ -26,7 +26,7 @@ Bugs fixed * #4817: wrong URLs on warning messages * #4784: latex: :confval:`latex_show_urls` assigns incorrect footnote numbers if hyperlinks exists inside substitutions -* #4837: latex: Class memoir Error Font command ``\sf`` is not supported +* #4837: latex with class memoir Error: Font command ``\sf`` is not supported Testing -------- diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index aca5d91ab..c742cdc1a 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -615,9 +615,10 @@ class LaTeXTranslator(nodes.NodeVisitor): if builder.config.language \ and 'fncychap' not in builder.config.latex_elements: # use Sonny style if any language specified - self.elements['fncychap'] = ('\\usepackage[Sonny]{fncychap}' - '\\ChNameVar{\\Large\\sffamily}' - '\\ChTitleVar{\\Large\\sffamily}') + self.elements['fncychap'] = ('\\usepackage[Sonny]{fncychap}\n' + '\\ChNameVar{\\Large\\normalfont' + '\\sffamily}\n\\ChTitleVar{\\Large' + '\\normalfont\\sffamily}') self.babel = ExtBabel(builder.config.language) if builder.config.language and not self.babel.is_supported_language(): From 36038f8196e6abf8902900bc5cd5ce7144da3771 Mon Sep 17 00:00:00 2001 From: "Bernhard M. Wiedemann" Date: Sat, 14 Apr 2018 06:45:35 +0200 Subject: [PATCH 28/68] Sort .hhp file list Without this change, the pgadmin3 openSUSE package differed for every build (happens in a disposable VM) because pgadmin3.hhp contained entries in indeterministic filesystem readdir order. See https://reproducible-builds.org/ for why this matters. --- sphinx/builders/htmlhelp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py index ac32a42db..8be51d50d 100644 --- a/sphinx/builders/htmlhelp.py +++ b/sphinx/builders/htmlhelp.py @@ -247,6 +247,8 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): outdir += os.sep olen = len(outdir) for root, dirs, files in os.walk(outdir): + dirs.sort() + files.sort() staticdir = root.startswith(path.join(outdir, '_static')) for fn in sorted(files): if (staticdir and not fn.endswith('.js')) or \ From adf5e9fda646cd93842a04fb6be730ea5f3193a2 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 15 Apr 2018 13:37:57 +0900 Subject: [PATCH 29/68] Update CHANGES for PR #4838 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index e27a94ff6..264a024e5 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Bugs fixed hyperlinks exists inside substitutions * #4837: latex with class memoir Error: Font command ``\sf`` is not supported * #4803: latex: too slow in proportion to number of auto numbered footnotes +* #4838: htmlhelp: The entries in .hhp file is not ordered Testing -------- From 9826d49cbd5d5c45cda4a8fca5a9a5130773b531 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 7 Mar 2018 21:49:54 +0900 Subject: [PATCH 30/68] Add testcase for toctree directive --- tests/test_directive_other.py | 138 ++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 tests/test_directive_other.py diff --git a/tests/test_directive_other.py b/tests/test_directive_other.py new file mode 100644 index 000000000..9a11a6d4f --- /dev/null +++ b/tests/test_directive_other.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +""" + test_directive_other + ~~~~~~~~~~~~~~~~~~~~ + + Test the other directives. + + :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import pytest +from docutils import nodes +from docutils.core import publish_doctree + +from sphinx import addnodes +from sphinx.io import SphinxStandaloneReader +from sphinx.parsers import RSTParser +from sphinx.testing.util import assert_node + + +def parse(app, docname, text): + app.env.temp_data['docname'] = docname + return publish_doctree(text, app.srcdir / docname + '.rst', + reader=SphinxStandaloneReader(app), + parser=RSTParser(), + settings_overrides={'env': app.env, + 'gettext_compact': True}) + + +@pytest.mark.sphinx(testroot='toctree-glob') +def test_toctree(app): + text = (".. toctree::\n" + "\n" + " foo\n" + " bar/index\n" + " baz\n") + + app.env.find_files(app.config, app.builder) + doctree = parse(app, 'index', text) + assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree]) + assert_node(doctree[0][0], + entries=[(None, 'foo'), (None, 'bar/index'), (None, 'baz')], + includefiles=['foo', 'bar/index', 'baz']) + + +@pytest.mark.sphinx(testroot='toctree-glob') +def test_relative_toctree(app): + text = (".. toctree::\n" + "\n" + " bar_1\n" + " bar_2\n" + " bar_3\n" + " ../quux\n") + + app.env.find_files(app.config, app.builder) + doctree = parse(app, 'bar/index', text) + assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree]) + assert_node(doctree[0][0], + entries=[(None, 'bar/bar_1'), (None, 'bar/bar_2'), (None, 'bar/bar_3'), + (None, 'quux')], + includefiles=['bar/bar_1', 'bar/bar_2', 'bar/bar_3', 'quux']) + + +@pytest.mark.sphinx(testroot='toctree-glob') +def test_toctree_urls_and_titles(app): + text = (".. toctree::\n" + "\n" + " Sphinx \n" + " https://readthedocs.org/\n" + " The BAR \n") + + app.env.find_files(app.config, app.builder) + doctree = parse(app, 'index', text) + assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree]) + assert_node(doctree[0][0], + entries=[('Sphinx', 'https://www.sphinx-doc.org/'), + (None, 'https://readthedocs.org/'), + ('The BAR', 'bar/index')], + includefiles=['bar/index']) + + +@pytest.mark.sphinx(testroot='toctree-glob') +def test_toctree_glob(app): + text = (".. toctree::\n" + " :glob:\n" + "\n" + " *\n") + + app.env.find_files(app.config, app.builder) + doctree = parse(app, 'index', text) + assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree]) + assert_node(doctree[0][0], + entries=[(None, 'baz'), (None, 'foo'), (None, 'quux')], + includefiles=['baz', 'foo', 'quux']) + + # give both docname and glob (case1) + text = (".. toctree::\n" + " :glob:\n" + "\n" + " foo\n" + " *\n") + + app.env.find_files(app.config, app.builder) + doctree = parse(app, 'index', text) + assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree]) + assert_node(doctree[0][0], + entries=[(None, 'foo'), (None, 'baz'), (None, 'quux')], + includefiles=['foo', 'baz', 'quux']) + + # give both docname and glob (case2) + text = (".. toctree::\n" + " :glob:\n" + "\n" + " *\n" + " foo\n") + + app.env.find_files(app.config, app.builder) + doctree = parse(app, 'index', text) + assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree]) + assert_node(doctree[0][0], + entries=[(None, 'baz'), (None, 'foo'), (None, 'quux'), (None, 'foo')], + includefiles=['baz', 'foo', 'quux', 'foo']) + + +@pytest.mark.sphinx(testroot='toctree-glob') +def test_toctree_twice(app): + text = (".. toctree::\n" + "\n" + " foo\n" + " foo\n") + + app.env.find_files(app.config, app.builder) + doctree = parse(app, 'index', text) + assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree]) + assert_node(doctree[0][0], + entries=[(None, 'foo'), (None, 'foo')], + includefiles=['foo', 'foo']) From 32fe3a05e8f38468851171c119c955139226491f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 7 Mar 2018 22:53:31 +0900 Subject: [PATCH 31/68] Refactor toctree directive --- sphinx/directives/other.py | 74 +++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 6aa8889af..1800d1853 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -7,6 +7,8 @@ :license: BSD, see LICENSE for details. """ +import re + from docutils import nodes from docutils.parsers.rst import Directive, directives from docutils.parsers.rst.directives.admonitions import BaseAdmonition @@ -27,6 +29,9 @@ if False: from sphinx.application import Sphinx # NOQA +glob_re = re.compile('.*[*?\[].*') + + def int_or_nothing(argument): # type: (unicode) -> int if not argument: @@ -57,30 +62,50 @@ class TocTree(Directive): def run(self): # type: () -> List[nodes.Node] - env = self.state.document.settings.env - suffixes = env.config.source_suffix - glob = 'glob' in self.options + subnode = addnodes.toctree() + subnode['parent'] = self.state.document.settings.env.docname - ret = [] # (title, ref) pairs, where ref may be a document, or an external link, # and title may be None if the document's title is to be used - entries = [] # type: List[Tuple[unicode, unicode]] - includefiles = [] + subnode['entries'] = [] + subnode['includefiles'] = [] + subnode['maxdepth'] = self.options.get('maxdepth', -1) + subnode['caption'] = self.options.get('caption') + subnode['glob'] = 'glob' in self.options + subnode['hidden'] = 'hidden' in self.options + subnode['includehidden'] = 'includehidden' in self.options + subnode['numbered'] = self.options.get('numbered', 0) + subnode['titlesonly'] = 'titlesonly' in self.options + set_source_info(self, subnode) + wrappernode = nodes.compound(classes=['toctree-wrapper']) + wrappernode.append(subnode) + self.add_name(wrappernode) + + ret = self.parse_content(subnode) + ret.append(wrappernode) + return ret + + def parse_content(self, toctree): + env = self.state.document.settings.env + suffixes = env.config.source_suffix + + # glob target documents all_docnames = env.found_docs.copy() - # don't add the currently visited file in catch-all patterns - all_docnames.remove(env.docname) + all_docnames.remove(env.docname) # remove current document + + ret = [] for entry in self.content: if not entry: continue # look for explicit titles ("Some Title ") explicit = explicit_title_re.match(entry) - if glob and ('*' in entry or '?' in entry or '[' in entry) and not explicit: + if toctree['glob'] and glob_re.match(entry) and not explicit: patname = docname_join(env.docname, entry) docnames = sorted(patfilter(all_docnames, patname)) for docname in docnames: all_docnames.remove(docname) # don't include it again - entries.append((None, docname)) - includefiles.append(docname) + toctree['entries'].append((None, docname)) + toctree['includefiles'].append(docname) if not docnames: ret.append(self.state.document.reporter.warning( 'toctree glob pattern %r didn\'t match any documents' @@ -101,7 +126,7 @@ class TocTree(Directive): # absolutize filenames docname = docname_join(env.docname, docname) if url_re.match(ref) or ref == 'self': - entries.append((title, ref)) + toctree['entries'].append((title, ref)) elif docname not in env.found_docs: ret.append(self.state.document.reporter.warning( 'toctree contains reference to nonexisting ' @@ -109,28 +134,13 @@ class TocTree(Directive): env.note_reread() else: all_docnames.discard(docname) - entries.append((title, docname)) - includefiles.append(docname) - subnode = addnodes.toctree() - subnode['parent'] = env.docname + toctree['entries'].append((title, docname)) + toctree['includefiles'].append(docname) + # entries contains all entries (self references, external links etc.) if 'reversed' in self.options: - entries.reverse() - subnode['entries'] = entries - # includefiles only entries that are documents - subnode['includefiles'] = includefiles - subnode['maxdepth'] = self.options.get('maxdepth', -1) - subnode['caption'] = self.options.get('caption') - subnode['glob'] = glob - subnode['hidden'] = 'hidden' in self.options - subnode['includehidden'] = 'includehidden' in self.options - subnode['numbered'] = self.options.get('numbered', 0) - subnode['titlesonly'] = 'titlesonly' in self.options - set_source_info(self, subnode) - wrappernode = nodes.compound(classes=['toctree-wrapper']) - wrappernode.append(subnode) - self.add_name(wrappernode) - ret.append(wrappernode) + toctree['entries'] = toctree['entries'].reverse() + return ret From 538c061e7c2589d00377669bc0b8e1667387a3e1 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 16 Apr 2018 00:16:18 +0900 Subject: [PATCH 32/68] Change priority of doctree-read event --- CHANGES | 3 ++- sphinx/environment/__init__.py | 4 ---- sphinx/io.py | 4 ++-- sphinx/transforms/__init__.py | 9 +++++++++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 642325847..9c730c410 100644 --- a/CHANGES +++ b/CHANGES @@ -17,7 +17,8 @@ Incompatible changes a ``conf.py`` file sphinx-build generates. * The ``gettext_compact`` attribute is removed from ``document.settings`` object. Please use ``config.gettext_compact`` instead. -* The processing order of smart_quotes and sphinx domains are changed. For more +* The processing order on reading phase is changed. smart_quotes, sphinx + domains and :event:`doctree-read` event are invoked earlier. For more details, please read a description of :py:meth:`.Sphinx.add_transform()` Deprecated diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index af76c7ab1..96c98d2dc 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -570,10 +570,6 @@ class BuildEnvironment(object): with sphinx_domains(self), rst.default_role(docname, self.config.default_role): doctree = read_doc(self.app, self, self.doc2path(docname)) - # allow extension-specific post-processing - if app: - app.emit('doctree-read', doctree) - # store time of reading, for outdated files detection # (Some filesystems have coarse timestamp resolution; # therefore time.time() can be older than filesystem's timestamp. diff --git a/sphinx/io.py b/sphinx/io.py index d9b28bed3..2c80fb4b3 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -24,7 +24,7 @@ from sphinx.transforms import ( ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences, DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds, AutoNumbering, AutoIndexUpgrader, FilterSystemMessages, - UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink + UnreferencedFootnotesDetector, SphinxSmartQuotes, DoctreeReadEvent, ManpageLink ) from sphinx.transforms import SphinxTransformer from sphinx.transforms.compact_bullet_list import RefOnlyBulletListTransform @@ -95,7 +95,7 @@ class SphinxStandaloneReader(SphinxBaseReader): HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, RemoveTranslatableInline, FilterSystemMessages, RefOnlyBulletListTransform, UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink, - SphinxDomains, + SphinxDomains, DoctreeReadEvent, ] # type: List[Transform] def __init__(self, app, *args, **kwargs): diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index 4f3d15752..ab69f981a 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -399,6 +399,15 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform): yield (texttype[notsmartquotable], txtnode.astext()) +class DoctreeReadEvent(SphinxTransform): + """Emit :event:`doctree-read` event.""" + default_priority = 880 + + def apply(self): + # type: () -> None + self.app.emit('doctree-read', self.document) + + class ManpageLink(SphinxTransform): """Find manpage section numbers and names""" default_priority = 999 From c24216b48d8f171a793ac6144e3405edf1f3de17 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 16 Apr 2018 01:50:05 +0900 Subject: [PATCH 33/68] Update CHANGES for #4771 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 264a024e5..507ceaadb 100644 --- a/CHANGES +++ b/CHANGES @@ -139,6 +139,8 @@ Incompatible changes (refs: #4295) * #4246: Limit width of text body for all themes. Conifigurable via theme options ``body_min_width`` and ``body_max_width``. +* #4771: apidoc: The ``exclude_patterns`` arguments are ignored if they are + placed just after command line options 1.7.0b2 From ef10ca11fce370aa4eb7d44675e770b9fbf8646e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 13 Apr 2018 01:40:59 +0900 Subject: [PATCH 34/68] Fix toctree directive tries to glob for URL having query_string --- CHANGES | 1 + sphinx/directives/other.py | 5 +++-- tests/test_directive_other.py | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 264a024e5..f09e60d2d 100644 --- a/CHANGES +++ b/CHANGES @@ -29,6 +29,7 @@ Bugs fixed * #4837: latex with class memoir Error: Font command ``\sf`` is not supported * #4803: latex: too slow in proportion to number of auto numbered footnotes * #4838: htmlhelp: The entries in .hhp file is not ordered +* toctree directive tries to glob for URL having query_string Testing -------- diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 1800d1853..e9a3864ab 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -99,7 +99,8 @@ class TocTree(Directive): continue # look for explicit titles ("Some Title ") explicit = explicit_title_re.match(entry) - if toctree['glob'] and glob_re.match(entry) and not explicit: + if (toctree['glob'] and glob_re.match(entry) and + not explicit and not url_re.match(entry)): patname = docname_join(env.docname, entry) docnames = sorted(patfilter(all_docnames, patname)) for docname in docnames: @@ -139,7 +140,7 @@ class TocTree(Directive): # entries contains all entries (self references, external links etc.) if 'reversed' in self.options: - toctree['entries'] = toctree['entries'].reverse() + toctree['entries'] = list(reversed(toctree['entries'])) return ret diff --git a/tests/test_directive_other.py b/tests/test_directive_other.py index 9a11a6d4f..6eb7a2056 100644 --- a/tests/test_directive_other.py +++ b/tests/test_directive_other.py @@ -123,6 +123,21 @@ def test_toctree_glob(app): includefiles=['baz', 'foo', 'quux', 'foo']) +@pytest.mark.sphinx(testroot='toctree-glob') +def test_toctree_glob_and_url(app): + text = (".. toctree::\n" + " :glob:\n" + "\n" + " https://example.com/?q=sphinx\n") + + app.env.find_files(app.config, app.builder) + doctree = parse(app, 'index', text) + assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree]) + assert_node(doctree[0][0], + entries=[(None, 'https://example.com/?q=sphinx')], + includefiles=[]) + + @pytest.mark.sphinx(testroot='toctree-glob') def test_toctree_twice(app): text = (".. toctree::\n" From 365c93f2278c068c7637adf6801c3cc41bbcde2c Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 18 Apr 2018 19:51:48 -0700 Subject: [PATCH 35/68] Update all pypi.python.org URLs to pypi.org For details on the new PyPI, see the blog post: https://pythoninsider.blogspot.ca/2018/04/new-pypi-launched-legacy-pypi-shutting.html --- EXAMPLES | 2 +- README.rst | 4 ++-- doc/_templates/indexsidebar.html | 4 ++-- doc/builders.rst | 2 +- doc/config.rst | 6 +++--- doc/develop.rst | 2 +- doc/extdev/index.rst | 2 +- doc/faq.rst | 2 +- doc/intl.rst | 4 ++-- doc/intro.rst | 4 ++-- doc/theming.rst | 4 ++-- doc/usage/installation.rst | 2 +- doc/usage/restructuredtext/domains.rst | 28 +++++++++++++------------- setup.py | 2 +- tests/roots/test-intl/refs.txt | 2 +- utils/release-checklist | 8 ++++---- 16 files changed, 39 insertions(+), 39 deletions(-) diff --git a/EXAMPLES b/EXAMPLES index 5e6ddfc67..4310eff75 100644 --- a/EXAMPLES +++ b/EXAMPLES @@ -317,7 +317,7 @@ Documentation using a custom theme or integrated in a website * PSI4: http://www.psicode.org/psi4manual/master/index.html * PyMOTW: https://pymotw.com/2/ * python-aspectlib: https://python-aspectlib.readthedocs.io/ - (`sphinx_py3doc_enhanced_theme `__) + (`sphinx_py3doc_enhanced_theme `__) * QGIS: https://qgis.org/en/docs/index.html * qooxdoo: http://www.qooxdoo.org/current/ * Roundup: http://www.roundup-tracker.org/ diff --git a/README.rst b/README.rst index 0d29c3984..85255f946 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ ======== .. image:: https://img.shields.io/pypi/v/sphinx.svg - :target: https://pypi.python.org/pypi/Sphinx + :target: https://pypi.org/project/Sphinx/ :alt: Package on PyPi .. image:: https://readthedocs.org/projects/sphinx/badge/ @@ -71,7 +71,7 @@ We also publish beta releases:: If you wish to install `Sphinx` for development purposes, refer to `the contributors guide`__. -__ https://pypi.python.org/pypi/Sphinx +__ https://pypi.org/project/Sphinx/ __ http://www.sphinx-doc.org/en/master/devguide.html Documentation diff --git a/doc/_templates/indexsidebar.html b/doc/_templates/indexsidebar.html index 668070579..e1747c762 100644 --- a/doc/_templates/indexsidebar.html +++ b/doc/_templates/indexsidebar.html @@ -8,11 +8,11 @@ not released yet.{%endtrans%}

{%trans%}You can use it from the Git repo or look for - released versions in the Python + released versions in the Python Package Index.{%endtrans%}

{% else %}

{%trans%}Current version: {{ version }}{%endtrans%}

-

{%trans%}Get Sphinx from the Python Package +

{%trans%}Get Sphinx from the Python Package Index, or install it with:{%endtrans%}

pip install -U Sphinx
{% endif %} diff --git a/doc/builders.rst b/doc/builders.rst index 6ab6227fa..d968f209f 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -288,7 +288,7 @@ name is ``rinoh``. Refer to the `rinohtype manual`_ for details. globalcontext_filename = 'globalcontext.phpdump' searchindex_filename = 'searchindex.phpdump' - .. _PHP serialization: https://pypi.python.org/pypi/phpserialize + .. _PHP serialization: https://pypi.org/project/phpserialize/ .. attribute:: implementation diff --git a/doc/config.rst b/doc/config.rst index 1c8ae48e0..2279cffe4 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -1147,8 +1147,8 @@ that use Sphinx's HTMLWriter class. Sphinx uses a Python implementation by default. You can use a C implementation to accelerate building the index file. - * `PorterStemmer `_ (``en``) - * `PyStemmer `_ (all languages) + * `PorterStemmer `_ (``en``) + * `PyStemmer `_ (all languages) .. versionadded:: 1.1 With support for ``en`` and ``ja``. @@ -1180,7 +1180,7 @@ that use Sphinx's HTMLWriter class. library ('libmecab.so' for linux, 'libmecab.dll' for windows) is required. :'sphinx.search.ja.JanomeSplitter': Janome binding. To use this splitter, - `Janome `_ is required. + `Janome `_ is required. To keep compatibility, ``'mecab'``, ``'janome'`` and ``'default'`` are also acceptable. However it will be deprecated in Sphinx-1.6. diff --git a/doc/develop.rst b/doc/develop.rst index af0302367..d2a51b8e2 100644 --- a/doc/develop.rst +++ b/doc/develop.rst @@ -141,5 +141,5 @@ own extensions. .. _inlinesyntaxhighlight: https://sphinxcontrib-inlinesyntaxhighlight.readthedocs.io/ .. _CMake: https://cmake.org .. _domaintools: https://bitbucket.org/klorenz/sphinxcontrib-domaintools -.. _restbuilder: https://pypi.python.org/pypi/sphinxcontrib-restbuilder +.. _restbuilder: https://pypi.org/project/sphinxcontrib-restbuilder/ .. _Lasso: http://www.lassosoft.com/ diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 94a23aaea..531457b83 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -268,7 +268,7 @@ The following is a list of deprecated interface. * - ``sphinx.websupport`` - 1.6 - 2.0 - - `sphinxcontrib-websupport `_ + - `sphinxcontrib-websupport `_ * - ``StandaloneHTMLBuilder.css_files`` - 1.6 diff --git a/doc/faq.rst b/doc/faq.rst index 270ceb68a..b2d6cc9e6 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -72,7 +72,7 @@ SCons PyPI Jannis Leidel wrote a `setuptools command - `_ that automatically + `_ that automatically uploads Sphinx documentation to the PyPI package documentation area at https://pythonhosted.org/. diff --git a/doc/intl.rst b/doc/intl.rst index 75263e6e4..43f620bd0 100644 --- a/doc/intl.rst +++ b/doc/intl.rst @@ -322,8 +322,8 @@ There is `sphinx translation page`_ for Sphinx (master) documentation. .. [2] Because nobody expects the Spanish Inquisition! -.. _`transifex-client`: https://pypi.python.org/pypi/transifex-client -.. _`sphinx-intl`: https://pypi.python.org/pypi/sphinx-intl +.. _`transifex-client`: https://pypi.org/project/transifex-client/ +.. _`sphinx-intl`: https://pypi.org/project/sphinx-intl/ .. _Transifex: https://www.transifex.com/ .. _`sphinx translation page`: https://www.transifex.com/sphinx-doc/sphinx-doc/ .. _`Transifex Client documentation`: http://docs.transifex.com/developer/client/ diff --git a/doc/intro.rst b/doc/intro.rst index e2a23f95a..d44003b71 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -29,7 +29,7 @@ This section is intended to collect helpful hints for those wanting to migrate to reStructuredText/Sphinx from other documentation systems. * Gerard Flanagan has written a script to convert pure HTML to reST; it can be - found at the `Python Package Index `_. + found at the `Python Package Index `_. * For converting the old Python docs to Sphinx, a converter was written which can be found at `the Python SVN repository @@ -40,7 +40,7 @@ to reStructuredText/Sphinx from other documentation systems. markup; it is at `GitHub `_. * Christophe de Vienne wrote a tool to convert from Open/LibreOffice documents - to Sphinx: `odt2sphinx `_. + to Sphinx: `odt2sphinx `_. * To convert different markups, `Pandoc `_ is a very helpful tool. diff --git a/doc/theming.rst b/doc/theming.rst index 67dd6adc2..bbc52cbbe 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -134,7 +134,7 @@ These themes are: Check out at its `installation page`_ how to set up properly :confval:`html_sidebars` for its use. - .. _Alabaster theme: https://pypi.python.org/pypi/alabaster + .. _Alabaster theme: https://pypi.org/project/alabaster/ .. _installation page: https://alabaster.readthedocs.io/en/latest/installation.html * **classic** -- This is the classic theme, which looks like `the Python 2 @@ -418,7 +418,7 @@ Third Party Themes View a working demo over on readthedocs.org. You can get install and options information at `Read the Docs Sphinx Theme`_ page. - .. _Read the Docs Sphinx Theme: https://pypi.python.org/pypi/sphinx_rtd_theme + .. _Read the Docs Sphinx Theme: https://pypi.org/project/sphinx_rtd_theme/ .. versionchanged:: 1.4 **sphinx_rtd_theme** has become optional. diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst index ba1146a76..5c0d7de75 100644 --- a/doc/usage/installation.rst +++ b/doc/usage/installation.rst @@ -132,7 +132,7 @@ Installation from PyPI ---------------------- Sphinx packages are published on the `Python Package Index -`_. The preferred tool for installing +`_. The preferred tool for installing packages from *PyPI* is :command:`pip`. This tool is provided with all modern versions of Python. diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 85ac80cb7..452cf63ae 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -1287,18 +1287,18 @@ Jinja_, Operation_, and Scala_. .. _sphinx-contrib: https://bitbucket.org/birkenfeld/sphinx-contrib/ -.. _Ada: https://pypi.python.org/pypi/sphinxcontrib-adadomain -.. _Chapel: https://pypi.python.org/pypi/sphinxcontrib-chapeldomain -.. _CoffeeScript: https://pypi.python.org/pypi/sphinxcontrib-coffee -.. _Common Lisp: https://pypi.python.org/pypi/sphinxcontrib-cldomain -.. _dqn: https://pypi.python.org/pypi/sphinxcontrib-dqndomain -.. _Erlang: https://pypi.python.org/pypi/sphinxcontrib-erlangdomain -.. _Go: https://pypi.python.org/pypi/sphinxcontrib-golangdomain -.. _HTTP: https://pypi.python.org/pypi/sphinxcontrib-httpdomain -.. _Jinja: https://pypi.python.org/pypi/sphinxcontrib-jinjadomain -.. _Lasso: https://pypi.python.org/pypi/sphinxcontrib-lassodomain -.. _MATLAB: https://pypi.python.org/pypi/sphinxcontrib-matlabdomain -.. _Operation: https://pypi.python.org/pypi/sphinxcontrib-operationdomain -.. _PHP: https://pypi.python.org/pypi/sphinxcontrib-phpdomain +.. _Ada: https://pypi.org/project/sphinxcontrib-adadomain/ +.. _Chapel: https://pypi.org/project/sphinxcontrib-chapeldomain/ +.. _CoffeeScript: https://pypi.org/project/sphinxcontrib-coffee/ +.. _Common Lisp: https://pypi.org/project/sphinxcontrib-cldomain/ +.. _dqn: https://pypi.org/project/sphinxcontrib-dqndomain/ +.. _Erlang: https://pypi.org/project/sphinxcontrib-erlangdomain/ +.. _Go: https://pypi.org/project/sphinxcontrib-golangdomain/ +.. _HTTP: https://pypi.org/project/sphinxcontrib-httpdomain/ +.. _Jinja: https://pypi.org/project/sphinxcontrib-jinjadomain/ +.. _Lasso: https://pypi.org/project/sphinxcontrib-lassodomain/ +.. _MATLAB: https://pypi.org/project/sphinxcontrib-matlabdomain/ +.. _Operation: https://pypi.org/project/sphinxcontrib-operationdomain/ +.. _PHP: https://pypi.org/project/sphinxcontrib-phpdomain/ .. _Ruby: https://bitbucket.org/birkenfeld/sphinx-contrib/src/default/rubydomain -.. _Scala: https://pypi.python.org/pypi/sphinxcontrib-scaladomain +.. _Scala: https://pypi.org/project/sphinxcontrib-scaladomain/ diff --git a/setup.py b/setup.py index d73f1d270..b6b3bc259 100644 --- a/setup.py +++ b/setup.py @@ -176,7 +176,7 @@ setup( name='Sphinx', version=sphinx.__version__, url='http://sphinx-doc.org/', - download_url='https://pypi.python.org/pypi/Sphinx', + download_url='https://pypi.org/project/Sphinx/', license='BSD', author='Georg Brandl', author_email='georg@python.org', diff --git a/tests/roots/test-intl/refs.txt b/tests/roots/test-intl/refs.txt index b093dbe60..0f923a9e7 100644 --- a/tests/roots/test-intl/refs.txt +++ b/tests/roots/test-intl/refs.txt @@ -4,7 +4,7 @@ References Translation Tips ----------------- -.. _download Sphinx: https://pypi.python.org/pypi/sphinx +.. _download Sphinx: https://pypi.org/project/Sphinx/ .. _Docutils site: http://docutils.sourceforge.net/ .. _Sphinx site: http://sphinx-doc.org/ diff --git a/utils/release-checklist b/utils/release-checklist index 6b2ad74c8..2ffbc6a81 100644 --- a/utils/release-checklist +++ b/utils/release-checklist @@ -12,7 +12,7 @@ for stable releases * ``git commit -am 'Bump to X.Y.Z final'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist upload --identity=[your key]`` -* open https://pypi.python.org/pypi/Sphinx and check there are no obvious errors +* open https://pypi.org/project/Sphinx/ and check there are no obvious errors * ``git tag vX.Y.Z`` * ``python utils/bump_version.py --in-develop X.Y.Zb0`` (ex. 1.5.3b0) * Check diff by ``git diff`` @@ -38,7 +38,7 @@ for first beta releases * ``git commit -am 'Bump to X.Y.0 beta1'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist upload --identity=[your key]`` -* open https://pypi.python.org/pypi/Sphinx and check there are no obvious errors +* open https://pypi.org/project/Sphinx/ and check there are no obvious errors * ``git tag vX.Y.0b1`` * ``python utils/bump_version.py --in-develop X.Y.0b2`` (ex. 1.6.0b2) * Check diff by ``git diff`` @@ -67,7 +67,7 @@ for other beta releases * ``git commit -am 'Bump to X.Y.0 betaN'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist upload --identity=[your key]`` -* open https://pypi.python.org/pypi/Sphinx and check there are no obvious errors +* open https://pypi.org/project/Sphinx/ and check there are no obvious errors * ``git tag vX.Y.0bN`` * ``python utils/bump_version.py --in-develop X.Y.0bM`` (ex. 1.6.0b3) * Check diff by `git diff`` @@ -95,7 +95,7 @@ for major releases * ``git commit -am 'Bump to X.Y.0 final'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist upload --identity=[your key]`` -* open https://pypi.python.org/pypi/Sphinx and check there are no obvious errors +* open https://pypi.org/project/Sphinx/ and check there are no obvious errors * ``git tag vX.Y.0`` * ``python utils/bump_version.py --in-develop X.Y.1b0`` (ex. 1.6.1b0) * Check diff by ``git diff`` From e368ac21ef81c1113a61397c0962f678f9c56769 Mon Sep 17 00:00:00 2001 From: Michael Tesch Date: Fri, 20 Apr 2018 13:13:00 +0200 Subject: [PATCH 36/68] proposed enhancement #4830 --- sphinx/texinputs/sphinx.sty | 1 + sphinx/writers/latex.py | 5 ++++- tests/test_markup.py | 12 ++++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 17aa6644d..8ff218504 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -1578,6 +1578,7 @@ \protected\def\sphinxtablecontinued#1{\textsf{#1}} \protected\def\sphinxtitleref#1{\emph{#1}} \protected\def\sphinxmenuselection#1{\emph{#1}} +\protected\def\sphinxguilabel#1{\emph{#1}} \protected\def\sphinxaccelerator#1{\underline{#1}} \protected\def\sphinxcrossref#1{\emph{#1}} \protected\def\sphinxtermref#1{\emph{#1}} diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index f05959aca..b65821529 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -2409,9 +2409,12 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_inline(self, node): # type: (nodes.Node) -> None classes = node.get('classes', []) - if classes in [['menuselection'], ['guilabel']]: + if classes in [['menuselection']]: self.body.append(r'\sphinxmenuselection{') self.context.append('}') + elif classes in [['guilabel']]: + self.body.append(r'\sphinxguilabel{') + self.context.append('}') elif classes in [['accelerator']]: self.body.append(r'\sphinxaccelerator{') self.context.append('}') diff --git a/tests/test_markup.py b/tests/test_markup.py index 37a1a721b..802202639 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -162,12 +162,20 @@ def get_verifier(verify, verify_re): '\\sphinxmenuselection{a \\(\\rightarrow\\) b}', ), ( - # interpolation of ampersands in guilabel/menuselection + # interpolation of ampersands in menuselection + 'verify', + ':menuselection:`&Foo -&&- &Bar`', + (u'

Foo ' + '-&- Bar

'), + r'\sphinxmenuselection{\sphinxaccelerator{F}oo -\&- \sphinxaccelerator{B}ar}', + ), + ( + # interpolation of ampersands in guilabel 'verify', ':guilabel:`&Foo -&&- &Bar`', (u'

Foo ' '-&- Bar

'), - r'\sphinxmenuselection{\sphinxaccelerator{F}oo -\&- \sphinxaccelerator{B}ar}', + r'\sphinxguilabel{\sphinxaccelerator{F}oo -\&- \sphinxaccelerator{B}ar}', ), ( # non-interpolation of dashes in option role From 86df550c667871f830a9bae93524f44f9667d925 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 20 Apr 2018 22:58:03 +0900 Subject: [PATCH 37/68] Fix short underline --- tests/roots/test-toctree-glob/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/roots/test-toctree-glob/index.rst b/tests/roots/test-toctree-glob/index.rst index 3fda0618b..4ed6bb409 100644 --- a/tests/roots/test-toctree-glob/index.rst +++ b/tests/roots/test-toctree-glob/index.rst @@ -15,7 +15,7 @@ normal order hyperref reversed order -------------- +-------------- .. toctree:: :glob: From b650ba2401799f434fef93d43cc0055e96d34dd2 Mon Sep 17 00:00:00 2001 From: Dennis Wegner Date: Fri, 20 Apr 2018 17:51:19 +0200 Subject: [PATCH 38/68] Fixed the German HTML search again, this time from the 1.7 branch --- sphinx/search/de.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/search/de.py b/sphinx/search/de.py index 89cbe3de5..f511748f7 100644 --- a/sphinx/search/de.py +++ b/sphinx/search/de.py @@ -311,4 +311,4 @@ class SearchGerman(SearchLanguage): self.stemmer = snowballstemmer.stemmer('german') def stem(self, word): - return self.stemmer.stemWord(word) + return self.stemmer.stemWord(word.lower()) From 5a63c2970f99d39a0202174454bd7452f7f79643 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 21 Apr 2018 11:02:46 +0900 Subject: [PATCH 39/68] doc: Add a note for dotfiles on html_static_path (refs: #4859) --- doc/config.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/config.rst b/doc/config.rst index 1c8ae48e0..a0cdbe709 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -877,6 +877,18 @@ that use Sphinx's HTMLWriter class. named :file:`default.css` will overwrite the theme's :file:`default.css`. + .. note:: + + For security reason, dotfiles under ``html_static_path`` will + not be copied. If you'd like to copy them intentionally, please + add them each filepath to this setting:: + + html_static_path = ['_static', '_static/.htaccess'] + + Another way to do that, you can also use + :confval:`html_extra_path`. It allows to copy dotfiles under + the directories. + .. versionchanged:: 0.4 The paths in :confval:`html_static_path` can now contain subdirectories. From ea7d30a190aff2e18d764fbdaea0b3c6b442388d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 12 Apr 2018 23:02:18 +0900 Subject: [PATCH 40/68] doc: Add ImageConverter to utils --- doc/ext/imgconverter.rst | 2 + doc/extdev/utils.rst | 3 ++ sphinx/transforms/post_transforms/images.py | 42 ++++++++++++++++----- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/doc/ext/imgconverter.rst b/doc/ext/imgconverter.rst index 4c43bdf8e..cbb3f5053 100644 --- a/doc/ext/imgconverter.rst +++ b/doc/ext/imgconverter.rst @@ -1,5 +1,7 @@ .. highlight:: rest +.. _sphinx.ext.imgconverter: + :mod:`sphinx.ext.imgconverter` -- Convert images to appropriate format for builders =================================================================================== diff --git a/doc/extdev/utils.rst b/doc/extdev/utils.rst index 6ec3f1b46..ebf05629e 100644 --- a/doc/extdev/utils.rst +++ b/doc/extdev/utils.rst @@ -17,3 +17,6 @@ components (e.g. :class:`.Config`, :class:`.BuildEnvironment` and so on) easily. .. autoclass:: sphinx.util.docutils.SphinxDirective :members: + +.. autoclass:: sphinx.transforms.post_transforms.images.ImageConverter + :members: diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 44b099198..7d52d5b8e 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -165,19 +165,37 @@ def get_filename_for(filename, mimetype): class ImageConverter(BaseImageConverter): - """A base class images converter. + """A base class for image converters. - The concrete image converters should derive this class and - overrides the following methods and attributes: + An image converter is kind of Docutils transform module. It is used to + convert image files which does not supported by builder to appropriate + format for that builder. - * default_priority (if needed) - * conversion_rules - * is_available() - * convert() + For example, :py:class:`LaTeX builder <.LaTeXBuilder>` supports PDF, + PNG and JPEG as image formats. However it does not support SVG images. + For such case, to use image converters allows to embed these + unsupported images into the document. One of image converters; + :ref:`sphinx.ext. imgconverter ` can convert + a SVG image to PNG format using Imagemagick internally. + + There are three steps to make your custom image converter: + + 1. Make a subclass of ``ImageConverter`` class + 2. Override ``conversion_rules``, ``is_available()`` and ``convert()`` + 3. Register your image converter to Sphinx using + :py:meth:`.Sphinx.add_post_transform` """ default_priority = 200 - #: A conversion rules between two mimetypes which this converters supports + #: A conversion rules the image converter supports. + #: It is represented as a list of pair of source image format (mimetype) and + #: destination one:: + #: + #: conversion_rules = [ + #: ('image/svg+xml', 'image/png'), + #: ('image/gif', 'image/png'), + #: ('application/pdf', 'image/png'), + #: ] conversion_rules = [] # type: List[Tuple[unicode, unicode]] def __init__(self, *args, **kwargs): @@ -216,7 +234,7 @@ class ImageConverter(BaseImageConverter): def is_available(self): # type: () -> bool - """Confirms the converter is available or not.""" + """Return the image converter is available or not.""" raise NotImplementedError() def guess_mimetypes(self, node): @@ -255,7 +273,11 @@ class ImageConverter(BaseImageConverter): def convert(self, _from, _to): # type: (unicode, unicode) -> bool - """Converts the image to expected one.""" + """Convert a image file to expected format. + + *_from* is a path for source image file, and *_to* is a path for + destination file. + """ raise NotImplementedError() From fdb96bb948c9040a66256dbabcc4d8db9e3489e1 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 12 Apr 2018 23:14:10 +0900 Subject: [PATCH 41/68] doc: Update about sphinx.ext.imgconverter --- doc/ext/imgconverter.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/ext/imgconverter.rst b/doc/ext/imgconverter.rst index cbb3f5053..322a9b5f3 100644 --- a/doc/ext/imgconverter.rst +++ b/doc/ext/imgconverter.rst @@ -2,8 +2,8 @@ .. _sphinx.ext.imgconverter: -:mod:`sphinx.ext.imgconverter` -- Convert images to appropriate format for builders -=================================================================================== +:mod:`sphinx.ext.imgconverter` -- A reference implementation for image converter using Imagemagick +================================================================================================== .. module:: sphinx.ext.imgconverter :synopsis: Convert images to appropriate format for builders @@ -18,6 +18,12 @@ Internally, this extension uses Imagemagick_ to convert images. .. _Imagemagick: https://www.imagemagick.org/script/index.php +.. note:: Imagemagick rasterizes a SVG image on conversion. As a result, the image + becomes not scalable. To avoid that, please use other image converters + like sphinxcontrib-svg2pdfconverter_ (which uses Inkscape or rsvg-convert). + +.. _sphinxcontrib-svg2pdfconverter: https://github.com/missinglinkelectronics/sphinxcontrib-svg2pdfconverter + Configuration ------------- From 6b14cd3bd62cec3d653965cfa7384e98c2c93bf7 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 16 Apr 2018 00:24:49 +0900 Subject: [PATCH 42/68] Change priority of versioning doctrees --- CHANGES | 6 ++++-- doc/extdev/index.rst | 5 +++++ sphinx/environment/__init__.py | 6 +----- sphinx/io.py | 3 ++- sphinx/versioning.py | 9 ++++++++- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 9c730c410..f259454d2 100644 --- a/CHANGES +++ b/CHANGES @@ -18,8 +18,9 @@ Incompatible changes * The ``gettext_compact`` attribute is removed from ``document.settings`` object. Please use ``config.gettext_compact`` instead. * The processing order on reading phase is changed. smart_quotes, sphinx - domains and :event:`doctree-read` event are invoked earlier. For more - details, please read a description of :py:meth:`.Sphinx.add_transform()` + domains, :event:`doctree-read` event and versioning doctrees are invoked + earlier than so far. For more details, please read a description of + :py:meth:`.Sphinx.add_transform()` Deprecated ---------- @@ -39,6 +40,7 @@ Deprecated * ``env._nitpick_ignore`` is deprecated * ``app.override_domain()`` is deprecated * ``app.add_stylesheet()`` is deprecated +* ``sphinx.versioning.prepare()`` is deprecated For more details, see `deprecation APIs list `_ diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 94a23aaea..b8f9243d6 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -119,6 +119,11 @@ The following is a list of deprecated interface. - 4.0 - :meth:`~sphinx.application.Sphinx.add_css_file()` + * - ``sphinx.versioning.prepare()`` + - 1.8 + - 3.0 + - ``sphinx.versioning.UIDTransform`` + * - ``sphinx.application.Sphinx.override_domain()`` - 1.8 - 3.0 diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 96c98d2dc..ccdeb03bd 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -24,7 +24,7 @@ from docutils.utils import Reporter, get_source_line from six import BytesIO, class_types, next from six.moves import cPickle as pickle -from sphinx import addnodes, versioning +from sphinx import addnodes from sphinx.deprecation import RemovedInSphinx20Warning, RemovedInSphinx30Warning from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.environment.adapters.toctree import TocTree @@ -577,10 +577,6 @@ class BuildEnvironment(object): self.all_docs[docname] = max( time.time(), path.getmtime(self.doc2path(docname))) - if self.versioning_condition: - # add uids for versioning - versioning.prepare(doctree) - # cleanup self.temp_data.clear() self.ref_context.clear() diff --git a/sphinx/io.py b/sphinx/io.py index 2c80fb4b3..0f54aa891 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -34,6 +34,7 @@ from sphinx.transforms.i18n import ( from sphinx.transforms.references import SphinxDomains from sphinx.util import logging from sphinx.util.docutils import LoggingReporter +from sphinx.versioning import UIDTransform if False: # For type annotation @@ -95,7 +96,7 @@ class SphinxStandaloneReader(SphinxBaseReader): HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, RemoveTranslatableInline, FilterSystemMessages, RefOnlyBulletListTransform, UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink, - SphinxDomains, DoctreeReadEvent, + SphinxDomains, DoctreeReadEvent, UIDTransform, ] # type: List[Transform] def __init__(self, app, *args, **kwargs): diff --git a/sphinx/versioning.py b/sphinx/versioning.py index a855cb8fe..58b648069 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -9,6 +9,7 @@ :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +import warnings from itertools import product from operator import itemgetter from uuid import uuid4 @@ -17,6 +18,7 @@ from six import iteritems from six.moves import cPickle as pickle from six.moves import range, zip_longest +from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.transforms import SphinxTransform if False: @@ -155,12 +157,15 @@ def levenshtein_distance(a, b): class UIDTransform(SphinxTransform): """Add UIDs to doctree for versioning.""" - default_priority = 100 + default_priority = 880 def apply(self): # type: () -> None env = self.env old_doctree = None + if not env.versioning_condition: + return + if env.versioning_compare: # get old doctree try: @@ -180,5 +185,7 @@ class UIDTransform(SphinxTransform): def prepare(document): # type: (nodes.Node) -> None """Simple wrapper for UIDTransform.""" + warnings.warn('versioning.prepare() is deprecated. Use UIDTransform instead.', + RemovedInSphinx30Warning) transform = UIDTransform(document) transform.apply() From ee09a00033e14a5d283454370f118816555cb51c Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Mon, 16 Apr 2018 17:04:31 +0900 Subject: [PATCH 43/68] Fix translation for zh_CN and zh_TW. --- sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po | 18 +++++++++--------- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po b/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po index bf7bbb7b9..6950e17fc 100644 --- a/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po @@ -425,7 +425,7 @@ msgstr "行规范 %r:未能从包含文件 %r 中拉取行" #: sphinx/directives/other.py:157 msgid "Section author: " -msgstr "节作者:" +msgstr "章节作者:" #: sphinx/directives/other.py:159 msgid "Module author: " @@ -878,7 +878,7 @@ msgstr "格式转换程序退出并报错:\n[stderr]\n%s\n[stdout]\n%s" #: sphinx/ext/imgmath.py:338 sphinx/ext/jsmath.py:41 sphinx/ext/mathjax.py:42 msgid "Permalink to this equation" -msgstr "永久链接至公式" +msgstr "公式的永久链接" #: sphinx/ext/intersphinx.py:339 #, python-format @@ -1047,7 +1047,7 @@ msgstr "搜索" #: sphinx/themes/agogo/layout.html:54 sphinx/themes/basic/searchbox.html:16 msgid "Go" -msgstr "转向" +msgstr "搜" #: sphinx/themes/agogo/layout.html:81 sphinx/themes/basic/sourcelink.html:15 msgid "Show Source" @@ -1250,13 +1250,13 @@ msgstr "其他更改" #: sphinx/writers/html.py:410 sphinx/writers/html5.py:351 #: sphinx/writers/html5.py:356 msgid "Permalink to this headline" -msgstr "永久链接至标题" +msgstr "标题的永久链接" #: sphinx/themes/basic/static/doctools.js_t:199 sphinx/writers/html.py:126 #: sphinx/writers/html.py:137 sphinx/writers/html5.py:95 #: sphinx/writers/html5.py:106 msgid "Permalink to this definition" -msgstr "永久链接至目标" +msgstr "定义的永久链接" #: sphinx/themes/basic/static/doctools.js_t:232 msgid "Hide Search Matches" @@ -1313,19 +1313,19 @@ msgstr "当添加指令类时,不应该给定额外参数" #: sphinx/writers/html.py:414 sphinx/writers/html5.py:360 msgid "Permalink to this table" -msgstr "永久链接至表格" +msgstr "表格的永久链接" #: sphinx/writers/html.py:466 sphinx/writers/html5.py:412 msgid "Permalink to this code" -msgstr "永久链接至代码" +msgstr "代码的永久链接" #: sphinx/writers/html.py:470 sphinx/writers/html5.py:416 msgid "Permalink to this image" -msgstr "永久链接至图片" +msgstr "图片的永久链接" #: sphinx/writers/html.py:472 sphinx/writers/html5.py:418 msgid "Permalink to this toctree" -msgstr "永久链接至目录树" +msgstr "目录的永久链接" #: sphinx/writers/latex.py:554 msgid "Release" diff --git a/sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po b/sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po index cd1d624aa..4ef2d04e6 100644 --- a/sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po @@ -1046,7 +1046,7 @@ msgstr "搜尋" #: sphinx/themes/agogo/layout.html:54 sphinx/themes/basic/searchbox.html:16 msgid "Go" -msgstr "前往" +msgstr "搜" #: sphinx/themes/agogo/layout.html:81 sphinx/themes/basic/sourcelink.html:15 msgid "Show Source" From 0541c14766dee0fcf62ba819db2d7b18cd682238 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 21 Apr 2018 17:00:48 +0900 Subject: [PATCH 44/68] Update CHANGES for PR #4871 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index dc860887e..fcefddca9 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Bugs fixed * #4803: latex: too slow in proportion to number of auto numbered footnotes * #4838: htmlhelp: The entries in .hhp file is not ordered * toctree directive tries to glob for URL having query_string +* #4871: html search: Upper characters problem in German Testing -------- From 61bf7c44df5fc12f5ad6cdd1257238c78b2e02f2 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 21 Apr 2018 18:45:28 +0900 Subject: [PATCH 45/68] Fix #4717: latex: Compilation for German docs failed with LuaLaTeX and XeLaTeX --- CHANGES | 1 + sphinx/writers/latex.py | 38 +++++++++++++++++++++++++++------ tests/test_build_latex.py | 44 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index fcefddca9..ad3fa708e 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Bugs fixed * #4838: htmlhelp: The entries in .hhp file is not ordered * toctree directive tries to glob for URL having query_string * #4871: html search: Upper characters problem in German +* #4717: latex: Compilation for German docs failed with LuaLaTeX and XeLaTeX Testing -------- diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 8dbe58963..f5209eb38 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -186,10 +186,11 @@ class LaTeXWriter(writers.Writer): # Helper classes class ExtBabel(Babel): - def __init__(self, language_code): - # type: (unicode) -> None + def __init__(self, language_code, use_polyglossia=False): + # type: (unicode, bool) -> None super(ExtBabel, self).__init__(language_code or '') self.language_code = language_code + self.use_polyglossia = use_polyglossia def get_shorthandoff(self): # type: () -> unicode @@ -217,11 +218,28 @@ class ExtBabel(Babel): def get_language(self): # type: () -> unicode language = super(ExtBabel, self).get_language() - if not language: + if language == 'ngerman' and self.use_polyglossia: + # polyglossia calls new orthography (Neue Rechtschreibung) as + # german (with new spelling option). + return 'german' + elif not language: return 'english' # fallback to english else: return language + def get_mainlanguage_options(self): + # type: () -> unicode + """Return options for polyglossia's ``\setmainlanguage``.""" + language = super(ExtBabel, self).get_language() + if self.use_polyglossia is False: + return None + elif language == 'ngerman': + return 'spelling=new' + elif language == 'german': + return 'spelling=old' + else: + return None + class Table(object): """A table data""" @@ -513,7 +531,8 @@ class LaTeXTranslator(nodes.NodeVisitor): '\\sffamily}\n\\ChTitleVar{\\Large' '\\normalfont\\sffamily}') - self.babel = ExtBabel(builder.config.language) + self.babel = ExtBabel(builder.config.language, + not self.elements['babel']) if builder.config.language and not self.babel.is_supported_language(): # emit warning if specified language is invalid # (only emitting, nothing changed to processing) @@ -547,8 +566,15 @@ class LaTeXTranslator(nodes.NodeVisitor): # disable fncychap in Japanese documents self.elements['fncychap'] = '' elif self.elements['polyglossia']: - self.elements['multilingual'] = '%s\n\\setmainlanguage{%s}' % \ - (self.elements['polyglossia'], self.babel.get_language()) + options = self.babel.get_mainlanguage_options() + if options: + mainlanguage = r'\setmainlanguage[%s]{%s}' % (options, + self.babel.get_language()) + else: + mainlanguage = r'\setmainlanguage{%s}' % self.babel.get_language() + + self.elements['multilingual'] = '%s\n%s' % (self.elements['polyglossia'], + mainlanguage) if getattr(builder, 'usepackages', None): def declare_package(packagename, options=None): diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index be9d800b3..c6aed1e3e 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -534,6 +534,50 @@ def test_babel_with_unknown_language(app, status, warning): assert "WARNING: no Babel option known for language 'unknown'" in warning.getvalue() +@pytest.mark.sphinx( + 'latex', testroot='latex-babel', + confoverrides={'language': 'de', 'latex_engine': 'lualatex'}) +def test_polyglossia_with_language_de(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert '\\documentclass[letterpaper,10pt,german]{sphinxmanual}' in result + assert '\\usepackage{polyglossia}' in result + assert '\\setmainlanguage[spelling=new]{german}' in result + assert '\\usepackage{times}' not in result + assert '\\usepackage[Sonny]{fncychap}' in result + assert ('\\addto\\captionsgerman{\\renewcommand{\\contentsname}{Table of content}}\n' + in result) + assert '\\addto\\captionsgerman{\\renewcommand{\\figurename}{Fig.}}\n' in result + assert '\\addto\\captionsgerman{\\renewcommand{\\tablename}{Table.}}\n' in result + assert '\\def\\pageautorefname{Seite}\n' in result + assert '\\shorthandoff' not in result + + +@pytest.mark.sphinx( + 'latex', testroot='latex-babel', + confoverrides={'language': 'de-1901', 'latex_engine': 'lualatex'}) +def test_polyglossia_with_language_de_1901(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert '\\documentclass[letterpaper,10pt,german]{sphinxmanual}' in result + assert '\\usepackage{polyglossia}' in result + assert '\\setmainlanguage[spelling=old]{german}' in result + assert '\\usepackage{times}' not in result + assert '\\usepackage[Sonny]{fncychap}' in result + assert ('\\addto\\captionsgerman{\\renewcommand{\\contentsname}{Table of content}}\n' + in result) + assert '\\addto\\captionsgerman{\\renewcommand{\\figurename}{Fig.}}\n' in result + assert '\\addto\\captionsgerman{\\renewcommand{\\tablename}{Table.}}\n' in result + assert '\\def\\pageautorefname{page}\n' in result + assert '\\shorthandoff' not in result + + @pytest.mark.sphinx('latex') def test_footnote(app, status, warning): app.builder.build_all() From d388712f6ec11d79cff24724f64addd60adc4045 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 21 Apr 2018 20:01:27 +0900 Subject: [PATCH 46/68] Fix flake8 violation --- sphinx/io.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/io.py b/sphinx/io.py index 3544fffeb..b9fa3f002 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -96,7 +96,8 @@ class SphinxStandaloneReader(SphinxBaseReader): HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, RemoveTranslatableInline, FilterSystemMessages, RefOnlyBulletListTransform, UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink, - SphinxDomains, SubstitutionDefinitionsRemover, DoctreeReadEvent, UIDTransform, + SphinxDomains, SubstitutionDefinitionsRemover, DoctreeReadEvent, + UIDTransform, ] # type: List[Transform] def __init__(self, app, *args, **kwargs): From 6d6864abf0f1eaf75a31afeac0aa54a4d00252d0 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 22 Apr 2018 18:00:59 +0900 Subject: [PATCH 47/68] doc: Remove meaningless spaces --- sphinx/transforms/post_transforms/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 7d52d5b8e..a6b82f262 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -175,7 +175,7 @@ class ImageConverter(BaseImageConverter): PNG and JPEG as image formats. However it does not support SVG images. For such case, to use image converters allows to embed these unsupported images into the document. One of image converters; - :ref:`sphinx.ext. imgconverter ` can convert + :ref:`sphinx.ext.imgconverter ` can convert a SVG image to PNG format using Imagemagick internally. There are three steps to make your custom image converter: From a4dbb6e657bf86e5940e352a2935d1b3264d74e2 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 8 Apr 2018 16:19:40 +0900 Subject: [PATCH 48/68] Improve warning messages during including (refs: #4818) --- CHANGES | 1 + sphinx/directives/other.py | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 7a595320c..a1c77c790 100644 --- a/CHANGES +++ b/CHANGES @@ -71,6 +71,7 @@ Features added * #4834: Ensure set object descriptions are reproducible. * #4828: Allow to override :confval:`numfig_format` partially. Full definition is not needed. +* Improve warning messages during including (refs: #4818) Bugs fixed ---------- diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 7adbe3f10..41b21f917 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -8,6 +8,7 @@ """ import re +from contextlib import contextmanager from docutils import nodes from docutils.parsers.rst import directives @@ -436,6 +437,7 @@ class Include(BaseInclude, SphinxDirective): def run(self): # type: () -> List[nodes.Node] + current_filename = self.env.doc2path(self.env.docname) if self.arguments[0].startswith('<') and \ self.arguments[0].endswith('>'): # docutils "standard" includes, do not do path processing @@ -443,7 +445,27 @@ class Include(BaseInclude, SphinxDirective): rel_filename, filename = self.env.relfn2path(self.arguments[0]) self.arguments[0] = filename self.env.note_included(filename) - return BaseInclude.run(self) + with patched_warnings(self, current_filename): + return BaseInclude.run(self) + + +@contextmanager +def patched_warnings(directive, parent_filename): + # type: (BaseInclude, unicode) -> Generator[None, None, None] + """Add includee filename to the warnings during inclusion.""" + try: + original = directive.state_machine.insert_input + + def insert_input(input_lines, source): + # type: (Any, unicode) -> None + source += ' ' % parent_filename + original(input_lines, source) + + # patch insert_input() temporarily + directive.state_machine.insert_input = insert_input + yield + finally: + directive.state_machine.insert_input = original def setup(app): From f4f693eff7b081785cc5704a6ff22cf0371355f3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 10 Feb 2018 14:25:52 +0900 Subject: [PATCH 49/68] #4459: duplicated labels detector does not work well in parallel build --- CHANGES | 1 + sphinx/domains/c.py | 9 ++++++++- sphinx/domains/javascript.py | 20 +++++++++++++++++++- sphinx/domains/python.py | 25 +++++++++++++++++++++---- sphinx/domains/rst.py | 8 +++++++- sphinx/domains/std.py | 10 +++++++++- sphinx/ext/mathbase.py | 4 ++++ 7 files changed, 69 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index ad3fa708e..a3dca85e9 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Bugs fixed * toctree directive tries to glob for URL having query_string * #4871: html search: Upper characters problem in German * #4717: latex: Compilation for German docs failed with LuaLaTeX and XeLaTeX +* #4459: duplicated labels detector does not work well in parallel build Testing -------- diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index f0c81db7b..df07c7731 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -19,6 +19,7 @@ from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.locale import l_, _ from sphinx.roles import XRefRole +from sphinx.util import logging from sphinx.util.docfields import Field, TypedField from sphinx.util.nodes import make_refnode @@ -29,6 +30,8 @@ if False: from sphinx.builders import Builder # NOQA from sphinx.environment import BuildEnvironment # NOQA +logger = logging.getLogger(__name__) + # RE to split at word boundaries wsplit_re = re.compile(r'(\W+)') @@ -287,9 +290,13 @@ class CDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None - # XXX check duplicates for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: + if fullname in self.data['objects']: + other, _ = self.data['objects'][fullname] + logger.warning('duplicate C object description of %s, ' + 'other instance in %s' % + (fullname, self.env.doc2path(other))) self.data['objects'][fullname] = (fn, objtype) def resolve_xref(self, env, fromdocname, builder, diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index 734969fc1..e64177cdc 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -18,6 +18,7 @@ from sphinx.domains import Domain, ObjType from sphinx.domains.python import _pseudo_parse_arglist from sphinx.locale import l_, _ from sphinx.roles import XRefRole +from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.nodes import make_refnode @@ -29,6 +30,8 @@ if False: from sphinx.builders import Builder # NOQA from sphinx.environment import BuildEnvironment # NOQA +logger = logging.getLogger(__name__) + class JSObject(ObjectDescription): """ @@ -255,6 +258,12 @@ class JSModule(Directive): noindex = 'noindex' in self.options ret = [] if not noindex: + modules = env.domaindata['js']['modules'] + if mod_name in modules: + self.state_machine.reporter.warning( + 'duplicate module description of %s, ' % mod_name + + 'other instance in ' + self.env.doc2path(modules[mod_name]), + line=self.lineno) env.domaindata['js']['modules'][mod_name] = env.docname # Make a duplicate entry in 'objects' to facilitate searching for # the module in JavaScriptDomain.find_obj() @@ -335,12 +344,21 @@ class JavaScriptDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None - # XXX check duplicates for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: + if fullname in self.data['objects']: + otherdoc, _ = self.data['objects'][fullname] + logger.warning('duplicate object description of %s, ' + 'other instance in %s' % + (fullname, self.env.doc2path(otherdoc))) self.data['objects'][fullname] = (fn, objtype) for mod_name, pkg_docname in otherdata['modules'].items(): if pkg_docname in docnames: + if mod_name in self.data['modules']: + otherdoc = self.data['modules'][mod_name] + logger.warning('duplicate module description of %s, ' + 'other instance in %s' % + (mod_name, self.env.doc2path(otherdoc))) self.data['modules'][mod_name] = pkg_docname def find_obj(self, env, mod_name, prefix, name, typ, searchorder=0): diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index b0900b385..47ab5cd12 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -560,9 +560,17 @@ class PyModule(Directive): env.ref_context['py:module'] = modname ret = [] if not noindex: - env.domaindata['py']['modules'][modname] = \ - (env.docname, self.options.get('synopsis', ''), - self.options.get('platform', ''), 'deprecated' in self.options) + modules = env.domaindata['py']['modules'] + if modname in modules: + self.state_machine.reporter.warning( + 'duplicate module description of %s, ' + 'other instance in %s, use :noindex: for one of them' % + (modname, self.env.doc2path(modules[modname])), + line=self.lineno) + modules[modname] = (env.docname, + self.options.get('synopsis', ''), + self.options.get('platform', ''), + 'deprecated' in self.options) # make a duplicate entry in 'objects' to facilitate searching for # the module in PythonDomain.find_obj() env.domaindata['py']['objects'][modname] = (env.docname, 'module') @@ -757,12 +765,21 @@ class PythonDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None - # XXX check duplicates? for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: + if fullname in self.data['objects']: + otherdoc, _ = self.data['objects'][fullname] + logger.warning('duplicate object description of %s, ' + 'other instance in %s, use :noindex: for one of them' % + (fullname, self.env.doc2path(otherdoc))) self.data['objects'][fullname] = (fn, objtype) for modname, data in otherdata['modules'].items(): if data[0] in docnames: + if modname in self.data['modules']: + otherdoc, _, _, _ = self.data['modules'][modname] + logger.warning('duplicate module description of %s, ' + 'other instance in %s, use :noindex: for one of them' % + (modname, self.env.doc2path(otherdoc))) self.data['modules'][modname] = data def find_obj(self, env, modname, classname, name, type, searchmode=0): diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index c14eb180f..609fa501f 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -18,6 +18,7 @@ from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.locale import l_, _ from sphinx.roles import XRefRole +from sphinx.util import logging from sphinx.util.nodes import make_refnode if False: @@ -29,6 +30,7 @@ if False: from sphinx.environment import BuildEnvironment # NOQA +logger = logging.getLogger(__name__) dir_sig_re = re.compile(r'\.\. (.+?)::(.*)$') @@ -139,9 +141,13 @@ class ReSTDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None - # XXX check duplicates for (typ, name), doc in otherdata['objects'].items(): if doc in docnames: + if (typ, name) in self.data['objects']: + otherdoc = self.data['objects'][typ, name] + logger.warning('duplicate description of %s %s, ' + 'other instance in %s' % + (typ, name, self.env.doc2path(otherdoc))) self.data['objects'][typ, name] = doc def resolve_xref(self, env, fromdocname, builder, typ, target, node, diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 937760c45..2b726d3e7 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -547,7 +547,6 @@ class StandardDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None - # XXX duplicates? for key, data in otherdata['progoptions'].items(): if data[0] in docnames: self.data['progoptions'][key] = data @@ -556,6 +555,11 @@ class StandardDomain(Domain): self.data['objects'][key] = data for key, data in otherdata['citations'].items(): if data[0] in docnames: + if key in self.data['citations']: + otherdoc, _, _ = self.data['catations'] + logger.warning('duplicate citation %s, other instance in %s' % + (key, self.env.doc2path(otherdoc)), + type='ref', subtype='citation') self.data['citations'][key] = data for key, data in otherdata['citation_refs'].items(): citation_refs = self.data['citation_refs'].setdefault(key, []) @@ -564,6 +568,10 @@ class StandardDomain(Domain): citation_refs.append(docname) for key, data in otherdata['labels'].items(): if data[0] in docnames: + if key in self.data['labels']: + otherdoc, _, _ = self.data['labels'] + logger.warning('duplicate label %s, other instance in %s' % + (key, self.env.doc2path(otherdoc))) self.data['labels'][key] = data for key, data in otherdata['anonlabels'].items(): if data[0] in docnames: diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index 3cc734537..b217d5d56 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -72,6 +72,10 @@ class MathDomain(Domain): # type: (Iterable[unicode], Dict) -> None for labelid, (doc, eqno) in otherdata['objects'].items(): if doc in docnames: + if labelid in self.data['objects']: + otherdoc, _ = self.data['objects'] + logger.warning('duplicate label of equation %s, other instance in %s' % + (labelid, self.env.doc2path(otherdoc))) self.data['objects'][labelid] = (doc, eqno) def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): From a31c0ddfca721600d630f77a6ef363417f76362c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 22 Apr 2018 19:25:02 +0900 Subject: [PATCH 50/68] Fix #4878: Crashed with extension which returns invalid metadata --- CHANGES | 1 + sphinx/registry.py | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index ad3fa708e..1f4026ddb 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Bugs fixed * toctree directive tries to glob for URL having query_string * #4871: html search: Upper characters problem in German * #4717: latex: Compilation for German docs failed with LuaLaTeX and XeLaTeX +* #4878: Crashed with extension which returns invalid metadata Testing -------- diff --git a/sphinx/registry.py b/sphinx/registry.py index cc012b3f7..cdae77224 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -340,6 +340,7 @@ class SphinxComponentRegistry(object): logger.warning(__('extension %r returned an unsupported object from ' 'its setup() function; it should return None or a ' 'metadata dictionary'), extname) + metadata = {} app.extensions[extname] = Extension(extname, mod, **metadata) app._setting_up_extension.pop() From d97dc05b374b611e779cefe3556863d256492e4c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 22 Apr 2018 20:00:46 +0900 Subject: [PATCH 51/68] Fix #4873: Adjust word-break of deprecated-APIs table --- doc/_themes/sphinx13/static/sphinx13.css | 4 ++++ doc/extdev/index.rst | 2 ++ 2 files changed, 6 insertions(+) diff --git a/doc/_themes/sphinx13/static/sphinx13.css b/doc/_themes/sphinx13/static/sphinx13.css index 24a33fba7..eff18df3c 100644 --- a/doc/_themes/sphinx13/static/sphinx13.css +++ b/doc/_themes/sphinx13/static/sphinx13.css @@ -302,6 +302,10 @@ cite, code, tt { letter-spacing: -0.02em; } +table.deprecated code.literal { + word-break: break-all; +} + tt { background-color: #f2f2f2; border: 1px solid #ddd; diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index ace43fd71..b6c737643 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -99,6 +99,8 @@ The following is a list of deprecated interface. .. list-table:: deprecated APIs :header-rows: 1 + :class: deprecated + :widths: 40, 10, 10, 40 * - Target - Deprecated From d5a108428dcb25b71980a02904b6b2ae0376f296 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 23 Apr 2018 01:23:45 +0900 Subject: [PATCH 52/68] Bump to 1.7.3 final --- CHANGES | 19 ++----------------- sphinx/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/CHANGES b/CHANGES index 48d95d852..c5666219a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,17 +1,5 @@ -Release 1.7.3 (in development) -============================== - -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- - -Features added --------------- +Release 1.7.3 (released Apr 23, 2018) +===================================== Bugs fixed ---------- @@ -35,9 +23,6 @@ Bugs fixed * #4459: duplicated labels detector does not work well in parallel build * #4878: Crashed with extension which returns invalid metadata -Testing --------- - Release 1.7.2 (released Mar 21, 2018) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 67278f4c0..b1fb31b26 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -31,13 +31,13 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '1.7.3+' +__version__ = '1.7.3' __released__ = '1.7.3' # used when Sphinx builds its own docs # version info for better programmatic use # possible values for 3rd element: 'alpha', 'beta', 'rc', 'final' # 'final' has 0 as the last element -version_info = (1, 7, 3, 'beta', 0) +version_info = (1, 7, 3, 'final', 0) package_dir = path.abspath(path.dirname(__file__)) From b4a6841c466e38e8df636d2d967d1f90221acdb1 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 23 Apr 2018 01:30:45 +0900 Subject: [PATCH 53/68] Bump version --- CHANGES | 21 +++++++++++++++++++++ sphinx/__init__.py | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index c5666219a..dd715b8ba 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,24 @@ +Release 1.7.4 (in development) +============================== + +Dependencies +------------ + +Incompatible changes +-------------------- + +Deprecated +---------- + +Features added +-------------- + +Bugs fixed +---------- + +Testing +-------- + Release 1.7.3 (released Apr 23, 2018) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index b1fb31b26..28da450cb 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -31,13 +31,13 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '1.7.3' -__released__ = '1.7.3' # used when Sphinx builds its own docs +__version__ = '1.7.4+' +__released__ = '1.7.4' # used when Sphinx builds its own docs # version info for better programmatic use # possible values for 3rd element: 'alpha', 'beta', 'rc', 'final' # 'final' has 0 as the last element -version_info = (1, 7, 3, 'final', 0) +version_info = (1, 7, 4, 'beta', 0) package_dir = path.abspath(path.dirname(__file__)) From 3735ba39db51ee429344f88b8201cc3ac37496f4 Mon Sep 17 00:00:00 2001 From: Shingo Kitagawa Date: Mon, 23 Apr 2018 17:21:02 +0900 Subject: [PATCH 54/68] fix issue #4885 https://github.com/sphinx-doc/sphinx/issues/4885 --- sphinx/domains/python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 72e794944..89ed2f676 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -584,7 +584,7 @@ class PyModule(Directive): self.state_machine.reporter.warning( 'duplicate module description of %s, ' 'other instance in %s, use :noindex: for one of them' % - (modname, self.env.doc2path(modules[modname])), + (modname, env.doc2path(modules[modname][0])), line=self.lineno) modules[modname] = (env.docname, self.options.get('synopsis', ''), From 3b36ac8f53322036c5fe91ffda730718998f1650 Mon Sep 17 00:00:00 2001 From: Sumana Harihareswara Date: Mon, 23 Apr 2018 09:27:17 -0400 Subject: [PATCH 55/68] Fix PyPI link in theming docs --- doc/theming.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/theming.rst b/doc/theming.rst index bbc52cbbe..e20ab49ef 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -427,6 +427,6 @@ Third Party Themes Besides this, there are a lot of third party themes. You can find them on PyPI__, GitHub__, sphinx-themes.org__ and so on. -.. __: https://pypi.python.org/pypi?:action=browse&c=599 +.. __: https://pypi.org/search/?q=&o=&c=Framework+%3A%3A+Sphinx+%3A%3A+Theme .. __: https://github.com/search?utf8=%E2%9C%93&q=sphinx+theme&type= .. __: https://sphinx-themes.org/ From 51f7d351a141a623efd06f2682d90ce8aad412e4 Mon Sep 17 00:00:00 2001 From: Shingo Kitagawa Date: Mon, 23 Apr 2018 17:21:02 +0900 Subject: [PATCH 56/68] fix issue #4885 https://github.com/sphinx-doc/sphinx/issues/4885 --- sphinx/domains/python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 47ab5cd12..25c83244c 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -565,7 +565,7 @@ class PyModule(Directive): self.state_machine.reporter.warning( 'duplicate module description of %s, ' 'other instance in %s, use :noindex: for one of them' % - (modname, self.env.doc2path(modules[modname])), + (modname, env.doc2path(modules[modname][0])), line=self.lineno) modules[modname] = (env.docname, self.options.get('synopsis', ''), From 7a13a5996b1315d5edbd8897d501018f0b22b317 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 24 Apr 2018 00:44:27 +0900 Subject: [PATCH 57/68] Update CHANGES for PR #4887 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index dd715b8ba..856d23e8b 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,8 @@ Features added Bugs fixed ---------- +* #4885, #4887: py domain: Crashed with duplicated objects + Testing -------- From 1a59aca8a107e053b865bd1b5193f930d0fb8c24 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 24 Apr 2018 01:02:39 +0900 Subject: [PATCH 58/68] Fix #4889: latex: sphinx.writers.latex causes recusrive import --- CHANGES | 1 + sphinx/writers/latex.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 856d23e8b..6812d23fe 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Bugs fixed ---------- * #4885, #4887: py domain: Crashed with duplicated objects +* #4889: latex: sphinx.writers.latex causes recusrive import Testing -------- diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index f5209eb38..17eabc462 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -23,7 +23,6 @@ from six import itervalues, text_type from sphinx import addnodes from sphinx import highlighting -from sphinx.builders.latex.transforms import URI_SCHEMES, ShowUrlsTransform # NOQA # for compatibility from sphinx.errors import SphinxError from sphinx.locale import admonitionlabels, _ from sphinx.util import split_into, logging @@ -2561,3 +2560,10 @@ class LaTeXTranslator(nodes.NodeVisitor): def unknown_visit(self, node): # type: (nodes.Node) -> None raise NotImplementedError('Unknown node: ' + node.__class__.__name__) + + +# Import old modules here for compatibility +# They should be imported after `LaTeXTranslator` to avoid recursive import. +# +# refs: https://github.com/sphinx-doc/sphinx/issues/4889 +from sphinx.builders.latex.transforms import URI_SCHEMES, ShowUrlsTransform # NOQA From d2ed2d4c4c44fa2009bfdf536a85eeda0c6b72f2 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 24 Apr 2018 01:09:34 +0900 Subject: [PATCH 59/68] Fix #4885: jsdomain also crashed with duplicated objects --- CHANGES | 2 +- sphinx/domains/javascript.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 856d23e8b..5456397ca 100644 --- a/CHANGES +++ b/CHANGES @@ -16,7 +16,7 @@ Features added Bugs fixed ---------- -* #4885, #4887: py domain: Crashed with duplicated objects +* #4885, #4887: domains: Crashed with duplicated objects Testing -------- diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index e64177cdc..eca7edda0 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -262,7 +262,7 @@ class JSModule(Directive): if mod_name in modules: self.state_machine.reporter.warning( 'duplicate module description of %s, ' % mod_name + - 'other instance in ' + self.env.doc2path(modules[mod_name]), + 'other instance in ' + env.doc2path(modules[mod_name]), line=self.lineno) env.domaindata['js']['modules'][mod_name] = env.docname # Make a duplicate entry in 'objects' to facilitate searching for From 0a0803fd450ec618f24a03cf9ef4309b9db1d36b Mon Sep 17 00:00:00 2001 From: jfbu Date: Tue, 24 Apr 2018 14:09:53 +0200 Subject: [PATCH 60/68] Update CHANGES for PR #4868 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 069a89ec8..aa7d8cc65 100644 --- a/CHANGES +++ b/CHANGES @@ -72,6 +72,8 @@ Features added * #4828: Allow to override :confval:`numfig_format` partially. Full definition is not needed. * Improve warning messages during including (refs: #4818) +* LaTeX: separate customizability of :rst:role:`guilabel` and + :rst:role:`menuselection` (refs: #4830) Bugs fixed ---------- From acb7edcae85318e373725fd003e3609fdef71d79 Mon Sep 17 00:00:00 2001 From: jfbu Date: Wed, 25 Apr 2018 10:26:12 +0200 Subject: [PATCH 61/68] Fix typos in a docstring (refs: #4901) --- sphinx/util/logging.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index 1ac4fb327..5034c007d 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -62,13 +62,13 @@ def getLogger(name): # type: (str) -> SphinxLoggerAdapter """Get logger wrapped by :class:`sphinx.util.logging.SphinxLoggerAdapter`. - Sphinx logger always uses ``sphinx.*`` namesapce to be independent from - settings of root logger. It ensure logging is consistent even if a + Sphinx logger always uses ``sphinx.*`` namespace to be independent from + settings of root logger. It ensures logging is consistent even if a third-party extension or imported application resets logger settings. Example usage:: - >>> from sphinx.utils import logging + >>> from sphinx.util import logging >>> logger = logging.getLogger(__name__) >>> logger.info('Hello, this is an extension!') Hello, this is an extension! From 10d127d017727835085639c941e11817b0096b30 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 25 Apr 2018 22:44:03 +0900 Subject: [PATCH 62/68] Revert f4f693eff7b081785cc5704a6ff22cf0371355f3 --- sphinx/domains/c.py | 9 +-------- sphinx/domains/javascript.py | 20 +------------------- sphinx/domains/python.py | 26 +++++--------------------- sphinx/domains/rst.py | 8 +------- sphinx/domains/std.py | 10 +--------- sphinx/ext/mathbase.py | 4 ---- 6 files changed, 9 insertions(+), 68 deletions(-) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index df07c7731..f0c81db7b 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -19,7 +19,6 @@ from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.locale import l_, _ from sphinx.roles import XRefRole -from sphinx.util import logging from sphinx.util.docfields import Field, TypedField from sphinx.util.nodes import make_refnode @@ -30,8 +29,6 @@ if False: from sphinx.builders import Builder # NOQA from sphinx.environment import BuildEnvironment # NOQA -logger = logging.getLogger(__name__) - # RE to split at word boundaries wsplit_re = re.compile(r'(\W+)') @@ -290,13 +287,9 @@ class CDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None + # XXX check duplicates for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: - if fullname in self.data['objects']: - other, _ = self.data['objects'][fullname] - logger.warning('duplicate C object description of %s, ' - 'other instance in %s' % - (fullname, self.env.doc2path(other))) self.data['objects'][fullname] = (fn, objtype) def resolve_xref(self, env, fromdocname, builder, diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index eca7edda0..734969fc1 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -18,7 +18,6 @@ from sphinx.domains import Domain, ObjType from sphinx.domains.python import _pseudo_parse_arglist from sphinx.locale import l_, _ from sphinx.roles import XRefRole -from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.nodes import make_refnode @@ -30,8 +29,6 @@ if False: from sphinx.builders import Builder # NOQA from sphinx.environment import BuildEnvironment # NOQA -logger = logging.getLogger(__name__) - class JSObject(ObjectDescription): """ @@ -258,12 +255,6 @@ class JSModule(Directive): noindex = 'noindex' in self.options ret = [] if not noindex: - modules = env.domaindata['js']['modules'] - if mod_name in modules: - self.state_machine.reporter.warning( - 'duplicate module description of %s, ' % mod_name + - 'other instance in ' + env.doc2path(modules[mod_name]), - line=self.lineno) env.domaindata['js']['modules'][mod_name] = env.docname # Make a duplicate entry in 'objects' to facilitate searching for # the module in JavaScriptDomain.find_obj() @@ -344,21 +335,12 @@ class JavaScriptDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None + # XXX check duplicates for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: - if fullname in self.data['objects']: - otherdoc, _ = self.data['objects'][fullname] - logger.warning('duplicate object description of %s, ' - 'other instance in %s' % - (fullname, self.env.doc2path(otherdoc))) self.data['objects'][fullname] = (fn, objtype) for mod_name, pkg_docname in otherdata['modules'].items(): if pkg_docname in docnames: - if mod_name in self.data['modules']: - otherdoc = self.data['modules'][mod_name] - logger.warning('duplicate module description of %s, ' - 'other instance in %s' % - (mod_name, self.env.doc2path(otherdoc))) self.data['modules'][mod_name] = pkg_docname def find_obj(self, env, mod_name, prefix, name, typ, searchorder=0): diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 25c83244c..2b46596af 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -560,17 +560,10 @@ class PyModule(Directive): env.ref_context['py:module'] = modname ret = [] if not noindex: - modules = env.domaindata['py']['modules'] - if modname in modules: - self.state_machine.reporter.warning( - 'duplicate module description of %s, ' - 'other instance in %s, use :noindex: for one of them' % - (modname, env.doc2path(modules[modname][0])), - line=self.lineno) - modules[modname] = (env.docname, - self.options.get('synopsis', ''), - self.options.get('platform', ''), - 'deprecated' in self.options) + env.domaindata['py']['modules'][modname] = (env.docname, + self.options.get('synopsis', ''), + self.options.get('platform', ''), + 'deprecated' in self.options) # make a duplicate entry in 'objects' to facilitate searching for # the module in PythonDomain.find_obj() env.domaindata['py']['objects'][modname] = (env.docname, 'module') @@ -765,21 +758,12 @@ class PythonDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None + # XXX check duplicates? for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: - if fullname in self.data['objects']: - otherdoc, _ = self.data['objects'][fullname] - logger.warning('duplicate object description of %s, ' - 'other instance in %s, use :noindex: for one of them' % - (fullname, self.env.doc2path(otherdoc))) self.data['objects'][fullname] = (fn, objtype) for modname, data in otherdata['modules'].items(): if data[0] in docnames: - if modname in self.data['modules']: - otherdoc, _, _, _ = self.data['modules'][modname] - logger.warning('duplicate module description of %s, ' - 'other instance in %s, use :noindex: for one of them' % - (modname, self.env.doc2path(otherdoc))) self.data['modules'][modname] = data def find_obj(self, env, modname, classname, name, type, searchmode=0): diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index 609fa501f..c14eb180f 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -18,7 +18,6 @@ from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.locale import l_, _ from sphinx.roles import XRefRole -from sphinx.util import logging from sphinx.util.nodes import make_refnode if False: @@ -30,7 +29,6 @@ if False: from sphinx.environment import BuildEnvironment # NOQA -logger = logging.getLogger(__name__) dir_sig_re = re.compile(r'\.\. (.+?)::(.*)$') @@ -141,13 +139,9 @@ class ReSTDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None + # XXX check duplicates for (typ, name), doc in otherdata['objects'].items(): if doc in docnames: - if (typ, name) in self.data['objects']: - otherdoc = self.data['objects'][typ, name] - logger.warning('duplicate description of %s %s, ' - 'other instance in %s' % - (typ, name, self.env.doc2path(otherdoc))) self.data['objects'][typ, name] = doc def resolve_xref(self, env, fromdocname, builder, typ, target, node, diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 2b726d3e7..937760c45 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -547,6 +547,7 @@ class StandardDomain(Domain): def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None + # XXX duplicates? for key, data in otherdata['progoptions'].items(): if data[0] in docnames: self.data['progoptions'][key] = data @@ -555,11 +556,6 @@ class StandardDomain(Domain): self.data['objects'][key] = data for key, data in otherdata['citations'].items(): if data[0] in docnames: - if key in self.data['citations']: - otherdoc, _, _ = self.data['catations'] - logger.warning('duplicate citation %s, other instance in %s' % - (key, self.env.doc2path(otherdoc)), - type='ref', subtype='citation') self.data['citations'][key] = data for key, data in otherdata['citation_refs'].items(): citation_refs = self.data['citation_refs'].setdefault(key, []) @@ -568,10 +564,6 @@ class StandardDomain(Domain): citation_refs.append(docname) for key, data in otherdata['labels'].items(): if data[0] in docnames: - if key in self.data['labels']: - otherdoc, _, _ = self.data['labels'] - logger.warning('duplicate label %s, other instance in %s' % - (key, self.env.doc2path(otherdoc))) self.data['labels'][key] = data for key, data in otherdata['anonlabels'].items(): if data[0] in docnames: diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index b217d5d56..3cc734537 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -72,10 +72,6 @@ class MathDomain(Domain): # type: (Iterable[unicode], Dict) -> None for labelid, (doc, eqno) in otherdata['objects'].items(): if doc in docnames: - if labelid in self.data['objects']: - otherdoc, _ = self.data['objects'] - logger.warning('duplicate label of equation %s, other instance in %s' % - (labelid, self.env.doc2path(otherdoc))) self.data['objects'][labelid] = (doc, eqno) def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): From f7b3292d87e9a2b7eae0b4ef72e87779beefc699 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 26 Apr 2018 00:54:38 +0900 Subject: [PATCH 63/68] Bump to 1.7.4 final --- CHANGES | 19 ++----------------- sphinx/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/CHANGES b/CHANGES index 6af237189..ef4fe78b4 100644 --- a/CHANGES +++ b/CHANGES @@ -1,17 +1,5 @@ -Release 1.7.4 (in development) -============================== - -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- - -Features added --------------- +Release 1.7.4 (released Apr 25, 2018) +===================================== Bugs fixed ---------- @@ -19,9 +7,6 @@ Bugs fixed * #4885, #4887: domains: Crashed with duplicated objects * #4889: latex: sphinx.writers.latex causes recusrive import -Testing --------- - Release 1.7.3 (released Apr 23, 2018) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 28da450cb..1c276d66a 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -31,13 +31,13 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '1.7.4+' +__version__ = '1.7.4' __released__ = '1.7.4' # used when Sphinx builds its own docs # version info for better programmatic use # possible values for 3rd element: 'alpha', 'beta', 'rc', 'final' # 'final' has 0 as the last element -version_info = (1, 7, 4, 'beta', 0) +version_info = (1, 7, 4, 'final', 0) package_dir = path.abspath(path.dirname(__file__)) From 6ad0ad6f1721bcf5f04765db9b1fdb315d9042b6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 26 Apr 2018 01:02:14 +0900 Subject: [PATCH 64/68] Bump version --- CHANGES | 21 +++++++++++++++++++++ sphinx/__init__.py | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index ef4fe78b4..43c14e5b4 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,24 @@ +Release 1.7.5 (in development) +============================== + +Dependencies +------------ + +Incompatible changes +-------------------- + +Deprecated +---------- + +Features added +-------------- + +Bugs fixed +---------- + +Testing +-------- + Release 1.7.4 (released Apr 25, 2018) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 1c276d66a..7b3ddfea8 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -31,13 +31,13 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '1.7.4' -__released__ = '1.7.4' # used when Sphinx builds its own docs +__version__ = '1.7.5+' +__released__ = '1.7.5' # used when Sphinx builds its own docs # version info for better programmatic use # possible values for 3rd element: 'alpha', 'beta', 'rc', 'final' # 'final' has 0 as the last element -version_info = (1, 7, 4, 'final', 0) +version_info = (1, 7, 5, 'beta', 0) package_dir = path.abspath(path.dirname(__file__)) From b165ade6b0cbf74616876c80ff668a43b5ea7498 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 28 Apr 2018 13:04:21 +0900 Subject: [PATCH 65/68] Rename Config.read_from_py() to Config.read() --- CHANGES | 3 ++- doc/extdev/index.rst | 7 ++++++- sphinx/application.py | 5 ++--- sphinx/config.py | 6 ++++-- tests/test_config.py | 8 ++++---- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 282c6106f..4a94fc3e6 100644 --- a/CHANGES +++ b/CHANGES @@ -47,6 +47,7 @@ Deprecated argument has been deprecated * ``Config.check_types()`` is deprecated * ``Config.check_unicode()`` is deprecated +* ``sphinx.application.CONFIG_FILENAME`` is deprecated For more details, see `deprecation APIs list `_ @@ -75,7 +76,7 @@ Features added * #4834: Ensure set object descriptions are reproducible. * #4828: Allow to override :confval:`numfig_format` partially. Full definition is not needed. -* Add ``Config.from_conf_py()`` classmethod to create a new config object from +* Add ``Config.read()`` classmethod to create a new config object from configuration file Bugs fixed diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index f0236ed3c..e096bf993 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -119,6 +119,11 @@ The following is a list of deprecated interface. - 4.0 - :meth:`~sphinx.application.Sphinx.add_css_file()` + * - ``sphinx.application.CONFIG_FILENAME`` + - 1.8 + - 3.0 + - ``sphinx.config.CONFIG_FILENAME`` + * - ``Config.check_unicode()`` - 1.8 - 3.0 @@ -133,7 +138,7 @@ The following is a list of deprecated interface. ``Config.__init__()`` - 1.8 - 3.0 - - ``Config.from_conf_py()`` + - ``Config.read()`` * - ``sphinx.versioning.prepare()`` - 1.8 diff --git a/sphinx/application.py b/sphinx/application.py index 26aec9e39..0fbc6d2cb 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -26,6 +26,7 @@ from six.moves import cStringIO import sphinx from sphinx import package_dir, locale +from sphinx.config import CONFIG_FILENAME # NOQA # for compatibility (RemovedInSphinx30) from sphinx.config import Config, check_unicode from sphinx.deprecation import ( RemovedInSphinx20Warning, RemovedInSphinx30Warning, RemovedInSphinx40Warning @@ -108,7 +109,6 @@ builtin_extensions = ( 'alabaster', ) # type: Tuple[unicode, ...] -CONFIG_FILENAME = 'conf.py' ENV_PICKLE_FILENAME = 'environment.pickle' logger = logging.getLogger(__name__) @@ -190,8 +190,7 @@ class Sphinx(object): if self.confdir is None: self.config = Config({}, confoverrides or {}) else: - self.config = Config.from_conf_py(path.join(self.confdir, CONFIG_FILENAME), - confoverrides or {}, self.tags) + self.config = Config.read(self.confdir, confoverrides or {}, self.tags) check_unicode(self.config) # initialize some limited config variables before initialize i18n and loading diff --git a/sphinx/config.py b/sphinx/config.py index 0a7f71805..58e5be277 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -34,6 +34,7 @@ if False: logger = logging.getLogger(__name__) +CONFIG_FILENAME = 'conf.py' copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])') if PY3: @@ -148,7 +149,7 @@ class Config(object): if len(args) == 4: # old style arguments: (dirname, filename, overrides, tags) warnings.warn('The argument of Config() class has been changed. ' - 'Use Config.from_conf_py() to read configuration from conf.py.', + 'Use Config.read() to read configuration from conf.py.', RemovedInSphinx30Warning) dirname, filename, overrides, tags = args if dirname is None: @@ -177,9 +178,10 @@ class Config(object): self.extensions = config.get('extensions', []) # type: List[unicode] @classmethod - def from_conf_py(cls, filename, overrides=None, tags=None): + def read(cls, confdir, overrides=None, tags=None): # type: (unicode, Dict, Tags) -> Config """Create a Config object from configuration file.""" + filename = path.join(confdir, CONFIG_FILENAME) namespace = eval_config_file(filename, tags) return cls(namespace, overrides or {}) diff --git a/tests/test_config.py b/tests/test_config.py index 078a05a2a..e3b79c835 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -124,14 +124,14 @@ def test_errors_warnings(logger, tempdir): # test the error for syntax errors in the config file (tempdir / 'conf.py').write_text(u'project = \n', encoding='ascii') with pytest.raises(ConfigError) as excinfo: - Config.from_conf_py(tempdir / 'conf.py', {}, None) + Config.read(tempdir, {}, None) assert 'conf.py' in str(excinfo.value) # test the automatic conversion of 2.x only code in configs (tempdir / 'conf.py').write_text( u'# -*- coding: utf-8\n\nproject = u"Jägermeister"\n', encoding='utf-8') - cfg = Config.from_conf_py(tempdir / 'conf.py', {}, None) + cfg = Config.read(tempdir, {}, None) cfg.init_values() assert cfg.project == u'Jägermeister' assert logger.called is False @@ -143,7 +143,7 @@ def test_errors_warnings(logger, tempdir): return (tempdir / 'conf.py').write_text( u'# -*- coding: latin-1\nproject = "fooä"\n', encoding='latin-1') - cfg = Config.from_conf_py(tempdir / 'conf.py', {}, None) + cfg = Config.read(tempdir, {}, None) assert logger.warning.called is False cfg.check_unicode() @@ -202,7 +202,7 @@ def test_config_eol(logger, tempdir): configfile = tempdir / 'conf.py' for eol in (b'\n', b'\r\n'): configfile.write_bytes(b'project = "spam"' + eol) - cfg = Config.from_conf_py(tempdir / 'conf.py', {}, None) + cfg = Config.read(tempdir, {}, None) cfg.init_values() assert cfg.project == u'spam' assert logger.called is False From 6b397d1fb1c8c16da2e24522cb7b8db63918bdbf Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 28 Apr 2018 14:34:49 +0900 Subject: [PATCH 66/68] Show warnings for deprecated html_search_options --- doc/config.rst | 6 ++++-- doc/extdev/index.rst | 5 +++++ sphinx/search/ja.py | 7 ++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/config.rst b/doc/config.rst index be0be745f..2558a35f9 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -1194,8 +1194,10 @@ that use Sphinx's HTMLWriter class. Janome binding. To use this splitter, `Janome `_ is required. - To keep compatibility, ``'mecab'``, ``'janome'`` and ``'default'`` are also - acceptable. However it will be deprecated in Sphinx-1.6. + .. deprecated:: 1.6 + ``'mecab'``, ``'janome'`` and ``'default'`` is deprecated. + To keep compatibility, ``'mecab'``, ``'janome'`` and ``'default'`` are + also acceptable. Other option values depend on splitter value which you choose. diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index e4dfe14c7..db22f2276 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -121,6 +121,11 @@ The following is a list of deprecated interface. - 4.0 - :meth:`~sphinx.application.Sphinx.add_css_file()` + * - The value of :confval:`html_search_options` + - 1.8 + - 3.0 + - see :confval:`html_search_options` + * - ``sphinx.versioning.prepare()`` - 1.8 - 3.0 diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py index 0cdc14a11..a48a0df69 100644 --- a/sphinx/search/ja.py +++ b/sphinx/search/ja.py @@ -20,6 +20,7 @@ import os import re import sys +import warnings from six import iteritems, PY3 @@ -35,6 +36,7 @@ try: except ImportError: janome_module = False +from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.errors import SphinxError, ExtensionError from sphinx.search import SearchLanguage from sphinx.util import import_object @@ -556,9 +558,12 @@ class SearchJapanese(SearchLanguage): def init(self, options): # type: (Dict) -> None - type = options.get('type', 'default') + type = options.get('type', 'sphinx.search.ja.DefaultSplitter') if type in self.splitters: dotted_path = self.splitters[type] + warnings.warn('html_search_options["type"]: %s is deprecated. ' + 'Please give "%s" instead.' % (type, dotted_path), + RemovedInSphinx30Warning) else: dotted_path = type try: From e38218fdf5bfd697f7f860dc2226533afa36d1e4 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 28 Apr 2018 19:15:24 +0900 Subject: [PATCH 67/68] Fix flake8 violation --- sphinx/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/application.py b/sphinx/application.py index 0fbc6d2cb..97b9944d2 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -26,8 +26,8 @@ from six.moves import cStringIO import sphinx from sphinx import package_dir, locale -from sphinx.config import CONFIG_FILENAME # NOQA # for compatibility (RemovedInSphinx30) from sphinx.config import Config, check_unicode +from sphinx.config import CONFIG_FILENAME # NOQA # for compatibility (RemovedInSphinx30) from sphinx.deprecation import ( RemovedInSphinx20Warning, RemovedInSphinx30Warning, RemovedInSphinx40Warning ) From eb0c57ad79821b3a640bd3440abf12744ef8cc40 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 28 Apr 2018 15:52:55 -0700 Subject: [PATCH 68/68] Fix typo: remove space before full stop --- sphinx/templates/quickstart/conf.py_t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/templates/quickstart/conf.py_t b/sphinx/templates/quickstart/conf.py_t index 6a648f39c..cc8073a82 100644 --- a/sphinx/templates/quickstart/conf.py_t +++ b/sphinx/templates/quickstart/conf.py_t @@ -74,7 +74,7 @@ language = {{ language | repr }} # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . +# This pattern also affects html_static_path and html_extra_path. exclude_patterns = [{{ exclude_patterns }}] # The name of the Pygments (syntax highlighting) style to use.