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.

Files changed (313) hide show
  1. sphinx/__init__.py +6 -6
  2. sphinx/__main__.py +3 -1
  3. sphinx/addnodes.py +35 -22
  4. sphinx/application.py +40 -38
  5. sphinx/builders/__init__.py +16 -12
  6. sphinx/builders/_epub_base.py +15 -11
  7. sphinx/builders/changes.py +6 -4
  8. sphinx/builders/dirhtml.py +4 -2
  9. sphinx/builders/dummy.py +6 -4
  10. sphinx/builders/epub3.py +16 -8
  11. sphinx/builders/gettext.py +40 -43
  12. sphinx/builders/html/__init__.py +166 -196
  13. sphinx/builders/html/_assets.py +116 -0
  14. sphinx/builders/html/transforms.py +4 -2
  15. sphinx/builders/latex/__init__.py +12 -7
  16. sphinx/builders/latex/theming.py +5 -2
  17. sphinx/builders/latex/transforms.py +6 -3
  18. sphinx/builders/linkcheck.py +18 -11
  19. sphinx/builders/manpage.py +6 -4
  20. sphinx/builders/singlehtml.py +16 -9
  21. sphinx/builders/texinfo.py +11 -6
  22. sphinx/builders/text.py +8 -3
  23. sphinx/builders/xml.py +9 -4
  24. sphinx/cmd/build.py +27 -14
  25. sphinx/cmd/make_mode.py +13 -4
  26. sphinx/cmd/quickstart.py +13 -4
  27. sphinx/config.py +17 -14
  28. sphinx/deprecation.py +4 -2
  29. sphinx/directives/__init__.py +44 -12
  30. sphinx/directives/code.py +5 -4
  31. sphinx/directives/other.py +92 -44
  32. sphinx/directives/patches.py +1 -1
  33. sphinx/domains/__init__.py +11 -8
  34. sphinx/domains/c.py +67 -57
  35. sphinx/domains/changeset.py +3 -2
  36. sphinx/domains/citation.py +2 -1
  37. sphinx/domains/cpp.py +136 -93
  38. sphinx/domains/index.py +9 -5
  39. sphinx/domains/javascript.py +32 -19
  40. sphinx/domains/math.py +5 -3
  41. sphinx/domains/python.py +69 -57
  42. sphinx/domains/rst.py +20 -11
  43. sphinx/domains/std.py +21 -15
  44. sphinx/environment/__init__.py +97 -65
  45. sphinx/environment/adapters/indexentries.py +13 -10
  46. sphinx/environment/adapters/toctree.py +485 -308
  47. sphinx/environment/collectors/__init__.py +3 -4
  48. sphinx/environment/collectors/asset.py +10 -4
  49. sphinx/environment/collectors/dependencies.py +7 -4
  50. sphinx/environment/collectors/metadata.py +7 -5
  51. sphinx/environment/collectors/title.py +5 -3
  52. sphinx/environment/collectors/toctree.py +13 -8
  53. sphinx/errors.py +1 -1
  54. sphinx/events.py +5 -5
  55. sphinx/ext/apidoc.py +49 -27
  56. sphinx/ext/autodoc/__init__.py +179 -161
  57. sphinx/ext/autodoc/directive.py +10 -6
  58. sphinx/ext/autodoc/importer.py +22 -13
  59. sphinx/ext/autodoc/mock.py +4 -1
  60. sphinx/ext/autodoc/preserve_defaults.py +80 -12
  61. sphinx/ext/autodoc/type_comment.py +14 -10
  62. sphinx/ext/autodoc/typehints.py +7 -3
  63. sphinx/ext/autosectionlabel.py +6 -3
  64. sphinx/ext/autosummary/__init__.py +21 -15
  65. sphinx/ext/autosummary/generate.py +176 -126
  66. sphinx/ext/coverage.py +93 -8
  67. sphinx/ext/doctest.py +28 -17
  68. sphinx/ext/duration.py +19 -17
  69. sphinx/ext/extlinks.py +11 -6
  70. sphinx/ext/githubpages.py +8 -7
  71. sphinx/ext/graphviz.py +61 -17
  72. sphinx/ext/ifconfig.py +7 -4
  73. sphinx/ext/imgconverter.py +4 -2
  74. sphinx/ext/imgmath.py +29 -23
  75. sphinx/ext/inheritance_diagram.py +41 -27
  76. sphinx/ext/intersphinx.py +45 -38
  77. sphinx/ext/linkcode.py +8 -5
  78. sphinx/ext/mathjax.py +13 -9
  79. sphinx/ext/napoleon/__init__.py +3 -3
  80. sphinx/ext/napoleon/docstring.py +40 -31
  81. sphinx/ext/todo.py +10 -7
  82. sphinx/ext/viewcode.py +46 -25
  83. sphinx/extension.py +1 -1
  84. sphinx/highlighting.py +20 -12
  85. sphinx/io.py +5 -4
  86. sphinx/jinja2glue.py +24 -19
  87. sphinx/locale/__init__.py +8 -2
  88. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  89. sphinx/locale/ar/LC_MESSAGES/sphinx.po +756 -740
  90. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  91. sphinx/locale/bg/LC_MESSAGES/sphinx.po +754 -738
  92. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  93. sphinx/locale/bn/LC_MESSAGES/sphinx.po +755 -739
  94. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  95. sphinx/locale/ca/LC_MESSAGES/sphinx.po +768 -752
  96. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  97. sphinx/locale/cak/LC_MESSAGES/sphinx.po +754 -738
  98. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/cs/LC_MESSAGES/sphinx.po +758 -742
  100. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  101. sphinx/locale/cy/LC_MESSAGES/sphinx.po +759 -743
  102. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/da/LC_MESSAGES/sphinx.po +760 -744
  104. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/de/LC_MESSAGES/sphinx.po +759 -743
  106. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  107. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +754 -738
  108. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/el/LC_MESSAGES/sphinx.po +763 -747
  110. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +754 -738
  112. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  113. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +754 -738
  114. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +768 -752
  116. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +754 -738
  118. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  119. sphinx/locale/eo/LC_MESSAGES/sphinx.po +754 -738
  120. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/es/LC_MESSAGES/sphinx.po +767 -751
  122. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +754 -738
  124. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  125. sphinx/locale/et/LC_MESSAGES/sphinx.po +762 -746
  126. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/eu/LC_MESSAGES/sphinx.po +755 -739
  128. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/fa/LC_MESSAGES/sphinx.po +766 -750
  130. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  131. sphinx/locale/fi/LC_MESSAGES/sphinx.po +754 -738
  132. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/fr/LC_MESSAGES/sphinx.po +768 -752
  134. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +754 -738
  136. sphinx/locale/gl/LC_MESSAGES/sphinx.js +60 -0
  137. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  138. sphinx/locale/gl/LC_MESSAGES/sphinx.po +3695 -0
  139. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  140. sphinx/locale/he/LC_MESSAGES/sphinx.po +755 -739
  141. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  142. sphinx/locale/hi/LC_MESSAGES/sphinx.po +763 -747
  143. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +754 -738
  145. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  146. sphinx/locale/hr/LC_MESSAGES/sphinx.po +760 -744
  147. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  148. sphinx/locale/hu/LC_MESSAGES/sphinx.po +759 -743
  149. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  150. sphinx/locale/id/LC_MESSAGES/sphinx.po +765 -749
  151. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  152. sphinx/locale/is/LC_MESSAGES/sphinx.po +760 -744
  153. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/it/LC_MESSAGES/sphinx.po +760 -744
  155. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/ja/LC_MESSAGES/sphinx.po +767 -751
  157. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  158. sphinx/locale/ka/LC_MESSAGES/sphinx.po +759 -743
  159. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/ko/LC_MESSAGES/sphinx.po +767 -751
  161. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/lt/LC_MESSAGES/sphinx.po +755 -739
  163. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/lv/LC_MESSAGES/sphinx.po +755 -739
  165. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/mk/LC_MESSAGES/sphinx.po +754 -738
  167. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +755 -739
  169. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/ne/LC_MESSAGES/sphinx.po +755 -739
  171. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/nl/LC_MESSAGES/sphinx.po +760 -744
  173. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/pl/LC_MESSAGES/sphinx.po +762 -745
  175. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/pt/LC_MESSAGES/sphinx.po +754 -738
  177. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +768 -752
  179. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  180. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +755 -739
  181. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  182. sphinx/locale/ro/LC_MESSAGES/sphinx.po +759 -743
  183. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  184. sphinx/locale/ru/LC_MESSAGES/sphinx.po +760 -744
  185. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  186. sphinx/locale/si/LC_MESSAGES/sphinx.po +754 -738
  187. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  188. sphinx/locale/sk/LC_MESSAGES/sphinx.po +765 -749
  189. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/sl/LC_MESSAGES/sphinx.po +755 -739
  191. sphinx/locale/sphinx.pot +748 -740
  192. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  193. sphinx/locale/sq/LC_MESSAGES/sphinx.po +768 -752
  194. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  195. sphinx/locale/sr/LC_MESSAGES/sphinx.po +754 -738
  196. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
  197. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.po +754 -738
  198. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
  199. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.po +754 -738
  200. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  201. sphinx/locale/sv/LC_MESSAGES/sphinx.po +755 -739
  202. sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
  203. sphinx/locale/ta/LC_MESSAGES/sphinx.po +754 -738
  204. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  205. sphinx/locale/te/LC_MESSAGES/sphinx.po +754 -738
  206. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/tr/LC_MESSAGES/sphinx.po +763 -747
  208. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  209. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +760 -749
  210. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/ur/LC_MESSAGES/sphinx.po +759 -748
  212. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/vi/LC_MESSAGES/sphinx.po +754 -738
  214. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  215. sphinx/locale/yue/LC_MESSAGES/sphinx.po +754 -738
  216. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.mo +0 -0
  217. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +768 -752
  218. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  219. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +754 -738
  220. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  221. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +767 -751
  222. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  223. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +754 -738
  224. sphinx/parsers.py +5 -4
  225. sphinx/project.py +52 -34
  226. sphinx/pycode/__init__.py +2 -1
  227. sphinx/pycode/ast.py +7 -13
  228. sphinx/pycode/parser.py +42 -38
  229. sphinx/registry.py +35 -29
  230. sphinx/roles.py +9 -4
  231. sphinx/search/__init__.py +5 -17
  232. sphinx/search/da.py +1 -1
  233. sphinx/search/de.py +1 -1
  234. sphinx/search/en.py +1 -1
  235. sphinx/search/es.py +1 -1
  236. sphinx/search/fi.py +1 -1
  237. sphinx/search/fr.py +1 -1
  238. sphinx/search/hu.py +1 -1
  239. sphinx/search/it.py +1 -1
  240. sphinx/search/ja.py +1 -1
  241. sphinx/search/nl.py +1 -1
  242. sphinx/search/no.py +1 -1
  243. sphinx/search/pt.py +1 -1
  244. sphinx/search/ro.py +1 -1
  245. sphinx/search/ru.py +1 -1
  246. sphinx/search/sv.py +1 -1
  247. sphinx/search/tr.py +1 -1
  248. sphinx/search/zh.py +1 -1
  249. sphinx/testing/fixtures.py +23 -30
  250. sphinx/testing/path.py +9 -0
  251. sphinx/testing/restructuredtext.py +13 -5
  252. sphinx/testing/util.py +20 -63
  253. sphinx/texinputs/sphinxlatexobjects.sty +15 -15
  254. sphinx/themes/agogo/static/agogo.css_t +10 -4
  255. sphinx/themes/basic/layout.html +1 -1
  256. sphinx/themes/basic/static/basic.css_t +4 -0
  257. sphinx/themes/basic/static/documentation_options.js_t +1 -2
  258. sphinx/themes/basic/static/searchtools.js +17 -9
  259. sphinx/themes/basic/static/sphinx_highlight.js +13 -3
  260. sphinx/themes/bizstyle/static/bizstyle.css_t +4 -0
  261. sphinx/themes/classic/theme.conf +1 -1
  262. sphinx/themes/epub/static/epub.css_t +6 -1
  263. sphinx/themes/haiku/theme.conf +1 -1
  264. sphinx/themes/nature/static/nature.css_t +4 -0
  265. sphinx/themes/nonav/static/nonav.css_t +6 -1
  266. sphinx/themes/pyramid/static/pyramid.css_t +4 -0
  267. sphinx/themes/scrolls/static/scrolls.css_t +4 -0
  268. sphinx/themes/scrolls/theme.conf +1 -1
  269. sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +4 -0
  270. sphinx/theming.py +9 -7
  271. sphinx/transforms/__init__.py +79 -3
  272. sphinx/transforms/compact_bullet_list.py +6 -3
  273. sphinx/transforms/i18n.py +26 -10
  274. sphinx/transforms/post_transforms/__init__.py +21 -8
  275. sphinx/transforms/post_transforms/code.py +6 -3
  276. sphinx/transforms/post_transforms/images.py +13 -9
  277. sphinx/util/__init__.py +21 -92
  278. sphinx/util/cfamily.py +7 -4
  279. sphinx/util/display.py +3 -2
  280. sphinx/util/docfields.py +7 -6
  281. sphinx/util/docstrings.py +1 -1
  282. sphinx/util/docutils.py +41 -31
  283. sphinx/util/fileutil.py +9 -6
  284. sphinx/util/i18n.py +21 -18
  285. sphinx/util/images.py +2 -1
  286. sphinx/util/index_entries.py +27 -0
  287. sphinx/util/inspect.py +83 -67
  288. sphinx/util/inventory.py +4 -2
  289. sphinx/util/logging.py +9 -6
  290. sphinx/util/matching.py +5 -2
  291. sphinx/util/math.py +6 -3
  292. sphinx/util/nodes.py +70 -31
  293. sphinx/util/osutil.py +22 -40
  294. sphinx/util/parallel.py +4 -1
  295. sphinx/util/rst.py +7 -3
  296. sphinx/util/tags.py +11 -4
  297. sphinx/util/template.py +17 -14
  298. sphinx/util/typing.py +61 -20
  299. sphinx/versioning.py +6 -4
  300. sphinx/writers/html.py +1 -1
  301. sphinx/writers/html5.py +32 -24
  302. sphinx/writers/latex.py +67 -53
  303. sphinx/writers/manpage.py +9 -5
  304. sphinx/writers/texinfo.py +11 -9
  305. sphinx/writers/text.py +14 -9
  306. sphinx/writers/xml.py +3 -2
  307. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/METADATA +7 -5
  308. sphinx-7.2.0.dist-info/RECORD +568 -0
  309. sphinx/testing/comparer.py +0 -97
  310. sphinx-7.1.2.dist-info/RECORD +0 -564
  311. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/LICENSE +0 -0
  312. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/WHEEL +0 -0
  313. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/entry_points.txt +0 -0
