Sphinx 8.1.3__py3-none-any.whl → 8.2.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Sphinx might be problematic. Click here for more details.

Files changed (193) hide show
  1. sphinx/__init__.py +8 -4
  2. sphinx/__main__.py +2 -0
  3. sphinx/_cli/__init__.py +2 -5
  4. sphinx/_cli/util/colour.py +34 -11
  5. sphinx/_cli/util/errors.py +128 -61
  6. sphinx/addnodes.py +51 -35
  7. sphinx/application.py +362 -230
  8. sphinx/builders/__init__.py +87 -64
  9. sphinx/builders/_epub_base.py +65 -56
  10. sphinx/builders/changes.py +17 -23
  11. sphinx/builders/dirhtml.py +8 -13
  12. sphinx/builders/epub3.py +70 -38
  13. sphinx/builders/gettext.py +93 -73
  14. sphinx/builders/html/__init__.py +240 -186
  15. sphinx/builders/html/_assets.py +9 -2
  16. sphinx/builders/html/_build_info.py +3 -0
  17. sphinx/builders/latex/__init__.py +64 -54
  18. sphinx/builders/latex/constants.py +14 -11
  19. sphinx/builders/latex/nodes.py +2 -0
  20. sphinx/builders/latex/theming.py +8 -9
  21. sphinx/builders/latex/transforms.py +7 -5
  22. sphinx/builders/linkcheck.py +193 -149
  23. sphinx/builders/manpage.py +17 -17
  24. sphinx/builders/singlehtml.py +28 -16
  25. sphinx/builders/texinfo.py +28 -21
  26. sphinx/builders/text.py +10 -15
  27. sphinx/builders/xml.py +10 -19
  28. sphinx/cmd/build.py +49 -119
  29. sphinx/cmd/make_mode.py +35 -31
  30. sphinx/cmd/quickstart.py +78 -62
  31. sphinx/config.py +265 -163
  32. sphinx/directives/__init__.py +51 -54
  33. sphinx/directives/admonitions.py +107 -0
  34. sphinx/directives/code.py +24 -19
  35. sphinx/directives/other.py +21 -42
  36. sphinx/directives/patches.py +28 -16
  37. sphinx/domains/__init__.py +54 -31
  38. sphinx/domains/_domains_container.py +22 -17
  39. sphinx/domains/_index.py +5 -8
  40. sphinx/domains/c/__init__.py +366 -245
  41. sphinx/domains/c/_ast.py +378 -256
  42. sphinx/domains/c/_ids.py +89 -31
  43. sphinx/domains/c/_parser.py +283 -214
  44. sphinx/domains/c/_symbol.py +269 -198
  45. sphinx/domains/changeset.py +39 -24
  46. sphinx/domains/citation.py +54 -24
  47. sphinx/domains/cpp/__init__.py +517 -362
  48. sphinx/domains/cpp/_ast.py +999 -682
  49. sphinx/domains/cpp/_ids.py +133 -65
  50. sphinx/domains/cpp/_parser.py +746 -588
  51. sphinx/domains/cpp/_symbol.py +692 -489
  52. sphinx/domains/index.py +10 -8
  53. sphinx/domains/javascript.py +152 -74
  54. sphinx/domains/math.py +48 -40
  55. sphinx/domains/python/__init__.py +402 -211
  56. sphinx/domains/python/_annotations.py +114 -57
  57. sphinx/domains/python/_object.py +151 -67
  58. sphinx/domains/rst.py +94 -49
  59. sphinx/domains/std/__init__.py +510 -249
  60. sphinx/environment/__init__.py +345 -61
  61. sphinx/environment/adapters/asset.py +7 -1
  62. sphinx/environment/adapters/indexentries.py +15 -20
  63. sphinx/environment/adapters/toctree.py +19 -9
  64. sphinx/environment/collectors/__init__.py +3 -1
  65. sphinx/environment/collectors/asset.py +18 -15
  66. sphinx/environment/collectors/dependencies.py +8 -10
  67. sphinx/environment/collectors/metadata.py +6 -4
  68. sphinx/environment/collectors/title.py +3 -1
  69. sphinx/environment/collectors/toctree.py +4 -4
  70. sphinx/errors.py +1 -3
  71. sphinx/events.py +4 -4
  72. sphinx/ext/apidoc/__init__.py +21 -0
  73. sphinx/ext/apidoc/__main__.py +9 -0
  74. sphinx/ext/apidoc/_cli.py +356 -0
  75. sphinx/ext/apidoc/_generate.py +356 -0
  76. sphinx/ext/apidoc/_shared.py +66 -0
  77. sphinx/ext/autodoc/__init__.py +829 -480
  78. sphinx/ext/autodoc/directive.py +57 -21
  79. sphinx/ext/autodoc/importer.py +184 -67
  80. sphinx/ext/autodoc/mock.py +25 -10
  81. sphinx/ext/autodoc/preserve_defaults.py +17 -9
  82. sphinx/ext/autodoc/type_comment.py +56 -29
  83. sphinx/ext/autodoc/typehints.py +49 -26
  84. sphinx/ext/autosectionlabel.py +28 -11
  85. sphinx/ext/autosummary/__init__.py +271 -143
  86. sphinx/ext/autosummary/generate.py +121 -51
  87. sphinx/ext/coverage.py +152 -91
  88. sphinx/ext/doctest.py +169 -101
  89. sphinx/ext/duration.py +12 -6
  90. sphinx/ext/extlinks.py +33 -21
  91. sphinx/ext/githubpages.py +8 -8
  92. sphinx/ext/graphviz.py +175 -109
  93. sphinx/ext/ifconfig.py +11 -6
  94. sphinx/ext/imgconverter.py +48 -25
  95. sphinx/ext/imgmath.py +127 -97
  96. sphinx/ext/inheritance_diagram.py +177 -103
  97. sphinx/ext/intersphinx/__init__.py +22 -13
  98. sphinx/ext/intersphinx/__main__.py +3 -1
  99. sphinx/ext/intersphinx/_cli.py +18 -14
  100. sphinx/ext/intersphinx/_load.py +91 -82
  101. sphinx/ext/intersphinx/_resolve.py +108 -74
  102. sphinx/ext/intersphinx/_shared.py +2 -2
  103. sphinx/ext/linkcode.py +28 -12
  104. sphinx/ext/mathjax.py +60 -29
  105. sphinx/ext/napoleon/__init__.py +19 -7
  106. sphinx/ext/napoleon/docstring.py +229 -231
  107. sphinx/ext/todo.py +44 -49
  108. sphinx/ext/viewcode.py +105 -57
  109. sphinx/extension.py +3 -1
  110. sphinx/highlighting.py +13 -7
  111. sphinx/io.py +9 -13
  112. sphinx/jinja2glue.py +29 -26
  113. sphinx/locale/__init__.py +8 -9
  114. sphinx/parsers.py +8 -7
  115. sphinx/project.py +2 -2
  116. sphinx/pycode/__init__.py +31 -21
  117. sphinx/pycode/ast.py +6 -3
  118. sphinx/pycode/parser.py +14 -8
  119. sphinx/pygments_styles.py +4 -5
  120. sphinx/registry.py +192 -92
  121. sphinx/roles.py +58 -7
  122. sphinx/search/__init__.py +75 -54
  123. sphinx/search/en.py +11 -13
  124. sphinx/search/fi.py +1 -1
  125. sphinx/search/ja.py +8 -6
  126. sphinx/search/nl.py +1 -1
  127. sphinx/search/zh.py +19 -21
  128. sphinx/testing/fixtures.py +26 -29
  129. sphinx/testing/path.py +26 -62
  130. sphinx/testing/restructuredtext.py +14 -8
  131. sphinx/testing/util.py +21 -19
  132. sphinx/texinputs/make.bat.jinja +50 -50
  133. sphinx/texinputs/sphinx.sty +4 -3
  134. sphinx/texinputs/sphinxlatexadmonitions.sty +1 -1
  135. sphinx/texinputs/sphinxlatexobjects.sty +29 -10
  136. sphinx/themes/basic/static/searchtools.js +8 -5
  137. sphinx/theming.py +49 -61
  138. sphinx/transforms/__init__.py +17 -38
  139. sphinx/transforms/compact_bullet_list.py +5 -3
  140. sphinx/transforms/i18n.py +8 -21
  141. sphinx/transforms/post_transforms/__init__.py +142 -93
  142. sphinx/transforms/post_transforms/code.py +5 -5
  143. sphinx/transforms/post_transforms/images.py +28 -24
  144. sphinx/transforms/references.py +3 -1
  145. sphinx/util/__init__.py +109 -60
  146. sphinx/util/_files.py +39 -23
  147. sphinx/util/_importer.py +4 -1
  148. sphinx/util/_inventory_file_reader.py +76 -0
  149. sphinx/util/_io.py +2 -2
  150. sphinx/util/_lines.py +6 -3
  151. sphinx/util/_pathlib.py +40 -2
  152. sphinx/util/build_phase.py +2 -0
  153. sphinx/util/cfamily.py +19 -14
  154. sphinx/util/console.py +44 -179
  155. sphinx/util/display.py +9 -10
  156. sphinx/util/docfields.py +140 -122
  157. sphinx/util/docstrings.py +1 -1
  158. sphinx/util/docutils.py +118 -77
  159. sphinx/util/fileutil.py +25 -26
  160. sphinx/util/http_date.py +2 -0
  161. sphinx/util/i18n.py +77 -64
  162. sphinx/util/images.py +8 -6
  163. sphinx/util/inspect.py +147 -38
  164. sphinx/util/inventory.py +215 -116
  165. sphinx/util/logging.py +33 -33
  166. sphinx/util/matching.py +12 -4
  167. sphinx/util/nodes.py +18 -13
  168. sphinx/util/osutil.py +38 -39
  169. sphinx/util/parallel.py +22 -13
  170. sphinx/util/parsing.py +2 -1
  171. sphinx/util/png.py +6 -2
  172. sphinx/util/requests.py +33 -2
  173. sphinx/util/rst.py +3 -2
  174. sphinx/util/tags.py +1 -1
  175. sphinx/util/template.py +18 -10
  176. sphinx/util/texescape.py +8 -6
  177. sphinx/util/typing.py +148 -122
  178. sphinx/versioning.py +3 -3
  179. sphinx/writers/html.py +3 -1
  180. sphinx/writers/html5.py +61 -50
  181. sphinx/writers/latex.py +80 -65
  182. sphinx/writers/manpage.py +19 -38
  183. sphinx/writers/texinfo.py +44 -45
  184. sphinx/writers/text.py +48 -30
  185. sphinx/writers/xml.py +11 -8
  186. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/LICENSE.rst +1 -1
  187. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/METADATA +23 -15
  188. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/RECORD +190 -186
  189. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/WHEEL +1 -1
  190. sphinx/builders/html/transforms.py +0 -90
  191. sphinx/ext/apidoc.py +0 -721
  192. sphinx/util/exceptions.py +0 -74
  193. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/entry_points.txt +0 -0
