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
@@ -4,10 +4,9 @@ from __future__ import annotations
4
4
 
5
5
  import re
6
6
  from itertools import starmap
7
- from typing import TYPE_CHECKING, Any, cast
7
+ from typing import TYPE_CHECKING, cast
8
8
 
9
9
  from docutils import nodes
10
- from docutils.nodes import Element, Node
11
10
 
12
11
  from sphinx import addnodes
13
12
  from sphinx.errors import NoUri
@@ -19,6 +18,9 @@ from sphinx.util.nodes import find_pending_xref_condition, process_only_nodes
19
18
 
20
19
  if TYPE_CHECKING:
21
20
  from collections.abc import Sequence
21
+ from typing import Any
22
+
23
+ from docutils.nodes import Element, Node
22
24
 
23
25
  from sphinx.addnodes import pending_xref
24
26
  from sphinx.application import Sphinx
@@ -58,9 +60,7 @@ class SphinxPostTransform(SphinxTransform):
58
60
 
59
61
 
60
62
  class ReferencesResolver(SphinxPostTransform):
61
- """
62
- Resolves cross-references on doctrees.
63
- """
63
+ """Resolves cross-references on doctrees."""
64
64
 
65
65
  default_priority = 10
66
66
 
@@ -68,105 +68,158 @@ class ReferencesResolver(SphinxPostTransform):
68
68
  for node in self.document.findall(addnodes.pending_xref):
69
69
  content = self.find_pending_xref_condition(node, ('resolved', '*'))
70
70
  if content:
71
- contnode = cast(Element, content[0].deepcopy())
71
+ contnode = cast('Element', content[0].deepcopy())
72
72
  else:
73
- contnode = cast(Element, node[0].deepcopy())
74
-
75
- newnode = None
73
+ contnode = cast('Element', node[0].deepcopy())
76
74
 
77
- typ = node['reftype']
78
- target = node['reftarget']
79
- node.setdefault('refdoc', self.env.docname)
80
- refdoc = node.get('refdoc')
81
- domain = None
82
-
83
- try:
84
- if node.get('refdomain', False):
85
- # let the domain try to resolve the reference
86
- try:
87
- domain = self.env.domains[node['refdomain']]
88
- except KeyError as exc:
89
- raise NoUri(target, typ) from exc
90
- newnode = domain.resolve_xref(
91
- self.env, refdoc, self.app.builder, typ, target, node, contnode
92
- )
93
- # really hardwired reference types
94
- elif typ == 'any':
95
- newnode = self.resolve_anyref(refdoc, node, contnode)
96
- # no new node found? try the missing-reference event
97
- if newnode is None:
98
- newnode = self.app.emit_firstresult(
99
- 'missing-reference',
100
- self.env,
101
- node,
102
- contnode,
103
- allowed_exceptions=(NoUri,),
104
- )
105
- # still not found? warn if node wishes to be warned about or
106
- # we are in nitpicky mode
107
- if newnode is None:
108
- self.warn_missing_reference(refdoc, typ, target, node, domain)
109
- except NoUri:
110
- newnode = None
111
-
112
- if newnode:
113
- newnodes: list[Node] = [newnode]
75
+ new_node = self._resolve_pending_xref(node, contnode)
76
+ if new_node:
77
+ new_nodes: list[Node] = [new_node]
114
78
  else:
