Sphinx 7.1.2__py3-none-any.whl → 7.2.0__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 +6 -6
- sphinx/__main__.py +3 -1
- sphinx/addnodes.py +35 -22
- sphinx/application.py +40 -38
- sphinx/builders/__init__.py +16 -12
- sphinx/builders/_epub_base.py +15 -11
- sphinx/builders/changes.py +6 -4
- sphinx/builders/dirhtml.py +4 -2
- sphinx/builders/dummy.py +6 -4
- sphinx/builders/epub3.py +16 -8
- sphinx/builders/gettext.py +40 -43
- sphinx/builders/html/__init__.py +166 -196
- sphinx/builders/html/_assets.py +116 -0
- sphinx/builders/html/transforms.py +4 -2
- sphinx/builders/latex/__init__.py +12 -7
- sphinx/builders/latex/theming.py +5 -2
- sphinx/builders/latex/transforms.py +6 -3
- sphinx/builders/linkcheck.py +18 -11
- sphinx/builders/manpage.py +6 -4
- sphinx/builders/singlehtml.py +16 -9
- sphinx/builders/texinfo.py +11 -6
- sphinx/builders/text.py +8 -3
- sphinx/builders/xml.py +9 -4
- sphinx/cmd/build.py +27 -14
- sphinx/cmd/make_mode.py +13 -4
- sphinx/cmd/quickstart.py +13 -4
- sphinx/config.py +17 -14
- sphinx/deprecation.py +4 -2
- sphinx/directives/__init__.py +44 -12
- sphinx/directives/code.py +5 -4
- sphinx/directives/other.py +92 -44
- sphinx/directives/patches.py +1 -1
- sphinx/domains/__init__.py +11 -8
- sphinx/domains/c.py +67 -57
- sphinx/domains/changeset.py +3 -2
- sphinx/domains/citation.py +2 -1
- sphinx/domains/cpp.py +136 -93
- sphinx/domains/index.py +9 -5
- sphinx/domains/javascript.py +32 -19
- sphinx/domains/math.py +5 -3
- sphinx/domains/python.py +69 -57
- sphinx/domains/rst.py +20 -11
- sphinx/domains/std.py +21 -15
- sphinx/environment/__init__.py +97 -65
- sphinx/environment/adapters/indexentries.py +13 -10
- sphinx/environment/adapters/toctree.py +485 -308
- sphinx/environment/collectors/__init__.py +3 -4
- sphinx/environment/collectors/asset.py +10 -4
- sphinx/environment/collectors/dependencies.py +7 -4
- sphinx/environment/collectors/metadata.py +7 -5
- sphinx/environment/collectors/title.py +5 -3
- sphinx/environment/collectors/toctree.py +13 -8
- sphinx/errors.py +1 -1
- sphinx/events.py +5 -5
- sphinx/ext/apidoc.py +49 -27
- sphinx/ext/autodoc/__init__.py +179 -161
- sphinx/ext/autodoc/directive.py +10 -6
- sphinx/ext/autodoc/importer.py +22 -13
- sphinx/ext/autodoc/mock.py +4 -1
- sphinx/ext/autodoc/preserve_defaults.py +80 -12
- sphinx/ext/autodoc/type_comment.py +14 -10
- sphinx/ext/autodoc/typehints.py +7 -3
- sphinx/ext/autosectionlabel.py +6 -3
- sphinx/ext/autosummary/__init__.py +21 -15
- sphinx/ext/autosummary/generate.py +176 -126
- sphinx/ext/coverage.py +93 -8
- sphinx/ext/doctest.py +28 -17
- sphinx/ext/duration.py +19 -17
- sphinx/ext/extlinks.py +11 -6
- sphinx/ext/githubpages.py +8 -7
- sphinx/ext/graphviz.py +61 -17
- sphinx/ext/ifconfig.py +7 -4
- sphinx/ext/imgconverter.py +4 -2
- sphinx/ext/imgmath.py +29 -23
- sphinx/ext/inheritance_diagram.py +41 -27
- sphinx/ext/intersphinx.py +45 -38
- sphinx/ext/linkcode.py +8 -5
- sphinx/ext/mathjax.py +13 -9
- sphinx/ext/napoleon/__init__.py +3 -3
- sphinx/ext/napoleon/docstring.py +40 -31
- sphinx/ext/todo.py +10 -7
- sphinx/ext/viewcode.py +46 -25
- sphinx/extension.py +1 -1
- sphinx/highlighting.py +20 -12
- sphinx/io.py +5 -4
- sphinx/jinja2glue.py +24 -19
- sphinx/locale/__init__.py +8 -2
- sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ar/LC_MESSAGES/sphinx.po +756 -740
- sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bg/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bn/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ca/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cak/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cs/LC_MESSAGES/sphinx.po +758 -742
- sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cy/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/da/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/el/LC_MESSAGES/sphinx.po +763 -747
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eo/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es/LC_MESSAGES/sphinx.po +767 -751
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/et/LC_MESSAGES/sphinx.po +762 -746
- sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eu/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fa/LC_MESSAGES/sphinx.po +766 -750
- sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fi/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/gl/LC_MESSAGES/sphinx.js +60 -0
- sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/gl/LC_MESSAGES/sphinx.po +3695 -0
- sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/he/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi/LC_MESSAGES/sphinx.po +763 -747
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hr/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hu/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/id/LC_MESSAGES/sphinx.po +765 -749
- sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/is/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/it/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ja/LC_MESSAGES/sphinx.po +767 -751
- sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ka/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ko/LC_MESSAGES/sphinx.po +767 -751
- sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lt/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lv/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/mk/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ne/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nl/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pl/LC_MESSAGES/sphinx.po +762 -745
- sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ro/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ru/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/si/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sk/LC_MESSAGES/sphinx.po +765 -749
- sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sl/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/sphinx.pot +748 -740
- sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sq/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr@latin/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr_RS/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sv/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ta/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/te/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/tr/LC_MESSAGES/sphinx.po +763 -747
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +760 -749
- sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ur/LC_MESSAGES/sphinx.po +759 -748
- sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/vi/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/yue/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/zh_CN/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +767 -751
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +754 -738
- sphinx/parsers.py +5 -4
- sphinx/project.py +52 -34
- sphinx/pycode/__init__.py +2 -1
- sphinx/pycode/ast.py +7 -13
- sphinx/pycode/parser.py +42 -38
- sphinx/registry.py +35 -29
- sphinx/roles.py +9 -4
- sphinx/search/__init__.py +5 -17
- sphinx/search/da.py +1 -1
- sphinx/search/de.py +1 -1
- sphinx/search/en.py +1 -1
- sphinx/search/es.py +1 -1
- sphinx/search/fi.py +1 -1
- sphinx/search/fr.py +1 -1
- sphinx/search/hu.py +1 -1
- sphinx/search/it.py +1 -1
- sphinx/search/ja.py +1 -1
- sphinx/search/nl.py +1 -1
- sphinx/search/no.py +1 -1
- sphinx/search/pt.py +1 -1
- sphinx/search/ro.py +1 -1
- sphinx/search/ru.py +1 -1
- sphinx/search/sv.py +1 -1
- sphinx/search/tr.py +1 -1
- sphinx/search/zh.py +1 -1
- sphinx/testing/fixtures.py +23 -30
- sphinx/testing/path.py +9 -0
- sphinx/testing/restructuredtext.py +13 -5
- sphinx/testing/util.py +20 -63
- sphinx/texinputs/sphinxlatexobjects.sty +15 -15
- sphinx/themes/agogo/static/agogo.css_t +10 -4
- sphinx/themes/basic/layout.html +1 -1
- sphinx/themes/basic/static/basic.css_t +4 -0
- sphinx/themes/basic/static/documentation_options.js_t +1 -2
- sphinx/themes/basic/static/searchtools.js +17 -9
- sphinx/themes/basic/static/sphinx_highlight.js +13 -3
- sphinx/themes/bizstyle/static/bizstyle.css_t +4 -0
- sphinx/themes/classic/theme.conf +1 -1
- sphinx/themes/epub/static/epub.css_t +6 -1
- sphinx/themes/haiku/theme.conf +1 -1
- sphinx/themes/nature/static/nature.css_t +4 -0
- sphinx/themes/nonav/static/nonav.css_t +6 -1
- sphinx/themes/pyramid/static/pyramid.css_t +4 -0
- sphinx/themes/scrolls/static/scrolls.css_t +4 -0
- sphinx/themes/scrolls/theme.conf +1 -1
- sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +4 -0
- sphinx/theming.py +9 -7
- sphinx/transforms/__init__.py +79 -3
- sphinx/transforms/compact_bullet_list.py +6 -3
- sphinx/transforms/i18n.py +26 -10
- sphinx/transforms/post_transforms/__init__.py +21 -8
- sphinx/transforms/post_transforms/code.py +6 -3
- sphinx/transforms/post_transforms/images.py +13 -9
- sphinx/util/__init__.py +21 -92
- sphinx/util/cfamily.py +7 -4
- sphinx/util/display.py +3 -2
- sphinx/util/docfields.py +7 -6
- sphinx/util/docstrings.py +1 -1
- sphinx/util/docutils.py +41 -31
- sphinx/util/fileutil.py +9 -6
- sphinx/util/i18n.py +21 -18
- sphinx/util/images.py +2 -1
- sphinx/util/index_entries.py +27 -0
- sphinx/util/inspect.py +83 -67
- sphinx/util/inventory.py +4 -2
- sphinx/util/logging.py +9 -6
- sphinx/util/matching.py +5 -2
- sphinx/util/math.py +6 -3
- sphinx/util/nodes.py +70 -31
- sphinx/util/osutil.py +22 -40
- sphinx/util/parallel.py +4 -1
- sphinx/util/rst.py +7 -3
- sphinx/util/tags.py +11 -4
- sphinx/util/template.py +17 -14
- sphinx/util/typing.py +61 -20
- sphinx/versioning.py +6 -4
- sphinx/writers/html.py +1 -1
- sphinx/writers/html5.py +32 -24
- sphinx/writers/latex.py +67 -53
- sphinx/writers/manpage.py +9 -5
- sphinx/writers/texinfo.py +11 -9
- sphinx/writers/text.py +14 -9
- sphinx/writers/xml.py +3 -2
- {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/METADATA +7 -5
- sphinx-7.2.0.dist-info/RECORD +568 -0
- sphinx/testing/comparer.py +0 -97
- sphinx-7.1.2.dist-info/RECORD +0 -564
- {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/LICENSE +0 -0
- {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/WHEEL +0 -0
- {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/entry_points.txt +0 -0
sphinx/ext/doctest.py
CHANGED
|
@@ -11,10 +11,9 @@ import sys
|
|
|
11
11
|
import time
|
|
12
12
|
from io import StringIO
|
|
13
13
|
from os import path
|
|
14
|
-
from typing import TYPE_CHECKING, Any, Callable
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
15
15
|
|
|
16
16
|
from docutils import nodes
|
|
17
|
-
from docutils.nodes import Element, Node, TextElement
|
|
18
17
|
from docutils.parsers.rst import directives
|
|
19
18
|
from packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
20
19
|
from packaging.version import Version
|
|
@@ -23,13 +22,17 @@ import sphinx
|
|
|
23
22
|
from sphinx.builders import Builder
|
|
24
23
|
from sphinx.locale import __
|
|
25
24
|
from sphinx.util import logging
|
|
26
|
-
from sphinx.util.console import bold # type: ignore
|
|
25
|
+
from sphinx.util.console import bold # type: ignore[attr-defined]
|
|
27
26
|
from sphinx.util.docutils import SphinxDirective
|
|
28
27
|
from sphinx.util.osutil import relpath
|
|
29
|
-
from sphinx.util.typing import OptionSpec
|
|
30
28
|
|
|
31
29
|
if TYPE_CHECKING:
|
|
30
|
+
from collections.abc import Iterable, Sequence
|
|
31
|
+
|
|
32
|
+
from docutils.nodes import Element, Node, TextElement
|
|
33
|
+
|
|
32
34
|
from sphinx.application import Sphinx
|
|
35
|
+
from sphinx.util.typing import OptionSpec
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
logger = logging.getLogger(__name__)
|
|
@@ -236,7 +239,7 @@ class TestCode:
|
|
|
236
239
|
|
|
237
240
|
|
|
238
241
|
class SphinxDocTestRunner(doctest.DocTestRunner):
|
|
239
|
-
def summarize(self, out: Callable, verbose: bool | None = None, # type: ignore
|
|
242
|
+
def summarize(self, out: Callable, verbose: bool | None = None, # type: ignore[override]
|
|
240
243
|
) -> tuple[int, int]:
|
|
241
244
|
string_io = StringIO()
|
|
242
245
|
old_stdout = sys.stdout
|
|
@@ -251,7 +254,8 @@ class SphinxDocTestRunner(doctest.DocTestRunner):
|
|
|
251
254
|
def _DocTestRunner__patched_linecache_getlines(self, filename: str,
|
|
252
255
|
module_globals: Any = None) -> Any:
|
|
253
256
|
# this is overridden from DocTestRunner adding the try-except below
|
|
254
|
-
m = self._DocTestRunner__LINECACHE_FILENAME_RE.match(
|
|
257
|
+
m = self._DocTestRunner__LINECACHE_FILENAME_RE.match( # type: ignore[attr-defined]
|
|
258
|
+
filename)
|
|
255
259
|
if m and m.group('name') == self.test.name:
|
|
256
260
|
try:
|
|
257
261
|
example = self.test.examples[int(m.group('examplenum'))]
|
|
@@ -262,7 +266,8 @@ class SphinxDocTestRunner(doctest.DocTestRunner):
|
|
|
262
266
|
pass
|
|
263
267
|
else:
|
|
264
268
|
return example.source.splitlines(True)
|
|
265
|
-
return self.save_linecache_getlines(
|
|
269
|
+
return self.save_linecache_getlines( # type: ignore[attr-defined]
|
|
270
|
+
filename, module_globals)
|
|
266
271
|
|
|
267
272
|
|
|
268
273
|
# the new builder -- use sphinx-build.py -b doctest to run
|
|
@@ -284,7 +289,7 @@ class DocTestBuilder(Builder):
|
|
|
284
289
|
# for doctest examples but unusable for multi-statement code such
|
|
285
290
|
# as setup code -- to be able to use doctest error reporting with
|
|
286
291
|
# that code nevertheless, we monkey-patch the "compile" it uses.
|
|
287
|
-
doctest.compile = self.compile # type: ignore
|
|
292
|
+
doctest.compile = self.compile # type: ignore[attr-defined]
|
|
288
293
|
|
|
289
294
|
sys.path[0:0] = self.config.doctest_path
|
|
290
295
|
|
|
@@ -299,7 +304,8 @@ class DocTestBuilder(Builder):
|
|
|
299
304
|
|
|
300
305
|
date = time.strftime('%Y-%m-%d %H:%M:%S')
|
|
301
306
|
|
|
302
|
-
|
|
307
|
+
outpath = self.outdir.joinpath('output.txt')
|
|
308
|
+
self.outfile = outpath.open('w', encoding='utf-8') # NoQA: SIM115
|
|
303
309
|
self.outfile.write(('Results of doctest builder run on %s\n'
|
|
304
310
|
'==================================%s\n') %
|
|
305
311
|
(date, '=' * len(date)))
|
|
@@ -402,8 +408,8 @@ Doctest summary
|
|
|
402
408
|
self.cleanup_runner = SphinxDocTestRunner(verbose=False,
|
|
403
409
|
optionflags=self.opt)
|
|
404
410
|
|
|
405
|
-
self.test_runner._fakeout = self.setup_runner._fakeout # type: ignore
|
|
406
|
-
self.cleanup_runner._fakeout = self.setup_runner._fakeout # type: ignore
|
|
411
|
+
self.test_runner._fakeout = self.setup_runner._fakeout # type: ignore[attr-defined]
|
|
412
|
+
self.cleanup_runner._fakeout = self.setup_runner._fakeout # type: ignore[attr-defined]
|
|
407
413
|
|
|
408
414
|
if self.config.doctest_test_doctest_blocks:
|
|
409
415
|
def condition(node: Node) -> bool:
|
|
@@ -452,8 +458,11 @@ Doctest summary
|
|
|
452
458
|
if not groups:
|
|
453
459
|
return
|
|
454
460
|
|
|
455
|
-
self.
|
|
456
|
-
|
|
461
|
+
show_successes = self.config.doctest_show_successes
|
|
462
|
+
if show_successes:
|
|
463
|
+
self._out('\n'
|
|
464
|
+
f'Document: {docname}\n'
|
|
465
|
+
f'----------{"-" * len(docname)}\n')
|
|
457
466
|
for group in groups.values():
|
|
458
467
|
self.test_group(group)
|
|
459
468
|
# Separately count results from setup code
|
|
@@ -461,12 +470,13 @@ Doctest summary
|
|
|
461
470
|
self.setup_failures += res_f
|
|
462
471
|
self.setup_tries += res_t
|
|
463
472
|
if self.test_runner.tries:
|
|
464
|
-
res_f, res_t = self.test_runner.summarize(
|
|
473
|
+
res_f, res_t = self.test_runner.summarize(
|
|
474
|
+
self._out, verbose=show_successes)
|
|
465
475
|
self.total_failures += res_f
|
|
466
476
|
self.total_tries += res_t
|
|
467
477
|
if self.cleanup_runner.tries:
|
|
468
|
-
res_f, res_t = self.cleanup_runner.summarize(
|
|
469
|
-
|
|
478
|
+
res_f, res_t = self.cleanup_runner.summarize(
|
|
479
|
+
self._out, verbose=show_successes)
|
|
470
480
|
self.cleanup_failures += res_f
|
|
471
481
|
self.cleanup_tries += res_t
|
|
472
482
|
|
|
@@ -526,7 +536,7 @@ Doctest summary
|
|
|
526
536
|
# disable <BLANKLINE> processing as it is not needed
|
|
527
537
|
options[doctest.DONT_ACCEPT_BLANKLINE] = True
|
|
528
538
|
# find out if we're testing an exception
|
|
529
|
-
m = parser._EXCEPTION_RE.match(output) # type: ignore
|
|
539
|
+
m = parser._EXCEPTION_RE.match(output) # type: ignore[attr-defined]
|
|
530
540
|
if m:
|
|
531
541
|
exc_msg = m.group('msg')
|
|
532
542
|
else:
|
|
@@ -553,6 +563,7 @@ def setup(app: Sphinx) -> dict[str, Any]:
|
|
|
553
563
|
app.add_directive('testoutput', TestoutputDirective)
|
|
554
564
|
app.add_builder(DocTestBuilder)
|
|
555
565
|
# this config value adds to sys.path
|
|
566
|
+
app.add_config_value('doctest_show_successes', True, False, (bool,))
|
|
556
567
|
app.add_config_value('doctest_path', [], False)
|
|
557
568
|
app.add_config_value('doctest_test_doctest_blocks', 'default', False)
|
|
558
569
|
app.add_config_value('doctest_global_setup', '', False)
|
sphinx/ext/duration.py
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
"""Measure
|
|
1
|
+
"""Measure document reading durations."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import time
|
|
6
6
|
from itertools import islice
|
|
7
7
|
from operator import itemgetter
|
|
8
|
-
from typing import
|
|
9
|
-
|
|
10
|
-
from docutils import nodes
|
|
8
|
+
from typing import TYPE_CHECKING, cast
|
|
11
9
|
|
|
12
10
|
import sphinx
|
|
13
|
-
from sphinx.application import Sphinx
|
|
14
11
|
from sphinx.domains import Domain
|
|
15
12
|
from sphinx.locale import __
|
|
16
13
|
from sphinx.util import logging
|
|
17
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from docutils import nodes
|
|
17
|
+
|
|
18
|
+
from sphinx.application import Sphinx
|
|
19
|
+
|
|
18
20
|
logger = logging.getLogger(__name__)
|
|
19
21
|
|
|
20
22
|
|
|
@@ -23,10 +25,10 @@ class DurationDomain(Domain):
|
|
|
23
25
|
name = 'duration'
|
|
24
26
|
|
|
25
27
|
@property
|
|
26
|
-
def reading_durations(self) -> dict[str,
|
|
28
|
+
def reading_durations(self) -> dict[str, float]:
|
|
27
29
|
return self.data.setdefault('reading_durations', {})
|
|
28
30
|
|
|
29
|
-
def note_reading_duration(self, duration:
|
|
31
|
+
def note_reading_duration(self, duration: float) -> None:
|
|
30
32
|
self.reading_durations[self.env.docname] = duration
|
|
31
33
|
|
|
32
34
|
def clear(self) -> None:
|
|
@@ -35,7 +37,7 @@ class DurationDomain(Domain):
|
|
|
35
37
|
def clear_doc(self, docname: str) -> None:
|
|
36
38
|
self.reading_durations.pop(docname, None)
|
|
37
39
|
|
|
38
|
-
def merge_domaindata(self, docnames: list[str], otherdata: dict[str,
|
|
40
|
+
def merge_domaindata(self, docnames: list[str], otherdata: dict[str, float]) -> None:
|
|
39
41
|
for docname, duration in otherdata.items():
|
|
40
42
|
if docname in docnames:
|
|
41
43
|
self.reading_durations[docname] = duration
|
|
@@ -44,7 +46,7 @@ class DurationDomain(Domain):
|
|
|
44
46
|
def on_builder_inited(app: Sphinx) -> None:
|
|
45
47
|
"""Initialize DurationDomain on bootstrap.
|
|
46
48
|
|
|
47
|
-
This clears results of last build.
|
|
49
|
+
This clears the results of the last build.
|
|
48
50
|
"""
|
|
49
51
|
domain = cast(DurationDomain, app.env.get_domain('duration'))
|
|
50
52
|
domain.clear()
|
|
@@ -52,31 +54,31 @@ def on_builder_inited(app: Sphinx) -> None:
|
|
|
52
54
|
|
|
53
55
|
def on_source_read(app: Sphinx, docname: str, content: list[str]) -> None:
|
|
54
56
|
"""Start to measure reading duration."""
|
|
55
|
-
app.env.temp_data['started_at'] =
|
|
57
|
+
app.env.temp_data['started_at'] = time.monotonic()
|
|
56
58
|
|
|
57
59
|
|
|
58
60
|
def on_doctree_read(app: Sphinx, doctree: nodes.document) -> None:
|
|
59
61
|
"""Record a reading duration."""
|
|
60
62
|
started_at = app.env.temp_data['started_at']
|
|
61
|
-
duration =
|
|
63
|
+
duration = time.monotonic() - started_at
|
|
62
64
|
domain = cast(DurationDomain, app.env.get_domain('duration'))
|
|
63
65
|
domain.note_reading_duration(duration)
|
|
64
66
|
|
|
65
67
|
|
|
66
68
|
def on_build_finished(app: Sphinx, error: Exception) -> None:
|
|
67
|
-
"""Display duration ranking on current build."""
|
|
69
|
+
"""Display duration ranking on the current build."""
|
|
68
70
|
domain = cast(DurationDomain, app.env.get_domain('duration'))
|
|
69
|
-
|
|
70
|
-
if not durations:
|
|
71
|
+
if not domain.reading_durations:
|
|
71
72
|
return
|
|
73
|
+
durations = sorted(domain.reading_durations.items(), key=itemgetter(1), reverse=True)
|
|
72
74
|
|
|
73
75
|
logger.info('')
|
|
74
76
|
logger.info(__('====================== slowest reading durations ======================='))
|
|
75
77
|
for docname, d in islice(durations, 5):
|
|
76
|
-
logger.info('
|
|
78
|
+
logger.info(f'{d:.3f} {docname}') # NoQA: G004
|
|
77
79
|
|
|
78
80
|
|
|
79
|
-
def setup(app: Sphinx) -> dict[str,
|
|
81
|
+
def setup(app: Sphinx) -> dict[str, bool | str]:
|
|
80
82
|
app.add_domain(DurationDomain)
|
|
81
83
|
app.connect('builder-inited', on_builder_inited)
|
|
82
84
|
app.connect('source-read', on_source_read)
|
sphinx/ext/extlinks.py
CHANGED
|
@@ -20,19 +20,24 @@ Both, the url string and the caption string must escape ``%`` as ``%%``.
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import re
|
|
23
|
-
from typing import Any
|
|
23
|
+
from typing import TYPE_CHECKING, Any
|
|
24
24
|
|
|
25
25
|
from docutils import nodes, utils
|
|
26
|
-
from docutils.nodes import Node, system_message
|
|
27
|
-
from docutils.parsers.rst.states import Inliner
|
|
28
26
|
|
|
29
27
|
import sphinx
|
|
30
|
-
from sphinx.application import Sphinx
|
|
31
28
|
from sphinx.locale import __
|
|
32
29
|
from sphinx.transforms.post_transforms import SphinxPostTransform
|
|
33
30
|
from sphinx.util import logging, rst
|
|
34
31
|
from sphinx.util.nodes import split_explicit_title
|
|
35
|
-
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from collections.abc import Sequence
|
|
35
|
+
|
|
36
|
+
from docutils.nodes import Node, system_message
|
|
37
|
+
from docutils.parsers.rst.states import Inliner
|
|
38
|
+
|
|
39
|
+
from sphinx.application import Sphinx
|
|
40
|
+
from sphinx.util.typing import RoleFunction
|
|
36
41
|
|
|
37
42
|
logger = logging.getLogger(__name__)
|
|
38
43
|
|
|
@@ -91,7 +96,7 @@ def make_link_role(name: str, base_url: str, caption: str) -> RoleFunction:
|
|
|
91
96
|
# Remark: It is an implementation detail that we use Pythons %-formatting.
|
|
92
97
|
# So far we only expose ``%s`` and require quoting of ``%`` using ``%%``.
|
|
93
98
|
def role(typ: str, rawtext: str, text: str, lineno: int,
|
|
94
|
-
inliner: Inliner, options: dict =
|
|
99
|
+
inliner: Inliner, options: dict | None = None, content: Sequence[str] = (),
|
|
95
100
|
) -> tuple[list[Node], list[system_message]]:
|
|
96
101
|
text = utils.unescape(text)
|
|
97
102
|
has_explicit_title, title, part = split_explicit_title(text)
|
sphinx/ext/githubpages.py
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import contextlib
|
|
5
6
|
import os
|
|
6
7
|
import urllib.parse
|
|
7
|
-
from typing import Any
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
8
9
|
|
|
9
10
|
import sphinx
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from sphinx.application import Sphinx
|
|
14
|
+
from sphinx.environment import BuildEnvironment
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
def _get_domain_from_url(url: str) -> str:
|
|
@@ -34,7 +37,7 @@ def create_nojekyll_and_cname(app: Sphinx, env: BuildEnvironment) -> None:
|
|
|
34
37
|
if app.builder.format != 'html':
|
|
35
38
|
return
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
app.builder.outdir.joinpath('.nojekyll').touch()
|
|
38
41
|
cname_path = os.path.join(app.builder.outdir, 'CNAME')
|
|
39
42
|
|
|
40
43
|
domain = _get_domain_from_url(app.config.html_baseurl)
|
|
@@ -45,10 +48,8 @@ def create_nojekyll_and_cname(app: Sphinx, env: BuildEnvironment) -> None:
|
|
|
45
48
|
# auto-generated by the GitHub UI doesn't have one.
|
|
46
49
|
f.write(domain)
|
|
47
50
|
else:
|
|
48
|
-
|
|
51
|
+
with contextlib.suppress(FileNotFoundError):
|
|
49
52
|
os.unlink(cname_path)
|
|
50
|
-
except FileNotFoundError:
|
|
51
|
-
pass
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
def setup(app: Sphinx) -> dict[str, Any]:
|
sphinx/ext/graphviz.py
CHANGED
|
@@ -6,32 +6,37 @@ from __future__ import annotations
|
|
|
6
6
|
import posixpath
|
|
7
7
|
import re
|
|
8
8
|
import subprocess
|
|
9
|
+
import xml.etree.ElementTree as ET
|
|
10
|
+
from hashlib import sha1
|
|
11
|
+
from itertools import chain
|
|
9
12
|
from os import path
|
|
10
13
|
from subprocess import CalledProcessError
|
|
11
14
|
from typing import TYPE_CHECKING, Any
|
|
15
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
12
16
|
|
|
13
17
|
from docutils import nodes
|
|
14
|
-
from docutils.nodes import Node
|
|
15
18
|
from docutils.parsers.rst import Directive, directives
|
|
16
19
|
|
|
17
20
|
import sphinx
|
|
18
|
-
from sphinx.application import Sphinx
|
|
19
21
|
from sphinx.errors import SphinxError
|
|
20
22
|
from sphinx.locale import _, __
|
|
21
|
-
from sphinx.util import logging
|
|
23
|
+
from sphinx.util import logging
|
|
22
24
|
from sphinx.util.docutils import SphinxDirective, SphinxTranslator
|
|
23
25
|
from sphinx.util.i18n import search_image_for_language
|
|
24
26
|
from sphinx.util.nodes import set_source_info
|
|
25
27
|
from sphinx.util.osutil import ensuredir
|
|
26
|
-
from sphinx.util.typing import OptionSpec
|
|
27
|
-
from sphinx.writers.html import HTML5Translator
|
|
28
|
-
from sphinx.writers.latex import LaTeXTranslator
|
|
29
|
-
from sphinx.writers.manpage import ManualPageTranslator
|
|
30
|
-
from sphinx.writers.texinfo import TexinfoTranslator
|
|
31
|
-
from sphinx.writers.text import TextTranslator
|
|
32
28
|
|
|
33
29
|
if TYPE_CHECKING:
|
|
30
|
+
from docutils.nodes import Node
|
|
31
|
+
|
|
32
|
+
from sphinx.application import Sphinx
|
|
34
33
|
from sphinx.config import Config
|
|
34
|
+
from sphinx.util.typing import OptionSpec
|
|
35
|
+
from sphinx.writers.html import HTML5Translator
|
|
36
|
+
from sphinx.writers.latex import LaTeXTranslator
|
|
37
|
+
from sphinx.writers.manpage import ManualPageTranslator
|
|
38
|
+
from sphinx.writers.texinfo import TexinfoTranslator
|
|
39
|
+
from sphinx.writers.text import TextTranslator
|
|
35
40
|
|
|
36
41
|
logger = logging.getLogger(__name__)
|
|
37
42
|
|
|
@@ -62,7 +67,7 @@ class ClickableMapDefinition:
|
|
|
62
67
|
if self.id == '%3':
|
|
63
68
|
# graphviz generates wrong ID if graph name not specified
|
|
64
69
|
# https://gitlab.com/graphviz/graphviz/issues/1327
|
|
65
|
-
hashed = sha1(dot.encode()).hexdigest()
|
|
70
|
+
hashed = sha1(dot.encode(), usedforsecurity=False).hexdigest()
|
|
66
71
|
self.id = 'grapviz%s' % hashed[-10:]
|
|
67
72
|
self.content[0] = self.content[0].replace('%3', self.id)
|
|
68
73
|
|
|
@@ -213,15 +218,50 @@ class GraphvizSimple(SphinxDirective):
|
|
|
213
218
|
return [figure]
|
|
214
219
|
|
|
215
220
|
|
|
221
|
+
def fix_svg_relative_paths(self: SphinxTranslator, filepath: str) -> None:
|
|
222
|
+
"""Change relative links in generated svg files to be relative to imgpath."""
|
|
223
|
+
tree = ET.parse(filepath) # NoQA: S314
|
|
224
|
+
root = tree.getroot()
|
|
225
|
+
ns = {'svg': 'http://www.w3.org/2000/svg', 'xlink': 'http://www.w3.org/1999/xlink'}
|
|
226
|
+
href_name = '{http://www.w3.org/1999/xlink}href'
|
|
227
|
+
modified = False
|
|
228
|
+
|
|
229
|
+
for element in chain(
|
|
230
|
+
root.findall('.//svg:image[@xlink:href]', ns),
|
|
231
|
+
root.findall('.//svg:a[@xlink:href]', ns),
|
|
232
|
+
):
|
|
233
|
+
scheme, hostname, url, query, fragment = urlsplit(element.attrib[href_name])
|
|
234
|
+
if hostname:
|
|
235
|
+
# not a relative link
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
old_path = path.join(self.builder.outdir, url)
|
|
239
|
+
new_path = path.relpath(
|
|
240
|
+
old_path,
|
|
241
|
+
start=path.join(self.builder.outdir, self.builder.imgpath),
|
|
242
|
+
)
|
|
243
|
+
modified_url = urlunsplit((scheme, hostname, new_path, query, fragment))
|
|
244
|
+
|
|
245
|
+
element.set(href_name, modified_url)
|
|
246
|
+
modified = True
|
|
247
|
+
|
|
248
|
+
if modified:
|
|
249
|
+
tree.write(filepath)
|
|
250
|
+
|
|
251
|
+
|
|
216
252
|
def render_dot(self: SphinxTranslator, code: str, options: dict, format: str,
|
|
217
253
|
prefix: str = 'graphviz', filename: str | None = None,
|
|
218
254
|
) -> tuple[str | None, str | None]:
|
|
219
255
|
"""Render graphviz code into a PNG or PDF output file."""
|
|
220
256
|
graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot)
|
|
257
|
+
if not graphviz_dot:
|
|
258
|
+
raise GraphvizError(
|
|
259
|
+
__('graphviz_dot executable path must be set! %r') % graphviz_dot,
|
|
260
|
+
)
|
|
221
261
|
hashkey = (code + str(options) + str(graphviz_dot) +
|
|
222
262
|
str(self.builder.config.graphviz_dot_args)).encode()
|
|
223
263
|
|
|
224
|
-
fname = f'{prefix}-{sha1(hashkey).hexdigest()}.{format}'
|
|
264
|
+
fname = f'{prefix}-{sha1(hashkey, usedforsecurity=False).hexdigest()}.{format}'
|
|
225
265
|
relfn = posixpath.join(self.builder.imgpath, fname)
|
|
226
266
|
outfn = path.join(self.builder.outdir, self.builder.imagedir, fname)
|
|
227
267
|
|
|
@@ -250,20 +290,24 @@ def render_dot(self: SphinxTranslator, code: str, options: dict, format: str,
|
|
|
250
290
|
try:
|
|
251
291
|
ret = subprocess.run(dot_args, input=code.encode(), capture_output=True,
|
|
252
292
|
cwd=cwd, check=True)
|
|
253
|
-
if not path.isfile(outfn):
|
|
254
|
-
raise GraphvizError(__('dot did not produce an output file:\n[stderr]\n%r\n'
|
|
255
|
-
'[stdout]\n%r') % (ret.stderr, ret.stdout))
|
|
256
|
-
return relfn, outfn
|
|
257
293
|
except OSError:
|
|
258
294
|
logger.warning(__('dot command %r cannot be run (needed for graphviz '
|
|
259
295
|
'output), check the graphviz_dot setting'), graphviz_dot)
|
|
260
296
|
if not hasattr(self.builder, '_graphviz_warned_dot'):
|
|
261
|
-
self.builder._graphviz_warned_dot = {} # type: ignore
|
|
262
|
-
self.builder._graphviz_warned_dot[graphviz_dot] = True # type: ignore
|
|
297
|
+
self.builder._graphviz_warned_dot = {} # type: ignore[attr-defined]
|
|
298
|
+
self.builder._graphviz_warned_dot[graphviz_dot] = True # type: ignore[attr-defined]
|
|
263
299
|
return None, None
|
|
264
300
|
except CalledProcessError as exc:
|
|
265
301
|
raise GraphvizError(__('dot exited with error:\n[stderr]\n%r\n'
|
|
266
302
|
'[stdout]\n%r') % (exc.stderr, exc.stdout)) from exc
|
|
303
|
+
if not path.isfile(outfn):
|
|
304
|
+
raise GraphvizError(__('dot did not produce an output file:\n[stderr]\n%r\n'
|
|
305
|
+
'[stdout]\n%r') % (ret.stderr, ret.stdout))
|
|
306
|
+
|
|
307
|
+
if format == 'svg':
|
|
308
|
+
fix_svg_relative_paths(self, outfn)
|
|
309
|
+
|
|
310
|
+
return relfn, outfn
|
|
267
311
|
|
|
268
312
|
|
|
269
313
|
def render_dot_html(self: HTML5Translator, node: graphviz, code: str, options: dict,
|
sphinx/ext/ifconfig.py
CHANGED
|
@@ -16,16 +16,19 @@ namespace of the project configuration (that is, all variables from
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
from typing import Any
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
20
|
|
|
21
21
|
from docutils import nodes
|
|
22
|
-
from docutils.nodes import Node
|
|
23
22
|
|
|
24
23
|
import sphinx
|
|
25
|
-
from sphinx.application import Sphinx
|
|
26
24
|
from sphinx.util.docutils import SphinxDirective
|
|
27
25
|
from sphinx.util.nodes import nested_parse_with_titles
|
|
28
|
-
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from docutils.nodes import Node
|
|
29
|
+
|
|
30
|
+
from sphinx.application import Sphinx
|
|
31
|
+
from sphinx.util.typing import OptionSpec
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
class ifconfig(nodes.Element):
|
sphinx/ext/imgconverter.py
CHANGED
|
@@ -5,15 +5,17 @@ from __future__ import annotations
|
|
|
5
5
|
import subprocess
|
|
6
6
|
import sys
|
|
7
7
|
from subprocess import CalledProcessError
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
9
|
|
|
10
10
|
import sphinx
|
|
11
|
-
from sphinx.application import Sphinx
|
|
12
11
|
from sphinx.errors import ExtensionError
|
|
13
12
|
from sphinx.locale import __
|
|
14
13
|
from sphinx.transforms.post_transforms.images import ImageConverter
|
|
15
14
|
from sphinx.util import logging
|
|
16
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from sphinx.application import Sphinx
|
|
18
|
+
|
|
17
19
|
logger = logging.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
sphinx/ext/imgmath.py
CHANGED
|
@@ -3,30 +3,37 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
|
+
import contextlib
|
|
6
7
|
import re
|
|
7
8
|
import shutil
|
|
8
9
|
import subprocess
|
|
9
10
|
import tempfile
|
|
11
|
+
from hashlib import sha1
|
|
10
12
|
from os import path
|
|
11
13
|
from subprocess import CalledProcessError
|
|
12
|
-
from typing import Any
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
13
15
|
|
|
14
16
|
from docutils import nodes
|
|
15
|
-
from docutils.nodes import Element
|
|
16
17
|
|
|
17
18
|
import sphinx
|
|
18
19
|
from sphinx import package_dir
|
|
19
|
-
from sphinx.application import Sphinx
|
|
20
|
-
from sphinx.builders import Builder
|
|
21
|
-
from sphinx.config import Config
|
|
22
20
|
from sphinx.errors import SphinxError
|
|
23
21
|
from sphinx.locale import _, __
|
|
24
|
-
from sphinx.util import logging
|
|
22
|
+
from sphinx.util import logging
|
|
25
23
|
from sphinx.util.math import get_node_equation_number, wrap_displaymath
|
|
26
24
|
from sphinx.util.osutil import ensuredir
|
|
27
25
|
from sphinx.util.png import read_png_depth, write_png_depth
|
|
28
26
|
from sphinx.util.template import LaTeXRenderer
|
|
29
|
-
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
import os
|
|
30
|
+
|
|
31
|
+
from docutils.nodes import Element
|
|
32
|
+
|
|
33
|
+
from sphinx.application import Sphinx
|
|
34
|
+
from sphinx.builders import Builder
|
|
35
|
+
from sphinx.config import Config
|
|
36
|
+
from sphinx.writers.html import HTML5Translator
|
|
30
37
|
|
|
31
38
|
logger = logging.getLogger(__name__)
|
|
32
39
|
|
|
@@ -82,7 +89,7 @@ def write_svg_depth(filename: str, depth: int) -> None:
|
|
|
82
89
|
def generate_latex_macro(image_format: str,
|
|
83
90
|
math: str,
|
|
84
91
|
config: Config,
|
|
85
|
-
confdir: str = '') -> str:
|
|
92
|
+
confdir: str | os.PathLike[str] = '') -> str:
|
|
86
93
|
"""Generate LaTeX macro."""
|
|
87
94
|
variables = {
|
|
88
95
|
'fontsize': config.imgmath_font_size,
|
|
@@ -115,9 +122,9 @@ def ensure_tempdir(builder: Builder) -> str:
|
|
|
115
122
|
just removing the whole directory (see cleanup_tempdir)
|
|
116
123
|
"""
|
|
117
124
|
if not hasattr(builder, '_imgmath_tempdir'):
|
|
118
|
-
builder._imgmath_tempdir = tempfile.mkdtemp() # type: ignore
|
|
125
|
+
builder._imgmath_tempdir = tempfile.mkdtemp() # type: ignore[attr-defined]
|
|
119
126
|
|
|
120
|
-
return builder._imgmath_tempdir # type: ignore
|
|
127
|
+
return builder._imgmath_tempdir # type: ignore[attr-defined]
|
|
121
128
|
|
|
122
129
|
|
|
123
130
|
def compile_math(latex: str, builder: Builder) -> str:
|
|
@@ -152,7 +159,8 @@ def compile_math(latex: str, builder: Builder) -> str:
|
|
|
152
159
|
builder.config.imgmath_latex)
|
|
153
160
|
raise InvokeError from exc
|
|
154
161
|
except CalledProcessError as exc:
|
|
155
|
-
|
|
162
|
+
msg = 'latex exited with error'
|
|
163
|
+
raise MathExtError(msg, exc.stderr, exc.stdout) from exc
|
|
156
164
|
|
|
157
165
|
|
|
158
166
|
def convert_dvi_to_image(command: list[str], name: str) -> tuple[str, str]:
|
|
@@ -232,14 +240,15 @@ def render_math(
|
|
|
232
240
|
"""
|
|
233
241
|
image_format = self.builder.config.imgmath_image_format.lower()
|
|
234
242
|
if image_format not in SUPPORT_FORMAT:
|
|
235
|
-
|
|
243
|
+
unsupported_format_msg = 'imgmath_image_format must be either "png" or "svg"'
|
|
244
|
+
raise MathExtError(unsupported_format_msg)
|
|
236
245
|
|
|
237
246
|
latex = generate_latex_macro(image_format,
|
|
238
247
|
math,
|
|
239
248
|
self.builder.config,
|
|
240
249
|
self.builder.confdir)
|
|
241
250
|
|
|
242
|
-
filename = f"{sha1(latex.encode()).hexdigest()}.{image_format}"
|
|
251
|
+
filename = f"{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}"
|
|
243
252
|
generated_path = path.join(self.builder.outdir, self.builder.imagedir, 'math', filename)
|
|
244
253
|
ensuredir(path.dirname(generated_path))
|
|
245
254
|
if path.isfile(generated_path):
|
|
@@ -258,7 +267,7 @@ def render_math(
|
|
|
258
267
|
try:
|
|
259
268
|
dvipath = compile_math(latex, self.builder)
|
|
260
269
|
except InvokeError:
|
|
261
|
-
self.builder._imgmath_warned_latex = True # type: ignore
|
|
270
|
+
self.builder._imgmath_warned_latex = True # type: ignore[attr-defined]
|
|
262
271
|
return None, None
|
|
263
272
|
|
|
264
273
|
# .dvi -> .png/.svg
|
|
@@ -268,7 +277,7 @@ def render_math(
|
|
|
268
277
|
elif image_format == 'svg':
|
|
269
278
|
depth = convert_dvi_to_svg(dvipath, self.builder, generated_path)
|
|
270
279
|
except InvokeError:
|
|
271
|
-
self.builder._imgmath_warned_image_translator = True # type: ignore
|
|
280
|
+
self.builder._imgmath_warned_image_translator = True # type: ignore[attr-defined]
|
|
272
281
|
return None, None
|
|
273
282
|
|
|
274
283
|
return generated_path, depth
|
|
@@ -281,7 +290,8 @@ def render_maths_to_base64(image_format: str, generated_path: str) -> str:
|
|
|
281
290
|
return f'data:image/png;base64,{encoded}'
|
|
282
291
|
if image_format == 'svg':
|
|
283
292
|
return f'data:image/svg+xml;base64,{encoded}'
|
|
284
|
-
|
|
293
|
+
unsupported_format_msg = 'imgmath_image_format must be either "png" or "svg"'
|
|
294
|
+
raise MathExtError(unsupported_format_msg)
|
|
285
295
|
|
|
286
296
|
|
|
287
297
|
def clean_up_files(app: Sphinx, exc: Exception) -> None:
|
|
@@ -289,18 +299,14 @@ def clean_up_files(app: Sphinx, exc: Exception) -> None:
|
|
|
289
299
|
return
|
|
290
300
|
|
|
291
301
|
if hasattr(app.builder, '_imgmath_tempdir'):
|
|
292
|
-
|
|
302
|
+
with contextlib.suppress(Exception):
|
|
293
303
|
shutil.rmtree(app.builder._imgmath_tempdir)
|
|
294
|
-
except Exception:
|
|
295
|
-
pass
|
|
296
304
|
|
|
297
305
|
if app.builder.config.imgmath_embed:
|
|
298
306
|
# in embed mode, the images are still generated in the math output dir
|
|
299
307
|
# to be shared across workers, but are not useful to the final document
|
|
300
|
-
|
|
308
|
+
with contextlib.suppress(Exception):
|
|
301
309
|
shutil.rmtree(path.join(app.builder.outdir, app.builder.imagedir, 'math'))
|
|
302
|
-
except Exception:
|
|
303
|
-
pass
|
|
304
310
|
|
|
305
311
|
|
|
306
312
|
def get_tooltip(self: HTML5Translator, node: Element) -> str:
|
|
@@ -358,7 +364,7 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non
|
|
|
358
364
|
if node['number']:
|
|
359
365
|
number = get_node_equation_number(self, node)
|
|
360
366
|
self.body.append('<span class="eqno">(%s)' % number)
|
|
361
|
-
self.add_permalink_ref(node, _('
|
|
367
|
+
self.add_permalink_ref(node, _('Link to this equation'))
|
|
362
368
|
self.body.append('</span>')
|
|
363
369
|
|
|
364
370
|
if rendered_path is None:
|