@@ -14,6 +14,8 @@ from sphinx.transforms.post_transforms.images import ImageConverter
14
14
  from sphinx.util import logging
15
15
 
16
16
  if TYPE_CHECKING:
17
+ import os
18
+
17
19
  from sphinx.application import Sphinx
18
20
  from sphinx.util.typing import ExtensionMetadata
19
21
 
@@ -37,42 +39,57 @@ class ImagemagickConverter(ImageConverter):
37
39
  subprocess.run(args, capture_output=True, check=True)
38
40
  return True
39
41
  except OSError as exc:
40
- logger.warning(__(
41
- "Unable to run the image conversion command %r. "
42
- "'sphinx.ext.imgconverter' requires ImageMagick by default. "
43
- "Ensure it is installed, or set the 'image_converter' option "
44
- "to a custom conversion command.\n\n"
45
- "Traceback: %s",
46
- ), self.config.image_converter, exc)
42
+ logger.warning(
43
+ __(
44
+ 'Unable to run the image conversion command %r. '
45
+ "'sphinx.ext.imgconverter' requires ImageMagick by default. "
46
+ "Ensure it is installed, or set the 'image_converter' option "
47
+ 'to a custom conversion command.\n\n'
48
+ 'Traceback: %s',
49
+ ),
50
+ self.config.image_converter,
51
+ exc,
52
+ )
47
53
  return False
