Sphinx 7.4.6__py3-none-any.whl → 8.0.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 +2 -2
- sphinx/_cli/__init__.py +4 -4
- sphinx/application.py +7 -7
- sphinx/builders/__init__.py +2 -3
- sphinx/builders/_epub_base.py +33 -12
- sphinx/builders/changes.py +13 -5
- sphinx/builders/epub3.py +6 -2
- sphinx/builders/html/__init__.py +90 -59
- sphinx/builders/latex/__init__.py +38 -12
- sphinx/builders/latex/transforms.py +1 -1
- sphinx/builders/linkcheck.py +8 -49
- sphinx/builders/texinfo.py +12 -6
- sphinx/builders/text.py +7 -3
- sphinx/builders/xml.py +7 -3
- sphinx/cmd/quickstart.py +10 -20
- sphinx/config.py +12 -12
- sphinx/deprecation.py +8 -8
- sphinx/directives/__init__.py +14 -9
- sphinx/directives/other.py +2 -3
- sphinx/directives/patches.py +2 -2
- sphinx/domains/__init__.py +4 -2
- sphinx/domains/c/__init__.py +2 -2
- sphinx/domains/c/_ast.py +3 -2
- sphinx/domains/c/_parser.py +4 -3
- sphinx/domains/cpp/__init__.py +2 -2
- sphinx/domains/cpp/_ast.py +1 -2
- sphinx/domains/cpp/_parser.py +2 -2
- sphinx/domains/cpp/_symbol.py +2 -2
- sphinx/domains/javascript.py +1 -1
- sphinx/domains/math.py +1 -1
- sphinx/domains/python/__init__.py +1 -1
- sphinx/domains/python/_annotations.py +23 -1
- sphinx/domains/python/_object.py +0 -1
- sphinx/domains/std/__init__.py +7 -8
- sphinx/environment/__init__.py +14 -32
- sphinx/environment/adapters/indexentries.py +4 -6
- sphinx/environment/adapters/toctree.py +4 -4
- sphinx/environment/collectors/title.py +1 -1
- sphinx/environment/collectors/toctree.py +1 -1
- sphinx/events.py +3 -1
- sphinx/ext/autodoc/__init__.py +25 -67
- sphinx/ext/autodoc/directive.py +7 -5
- sphinx/ext/autodoc/importer.py +2 -1
- sphinx/ext/autodoc/preserve_defaults.py +2 -2
- sphinx/ext/autosummary/__init__.py +15 -7
- sphinx/ext/autosummary/generate.py +5 -4
- sphinx/ext/doctest.py +5 -5
- sphinx/ext/graphviz.py +1 -1
- sphinx/ext/imgmath.py +1 -1
- sphinx/ext/inheritance_diagram.py +1 -1
- sphinx/ext/intersphinx/__init__.py +25 -5
- sphinx/ext/intersphinx/_cli.py +7 -6
- sphinx/ext/intersphinx/_load.py +240 -115
- sphinx/ext/intersphinx/_resolve.py +12 -11
- sphinx/ext/intersphinx/_shared.py +102 -9
- sphinx/ext/mathjax.py +1 -1
- sphinx/ext/napoleon/docstring.py +2 -2
- sphinx/ext/todo.py +2 -2
- sphinx/ext/viewcode.py +2 -1
- sphinx/highlighting.py +3 -3
- sphinx/io.py +2 -2
- sphinx/jinja2glue.py +13 -6
- sphinx/locale/__init__.py +4 -3
- sphinx/locale/ta/LC_MESSAGES/sphinx.js +54 -54
- sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ta/LC_MESSAGES/sphinx.po +1578 -1843
- sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +496 -704
- sphinx/project.py +23 -19
- sphinx/pycode/ast.py +2 -2
- sphinx/pycode/parser.py +2 -2
- sphinx/pygments_styles.py +3 -3
- sphinx/registry.py +3 -8
- sphinx/search/__init__.py +1 -1
- sphinx/testing/path.py +2 -1
- sphinx/testing/util.py +1 -1
- sphinx/texinputs/Makefile.jinja +2 -1
- sphinx/texinputs_win/Makefile.jinja +2 -1
- sphinx/theming.py +3 -12
- sphinx/transforms/__init__.py +5 -5
- sphinx/transforms/references.py +1 -1
- sphinx/util/__init__.py +11 -35
- sphinx/util/_timestamps.py +12 -0
- sphinx/util/cfamily.py +5 -5
- sphinx/util/console.py +4 -3
- sphinx/util/display.py +3 -3
- sphinx/util/docfields.py +1 -1
- sphinx/util/docutils.py +44 -10
- sphinx/util/fileutil.py +41 -9
- sphinx/util/i18n.py +9 -4
- sphinx/util/images.py +3 -2
- sphinx/util/inspect.py +29 -44
- sphinx/util/inventory.py +2 -2
- sphinx/util/matching.py +2 -2
- sphinx/util/math.py +1 -1
- sphinx/util/nodes.py +8 -8
- sphinx/util/osutil.py +46 -23
- sphinx/util/parallel.py +2 -2
- sphinx/util/requests.py +1 -1
- sphinx/util/template.py +3 -3
- sphinx/util/typing.py +67 -70
- sphinx/writers/html.py +1 -1
- sphinx/writers/html5.py +1 -1
- sphinx/writers/latex.py +4 -4
- sphinx/writers/manpage.py +2 -2
- sphinx/writers/texinfo.py +5 -5
- sphinx/writers/text.py +4 -4
- sphinx/writers/xml.py +2 -2
- {sphinx-7.4.6.dist-info → sphinx-8.0.0rc1.dist-info}/METADATA +10 -9
- {sphinx-7.4.6.dist-info → sphinx-8.0.0rc1.dist-info}/RECORD +112 -114
- sphinx/templates/quickstart/Makefile.jinja +0 -98
- sphinx/templates/quickstart/make.bat.jinja +0 -110
- sphinx/util/_pathlib.py +0 -120
- {sphinx-7.4.6.dist-info → sphinx-8.0.0rc1.dist-info}/LICENSE.rst +0 -0
- {sphinx-7.4.6.dist-info → sphinx-8.0.0rc1.dist-info}/WHEEL +0 -0
- {sphinx-7.4.6.dist-info → sphinx-8.0.0rc1.dist-info}/entry_points.txt +0 -0
sphinx/ext/intersphinx/_load.py
CHANGED
|
@@ -6,175 +6,300 @@ import concurrent.futures
|
|
|
6
6
|
import functools
|
|
7
7
|
import posixpath
|
|
8
8
|
import time
|
|
9
|
+
from operator import itemgetter
|
|
9
10
|
from os import path
|
|
10
11
|
from typing import TYPE_CHECKING
|
|
11
12
|
from urllib.parse import urlsplit, urlunsplit
|
|
12
13
|
|
|
13
14
|
from sphinx.builders.html import INVENTORY_FILENAME
|
|
14
|
-
from sphinx.
|
|
15
|
+
from sphinx.errors import ConfigError
|
|
16
|
+
from sphinx.ext.intersphinx._shared import LOGGER, InventoryAdapter, _IntersphinxProject
|
|
15
17
|
from sphinx.locale import __
|
|
16
18
|
from sphinx.util import requests
|
|
17
19
|
from sphinx.util.inventory import InventoryFile
|
|
18
20
|
|
|
19
21
|
if TYPE_CHECKING:
|
|
22
|
+
from pathlib import Path
|
|
20
23
|
from typing import IO
|
|
21
24
|
|
|
22
25
|
from sphinx.application import Sphinx
|
|
23
26
|
from sphinx.config import Config
|
|
24
|
-
from sphinx.ext.intersphinx._shared import
|
|
27
|
+
from sphinx.ext.intersphinx._shared import (
|
|
28
|
+
IntersphinxMapping,
|
|
29
|
+
InventoryCacheEntry,
|
|
30
|
+
InventoryLocation,
|
|
31
|
+
InventoryName,
|
|
32
|
+
InventoryURI,
|
|
33
|
+
)
|
|
25
34
|
from sphinx.util.typing import Inventory
|
|
26
35
|
|
|
27
36
|
|
|
28
|
-
def
|
|
29
|
-
|
|
37
|
+
def validate_intersphinx_mapping(app: Sphinx, config: Config) -> None:
|
|
38
|
+
"""Validate and normalise :confval:`intersphinx_mapping`.
|
|
39
|
+
|
|
40
|
+
Ensure that:
|
|
41
|
+
|
|
42
|
+
* Keys are non-empty strings.
|
|
43
|
+
* Values are two-element tuples or lists.
|
|
44
|
+
* The first element of each value pair (the target URI)
|
|
45
|
+
is a non-empty string.
|
|
46
|
+
* The second element of each value pair (inventory locations)
|
|
47
|
+
is a tuple of non-empty strings or None.
|
|
48
|
+
"""
|
|
49
|
+
# URIs should NOT be duplicated, otherwise different builds may use
|
|
50
|
+
# different project names (and thus, the build are no more reproducible)
|
|
51
|
+
# depending on which one is inserted last in the cache.
|
|
52
|
+
seen: dict[InventoryURI, InventoryName] = {}
|
|
53
|
+
|
|
54
|
+
errors = 0
|
|
55
|
+
for name, value in config.intersphinx_mapping.copy().items():
|
|
56
|
+
# ensure that intersphinx projects are always named
|
|
57
|
+
if not isinstance(name, str) or not name:
|
|
58
|
+
errors += 1
|
|
59
|
+
msg = __(
|
|
60
|
+
'Invalid intersphinx project identifier `%r` in intersphinx_mapping. '
|
|
61
|
+
'Project identifiers must be non-empty strings.'
|
|
62
|
+
)
|
|
63
|
+
LOGGER.error(msg, name)
|
|
64
|
+
del config.intersphinx_mapping[name]
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
# ensure values are properly formatted
|
|
68
|
+
if not isinstance(value, (tuple | list)):
|
|
69
|
+
errors += 1
|
|
70
|
+
msg = __(
|
|
71
|
+
'Invalid value `%r` in intersphinx_mapping[%r]. '
|
|
72
|
+
'Expected a two-element tuple or list.'
|
|
73
|
+
)
|
|
74
|
+
LOGGER.error(msg, value, name)
|
|
75
|
+
del config.intersphinx_mapping[name]
|
|
76
|
+
continue
|
|
30
77
|
try:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
78
|
+
uri, inv = value
|
|
79
|
+
except (TypeError, ValueError, Exception):
|
|
80
|
+
errors += 1
|
|
81
|
+
msg = __(
|
|
82
|
+
'Invalid value `%r` in intersphinx_mapping[%r]. '
|
|
83
|
+
'Values must be a (target URI, inventory locations) pair.'
|
|
84
|
+
)
|
|
85
|
+
LOGGER.error(msg, value, name)
|
|
86
|
+
del config.intersphinx_mapping[name]
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# ensure target URIs are non-empty and unique
|
|
90
|
+
if not uri or not isinstance(uri, str):
|
|
91
|
+
errors += 1
|
|
92
|
+
msg = __('Invalid target URI value `%r` in intersphinx_mapping[%r][0]. '
|
|
93
|
+
'Target URIs must be unique non-empty strings.')
|
|
94
|
+
LOGGER.error(msg, uri, name)
|
|
95
|
+
del config.intersphinx_mapping[name]
|
|
96
|
+
continue
|
|
97
|
+
if uri in seen:
|
|
98
|
+
errors += 1
|
|
99
|
+
msg = __(
|
|
100
|
+
'Invalid target URI value `%r` in intersphinx_mapping[%r][0]. '
|
|
101
|
+
'Target URIs must be unique (other instance in intersphinx_mapping[%r]).'
|
|
102
|
+
)
|
|
103
|
+
LOGGER.error(msg, uri, name, seen[uri])
|
|
104
|
+
del config.intersphinx_mapping[name]
|
|
105
|
+
continue
|
|
106
|
+
seen[uri] = name
|
|
107
|
+
|
|
108
|
+
# ensure inventory locations are None or non-empty
|
|
109
|
+
targets: list[InventoryLocation] = []
|
|
110
|
+
for target in (inv if isinstance(inv, (tuple | list)) else (inv,)):
|
|
111
|
+
if target is None or target and isinstance(target, str):
|
|
112
|
+
targets.append(target)
|
|
39
113
|
else:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"The pre-Sphinx 1.0 'intersphinx_mapping' format is "
|
|
45
|
-
'deprecated and will be removed in Sphinx 8. Update to the '
|
|
46
|
-
'current format as described in the documentation. '
|
|
47
|
-
f"Hint: `intersphinx_mapping = {{'<name>': {(uri, inv)!r}}}`."
|
|
48
|
-
'https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#confval-intersphinx_mapping' # NoQA: E501
|
|
114
|
+
errors += 1
|
|
115
|
+
msg = __(
|
|
116
|
+
'Invalid inventory location value `%r` in intersphinx_mapping[%r][1]. '
|
|
117
|
+
'Inventory locations must be non-empty strings or None.'
|
|
49
118
|
)
|
|
50
|
-
LOGGER.
|
|
119
|
+
LOGGER.error(msg, target, name)
|
|
120
|
+
del config.intersphinx_mapping[name]
|
|
121
|
+
continue
|
|
51
122
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
123
|
+
config.intersphinx_mapping[name] = (name, (uri, tuple(targets)))
|
|
124
|
+
|
|
125
|
+
if errors == 1:
|
|
126
|
+
msg = __('Invalid `intersphinx_mapping` configuration (1 error).')
|
|
127
|
+
raise ConfigError(msg)
|
|
128
|
+
if errors > 1:
|
|
129
|
+
msg = __('Invalid `intersphinx_mapping` configuration (%s errors).')
|
|
130
|
+
raise ConfigError(msg % errors)
|
|
59
131
|
|
|
60
132
|
|
|
61
133
|
def load_mappings(app: Sphinx) -> None:
|
|
62
|
-
"""Load all intersphinx mappings into the environment.
|
|
134
|
+
"""Load all intersphinx mappings into the environment.
|
|
135
|
+
|
|
136
|
+
The intersphinx mappings are expected to be normalized.
|
|
137
|
+
"""
|
|
63
138
|
now = int(time.time())
|
|
64
139
|
inventories = InventoryAdapter(app.builder.env)
|
|
65
|
-
intersphinx_cache: dict[
|
|
140
|
+
intersphinx_cache: dict[InventoryURI, InventoryCacheEntry] = inventories.cache
|
|
141
|
+
intersphinx_mapping: IntersphinxMapping = app.config.intersphinx_mapping
|
|
142
|
+
|
|
143
|
+
projects = []
|
|
144
|
+
for name, (uri, locations) in intersphinx_mapping.values():
|
|
145
|
+
try:
|
|
146
|
+
project = _IntersphinxProject(name=name, target_uri=uri, locations=locations)
|
|
147
|
+
except ValueError as err:
|
|
148
|
+
msg = __('An invalid intersphinx_mapping entry was added after normalisation.')
|
|
149
|
+
raise ConfigError(msg) from err
|
|
150
|
+
else:
|
|
151
|
+
projects.append(project)
|
|
152
|
+
|
|
153
|
+
expected_uris = {project.target_uri for project in projects}
|
|
154
|
+
for uri in frozenset(intersphinx_cache):
|
|
155
|
+
if intersphinx_cache[uri][0] not in intersphinx_mapping:
|
|
156
|
+
# Remove all cached entries that are no longer in `intersphinx_mapping`.
|
|
157
|
+
del intersphinx_cache[uri]
|
|
158
|
+
elif uri not in expected_uris:
|
|
159
|
+
# Remove cached entries with a different target URI
|
|
160
|
+
# than the one in `intersphinx_mapping`.
|
|
161
|
+
# This happens when the URI in `intersphinx_mapping` is changed.
|
|
162
|
+
del intersphinx_cache[uri]
|
|
66
163
|
|
|
67
164
|
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
68
|
-
futures = [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
165
|
+
futures = [
|
|
166
|
+
pool.submit(
|
|
167
|
+
_fetch_inventory_group,
|
|
168
|
+
project=project,
|
|
169
|
+
cache=intersphinx_cache,
|
|
170
|
+
now=now,
|
|
171
|
+
config=app.config,
|
|
172
|
+
srcdir=app.srcdir,
|
|
173
|
+
)
|
|
174
|
+
for project in projects
|
|
175
|
+
]
|
|
76
176
|
updated = [f.result() for f in concurrent.futures.as_completed(futures)]
|
|
77
177
|
|
|
78
178
|
if any(updated):
|
|
179
|
+
# clear the local inventories
|
|
79
180
|
inventories.clear()
|
|
80
181
|
|
|
81
182
|
# Duplicate values in different inventories will shadow each
|
|
82
|
-
# other; which one will override which can vary between builds
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
inventories.main_inventory.setdefault(type, {}).update(objects)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def fetch_inventory_group(
|
|
103
|
-
name: str | None,
|
|
104
|
-
uri: str,
|
|
105
|
-
invs: tuple[str | None, ...],
|
|
106
|
-
cache: dict[str, InventoryCacheEntry],
|
|
107
|
-
app: Sphinx,
|
|
183
|
+
# other; which one will override which can vary between builds.
|
|
184
|
+
#
|
|
185
|
+
# In an attempt to make this more consistent,
|
|
186
|
+
# we sort the named inventories in the cache
|
|
187
|
+
# by their name and expiry time ``(NAME, EXPIRY)``.
|
|
188
|
+
by_name_and_time = itemgetter(0, 1) # 0: name, 1: expiry
|
|
189
|
+
cache_values = sorted(intersphinx_cache.values(), key=by_name_and_time)
|
|
190
|
+
for name, _expiry, invdata in cache_values:
|
|
191
|
+
inventories.named_inventory[name] = invdata
|
|
192
|
+
for objtype, objects in invdata.items():
|
|
193
|
+
inventories.main_inventory.setdefault(objtype, {}).update(objects)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _fetch_inventory_group(
|
|
197
|
+
*,
|
|
198
|
+
project: _IntersphinxProject,
|
|
199
|
+
cache: dict[InventoryURI, InventoryCacheEntry],
|
|
108
200
|
now: int,
|
|
201
|
+
config: Config,
|
|
202
|
+
srcdir: Path,
|
|
109
203
|
) -> bool:
|
|
110
|
-
cache_time = now -
|
|
204
|
+
cache_time = now - config.intersphinx_cache_limit * 86400
|
|
205
|
+
|
|
206
|
+
updated = False
|
|
111
207
|
failures = []
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
208
|
+
|
|
209
|
+
for location in project.locations:
|
|
210
|
+
# location is either None or a non-empty string
|
|
211
|
+
inv = f'{project.target_uri}/{INVENTORY_FILENAME}' if location is None else location
|
|
212
|
+
|
|
213
|
+
# decide whether the inventory must be read: always read local
|
|
214
|
+
# files; remote ones only if the cache time is expired
|
|
215
|
+
if (
|
|
216
|
+
'://' not in inv
|
|
217
|
+
or project.target_uri not in cache
|
|
218
|
+
or cache[project.target_uri][1] < cache_time
|
|
219
|
+
):
|
|
220
|
+
LOGGER.info(__("loading intersphinx inventory '%s' from %s ..."),
|
|
221
|
+
project.name, _get_safe_url(inv))
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
invdata = _fetch_inventory(
|
|
225
|
+
target_uri=project.target_uri,
|
|
226
|
+
inv_location=inv,
|
|
227
|
+
config=config,
|
|
228
|
+
srcdir=srcdir,
|
|
229
|
+
)
|
|
230
|
+
except Exception as err:
|
|
231
|
+
failures.append(err.args)
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
if invdata:
|
|
235
|
+
cache[project.target_uri] = project.name, now, invdata
|
|
236
|
+
updated = True
|
|
237
|
+
break
|
|
238
|
+
|
|
239
|
+
if not failures:
|
|
240
|
+
pass
|
|
241
|
+
elif len(failures) < len(project.locations):
|
|
242
|
+
LOGGER.info(__('encountered some issues with some of the inventories,'
|
|
243
|
+
' but they had working alternatives:'))
|
|
244
|
+
for fail in failures:
|
|
245
|
+
LOGGER.info(*fail)
|
|
246
|
+
else:
|
|
247
|
+
issues = '\n'.join(f[0] % f[1:] for f in failures)
|
|
248
|
+
LOGGER.warning(__('failed to reach any of the inventories '
|
|
249
|
+
'with the following issues:') + '\n' + issues)
|
|
250
|
+
return updated
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def fetch_inventory(app: Sphinx, uri: InventoryURI, inv: str) -> Inventory:
|
|
254
|
+
"""Fetch, parse and return an intersphinx inventory file."""
|
|
255
|
+
return _fetch_inventory(
|
|
256
|
+
target_uri=uri,
|
|
257
|
+
inv_location=inv,
|
|
258
|
+
config=app.config,
|
|
259
|
+
srcdir=app.srcdir,
|
|
260
|
+
)
|
|
144
261
|
|
|
145
262
|
|
|
146
|
-
def
|
|
263
|
+
def _fetch_inventory(
|
|
264
|
+
*, target_uri: InventoryURI, inv_location: str, config: Config, srcdir: Path,
|
|
265
|
+
) -> Inventory:
|
|
147
266
|
"""Fetch, parse and return an intersphinx inventory file."""
|
|
148
|
-
# both *
|
|
149
|
-
# location of the inventory file)
|
|
150
|
-
|
|
267
|
+
# both *target_uri* (base URI of the links to generate)
|
|
268
|
+
# and *inv_location* (actual location of the inventory file)
|
|
269
|
+
# can be local or remote URIs
|
|
270
|
+
if '://' in target_uri:
|
|
151
271
|
# case: inv URI points to remote resource; strip any existing auth
|
|
152
|
-
|
|
272
|
+
target_uri = _strip_basic_auth(target_uri)
|
|
153
273
|
try:
|
|
154
|
-
if '://' in
|
|
155
|
-
f = _read_from_url(
|
|
274
|
+
if '://' in inv_location:
|
|
275
|
+
f = _read_from_url(inv_location, config=config)
|
|
156
276
|
else:
|
|
157
|
-
f = open(path.join(
|
|
277
|
+
f = open(path.join(srcdir, inv_location), 'rb') # NoQA: SIM115
|
|
158
278
|
except Exception as err:
|
|
159
279
|
err.args = ('intersphinx inventory %r not fetchable due to %s: %s',
|
|
160
|
-
|
|
280
|
+
inv_location, err.__class__, str(err))
|
|
161
281
|
raise
|
|
162
282
|
try:
|
|
163
283
|
if hasattr(f, 'url'):
|
|
164
|
-
|
|
165
|
-
if
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
284
|
+
new_inv_location = f.url
|
|
285
|
+
if inv_location != new_inv_location:
|
|
286
|
+
msg = __('intersphinx inventory has moved: %s -> %s')
|
|
287
|
+
LOGGER.info(msg, inv_location, new_inv_location)
|
|
288
|
+
|
|
289
|
+
if target_uri in {
|
|
290
|
+
inv_location,
|
|
291
|
+
path.dirname(inv_location),
|
|
292
|
+
path.dirname(inv_location) + '/'
|
|
293
|
+
}:
|
|
294
|
+
target_uri = path.dirname(new_inv_location)
|
|
170
295
|
with f:
|
|
171
296
|
try:
|
|
172
|
-
invdata = InventoryFile.load(f,
|
|
297
|
+
invdata = InventoryFile.load(f, target_uri, posixpath.join)
|
|
173
298
|
except ValueError as exc:
|
|
174
299
|
raise ValueError('unknown or unsupported inventory version: %r' % exc) from exc
|
|
175
300
|
except Exception as err:
|
|
176
301
|
err.args = ('intersphinx inventory %r not readable due to %s: %s',
|
|
177
|
-
|
|
302
|
+
inv_location, err.__class__.__name__, str(err))
|
|
178
303
|
raise
|
|
179
304
|
else:
|
|
180
305
|
return invdata
|
|
@@ -28,10 +28,11 @@ if TYPE_CHECKING:
|
|
|
28
28
|
from sphinx.application import Sphinx
|
|
29
29
|
from sphinx.domains import Domain
|
|
30
30
|
from sphinx.environment import BuildEnvironment
|
|
31
|
+
from sphinx.ext.intersphinx._shared import InventoryName
|
|
31
32
|
from sphinx.util.typing import Inventory, InventoryItem, RoleFunction
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
def _create_element_from_result(domain: Domain, inv_name:
|
|
35
|
+
def _create_element_from_result(domain: Domain, inv_name: InventoryName | None,
|
|
35
36
|
data: InventoryItem,
|
|
36
37
|
node: pending_xref, contnode: TextElement) -> nodes.reference:
|
|
37
38
|
proj, version, uri, dispname = data
|
|
@@ -61,7 +62,7 @@ def _create_element_from_result(domain: Domain, inv_name: str | None,
|
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
def _resolve_reference_in_domain_by_target(
|
|
64
|
-
inv_name:
|
|
65
|
+
inv_name: InventoryName | None, inventory: Inventory,
|
|
65
66
|
domain: Domain, objtypes: Iterable[str],
|
|
66
67
|
target: str,
|
|
67
68
|
node: pending_xref, contnode: TextElement) -> nodes.reference | None:
|
|
@@ -100,7 +101,7 @@ def _resolve_reference_in_domain_by_target(
|
|
|
100
101
|
|
|
101
102
|
|
|
102
103
|
def _resolve_reference_in_domain(env: BuildEnvironment,
|
|
103
|
-
inv_name:
|
|
104
|
+
inv_name: InventoryName | None, inventory: Inventory,
|
|
104
105
|
honor_disabled_refs: bool,
|
|
105
106
|
domain: Domain, objtypes: Iterable[str],
|
|
106
107
|
node: pending_xref, contnode: TextElement,
|
|
@@ -142,20 +143,21 @@ def _resolve_reference_in_domain(env: BuildEnvironment,
|
|
|
142
143
|
full_qualified_name, node, contnode)
|
|
143
144
|
|
|
144
145
|
|
|
145
|
-
def _resolve_reference(env: BuildEnvironment,
|
|
146
|
+
def _resolve_reference(env: BuildEnvironment,
|
|
147
|
+
inv_name: InventoryName | None, inventory: Inventory,
|
|
146
148
|
honor_disabled_refs: bool,
|
|
147
149
|
node: pending_xref, contnode: TextElement) -> nodes.reference | None:
|
|
148
150
|
# disabling should only be done if no inventory is given
|
|
149
151
|
honor_disabled_refs = honor_disabled_refs and inv_name is None
|
|
152
|
+
intersphinx_disabled_reftypes = env.config.intersphinx_disabled_reftypes
|
|
150
153
|
|
|
151
|
-
if honor_disabled_refs and '*' in
|
|
154
|
+
if honor_disabled_refs and '*' in intersphinx_disabled_reftypes:
|
|
152
155
|
return None
|
|
153
156
|
|
|
154
157
|
typ = node['reftype']
|
|
155
158
|
if typ == 'any':
|
|
156
159
|
for domain_name, domain in env.domains.items():
|
|
157
|
-
if
|
|
158
|
-
and (domain_name + ':*') in env.config.intersphinx_disabled_reftypes):
|
|
160
|
+
if honor_disabled_refs and f'{domain_name}:*' in intersphinx_disabled_reftypes:
|
|
159
161
|
continue
|
|
160
162
|
objtypes: Iterable[str] = domain.object_types.keys()
|
|
161
163
|
res = _resolve_reference_in_domain(env, inv_name, inventory,
|
|
@@ -170,8 +172,7 @@ def _resolve_reference(env: BuildEnvironment, inv_name: str | None, inventory: I
|
|
|
170
172
|
if not domain_name:
|
|
171
173
|
# only objects in domains are in the inventory
|
|
172
174
|
return None
|
|
173
|
-
if
|
|
174
|
-
and (domain_name + ':*') in env.config.intersphinx_disabled_reftypes):
|
|
175
|
+
if honor_disabled_refs and f'{domain_name}:*' in intersphinx_disabled_reftypes:
|
|
175
176
|
return None
|
|
176
177
|
domain = env.get_domain(domain_name)
|
|
177
178
|
objtypes = domain.objtypes_for_role(typ) or ()
|
|
@@ -183,12 +184,12 @@ def _resolve_reference(env: BuildEnvironment, inv_name: str | None, inventory: I
|
|
|
183
184
|
node, contnode)
|
|
184
185
|
|
|
185
186
|
|
|
186
|
-
def inventory_exists(env: BuildEnvironment, inv_name:
|
|
187
|
+
def inventory_exists(env: BuildEnvironment, inv_name: InventoryName) -> bool:
|
|
187
188
|
return inv_name in InventoryAdapter(env).named_inventory
|
|
188
189
|
|
|
189
190
|
|
|
190
191
|
def resolve_reference_in_inventory(env: BuildEnvironment,
|
|
191
|
-
inv_name:
|
|
192
|
+
inv_name: InventoryName,
|
|
192
193
|
node: pending_xref, contnode: TextElement,
|
|
193
194
|
) -> nodes.reference | None:
|
|
194
195
|
"""Attempt to resolve a missing reference via intersphinx references.
|
|
@@ -2,19 +2,113 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING, Final,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Final, NoReturn
|
|
6
6
|
|
|
7
7
|
from sphinx.util import logging
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
from typing import TypeAlias
|
|
12
|
+
|
|
10
13
|
from sphinx.environment import BuildEnvironment
|
|
11
14
|
from sphinx.util.typing import Inventory
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
#: The inventory project URL to which links are resolved.
|
|
17
|
+
#:
|
|
18
|
+
#: This value is unique in :confval:`intersphinx_mapping`.
|
|
19
|
+
InventoryURI = str
|
|
20
|
+
|
|
21
|
+
#: The inventory (non-empty) name.
|
|
22
|
+
#:
|
|
23
|
+
#: It is unique and in bijection with an inventory remote URL.
|
|
24
|
+
InventoryName = str
|
|
25
|
+
|
|
26
|
+
#: A target (local or remote) containing the inventory data to fetch.
|
|
27
|
+
#:
|
|
28
|
+
#: Empty strings are not expected and ``None`` indicates the default
|
|
29
|
+
#: inventory file name :data:`~sphinx.builder.html.INVENTORY_FILENAME`.
|
|
30
|
+
InventoryLocation = str | None
|
|
31
|
+
|
|
32
|
+
#: Inventory cache entry. The integer field is the cache expiration time.
|
|
33
|
+
InventoryCacheEntry: TypeAlias = tuple[InventoryName, int, Inventory]
|
|
34
|
+
|
|
35
|
+
#: The type of :confval:`intersphinx_mapping` *after* normalisation.
|
|
36
|
+
IntersphinxMapping = dict[
|
|
37
|
+
InventoryName,
|
|
38
|
+
tuple[InventoryName, tuple[InventoryURI, tuple[InventoryLocation, ...]]],
|
|
39
|
+
]
|
|
14
40
|
|
|
15
41
|
LOGGER: Final[logging.SphinxLoggerAdapter] = logging.getLogger('sphinx.ext.intersphinx')
|
|
16
42
|
|
|
17
43
|
|
|
44
|
+
class _IntersphinxProject:
|
|
45
|
+
name: InventoryName
|
|
46
|
+
target_uri: InventoryURI
|
|
47
|
+
locations: tuple[InventoryLocation, ...]
|
|
48
|
+
|
|
49
|
+
__slots__ = {
|
|
50
|
+
'name': 'The inventory name. '
|
|
51
|
+
'It is unique and in bijection with an remote inventory URL.',
|
|
52
|
+
'target_uri': 'The inventory project URL to which links are resolved. '
|
|
53
|
+
'It is unique and in bijection with an inventory name.',
|
|
54
|
+
'locations': 'A tuple of local or remote targets containing '
|
|
55
|
+
'the inventory data to fetch. '
|
|
56
|
+
'None indicates the default inventory file name.',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
*,
|
|
62
|
+
name: InventoryName,
|
|
63
|
+
target_uri: InventoryURI,
|
|
64
|
+
locations: Sequence[InventoryLocation],
|
|
65
|
+
) -> None:
|
|
66
|
+
if not name or not isinstance(name, str):
|
|
67
|
+
msg = 'name must be a non-empty string'
|
|
68
|
+
raise ValueError(msg)
|
|
69
|
+
if not target_uri or not isinstance(target_uri, str):
|
|
70
|
+
msg = 'target_uri must be a non-empty string'
|
|
71
|
+
raise ValueError(msg)
|
|
72
|
+
if not locations or not isinstance(locations, tuple):
|
|
73
|
+
msg = 'locations must be a non-empty tuple'
|
|
74
|
+
raise ValueError(msg)
|
|
75
|
+
if any(
|
|
76
|
+
location is not None and (not location or not isinstance(location, str))
|
|
77
|
+
for location in locations
|
|
78
|
+
):
|
|
79
|
+
msg = 'locations must be a tuple of strings or None'
|
|
80
|
+
raise ValueError(msg)
|
|
81
|
+
object.__setattr__(self, 'name', name)
|
|
82
|
+
object.__setattr__(self, 'target_uri', target_uri)
|
|
83
|
+
object.__setattr__(self, 'locations', tuple(locations))
|
|
84
|
+
|
|
85
|
+
def __repr__(self) -> str:
|
|
86
|
+
return (f'{self.__class__.__name__}('
|
|
87
|
+
f'name={self.name!r}, '
|
|
88
|
+
f'target_uri={self.target_uri!r}, '
|
|
89
|
+
f'locations={self.locations!r})')
|
|
90
|
+
|
|
91
|
+
def __eq__(self, other: object) -> bool:
|
|
92
|
+
if not isinstance(other, _IntersphinxProject):
|
|
93
|
+
return NotImplemented
|
|
94
|
+
return (
|
|
95
|
+
self.name == other.name
|
|
96
|
+
and self.target_uri == other.target_uri
|
|
97
|
+
and self.locations == other.locations
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def __hash__(self) -> int:
|
|
101
|
+
return hash((self.name, self.target_uri, self.locations))
|
|
102
|
+
|
|
103
|
+
def __setattr__(self, key: str, value: Any) -> NoReturn:
|
|
104
|
+
msg = f'{self.__class__.__name__} is immutable'
|
|
105
|
+
raise AttributeError(msg)
|
|
106
|
+
|
|
107
|
+
def __delattr__(self, key: str) -> NoReturn:
|
|
108
|
+
msg = f'{self.__class__.__name__} is immutable'
|
|
109
|
+
raise AttributeError(msg)
|
|
110
|
+
|
|
111
|
+
|
|
18
112
|
class InventoryAdapter:
|
|
19
113
|
"""Inventory adapter for environment"""
|
|
20
114
|
|
|
@@ -29,14 +123,13 @@ class InventoryAdapter:
|
|
|
29
123
|
self.env.intersphinx_named_inventory = {} # type: ignore[attr-defined]
|
|
30
124
|
|
|
31
125
|
@property
|
|
32
|
-
def cache(self) -> dict[
|
|
126
|
+
def cache(self) -> dict[InventoryURI, InventoryCacheEntry]:
|
|
33
127
|
"""Intersphinx cache.
|
|
34
128
|
|
|
35
|
-
- Key is the URI of the remote inventory
|
|
36
|
-
- Element one is the key given in the Sphinx intersphinx_mapping
|
|
37
|
-
|
|
38
|
-
- Element
|
|
39
|
-
- Element three is the loaded remote inventory, type Inventory
|
|
129
|
+
- Key is the URI of the remote inventory.
|
|
130
|
+
- Element one is the key given in the Sphinx :confval:`intersphinx_mapping`.
|
|
131
|
+
- Element two is a time value for cache invalidation, an integer.
|
|
132
|
+
- Element three is the loaded remote inventory of type :class:`!Inventory`.
|
|
40
133
|
"""
|
|
41
134
|
return self.env.intersphinx_cache # type: ignore[attr-defined]
|
|
42
135
|
|
|
@@ -45,7 +138,7 @@ class InventoryAdapter:
|
|
|
45
138
|
return self.env.intersphinx_inventory # type: ignore[attr-defined]
|
|
46
139
|
|
|
47
140
|
@property
|
|
48
|
-
def named_inventory(self) -> dict[
|
|
141
|
+
def named_inventory(self) -> dict[InventoryName, Inventory]:
|
|
49
142
|
return self.env.intersphinx_named_inventory # type: ignore[attr-defined]
|
|
50
143
|
|
|
51
144
|
def clear(self) -> None:
|
sphinx/ext/mathjax.py
CHANGED
|
@@ -22,7 +22,7 @@ from sphinx.util.math import get_node_equation_number
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from sphinx.application import Sphinx
|
|
24
24
|
from sphinx.util.typing import ExtensionMetadata
|
|
25
|
-
from sphinx.writers.
|
|
25
|
+
from sphinx.writers.html5 import HTML5Translator
|
|
26
26
|
|
|
27
27
|
# more information for mathjax secure url is here:
|
|
28
28
|
# https://docs.mathjax.org/en/latest/web/start.html#using-mathjax-from-a-content-delivery-network-cdn
|