Sphinx 8.1.3__py3-none-any.whl → 8.2.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Sphinx might be problematic. Click here for more details.
- sphinx/__init__.py +8 -4
- sphinx/__main__.py +2 -0
- sphinx/_cli/__init__.py +2 -5
- sphinx/_cli/util/colour.py +34 -11
- sphinx/_cli/util/errors.py +128 -61
- sphinx/addnodes.py +51 -35
- sphinx/application.py +362 -230
- sphinx/builders/__init__.py +87 -64
- sphinx/builders/_epub_base.py +65 -56
- sphinx/builders/changes.py +17 -23
- sphinx/builders/dirhtml.py +8 -13
- sphinx/builders/epub3.py +70 -38
- sphinx/builders/gettext.py +93 -73
- sphinx/builders/html/__init__.py +240 -186
- sphinx/builders/html/_assets.py +9 -2
- sphinx/builders/html/_build_info.py +3 -0
- sphinx/builders/latex/__init__.py +64 -54
- sphinx/builders/latex/constants.py +14 -11
- sphinx/builders/latex/nodes.py +2 -0
- sphinx/builders/latex/theming.py +8 -9
- sphinx/builders/latex/transforms.py +7 -5
- sphinx/builders/linkcheck.py +193 -149
- sphinx/builders/manpage.py +17 -17
- sphinx/builders/singlehtml.py +28 -16
- sphinx/builders/texinfo.py +28 -21
- sphinx/builders/text.py +10 -15
- sphinx/builders/xml.py +10 -19
- sphinx/cmd/build.py +49 -119
- sphinx/cmd/make_mode.py +35 -31
- sphinx/cmd/quickstart.py +78 -62
- sphinx/config.py +265 -163
- sphinx/directives/__init__.py +51 -54
- sphinx/directives/admonitions.py +107 -0
- sphinx/directives/code.py +24 -19
- sphinx/directives/other.py +21 -42
- sphinx/directives/patches.py +28 -16
- sphinx/domains/__init__.py +54 -31
- sphinx/domains/_domains_container.py +22 -17
- sphinx/domains/_index.py +5 -8
- sphinx/domains/c/__init__.py +366 -245
- sphinx/domains/c/_ast.py +378 -256
- sphinx/domains/c/_ids.py +89 -31
- sphinx/domains/c/_parser.py +283 -214
- sphinx/domains/c/_symbol.py +269 -198
- sphinx/domains/changeset.py +39 -24
- sphinx/domains/citation.py +54 -24
- sphinx/domains/cpp/__init__.py +517 -362
- sphinx/domains/cpp/_ast.py +999 -682
- sphinx/domains/cpp/_ids.py +133 -65
- sphinx/domains/cpp/_parser.py +746 -588
- sphinx/domains/cpp/_symbol.py +692 -489
- sphinx/domains/index.py +10 -8
- sphinx/domains/javascript.py +152 -74
- sphinx/domains/math.py +48 -40
- sphinx/domains/python/__init__.py +402 -211
- sphinx/domains/python/_annotations.py +114 -57
- sphinx/domains/python/_object.py +151 -67
- sphinx/domains/rst.py +94 -49
- sphinx/domains/std/__init__.py +510 -249
- sphinx/environment/__init__.py +345 -61
- sphinx/environment/adapters/asset.py +7 -1
- sphinx/environment/adapters/indexentries.py +15 -20
- sphinx/environment/adapters/toctree.py +19 -9
- sphinx/environment/collectors/__init__.py +3 -1
- sphinx/environment/collectors/asset.py +18 -15
- sphinx/environment/collectors/dependencies.py +8 -10
- sphinx/environment/collectors/metadata.py +6 -4
- sphinx/environment/collectors/title.py +3 -1
- sphinx/environment/collectors/toctree.py +4 -4
- sphinx/errors.py +1 -3
- sphinx/events.py +4 -4
- sphinx/ext/apidoc/__init__.py +21 -0
- sphinx/ext/apidoc/__main__.py +9 -0
- sphinx/ext/apidoc/_cli.py +356 -0
- sphinx/ext/apidoc/_generate.py +356 -0
- sphinx/ext/apidoc/_shared.py +66 -0
- sphinx/ext/autodoc/__init__.py +829 -480
- sphinx/ext/autodoc/directive.py +57 -21
- sphinx/ext/autodoc/importer.py +184 -67
- sphinx/ext/autodoc/mock.py +25 -10
- sphinx/ext/autodoc/preserve_defaults.py +17 -9
- sphinx/ext/autodoc/type_comment.py +56 -29
- sphinx/ext/autodoc/typehints.py +49 -26
- sphinx/ext/autosectionlabel.py +28 -11
- sphinx/ext/autosummary/__init__.py +271 -143
- sphinx/ext/autosummary/generate.py +121 -51
- sphinx/ext/coverage.py +152 -91
- sphinx/ext/doctest.py +169 -101
- sphinx/ext/duration.py +12 -6
- sphinx/ext/extlinks.py +33 -21
- sphinx/ext/githubpages.py +8 -8
- sphinx/ext/graphviz.py +175 -109
- sphinx/ext/ifconfig.py +11 -6
- sphinx/ext/imgconverter.py +48 -25
- sphinx/ext/imgmath.py +127 -97
- sphinx/ext/inheritance_diagram.py +177 -103
- sphinx/ext/intersphinx/__init__.py +22 -13
- sphinx/ext/intersphinx/__main__.py +3 -1
- sphinx/ext/intersphinx/_cli.py +18 -14
- sphinx/ext/intersphinx/_load.py +91 -82
- sphinx/ext/intersphinx/_resolve.py +108 -74
- sphinx/ext/intersphinx/_shared.py +2 -2
- sphinx/ext/linkcode.py +28 -12
- sphinx/ext/mathjax.py +60 -29
- sphinx/ext/napoleon/__init__.py +19 -7
- sphinx/ext/napoleon/docstring.py +229 -231
- sphinx/ext/todo.py +44 -49
- sphinx/ext/viewcode.py +105 -57
- sphinx/extension.py +3 -1
- sphinx/highlighting.py +13 -7
- sphinx/io.py +9 -13
- sphinx/jinja2glue.py +29 -26
- sphinx/locale/__init__.py +8 -9
- sphinx/parsers.py +8 -7
- sphinx/project.py +2 -2
- sphinx/pycode/__init__.py +31 -21
- sphinx/pycode/ast.py +6 -3
- sphinx/pycode/parser.py +14 -8
- sphinx/pygments_styles.py +4 -5
- sphinx/registry.py +192 -92
- sphinx/roles.py +58 -7
- sphinx/search/__init__.py +75 -54
- sphinx/search/en.py +11 -13
- sphinx/search/fi.py +1 -1
- sphinx/search/ja.py +8 -6
- sphinx/search/nl.py +1 -1
- sphinx/search/zh.py +19 -21
- sphinx/testing/fixtures.py +26 -29
- sphinx/testing/path.py +26 -62
- sphinx/testing/restructuredtext.py +14 -8
- sphinx/testing/util.py +21 -19
- sphinx/texinputs/make.bat.jinja +50 -50
- sphinx/texinputs/sphinx.sty +4 -3
- sphinx/texinputs/sphinxlatexadmonitions.sty +1 -1
- sphinx/texinputs/sphinxlatexobjects.sty +29 -10
- sphinx/themes/basic/static/searchtools.js +8 -5
- sphinx/theming.py +49 -61
- sphinx/transforms/__init__.py +17 -38
- sphinx/transforms/compact_bullet_list.py +5 -3
- sphinx/transforms/i18n.py +8 -21
- sphinx/transforms/post_transforms/__init__.py +142 -93
- sphinx/transforms/post_transforms/code.py +5 -5
- sphinx/transforms/post_transforms/images.py +28 -24
- sphinx/transforms/references.py +3 -1
- sphinx/util/__init__.py +109 -60
- sphinx/util/_files.py +39 -23
- sphinx/util/_importer.py +4 -1
- sphinx/util/_inventory_file_reader.py +76 -0
- sphinx/util/_io.py +2 -2
- sphinx/util/_lines.py +6 -3
- sphinx/util/_pathlib.py +40 -2
- sphinx/util/build_phase.py +2 -0
- sphinx/util/cfamily.py +19 -14
- sphinx/util/console.py +44 -179
- sphinx/util/display.py +9 -10
- sphinx/util/docfields.py +140 -122
- sphinx/util/docstrings.py +1 -1
- sphinx/util/docutils.py +118 -77
- sphinx/util/fileutil.py +25 -26
- sphinx/util/http_date.py +2 -0
- sphinx/util/i18n.py +77 -64
- sphinx/util/images.py +8 -6
- sphinx/util/inspect.py +147 -38
- sphinx/util/inventory.py +215 -116
- sphinx/util/logging.py +33 -33
- sphinx/util/matching.py +12 -4
- sphinx/util/nodes.py +18 -13
- sphinx/util/osutil.py +38 -39
- sphinx/util/parallel.py +22 -13
- sphinx/util/parsing.py +2 -1
- sphinx/util/png.py +6 -2
- sphinx/util/requests.py +33 -2
- sphinx/util/rst.py +3 -2
- sphinx/util/tags.py +1 -1
- sphinx/util/template.py +18 -10
- sphinx/util/texescape.py +8 -6
- sphinx/util/typing.py +148 -122
- sphinx/versioning.py +3 -3
- sphinx/writers/html.py +3 -1
- sphinx/writers/html5.py +61 -50
- sphinx/writers/latex.py +80 -65
- sphinx/writers/manpage.py +19 -38
- sphinx/writers/texinfo.py +44 -45
- sphinx/writers/text.py +48 -30
- sphinx/writers/xml.py +11 -8
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/LICENSE.rst +1 -1
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/METADATA +23 -15
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/RECORD +190 -186
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/WHEEL +1 -1
- sphinx/builders/html/transforms.py +0 -90
- sphinx/ext/apidoc.py +0 -721
- sphinx/util/exceptions.py +0 -74
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/entry_points.txt +0 -0
sphinx/config.py
CHANGED
|
@@ -2,24 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import sys
|
|
6
5
|
import time
|
|
7
6
|
import traceback
|
|
8
7
|
import types
|
|
9
8
|
import warnings
|
|
10
|
-
from
|
|
9
|
+
from contextlib import chdir
|
|
10
|
+
from os import getenv
|
|
11
|
+
from pathlib import Path
|
|
11
12
|
from typing import TYPE_CHECKING, Any, Literal, NamedTuple
|
|
12
13
|
|
|
13
14
|
from sphinx.deprecation import RemovedInSphinx90Warning
|
|
14
15
|
from sphinx.errors import ConfigError, ExtensionError
|
|
15
16
|
from sphinx.locale import _, __
|
|
16
17
|
from sphinx.util import logging
|
|
17
|
-
from sphinx.util.osutil import fs_encoding
|
|
18
|
-
|
|
19
|
-
if sys.version_info >= (3, 11):
|
|
20
|
-
from contextlib import chdir
|
|
21
|
-
else:
|
|
22
|
-
from sphinx.util.osutil import _chdir as chdir
|
|
23
18
|
|
|
24
19
|
if TYPE_CHECKING:
|
|
25
20
|
import os
|
|
@@ -34,7 +29,11 @@ if TYPE_CHECKING:
|
|
|
34
29
|
logger = logging.getLogger(__name__)
|
|
35
30
|
|
|
36
31
|
_ConfigRebuild: TypeAlias = Literal[
|
|
37
|
-
'',
|
|
32
|
+
'',
|
|
33
|
+
'env',
|
|
34
|
+
'epub',
|
|
35
|
+
'gettext',
|
|
36
|
+
'html',
|
|
38
37
|
# sphinxcontrib-applehelp
|
|
39
38
|
'applehelp',
|
|
40
39
|
# sphinxcontrib-devhelp
|
|
@@ -84,16 +83,18 @@ class ENUM:
|
|
|
84
83
|
"""
|
|
85
84
|
|
|
86
85
|
def __init__(self, *candidates: str | bool | None) -> None:
|
|
87
|
-
self.
|
|
86
|
+
self._candidates = frozenset(candidates)
|
|
88
87
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
def __repr__(self) -> str:
|
|
89
|
+
return f'ENUM({", ".join(sorted(map(repr, self._candidates)))})'
|
|
90
|
+
|
|
91
|
+
def match(self, value: str | bool | None | Sequence[str | bool | None]) -> bool: # NoQA: RUF036
|
|
92
|
+
if isinstance(value, str | bool | None):
|
|
93
|
+
return value in self._candidates
|
|
94
|
+
return all(item in self._candidates for item in value)
|
|
94
95
|
|
|
95
96
|
|
|
96
|
-
_OptValidTypes: TypeAlias =
|
|
97
|
+
_OptValidTypes: TypeAlias = frozenset[type] | ENUM
|
|
97
98
|
|
|
98
99
|
|
|
99
100
|
class _Opt:
|
|
@@ -135,15 +136,35 @@ class _Opt:
|
|
|
135
136
|
|
|
136
137
|
def __eq__(self, other: object) -> bool:
|
|
137
138
|
if isinstance(other, _Opt):
|
|
138
|
-
self_tpl = (
|
|
139
|
-
|
|
139
|
+
self_tpl = (
|
|
140
|
+
self.default,
|
|
141
|
+
self.rebuild,
|
|
142
|
+
self.valid_types,
|
|
143
|
+
self.description,
|
|
144
|
+
)
|
|
145
|
+
other_tpl = (
|
|
146
|
+
other.default,
|
|
147
|
+
other.rebuild,
|
|
148
|
+
other.valid_types,
|
|
149
|
+
other.description,
|
|
150
|
+
)
|
|
140
151
|
return self_tpl == other_tpl
|
|
141
152
|
return NotImplemented
|
|
142
153
|
|
|
143
154
|
def __lt__(self, other: _Opt) -> bool:
|
|
144
155
|
if self.__class__ is other.__class__:
|
|
145
|
-
self_tpl = (
|
|
146
|
-
|
|
156
|
+
self_tpl = (
|
|
157
|
+
self.default,
|
|
158
|
+
self.rebuild,
|
|
159
|
+
self.valid_types,
|
|
160
|
+
self.description,
|
|
161
|
+
)
|
|
162
|
+
other_tpl = (
|
|
163
|
+
other.default,
|
|
164
|
+
other.rebuild,
|
|
165
|
+
other.valid_types,
|
|
166
|
+
other.description,
|
|
167
|
+
)
|
|
147
168
|
return self_tpl > other_tpl
|
|
148
169
|
return NotImplemented
|
|
149
170
|
|
|
@@ -166,7 +187,8 @@ class _Opt:
|
|
|
166
187
|
return self.default, self.rebuild, self.valid_types, self.description
|
|
167
188
|
|
|
168
189
|
def __setstate__(
|
|
169
|
-
|
|
190
|
+
self, state: tuple[Any, _ConfigRebuild, _OptValidTypes, str]
|
|
191
|
+
) -> None:
|
|
170
192
|
default, rebuild, valid_types, description = state
|
|
171
193
|
super().__setattr__('default', default)
|
|
172
194
|
super().__setattr__('rebuild', rebuild)
|
|
@@ -177,7 +199,9 @@ class _Opt:
|
|
|
177
199
|
warnings.warn(
|
|
178
200
|
f'The {self.__class__.__name__!r} object tuple interface is deprecated, '
|
|
179
201
|
"use attribute access instead for 'default', 'rebuild', and 'valid_types'.",
|
|
180
|
-
RemovedInSphinx90Warning,
|
|
202
|
+
RemovedInSphinx90Warning,
|
|
203
|
+
stacklevel=2,
|
|
204
|
+
)
|
|
181
205
|
return (self.default, self.rebuild, self.valid_types)[item]
|
|
182
206
|
|
|
183
207
|
|
|
@@ -202,81 +226,93 @@ class Config:
|
|
|
202
226
|
|
|
203
227
|
config_values: dict[str, _Opt] = {
|
|
204
228
|
# general options
|
|
205
|
-
'project': _Opt('Project name not set', 'env', ()),
|
|
206
|
-
'author': _Opt('Author name not set', 'env', ()),
|
|
229
|
+
'project': _Opt('Project name not set', 'env', frozenset((str,))),
|
|
230
|
+
'author': _Opt('Author name not set', 'env', frozenset((str,))),
|
|
207
231
|
'project_copyright': _Opt('', 'html', frozenset((str, tuple, list))),
|
|
208
232
|
'copyright': _Opt(
|
|
209
|
-
lambda config: config.project_copyright,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
233
|
+
lambda config: config.project_copyright,
|
|
234
|
+
'html',
|
|
235
|
+
frozenset((str, tuple, list)),
|
|
236
|
+
),
|
|
237
|
+
'version': _Opt('', 'env', frozenset((str,))),
|
|
238
|
+
'release': _Opt('', 'env', frozenset((str,))),
|
|
239
|
+
'today': _Opt('', 'env', frozenset((str,))),
|
|
213
240
|
# the real default is locale-dependent
|
|
214
241
|
'today_fmt': _Opt(None, 'env', frozenset((str,))),
|
|
215
|
-
|
|
216
242
|
'language': _Opt('en', 'env', frozenset((str,))),
|
|
217
|
-
'locale_dirs': _Opt(['locales'], 'env', ()),
|
|
218
|
-
'figure_language_filename': _Opt(
|
|
219
|
-
|
|
243
|
+
'locale_dirs': _Opt(['locales'], 'env', frozenset((list, tuple))),
|
|
244
|
+
'figure_language_filename': _Opt(
|
|
245
|
+
'{root}.{language}{ext}', 'env', frozenset((str,))
|
|
246
|
+
),
|
|
247
|
+
'gettext_allow_fuzzy_translations': _Opt(False, 'gettext', frozenset((bool,))),
|
|
220
248
|
'translation_progress_classes': _Opt(
|
|
221
|
-
False, 'env', ENUM(True, False, 'translated', 'untranslated')
|
|
222
|
-
|
|
223
|
-
'master_doc': _Opt('index', 'env', ()),
|
|
224
|
-
'root_doc': _Opt(lambda config: config.master_doc, 'env', ()),
|
|
249
|
+
False, 'env', ENUM(True, False, 'translated', 'untranslated')
|
|
250
|
+
),
|
|
251
|
+
'master_doc': _Opt('index', 'env', frozenset((str,))),
|
|
252
|
+
'root_doc': _Opt(lambda config: config.master_doc, 'env', frozenset((str,))),
|
|
225
253
|
# ``source_suffix`` type is actually ``dict[str, str | None]``:
|
|
226
254
|
# see ``convert_source_suffix()`` below.
|
|
227
|
-
'source_suffix': _Opt(
|
|
228
|
-
|
|
229
|
-
'source_encoding': _Opt('utf-8-sig', 'env', ()),
|
|
255
|
+
'source_suffix': _Opt({'.rst': 'restructuredtext'}, 'env', Any), # type: ignore[arg-type]
|
|
256
|
+
'source_encoding': _Opt('utf-8-sig', 'env', frozenset((str,))),
|
|
230
257
|
'exclude_patterns': _Opt([], 'env', frozenset((str,))),
|
|
231
|
-
'include_patterns': _Opt([
|
|
258
|
+
'include_patterns': _Opt(['**'], 'env', frozenset((str,))),
|
|
232
259
|
'default_role': _Opt(None, 'env', frozenset((str,))),
|
|
233
|
-
'add_function_parentheses': _Opt(True, 'env', ()),
|
|
234
|
-
'add_module_names': _Opt(True, 'env', ()),
|
|
260
|
+
'add_function_parentheses': _Opt(True, 'env', frozenset((bool,))),
|
|
261
|
+
'add_module_names': _Opt(True, 'env', frozenset((bool,))),
|
|
235
262
|
'toc_object_entries': _Opt(True, 'env', frozenset((bool,))),
|
|
236
263
|
'toc_object_entries_show_parents': _Opt(
|
|
237
|
-
'domain', 'env', ENUM('domain', 'all', 'hide')
|
|
238
|
-
|
|
239
|
-
'
|
|
264
|
+
'domain', 'env', ENUM('domain', 'all', 'hide')
|
|
265
|
+
),
|
|
266
|
+
'trim_footnote_reference_space': _Opt(False, 'env', frozenset((bool,))),
|
|
267
|
+
'show_authors': _Opt(False, 'env', frozenset((bool,))),
|
|
240
268
|
'pygments_style': _Opt(None, 'html', frozenset((str,))),
|
|
241
|
-
'highlight_language': _Opt('default', 'env', ()),
|
|
242
|
-
'highlight_options': _Opt({}, 'env', ()),
|
|
243
|
-
'templates_path': _Opt([], 'html', ()),
|
|
269
|
+
'highlight_language': _Opt('default', 'env', frozenset((str,))),
|
|
270
|
+
'highlight_options': _Opt({}, 'env', frozenset((dict,))),
|
|
271
|
+
'templates_path': _Opt([], 'html', frozenset((list,))),
|
|
244
272
|
'template_bridge': _Opt(None, 'html', frozenset((str,))),
|
|
245
|
-
'keep_warnings': _Opt(False, 'env', ()),
|
|
246
|
-
'suppress_warnings': _Opt([], 'env', ()),
|
|
273
|
+
'keep_warnings': _Opt(False, 'env', frozenset((bool,))),
|
|
274
|
+
'suppress_warnings': _Opt([], 'env', frozenset((list, tuple))),
|
|
247
275
|
'show_warning_types': _Opt(True, 'env', frozenset((bool,))),
|
|
248
|
-
'modindex_common_prefix': _Opt([], 'html', ()),
|
|
276
|
+
'modindex_common_prefix': _Opt([], 'html', frozenset((list, tuple))),
|
|
249
277
|
'rst_epilog': _Opt(None, 'env', frozenset((str,))),
|
|
250
278
|
'rst_prolog': _Opt(None, 'env', frozenset((str,))),
|
|
251
|
-
'trim_doctest_flags': _Opt(True, 'env', ()),
|
|
279
|
+
'trim_doctest_flags': _Opt(True, 'env', frozenset((bool,))),
|
|
252
280
|
'primary_domain': _Opt('py', 'env', frozenset((types.NoneType,))),
|
|
253
281
|
'needs_sphinx': _Opt(None, '', frozenset((str,))),
|
|
254
|
-
'needs_extensions': _Opt({}, '', ()),
|
|
255
|
-
'manpages_url': _Opt(None, 'env', ()),
|
|
256
|
-
'nitpicky': _Opt(False, '', ()),
|
|
282
|
+
'needs_extensions': _Opt({}, '', frozenset((dict,))),
|
|
283
|
+
'manpages_url': _Opt(None, 'env', frozenset((str, types.NoneType))),
|
|
284
|
+
'nitpicky': _Opt(False, '', frozenset((bool,))),
|
|
257
285
|
'nitpick_ignore': _Opt([], '', frozenset((set, list, tuple))),
|
|
258
286
|
'nitpick_ignore_regex': _Opt([], '', frozenset((set, list, tuple))),
|
|
259
|
-
'numfig': _Opt(False, 'env', ()),
|
|
260
|
-
'numfig_secnum_depth': _Opt(1, 'env', ()),
|
|
261
|
-
|
|
287
|
+
'numfig': _Opt(False, 'env', frozenset((bool,))),
|
|
288
|
+
'numfig_secnum_depth': _Opt(1, 'env', frozenset((int, types.NoneType))),
|
|
289
|
+
# numfig_format will be initialized in init_numfig_format()
|
|
290
|
+
'numfig_format': _Opt({}, 'env', frozenset((dict,))),
|
|
262
291
|
'maximum_signature_line_length': _Opt(
|
|
263
|
-
None, 'env', frozenset((int, types.NoneType))
|
|
264
|
-
|
|
292
|
+
None, 'env', frozenset((int, types.NoneType))
|
|
293
|
+
),
|
|
294
|
+
'math_number_all': _Opt(False, 'env', frozenset((bool,))),
|
|
265
295
|
'math_eqref_format': _Opt(None, 'env', frozenset((str,))),
|
|
266
|
-
'math_numfig': _Opt(True, 'env', ()),
|
|
296
|
+
'math_numfig': _Opt(True, 'env', frozenset((bool,))),
|
|
267
297
|
'math_numsep': _Opt('.', 'env', frozenset((str,))),
|
|
268
|
-
'tls_verify': _Opt(True, 'env', ()),
|
|
269
|
-
'tls_cacerts': _Opt(None, 'env', ()),
|
|
298
|
+
'tls_verify': _Opt(True, 'env', frozenset((bool,))),
|
|
299
|
+
'tls_cacerts': _Opt(None, 'env', frozenset((str, dict, types.NoneType))),
|
|
270
300
|
'user_agent': _Opt(None, 'env', frozenset((str,))),
|
|
271
|
-
'smartquotes': _Opt(True, 'env', ()),
|
|
272
|
-
'smartquotes_action': _Opt('qDe', 'env', ()),
|
|
301
|
+
'smartquotes': _Opt(True, 'env', frozenset((bool,))),
|
|
302
|
+
'smartquotes_action': _Opt('qDe', 'env', frozenset((str,))),
|
|
273
303
|
'smartquotes_excludes': _Opt(
|
|
274
|
-
{'languages': ['ja', 'zh_CN', 'zh_TW'], 'builders': ['man', 'text']},
|
|
275
|
-
|
|
304
|
+
{'languages': ['ja', 'zh_CN', 'zh_TW'], 'builders': ['man', 'text']},
|
|
305
|
+
'env',
|
|
306
|
+
frozenset((dict,)),
|
|
307
|
+
),
|
|
308
|
+
'option_emphasise_placeholders': _Opt(False, 'env', frozenset((bool,))),
|
|
276
309
|
}
|
|
277
310
|
|
|
278
|
-
def __init__(
|
|
279
|
-
|
|
311
|
+
def __init__(
|
|
312
|
+
self,
|
|
313
|
+
config: dict[str, Any] | None = None,
|
|
314
|
+
overrides: dict[str, Any] | None = None,
|
|
315
|
+
) -> None:
|
|
280
316
|
raw_config: dict[str, Any] = config or {}
|
|
281
317
|
self._overrides = dict(overrides) if overrides is not None else {}
|
|
282
318
|
self._options = Config.config_values.copy()
|
|
@@ -306,24 +342,33 @@ class Config:
|
|
|
306
342
|
return self._overrides
|
|
307
343
|
|
|
308
344
|
@classmethod
|
|
309
|
-
def read(
|
|
310
|
-
|
|
345
|
+
def read(
|
|
346
|
+
cls: type[Config],
|
|
347
|
+
confdir: str | os.PathLike[str],
|
|
348
|
+
overrides: dict[str, Any] | None = None,
|
|
349
|
+
tags: Tags | None = None,
|
|
350
|
+
) -> Config:
|
|
311
351
|
"""Create a Config object from configuration file."""
|
|
312
|
-
filename =
|
|
313
|
-
if not
|
|
314
|
-
raise ConfigError(
|
|
315
|
-
|
|
352
|
+
filename = Path(confdir, CONFIG_FILENAME)
|
|
353
|
+
if not filename.is_file():
|
|
354
|
+
raise ConfigError(
|
|
355
|
+
__("config directory doesn't contain a conf.py file (%s)") % confdir
|
|
356
|
+
)
|
|
316
357
|
namespace = eval_config_file(filename, tags)
|
|
317
358
|
|
|
318
359
|
# Note: Old sphinx projects have been configured as "language = None" because
|
|
319
360
|
# sphinx-quickstart previously generated this by default.
|
|
320
361
|
# To keep compatibility, they should be fallback to 'en' for a while
|
|
321
362
|
# (This conversion should not be removed before 2025-01-01).
|
|
322
|
-
if namespace.get(
|
|
323
|
-
logger.warning(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
363
|
+
if namespace.get('language', ...) is None:
|
|
364
|
+
logger.warning(
|
|
365
|
+
__(
|
|
366
|
+
"Invalid configuration value found: 'language = None'. "
|
|
367
|
+
'Update your configuration to a valid language code. '
|
|
368
|
+
"Falling back to 'en' (English)."
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
namespace['language'] = 'en'
|
|
327
372
|
|
|
328
373
|
return cls(namespace, overrides)
|
|
329
374
|
|
|
@@ -333,33 +378,47 @@ class Config:
|
|
|
333
378
|
valid_types = opt.valid_types
|
|
334
379
|
if valid_types == Any:
|
|
335
380
|
return value
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if
|
|
340
|
-
|
|
341
|
-
|
|
381
|
+
if isinstance(valid_types, ENUM):
|
|
382
|
+
if False in valid_types._candidates and value == '0':
|
|
383
|
+
return False
|
|
384
|
+
if True in valid_types._candidates and value == '1':
|
|
385
|
+
return True
|
|
386
|
+
return value
|
|
387
|
+
elif type(default) is bool or (bool in valid_types):
|
|
388
|
+
if value == '0':
|
|
389
|
+
return False
|
|
390
|
+
if value == '1':
|
|
391
|
+
return True
|
|
392
|
+
if len(valid_types) > 1:
|
|
342
393
|
return value
|
|
343
|
-
|
|
344
|
-
|
|
394
|
+
msg = __("'%s' must be '0' or '1', got '%s'") % (name, value)
|
|
395
|
+
raise ConfigError(msg)
|
|
345
396
|
if isinstance(default, dict):
|
|
346
|
-
raise ValueError(
|
|
347
|
-
|
|
348
|
-
|
|
397
|
+
raise ValueError( # NoQA: TRY004
|
|
398
|
+
__(
|
|
399
|
+
'cannot override dictionary config setting %r, '
|
|
400
|
+
'ignoring (use %r to set individual elements)'
|
|
401
|
+
)
|
|
402
|
+
% (name, f'{name}.key=value')
|
|
403
|
+
)
|
|
349
404
|
if isinstance(default, list):
|
|
350
405
|
return value.split(',')
|
|
351
406
|
if isinstance(default, int):
|
|
352
407
|
try:
|
|
353
408
|
return int(value)
|
|
354
409
|
except ValueError as exc:
|
|
355
|
-
raise ValueError(
|
|
356
|
-
|
|
410
|
+
raise ValueError(
|
|
411
|
+
__('invalid number %r for config value %r, ignoring')
|
|
412
|
+
% (value, name)
|
|
413
|
+
) from exc
|
|
357
414
|
if callable(default):
|
|
358
415
|
return value
|
|
359
416
|
if isinstance(default, str) or default is None:
|
|
360
417
|
return value
|
|
361
|
-
raise ValueError(
|
|
362
|
-
|
|
418
|
+
raise ValueError(
|
|
419
|
+
__('cannot override config setting %r with unsupported type, ignoring')
|
|
420
|
+
% name
|
|
421
|
+
)
|
|
363
422
|
|
|
364
423
|
@staticmethod
|
|
365
424
|
def pre_init_values() -> None:
|
|
@@ -379,7 +438,9 @@ class Config:
|
|
|
379
438
|
def _report_override_warnings(self) -> None:
|
|
380
439
|
for name in self._overrides:
|
|
381
440
|
if name not in self._options:
|
|
382
|
-
logger.warning(
|
|
441
|
+
logger.warning(
|
|
442
|
+
__('unknown config value %r in override, ignoring'), name
|
|
443
|
+
)
|
|
383
444
|
|
|
384
445
|
def __repr__(self) -> str:
|
|
385
446
|
values = []
|
|
@@ -388,7 +449,7 @@ class Config:
|
|
|
388
449
|
opt_value = getattr(self, opt_name)
|
|
389
450
|
except Exception:
|
|
390
451
|
opt_value = '<error!>'
|
|
391
|
-
values.append(f
|
|
452
|
+
values.append(f'{opt_name}={opt_value!r}')
|
|
392
453
|
return self.__class__.__qualname__ + '(' + ', '.join(values) + ')'
|
|
393
454
|
|
|
394
455
|
def __setattr__(self, key: str, value: object) -> None:
|
|
@@ -414,7 +475,7 @@ class Config:
|
|
|
414
475
|
try:
|
|
415
476
|
value = self.convert_overrides(name, value)
|
|
416
477
|
except ValueError as exc:
|
|
417
|
-
logger.warning(
|
|
478
|
+
logger.warning('%s', exc)
|
|
418
479
|
else:
|
|
419
480
|
self.__setattr__(name, value)
|
|
420
481
|
return value
|
|
@@ -451,9 +512,14 @@ class Config:
|
|
|
451
512
|
for name, opt in self._options.items():
|
|
452
513
|
yield ConfigValue(name, getattr(self, name), opt.rebuild)
|
|
453
514
|
|
|
454
|
-
def add(
|
|
455
|
-
|
|
456
|
-
|
|
515
|
+
def add(
|
|
516
|
+
self,
|
|
517
|
+
name: str,
|
|
518
|
+
default: Any,
|
|
519
|
+
rebuild: _ConfigRebuild,
|
|
520
|
+
types: type | Collection[type] | ENUM,
|
|
521
|
+
description: str = '',
|
|
522
|
+
) -> None:
|
|
457
523
|
if name in self._options:
|
|
458
524
|
raise ExtensionError(__('Config value %r already present') % name)
|
|
459
525
|
|
|
@@ -470,7 +536,7 @@ class Config:
|
|
|
470
536
|
return (value for value in self if value.rebuild == rebuild)
|
|
471
537
|
return (value for value in self if value.rebuild in rebuild)
|
|
472
538
|
|
|
473
|
-
def __getstate__(self) -> dict:
|
|
539
|
+
def __getstate__(self) -> dict[str, Any]:
|
|
474
540
|
"""Obtains serializable data for pickling."""
|
|
475
541
|
# remove potentially pickling-problematic values from config
|
|
476
542
|
__dict__ = {
|
|
@@ -478,7 +544,7 @@ class Config:
|
|
|
478
544
|
for key, value in self.__dict__.items()
|
|
479
545
|
if not key.startswith('_') and is_serializable(value)
|
|
480
546
|
}
|
|
481
|
-
# create a
|
|
547
|
+
# create a pickleable copy of ``self._options``
|
|
482
548
|
__dict__['_options'] = _options = {}
|
|
483
549
|
for name, opt in self._options.items():
|
|
484
550
|
if not isinstance(opt, _Opt) and isinstance(opt, tuple) and len(opt) <= 3:
|
|
@@ -491,8 +557,10 @@ class Config:
|
|
|
491
557
|
# will always mark the config value as changed,
|
|
492
558
|
# and thus always invalidate the cache and perform a rebuild.
|
|
493
559
|
logger.warning(
|
|
494
|
-
__(
|
|
495
|
-
|
|
560
|
+
__(
|
|
561
|
+
'cannot cache unpickleable configuration value: %r '
|
|
562
|
+
'(because it contains a function, class, or module object)'
|
|
563
|
+
),
|
|
496
564
|
name,
|
|
497
565
|
type='config',
|
|
498
566
|
subtype='cache',
|
|
@@ -505,50 +573,56 @@ class Config:
|
|
|
505
573
|
|
|
506
574
|
return __dict__
|
|
507
575
|
|
|
508
|
-
def __setstate__(self, state: dict) -> None:
|
|
576
|
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
509
577
|
self._overrides = {}
|
|
510
578
|
self._options = {
|
|
511
|
-
name: _Opt(real_value, rebuild, ())
|
|
579
|
+
name: _Opt(real_value, rebuild, frozenset())
|
|
512
580
|
for name, (real_value, rebuild) in state.pop('_options').items()
|
|
513
581
|
}
|
|
514
582
|
self._raw_config = {}
|
|
515
583
|
self.__dict__.update(state)
|
|
516
584
|
|
|
517
585
|
|
|
518
|
-
def eval_config_file(
|
|
586
|
+
def eval_config_file(
|
|
587
|
+
filename: str | os.PathLike[str], tags: Tags | None
|
|
588
|
+
) -> dict[str, Any]:
|
|
519
589
|
"""Evaluate a config file."""
|
|
520
|
-
|
|
521
|
-
namespace['__file__'] = filename
|
|
522
|
-
namespace['tags'] = tags
|
|
590
|
+
filename = Path(filename)
|
|
523
591
|
|
|
524
|
-
|
|
592
|
+
namespace: dict[str, Any] = {
|
|
593
|
+
'__file__': str(filename),
|
|
594
|
+
'tags': tags,
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
with chdir(filename.parent):
|
|
525
598
|
# during executing config file, current dir is changed to ``confdir``.
|
|
526
599
|
try:
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
exec(code, namespace) # NoQA: S102
|
|
600
|
+
code = compile(filename.read_bytes(), filename, 'exec')
|
|
601
|
+
exec(code, namespace) # NoQA: S102
|
|
530
602
|
except SyntaxError as err:
|
|
531
|
-
msg = __(
|
|
603
|
+
msg = __('There is a syntax error in your configuration file: %s\n')
|
|
532
604
|
raise ConfigError(msg % err) from err
|
|
533
605
|
except SystemExit as exc:
|
|
534
|
-
msg = __(
|
|
535
|
-
|
|
606
|
+
msg = __(
|
|
607
|
+
'The configuration file (or one of the modules it imports) '
|
|
608
|
+
'called sys.exit()'
|
|
609
|
+
)
|
|
536
610
|
raise ConfigError(msg) from exc
|
|
537
611
|
except ConfigError:
|
|
538
612
|
# pass through ConfigError from conf.py as is. It will be shown in console.
|
|
539
613
|
raise
|
|
540
614
|
except Exception as exc:
|
|
541
|
-
msg = __(
|
|
615
|
+
msg = __('There is a programmable error in your configuration file:\n\n%s')
|
|
542
616
|
raise ConfigError(msg % traceback.format_exc()) from exc
|
|
543
617
|
|
|
544
618
|
return namespace
|
|
545
619
|
|
|
546
620
|
|
|
547
621
|
def _validate_valid_types(
|
|
548
|
-
valid_types: type | Collection[type] | ENUM,
|
|
549
|
-
) ->
|
|
622
|
+
valid_types: type | Collection[type] | ENUM, /
|
|
623
|
+
) -> frozenset[type] | ENUM:
|
|
550
624
|
if not valid_types:
|
|
551
|
-
return ()
|
|
625
|
+
return frozenset()
|
|
552
626
|
if isinstance(valid_types, frozenset | ENUM):
|
|
553
627
|
return valid_types
|
|
554
628
|
if isinstance(valid_types, type):
|
|
@@ -557,16 +631,11 @@ def _validate_valid_types(
|
|
|
557
631
|
return frozenset({Any}) # type: ignore[arg-type]
|
|
558
632
|
if isinstance(valid_types, set):
|
|
559
633
|
return frozenset(valid_types)
|
|
560
|
-
if not isinstance(valid_types, tuple):
|
|
561
|
-
try:
|
|
562
|
-
valid_types = tuple(valid_types)
|
|
563
|
-
except TypeError:
|
|
564
|
-
logger.warning(__('Failed to convert %r to a set or tuple'), valid_types)
|
|
565
|
-
return valid_types # type: ignore[return-value]
|
|
566
634
|
try:
|
|
567
635
|
return frozenset(valid_types)
|
|
568
636
|
except TypeError:
|
|
569
|
-
|
|
637
|
+
logger.warning(__('Failed to convert %r to a frozenset'), valid_types)
|
|
638
|
+
return frozenset()
|
|
570
639
|
|
|
571
640
|
|
|
572
641
|
def convert_source_suffix(app: Sphinx, config: Config) -> None:
|
|
@@ -582,16 +651,24 @@ def convert_source_suffix(app: Sphinx, config: Config) -> None:
|
|
|
582
651
|
# The default filetype is determined on later step.
|
|
583
652
|
# By default, it is considered as restructuredtext.
|
|
584
653
|
config.source_suffix = {source_suffix: 'restructuredtext'}
|
|
585
|
-
logger.info(
|
|
586
|
-
|
|
654
|
+
logger.info(
|
|
655
|
+
__('Converting `source_suffix = %r` to `source_suffix = %r`.'),
|
|
656
|
+
source_suffix,
|
|
657
|
+
config.source_suffix,
|
|
658
|
+
)
|
|
587
659
|
elif isinstance(source_suffix, list | tuple):
|
|
588
660
|
# if list, considers as all of them are default filetype
|
|
589
661
|
config.source_suffix = dict.fromkeys(source_suffix, 'restructuredtext')
|
|
590
|
-
logger.info(
|
|
591
|
-
|
|
662
|
+
logger.info(
|
|
663
|
+
__('Converting `source_suffix = %r` to `source_suffix = %r`.'),
|
|
664
|
+
source_suffix,
|
|
665
|
+
config.source_suffix,
|
|
666
|
+
)
|
|
592
667
|
elif not isinstance(source_suffix, dict):
|
|
593
|
-
msg = __(
|
|
594
|
-
|
|
668
|
+
msg = __(
|
|
669
|
+
"The config value `source_suffix' expects a dictionary, "
|
|
670
|
+
"a string, or a list of strings. Got `%r' instead (type %s)."
|
|
671
|
+
)
|
|
595
672
|
raise ConfigError(msg % (source_suffix, type(source_suffix)))
|
|
596
673
|
|
|
597
674
|
|
|
@@ -609,10 +686,12 @@ def convert_highlight_options(app: Sphinx, config: Config) -> None:
|
|
|
609
686
|
|
|
610
687
|
def init_numfig_format(app: Sphinx, config: Config) -> None:
|
|
611
688
|
"""Initialize :confval:`numfig_format`."""
|
|
612
|
-
numfig_format = {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
689
|
+
numfig_format = {
|
|
690
|
+
'section': _('Section %s'),
|
|
691
|
+
'figure': _('Fig. %s'),
|
|
692
|
+
'table': _('Table %s'),
|
|
693
|
+
'code-block': _('Listing %s'),
|
|
694
|
+
}
|
|
616
695
|
|
|
617
696
|
# override default labels by configuration
|
|
618
697
|
numfig_format.update(config.numfig_format)
|
|
@@ -719,10 +798,14 @@ def check_confval_types(app: Sphinx | None, config: Config) -> None:
|
|
|
719
798
|
|
|
720
799
|
if isinstance(valid_types, ENUM):
|
|
721
800
|
if not valid_types.match(value):
|
|
722
|
-
msg = __(
|
|
723
|
-
|
|
801
|
+
msg = __(
|
|
802
|
+
'The config value `{name}` has to be a one of {candidates}, '
|
|
803
|
+
'but `{current}` is given.'
|
|
804
|
+
)
|
|
724
805
|
logger.warning(
|
|
725
|
-
msg.format(
|
|
806
|
+
msg.format(
|
|
807
|
+
name=name, current=value, candidates=valid_types._candidates
|
|
808
|
+
),
|
|
726
809
|
once=True,
|
|
727
810
|
)
|
|
728
811
|
continue
|
|
@@ -734,30 +817,39 @@ def check_confval_types(app: Sphinx | None, config: Config) -> None:
|
|
|
734
817
|
continue
|
|
735
818
|
|
|
736
819
|
if type_value in valid_types: # check explicitly listed types
|
|
820
|
+
if frozenset in valid_types and type_value in {list, tuple, set}:
|
|
821
|
+
setattr(config, name, frozenset(value))
|
|
822
|
+
elif tuple in valid_types and type_value is list:
|
|
823
|
+
setattr(config, name, tuple(value))
|
|
737
824
|
continue
|
|
738
825
|
|
|
739
|
-
common_bases =
|
|
740
|
-
& set(type_default.__bases__))
|
|
826
|
+
common_bases = {*type_value.__bases__, type_value} & set(type_default.__bases__)
|
|
741
827
|
common_bases.discard(object)
|
|
742
828
|
if common_bases:
|
|
743
829
|
continue # at least we share a non-trivial base class
|
|
744
830
|
|
|
745
831
|
if valid_types:
|
|
746
|
-
msg = __(
|
|
747
|
-
|
|
832
|
+
msg = __(
|
|
833
|
+
"The config value `{name}' has type `{current.__name__}'; "
|
|
834
|
+
'expected {permitted}.'
|
|
835
|
+
)
|
|
748
836
|
wrapped_valid_types = sorted(f"`{c.__name__}'" for c in valid_types)
|
|
749
837
|
if len(wrapped_valid_types) > 2:
|
|
750
|
-
permitted = (
|
|
751
|
-
|
|
838
|
+
permitted = (
|
|
839
|
+
', '.join(wrapped_valid_types[:-1])
|
|
840
|
+
+ f', or {wrapped_valid_types[-1]}'
|
|
841
|
+
)
|
|
752
842
|
else:
|
|
753
|
-
permitted =
|
|
843
|
+
permitted = ' or '.join(wrapped_valid_types)
|
|
754
844
|
logger.warning(
|
|
755
845
|
msg.format(name=name, current=type_value, permitted=permitted),
|
|
756
846
|
once=True,
|
|
757
847
|
)
|
|
758
848
|
else:
|
|
759
|
-
msg = __(
|
|
760
|
-
|
|
849
|
+
msg = __(
|
|
850
|
+
"The config value `{name}' has type `{current.__name__}', "
|
|
851
|
+
"defaults to `{default.__name__}'."
|
|
852
|
+
)
|
|
761
853
|
logger.warning(
|
|
762
854
|
msg.format(name=name, current=type_value, default=type_default),
|
|
763
855
|
once=True,
|
|
@@ -771,17 +863,27 @@ def check_primary_domain(app: Sphinx, config: Config) -> None:
|
|
|
771
863
|
config.primary_domain = None
|
|
772
864
|
|
|
773
865
|
|
|
774
|
-
def
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
app.config.
|
|
866
|
+
def check_master_doc(
|
|
867
|
+
app: Sphinx,
|
|
868
|
+
env: BuildEnvironment,
|
|
869
|
+
added: Set[str],
|
|
870
|
+
changed: Set[str],
|
|
871
|
+
removed: Set[str],
|
|
872
|
+
) -> Iterable[str]:
|
|
873
|
+
"""Sphinx 2.0 changed the default from 'contents' to 'index'."""
|
|
874
|
+
docnames = app.project.docnames
|
|
875
|
+
if (
|
|
876
|
+
app.config.master_doc == 'index'
|
|
877
|
+
and 'index' not in docnames
|
|
878
|
+
and 'contents' in docnames
|
|
879
|
+
):
|
|
880
|
+
logger.warning(
|
|
881
|
+
__(
|
|
882
|
+
'Sphinx now uses "index" as the master document by default. '
|
|
883
|
+
'To keep pre-2.0 behaviour, set "master_doc = \'contents\'".'
|
|
884
|
+
)
|
|
885
|
+
)
|
|
886
|
+
app.config.master_doc = 'contents'
|
|
785
887
|
|
|
786
888
|
return changed
|
|
787
889
|
|
|
@@ -794,7 +896,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
|
|
|
794
896
|
app.connect('config-inited', correct_copyright_year, priority=800)
|
|
795
897
|
app.connect('config-inited', check_confval_types, priority=800)
|
|
796
898
|
app.connect('config-inited', check_primary_domain, priority=800)
|
|
797
|
-
app.connect('env-get-outdated',
|
|
899
|
+
app.connect('env-get-outdated', check_master_doc)
|
|
798
900
|
|
|
799
901
|
return {
|
|
800
902
|
'version': 'builtin',
|