@@ -2,16 +2,17 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import contextlib
6
+ import hashlib
5
7
  import html
6
8
  import os
7
9
  import posixpath
8
10
  import re
9
11
  import sys
12
+ import time
10
13
  import warnings
11
- import zlib
12
- from datetime import datetime, timezone
13
14
  from os import path
14
- from typing import IO, Any, Iterable, Iterator, List, Tuple, Type
15
+ from typing import IO, TYPE_CHECKING, Any
15
16
  from urllib.parse import quote
16
17
 
17
18
  import docutils.readers.doctree
@@ -19,49 +20,56 @@ from docutils import nodes
19
20
  from docutils.core import Publisher
20
21
  from docutils.frontend import OptionParser
21
22
  from docutils.io import DocTreeInput, StringOutput
22
- from docutils.nodes import Node
23
23
  from docutils.utils import relative_path
24
24
 
25
25
  from sphinx import __display_version__, package_dir
26
26
  from sphinx import version_info as sphinx_version
27
- from sphinx.application import Sphinx
28
27
  from sphinx.builders import Builder
28
+ from sphinx.builders.html._assets import _CascadingStyleSheet, _file_checksum, _JavaScript
29
29
  from sphinx.config import ENUM, Config
30
+ from sphinx.deprecation import _deprecation_warning
30
31
  from sphinx.domains import Domain, Index, IndexEntry
