Sphinx 7.4.7__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 +88 -58
- 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/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/math.py +1 -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 +17 -63
- 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 +7 -6
- 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/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 +25 -20
- sphinx/util/i18n.py +9 -4
- sphinx/util/images.py +3 -2
- sphinx/util/inspect.py +28 -43
- 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 +29 -28
- sphinx/util/parallel.py +2 -2
- sphinx/util/requests.py +1 -1
- sphinx/util/template.py +3 -3
- sphinx/util/typing.py +36 -72
- 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.7.dist-info → sphinx-8.0.0rc1.dist-info}/METADATA +10 -9
- {sphinx-7.4.7.dist-info → sphinx-8.0.0rc1.dist-info}/RECORD +104 -106
- sphinx/templates/quickstart/Makefile.jinja +0 -98
- sphinx/templates/quickstart/make.bat.jinja +0 -110
- sphinx/util/_pathlib.py +0 -120
- {sphinx-7.4.7.dist-info → sphinx-8.0.0rc1.dist-info}/LICENSE.rst +0 -0
- {sphinx-7.4.7.dist-info → sphinx-8.0.0rc1.dist-info}/WHEEL +0 -0
- {sphinx-7.4.7.dist-info → sphinx-8.0.0rc1.dist-info}/entry_points.txt +0 -0
sphinx/theming.py
CHANGED
|
@@ -10,6 +10,7 @@ import os
|
|
|
10
10
|
import shutil
|
|
11
11
|
import sys
|
|
12
12
|
import tempfile
|
|
13
|
+
from importlib.metadata import entry_points
|
|
13
14
|
from os import path
|
|
14
15
|
from typing import TYPE_CHECKING, Any
|
|
15
16
|
from zipfile import ZipFile
|
|
@@ -26,10 +27,6 @@ if sys.version_info >= (3, 11):
|
|
|
26
27
|
else:
|
|
27
28
|
import tomli as tomllib
|
|
28
29
|
|
|
29
|
-
if sys.version_info >= (3, 10):
|
|
30
|
-
from importlib.metadata import entry_points
|
|
31
|
-
else:
|
|
32
|
-
from importlib_metadata import entry_points
|
|
33
30
|
|
|
34
31
|
if TYPE_CHECKING:
|
|
35
32
|
from collections.abc import Callable
|
|
@@ -121,17 +118,11 @@ class Theme:
|
|
|
121
118
|
elif section == 'options':
|
|
122
119
|
value = self._options.get(name, default)
|
|
123
120
|
else:
|
|
124
|
-
# https://github.com/sphinx-doc/sphinx/issues/12305
|
|
125
|
-
# For backwards compatibility when attempting to read a value
|
|
126
|
-
# from an unsupported configuration section.
|
|
127
|
-
# xref: RemovedInSphinx80Warning
|
|
128
121
|
msg = __(
|
|
129
122
|
'Theme configuration sections other than [theme] and [options] '
|
|
130
|
-
'are not supported
|
|
131
|
-
'(tried to get a value from %r)'
|
|
123
|
+
'are not supported (tried to get a value from %r).'
|
|
132
124
|
)
|
|
133
|
-
|
|
134
|
-
value = default
|
|
125
|
+
raise ThemeError(msg)
|
|
135
126
|
if value is _NO_DEFAULT:
|
|
136
127
|
msg = __('setting %s.%s occurs in none of the searched theme configs') % (
|
|
137
128
|
section,
|
sphinx/transforms/__init__.py
CHANGED
|
@@ -22,10 +22,10 @@ from sphinx.util.nodes import apply_source_workaround, is_smartquotable
|
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
24
|
from collections.abc import Iterator
|
|
25
|
-
from typing import Literal
|
|
25
|
+
from typing import Literal, TypeAlias
|
|
26
26
|
|
|
27
27
|
from docutils.nodes import Node, Text
|
|
28
|
-
from typing_extensions import
|
|
28
|
+
from typing_extensions import TypeIs
|
|
29
29
|
|
|
30
30
|
from sphinx.application import Sphinx
|
|
31
31
|
from sphinx.config import Config
|
|
@@ -247,7 +247,7 @@ class ApplySourceWorkaround(SphinxTransform):
|
|
|
247
247
|
|
|
248
248
|
def apply(self, **kwargs: Any) -> None:
|
|
249
249
|
for node in self.document.findall(): # type: Node
|
|
250
|
-
if isinstance(node,
|
|
250
|
+
if isinstance(node, nodes.TextElement | nodes.image | nodes.topic):
|
|
251
251
|
apply_source_workaround(node)
|
|
252
252
|
|
|
253
253
|
|
|
@@ -364,7 +364,7 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform):
|
|
|
364
364
|
# override default settings with :confval:`smartquotes_action`
|
|
365
365
|
self.smartquotes_action = self.config.smartquotes_action
|
|
366
366
|
|
|
367
|
-
super().apply()
|
|
367
|
+
super().apply() # type: ignore[no-untyped-call]
|
|
368
368
|
|
|
369
369
|
def is_available(self) -> bool:
|
|
370
370
|
builders = self.config.smartquotes_excludes.get('builders', [])
|
|
@@ -477,7 +477,7 @@ def _reorder_index_target_nodes(start_node: nodes.target) -> None:
|
|
|
477
477
|
# as we want *consecutive* target & index nodes.
|
|
478
478
|
node: nodes.Node
|
|
479
479
|
for node in start_node.findall(descend=False, siblings=True):
|
|
480
|
-
if isinstance(node,
|
|
480
|
+
if isinstance(node, nodes.target | addnodes.index):
|
|
481
481
|
nodes_to_reorder.append(node)
|
|
482
482
|
continue
|
|
483
483
|
break # must be a consecutive run of target or index nodes
|
sphinx/transforms/references.py
CHANGED
|
@@ -23,7 +23,7 @@ class SphinxDanglingReferences(DanglingReferences):
|
|
|
23
23
|
|
|
24
24
|
# suppress INFO level messages for a while
|
|
25
25
|
reporter.report_level = max(reporter.WARNING_LEVEL, reporter.report_level)
|
|
26
|
-
super().apply()
|
|
26
|
+
super().apply() # type: ignore[no-untyped-call]
|
|
27
27
|
finally:
|
|
28
28
|
reporter.report_level = report_level
|
|
29
29
|
|
sphinx/util/__init__.py
CHANGED
|
@@ -13,12 +13,8 @@ from urllib.parse import parse_qsl, quote_plus, urlencode, urlsplit, urlunsplit
|
|
|
13
13
|
|
|
14
14
|
from sphinx.errors import ExtensionError, FiletypeNotFoundError
|
|
15
15
|
from sphinx.locale import __
|
|
16
|
-
from sphinx.util import display as _display
|
|
17
|
-
from sphinx.util import exceptions as _exceptions
|
|
18
|
-
from sphinx.util import http_date as _http_date
|
|
19
16
|
from sphinx.util import index_entries as _index_entries
|
|
20
17
|
from sphinx.util import logging
|
|
21
|
-
from sphinx.util import osutil as _osutil
|
|
22
18
|
from sphinx.util.console import strip_colors # NoQA: F401
|
|
23
19
|
from sphinx.util.matching import patfilter # NoQA: F401
|
|
24
20
|
from sphinx.util.nodes import ( # NoQA: F401
|
|
@@ -33,10 +29,8 @@ from sphinx.util.nodes import ( # NoQA: F401
|
|
|
33
29
|
from sphinx.util.osutil import ( # NoQA: F401
|
|
34
30
|
SEP,
|
|
35
31
|
copyfile,
|
|
36
|
-
copytimes,
|
|
37
32
|
ensuredir,
|
|
38
33
|
make_filename,
|
|
39
|
-
mtimes_of_files,
|
|
40
34
|
os_path,
|
|
41
35
|
relative_uri,
|
|
42
36
|
)
|
|
@@ -54,9 +48,9 @@ def docname_join(basedocname: str, docname: str) -> str:
|
|
|
54
48
|
return posixpath.normpath(posixpath.join('/' + basedocname, '..', docname))[1:]
|
|
55
49
|
|
|
56
50
|
|
|
57
|
-
def get_filetype(source_suffix: dict[str, str], filename: str) -> str:
|
|
51
|
+
def get_filetype(source_suffix: dict[str, str], filename: str | os.PathLike) -> str:
|
|
58
52
|
for suffix, filetype in source_suffix.items():
|
|
59
|
-
if filename.endswith(suffix):
|
|
53
|
+
if os.fspath(filename).endswith(suffix):
|
|
60
54
|
# If default filetype (None), considered as restructuredtext.
|
|
61
55
|
return filetype or 'restructuredtext'
|
|
62
56
|
raise FiletypeNotFoundError
|
|
@@ -258,32 +252,16 @@ def isurl(url: str) -> bool:
|
|
|
258
252
|
return bool(url) and '://' in url
|
|
259
253
|
|
|
260
254
|
|
|
261
|
-
def _xml_name_checker() -> re.Pattern[str]:
|
|
262
|
-
# to prevent import cycles
|
|
263
|
-
from sphinx.builders.epub3 import _XML_NAME_PATTERN
|
|
264
|
-
|
|
265
|
-
return _XML_NAME_PATTERN
|
|
266
|
-
|
|
267
|
-
|
|
268
255
|
# deprecated name -> (object to return, canonical path or empty string)
|
|
269
|
-
_DEPRECATED_OBJECTS: dict[str, tuple[Any, str
|
|
270
|
-
'path_stabilize': (_osutil.path_stabilize, 'sphinx.util.osutil.path_stabilize'),
|
|
271
|
-
'display_chunk': (_display.display_chunk, 'sphinx.util.display.display_chunk'),
|
|
272
|
-
'status_iterator': (_display.status_iterator, 'sphinx.util.display.status_iterator'),
|
|
273
|
-
'SkipProgressMessage': (_display.SkipProgressMessage,
|
|
274
|
-
'sphinx.util.display.SkipProgressMessage'),
|
|
275
|
-
'progress_message': (_display.progress_message, 'sphinx.util.display.progress_message'),
|
|
276
|
-
'epoch_to_rfc1123': (_http_date.epoch_to_rfc1123, 'sphinx.http_date.epoch_to_rfc1123'),
|
|
277
|
-
'rfc1123_to_epoch': (_http_date.rfc1123_to_epoch, 'sphinx.http_date.rfc1123_to_epoch'),
|
|
278
|
-
'save_traceback': (_exceptions.save_traceback, 'sphinx.exceptions.save_traceback'),
|
|
279
|
-
'format_exception_cut_frames': (_exceptions.format_exception_cut_frames,
|
|
280
|
-
'sphinx.exceptions.format_exception_cut_frames'),
|
|
281
|
-
'xmlname_checker': (_xml_name_checker, 'sphinx.builders.epub3._XML_NAME_PATTERN'),
|
|
256
|
+
_DEPRECATED_OBJECTS: dict[str, tuple[Any, str, tuple[int, int]]] = {
|
|
282
257
|
'split_index_msg': (_index_entries.split_index_msg,
|
|
283
|
-
'sphinx.util.index_entries.split_index_msg'
|
|
284
|
-
|
|
285
|
-
'
|
|
286
|
-
|
|
258
|
+
'sphinx.util.index_entries.split_index_msg',
|
|
259
|
+
(9, 0)),
|
|
260
|
+
'split_into': (_index_entries.split_index_msg,
|
|
261
|
+
'sphinx.util.index_entries.split_into',
|
|
262
|
+
(9, 0)),
|
|
263
|
+
'md5': (_md5, '', (9, 0)),
|
|
264
|
+
'sha1': (_sha1, '', (9, 0)),
|
|
287
265
|
}
|
|
288
266
|
|
|
289
267
|
|
|
@@ -294,8 +272,6 @@ def __getattr__(name: str) -> Any:
|
|
|
294
272
|
|
|
295
273
|
from sphinx.deprecation import _deprecation_warning
|
|
296
274
|
|
|
297
|
-
|
|
298
|
-
deprecated_object, canonical_name = info[:2]
|
|
299
|
-
remove = info[2] if len(info) == 3 else (8, 0)
|
|
275
|
+
deprecated_object, canonical_name, remove = _DEPRECATED_OBJECTS[name]
|
|
300
276
|
_deprecation_warning(__name__, name, canonical_name, remove=remove)
|
|
301
277
|
return deprecated_object
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _format_rfc3339_microseconds(timestamp: int, /) -> str:
|
|
7
|
+
"""Return an RFC 3339 formatted string representing the given timestamp.
|
|
8
|
+
|
|
9
|
+
:param timestamp: The timestamp to format, in microseconds.
|
|
10
|
+
"""
|
|
11
|
+
seconds, fraction = divmod(timestamp, 10**6)
|
|
12
|
+
return time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(seconds)) + f'.{fraction // 1_000}'
|
sphinx/util/cfamily.py
CHANGED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
from copy import deepcopy
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
from docutils import nodes
|
|
10
10
|
|
|
@@ -12,16 +12,16 @@ from sphinx import addnodes
|
|
|
12
12
|
from sphinx.util import logging
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
from collections.abc import Sequence
|
|
15
|
+
from collections.abc import Callable, Sequence
|
|
16
|
+
from typing import Any, TypeAlias
|
|
16
17
|
|
|
17
18
|
from docutils.nodes import TextElement
|
|
18
19
|
|
|
19
20
|
from sphinx.config import Config
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
StringifyTransform = Callable[[Any], str]
|
|
22
|
+
StringifyTransform: TypeAlias = Callable[[Any], str]
|
|
24
23
|
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
_whitespace_re = re.compile(r'\s+')
|
|
27
27
|
anon_identifier_re = re.compile(r'(@[a-zA-Z0-9_])[a-zA-Z0-9_]*\b')
|
sphinx/util/console.py
CHANGED
|
@@ -41,8 +41,9 @@ if TYPE_CHECKING:
|
|
|
41
41
|
try:
|
|
42
42
|
# check if colorama is installed to support color on Windows
|
|
43
43
|
import colorama
|
|
44
|
+
COLORAMA_AVAILABLE = True
|
|
44
45
|
except ImportError:
|
|
45
|
-
|
|
46
|
+
COLORAMA_AVAILABLE = False
|
|
46
47
|
|
|
47
48
|
_CSI: Final[str] = re.escape('\x1b[') # 'ESC [': Control Sequence Introducer
|
|
48
49
|
|
|
@@ -92,7 +93,7 @@ def term_width_line(text: str) -> str:
|
|
|
92
93
|
def color_terminal() -> bool:
|
|
93
94
|
if 'NO_COLOR' in os.environ:
|
|
94
95
|
return False
|
|
95
|
-
if sys.platform == 'win32' and
|
|
96
|
+
if sys.platform == 'win32' and COLORAMA_AVAILABLE:
|
|
96
97
|
colorama.just_fix_windows_console()
|
|
97
98
|
return True
|
|
98
99
|
if 'FORCE_COLOR' in os.environ:
|
|
@@ -108,7 +109,7 @@ def color_terminal() -> bool:
|
|
|
108
109
|
|
|
109
110
|
|
|
110
111
|
def nocolor() -> None:
|
|
111
|
-
if sys.platform == 'win32' and
|
|
112
|
+
if sys.platform == 'win32' and COLORAMA_AVAILABLE:
|
|
112
113
|
colorama.deinit()
|
|
113
114
|
codes.clear()
|
|
114
115
|
|
sphinx/util/display.py
CHANGED
|
@@ -7,9 +7,9 @@ from sphinx.util import logging
|
|
|
7
7
|
from sphinx.util.console import bold, color_terminal
|
|
8
8
|
|
|
9
9
|
if False:
|
|
10
|
-
from collections.abc import Iterable, Iterator
|
|
10
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
11
11
|
from types import TracebackType
|
|
12
|
-
from typing import Any,
|
|
12
|
+
from typing import Any, TypeVar
|
|
13
13
|
|
|
14
14
|
from typing_extensions import ParamSpec
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def display_chunk(chunk: Any) -> str:
|
|
24
|
-
if isinstance(chunk,
|
|
24
|
+
if isinstance(chunk, list | tuple):
|
|
25
25
|
if len(chunk) == 1:
|
|
26
26
|
return str(chunk[0])
|
|
27
27
|
return f'{chunk[0]} .. {chunk[-1]}'
|
sphinx/util/docfields.py
CHANGED
|
@@ -356,7 +356,7 @@ class DocFieldTransformer:
|
|
|
356
356
|
if is_typefield:
|
|
357
357
|
# filter out only inline nodes; others will result in invalid
|
|
358
358
|
# markup being written out
|
|
359
|
-
content = [n for n in content if isinstance(n,
|
|
359
|
+
content = [n for n in content if isinstance(n, nodes.Inline | nodes.Text)]
|
|
360
360
|
if content:
|
|
361
361
|
types.setdefault(typename, {})[fieldarg] = content
|
|
362
362
|
continue
|
sphinx/util/docutils.py
CHANGED
|
@@ -8,7 +8,7 @@ from collections.abc import Sequence # NoQA: TCH003
|
|
|
8
8
|
from contextlib import contextmanager
|
|
9
9
|
from copy import copy
|
|
10
10
|
from os import path
|
|
11
|
-
from typing import IO, TYPE_CHECKING, Any,
|
|
11
|
+
from typing import IO, TYPE_CHECKING, Any, cast
|
|
12
12
|
|
|
13
13
|
import docutils
|
|
14
14
|
from docutils import nodes
|
|
@@ -27,7 +27,7 @@ logger = logging.getLogger(__name__)
|
|
|
27
27
|
report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(\\d+)?\\) ')
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
30
|
-
from collections.abc import Iterator
|
|
30
|
+
from collections.abc import Callable, Iterator # NoQA: TCH003
|
|
31
31
|
from types import ModuleType
|
|
32
32
|
|
|
33
33
|
from docutils.frontend import Values
|
|
@@ -366,30 +366,47 @@ class SphinxDirective(Directive):
|
|
|
366
366
|
|
|
367
367
|
This class provides helper methods for Sphinx directives.
|
|
368
368
|
|
|
369
|
+
.. versionadded:: 1.8
|
|
370
|
+
|
|
369
371
|
.. note:: The subclasses of this class might not work with docutils.
|
|
370
372
|
This class is strongly coupled with Sphinx.
|
|
371
373
|
"""
|
|
372
374
|
|
|
373
375
|
@property
|
|
374
376
|
def env(self) -> BuildEnvironment:
|
|
375
|
-
"""Reference to the :class:`.BuildEnvironment` object.
|
|
377
|
+
"""Reference to the :class:`.BuildEnvironment` object.
|
|
378
|
+
|
|
379
|
+
.. versionadded:: 1.8
|
|
380
|
+
"""
|
|
376
381
|
return self.state.document.settings.env
|
|
377
382
|
|
|
378
383
|
@property
|
|
379
384
|
def config(self) -> Config:
|
|
380
|
-
"""Reference to the :class:`.Config` object.
|
|
385
|
+
"""Reference to the :class:`.Config` object.
|
|
386
|
+
|
|
387
|
+
.. versionadded:: 1.8
|
|
388
|
+
"""
|
|
381
389
|
return self.env.config
|
|
382
390
|
|
|
383
391
|
def get_source_info(self) -> tuple[str, int]:
|
|
384
|
-
"""Get source and line number.
|
|
392
|
+
"""Get source and line number.
|
|
393
|
+
|
|
394
|
+
.. versionadded:: 3.0
|
|
395
|
+
"""
|
|
385
396
|
return self.state_machine.get_source_and_line(self.lineno)
|
|
386
397
|
|
|
387
398
|
def set_source_info(self, node: Node) -> None:
|
|
388
|
-
"""Set source and line number to the node.
|
|
399
|
+
"""Set source and line number to the node.
|
|
400
|
+
|
|
401
|
+
.. versionadded:: 2.1
|
|
402
|
+
"""
|
|
389
403
|
node.source, node.line = self.get_source_info()
|
|
390
404
|
|
|
391
405
|
def get_location(self) -> str:
|
|
392
|
-
"""Get current location info for logging.
|
|
406
|
+
"""Get current location info for logging.
|
|
407
|
+
|
|
408
|
+
.. versionadded:: 4.2
|
|
409
|
+
"""
|
|
393
410
|
source, line = self.get_source_info()
|
|
394
411
|
if source and line:
|
|
395
412
|
return f'{source}:{line}'
|
|
@@ -473,6 +490,8 @@ class SphinxRole:
|
|
|
473
490
|
|
|
474
491
|
This class provides helper methods for Sphinx roles.
|
|
475
492
|
|
|
493
|
+
.. versionadded:: 2.0
|
|
494
|
+
|
|
476
495
|
.. note:: The subclasses of this class might not work with docutils.
|
|
477
496
|
This class is strongly coupled with Sphinx.
|
|
478
497
|
"""
|
|
@@ -517,24 +536,35 @@ class SphinxRole:
|
|
|
517
536
|
|
|
518
537
|
@property
|
|
519
538
|
def env(self) -> BuildEnvironment:
|
|
520
|
-
"""Reference to the :class:`.BuildEnvironment` object.
|
|
539
|
+
"""Reference to the :class:`.BuildEnvironment` object.
|
|
540
|
+
|
|
541
|
+
.. versionadded:: 2.0
|
|
542
|
+
"""
|
|
521
543
|
return self.inliner.document.settings.env
|
|
522
544
|
|
|
523
545
|
@property
|
|
524
546
|
def config(self) -> Config:
|
|
525
|
-
"""Reference to the :class:`.Config` object.
|
|
547
|
+
"""Reference to the :class:`.Config` object.
|
|
548
|
+
|
|
549
|
+
.. versionadded:: 2.0
|
|
550
|
+
"""
|
|
526
551
|
return self.env.config
|
|
527
552
|
|
|
528
553
|
def get_source_info(self, lineno: int | None = None) -> tuple[str, int]:
|
|
554
|
+
# .. versionadded:: 3.0
|
|
529
555
|
if lineno is None:
|
|
530
556
|
lineno = self.lineno
|
|
531
557
|
return self.inliner.reporter.get_source_and_line(lineno) # type: ignore[attr-defined]
|
|
532
558
|
|
|
533
559
|
def set_source_info(self, node: Node, lineno: int | None = None) -> None:
|
|
560
|
+
# .. versionadded:: 2.0
|
|
534
561
|
node.source, node.line = self.get_source_info(lineno)
|
|
535
562
|
|
|
536
563
|
def get_location(self) -> str:
|
|
537
|
-
"""Get current location info for logging.
|
|
564
|
+
"""Get current location info for logging.
|
|
565
|
+
|
|
566
|
+
.. versionadded:: 4.2
|
|
567
|
+
"""
|
|
538
568
|
source, line = self.get_source_info()
|
|
539
569
|
if source and line:
|
|
540
570
|
return f'{source}:{line}'
|
|
@@ -551,6 +581,8 @@ class ReferenceRole(SphinxRole):
|
|
|
551
581
|
The reference roles can accept ``link title <target>`` style as a text for
|
|
552
582
|
the role. The parsed result; link title and target will be stored to
|
|
553
583
|
``self.title`` and ``self.target``.
|
|
584
|
+
|
|
585
|
+
.. versionadded:: 2.0
|
|
554
586
|
"""
|
|
555
587
|
|
|
556
588
|
has_explicit_title: bool #: A boolean indicates the role has explicit title or not.
|
|
@@ -591,6 +623,8 @@ class SphinxTranslator(nodes.NodeVisitor):
|
|
|
591
623
|
|
|
592
624
|
It also provides helper methods for Sphinx translators.
|
|
593
625
|
|
|
626
|
+
.. versionadded:: 2.0
|
|
627
|
+
|
|
594
628
|
.. note:: The subclasses of this class might not work with docutils.
|
|
595
629
|
This class is strongly coupled with Sphinx.
|
|
596
630
|
"""
|
sphinx/util/fileutil.py
CHANGED
|
@@ -4,14 +4,17 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import posixpath
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
from docutils.utils import relative_path
|
|
10
10
|
|
|
11
|
+
from sphinx.locale import __
|
|
11
12
|
from sphinx.util import logging
|
|
12
13
|
from sphinx.util.osutil import copyfile, ensuredir
|
|
13
14
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
|
|
15
18
|
from sphinx.util.template import BaseRenderer
|
|
16
19
|
from sphinx.util.typing import PathMatcher
|
|
17
20
|
|
|
@@ -34,7 +37,8 @@ def _template_basename(filename: str | os.PathLike[str]) -> str | None:
|
|
|
34
37
|
def copy_asset_file(source: str | os.PathLike[str], destination: str | os.PathLike[str],
|
|
35
38
|
context: dict[str, Any] | None = None,
|
|
36
39
|
renderer: BaseRenderer | None = None,
|
|
37
|
-
*,
|
|
40
|
+
*,
|
|
41
|
+
force: bool = False) -> None:
|
|
38
42
|
"""Copy an asset file to destination.
|
|
39
43
|
|
|
40
44
|
On copying, it expands the template variables if context argument is given and
|
|
@@ -44,6 +48,7 @@ def copy_asset_file(source: str | os.PathLike[str], destination: str | os.PathLi
|
|
|
44
48
|
:param destination: The path to destination file or directory
|
|
45
49
|
:param context: The template variables. If not given, template files are simply copied
|
|
46
50
|
:param renderer: The template engine. If not given, SphinxRenderer is used by default
|
|
51
|
+
:param bool force: Overwrite the destination file even if it exists.
|
|
47
52
|
"""
|
|
48
53
|
if not os.path.exists(source):
|
|
49
54
|
return
|
|
@@ -64,45 +69,42 @@ def copy_asset_file(source: str | os.PathLike[str], destination: str | os.PathLi
|
|
|
64
69
|
rendered_template = renderer.render_string(template_content, context)
|
|
65
70
|
|
|
66
71
|
if (
|
|
67
|
-
|
|
72
|
+
not force
|
|
68
73
|
and os.path.exists(destination)
|
|
69
74
|
and template_content != rendered_template
|
|
70
75
|
):
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
'as a file already exists at the destination path '
|
|
77
|
-
'and the content does not match.')
|
|
78
|
-
# See https://github.com/sphinx-doc/sphinx/pull/12627#issuecomment-2241144330
|
|
79
|
-
# for the rationale for logger.info().
|
|
80
|
-
logger.info(msg, os.fsdecode(source), os.fsdecode(destination),
|
|
81
|
-
type='misc', subtype='copy_overwrite')
|
|
76
|
+
msg = __('Aborted attempted copy from rendered template %s to %s '
|
|
77
|
+
'(the destination path has existing data).')
|
|
78
|
+
logger.warning(msg, os.fsdecode(source), os.fsdecode(destination),
|
|
79
|
+
type='misc', subtype='copy_overwrite')
|
|
80
|
+
return
|
|
82
81
|
|
|
83
82
|
destination = _template_basename(destination) or destination
|
|
84
83
|
with open(destination, 'w', encoding='utf-8') as fdst:
|
|
85
84
|
fdst.write(rendered_template)
|
|
86
85
|
else:
|
|
87
|
-
copyfile(source, destination,
|
|
86
|
+
copyfile(source, destination, force=force)
|
|
88
87
|
|
|
89
88
|
|
|
90
89
|
def copy_asset(source: str | os.PathLike[str], destination: str | os.PathLike[str],
|
|
91
90
|
excluded: PathMatcher = lambda path: False,
|
|
92
91
|
context: dict[str, Any] | None = None, renderer: BaseRenderer | None = None,
|
|
93
92
|
onerror: Callable[[str, Exception], None] | None = None,
|
|
94
|
-
*,
|
|
93
|
+
*, force: bool = False) -> None:
|
|
95
94
|
"""Copy asset files to destination recursively.
|
|
96
95
|
|
|
97
96
|
On copying, it expands the template variables if context argument is given and
|
|
98
97
|
the asset is a template file.
|
|
99
98
|
|
|
99
|
+
Use ``copy_asset_file`` instead to copy a single file.
|
|
100
|
+
|
|
100
101
|
:param source: The path to source file or directory
|
|
101
102
|
:param destination: The path to destination directory
|
|
102
103
|
:param excluded: The matcher to determine the given path should be copied or not
|
|
103
104
|
:param context: The template variables. If not given, template files are simply copied
|
|
104
105
|
:param renderer: The template engine. If not given, SphinxRenderer is used by default
|
|
105
106
|
:param onerror: The error handler.
|
|
107
|
+
:param bool force: Overwrite the destination file even if it exists.
|
|
106
108
|
"""
|
|
107
109
|
if not os.path.exists(source):
|
|
108
110
|
return
|
|
@@ -113,8 +115,10 @@ def copy_asset(source: str | os.PathLike[str], destination: str | os.PathLike[st
|
|
|
113
115
|
|
|
114
116
|
ensuredir(destination)
|
|
115
117
|
if os.path.isfile(source):
|
|
116
|
-
copy_asset_file(source, destination,
|
|
117
|
-
|
|
118
|
+
copy_asset_file(source, destination,
|
|
119
|
+
context=context,
|
|
120
|
+
renderer=renderer,
|
|
121
|
+
force=force)
|
|
118
122
|
return
|
|
119
123
|
|
|
120
124
|
for root, dirs, files in os.walk(source, followlinks=True):
|
|
@@ -130,8 +134,9 @@ def copy_asset(source: str | os.PathLike[str], destination: str | os.PathLike[st
|
|
|
130
134
|
try:
|
|
131
135
|
copy_asset_file(posixpath.join(root, filename),
|
|
132
136
|
posixpath.join(destination, reldir),
|
|
133
|
-
context,
|
|
134
|
-
|
|
137
|
+
context=context,
|
|
138
|
+
renderer=renderer,
|
|
139
|
+
force=force)
|
|
135
140
|
except Exception as exc:
|
|
136
141
|
if onerror:
|
|
137
142
|
onerror(posixpath.join(root, filename), exc)
|
sphinx/util/i18n.py
CHANGED
|
@@ -15,12 +15,17 @@ from babel.messages.pofile import read_po
|
|
|
15
15
|
from sphinx.errors import SphinxError
|
|
16
16
|
from sphinx.locale import __
|
|
17
17
|
from sphinx.util import logging
|
|
18
|
-
from sphinx.util.osutil import
|
|
18
|
+
from sphinx.util.osutil import (
|
|
19
|
+
SEP,
|
|
20
|
+
_last_modified_time,
|
|
21
|
+
canon_path,
|
|
22
|
+
relpath,
|
|
23
|
+
)
|
|
19
24
|
|
|
20
25
|
if TYPE_CHECKING:
|
|
21
26
|
import datetime as dt
|
|
22
27
|
from collections.abc import Iterator
|
|
23
|
-
from typing import Protocol,
|
|
28
|
+
from typing import Protocol, TypeAlias
|
|
24
29
|
|
|
25
30
|
from babel.core import Locale
|
|
26
31
|
|
|
@@ -52,7 +57,7 @@ if TYPE_CHECKING:
|
|
|
52
57
|
locale: str | Locale | None = ...,
|
|
53
58
|
) -> str: ...
|
|
54
59
|
|
|
55
|
-
Formatter =
|
|
60
|
+
Formatter: TypeAlias = DateFormatter | TimeFormatter | DatetimeFormatter
|
|
56
61
|
|
|
57
62
|
logger = logging.getLogger(__name__)
|
|
58
63
|
|
|
@@ -84,7 +89,7 @@ class CatalogInfo(LocaleFileInfoBase):
|
|
|
84
89
|
def is_outdated(self) -> bool:
|
|
85
90
|
return (
|
|
86
91
|
not path.exists(self.mo_path) or
|
|
87
|
-
|
|
92
|
+
_last_modified_time(self.mo_path) < _last_modified_time(self.po_path))
|
|
88
93
|
|
|
89
94
|
def write_mo(self, locale: str, use_fuzzy: bool = False) -> None:
|
|
90
95
|
with open(self.po_path, encoding=self.charset) as file_po:
|
sphinx/util/images.py
CHANGED
|
@@ -13,8 +13,9 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
try:
|
|
15
15
|
from PIL import Image
|
|
16
|
+
PILLOW_AVAILABLE = True
|
|
16
17
|
except ImportError:
|
|
17
|
-
|
|
18
|
+
PILLOW_AVAILABLE = False
|
|
18
19
|
|
|
19
20
|
mime_suffixes = {
|
|
20
21
|
'.gif': 'image/gif',
|
|
@@ -43,7 +44,7 @@ def get_image_size(filename: str) -> tuple[int, int] | None:
|
|
|
43
44
|
elif isinstance(size[0], float) or isinstance(size[1], float):
|
|
44
45
|
size = (int(size[0]), int(size[1]))
|
|
45
46
|
|
|
46
|
-
if size is None and
|
|
47
|
+
if size is None and PILLOW_AVAILABLE: # fallback to Pillow
|
|
47
48
|
with Image.open(filename) as im:
|
|
48
49
|
size = im.size
|
|
49
50
|
|