mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
commit
a1d78b330b
7
CHANGES
7
CHANGES
@ -43,6 +43,11 @@ Deprecated
|
||||
* ``app.override_domain()`` is deprecated
|
||||
* ``app.add_stylesheet()`` is deprecated
|
||||
* ``sphinx.versioning.prepare()`` is deprecated
|
||||
* ``Config.__init__()`` has changed; the *dirname*, *filename* and *tags*
|
||||
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
|
||||
<http://www.sphinx-doc.org/en/master/extdev/index.html#deprecated-apis>`_
|
||||
@ -74,6 +79,8 @@ Features added
|
||||
* Improve warning messages during including (refs: #4818)
|
||||
* LaTeX: separate customizability of :rst:role:`guilabel` and
|
||||
:rst:role:`menuselection` (refs: #4830)
|
||||
* Add ``Config.read()`` classmethod to create a new config object from
|
||||
configuration file
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
@ -121,6 +121,27 @@ 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
|
||||
- ``sphinx.config.check_unicode()``
|
||||
|
||||
* - ``Config.check_types()``
|
||||
- 1.8
|
||||
- 3.0
|
||||
- ``sphinx.config.check_confval_types()``
|
||||
|
||||
* - ``dirname``, ``filename`` and ``tags`` arguments of
|
||||
``Config.__init__()``
|
||||
- 1.8
|
||||
- 3.0
|
||||
- ``Config.read()``
|
||||
|
||||
* - The value of :confval:`html_search_options`
|
||||
- 1.8
|
||||
- 3.0
|
||||
|
@ -26,14 +26,13 @@ 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.config import CONFIG_FILENAME # NOQA # for compatibility (RemovedInSphinx30)
|
||||
from sphinx.deprecation import (
|
||||
RemovedInSphinx20Warning, RemovedInSphinx30Warning, RemovedInSphinx40Warning
|
||||
)
|
||||
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
|
||||
@ -110,7 +109,6 @@ builtin_extensions = (
|
||||
'alabaster',
|
||||
) # type: Tuple[unicode, ...]
|
||||
|
||||
CONFIG_FILENAME = 'conf.py'
|
||||
ENV_PICKLE_FILENAME = 'environment.pickle'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -189,10 +187,11 @@ class Sphinx(object):
|
||||
|
||||
# read config
|
||||
self.tags = Tags(tags)
|
||||
self.config = Config(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({}, confoverrides or {})
|
||||
else:
|
||||
self.config = Config.read(self.confdir, confoverrides or {}, self.tags)
|
||||
check_unicode(self.config)
|
||||
|
||||
# initialize some limited config variables before initialize i18n and loading
|
||||
# extensions
|
||||
@ -250,8 +249,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
|
||||
@ -561,8 +558,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)
|
||||
|
@ -323,7 +323,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)
|
||||
|
250
sphinx/config.py
250
sphinx/config.py
@ -11,13 +11,15 @@
|
||||
|
||||
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.errors import ConfigError
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||
from sphinx.errors import ConfigError, ExtensionError
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.i18n import format_date
|
||||
@ -26,28 +28,15 @@ 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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
nonascii_re = re.compile(br'[\x80-\xff]')
|
||||
CONFIG_FILENAME = 'conf.py'
|
||||
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__}', "
|
||||
"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...
|
||||
|
||||
@ -155,30 +144,30 @@ 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
|
||||
if dirname is None:
|
||||
config = {} # type: Dict[unicode, Any]
|
||||
else:
|
||||
config = eval_config_file(path.join(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:
|
||||
@ -188,69 +177,25 @@ 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, 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 {})
|
||||
|
||||
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
|
||||
# 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
|
||||
@ -346,19 +291,49 @@ 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
|
||||
|
||||
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]
|
||||
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(filename, tags):
|
||||
# type: (unicode, Tags) -> Dict[unicode, Any]
|
||||
"""Evaluate a config file."""
|
||||
namespace = {} # type: Dict[unicode, Any]
|
||||
namespace['__file__'] = filename
|
||||
namespace['tags'] = tags
|
||||
|
||||
with cd(path.dirname(filename)):
|
||||
# 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):
|
||||
@ -400,10 +375,91 @@ def init_numfig_format(app, config):
|
||||
config.numfig_format = numfig_format # type: ignore
|
||||
|
||||
|
||||
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 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 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)
|
||||
app.connect('config-inited', init_numfig_format)
|
||||
app.connect('config-inited', correct_copyright_year)
|
||||
app.connect('config-inited', check_confval_types)
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -13,7 +13,7 @@
|
||||
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 _
|
||||
|
@ -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 <Tests>'
|
||||
release = '0.6alpha1'
|
||||
templates_path = ['_templates']
|
||||
|
@ -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')
|
||||
|
@ -1,5 +0,0 @@
|
||||
# Test extension module
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_config_value('value_from_ext', [], False)
|
@ -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
|
||||
|
@ -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, check_confval_types
|
||||
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',
|
||||
@ -74,36 +74,64 @@ 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)
|
||||
|
||||
|
||||
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
|
||||
(tempdir / 'conf.py').write_text(u'project = \n', encoding='ascii')
|
||||
with pytest.raises(ConfigError) as excinfo:
|
||||
Config(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(tempdir, 'conf.py', {}, None)
|
||||
cfg = Config.read(tempdir, {}, None)
|
||||
cfg.init_values()
|
||||
assert cfg.project == u'Jägermeister'
|
||||
assert logger.called is False
|
||||
@ -115,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(tempdir, 'conf.py', {}, None)
|
||||
cfg = Config.read(tempdir, {}, None)
|
||||
|
||||
assert logger.warning.called is False
|
||||
cfg.check_unicode()
|
||||
@ -174,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(tempdir, 'conf.py', {}, None)
|
||||
cfg = Config.read(tempdir, {}, None)
|
||||
cfg.init_values()
|
||||
assert cfg.project == u'spam'
|
||||
assert logger.called is False
|
||||
@ -195,60 +223,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()
|
||||
check_confval_types(None, config)
|
||||
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()
|
||||
check_confval_types(None, config)
|
||||
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()
|
||||
check_confval_types(None, config)
|
||||
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()
|
||||
check_confval_types(None, config)
|
||||
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()
|
||||
check_confval_types(None, config)
|
||||
logger.warning.assert_called()
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user