31
- from sphinx.environment import BuildEnvironment
32
32
  from sphinx.environment.adapters.asset import ImageAdapter
33
33
  from sphinx.environment.adapters.indexentries import IndexEntries
34
- from sphinx.environment.adapters.toctree import TocTree
34
+ from sphinx.environment.adapters.toctree import document_toc, global_toctree_for_doc
35
35
  from sphinx.errors import ConfigError, ThemeError
36
36
  from sphinx.highlighting import PygmentsBridge
37
37
  from sphinx.locale import _, __
38
38
  from sphinx.search import js_index
39
39
  from sphinx.theming import HTMLThemeFactory
40
- from sphinx.util import isurl, logging, md5
40
+ from sphinx.util import isurl, logging
41
41
  from sphinx.util.display import progress_message, status_iterator
42
42
  from sphinx.util.docutils import new_document
43
43
  from sphinx.util.fileutil import copy_asset
44
44
  from sphinx.util.i18n import format_date
45
45
  from sphinx.util.inventory import InventoryFile
46
46
  from sphinx.util.matching import DOTFILES, Matcher, patmatch
47
- from sphinx.util.osutil import copyfile, ensuredir, os_path, relative_uri
48
- from sphinx.util.tags import Tags
47
+ from sphinx.util.osutil import SEP, copyfile, ensuredir, os_path, relative_uri
49
48
  from sphinx.writers.html import HTMLWriter
50
49
  from sphinx.writers.html5 import HTML5Translator
51
50
 
51
+ if TYPE_CHECKING:
52
+ from collections.abc import Iterable, Iterator, Sequence
53
+
54
+ from docutils.nodes import Node
55
+
56
+ from sphinx.application import Sphinx
57
+ from sphinx.environment import BuildEnvironment
58
+ from sphinx.util.tags import Tags
59
+
52
60
  #: the filename for the inventory of objects
53
61
  INVENTORY_FILENAME = 'objects.inv'
54
62
 
55
63
  logger = logging.getLogger(__name__)
56
64
  return_codes_re = re.compile('[\r\n]+')
57
65
 
