summaryrefslogtreecommitdiff
path: root/sphinx/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/config.py')
-rw-r--r--sphinx/config.py251
1 files changed, 138 insertions, 113 deletions
diff --git a/sphinx/config.py b/sphinx/config.py
index e456d00b6..338de770c 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
sphinx.config
~~~~~~~~~~~~~
@@ -17,11 +16,7 @@ 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, class_types
-)
-
-from sphinx.deprecation import RemovedInSphinx30Warning
+from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
from sphinx.errors import ConfigError, ExtensionError
from sphinx.locale import _, __
from sphinx.util import logging
@@ -31,22 +26,20 @@ from sphinx.util.pycompat import execfile_, NoneType
if False:
# For type annotation
- from typing import Any, Callable, Dict, Generator, Iterator, List, Tuple, Union # NOQA
+ from typing import Any, Callable, Dict, Generator, Iterator, List, Set, Tuple, Union # NOQA
from sphinx.application import Sphinx # NOQA
+ from sphinx.environment import BuildEnvironment # NOQA
from sphinx.util.tags import Tags # NOQA
logger = logging.getLogger(__name__)
CONFIG_FILENAME = 'conf.py'
-UNSERIALIZABLE_TYPES = class_types + (types.ModuleType, types.FunctionType)
+UNSERIALIZABLE_TYPES = (type, types.ModuleType, types.FunctionType)
copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])')
-if PY3:
- unicode = str # special alias for static typing...
-
ConfigValue = NamedTuple('ConfigValue', [('name', str),
('value', Any),
- ('rebuild', Union[bool, unicode])])
+ ('rebuild', Union[bool, str])])
def is_serializable(obj):
@@ -55,7 +48,7 @@ def is_serializable(obj):
if isinstance(obj, UNSERIALIZABLE_TYPES):
return False
elif isinstance(obj, dict):
- for key, value in iteritems(obj):
+ for key, value in obj.items():
if not is_serializable(key) or not is_serializable(value):
return False
elif isinstance(obj, (list, tuple, set)):
@@ -64,30 +57,29 @@ def is_serializable(obj):
return True
-class ENUM(object):
+class ENUM:
"""represents the config value should be a one of candidates.
Example:
app.add_config_value('latex_show_urls', 'no', None, ENUM('no', 'footnote', 'inline'))
"""
def __init__(self, *candidates):
- # type: (unicode) -> None
+ # type: (str) -> None
self.candidates = candidates
def match(self, value):
- # type: (Union[unicode,List,Tuple]) -> bool
+ # type: (Union[str, List, Tuple]) -> bool
if isinstance(value, (list, tuple)):
return all(item in self.candidates for item in value)
else:
return value in self.candidates
-string_classes = [text_type] # type: List
-if PY2:
- string_classes.append(binary_type) # => [str, unicode]
+# RemovedInSphinx40Warning
+string_classes = [str] # type: List
-class Config(object):
+class Config:
"""Configuration file abstraction.
The config object makes the values of all config values available as
@@ -104,63 +96,63 @@ class Config(object):
# If you add a value here, don't forget to include it in the
# quickstart.py file template as well as in the docs!
- config_values = dict(
+ config_values = {
# general options
- project = ('Python', 'env', []),
- author = ('unknown', 'env', []),
- copyright = ('', 'html', []),
- version = ('', 'env', []),
- release = ('', 'env', []),
- today = ('', 'env', []),
+ 'project': ('Python', 'env', []),
+ 'author': ('unknown', 'env', []),
+ 'copyright': ('', 'html', []),
+ 'version': ('', 'env', []),
+ 'release': ('', 'env', []),
+ 'today': ('', 'env', []),
# the real default is locale-dependent
- today_fmt = (None, 'env', string_classes),
-
- language = (None, 'env', string_classes),
- locale_dirs = (['locales'], 'env', []),
- figure_language_filename = (u'{root}.{language}{ext}', 'env', [str]),
-
- master_doc = ('contents', 'env', []),
- source_suffix = ({'.rst': 'restructuredtext'}, 'env', Any),
- source_encoding = ('utf-8-sig', 'env', []),
- source_parsers = ({}, 'env', []),
- exclude_patterns = ([], 'env', []),
- default_role = (None, 'env', string_classes),
- add_function_parentheses = (True, 'env', []),
- add_module_names = (True, 'env', []),
- trim_footnote_reference_space = (False, 'env', []),
- show_authors = (False, 'env', []),
- pygments_style = (None, 'html', string_classes),
- highlight_language = ('default', 'env', []),
- highlight_options = ({}, 'env', []),
- templates_path = ([], 'html', []),
- template_bridge = (None, 'html', string_classes),
- keep_warnings = (False, 'env', []),
- suppress_warnings = ([], 'env', []),
- modindex_common_prefix = ([], 'html', []),
- rst_epilog = (None, 'env', string_classes),
- rst_prolog = (None, 'env', string_classes),
- trim_doctest_flags = (True, 'env', []),
- primary_domain = ('py', 'env', [NoneType]), # type: ignore
- needs_sphinx = (None, None, string_classes),
- needs_extensions = ({}, None, []),
- manpages_url = (None, 'env', []),
- nitpicky = (False, None, []),
- nitpick_ignore = ([], None, []),
- numfig = (False, 'env', []),
- numfig_secnum_depth = (1, 'env', []),
- numfig_format = ({}, 'env', []), # will be initialized in init_numfig_format()
-
- math_number_all = (False, 'env', []),
- math_eqref_format = (None, 'env', string_classes),
- math_numfig = (True, 'env', []),
- tls_verify = (True, 'env', []),
- tls_cacerts = (None, 'env', []),
- smartquotes = (True, 'env', []),
- smartquotes_action = ('qDe', 'env', []),
- smartquotes_excludes = ({'languages': ['ja'],
- 'builders': ['man', 'text']},
- 'env', []),
- ) # type: Dict[unicode, Tuple]
+ 'today_fmt': (None, 'env', [str]),
+
+ 'language': (None, 'env', [str]),
+ 'locale_dirs': (['locales'], 'env', []),
+ 'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]),
+
+ 'master_doc': ('index', 'env', []),
+ 'source_suffix': ({'.rst': 'restructuredtext'}, 'env', Any),
+ 'source_encoding': ('utf-8-sig', 'env', []),
+ 'source_parsers': ({}, 'env', []),
+ 'exclude_patterns': ([], 'env', []),
+ 'default_role': (None, 'env', [str]),
+ 'add_function_parentheses': (True, 'env', []),
+ 'add_module_names': (True, 'env', []),
+ 'trim_footnote_reference_space': (False, 'env', []),
+ 'show_authors': (False, 'env', []),
+ 'pygments_style': (None, 'html', [str]),
+ 'highlight_language': ('default', 'env', []),
+ 'highlight_options': ({}, 'env', []),
+ 'templates_path': ([], 'html', []),
+ 'template_bridge': (None, 'html', [str]),
+ 'keep_warnings': (False, 'env', []),
+ 'suppress_warnings': ([], 'env', []),
+ 'modindex_common_prefix': ([], 'html', []),
+ 'rst_epilog': (None, 'env', [str]),
+ 'rst_prolog': (None, 'env', [str]),
+ 'trim_doctest_flags': (True, 'env', []),
+ 'primary_domain': ('py', 'env', [NoneType]), # type: ignore
+ 'needs_sphinx': (None, None, [str]),
+ 'needs_extensions': ({}, None, []),
+ 'manpages_url': (None, 'env', []),
+ 'nitpicky': (False, None, []),
+ 'nitpick_ignore': ([], None, []),
+ 'numfig': (False, 'env', []),
+ 'numfig_secnum_depth': (1, 'env', []),
+ 'numfig_format': ({}, 'env', []), # will be initialized in init_numfig_format()
+
+ 'math_number_all': (False, 'env', []),
+ 'math_eqref_format': (None, 'env', [str]),
+ 'math_numfig': (True, 'env', []),
+ 'tls_verify': (True, 'env', []),
+ 'tls_cacerts': (None, 'env', []),
+ 'smartquotes': (True, 'env', []),
+ 'smartquotes_action': ('qDe', 'env', []),
+ 'smartquotes_excludes': ({'languages': ['ja'],
+ 'builders': ['man', 'text']},
+ 'env', []),
+ } # type: Dict[str, Tuple]
def __init__(self, *args):
# type: (Any) -> None
@@ -171,7 +163,7 @@ class Config(object):
RemovedInSphinx30Warning, stacklevel=2)
dirname, filename, overrides, tags = args
if dirname is None:
- config = {} # type: Dict[unicode, Any]
+ config = {} # type: Dict[str, Any]
else:
config = eval_config_file(path.join(dirname, filename), tags)
else:
@@ -189,15 +181,15 @@ class Config(object):
self.setup = config.get('setup', None) # type: Callable
if 'extensions' in overrides:
- if isinstance(overrides['extensions'], string_types):
+ if isinstance(overrides['extensions'], str):
config['extensions'] = overrides.pop('extensions').split(',')
else:
config['extensions'] = overrides.pop('extensions')
- self.extensions = config.get('extensions', []) # type: List[unicode]
+ self.extensions = config.get('extensions', []) # type: List[str]
@classmethod
def read(cls, confdir, overrides=None, tags=None):
- # type: (unicode, Dict, Tags) -> Config
+ # type: (str, Dict, Tags) -> Config
"""Create a Config object from configuration file."""
filename = path.join(confdir, CONFIG_FILENAME)
namespace = eval_config_file(filename, tags)
@@ -216,8 +208,8 @@ class Config(object):
check_unicode(self)
def convert_overrides(self, name, value):
- # type: (unicode, Any) -> Any
- if not isinstance(value, string_types):
+ # type: (str, Any) -> Any
+ if not isinstance(value, str):
return value
else:
defvalue = self.values[name][0]
@@ -229,7 +221,7 @@ class Config(object):
(name, name + '.key=value'))
elif isinstance(defvalue, list):
return value.split(',')
- elif isinstance(defvalue, integer_types):
+ elif isinstance(defvalue, int):
try:
return int(value)
except ValueError:
@@ -237,7 +229,7 @@ class Config(object):
(value, name))
elif hasattr(defvalue, '__call__'):
return value
- elif defvalue is not None and not isinstance(defvalue, string_types):
+ elif defvalue is not None and not isinstance(defvalue, str):
raise ValueError(__('cannot override config setting %r with unsupported '
'type, ignoring') % name)
else:
@@ -261,7 +253,7 @@ class Config(object):
def init_values(self):
# type: () -> None
config = self._raw_config
- for valname, value in iteritems(self.overrides):
+ for valname, value in self.overrides.items():
try:
if '.' in valname:
realvalname, key = valname.split('.', 1)
@@ -271,7 +263,7 @@ class Config(object):
logger.warning(__('unknown config value %r in override, ignoring'),
valname)
continue
- if isinstance(value, string_types):
+ if isinstance(value, str):
config[valname] = self.convert_overrides(valname, value)
else:
config[valname] = value
@@ -279,10 +271,10 @@ class Config(object):
logger.warning("%s", exc)
for name in config:
if name in self.values:
- self.__dict__[name] = config[name] # type: ignore
+ self.__dict__[name] = config[name]
def __getattr__(self, name):
- # type: (unicode) -> Any
+ # type: (str) -> Any
if name.startswith('_'):
raise AttributeError(name)
if name not in self.values:
@@ -293,36 +285,36 @@ class Config(object):
return default
def __getitem__(self, name):
- # type: (unicode) -> unicode
+ # type: (str) -> str
return getattr(self, name)
def __setitem__(self, name, value):
- # type: (unicode, Any) -> None
+ # type: (str, Any) -> None
setattr(self, name, value)
def __delitem__(self, name):
- # type: (unicode) -> None
+ # type: (str) -> None
delattr(self, name)
def __contains__(self, name):
- # type: (unicode) -> bool
+ # type: (str) -> bool
return name in self.values
def __iter__(self):
# type: () -> Generator[ConfigValue, None, None]
- for name, value in iteritems(self.values):
- yield ConfigValue(name, getattr(self, name), value[1]) # type: ignore
+ for name, value in self.values.items():
+ yield ConfigValue(name, getattr(self, name), value[1])
def add(self, name, default, rebuild, types):
- # type: (unicode, Any, Union[bool, unicode], Any) -> None
+ # type: (str, Any, Union[bool, str], Any) -> None
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):
+ # type: (Union[str, List[str]]) -> Iterator[ConfigValue]
+ if isinstance(rebuild, str):
rebuild = [rebuild]
return (value for value in self if value.rebuild in rebuild)
@@ -331,7 +323,7 @@ class Config(object):
"""Obtains serializable data for pickling."""
# remove potentially pickling-problematic values from config
__dict__ = {}
- for key, value in iteritems(self.__dict__):
+ for key, value in self.__dict__.items():
if key.startswith('_') or not is_serializable(value):
pass
else:
@@ -339,7 +331,7 @@ class Config(object):
# create a picklable copy of values list
__dict__['values'] = {}
- for key, value in iteritems(self.values): # type: ignore
+ for key, value in self.values.items():
real_value = getattr(self, key)
if not is_serializable(real_value):
# omit unserializable value
@@ -356,9 +348,9 @@ class Config(object):
def eval_config_file(filename, tags):
- # type: (unicode, Tags) -> Dict[unicode, Any]
+ # type: (str, Tags) -> Dict[str, Any]
"""Evaluate a config file."""
- namespace = {} # type: Dict[unicode, Any]
+ namespace = {} # type: Dict[str, Any]
namespace['__file__'] = filename
namespace['tags'] = tags
@@ -367,9 +359,7 @@ def eval_config_file(filename, tags):
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?")
+ msg = __("There is a syntax error in your configuration file: %s\n")
raise ConfigError(msg % err)
except SystemExit:
msg = __("The configuration file (or one of the modules it imports) "
@@ -390,7 +380,7 @@ def convert_source_suffix(app, config):
* new style: a dict which maps from fileext to filetype
"""
source_suffix = config.source_suffix
- if isinstance(source_suffix, string_types):
+ if isinstance(source_suffix, str):
# if str, considers as default filetype (None)
#
# The default filetype is determined on later step.
@@ -403,8 +393,8 @@ def convert_source_suffix(app, config):
# if dict, convert it to OrderedDict
config.source_suffix = OrderedDict(config.source_suffix) # type: ignore
else:
- logger.warning(__("The config value `source_suffix' expected to "
- "a string, list of strings or dictionary. "
+ logger.warning(__("The config value `source_suffix' expects "
+ "a string, list of strings, or dictionary. "
"But `%r' is given." % source_suffix))
@@ -471,11 +461,18 @@ def check_confval_types(app, config):
continue # at least we share a non-trivial base class
if annotations:
- msg = __("The config value `{name}' has type `{current.__name__}', "
- "expected to {permitted}.")
+ msg = __("The config value `{name}' has type `{current.__name__}'; "
+ "expected {permitted}.")
+ wrapped_annotations = ["`{}'".format(c.__name__) for c in annotations]
+ if len(wrapped_annotations) > 2:
+ permitted = "{}, or {}".format(
+ ", ".join(wrapped_annotations[:-1]),
+ wrapped_annotations[-1])
+ else:
+ permitted = " or ".join(wrapped_annotations)
logger.warning(msg.format(name=confval.name,
current=type(confval.value),
- permitted=str([c.__name__ for c in annotations])))
+ permitted=permitted))
else:
msg = __("The config value `{name}' has type `{current.__name__}', "
"defaults to `{default.__name__}'.")
@@ -489,21 +486,49 @@ def check_unicode(config):
"""check all string values for non-ASCII characters in bytestrings,
since that can result in UnicodeErrors all over the place
"""
+ warnings.warn('sphinx.config.check_unicode() is deprecated.',
+ RemovedInSphinx40Warning)
+
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):
+ for name, value in config._raw_config.items():
+ if isinstance(value, bytes) 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')
+ 'Please use Unicode strings, e.g. %r.'), name, 'Content')
+
+
+def check_primary_domain(app, config):
+ # type: (Sphinx, Config) -> None
+ primary_domain = config.primary_domain
+ if primary_domain and not app.registry.has_domain(primary_domain):
+ logger.warning(__('primary_domain %r not found, ignored.'), primary_domain)
+ config.primary_domain = None # type: ignore
+
+
+def check_master_doc(app, env, added, changed, removed):
+ # type: (Sphinx, BuildEnvironment, Set[str], Set[str], Set[str]) -> Set[str]
+ """Adjust master_doc to 'contents' to support an old project which does not have
+ no master_doc setting.
+ """
+ if (app.config.master_doc == 'index' and
+ 'index' not in app.project.docnames and
+ 'contents' in app.project.docnames):
+ logger.warning(__('Since v2.0, Sphinx uses "index" as master_doc by default. '
+ 'Please add "master_doc = \'contents\'" to your conf.py.'))
+ app.config.master_doc = "contents" # type: ignore
+
+ return changed
def setup(app):
- # type: (Sphinx) -> Dict[unicode, Any]
+ # type: (Sphinx) -> Dict[str, 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)
+ app.connect('config-inited', check_primary_domain)
+ app.connect('env-get-outdated', check_master_doc)
return {
'version': 'builtin',