48
54
  except CalledProcessError as exc:
49
- logger.warning(__('convert exited with error:\n'
50
- '[stderr]\n%r\n[stdout]\n%r'),
51
- exc.stderr, exc.stdout)
55
+ logger.warning(
56
+ __('convert exited with error:\n[stderr]\n%r\n[stdout]\n%r'),
57
+ exc.stderr,
58
+ exc.stdout,
59
+ )
52
60
  return False
53
61
 
54
- def convert(self, _from: str, _to: str) -> bool:
62
+ def convert(
63
+ self, _from: str | os.PathLike[str], _to: str | os.PathLike[str]
64
+ ) -> bool:
55
65
  """Converts the image to expected one."""
56
66
  try:
57
67
  # append an index 0 to source filename to pick up the first frame
58
68
  # (or first page) of image (ex. Animation GIF, PDF)
59
- _from += '[0]'
69
+ from_ = f'{_from}[0]'
60
70
 
61
- args = ([
62
- self.config.image_converter, *self.config.image_converter_args, _from, _to,
63
- ])
71
+ args = [
72
+ self.config.image_converter,
73
+ *self.config.image_converter_args,
74
+ from_,
75
+ _to,
76
+ ]
64
77
  logger.debug('Invoking %r ...', args)
65
78
  subprocess.run(args, capture_output=True, check=True)
66
79
  return True
67
80
  except OSError:
68
- logger.warning(__('convert command %r cannot be run, '
69
- 'check the image_converter setting'),
70
- self.config.image_converter)
81
+ logger.warning(
82
+ __(
83
+ 'convert command %r cannot be run, check the image_converter setting'
84
+ ),
85
+ self.config.image_converter,
86
+ )
71
87
  return False