115
- newnodes = [contnode]
116
- if newnode is None and isinstance(
79
+ new_nodes = [contnode]
80
+ if new_node is None and isinstance(
117
81
  node[0], addnodes.pending_xref_condition
118
82
  ):
119
83
  matched = self.find_pending_xref_condition(node, ('*',))
120
84
  if matched:
121
- newnodes = matched
85
+ new_nodes = matched
122
86
  else:
123
- logger.warning(
124
- __(
125
- 'Could not determine the fallback text for the '
126
- 'cross-reference. Might be a bug.'
127
- ),
128
- location=node,
87
+ msg = __(
88
+ 'Could not determine the fallback text for the '
89
+ 'cross-reference. Might be a bug.'
129
90
  )
91
+ logger.warning(msg, location=node)
92
+
93
+ node.replace_self(new_nodes)
94
+
95
+ def _resolve_pending_xref(
96
+ self, node: addnodes.pending_xref, contnode: Element
97
+ ) -> nodes.reference | None:
98
+ new_node: nodes.reference | None
99
+ typ = node['reftype']
100
+ target = node['reftarget']
101
+ ref_doc = node.setdefault('refdoc', self.env.docname)
102
+ ref_domain = node.get('refdomain', '')
103
+ domain: Domain | None
104
+ if ref_domain:
105
+ try:
106
+ domain = self.env.domains[ref_domain]
107
+ except KeyError:
108
+ return None
109
+ else:
110
+ domain = None
111
+
112
+ try:
113
+ new_node = self._resolve_pending_xref_in_domain(
114
+ domain=domain,
115
+ node=node,
116
+ contnode=contnode,
117
+ ref_doc=ref_doc,
118
+ typ=typ,
119
+ target=target,
120
+ )
121
+ except NoUri:
122
+ return None
123
+ if new_node is not None:
124
+ return new_node
125
+
126
+ try:
127
+ # no new node found? try the missing-reference event
128
+ new_node = self.app.events.emit_firstresult(
129
+ 'missing-reference',
130
+ self.env,
131
+ node,
132
+ contnode,
133
+ allowed_exceptions=(NoUri,),
134
+ )
135
+ except NoUri:
136
+ return None
137
+ if new_node is not None:
138
+ return new_node
130
139
 
131
- node.replace_self(newnodes)
140
+ # Is this a self-referential intersphinx reference?
141
+ if 'intersphinx_self_referential' in node:
142
+ del node.attributes['intersphinx_self_referential']
143
+ try:
144
+ new_node = self._resolve_pending_xref_in_domain(
145
+ domain=domain,
146
+ node=node,
147
+ contnode=contnode,
148
+ ref_doc=ref_doc,
149
+ typ=typ,
150
+ target=node['reftarget'],
151
+ )
152
+ except NoUri:
153
+ return None
154
+ if new_node is not None:
155
+ return new_node
132
156
 
133
- def resolve_anyref(
157
+ # Still not found? Emit a warning if we are in nitpicky mode
158
+ # or if the node wishes to be warned about.
159
+ self.warn_missing_reference(ref_doc, typ, target, node, domain)
160
+ return None
161
+
162
+ def _resolve_pending_xref_in_domain(
134
163
  self,
135
- refdoc: str,
136
- node: pending_xref,
164
+ *,
165
+ domain: Domain | None,
166
+ node: addnodes.pending_xref,
137
167
  contnode: Element,
138
- ) -> Element | None:
168
+ ref_doc: str,
169
+ typ: str,
170
+ target: str,
171
+ ) -> nodes.reference | None:
172
+ # let the domain try to resolve the reference
173
+ if domain is not None:
174
+ return domain.resolve_xref(
175
+ self.env, ref_doc, self.app.builder, typ, target, node, contnode
176
+ )
177
+
178
+ # really hardwired reference types
179
+ if typ == 'any':
180
+ return self._resolve_pending_any_xref(
181
+ node=node, contnode=contnode, ref_doc=ref_doc, target=target
182
+ )
183
+
184
+ return None
185
+
186
+ def _resolve_pending_any_xref(
187
+ self,
188
+ *,
189
+ node: addnodes.pending_xref,
190
+ contnode: Element,
191
+ ref_doc: str,
192
+ target: str,
193
+ ) -> nodes.reference | None:
139
194
  """Resolve reference generated by the "any" role."""
140
- stddomain = self.env.domains.standard_domain
141
- target = node['reftarget']
142
- results: list[tuple[str, Element]] = []
195
+ env = self.env
196
+ builder = self.app.builder
197
+ domains = env.domains
198
+
199
+ results: list[tuple[str, nodes.reference]] = []
143
200
  # first, try resolving as :doc:
144
- doc_ref = stddomain.resolve_xref(
145
- self.env, refdoc, self.app.builder, 'doc', target, node, contnode
201
+ doc_ref = domains.standard_domain.resolve_xref(
202
+ env, ref_doc, builder, 'doc', target, node, contnode
146
203
  )
147
204
  if doc_ref:
148
205
  results.append(('doc', doc_ref))
149
206
  # next, do the standard domain (makes this a priority)
150
- results.extend(
151
- stddomain.resolve_any_xref(
152
- self.env, refdoc, self.app.builder, target, node, contnode
153
- )
207
+ results += domains.standard_domain.resolve_any_xref(
208
+ env, ref_doc, builder, target, node, contnode
154
209
  )
155
- for domain in self.env.domains.sorted():
210
+ for domain in domains.sorted():
156
211
  if domain.name == 'std':
157
212
  continue # we did this one already
158
213
  try:
159
- results.extend(
160
- domain.resolve_any_xref(
161
- self.env, refdoc, self.app.builder, target, node, contnode
162
- )
214
+ results += domain.resolve_any_xref(
215
+ env, ref_doc, builder, target, node, contnode
163
216
  )
164
217
  except NotImplementedError:
165
218
  # the domain doesn't yet support the new interface
166
219
  # we have to manually collect possible references (SLOW)
167
220
  for role in domain.roles:
168
221
  res = domain.resolve_xref(
169
- self.env, refdoc, self.app.builder, role, target, node, contnode
222
+ env, ref_doc, builder, role, target, node, contnode
170
223
  )
171
224
  if res and len(res) > 0 and isinstance(res[0], nodes.Element):
172
225
  results.append((f'{domain.name}:{role}', res))
@@ -180,27 +233,23 @@ class ReferencesResolver(SphinxPostTransform):
180
233
  return f':{name}:`{reftitle}`'
181
234
 
182
235
  candidates = ' or '.join(starmap(stringify, results))
236
+ msg = __(
237
+ "more than one target found for 'any' cross-reference %r: could be %s"
238
+ )
183
239
  logger.warning(
184
- __(
185
- "more than one target found for 'any' cross-"
186
- 'reference %r: could be %s'
187
- ),
188
- target,
189
- candidates,
190
- location=node,
240
+ msg, target, candidates, location=node, type='ref', subtype='any'
191
241
  )
192
- res_role, newnode = results[0]
242
+ res_role, new_node = results[0]
193
243
  # Override "any" class with the actual role type to get the styling
194
244
  # approximately correct.
195
- res_domain = res_role.split(':')[0]
245
+ res_domain = res_role.partition(':')[0]
196
246
  if (
197
- len(newnode) > 0
198
- and isinstance(newnode[0], nodes.Element)
199
- and newnode[0].get('classes')
247
+ len(new_node) > 0
248
+ and isinstance(new_node[0], nodes.Element)
249
+ and new_node[0].get('classes')
200
250
  ):
201
- newnode[0]['classes'].append(res_domain)
202
- newnode[0]['classes'].append(res_role.replace(':', '-'))
203
- return newnode
251
+ new_node[0]['classes'].extend((res_domain, res_role.replace(':', '-')))
252
+ return new_node
204
253
 
205
254
  def warn_missing_reference(
206
255
  self,
@@ -242,11 +291,11 @@ class ReferencesResolver(SphinxPostTransform):
242
291
  if not warn:
243
292
  return
244
293
 
245
- if self.app.emit_firstresult('warn-missing-reference', domain, node):
294
+ if self.app.events.emit_firstresult('warn-missing-reference', domain, node):
246
295
  return
247
296
  elif domain and typ in domain.dangling_warnings:
248
297
  msg = domain.dangling_warnings[typ] % {'target': target}
249
- elif node.get('refdomain', 'std') not in ('', 'std'):
298
+ elif node.get('refdomain', 'std') not in {'', 'std'}:
250
299
  msg = __('%s:%s reference target not found: %s') % (
251
300
  node['refdomain'],
252
301
  typ,
@@ -276,7 +325,7 @@ class OnlyNodeTransform(SphinxPostTransform):
276
325
  # result in a "Losing ids" exception if there is a target node before
277
326
  # the only node, so we make sure docutils can transfer the id to
278
327
  # something, even if it's just a comment and will lose the id anyway...
279
- process_only_nodes(self.document, self.app.builder.tags)
328
+ process_only_nodes(self.document, self.app.tags)
280
329
 
281
330
 
282
331
  class SigElementFallbackTransform(SphinxPostTransform):
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import sys
6
- from typing import TYPE_CHECKING, Any, NamedTuple
6
+ from typing import TYPE_CHECKING, NamedTuple
7
7
 
8
8
  from docutils import nodes
9
9
  from pygments.lexers import PythonConsoleLexer, guess_lexer
@@ -13,6 +13,8 @@ from sphinx.ext import doctest
13
13
  from sphinx.transforms import SphinxTransform
14
14
 
15
15
  if TYPE_CHECKING:
16
+ from typing import Any
17
+
16
18
  from docutils.nodes import Node, TextElement
17
19
 
18
20
  from sphinx.application import Sphinx
@@ -26,8 +28,7 @@ class HighlightSetting(NamedTuple):
26
28
 
27
29
 
28
30
  class HighlightLanguageTransform(SphinxTransform):
29
- """
30
- Apply highlight_language to all literal_block nodes.
31
+ """Apply highlight_language to all literal_block nodes.
31
32
 
32
33
  This refers both :confval:`highlight_language` setting and
33
34
  :rst:dir:`highlight` directive. After processing, this transform
@@ -86,8 +87,7 @@ class HighlightLanguageVisitor(nodes.NodeVisitor):
86
87
 
87
88
 
88
89
  class TrimDoctestFlagsTransform(SphinxTransform):
89
- """
90
- Trim doctest flags like ``# doctest: +FLAG`` from python code-blocks.
90
+ """Trim doctest flags like ``# doctest: +FLAG`` from python code-blocks.
91
91
 
92
92
  see :confval:`trim_doctest_flags` for more information.
93
93
  """
@@ -7,7 +7,7 @@ import re
7
7
  from hashlib import sha1
8
8
  from math import ceil
9
9
  from pathlib import Path
10
- from typing import TYPE_CHECKING, Any
10
+ from typing import TYPE_CHECKING
11
11
 
12
12
  from docutils import nodes
13
13
 
@@ -20,6 +20,8 @@ from sphinx.util.images import get_image_extension, guess_mimetype, parse_data_u
20
20
  from sphinx.util.osutil import ensuredir
21
21
 
22
22
  if TYPE_CHECKING:
23
+ from typing import Any
24
+
23
25
  from sphinx.application import Sphinx
24
26
  from sphinx.util.typing import ExtensionMetadata
25
27
 
@@ -42,8 +44,8 @@ class BaseImageConverter(SphinxTransform):
42
44
  pass
43
45
 
44
46
  @property
45
- def imagedir(self) -> str:
46
- return os.path.join(self.app.doctreedir, 'images')
47
+ def imagedir(self) -> _StrPath:
48
+ return self.app.doctreedir / 'images'
47
49
 
48
50
 
49
51
  class ImageDownloader(BaseImageConverter):
@@ -61,7 +63,7 @@ class ImageDownloader(BaseImageConverter):
61
63
  basename = os.path.basename(node['uri'])
62
64
  if '?' in basename:
63
65
  basename = basename.split('?')[0]
64
- if basename == '' or len(basename) > MAX_FILENAME_LEN:
66
+ if not basename or len(basename) > MAX_FILENAME_LEN:
65
67
  filename, ext = os.path.splitext(node['uri'])
66
68
  basename = (
67
69
  sha1(filename.encode(), usedforsecurity=False).hexdigest() + ext
@@ -83,7 +85,7 @@ class ImageDownloader(BaseImageConverter):
83
85
  timestamp: float = ceil(path.stat().st_mtime)
84
86
  headers['If-Modified-Since'] = epoch_to_rfc1123(timestamp)
85
87
 
86
- config = self.app.config
88
+ config = self.config
87
89
  r = requests.get(
88
90
  node['uri'],
89
91
  headers=headers,
@@ -94,7 +96,7 @@ class ImageDownloader(BaseImageConverter):
94
96
  msg = __('Could not fetch remote image: %s [%d]')
95
97
  logger.warning(msg, node['uri'], r.status_code)
96
98
  else:
97
- self.app.env.original_image_uri[_StrPath(path)] = node['uri']
99
+ self.env.original_image_uri[_StrPath(path)] = node['uri']
98
100
 
99
101
  if r.status_code == 200:
100
102
  path.write_bytes(r.content)
@@ -106,22 +108,22 @@ class ImageDownloader(BaseImageConverter):
106
108
 
107
109
  def _process_image(self, node: nodes.image, path: Path) -> None:
108
110
  str_path = _StrPath(path)
109
- self.app.env.original_image_uri[str_path] = node['uri']
111
+ self.env.original_image_uri[str_path] = node['uri']
110
112
 
111
113
  mimetype = guess_mimetype(path, default='*')
112
- if mimetype != '*' and path.suffix == '':
114
+ if mimetype != '*' and not path.suffix:
113
115
  # append a suffix if URI does not contain suffix
114
116
  ext = get_image_extension(mimetype) or ''
115
117
  with_ext = path.with_name(path.name + ext)
116
- os.replace(path, with_ext)
117
- self.app.env.original_image_uri.pop(str_path)
118
- self.app.env.original_image_uri[_StrPath(with_ext)] = node['uri']
118
+ path.replace(with_ext)
119
+ self.env.original_image_uri.pop(str_path)
120
+ self.env.original_image_uri[_StrPath(with_ext)] = node['uri']
119
121
  path = with_ext
120
122
  path_str = str(path)
121
123
  node['candidates'].pop('?')
122
124
  node['candidates'][mimetype] = path_str
123
125
  node['uri'] = path_str
124
- self.app.env.images.add_file(self.env.docname, path_str)
126
+ self.env.images.add_file(self.env.docname, path_str)
125
127
 
126
128
 
127
129
  class DataURIExtractor(BaseImageConverter):
@@ -142,10 +144,10 @@ class DataURIExtractor(BaseImageConverter):
142
144
  )
143
145
  return
144
146
 
145
- ensuredir(os.path.join(self.imagedir, 'embeded'))
147
+ ensuredir(self.imagedir / 'embeded')
146
148
  digest = sha1(image.data, usedforsecurity=False).hexdigest()
147
- path = _StrPath(self.imagedir, 'embeded', digest + ext)
148
- self.app.env.original_image_uri[path] = node['uri']
149
+ path = self.imagedir / 'embeded' / (digest + ext)
150
+ self.env.original_image_uri[path] = node['uri']
149
151
 
150
152
  with open(path, 'wb') as f:
151
153
  f.write(image.data)
@@ -154,7 +156,7 @@ class DataURIExtractor(BaseImageConverter):
154
156
  node['candidates'].pop('?')
155
157
  node['candidates'][image.mimetype] = path_str
156
158
  node['uri'] = path_str
157
- self.app.env.images.add_file(self.env.docname, path_str)
159
+ self.env.images.add_file(self.env.docname, path_str)
158
160
 
159
161
 
160
162
  def get_filename_for(filename: str, mimetype: str) -> str:
@@ -248,7 +250,7 @@ class ImageConverter(BaseImageConverter):
248
250
  if '?' in node['candidates']:
249
251
  return []
250
252
  elif '*' in node['candidates']:
251
- path = os.path.join(self.app.srcdir, node['uri'])
253
+ path = self.app.srcdir / node['uri']
252
254
  guessed = guess_mimetype(path)
253
255
  return [guessed] if guessed is not None else []
254
256
  else:
@@ -265,20 +267,22 @@ class ImageConverter(BaseImageConverter):
265
267
  filename = self.env.images[srcpath][1]
266
268
  filename = get_filename_for(filename, _to)
267
269
  ensuredir(self.imagedir)
268
- destpath = os.path.join(self.imagedir, filename)
270
+ destpath = self.imagedir / filename
269
271
 
270
- abs_srcpath = os.path.join(self.app.srcdir, srcpath)
272
+ abs_srcpath = self.app.srcdir / srcpath
271
273
  if self.convert(abs_srcpath, destpath):
272
274
  if '*' in node['candidates']:
273
- node['candidates']['*'] = destpath
275
+ node['candidates']['*'] = str(destpath)
274
276
  else:
275
- node['candidates'][_to] = destpath
276
- node['uri'] = destpath
277
+ node['candidates'][_to] = str(destpath)
278
+ node['uri'] = str(destpath)
277
279
 
278
- self.env.original_image_uri[_StrPath(destpath)] = srcpath
280
+ self.env.original_image_uri[destpath] = srcpath
279
281
  self.env.images.add_file(self.env.docname, destpath)
280
282
 
281
- def convert(self, _from: str, _to: str) -> bool:
283
+ def convert(
284
+ self, _from: str | os.PathLike[str], _to: str | os.PathLike[str]
285
+ ) -> bool:
282
286
  """Convert an image file to the expected format.
283
287
 
284
288
  *_from* is a path of the source image file, and *_to* is a path
@@ -2,13 +2,15 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any
5
+ from typing import TYPE_CHECKING
6
6
 
7
7
  from docutils.transforms.references import DanglingReferences
8
8
 
9
9
  from sphinx.transforms import SphinxTransform
10
10
 
11
11
  if TYPE_CHECKING:
12
+ from typing import Any
13
+
12
14
  from sphinx.application import Sphinx
13
15
  from sphinx.util.typing import ExtensionMetadata
14
16