58
- DOMAIN_INDEX_TYPE = Tuple[
66
+ DOMAIN_INDEX_TYPE = tuple[
59
67
  # Index name (e.g. py-modindex)
60
68
  str,
61
69
  # Index class
62
- Type[Index],
70
+ type[Index],
63
71
  # list of (heading string, list of index entries) pairs.
64
- List[Tuple[str, List[IndexEntry]]],
72
+ list[tuple[str, list[IndexEntry]]],
65
73
  # whether sub-entries should start collapsed
66
74
  bool,
67
75
  ]
@@ -77,7 +85,7 @@ def get_stable_hash(obj: Any) -> str:
77
85
  return get_stable_hash(list(obj.items()))
78
86
  elif isinstance(obj, (list, tuple)):
79
87
  obj = sorted(get_stable_hash(o) for o in obj)
80
- return md5(str(obj).encode()).hexdigest()
88
+ return hashlib.md5(str(obj).encode(), usedforsecurity=False).hexdigest()
81
89
 
82
90
 
83
91
  def convert_locale_to_language_tag(locale: str | None) -> str | None:
@@ -91,52 +99,6 @@ def convert_locale_to_language_tag(locale: str | None) -> str | None:
91
99
  return None
92
100
 
93
101
 
94
- class Stylesheet(str):
95
- """A metadata of stylesheet.
96
-
97
- To keep compatibility with old themes, an instance of stylesheet behaves as
98
- its filename (str).
99
- """
100
-
101
- attributes: dict[str, str] = None
102
- filename: str = None
103
- priority: int = None
104
-
105
- def __new__(cls, filename: str, *args: str, priority: int = 500, **attributes: Any,
106
- ) -> Stylesheet:
107
- self = str.__new__(cls, filename)
108
- self.filename = filename
109
- self.priority = priority
110
- self.attributes = attributes
111
- self.attributes.setdefault('rel', 'stylesheet')
112
- self.attributes.setdefault('type', 'text/css')
113
- if args: # old style arguments (rel, title)
114
- self.attributes['rel'] = args[0]
115
- self.attributes['title'] = args[1]
116
-
117
- return self
118
-
119
-
120
- class JavaScript(str):
121
- """A metadata of javascript file.
122
-
123
- To keep compatibility with old themes, an instance of javascript behaves as
124
- its filename (str).
125
- """
126
-
127
- attributes: dict[str, str] = None
128
- filename: str = None
129
- priority: int = None
130
-
131
- def __new__(cls, filename: str, priority: int = 500, **attributes: str) -> JavaScript:
132
- self = str.__new__(cls, filename)
133
- self.filename = filename
134
- self.priority = priority
135
- self.attributes = attributes
136
-
137
- return self
138
-
139
-
140
102
  class BuildInfo:
141
103
  """buildinfo file manipulator.
142
104
 
@@ -163,7 +125,7 @@ class BuildInfo:
163
125
  self,
164
126
  config: Config | None = None,
165
127
  tags: Tags | None = None,
166
- config_categories: list[str] = [],
128
+ config_categories: Sequence[str] = (),
167
129
  ) -> None:
168
130
  self.config_hash = ''
169
131
  self.tags_hash = ''
@@ -175,7 +137,7 @@ class BuildInfo:
175
137
  if tags:
176
138
  self.tags_hash = get_stable_hash(sorted(tags))
177
139
 
178
- def __eq__(self, other: BuildInfo) -> bool: # type: ignore
140
+ def __eq__(self, other: BuildInfo) -> bool: # type: ignore[override]
179
141
  return (self.config_hash == other.config_hash and
180
142
  self.tags_hash == other.tags_hash)
181
143
 
@@ -217,17 +179,17 @@ class StandaloneHTMLBuilder(Builder):
217
179
  use_index = False
218
180
  download_support = True # enable download role
219
181
 
220
- imgpath: str = None
182
+ imgpath: str = ''
221
183
  domain_indices: list[DOMAIN_INDEX_TYPE] = []
222
184
 
223
- def __init__(self, app: Sphinx, env: BuildEnvironment | None = None) -> None:
185
+ def __init__(self, app: Sphinx, env: BuildEnvironment) -> None:
224
186
  super().__init__(app, env)
225
187
 
226
188
  # CSS files
227
- self.css_files: list[Stylesheet] = []
189
+ self._css_files: list[_CascadingStyleSheet] = []
228
190
 
229
191
  # JS files
230
- self.script_files: list[JavaScript] = []
192
+ self._js_files: list[_JavaScript] = []
231
193
 
232
194
  # Cached Publisher for writing doctrees to HTML
233
195
  reader = docutils.readers.doctree.Reader(parser_name='restructuredtext')
@@ -252,7 +214,7 @@ class StandaloneHTMLBuilder(Builder):
252
214
  # section numbers for headings in the currently visited document
253
215
  self.secnumbers: dict[str, tuple[int, ...]] = {}
254
216
  # currently written docname
255
- self.current_docname: str = None
217
+ self.current_docname: str = ''
256
218
 
257
219
  self.init_templates()
258
220
  self.init_highlighter()
@@ -286,7 +248,7 @@ class StandaloneHTMLBuilder(Builder):
286
248
  for jsfile in candidates:
287
249
  if path.isfile(jsfile):
288
250
  return jsfile
289
- return None
251
+ return ''
290
252
 
291
253
  def _get_style_filenames(self) -> Iterator[str]:
292
254
  if isinstance(self.config.html_style, str):
@@ -325,6 +287,7 @@ class StandaloneHTMLBuilder(Builder):
325
287
  else:
326
288
  dark_style = None
327
289
 
290
+ self.dark_highlighter: PygmentsBridge | None
328
291
  if dark_style is not None:
329
292
  self.dark_highlighter = PygmentsBridge('html', dark_style)
330
293
  self.app.add_css_file('pygments_dark.css',
@@ -333,8 +296,14 @@ class StandaloneHTMLBuilder(Builder):
333
296
  else:
334
297
  self.dark_highlighter = None
335
298
 
299
+ @property
300
+ def css_files(self) -> list[_CascadingStyleSheet]:
301
+ _deprecation_warning(__name__, f'{self.__class__.__name__}.css_files', '',
302
+ remove=(9, 0))
303
+ return self._css_files
304
+
336
305
  def init_css_files(self) -> None:
337
- self.css_files = []
306
+ self._css_files = []
338
307
  self.add_css_file('pygments.css', priority=200)
339
308
 
340
309
  for filename in self._get_style_filenames():
@@ -351,21 +320,26 @@ class StandaloneHTMLBuilder(Builder):
351
320
  if '://' not in filename:
352
321
  filename = posixpath.join('_static', filename)
353
322
 
354
- self.css_files.append(Stylesheet(filename, **kwargs))
323
+ self._css_files.append(_CascadingStyleSheet(filename, **kwargs))
324
+
325
+ @property
326
+ def script_files(self) -> list[_JavaScript]:
327
+ _deprecation_warning(__name__, f'{self.__class__.__name__}.script_files', '',
328
+ remove=(9, 0))
329
+ return self._js_files
355
330
 
356
331
  def init_js_files(self) -> None:
357
- self.script_files = []
358
- self.add_js_file('documentation_options.js', id="documentation_options",
359
- data_url_root='', priority=200)
332
+ self._js_files = []
333
+ self.add_js_file('documentation_options.js', priority=200)
360
334
  self.add_js_file('doctools.js', priority=200)
361
335
  self.add_js_file('sphinx_highlight.js', priority=200)
362
336
 
363
337
  for filename, attrs in self.app.registry.js_files:
364
- self.add_js_file(filename, **attrs)
338
+ self.add_js_file(filename or '', **attrs)
365
339
 
366
340
  for filename, attrs in self.get_builder_config('js_files', 'html'):
367
341
  attrs.setdefault('priority', 800) # User's JSs are loaded after extensions'
368
- self.add_js_file(filename, **attrs)
342
+ self.add_js_file(filename or '', **attrs)
369
343
 
370
344
  if self._get_translations_js():
371
345
  self.add_js_file('translations.js')
@@ -374,10 +348,10 @@ class StandaloneHTMLBuilder(Builder):
374
348
  if filename and '://' not in filename:
375
349
  filename = posixpath.join('_static', filename)
376
350
 
377
- self.script_files.append(JavaScript(filename, **kwargs))
351
+ self._js_files.append(_JavaScript(filename, **kwargs))
378
352
 
379
353
  @property
380
- def math_renderer_name(self) -> str:
354
+ def math_renderer_name(self) -> str | None:
381
355
  name = self.get_builder_config('math_renderer', 'html')
382
356
  if name is not None:
383
357
  # use given name
@@ -426,17 +400,15 @@ class StandaloneHTMLBuilder(Builder):
426
400
  except Exception:
427
401
  targetmtime = 0
428
402
  try:
429
- srcmtime = max(path.getmtime(self.env.doc2path(docname)),
430
- template_mtime)
403
+ srcmtime = max(path.getmtime(self.env.doc2path(docname)), template_mtime)
431
404
  if srcmtime > targetmtime:
432
405
  logger.debug(
433
406
  '[build target] targetname %r(%s), template(%s), docname %r(%s)',
434
407
  targetname,
435
- datetime.fromtimestamp(targetmtime, tz=timezone.utc),
436
- datetime.fromtimestamp(template_mtime, tz=timezone.utc),
408
+ _format_modified_time(targetmtime),
409
+ _format_modified_time(template_mtime),
437
410
  docname,
438
- datetime.fromtimestamp(path.getmtime(self.env.doc2path(docname)),
439
- tz=timezone.utc),
411
+ _format_modified_time(path.getmtime(self.env.doc2path(docname))),
440
412
  )
441
413
  yield docname
442
414
  except OSError:
@@ -455,7 +427,7 @@ class StandaloneHTMLBuilder(Builder):
455
427
  doc.append(node)
456
428
  self._publisher.set_source(doc)
457
429
  self._publisher.publish()
458
- return self._publisher.writer.parts
430
+ return self._publisher.writer.parts # type: ignore[union-attr]
459
431
 
460
432
  def prepare_writing(self, docnames: set[str]) -> None:
461
433
  # create the search indexer
@@ -498,6 +470,7 @@ class StandaloneHTMLBuilder(Builder):
498
470
 
499
471
  # format the "last updated on" string, only once is enough since it
500
472
  # typically doesn't include the time of day
473
+ self.last_updated: str | None
501
474
  lufmt = self.config.html_last_updated_fmt
502
475
  if lufmt is not None:
503
476
  self.last_updated = format_date(lufmt or _('%b %d, %Y'),
@@ -526,9 +499,15 @@ class StandaloneHTMLBuilder(Builder):
526
499
  rellinks.append((indexname, indexcls.localname,
527
500
  '', indexcls.shortname))
528
501
 
529
- # back up script_files and css_files to allow adding JS/CSS files to a specific page.
530
- self._script_files = list(self.script_files)
531
- self._css_files = list(self.css_files)
502
+ # add assets registered after ``Builder.init()``.
503
+ for css_filename, attrs in self.app.registry.css_files:
504
+ self.add_css_file(css_filename, **attrs)
505
+ for js_filename, attrs in self.app.registry.js_files:
506
+ self.add_js_file(js_filename or '', **attrs)
507
+
508
+ # back up _css_files and _js_files to allow adding CSS/JS files to a specific page.
509
+ self._orig_css_files = list(dict.fromkeys(self._css_files))
510
+ self._orig_js_files = list(dict.fromkeys(self._js_files))
532
511
  styles = list(self._get_style_filenames())
533
512
 
534
513
  self.globalcontext = {
@@ -551,9 +530,9 @@ class StandaloneHTMLBuilder(Builder):
551
530
  'sourcelink_suffix': self.config.html_sourcelink_suffix,
552
531
  'file_suffix': self.out_suffix,
553
532
  'link_suffix': self.link_suffix,
554
- 'script_files': self.script_files,
533
+ 'script_files': self._js_files,
555
534
  'language': convert_locale_to_language_tag(self.config.language),
556
- 'css_files': self.css_files,
535
+ 'css_files': self._css_files,
557
536
  'sphinx_version': __display_version__,
558
537
  'sphinx_version_tuple': sphinx_version,
559
538
  'docutils_version_info': docutils.__version_info__[:5],
@@ -600,12 +579,11 @@ class StandaloneHTMLBuilder(Builder):
600
579
  # that gracefully
601
580
  prev = None
602
581
  while related and related[0]:
603
- try:
582
+ with contextlib.suppress(KeyError):
604
583
  parents.append(
605
584
  {'link': self.get_relative_uri(docname, related[0]),
606
585
  'title': self.render_partial(titles[related[0]])['title']})
607
- except KeyError:
608
- pass
586
+
609
587
  related = self.relations.get(related[0])
610
588
  if parents:
611
589
  # remove link to the master file; we have a generic
@@ -632,7 +610,7 @@ class StandaloneHTMLBuilder(Builder):
632
610
  meta = self.env.metadata.get(docname)
633
611
 
634
612
  # local TOC and global TOC tree
635
- self_toc = TocTree(self.env).get_toc_for(docname, self)
613
+ self_toc = document_toc(self.env, docname, self.tags)
636
614
  toc = self.render_partial(self_toc)['fragment']
637
615
 
638
616
  return {
@@ -780,7 +758,7 @@ class StandaloneHTMLBuilder(Builder):
780
758
 
781
759
  def copy_download_files(self) -> None:
782
760
  def to_relpath(f: str) -> str:
783
- return relative_path(self.srcdir, f)
761
+ return relative_path(self.srcdir, f) # type: ignore[arg-type]
784
762
 
785
763
  # copy downloadable files
786
764
  if self.env.dlfiles:
@@ -820,9 +798,9 @@ class StandaloneHTMLBuilder(Builder):
820
798
  for jsfile in self.indexer.get_js_stemmer_rawcodes():
821
799
  copyfile(jsfile, path.join(self.outdir, '_static', path.basename(jsfile)))
822
800
  else:
823
- jsfile = self.indexer.get_js_stemmer_rawcode()
824
- if jsfile:
825
- copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
801
+ if js_stemmer_rawcode := self.indexer.get_js_stemmer_rawcode():
802
+ copyfile(js_stemmer_rawcode,
803
+ path.join(self.outdir, '_static', '_stemmer.js'))
826
804
 
827
805
  def copy_theme_static_files(self, context: dict[str, Any]) -> None:
828
806
  def onerror(filename: str, error: Exception) -> None:
@@ -930,6 +908,7 @@ class StandaloneHTMLBuilder(Builder):
930
908
  reference.append(node)
931
909
 
932
910
  def load_indexer(self, docnames: Iterable[str]) -> None:
911
+ assert self.indexer is not None
933
912
  keep = set(self.env.all_docs) - set(docnames)
934
913
  try:
935
914
  searchindexfn = path.join(self.outdir, self.searchindex_filename)
@@ -950,7 +929,7 @@ class StandaloneHTMLBuilder(Builder):
950
929
  def index_page(self, pagename: str, doctree: nodes.document, title: str) -> None:
951
930
  # only index pages with title
952
931
  if self.indexer is not None and title:
953
- filename = self.env.doc2path(pagename, base=None)
932
+ filename = self.env.doc2path(pagename, base=False)
954
933
  metadata = self.env.metadata.get(pagename, {})
955
934
  if 'nosearch' in metadata:
956
935
  self.indexer.feed(pagename, filename, '', new_document(''))
@@ -962,8 +941,8 @@ class StandaloneHTMLBuilder(Builder):
962
941
  kwargs['includehidden'] = False
963
942
  if kwargs.get('maxdepth') == '':
964
943
  kwargs.pop('maxdepth')
965
- return self.render_partial(TocTree(self.env).get_toctree_for(
966
- docname, self, collapse, **kwargs))['fragment']
944
+ toctree = global_toctree_for_doc(self.env, docname, self, collapse=collapse, **kwargs)
945
+ return self.render_partial(toctree)['fragment']
967
946
 
968
947
  def get_outfilename(self, pagename: str) -> str:
969
948
  return path.join(self.outdir, os_path(pagename) + self.out_suffix)
@@ -1068,9 +1047,53 @@ class StandaloneHTMLBuilder(Builder):
1068
1047
  self.add_sidebars(pagename, ctx)
1069
1048
  ctx.update(addctx)
1070
1049
 
1071
- # revert script_files and css_files
1072
- self.script_files[:] = self._script_files
1073
- self.css_files[:] = self._css_files
1050
+ # 'blah.html' should have content_root = './' not ''.
1051
+ ctx['content_root'] = (f'..{SEP}' * default_baseuri.count(SEP)) or f'.{SEP}'
1052
+
1053
+ outdir = self.app.outdir
1054
+
1055
+ def css_tag(css: _CascadingStyleSheet) -> str:
1056
+ attrs = []
1057
+ for key, value in css.attributes.items():
1058
+ if value is not None:
1059
+ attrs.append(f'{key}="{html.escape(value, quote=True)}"')
1060
+ uri = pathto(os.fspath(css.filename), resource=True)
1061
+ if checksum := _file_checksum(outdir, css.filename):
1062
+ uri += f'?v={checksum}'
1063
+ return f'<link {" ".join(sorted(attrs))} href="{uri}" />'
1064
+
1065
+ ctx['css_tag'] = css_tag
1066
+
1067
+ def js_tag(js: _JavaScript | str) -> str:
1068
+ if not isinstance(js, _JavaScript):
1069
+ # str value (old styled)
1070
+ return f'<script src="{pathto(js, resource=True)}"></script>'
1071
+
1072
+ attrs = []
1073
+ body = js.attributes.get('body', '')
1074
+ for key, value in js.attributes.items():
1075
+ if key == 'body':
1076
+ continue
1077
+ if value is not None:
1078
+ attrs.append(f'{key}="{html.escape(value, quote=True)}"')
1079
+
1080
+ if not js.filename:
1081
+ if attrs:
1082
+ return f'<script {" ".join(sorted(attrs))}>{body}</script>'
1083
+ return f'<script>{body}</script>'
1084
+
1085
+ uri = pathto(os.fspath(js.filename), resource=True)
1086
+ if checksum := _file_checksum(outdir, js.filename):
1087
+ uri += f'?v={checksum}'
1088
+ if attrs:
1089
+ return f'<script {" ".join(sorted(attrs))} src="{uri}"></script>'
1090
+ return f'<script src="{uri}"></script>'
1091
+
1092
+ ctx['js_tag'] = js_tag
1093
+
1094
+ # revert _css_files and _js_files
1095
+ self._css_files[:] = self._orig_css_files
1096
+ self._js_files[:] = self._orig_js_files
1074
1097
 
1075
1098
  self.update_page_context(pagename, templatename, ctx, event_arg)
1076
1099
  newtmpl = self.app.emit_firstresult('html-page-context', pagename,
@@ -1079,7 +1102,7 @@ class StandaloneHTMLBuilder(Builder):
1079
1102
  templatename = newtmpl
1080
1103
 
1081
1104
  # sort JS/CSS before rendering HTML
1082
- try:
1105
+ try: # NoQA: SIM105
1083
1106
  # Convert script_files to list to support non-list script_files (refs: #8889)
1084
1107
  ctx['script_files'] = sorted(ctx['script_files'], key=lambda js: js.priority)
1085
1108
  except AttributeError:
@@ -1089,10 +1112,8 @@ class StandaloneHTMLBuilder(Builder):
1089
1112
  # Note: priority sorting feature will not work in this case.
1090
1113
  pass
1091
1114
 
1092
- try:
1115
+ with contextlib.suppress(AttributeError):
1093
1116
  ctx['css_files'] = sorted(ctx['css_files'], key=lambda css: css.priority)
1094
- except AttributeError:
1095
- pass
1096
1117
 
1097
1118
  try:
1098
1119
  output = self.templates.render(templatename, ctx)
@@ -1127,8 +1148,7 @@ class StandaloneHTMLBuilder(Builder):
1127
1148
  pass
1128
1149
 
1129
1150
  def handle_finish(self) -> None:
1130
- if self.indexer:
1131
- self.finish_tasks.add_task(self.dump_search_index)
1151
+ self.finish_tasks.add_task(self.dump_search_index)
1132
1152
  self.finish_tasks.add_task(self.dump_inventory)
1133
1153
 
1134
1154
  @progress_message(__('dumping object inventory'))
@@ -1136,6 +1156,9 @@ class StandaloneHTMLBuilder(Builder):
1136
1156
  InventoryFile.dump(path.join(self.outdir, INVENTORY_FILENAME), self.env, self)
1137
1157
 
1138
1158
  def dump_search_index(self) -> None:
1159
+ if self.indexer is None:
1160
+ return
1161
+
1139
1162
  with progress_message(__('dumping search index in %s') % self.indexer.label()):
1140
1163
  self.indexer.prune(self.env.all_docs)
1141
1164
  searchindexfn = path.join(self.outdir, self.searchindex_filename)
@@ -1164,7 +1187,13 @@ def convert_html_css_files(app: Sphinx, config: Config) -> None:
1164
1187
  logger.warning(__('invalid css_file: %r, ignored'), entry)
1165
1188
  continue
1166
1189
 
1167
- config.html_css_files = html_css_files # type: ignore
1190
+ config.html_css_files = html_css_files # type: ignore[attr-defined]
1191
+
1192
+
1193
+ def _format_modified_time(timestamp: float) -> str:
1194
+ """Return an RFC 3339 formatted string representing the given timestamp."""
1195
+ seconds, fraction = divmod(timestamp, 1)
1196
+ return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(seconds)) + f'.{fraction:.3f}'
1168
1197
 
1169
1198
 
1170
1199
  def convert_html_js_files(app: Sphinx, config: Config) -> None:
@@ -1181,89 +1210,13 @@ def convert_html_js_files(app: Sphinx, config: Config) -> None:
1181
1210
  logger.warning(__('invalid js_file: %r, ignored'), entry)
1182
1211
  continue
1183
1212
 
1184
- config.html_js_files = html_js_files # type: ignore
1185
-
1186
-
1187
- def setup_css_tag_helper(app: Sphinx, pagename: str, templatename: str,
1188
- context: dict, doctree: Node) -> None:
1189
- """Set up css_tag() template helper.
1190
-
1191
- .. note:: This set up function is added to keep compatibility with webhelper.
1192
- """
1193
- pathto = context.get('pathto')
1194
-
1195
- def css_tag(css: Stylesheet) -> str:
1196
- attrs = []
1197
- for key in sorted(css.attributes):
1198
- value = css.attributes[key]
1199
- if value is not None:
1200
- attrs.append(f'{key}="{html.escape(value, True)}"')
1201
- uri = pathto(css.filename, resource=True)
1202
- if checksum := _file_checksum(app.outdir, css.filename):
1203
- uri += f'?v={checksum}'
1204
- attrs.append(f'href="{uri}"')
1205
- return f'<link {" ".join(attrs)} />'
1206
-
1207
- context['css_tag'] = css_tag
1208
-
1209
-
1210
- def setup_js_tag_helper(app: Sphinx, pagename: str, templatename: str,
1211
- context: dict, doctree: Node) -> None:
1212
- """Set up js_tag() template helper.
1213
-
1214
- .. note:: This set up function is added to keep compatibility with webhelper.
1215
- """
1216
- pathto = context.get('pathto')
1217
-
1218
- def js_tag(js: JavaScript) -> str:
1219
- attrs = []
1220
- body = ''
1221
- if isinstance(js, JavaScript):
1222
- for key in sorted(js.attributes):
1223
- value = js.attributes[key]
1224
- if value is not None:
1225
- if key == 'body':
1226
- body = value
1227
- elif key == 'data_url_root':
1228
- attrs.append(f'data-url_root="{pathto("", resource=True)}"')
1229
- else:
1230
- attrs.append(f'{key}="{html.escape(value, True)}"')
1231
- if js.filename:
1232
- uri = pathto(js.filename, resource=True)
1233
- if checksum := _file_checksum(app.outdir, js.filename):
1234
- uri += f'?v={checksum}'
1235
- attrs.append(f'src="{uri}"')
1236
- else:
1237
- # str value (old styled)
1238
- attrs.append(f'src="{pathto(js, resource=True)}"')
1239
-
1240
- if attrs:
1241
- return f'<script {" ".join(attrs)}>{body}</script>'
1242
- else:
1243
- return f'<script>{body}</script>'
1244
-
1245
- context['js_tag'] = js_tag
1246
-
1247
-
1248
- def _file_checksum(outdir: str, filename: str) -> str:
1249
- # Don't generate checksums for HTTP URIs
1250
- if '://' in filename:
1251
- return ''
1252
- try:
1253
- # Ensure universal newline mode is used to avoid checksum differences
1254
- with open(path.join(outdir, filename), encoding='utf-8') as f:
1255
- content = f.read().encode(encoding='utf-8')
1256
- except FileNotFoundError:
1257
- return ''
1258
- if not content:
1259
- return ''
1260
- return f'{zlib.crc32(content):08x}'
1213
+ config.html_js_files = html_js_files # type: ignore[attr-defined]
1261
1214
 
1262
1215
 
1263
1216
  def setup_resource_paths(app: Sphinx, pagename: str, templatename: str,
1264
1217
  context: dict, doctree: Node) -> None:
1265
1218
  """Set up relative resource paths."""
1266
- pathto = context.get('pathto')
1219
+ pathto = context['pathto']
1267
1220
 
1268
1221
  # favicon_url
1269
1222
  favicon_url = context.get('favicon_url')
@@ -1280,7 +1233,7 @@ def validate_math_renderer(app: Sphinx) -> None:
1280
1233
  if app.builder.format != 'html':
1281
1234
  return
1282
1235
 
1283
- name = app.builder.math_renderer_name # type: ignore
1236
+ name = app.builder.math_renderer_name # type: ignore[attr-defined]
1284
1237
  if name is None:
1285
1238
  raise ConfigError(__('Many math_renderers are registered. '
1286
1239
  'But no math_renderer is selected.'))
@@ -1296,7 +1249,7 @@ def validate_html_extra_path(app: Sphinx, config: Config) -> None:
1296
1249
  logger.warning(__('html_extra_path entry %r does not exist'), entry)
1297
1250
  config.html_extra_path.remove(entry)
1298
1251
  elif (path.splitdrive(app.outdir)[0] == path.splitdrive(extra_path)[0] and
1299
- path.commonpath([app.outdir, extra_path]) == app.outdir):
1252
+ path.commonpath((app.outdir, extra_path)) == path.normpath(app.outdir)):
1300
1253
  logger.warning(__('html_extra_path entry %r is placed inside outdir'), entry)
1301
1254
  config.html_extra_path.remove(entry)
1302
1255
 
@@ -1309,7 +1262,7 @@ def validate_html_static_path(app: Sphinx, config: Config) -> None:
1309
1262
  logger.warning(__('html_static_path entry %r does not exist'), entry)
1310
1263
  config.html_static_path.remove(entry)
1311
1264
  elif (path.splitdrive(app.outdir)[0] == path.splitdrive(static_path)[0] and
1312
- path.commonpath([app.outdir, static_path]) == app.outdir):
1265
+ path.commonpath((app.outdir, static_path)) == path.normpath(app.outdir)):
1313
1266
  logger.warning(__('html_static_path entry %r is placed inside outdir'), entry)
1314
1267
  config.html_static_path.remove(entry)
1315
1268
 
@@ -1320,7 +1273,7 @@ def validate_html_logo(app: Sphinx, config: Config) -> None:
1320
1273
  not path.isfile(path.join(app.confdir, config.html_logo)) and
1321
1274
  not isurl(config.html_logo)):
1322
1275
  logger.warning(__('logo file %r does not exist'), config.html_logo)
1323
- config.html_logo = None # type: ignore
1276
+ config.html_logo = None # type: ignore[attr-defined]
1324
1277
 
1325
1278
 
1326
1279
  def validate_html_favicon(app: Sphinx, config: Config) -> None:
@@ -1329,7 +1282,7 @@ def validate_html_favicon(app: Sphinx, config: Config) -> None:
1329
1282
  not path.isfile(path.join(app.confdir, config.html_favicon)) and
1330
1283
  not isurl(config.html_favicon)):
1331
1284
  logger.warning(__('favicon file %r does not exist'), config.html_favicon)
1332
- config.html_favicon = None # type: ignore
1285
+ config.html_favicon = None # type: ignore[attr-defined]
1333
1286
 
1334
1287
 
1335
1288
  def error_on_html_4(_app: Sphinx, config: Config) -> None:
@@ -1383,7 +1336,7 @@ def setup(app: Sphinx) -> dict[str, Any]:
1383
1336
  app.add_config_value('html_secnumber_suffix', '. ', 'html')
1384
1337
  app.add_config_value('html_search_language', None, 'html', [str])
1385
1338
  app.add_config_value('html_search_options', {}, 'html')
1386
- app.add_config_value('html_search_scorer', '', None)
1339
+ app.add_config_value('html_search_scorer', '', '')
1387
1340
  app.add_config_value('html_scaled_image_link', True, 'html')
1388
1341
  app.add_config_value('html_baseurl', '', 'html')
1389
1342
  app.add_config_value('html_codeblock_linenos_style', 'inline', 'html', # RemovedInSphinx70Warning # noqa: E501
@@ -1404,8 +1357,6 @@ def setup(app: Sphinx) -> dict[str, Any]:
1404
1357
  app.connect('config-inited', validate_html_favicon, priority=800)
1405
1358
  app.connect('config-inited', error_on_html_4, priority=800)
1406
1359
  app.connect('builder-inited', validate_math_renderer)
1407
- app.connect('html-page-context', setup_css_tag_helper)
1408
- app.connect('html-page-context', setup_js_tag_helper)
1409
1360
  app.connect('html-page-context', setup_resource_paths)
1410
1361
 
1411
1362
  # load default math renderer
@@ -1419,3 +1370,22 @@ def setup(app: Sphinx) -> dict[str, Any]:
1419
1370
  'parallel_read_safe': True,
1420
1371
  'parallel_write_safe': True,
1421
1372
  }
1373
+
1374
+
1375
+ # deprecated name -> (object to return, canonical path or empty string)
1376
+ _DEPRECATED_OBJECTS = {
1377
+ 'Stylesheet': (_CascadingStyleSheet, 'sphinx.builders.html._assets._CascadingStyleSheet', (9, 0)), # NoQA: E501
1378
+ 'JavaScript': (_JavaScript, 'sphinx.builders.html._assets._JavaScript', (9, 0)),
1379
+ }
1380
+
1381
+
1382
+ def __getattr__(name):
1383
+ if name not in _DEPRECATED_OBJECTS:
1384
+ msg = f'module {__name__!r} has no attribute {name!r}'
1385
+ raise AttributeError(msg)
1386
+
1387
+ from sphinx.deprecation import _deprecation_warning
1388
+
1389
+ deprecated_object, canonical_name, remove = _DEPRECATED_OBJECTS[name]
1390
+ _deprecation_warning(__name__, name, canonical_name, remove=remove)
1391
+ return deprecated_object