72
88
  except CalledProcessError as exc:
73
- raise ExtensionError(__('convert exited with error:\n'
74
- '[stderr]\n%r\n[stdout]\n%r') %
75
- (exc.stderr, exc.stdout)) from exc
89
+ raise ExtensionError(
90
+ __('convert exited with error:\n[stderr]\n%r\n[stdout]\n%r')
91
+ % (exc.stderr, exc.stdout)
92
+ ) from exc
76
93
 
77
94
 
78
95
  def setup(app: Sphinx) -> ExtensionMetadata:
@@ -80,14 +97,20 @@ def setup(app: Sphinx) -> ExtensionMetadata:
80
97
  if sys.platform == 'win32':
81
98
  # On Windows, we use Imagemagik v7 by default to avoid the trouble for
82
99
  # convert.exe bundled with Windows.
83
- app.add_config_value('image_converter', 'magick', 'env')
84
- app.add_config_value('image_converter_args', ['convert'], 'env')
100
+ app.add_config_value('image_converter', 'magick', 'env', types=frozenset({str}))
101
+ app.add_config_value(
102
+ 'image_converter_args', ['convert'], 'env', types=frozenset({list, tuple})
103
+ )
85
104
  else:
86
105
  # On other platform, we use Imagemagick v6 by default. Especially,
87
106
  # Debian/Ubuntu are still based of v6. So we can't use "magick" command
88
107
  # for these platforms.
89
- app.add_config_value('image_converter', 'convert', 'env')
90
- app.add_config_value('image_converter_args', [], 'env')
108
+ app.add_config_value(
109
+ 'image_converter', 'convert', 'env', types=frozenset({str})
110
+ )
111
+ app.add_config_value(
112
+ 'image_converter_args', [], 'env', types=frozenset({list, tuple})
113
+ )
91
114
 
