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/util/nodes.py
CHANGED
|
@@ -95,12 +95,11 @@ class NodeMatcher(Generic[N]):
|
|
|
95
95
|
confounds type checkers' ability to determine the return type of the iterator.
|
|
96
96
|
"""
|
|
97
97
|
for found in node.findall(self):
|
|
98
|
-
yield cast(N, found)
|
|
98
|
+
yield cast('N', found)
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def get_full_module_name(node: Node) -> str:
|
|
102
|
-
"""
|
|
103
|
-
Return full module dotted path like: 'docutils.nodes.paragraph'
|
|
102
|
+
"""Return full module dotted path like: 'docutils.nodes.paragraph'
|
|
104
103
|
|
|
105
104
|
:param nodes.Node node: target node
|
|
106
105
|
:return: full module dotted path
|
|
@@ -109,8 +108,7 @@ def get_full_module_name(node: Node) -> str:
|
|
|
109
108
|
|
|
110
109
|
|
|
111
110
|
def repr_domxml(node: Node, length: int = 80) -> str:
|
|
112
|
-
"""
|
|
113
|
-
return DOM XML representation of the specified node like:
|
|
111
|
+
"""Return DOM XML representation of the specified node like:
|
|
114
112
|
'<paragraph translatable="False"><inline classes="versionadded">Added in version...'
|
|
115
113
|
|
|
116
114
|
:param nodes.Node node: target node
|
|
@@ -181,7 +179,8 @@ def apply_source_workaround(node: Element) -> None:
|
|
|
181
179
|
)
|
|
182
180
|
node.source, node.line = node.parent.source, node.parent.line
|
|
183
181
|
|
|
184
|
-
# workaround: literal_block under bullet list
|
|
182
|
+
# workaround: literal_block under bullet list
|
|
183
|
+
# See: https://github.com/sphinx-doc/sphinx/issues/4913
|
|
185
184
|
if isinstance(node, nodes.literal_block) and node.source is None:
|
|
186
185
|
with contextlib.suppress(ValueError):
|
|
187
186
|
node.source = get_node_source(node)
|
|
@@ -197,10 +196,14 @@ def apply_source_workaround(node: Element) -> None:
|
|
|
197
196
|
if isinstance(
|
|
198
197
|
node,
|
|
199
198
|
(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
| nodes.
|
|
199
|
+
# https://github.com/sphinx-doc/sphinx/issues/1305 rubric directive
|
|
200
|
+
nodes.rubric
|
|
201
|
+
# https://github.com/sphinx-doc/sphinx/issues/1477 line node
|
|
202
|
+
| nodes.line
|
|
203
|
+
# https://github.com/sphinx-doc/sphinx/issues/3093 image directive in substitution
|
|
204
|
+
| nodes.image
|
|
205
|
+
# https://github.com/sphinx-doc/sphinx/issues/3335 field list syntax
|
|
206
|
+
| nodes.field_name
|
|
204
207
|
),
|
|
205
208
|
):
|
|
206
209
|
logger.debug(
|
|
@@ -471,7 +474,7 @@ def inline_all_toctrees(
|
|
|
471
474
|
if includefile not in traversed:
|
|
472
475
|
try:
|
|
473
476
|
traversed.append(includefile)
|
|
474
|
-
logger.info(indent + colorfunc(includefile))
|
|
477
|
+
logger.info(indent + colorfunc(includefile)) # NoQA: G003
|
|
475
478
|
subtree = inline_all_toctrees(
|
|
476
479
|
builder,
|
|
477
480
|
docnameset,
|
|
@@ -487,6 +490,8 @@ def inline_all_toctrees(
|
|
|
487
490
|
__('toctree contains ref to nonexisting file %r'),
|
|
488
491
|
includefile,
|
|
489
492
|
location=docname,
|
|
493
|
+
type='toc',
|
|
494
|
+
subtype='not_readable',
|
|
490
495
|
)
|
|
491
496
|
else:
|
|
492
497
|
sof = addnodes.start_of_file(docname=includefile)
|
|
@@ -593,7 +598,7 @@ def make_id(
|
|
|
593
598
|
node_id = None
|
|
594
599
|
elif term:
|
|
595
600
|
node_id = _make_id(term)
|
|
596
|
-
if node_id
|
|
601
|
+
if not node_id:
|
|
597
602
|
node_id = None # fallback to None
|
|
598
603
|
|
|
599
604
|
while node_id is None or node_id in document.ids:
|
|
@@ -709,7 +714,7 @@ def _copy_except__document(el: Element) -> Element:
|
|
|
709
714
|
"""Monkey-patch ```nodes.Element.copy``` to not copy the ``_document``
|
|
710
715
|
attribute.
|
|
711
716
|
|
|
712
|
-
|
|
717
|
+
See: https://github.com/sphinx-doc/sphinx/issues/11116#issuecomment-1376767086
|
|
713
718
|
"""
|
|
714
719
|
newnode = object.__new__(el.__class__)
|
|
715
720
|
# set in Element.__init__()
|
sphinx/util/osutil.py
CHANGED
|
@@ -5,12 +5,12 @@ from __future__ import annotations
|
|
|
5
5
|
import contextlib
|
|
6
6
|
import filecmp
|
|
7
7
|
import os
|
|
8
|
+
import os.path
|
|
8
9
|
import re
|
|
9
10
|
import shutil
|
|
10
11
|
import sys
|
|
11
12
|
import unicodedata
|
|
12
13
|
from io import StringIO
|
|
13
|
-
from os import path
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import TYPE_CHECKING
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ from sphinx.locale import __
|
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
20
|
from types import TracebackType
|
|
21
|
-
from typing import Any
|
|
21
|
+
from typing import Any, Self
|
|
22
22
|
|
|
23
23
|
# SEP separates path elements in the canonical file names
|
|
24
24
|
#
|
|
@@ -29,12 +29,12 @@ SEP = '/'
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def os_path(canonical_path: str, /) -> str:
|
|
32
|
-
return canonical_path.replace(SEP, path.sep)
|
|
32
|
+
return canonical_path.replace(SEP, os.path.sep)
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def canon_path(native_path: str | os.PathLike[str], /) -> str:
|
|
36
36
|
"""Return path in OS-independent form"""
|
|
37
|
-
return os.fspath(native_path).replace(path.sep, SEP)
|
|
37
|
+
return os.fspath(native_path).replace(os.path.sep, SEP)
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def path_stabilize(filepath: str | os.PathLike[str], /) -> str:
|
|
@@ -134,10 +134,14 @@ def copyfile(
|
|
|
134
134
|
logger.warning(msg, source, dest, type='misc', subtype='copy_overwrite')
|
|
135
135
|
return
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
if sys.platform == 'win32':
|
|
138
|
+
# copy2() uses Windows API calls
|
|
139
|
+
shutil.copy2(source, dest)
|
|
140
|
+
else:
|
|
141
|
+
shutil.copyfile(source, dest)
|
|
142
|
+
with contextlib.suppress(OSError):
|
|
143
|
+
# don't do full copystat because the source may be read-only
|
|
144
|
+
_copy_times(source, dest)
|
|
141
145
|
|
|
142
146
|
|
|
143
147
|
_no_fn_re = re.compile(r'[^a-zA-Z0-9_-]')
|
|
@@ -166,36 +170,27 @@ def relpath(
|
|
|
166
170
|
return str(path)
|
|
167
171
|
|
|
168
172
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
abspath = path.abspath
|
|
173
|
+
def _relative_path(path: Path, root: Path, /) -> Path:
|
|
174
|
+
"""Return a relative filepath to *path* from the given *root* directory.
|
|
174
175
|
|
|
176
|
+
This is an alternative of ``Path.relative_to``.
|
|
177
|
+
It returns the original path if *path* and *root* are on different drives,
|
|
178
|
+
which may happen on Windows.
|
|
179
|
+
"""
|
|
180
|
+
if path.anchor != root.anchor or '..' in root.parts:
|
|
181
|
+
# If the drives are different, no relative path exists.
|
|
182
|
+
# Path.relative_to() requires fully-resolved paths (no '..').
|
|
183
|
+
return path
|
|
184
|
+
if sys.version_info[:2] < (3, 12):
|
|
185
|
+
return Path(os.path.relpath(path, root))
|
|
186
|
+
return path.relative_to(root, walk_up=True)
|
|
175
187
|
|
|
176
|
-
class _chdir:
|
|
177
|
-
"""Remove this fall-back once support for Python 3.10 is removed."""
|
|
178
|
-
|
|
179
|
-
def __init__(self, target_dir: str, /) -> None:
|
|
180
|
-
self.path = target_dir
|
|
181
|
-
self._dirs: list[str] = []
|
|
182
|
-
|
|
183
|
-
def __enter__(self) -> None:
|
|
184
|
-
self._dirs.append(os.getcwd())
|
|
185
|
-
os.chdir(self.path)
|
|
186
188
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
type: type[BaseException] | None,
|
|
190
|
-
value: BaseException | None,
|
|
191
|
-
traceback: TracebackType | None,
|
|
192
|
-
/,
|
|
193
|
-
) -> None:
|
|
194
|
-
os.chdir(self._dirs.pop())
|
|
189
|
+
safe_relpath = relpath # for compatibility
|
|
190
|
+
fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
|
195
191
|
|
|
196
192
|
|
|
197
|
-
|
|
198
|
-
cd = _chdir
|
|
193
|
+
abspath = os.path.abspath
|
|
199
194
|
|
|
200
195
|
|
|
201
196
|
class FileAvoidWrite:
|
|
@@ -232,19 +227,22 @@ class FileAvoidWrite:
|
|
|
232
227
|
try:
|
|
233
228
|
with open(self._path, encoding='utf-8') as old_f:
|
|
234
229
|
old_content = old_f.read()
|
|
235
|
-
|
|
236
|
-
|
|
230
|
+
if old_content == buf:
|
|
231
|
+
return
|
|
237
232
|
except OSError:
|
|
238
233
|
pass
|
|
239
234
|
|
|
240
235
|
with open(self._path, 'w', encoding='utf-8') as f:
|
|
241
236
|
f.write(buf)
|
|
242
237
|
|
|
243
|
-
def __enter__(self) ->
|
|
238
|
+
def __enter__(self) -> Self:
|
|
244
239
|
return self
|
|
245
240
|
|
|
246
241
|
def __exit__(
|
|
247
|
-
self,
|
|
242
|
+
self,
|
|
243
|
+
exc_type: type[BaseException] | None,
|
|
244
|
+
exc_value: BaseException | None,
|
|
245
|
+
traceback: TracebackType | None,
|
|
248
246
|
) -> bool:
|
|
249
247
|
self.close()
|
|
250
248
|
return True
|
|
@@ -258,8 +256,9 @@ class FileAvoidWrite:
|
|
|
258
256
|
return getattr(self._io, name)
|
|
259
257
|
|
|
260
258
|
|
|
261
|
-
def rmtree(path: str) -> None:
|
|
262
|
-
|
|
259
|
+
def rmtree(path: str | os.PathLike[str], /) -> None:
|
|
260
|
+
path = Path(path)
|
|
261
|
+
if path.is_dir():
|
|
263
262
|
shutil.rmtree(path)
|
|
264
263
|
else:
|
|
265
264
|
os.remove(path)
|
sphinx/util/parallel.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import time
|
|
7
7
|
import traceback
|
|
8
8
|
from math import sqrt
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
11
|
try:
|
|
12
12
|
import multiprocessing
|
|
@@ -20,6 +20,7 @@ from sphinx.util import logging
|
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from collections.abc import Callable, Sequence
|
|
23
|
+
from typing import Any
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
26
|
|
|
@@ -34,12 +35,15 @@ class SerialTasks:
|
|
|
34
35
|
pass
|
|
35
36
|
|
|
36
37
|
def add_task(
|
|
37
|
-
self,
|
|
38
|
+
self,
|
|
39
|
+
task_func: Callable[[Any], Any] | Callable[[], Any],
|
|
40
|
+
arg: Any = None,
|
|
41
|
+
result_func: Callable[[Any], Any] | None = None,
|
|
38
42
|
) -> None:
|
|
39
43
|
if arg is not None:
|
|
40
|
-
res = task_func(arg)
|
|
44
|
+
res = task_func(arg) # type: ignore[call-arg]
|
|
41
45
|
else:
|
|
42
|
-
res = task_func()
|
|
46
|
+
res = task_func() # type: ignore[call-arg]
|
|
43
47
|
if result_func:
|
|
44
48
|
result_func(res)
|
|
45
49
|
|
|
@@ -53,7 +57,7 @@ class ParallelTasks:
|
|
|
53
57
|
def __init__(self, nproc: int) -> None:
|
|
54
58
|
self.nproc = nproc
|
|
55
59
|
# (optional) function performed by each task on the result of main task
|
|
56
|
-
self._result_funcs: dict[int, Callable] = {}
|
|
60
|
+
self._result_funcs: dict[int, Callable[[Any, Any], Any]] = {}
|
|
57
61
|
# task arguments
|
|
58
62
|
self._args: dict[int, list[Any] | None] = {}
|
|
59
63
|
# list of subprocesses (both started and waiting)
|
|
@@ -61,20 +65,22 @@ class ParallelTasks:
|
|
|
61
65
|
# list of receiving pipe connections of running subprocesses
|
|
62
66
|
self._precvs: dict[int, Any] = {}
|
|
63
67
|
# list of receiving pipe connections of waiting subprocesses
|
|
64
|
-
self.
|
|
68
|
+
self._precvs_waiting: dict[int, Any] = {}
|
|
65
69
|
# number of working subprocesses
|
|
66
70
|
self._pworking = 0
|
|
67
71
|
# task number of each subprocess
|
|
68
72
|
self._taskid = 0
|
|
69
73
|
|
|
70
|
-
def _process(
|
|
74
|
+
def _process(
|
|
75
|
+
self, pipe: Any, func: Callable[[Any], Any] | Callable[[], Any], arg: Any
|
|
76
|
+
) -> None:
|
|
71
77
|
try:
|
|
72
78
|
collector = logging.LogCollector()
|
|
73
79
|
with collector.collect():
|
|
74
80
|
if arg is None:
|
|
75
|
-
ret = func()
|
|
81
|
+
ret = func() # type: ignore[call-arg]
|
|
76
82
|
else:
|
|
77
|
-
ret = func(arg)
|
|
83
|
+
ret = func(arg) # type: ignore[call-arg]
|
|
78
84
|
failed = False
|
|
79
85
|
except BaseException as err:
|
|
80
86
|
failed = True
|
|
@@ -84,7 +90,10 @@ class ParallelTasks:
|
|
|
84
90
|
pipe.send((failed, collector.logs, ret))
|
|
85
91
|
|
|
86
92
|
def add_task(
|
|
87
|
-
self,
|
|
93
|
+
self,
|
|
94
|
+
task_func: Callable[[Any], Any] | Callable[[], Any],
|
|
95
|
+
arg: Any = None,
|
|
96
|
+
result_func: Callable[[Any, Any], Any] | None = None,
|
|
88
97
|
) -> None:
|
|
89
98
|
tid = self._taskid
|
|
90
99
|
self._taskid += 1
|
|
@@ -94,7 +103,7 @@ class ParallelTasks:
|
|
|
94
103
|
context: Any = multiprocessing.get_context('fork')
|
|
95
104
|
proc = context.Process(target=self._process, args=(psend, task_func, arg))
|
|
96
105
|
self._procs[tid] = proc
|
|
97
|
-
self.
|
|
106
|
+
self._precvs_waiting[tid] = precv
|
|
98
107
|
try:
|
|
99
108
|
self._join_one()
|
|
100
109
|
except Exception:
|
|
@@ -135,8 +144,8 @@ class ParallelTasks:
|
|
|
135
144
|
joined_any = True
|
|
136
145
|
break
|
|
137
146
|
|
|
138
|
-
while self.
|
|
139
|
-
newtid, newprecv = self.
|
|
147
|
+
while self._precvs_waiting and self._pworking < self.nproc:
|
|
148
|
+
newtid, newprecv = self._precvs_waiting.popitem()
|
|
140
149
|
self._precvs[newtid] = newprecv
|
|
141
150
|
self._procs[newtid].start()
|
|
142
151
|
self._pworking += 1
|
sphinx/util/parsing.py
CHANGED
|
@@ -5,12 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
import contextlib
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from docutils.nodes import Element
|
|
8
|
+
from docutils.nodes import Element
|
|
9
9
|
from docutils.statemachine import StringList, string2lines
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from collections.abc import Iterator
|
|
13
13
|
|
|
14
|
+
from docutils.nodes import Node
|
|
14
15
|
from docutils.parsers.rst.states import RSTState
|
|
15
16
|
|
|
16
17
|
|
sphinx/util/png.py
CHANGED
|
@@ -4,6 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import binascii
|
|
6
6
|
import struct
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import os
|
|
7
11
|
|
|
8
12
|
LEN_IEND = 12
|
|
9
13
|
LEN_DEPTH = 22
|
|
@@ -13,7 +17,7 @@ DEPTH_CHUNK_START = b'tEXtDepth\x00'
|
|
|
13
17
|
IEND_CHUNK = b'\x00\x00\x00\x00IEND\xae\x42\x60\x82'
|
|
14
18
|
|
|
15
19
|
|
|
16
|
-
def read_png_depth(filename: str) -> int | None:
|
|
20
|
+
def read_png_depth(filename: str | os.PathLike[str]) -> int | None:
|
|
17
21
|
"""Read the special tEXt chunk indicating the depth from a PNG file."""
|
|
18
22
|
with open(filename, 'rb') as f:
|
|
19
23
|
f.seek(-(LEN_IEND + LEN_DEPTH), 2)
|
|
@@ -25,7 +29,7 @@ def read_png_depth(filename: str) -> int | None:
|
|
|
25
29
|
return struct.unpack('!i', depthchunk[14:18])[0]
|
|
26
30
|
|
|
27
31
|
|
|
28
|
-
def write_png_depth(filename: str, depth: int) -> None:
|
|
32
|
+
def write_png_depth(filename: str | os.PathLike[str], depth: int) -> None:
|
|
29
33
|
"""Write the special tEXt chunk indicating the depth to a PNG file.
|
|
30
34
|
|
|
31
35
|
The chunk is placed immediately before the special IEND chunk.
|
sphinx/util/requests.py
CHANGED
|
@@ -3,20 +3,34 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import warnings
|
|
6
|
-
from typing import
|
|
7
|
-
from urllib.parse import urlsplit
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
from urllib.parse import urljoin, urlsplit
|
|
8
8
|
|
|
9
9
|
import requests
|
|
10
10
|
from urllib3.exceptions import InsecureRequestWarning
|
|
11
11
|
|
|
12
12
|
import sphinx
|
|
13
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
import re
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
|
|
14
20
|
_USER_AGENT = (
|
|
15
21
|
f'Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0 '
|
|
16
22
|
f'Sphinx/{sphinx.__version__}'
|
|
17
23
|
)
|
|
18
24
|
|
|
19
25
|
|
|
26
|
+
class _IgnoredRedirection(Exception):
|
|
27
|
+
"""Sphinx-internal exception raised when an HTTP redirect is ignored"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, destination: str, status_code: int) -> None:
|
|
30
|
+
self.destination = destination
|
|
31
|
+
self.status_code = status_code
|
|
32
|
+
|
|
33
|
+
|
|
20
34
|
def _get_tls_cacert(url: str, certs: str | dict[str, str] | None) -> str | bool:
|
|
21
35
|
"""Get additional CA cert for a specific URL."""
|
|
22
36
|
if not certs:
|
|
@@ -50,6 +64,23 @@ def head(url: str, **kwargs: Any) -> requests.Response:
|
|
|
50
64
|
|
|
51
65
|
|
|
52
66
|
class _Session(requests.Session):
|
|
67
|
+
_ignored_redirects: Sequence[re.Pattern[str]]
|
|
68
|
+
|
|
69
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
70
|
+
self._ignored_redirects = kwargs.pop('_ignored_redirects', ())
|
|
71
|
+
super().__init__(*args, **kwargs)
|
|
72
|
+
|
|
73
|
+
def get_redirect_target(self, resp: requests.Response) -> str | None:
|
|
74
|
+
"""Overrides the default requests.Session.get_redirect_target"""
|
|
75
|
+
# do not follow redirections that match ignored URI patterns
|
|
76
|
+
if resp.is_redirect:
|
|
77
|
+
destination = urljoin(resp.url, resp.headers['location'])
|
|
78
|
+
if any(pat.match(destination) for pat in self._ignored_redirects):
|
|
79
|
+
raise _IgnoredRedirection(
|
|
80
|
+
destination=destination, status_code=resp.status_code
|
|
81
|
+
)
|
|
82
|
+
return super().get_redirect_target(resp)
|
|
83
|
+
|
|
53
84
|
def request( # type: ignore[override]
|
|
54
85
|
self,
|
|
55
86
|
method: str,
|
sphinx/util/rst.py
CHANGED
|
@@ -12,7 +12,7 @@ from docutils.parsers.rst import roles
|
|
|
12
12
|
from docutils.parsers.rst.languages import en as english # type: ignore[attr-defined]
|
|
13
13
|
from docutils.parsers.rst.states import Body
|
|
14
14
|
from docutils.utils import Reporter
|
|
15
|
-
from jinja2 import
|
|
15
|
+
from jinja2 import pass_environment
|
|
16
16
|
|
|
17
17
|
from sphinx.locale import __
|
|
18
18
|
from sphinx.util import docutils, logging
|
|
@@ -21,6 +21,7 @@ if TYPE_CHECKING:
|
|
|
21
21
|
from collections.abc import Iterator
|
|
22
22
|
|
|
23
23
|
from docutils.statemachine import StringList
|
|
24
|
+
from jinja2 import Environment
|
|
24
25
|
|
|
25
26
|
logger = logging.getLogger(__name__)
|
|
26
27
|
|
|
@@ -105,7 +106,7 @@ def append_epilog(content: StringList, epilog: str) -> None:
|
|
|
105
106
|
if len(content) > 0:
|
|
106
107
|
source, lineno = content.info(-1)
|
|
107
108
|
# lineno will never be None, since len(content) > 0
|
|
108
|
-
lineno = cast(int, lineno)
|
|
109
|
+
lineno = cast('int', lineno)
|
|
109
110
|
else:
|
|
110
111
|
source = '<generated>'
|
|
111
112
|
lineno = 0
|
sphinx/util/tags.py
CHANGED
sphinx/util/template.py
CHANGED
|
@@ -4,8 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
from functools import partial
|
|
7
|
-
from
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
10
|
from jinja2 import TemplateNotFound
|
|
11
11
|
from jinja2.loaders import BaseLoader
|
|
@@ -18,9 +18,13 @@ from sphinx.util import rst, texescape
|
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
20
|
from collections.abc import Callable, Sequence
|
|
21
|
+
from typing import Any
|
|
21
22
|
|
|
22
23
|
from jinja2.environment import Environment
|
|
23
24
|
|
|
25
|
+
_TEMPLATES_PATH = package_dir / 'templates'
|
|
26
|
+
_LATEX_TEMPLATES_PATH = _TEMPLATES_PATH / 'latex'
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
class BaseRenderer:
|
|
26
30
|
def __init__(self, loader: BaseLoader | None = None) -> None:
|
|
@@ -49,11 +53,12 @@ class FileRenderer(BaseRenderer):
|
|
|
49
53
|
|
|
50
54
|
@classmethod
|
|
51
55
|
def render_from_file(
|
|
52
|
-
cls: type[FileRenderer],
|
|
56
|
+
cls: type[FileRenderer],
|
|
57
|
+
filename: str | os.PathLike[str],
|
|
58
|
+
context: dict[str, Any],
|
|
53
59
|
) -> str:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return cls(dirname).render(basename, context)
|
|
60
|
+
filename = Path(filename)
|
|
61
|
+
return cls((filename.parent,)).render(filename.name, context)
|
|
57
62
|
|
|
58
63
|
|
|
59
64
|
class SphinxRenderer(FileRenderer):
|
|
@@ -61,12 +66,14 @@ class SphinxRenderer(FileRenderer):
|
|
|
61
66
|
self, template_path: Sequence[str | os.PathLike[str]] | None = None
|
|
62
67
|
) -> None:
|
|
63
68
|
if template_path is None:
|
|
64
|
-
template_path =
|
|
69
|
+
template_path = (_TEMPLATES_PATH,)
|
|
65
70
|
super().__init__(template_path)
|
|
66
71
|
|
|
67
72
|
@classmethod
|
|
68
73
|
def render_from_file(
|
|
69
|
-
cls: type[FileRenderer],
|
|
74
|
+
cls: type[FileRenderer],
|
|
75
|
+
filename: str | os.PathLike[str],
|
|
76
|
+
context: dict[str, Any],
|
|
70
77
|
) -> str:
|
|
71
78
|
return FileRenderer.render_from_file(filename, context)
|
|
72
79
|
|
|
@@ -78,7 +85,7 @@ class LaTeXRenderer(SphinxRenderer):
|
|
|
78
85
|
latex_engine: str | None = None,
|
|
79
86
|
) -> None:
|
|
80
87
|
if template_path is None:
|
|
81
|
-
template_path =
|
|
88
|
+
template_path = (_LATEX_TEMPLATES_PATH,)
|
|
82
89
|
super().__init__(template_path)
|
|
83
90
|
|
|
84
91
|
# use texescape as escape filter
|
|
@@ -126,8 +133,9 @@ class SphinxTemplateLoader(BaseLoader):
|
|
|
126
133
|
self.loaders = []
|
|
127
134
|
self.sysloaders = []
|
|
128
135
|
|
|
136
|
+
conf_dir = Path(confdir)
|
|
129
137
|
for templates_path in templates_paths:
|
|
130
|
-
loader = SphinxFileSystemLoader(
|
|
138
|
+
loader = SphinxFileSystemLoader(conf_dir / templates_path)
|
|
131
139
|
self.loaders.append(loader)
|
|
132
140
|
|
|
133
141
|
for templates_path in system_templates_paths:
|
sphinx/util/texescape.py
CHANGED
|
@@ -29,14 +29,16 @@ tex_replacements = [
|
|
|
29
29
|
# map some special Unicode characters to similar ASCII ones
|
|
30
30
|
# (even for Unicode LaTeX as may not be supported by OpenType font)
|
|
31
31
|
('⎽', r'\_'),
|
|
32
|
-
('ℯ', r'e'),
|
|
33
|
-
('ⅈ', r'i'),
|
|
32
|
+
('ℯ', r'e'), # U+212F # NoQA: RUF001
|
|
33
|
+
('ⅈ', r'i'), # U+2148 # NoQA: RUF001
|
|
34
34
|
# Greek alphabet not escaped: pdflatex handles it via textalpha and inputenc
|
|
35
35
|
# OHM SIGN U+2126 is handled by LaTeX textcomp package
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
# A map to avoid TeX ligatures or character replacements in PDF output
|
|
39
|
-
# xelatex/lualatex/uplatex are handled differently
|
|
39
|
+
# xelatex/lualatex/uplatex are handled differently
|
|
40
|
+
# https://github.com/sphinx-doc/sphinx/pull/5790
|
|
41
|
+
# https://github.com/sphinx-doc/sphinx/pull/6888
|
|
40
42
|
ascii_tex_replacements = [
|
|
41
43
|
# Note: the " renders curly in OT1 encoding but straight in T1, T2A, LY1...
|
|
42
44
|
# escaping it to \textquotedbl would break documents using OT1
|
|
@@ -63,7 +65,7 @@ unicode_tex_replacements = [
|
|
|
63
65
|
('±', r'\(\pm\)'),
|
|
64
66
|
('→', r'\(\rightarrow\)'),
|
|
65
67
|
('‣', r'\(\rightarrow\)'),
|
|
66
|
-
('
|
|
68
|
+
('\N{EN DASH}', r'\textendash{}'),
|
|
67
69
|
# superscript
|
|
68
70
|
('⁰', r'\(\sp{\text{0}}\)'),
|
|
69
71
|
('¹', r'\(\sp{\text{1}}\)'),
|
|
@@ -103,7 +105,7 @@ _tex_hlescape_map_without_unicode: dict[int, str] = {}
|
|
|
103
105
|
|
|
104
106
|
def escape(s: str, latex_engine: str | None = None) -> str:
|
|
105
107
|
"""Escape text for LaTeX output."""
|
|
106
|
-
if latex_engine in
|
|
108
|
+
if latex_engine in {'lualatex', 'xelatex'}:
|
|
107
109
|
# unicode based LaTeX engine
|
|
108
110
|
return s.translate(_tex_escape_map_without_unicode)
|
|
109
111
|
else:
|
|
@@ -112,7 +114,7 @@ def escape(s: str, latex_engine: str | None = None) -> str:
|
|
|
112
114
|
|
|
113
115
|
def hlescape(s: str, latex_engine: str | None = None) -> str:
|
|
114
116
|
"""Escape text for LaTeX highlighter."""
|
|
115
|
-
if latex_engine in
|
|
117
|
+
if latex_engine in {'lualatex', 'xelatex'}:
|
|
116
118
|
# unicode based LaTeX engine
|
|
117
119
|
return s.translate(_tex_hlescape_map_without_unicode)
|
|
118
120
|
else:
|