summaryrefslogtreecommitdiffstats
path: root/sphinx/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/config.py')
-rw-r--r--sphinx/config.py105
1 files changed, 64 insertions, 41 deletions
diff --git a/sphinx/config.py b/sphinx/config.py
index 1f4b470..6710729 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -97,17 +97,19 @@ _OptValidTypes = Union[tuple[()], tuple[type, ...], frozenset[type], ENUM]
class _Opt:
- __slots__ = 'default', 'rebuild', 'valid_types'
+ __slots__ = 'default', 'rebuild', 'valid_types', 'description'
default: Any
rebuild: _ConfigRebuild
valid_types: _OptValidTypes
+ description: str
def __init__(
self,
default: Any,
rebuild: _ConfigRebuild,
valid_types: _OptValidTypes,
+ description: str = '',
) -> None:
"""Configuration option type for Sphinx.
@@ -120,52 +122,56 @@ class _Opt:
super().__setattr__('default', default)
super().__setattr__('rebuild', rebuild)
super().__setattr__('valid_types', valid_types)
+ super().__setattr__('description', description)
def __repr__(self) -> str:
return (
f'{self.__class__.__qualname__}('
f'default={self.default!r}, '
f'rebuild={self.rebuild!r}, '
- f'valid_types={self.valid_types!r})'
+ f'valid_types={self.rebuild!r}, '
+ f'description={self.description!r})'
)
def __eq__(self, other: object) -> bool:
if isinstance(other, _Opt):
- self_tpl = (self.default, self.rebuild, self.valid_types)
- other_tpl = (other.default, other.rebuild, other.valid_types)
+ self_tpl = (self.default, self.rebuild, self.valid_types, self.description)
+ other_tpl = (other.default, other.rebuild, other.valid_types, self.description)
return self_tpl == other_tpl
return NotImplemented
def __lt__(self, other: _Opt) -> bool:
if self.__class__ is other.__class__:
- self_tpl = (self.default, self.rebuild, self.valid_types)
- other_tpl = (other.default, other.rebuild, other.valid_types)
+ self_tpl = (self.default, self.rebuild, self.valid_types, self.description)
+ other_tpl = (other.default, other.rebuild, other.valid_types, self.description)
return self_tpl > other_tpl
return NotImplemented
def __hash__(self) -> int:
- return hash((self.default, self.rebuild, self.valid_types))
+ return hash((self.default, self.rebuild, self.valid_types, self.description))
def __setattr__(self, key: str, value: Any) -> None:
- if key in {'default', 'rebuild', 'valid_types'}:
+ if key in {'default', 'rebuild', 'valid_types', 'description'}:
msg = f'{self.__class__.__name__!r} object does not support assignment to {key!r}'
raise TypeError(msg)
super().__setattr__(key, value)
def __delattr__(self, key: str) -> None:
- if key in {'default', 'rebuild', 'valid_types'}:
+ if key in {'default', 'rebuild', 'valid_types', 'description'}:
msg = f'{self.__class__.__name__!r} object does not support deletion of {key!r}'
raise TypeError(msg)
super().__delattr__(key)
- def __getstate__(self) -> tuple[Any, _ConfigRebuild, _OptValidTypes]:
- return self.default, self.rebuild, self.valid_types
+ def __getstate__(self) -> tuple[Any, _ConfigRebuild, _OptValidTypes, str]:
+ return self.default, self.rebuild, self.valid_types, self.description
- def __setstate__(self, state: tuple[Any, _ConfigRebuild, _OptValidTypes]) -> None:
- default, rebuild, valid_types = state
+ def __setstate__(
+ self, state: tuple[Any, _ConfigRebuild, _OptValidTypes, str]) -> None:
+ default, rebuild, valid_types, description = state
super().__setattr__('default', default)
super().__setattr__('rebuild', rebuild)
super().__setattr__('valid_types', valid_types)
+ super().__setattr__('description', description)
def __getitem__(self, item: int | slice) -> Any:
warnings.warn(
@@ -196,11 +202,11 @@ class Config:
config_values: dict[str, _Opt] = {
# general options
- 'project': _Opt('Python', 'env', ()),
- 'author': _Opt('unknown', 'env', ()),
+ 'project': _Opt('Project name not set', 'env', ()),
+ 'author': _Opt('Author name not set', 'env', ()),
'project_copyright': _Opt('', 'html', frozenset((str, tuple, list))),
'copyright': _Opt(
- lambda c: c.project_copyright, 'html', frozenset((str, tuple, list))),
+ lambda config: config.project_copyright, 'html', frozenset((str, tuple, list))),
'version': _Opt('', 'env', ()),
'release': _Opt('', 'env', ()),
'today': _Opt('', 'env', ()),
@@ -258,6 +264,7 @@ class Config:
'math_number_all': _Opt(False, 'env', ()),
'math_eqref_format': _Opt(None, 'env', frozenset((str,))),
'math_numfig': _Opt(True, 'env', ()),
+ 'math_numsep': _Opt('.', 'env', frozenset((str,))),
'tls_verify': _Opt(True, 'env', ()),
'tls_cacerts': _Opt(None, 'env', ()),
'user_agent': _Opt(None, 'env', frozenset((str,))),
@@ -326,34 +333,33 @@ class Config:
valid_types = opt.valid_types
if valid_types == Any:
return value
- elif (type(default) is bool
- or (not isinstance(valid_types, ENUM)
- and len(valid_types) == 1 and bool in valid_types)):
+ if (type(default) is bool
+ or (not isinstance(valid_types, ENUM)
+ and len(valid_types) == 1 and bool in valid_types)):
if isinstance(valid_types, ENUM) or len(valid_types) > 1:
# if valid_types are given, and non-bool valid types exist,
# return the value without coercing to a Boolean.
return value
# given falsy string from a command line option
return value not in {'0', ''}
- elif isinstance(default, dict):
+ if isinstance(default, dict):
raise ValueError(__('cannot override dictionary config setting %r, '
'ignoring (use %r to set individual elements)') %
(name, f'{name}.key=value'))
- elif isinstance(default, list):
+ if isinstance(default, list):
return value.split(',')
- elif isinstance(default, int):
+ if isinstance(default, int):
try:
return int(value)
except ValueError as exc:
raise ValueError(__('invalid number %r for config value %r, ignoring') %
(value, name)) from exc
- elif callable(default):
+ if callable(default):
return value
- elif default is not None and not isinstance(default, str):
- raise ValueError(__('cannot override config setting %r with unsupported '
- 'type, ignoring') % name)
- else:
+ if isinstance(default, str) or default is None:
return value
+ raise ValueError(__('cannot override config setting %r with unsupported '
+ 'type, ignoring') % name)
@staticmethod
def pre_init_values() -> None:
@@ -385,6 +391,18 @@ class Config:
values.append(f"{opt_name}={opt_value!r}")
return self.__class__.__qualname__ + '(' + ', '.join(values) + ')'
+ def __setattr__(self, key: str, value: object) -> None:
+ # Ensure aliases update their counterpart.
+ if key == 'master_doc':
+ super().__setattr__('root_doc', value)
+ elif key == 'root_doc':
+ super().__setattr__('master_doc', value)
+ elif key == 'copyright':
+ super().__setattr__('project_copyright', value)
+ elif key == 'project_copyright':
+ super().__setattr__('copyright', value)
+ super().__setattr__(key, value)
+
def __getattr__(self, name: str) -> Any:
if name in self._options:
# first check command-line overrides
@@ -398,11 +416,12 @@ class Config:
except ValueError as exc:
logger.warning("%s", exc)
else:
- self.__dict__[name] = value
+ self.__setattr__(name, value)
return value
# then check values from 'conf.py'
if name in self._raw_config:
- self.__dict__[name] = value = self._raw_config[name]
+ value = self._raw_config[name]
+ self.__setattr__(name, value)
return value
# finally, fall back to the default value
default = self._options[name].default
@@ -433,7 +452,8 @@ class Config:
yield ConfigValue(name, getattr(self, name), opt.rebuild)
def add(self, name: str, default: Any, rebuild: _ConfigRebuild,
- types: type | Collection[type] | ENUM) -> None:
+ types: type | Collection[type] | ENUM,
+ description: str = '') -> None:
if name in self._options:
raise ExtensionError(__('Config value %r already present') % name)
@@ -443,7 +463,7 @@ class Config:
# standardise valid_types
valid_types = _validate_valid_types(types)
- self._options[name] = _Opt(default, rebuild, valid_types)
+ self._options[name] = _Opt(default, rebuild, valid_types, description)
def filter(self, rebuild: Set[_ConfigRebuild]) -> Iterator[ConfigValue]:
if isinstance(rebuild, str):
@@ -561,14 +581,18 @@ def convert_source_suffix(app: Sphinx, config: Config) -> None:
#
# The default filetype is determined on later step.
# By default, it is considered as restructuredtext.
- config.source_suffix = {source_suffix: None} # type: ignore[attr-defined]
+ config.source_suffix = {source_suffix: 'restructuredtext'}
+ logger.info(__("Converting `source_suffix = %r` to `source_suffix = %r`."),
+ source_suffix, config.source_suffix)
elif isinstance(source_suffix, (list, tuple)):
# if list, considers as all of them are default filetype
- config.source_suffix = dict.fromkeys(source_suffix, None) # type: ignore[attr-defined]
+ config.source_suffix = dict.fromkeys(source_suffix, 'restructuredtext')
+ logger.info(__("Converting `source_suffix = %r` to `source_suffix = %r`."),
+ source_suffix, config.source_suffix)
elif not isinstance(source_suffix, dict):
- logger.warning(__("The config value `source_suffix' expects "
- "a string, list of strings, or dictionary. "
- "But `%r' is given." % source_suffix))
+ msg = __("The config value `source_suffix' expects a dictionary,"
+ "a string, or a list of strings. Got `%r' instead (type %s).")
+ raise ConfigError(msg % (source_suffix, type(source_suffix)))
def convert_highlight_options(app: Sphinx, config: Config) -> None:
@@ -580,8 +604,7 @@ def convert_highlight_options(app: Sphinx, config: Config) -> None:
options = config.highlight_options
if options and not all(isinstance(v, dict) for v in options.values()):
# old styled option detected because all values are not dictionary.
- config.highlight_options = {config.highlight_language: # type: ignore[attr-defined]
- options}
+ config.highlight_options = {config.highlight_language: options}
def init_numfig_format(app: Sphinx, config: Config) -> None:
@@ -593,7 +616,7 @@ def init_numfig_format(app: Sphinx, config: Config) -> None:
# override default labels by configuration
numfig_format.update(config.numfig_format)
- config.numfig_format = numfig_format # type: ignore[attr-defined]
+ config.numfig_format = numfig_format
def correct_copyright_year(_app: Sphinx, config: Config) -> None:
@@ -713,7 +736,7 @@ def check_primary_domain(app: Sphinx, config: 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[attr-defined]
+ config.primary_domain = None
def check_root_doc(app: Sphinx, env: BuildEnvironment, added: set[str],
@@ -726,7 +749,7 @@ def check_root_doc(app: Sphinx, env: BuildEnvironment, added: set[str],
'contents' in app.project.docnames):
logger.warning(__('Since v2.0, Sphinx uses "index" as root_doc by default. '
'Please add "root_doc = \'contents\'" to your conf.py.'))
- app.config.root_doc = "contents" # type: ignore[attr-defined]
+ app.config.root_doc = "contents"
return changed