92
115
  return {
93
116
  'version': sphinx.__display_version__,
sphinx/ext/imgmath.py CHANGED
@@ -6,12 +6,14 @@ __all__ = ()
6
6
 
7
7
  import base64
8
8
  import contextlib
9
+ import os
10
+ import os.path
9
11
  import re
10
12
  import shutil
11
13
  import subprocess
12
14
  import tempfile
13
15
  from hashlib import sha1
14
- from os import path
16
+ from pathlib import Path
15
17
  from subprocess import CalledProcessError
16
18
  from typing import TYPE_CHECKING
17
19
 
@@ -23,31 +25,29 @@ from sphinx.errors import SphinxError
23
25
  from sphinx.locale import _, __
24
26
  from sphinx.util import logging
25
27
  from sphinx.util.math import get_node_equation_number, wrap_displaymath
26
- from sphinx.util.osutil import ensuredir
27
28
  from sphinx.util.png import read_png_depth, write_png_depth
28
29
  from sphinx.util.template import LaTeXRenderer
29
30
 
30
31
  if TYPE_CHECKING:
31
- import os
32
-
33
32
  from docutils.nodes import Element
34
33
 
35
34
  from sphinx.application import Sphinx
36
35
  from sphinx.builders import Builder
37
36
  from sphinx.config import Config
37
+ from sphinx.util._pathlib import _StrPath
38
38
  from sphinx.util.typing import ExtensionMetadata
39
39
  from sphinx.writers.html5 import HTML5Translator
40
40
 
41
41
  logger = logging.getLogger(__name__)
42
42
 
43
- templates_path = path.join(package_dir, 'templates', 'imgmath')
43
+ templates_path = package_dir.joinpath('templates', 'imgmath')
44
44
 
45
45
 
46
46
  class MathExtError(SphinxError):
47
47
  category = 'Math extension error'
48
48
 
49
49
  def __init__(
50
- self, msg: str, stderr: str | None = None, stdout: str | None = None,
50
+ self, msg: str, stderr: str | None = None, stdout: str | None = None
51
51
  ) -> None:
52
52
  if stderr:
53
53
  msg += '\n[stderr]\n' + stderr
@@ -67,10 +67,9 @@ depthsvg_re = re.compile(r'.*, depth=(.*)pt')
67
67
  depthsvgcomment_re = re.compile(r'<!-- DEPTH=(-?\d+) -->')
68
68
 
69
69
 
70
- def read_svg_depth(filename: str) -> int | None:
71
- """Read the depth from comment at last line of SVG file
72
- """
73
- with open(filename, encoding="utf-8") as f:
70
+ def read_svg_depth(filename: str | os.PathLike[str]) -> int | None:
71
+ """Read the depth from comment at last line of SVG file"""
72
+ with open(filename, encoding='utf-8') as f:
74
73
  for line in f: # NoQA: B007
75
74
  pass
76
75
  # Only last line is checked
@@ -80,23 +79,24 @@ def read_svg_depth(filename: str) -> int | None:
80
79
  return None
81
80
 
82
81
 
83
- def write_svg_depth(filename: str, depth: int) -> None:
84
- """Write the depth to SVG file as a comment at end of file
85
- """
86
- with open(filename, 'a', encoding="utf-8") as f:
82
+ def write_svg_depth(filename: Path, depth: int) -> None:
83
+ """Write the depth to SVG file as a comment at end of file"""
84
+ with open(filename, 'a', encoding='utf-8') as f:
87
85
  f.write('\n<!-- DEPTH=%s -->' % depth)
88
86
 
89
87
 
90
- def generate_latex_macro(image_format: str,
91
- math: str,
92
- config: Config,
93
- confdir: str | os.PathLike[str] = '') -> str:
88
+ def generate_latex_macro(
89
+ image_format: str,
90
+ math: str,
91
+ config: Config,
92
+ confdir: _StrPath,
93
+ ) -> str:
94
94
  """Generate LaTeX macro."""
95
95
  variables = {
96
96
  'fontsize': config.imgmath_font_size,
97
- 'baselineskip': int(round(config.imgmath_font_size * 1.2)),
97
+ 'baselineskip': round(config.imgmath_font_size * 1.2),
98
98
  'preamble': config.imgmath_latex_preamble,
99
- # the dvips option is important when imgmath_latex in ["xelatex", "tectonic"],
99
+ # the dvips option is important when imgmath_latex in {"xelatex", "tectonic"},
100
100
  # it has no impact when imgmath_latex="latex"
101
101
  'tightpage': '' if image_format == 'png' else ',dvips,tightpage',
102
102
  'math': math,
@@ -109,14 +109,14 @@ def generate_latex_macro(image_format: str,
109
109
 
110
110
  for template_dir in config.templates_path:
111
111
  for template_suffix in ('.jinja', '_t'):
112
- template = path.join(confdir, template_dir, template_name + template_suffix)
113
- if path.exists(template):
112
+ template = confdir / template_dir / (template_name + template_suffix)
113
+ if template.exists():
114
114
  return LaTeXRenderer().render(template, variables)
115
115
 
116
- return LaTeXRenderer(templates_path).render(template_name + '.jinja', variables)
116
+ return LaTeXRenderer([templates_path]).render(template_name + '.jinja', variables)
117
117
 
118
118
 
119
- def ensure_tempdir(builder: Builder) -> str:
119
+ def ensure_tempdir(builder: Builder) -> Path:
120
120
  """Create temporary directory.
121
121
 
122
122
  use only one tempdir per build -- the use of a directory is cleaner
@@ -124,19 +124,19 @@ def ensure_tempdir(builder: Builder) -> str:
124
124
  just removing the whole directory (see cleanup_tempdir)
125
125
  """
126
126
  if not hasattr(builder, '_imgmath_tempdir'):
127
- builder._imgmath_tempdir = tempfile.mkdtemp() # type: ignore[attr-defined]
127
+ builder._imgmath_tempdir = Path(tempfile.mkdtemp()) # type: ignore[attr-defined]
128
128
 
129
129
  return builder._imgmath_tempdir # type: ignore[attr-defined]
130
130
 
131
131
 
132
- def compile_math(latex: str, builder: Builder) -> str:
132
+ def compile_math(latex: str, builder: Builder) -> Path:
133
133
  """Compile LaTeX macros for math to DVI."""
134
134
  tempdir = ensure_tempdir(builder)
135
- filename = path.join(tempdir, 'math.tex')
135
+ filename = tempdir / 'math.tex'
136
136
  with open(filename, 'w', encoding='utf-8') as f:
137
137
  f.write(latex)
138
138
 
139
- imgmath_latex_name = path.basename(builder.config.imgmath_latex)
139
+ imgmath_latex_name = os.path.basename(builder.config.imgmath_latex)
140
140
 
141
141
  # build latex command; old versions of latex don't have the
142
142
  # --output-directory option, so we have to manually chdir to the
@@ -149,16 +149,21 @@ def compile_math(latex: str, builder: Builder) -> str:
149
149
  command.append('math.tex')
150
150
 
151
151
  try:
152
- subprocess.run(command, capture_output=True, cwd=tempdir, check=True,
153
- encoding='ascii')
152
+ subprocess.run(
153
+ command, capture_output=True, cwd=tempdir, check=True, encoding='ascii'
154
+ )
154
155
  if imgmath_latex_name in {'xelatex', 'tectonic'}:
155
- return path.join(tempdir, 'math.xdv')
156
+ return tempdir / 'math.xdv'
156
157
  else:
157
- return path.join(tempdir, 'math.dvi')
158
+ return tempdir / 'math.dvi'
158
159
  except OSError as exc:
159
- logger.warning(__('LaTeX command %r cannot be run (needed for math '
160
- 'display), check the imgmath_latex setting'),
161
- builder.config.imgmath_latex)
160
+ logger.warning(
161
+ __(
162
+ 'LaTeX command %r cannot be run (needed for math '
163
+ 'display), check the imgmath_latex setting'
164
+ ),
165
+ builder.config.imgmath_latex,
166
+ )
162
167
  raise InvokeError from exc
163
168
  except CalledProcessError as exc:
164
169
  msg = 'latex exited with error'
@@ -171,15 +176,22 @@ def convert_dvi_to_image(command: list[str], name: str) -> tuple[str, str]:
171
176
  ret = subprocess.run(command, capture_output=True, check=True, encoding='ascii')
172
177
  return ret.stdout, ret.stderr
173
178
  except OSError as exc:
174
- logger.warning(__('%s command %r cannot be run (needed for math '
175
- 'display), check the imgmath_%s setting'),
176
- name, command[0], name)
179
+ logger.warning(
180
+ __(
181
+ '%s command %r cannot be run (needed for math '
182
+ 'display), check the imgmath_%s setting'
183
+ ),
184
+ name,
185
+ command[0],
186
+ name,
187
+ )
177
188
  raise InvokeError from exc
178
189
  except CalledProcessError as exc:
179
- raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout) from exc
190
+ msg = f'{name} exited with error'
191
+ raise MathExtError(msg, exc.stderr, exc.stdout) from exc
180
192
 
181
193
 
182
- def convert_dvi_to_png(dvipath: str, builder: Builder, out_path: str) -> int | None:
194
+ def convert_dvi_to_png(dvipath: Path, builder: Builder, out_path: Path) -> int | None:
183
195
  """Convert DVI file to PNG image."""
184
196
  name = 'dvipng'
185
197
  command = [builder.config.imgmath_dvipng, '-o', out_path, '-T', 'tight', '-z9']
@@ -202,7 +214,7 @@ def convert_dvi_to_png(dvipath: str, builder: Builder, out_path: str) -> int | N
202
214
  return depth
203
215
 
204
216
 
205
- def convert_dvi_to_svg(dvipath: str, builder: Builder, out_path: str) -> int | None:
217
+ def convert_dvi_to_svg(dvipath: Path, builder: Builder, out_path: Path) -> int | None:
206
218
  """Convert DVI file to SVG image."""
207
219
  name = 'dvisvgm'
208
220
  command = [builder.config.imgmath_dvisvgm, '-o', out_path]
@@ -226,7 +238,7 @@ def convert_dvi_to_svg(dvipath: str, builder: Builder, out_path: str) -> int | N
226
238
  def render_math(
227
239
  self: HTML5Translator,
228
240
  math: str,
229
- ) -> tuple[str | None, int | None]:
241
+ ) -> tuple[_StrPath | None, int | None]:
230
242
  """Render the LaTeX math expression *math* using latex and dvipng or
231
243
  dvisvgm.
232
244
 
@@ -245,15 +257,16 @@ def render_math(
245
257
  unsupported_format_msg = 'imgmath_image_format must be either "png" or "svg"'
246
258
  raise MathExtError(unsupported_format_msg)
247
259
 
248
- latex = generate_latex_macro(image_format,
249
- math,
250
- self.builder.config,
251
- self.builder.confdir)
260
+ latex = generate_latex_macro(
261
+ image_format, math, self.builder.config, self.builder.confdir
262
+ )
252
263
 
253
- filename = f"{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}"
254
- generated_path = path.join(self.builder.outdir, self.builder.imagedir, 'math', filename)
255
- ensuredir(path.dirname(generated_path))
256
- if path.isfile(generated_path):
264
+ filename = (
265
+ f'{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}'
266
+ )
267
+ generated_path = self.builder.outdir / self.builder.imagedir / 'math' / filename
268
+ generated_path.parent.mkdir(parents=True, exist_ok=True)
269
+ if generated_path.is_file():
257
270
  if image_format == 'png':
258
271
  depth = read_png_depth(generated_path)
259
272
  elif image_format == 'svg':
@@ -261,8 +274,9 @@ def render_math(
261
274
  return generated_path, depth
262
275
 
263
276
  # if latex or dvipng (dvisvgm) has failed once, don't bother to try again
264
- if hasattr(self.builder, '_imgmath_warned_latex') or \
265
- hasattr(self.builder, '_imgmath_warned_image_translator'):
277
+ latex_failed = hasattr(self.builder, '_imgmath_warned_latex')
278
+ trans_failed = hasattr(self.builder, '_imgmath_warned_image_translator')
279
+ if latex_failed or trans_failed:
266
280
  return None, None
267
281
 
268
282
  # .tex -> .dvi
@@ -285,9 +299,10 @@ def render_math(
285
299
  return generated_path, depth
286
300
 
287
301
 
288
- def render_maths_to_base64(image_format: str, generated_path: str) -> str:
289
- with open(generated_path, "rb") as f:
290
- encoded = base64.b64encode(f.read()).decode(encoding='utf-8')
302
+ def render_maths_to_base64(image_format: str, generated_path: Path) -> str:
303
+ with open(generated_path, 'rb') as f:
304
+ content = f.read()
305
+ encoded = base64.b64encode(content).decode(encoding='utf-8')
291
306
  if image_format == 'png':
292
307
  return f'data:image/png;base64,{encoded}'
293
308
  if image_format == 'svg':
@@ -308,12 +323,12 @@ def clean_up_files(app: Sphinx, exc: Exception) -> None:
308
323
  # in embed mode, the images are still generated in the math output dir
309
324
  # to be shared across workers, but are not useful to the final document
310
325
  with contextlib.suppress(Exception):
311
- shutil.rmtree(path.join(app.builder.outdir, app.builder.imagedir, 'math'))
326
+ shutil.rmtree(app.builder.outdir / app.builder.imagedir / 'math')
312
327
 
313
328
 
314
329
  def get_tooltip(self: HTML5Translator, node: Element) -> str:
315
330
  if self.builder.config.imgmath_add_tooltips:
316
- return ' alt="%s"' % self.encode(node.astext()).strip()
331
+ return f' alt="{self.encode(node.astext()).strip()}"'
317
332
  return ''
318
333
 
319
334
 
@@ -322,33 +337,35 @@ def html_visit_math(self: HTML5Translator, node: nodes.math) -> None:
322
337
  rendered_path, depth = render_math(self, '$' + node.astext() + '$')
323
338
  except MathExtError as exc:
324
339
  msg = str(exc)
325
- sm = nodes.system_message(msg, type='WARNING', level=2,
326
- backrefs=[], source=node.astext())
340
+ sm = nodes.system_message(
341
+ msg, type='WARNING', level=2, backrefs=[], source=node.astext()
342
+ )
327
343
  sm.walkabout(self)
328
344
  logger.warning(__('display latex %r: %s'), node.astext(), msg)
329
345
  raise nodes.SkipNode from exc
330
346
 
331
347
  if rendered_path is None:
332
348
  # something failed -- use text-only as a bad substitute
333
- self.body.append('<span class="math">%s</span>' %
334
- self.encode(node.astext()).strip())
349
+ self.body.append(
350
+ f'<span class="math">{self.encode(node.astext()).strip()}</span>'
351
+ )
335
352
  else:
336
353
  if self.builder.config.imgmath_embed:
337
354
  image_format = self.builder.config.imgmath_image_format.lower()
338
355
  img_src = render_maths_to_base64(image_format, rendered_path)
339
356
  else:
340
- bname = path.basename(rendered_path)
341
- relative_path = path.join(self.builder.imgpath, 'math', bname)
342
- img_src = relative_path.replace(path.sep, '/')
343
- c = f'<img class="math" src="{img_src}"' + get_tooltip(self, node)
344
- if depth is not None:
345
- c += f' style="vertical-align: {-depth:d}px"'
346
- self.body.append(c + '/>')
357
+ bname = os.path.basename(rendered_path)
358
+ relative_path = Path(self.builder.imgpath, 'math', bname)
359
+ img_src = relative_path.as_posix()
360
+ align = f' style="vertical-align: {-depth:d}px"' if depth is not None else ''
361
+ self.body.append(
362
+ f'<img class="math" src="{img_src}"{get_tooltip(self, node)}{align}/>'
363
+ )
347
364
  raise nodes.SkipNode
348
365
 
349
366
 
350
367
  def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> None:
351
- if node['nowrap']:
368
+ if node.get('no-wrap', node.get('nowrap', False)):
352
369
  latex = node.astext()
353
370
  else:
354
371
  latex = wrap_displaymath(node.astext(), None, False)
@@ -356,8 +373,9 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non
356
373
  rendered_path, depth = render_math(self, latex)
357
374
  except MathExtError as exc:
358
375
  msg = str(exc)
359
- sm = nodes.system_message(msg, type='WARNING', level=2,
360
- backrefs=[], source=node.astext())
376
+ sm = nodes.system_message(
377
+ msg, type='WARNING', level=2, backrefs=[], source=node.astext()
378
+ )
361
379
  sm.walkabout(self)
362
380
  logger.warning(__('inline latex %r: %s'), node.astext(), msg)
363
381
  raise nodes.SkipNode from exc
@@ -371,39 +389,51 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non
371
389
 
372
390
  if rendered_path is None:
373
391
  # something failed -- use text-only as a bad substitute
374
- self.body.append('<span class="math">%s</span></p>\n</div>' %
375
- self.encode(node.astext()).strip())
392
+ self.body.append(
393
+ f'<span class="math">{self.encode(node.astext()).strip()}</span></p>\n</div>'
394
+ )
376
395
  else:
377
396
  if self.builder.config.imgmath_embed:
378
397
  image_format = self.builder.config.imgmath_image_format.lower()
379
398
  img_src = render_maths_to_base64(image_format, rendered_path)
380
399
  else:
381
- bname = path.basename(rendered_path)
382
- relative_path = path.join(self.builder.imgpath, 'math', bname)
383
- img_src = relative_path.replace(path.sep, '/')
384
- self.body.append(f'<img src="{img_src}"' + get_tooltip(self, node) +
385
- '/></p>\n</div>')
400
+ bname = os.path.basename(rendered_path)
401
+ relative_path = Path(self.builder.imgpath, 'math', bname)
402
+ img_src = relative_path.as_posix()
403
+ self.body.append(f'<img src="{img_src}"{get_tooltip(self, node)}/></p>\n</div>')
386
404
  raise nodes.SkipNode
387
405
 
388
406
 
389
407
  def setup(app: Sphinx) -> ExtensionMetadata:
390
- app.add_html_math_renderer('imgmath',
391
- (html_visit_math, None),
392
- (html_visit_displaymath, None))
393
-
394
- app.add_config_value('imgmath_image_format', 'png', 'html')
395
- app.add_config_value('imgmath_dvipng', 'dvipng', 'html')
396
- app.add_config_value('imgmath_dvisvgm', 'dvisvgm', 'html')
397
- app.add_config_value('imgmath_latex', 'latex', 'html')
398
- app.add_config_value('imgmath_use_preview', False, 'html')
399
- app.add_config_value('imgmath_dvipng_args',
400
- ['-gamma', '1.5', '-D', '110', '-bg', 'Transparent'],
401
- 'html')
402
- app.add_config_value('imgmath_dvisvgm_args', ['--no-fonts'], 'html')
403
- app.add_config_value('imgmath_latex_args', [], 'html')
404
- app.add_config_value('imgmath_latex_preamble', '', 'html')
405
- app.add_config_value('imgmath_add_tooltips', True, 'html')
406
- app.add_config_value('imgmath_font_size', 12, 'html')
407
- app.add_config_value('imgmath_embed', False, 'html', bool)
408
+ app.add_html_math_renderer(
409
+ 'imgmath',
410
+ inline_renderers=(html_visit_math, None),
411
+ block_renderers=(html_visit_displaymath, None),
412
+ )
413
+
414
+ app.add_config_value('imgmath_image_format', 'png', 'html', types=frozenset({str}))
415
+ app.add_config_value('imgmath_dvipng', 'dvipng', 'html', types=frozenset({str}))
416
+ app.add_config_value('imgmath_dvisvgm', 'dvisvgm', 'html', types=frozenset({str}))
417
+ app.add_config_value('imgmath_latex', 'latex', 'html', types=frozenset({str}))
418
+ app.add_config_value('imgmath_use_preview', False, 'html', types=frozenset({bool}))
419
+ app.add_config_value(
420
+ 'imgmath_dvipng_args',
421
+ ['-gamma', '1.5', '-D', '110', '-bg', 'Transparent'],
422
+ 'html',
423
+ types=frozenset({list}),
424
+ )
425
+ app.add_config_value(
426
+ 'imgmath_dvisvgm_args', ['--no-fonts'], 'html', types=frozenset({list, tuple})
427
+ )
428
+ app.add_config_value(
429
+ 'imgmath_latex_args', [], 'html', types=frozenset({list, tuple})
430
+ )
431
+ app.add_config_value('imgmath_latex_preamble', '', 'html', types=frozenset({str}))
432
+ app.add_config_value('imgmath_add_tooltips', True, 'html', types=frozenset({bool}))
433
+ app.add_config_value('imgmath_font_size', 12, 'html', types=frozenset({int}))
434
+ app.add_config_value('imgmath_embed', False, 'html', types=frozenset({bool}))
408
435
  app.connect('build-finished', clean_up_files)
409
- return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
436
+ return {
437
+ 'version': sphinx.__display_version__,
438
+ 'parallel_read_safe': True,
439
+ }