diff options
Diffstat (limited to 'sphinx/config.py')
-rw-r--r-- | sphinx/config.py | 109 |
1 files changed, 75 insertions, 34 deletions
diff --git a/sphinx/config.py b/sphinx/config.py index 4222dbac9..451aa8b0d 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -13,12 +13,21 @@ import re from os import path, getenv from six import PY2, PY3, iteritems, string_types, binary_type, text_type, integer_types +from typing import Any, NamedTuple, Union from sphinx.errors import ConfigError -from sphinx.locale import l_ +from sphinx.locale import l_, _ +from sphinx.util import logging +from sphinx.util.i18n import format_date from sphinx.util.osutil import cd from sphinx.util.pycompat import execfile_, NoneType -from sphinx.util.i18n import format_date + +if False: + # For type annotation + from typing import Any, Callable, Dict, Iterable, Iterator, List, Tuple # NOQA + from sphinx.util.tags import Tags # NOQA + +logger = logging.getLogger(__name__) nonascii_re = re.compile(br'[\x80-\xff]') copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])') @@ -35,6 +44,13 @@ CONFIG_PERMITTED_TYPE_WARNING = "The config value `{name}' has type `{current.__ CONFIG_TYPE_WARNING = "The config value `{name}' has type `{current.__name__}', " \ "defaults to `{default.__name__}'." +if PY3: + unicode = str # special alias for static typing... + +ConfigValue = NamedTuple('ConfigValue', [('name', str), + ('value', Any), + ('rebuild', Union[bool, unicode])]) + class ENUM(object): """represents the config value should be a one of candidates. @@ -43,13 +59,15 @@ class ENUM(object): app.add_config_value('latex_show_urls', 'no', None, ENUM('no', 'footnote', 'inline')) """ def __init__(self, *candidates): + # type: (unicode) -> None self.candidates = candidates def match(self, value): + # type: (unicode) -> bool return value in self.candidates -string_classes = [text_type] +string_classes = [text_type] # type: List if PY2: string_classes.append(binary_type) # => [str, unicode] @@ -114,15 +132,13 @@ class Config(object): tls_verify = (True, 'env'), tls_cacerts = (None, 'env'), - - # pre-initialized confval for HTML builder - html_translator_class = (None, 'html', string_classes), - ) + ) # type: Dict[unicode, Tuple] def __init__(self, dirname, filename, overrides, tags): + # type: (unicode, unicode, Dict, Tags) -> None self.overrides = overrides self.values = Config.config_values.copy() - config = {} + config = {} # type: Dict[unicode, Any] if dirname is not None: config_file = path.join(dirname, filename) config['__file__'] = config_file @@ -140,14 +156,14 @@ class Config(object): self._raw_config = config # these two must be preinitialized because extensions can add their # own config values - self.setup = config.get('setup', None) + self.setup = config.get('setup', None) # type: Callable if 'extensions' in overrides: if isinstance(overrides['extensions'], string_types): config['extensions'] = overrides.pop('extensions').split(',') else: config['extensions'] = overrides.pop('extensions') - self.extensions = config.get('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) @@ -155,10 +171,11 @@ class Config(object): if getenv('SOURCE_DATE_EPOCH') is not None: for k in ('copyright', 'epub_copyright'): if k in config: - config[k] = copyright_year_re.sub('\g<1>%s' % format_date('%Y'), + config[k] = copyright_year_re.sub(r'\g<1>%s' % format_date('%Y'), config[k]) - def check_types(self, warn): + 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 l_() we have to wait with calling @@ -177,7 +194,7 @@ class Config(object): current = self[name] if isinstance(permitted, ENUM): if not permitted.match(current): - warn(CONFIG_ENUM_WARNING.format( + logger.warning(CONFIG_ENUM_WARNING.format( name=name, current=current, candidates=permitted.candidates)) else: if type(current) is type(default): @@ -192,30 +209,32 @@ class Config(object): continue # at least we share a non-trivial base class if permitted: - warn(CONFIG_PERMITTED_TYPE_WARNING.format( + logger.warning(CONFIG_PERMITTED_TYPE_WARNING.format( name=name, current=type(current), permitted=str([cls.__name__ for cls in permitted]))) else: - warn(CONFIG_TYPE_WARNING.format( + logger.warning(CONFIG_TYPE_WARNING.format( name=name, current=type(current), default=type(default))) - def check_unicode(self, warn): + 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): - warn('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')) + 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 convert_overrides(self, name, value): + # type: (unicode, Any) -> Any if not isinstance(value, string_types): return value else: defvalue = self.values[name][0] if isinstance(defvalue, dict): - raise ValueError('cannot override dictionary config setting %r, ' - 'ignoring (use %r to set individual elements)' % + raise ValueError(_('cannot override dictionary config setting %r, ' + 'ignoring (use %r to set individual elements)') % (name, name + '.key=value')) elif isinstance(defvalue, list): return value.split(',') @@ -223,19 +242,22 @@ class Config(object): try: return int(value) except ValueError: - raise ValueError('invalid number %r for config value %r, ignoring' % + raise ValueError(_('invalid number %r for config value %r, ignoring') % (value, name)) elif hasattr(defvalue, '__call__'): return value elif defvalue is not None and not isinstance(defvalue, string_types): - raise ValueError('cannot override config setting %r with unsupported ' - 'type, ignoring' % name) + raise ValueError(_('cannot override config setting %r with unsupported ' + 'type, ignoring') % name) else: return value - def pre_init_values(self, warn): - """Initialize some limited config variables before loading extensions""" - variables = ['needs_sphinx', 'suppress_warnings', 'html_translator_class'] + def pre_init_values(self): + # type: () -> None + """ + Initialize some limited config variables before initialize i18n and loading extensions + """ + variables = ['needs_sphinx', 'suppress_warnings', 'language', 'locale_dirs'] for name in variables: try: if name in self.overrides: @@ -243,9 +265,10 @@ class Config(object): elif name in self._raw_config: self.__dict__[name] = self._raw_config[name] except ValueError as exc: - warn(exc) + logger.warning("%s", exc) - def init_values(self, warn): + def init_values(self): + # type: () -> None config = self._raw_config for valname, value in iteritems(self.overrides): try: @@ -254,38 +277,56 @@ class Config(object): config.setdefault(realvalname, {})[key] = value continue elif valname not in self.values: - warn('unknown config value %r in override, ignoring' % valname) + logger.warning(_('unknown config value %r in override, ignoring'), valname) continue if isinstance(value, string_types): config[valname] = self.convert_overrides(valname, value) else: config[valname] = value except ValueError as exc: - warn(exc) + logger.warning("%s", exc) for name in config: if name in self.values: self.__dict__[name] = config[name] - if isinstance(self.source_suffix, string_types): - self.source_suffix = [self.source_suffix] + if isinstance(self.source_suffix, string_types): # type: ignore + self.source_suffix = [self.source_suffix] # type: ignore def __getattr__(self, name): + # type: (unicode) -> Any if name.startswith('_'): raise AttributeError(name) if name not in self.values: - raise AttributeError('No such config value: %s' % name) + raise AttributeError(_('No such config value: %s') % name) default = self.values[name][0] if hasattr(default, '__call__'): return default(self) return default def __getitem__(self, name): + # type: (unicode) -> unicode return getattr(self, name) def __setitem__(self, name, value): + # type: (unicode, Any) -> None setattr(self, name, value) def __delitem__(self, name): + # type: (unicode) -> None delattr(self, name) def __contains__(self, name): + # type: (unicode) -> bool return name in self.values + + def __iter__(self): + # type: () -> Iterable[ConfigValue] + 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) + + def filter(self, rebuild): + # type: (str) -> Iterator[ConfigValue] + return (value for value in self if value.rebuild == rebuild) # type: ignore |