Sphinx 8.0.2__py3-none-any.whl → 8.1.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 (424) hide show
  1. sphinx/__init__.py +6 -3
  2. sphinx/_cli/__init__.py +40 -20
  3. sphinx/_cli/util/colour.py +5 -4
  4. sphinx/_cli/util/errors.py +28 -11
  5. sphinx/application.py +361 -38
  6. sphinx/builders/__init__.py +229 -83
  7. sphinx/builders/_epub_base.py +118 -71
  8. sphinx/builders/changes.py +39 -21
  9. sphinx/builders/dirhtml.py +4 -4
  10. sphinx/builders/dummy.py +2 -5
  11. sphinx/builders/epub3.py +43 -22
  12. sphinx/builders/gettext.py +43 -25
  13. sphinx/builders/html/__init__.py +284 -218
  14. sphinx/builders/html/_assets.py +62 -26
  15. sphinx/builders/html/_build_info.py +76 -0
  16. sphinx/builders/html/transforms.py +11 -9
  17. sphinx/builders/latex/__init__.py +139 -81
  18. sphinx/builders/latex/constants.py +7 -7
  19. sphinx/builders/latex/nodes.py +3 -2
  20. sphinx/builders/latex/theming.py +7 -5
  21. sphinx/builders/latex/transforms.py +27 -19
  22. sphinx/builders/linkcheck.py +146 -72
  23. sphinx/builders/manpage.py +30 -13
  24. sphinx/builders/singlehtml.py +22 -14
  25. sphinx/builders/texinfo.py +67 -37
  26. sphinx/builders/text.py +5 -5
  27. sphinx/builders/xml.py +6 -9
  28. sphinx/cmd/build.py +282 -103
  29. sphinx/cmd/make_mode.py +106 -63
  30. sphinx/cmd/quickstart.py +341 -145
  31. sphinx/config.py +45 -12
  32. sphinx/deprecation.py +8 -2
  33. sphinx/directives/__init__.py +28 -19
  34. sphinx/directives/code.py +86 -56
  35. sphinx/directives/other.py +50 -36
  36. sphinx/directives/patches.py +29 -19
  37. sphinx/domains/__init__.py +20 -120
  38. sphinx/domains/_domains_container.py +281 -0
  39. sphinx/domains/_index.py +110 -0
  40. sphinx/domains/c/__init__.py +3 -3
  41. sphinx/domains/c/_parser.py +10 -6
  42. sphinx/domains/changeset.py +5 -3
  43. sphinx/domains/citation.py +5 -3
  44. sphinx/domains/cpp/__init__.py +9 -11
  45. sphinx/domains/cpp/_parser.py +8 -7
  46. sphinx/domains/index.py +3 -3
  47. sphinx/domains/javascript.py +12 -7
  48. sphinx/domains/math.py +2 -2
  49. sphinx/domains/python/__init__.py +10 -5
  50. sphinx/domains/python/_object.py +1 -1
  51. sphinx/domains/rst.py +5 -5
  52. sphinx/domains/std/__init__.py +16 -11
  53. sphinx/environment/__init__.py +206 -146
  54. sphinx/environment/adapters/asset.py +3 -2
  55. sphinx/environment/adapters/indexentries.py +74 -33
  56. sphinx/environment/adapters/toctree.py +100 -43
  57. sphinx/environment/collectors/__init__.py +19 -8
  58. sphinx/environment/collectors/asset.py +47 -15
  59. sphinx/environment/collectors/dependencies.py +8 -4
  60. sphinx/environment/collectors/metadata.py +7 -2
  61. sphinx/environment/collectors/title.py +7 -2
  62. sphinx/environment/collectors/toctree.py +54 -22
  63. sphinx/errors.py +4 -1
  64. sphinx/events.py +314 -7
  65. sphinx/ext/apidoc.py +42 -18
  66. sphinx/ext/autodoc/__init__.py +52 -24
  67. sphinx/ext/autodoc/importer.py +6 -9
  68. sphinx/ext/autosectionlabel.py +1 -2
  69. sphinx/ext/autosummary/__init__.py +3 -1
  70. sphinx/ext/autosummary/generate.py +28 -14
  71. sphinx/ext/coverage.py +7 -7
  72. sphinx/ext/doctest.py +4 -8
  73. sphinx/ext/duration.py +6 -5
  74. sphinx/ext/inheritance_diagram.py +1 -1
  75. sphinx/ext/intersphinx/_cli.py +6 -4
  76. sphinx/ext/intersphinx/_load.py +77 -32
  77. sphinx/ext/intersphinx/_resolve.py +173 -79
  78. sphinx/ext/intersphinx/_shared.py +7 -5
  79. sphinx/ext/linkcode.py +7 -1
  80. sphinx/ext/mathjax.py +1 -2
  81. sphinx/ext/napoleon/__init__.py +37 -24
  82. sphinx/ext/napoleon/docstring.py +202 -134
  83. sphinx/ext/todo.py +5 -3
  84. sphinx/highlighting.py +9 -2
  85. sphinx/io.py +1 -1
  86. sphinx/jinja2glue.py +27 -6
  87. sphinx/locale/__init__.py +6 -2
  88. sphinx/locale/ar/LC_MESSAGES/sphinx.js +8 -1
  89. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  90. sphinx/locale/ar/LC_MESSAGES/sphinx.po +2246 -2288
  91. sphinx/locale/bg/LC_MESSAGES/sphinx.js +4 -1
  92. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  93. sphinx/locale/bg/LC_MESSAGES/sphinx.po +2113 -2159
  94. sphinx/locale/bn/LC_MESSAGES/sphinx.js +4 -1
  95. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  96. sphinx/locale/bn/LC_MESSAGES/sphinx.po +2349 -2395
  97. sphinx/locale/ca/LC_MESSAGES/sphinx.js +4 -1
  98. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/ca/LC_MESSAGES/sphinx.po +2846 -2892
  100. sphinx/locale/cak/LC_MESSAGES/sphinx.js +4 -1
  101. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  102. sphinx/locale/cak/LC_MESSAGES/sphinx.po +2213 -2259
  103. sphinx/locale/cs/LC_MESSAGES/sphinx.js +6 -1
  104. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/cs/LC_MESSAGES/sphinx.po +2225 -2269
  106. sphinx/locale/cy/LC_MESSAGES/sphinx.js +6 -1
  107. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  108. sphinx/locale/cy/LC_MESSAGES/sphinx.po +2403 -2447
  109. sphinx/locale/da/LC_MESSAGES/sphinx.js +4 -1
  110. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/da/LC_MESSAGES/sphinx.po +2214 -2260
  112. sphinx/locale/de/LC_MESSAGES/sphinx.js +4 -1
  113. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  114. sphinx/locale/de/LC_MESSAGES/sphinx.po +2230 -2276
  115. sphinx/locale/de_DE/LC_MESSAGES/sphinx.js +4 -1
  116. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +2113 -2159
  118. sphinx/locale/el/LC_MESSAGES/sphinx.js +4 -1
  119. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  120. sphinx/locale/el/LC_MESSAGES/sphinx.po +2619 -2665
  121. sphinx/locale/en_DE/LC_MESSAGES/sphinx.js +4 -1
  122. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +2113 -2159
  124. sphinx/locale/en_FR/LC_MESSAGES/sphinx.js +4 -1
  125. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  126. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +2113 -2159
  127. sphinx/locale/en_GB/LC_MESSAGES/sphinx.js +4 -1
  128. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +2519 -2565
  130. sphinx/locale/en_HK/LC_MESSAGES/sphinx.js +4 -1
  131. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  132. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +2113 -2159
  133. sphinx/locale/eo/LC_MESSAGES/sphinx.js +4 -1
  134. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/eo/LC_MESSAGES/sphinx.po +2232 -2278
  136. sphinx/locale/es/LC_MESSAGES/sphinx.js +5 -1
  137. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  138. sphinx/locale/es/LC_MESSAGES/sphinx.po +2516 -2561
  139. sphinx/locale/es_CO/LC_MESSAGES/sphinx.js +5 -1
  140. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  141. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +2114 -2159
  142. sphinx/locale/et/LC_MESSAGES/sphinx.js +4 -1
  143. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/et/LC_MESSAGES/sphinx.po +2317 -2363
  145. sphinx/locale/eu/LC_MESSAGES/sphinx.js +4 -1
  146. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  147. sphinx/locale/eu/LC_MESSAGES/sphinx.po +2218 -2264
  148. sphinx/locale/fa/LC_MESSAGES/sphinx.js +4 -1
  149. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  150. sphinx/locale/fa/LC_MESSAGES/sphinx.po +2505 -2551
  151. sphinx/locale/fi/LC_MESSAGES/sphinx.js +4 -1
  152. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  153. sphinx/locale/fi/LC_MESSAGES/sphinx.po +2303 -2349
  154. sphinx/locale/fr/LC_MESSAGES/sphinx.js +6 -2
  155. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/fr/LC_MESSAGES/sphinx.po +2863 -2908
  157. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.js +5 -1
  158. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  159. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +2114 -2159
  160. sphinx/locale/gl/LC_MESSAGES/sphinx.js +4 -1
  161. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/gl/LC_MESSAGES/sphinx.po +2571 -2617
  163. sphinx/locale/he/LC_MESSAGES/sphinx.js +5 -1
  164. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  165. sphinx/locale/he/LC_MESSAGES/sphinx.po +2307 -2352
  166. sphinx/locale/hi/LC_MESSAGES/sphinx.js +4 -1
  167. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/hi/LC_MESSAGES/sphinx.po +2580 -2626
  169. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.js +4 -1
  170. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  171. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +2113 -2159
  172. sphinx/locale/hr/LC_MESSAGES/sphinx.js +5 -1
  173. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/hr/LC_MESSAGES/sphinx.po +2238 -2283
  175. sphinx/locale/hu/LC_MESSAGES/sphinx.js +4 -1
  176. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  177. sphinx/locale/hu/LC_MESSAGES/sphinx.po +2228 -2274
  178. sphinx/locale/id/LC_MESSAGES/sphinx.js +3 -1
  179. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  180. sphinx/locale/id/LC_MESSAGES/sphinx.po +2787 -2834
  181. sphinx/locale/is/LC_MESSAGES/sphinx.js +4 -1
  182. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  183. sphinx/locale/is/LC_MESSAGES/sphinx.po +2224 -2270
  184. sphinx/locale/it/LC_MESSAGES/sphinx.js +5 -1
  185. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  186. sphinx/locale/it/LC_MESSAGES/sphinx.po +2231 -2276
  187. sphinx/locale/ja/LC_MESSAGES/sphinx.js +3 -1
  188. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  189. sphinx/locale/ja/LC_MESSAGES/sphinx.po +2507 -2554
  190. sphinx/locale/ka/LC_MESSAGES/sphinx.js +4 -1
  191. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  192. sphinx/locale/ka/LC_MESSAGES/sphinx.po +2428 -2474
  193. sphinx/locale/ko/LC_MESSAGES/sphinx.js +3 -1
  194. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  195. sphinx/locale/ko/LC_MESSAGES/sphinx.po +2516 -2563
  196. sphinx/locale/lt/LC_MESSAGES/sphinx.js +6 -1
  197. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  198. sphinx/locale/lt/LC_MESSAGES/sphinx.po +2425 -2469
  199. sphinx/locale/lv/LC_MESSAGES/sphinx.js +5 -1
  200. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  201. sphinx/locale/lv/LC_MESSAGES/sphinx.po +2362 -2407
  202. sphinx/locale/mk/LC_MESSAGES/sphinx.js +4 -1
  203. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  204. sphinx/locale/mk/LC_MESSAGES/sphinx.po +2121 -2167
  205. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.js +4 -1
  206. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +2220 -2266
  208. sphinx/locale/ne/LC_MESSAGES/sphinx.js +4 -1
  209. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  210. sphinx/locale/ne/LC_MESSAGES/sphinx.po +2221 -2267
  211. sphinx/locale/nl/LC_MESSAGES/sphinx.js +4 -1
  212. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/nl/LC_MESSAGES/sphinx.po +2240 -2286
  214. sphinx/locale/pl/LC_MESSAGES/sphinx.js +6 -1
  215. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  216. sphinx/locale/pl/LC_MESSAGES/sphinx.po +2319 -2363
  217. sphinx/locale/pt/LC_MESSAGES/sphinx.js +5 -1
  218. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  219. sphinx/locale/pt/LC_MESSAGES/sphinx.po +2114 -2159
  220. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.js +5 -1
  221. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  222. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +2854 -2899
  223. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.js +5 -1
  224. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  225. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +2224 -2269
  226. sphinx/locale/ro/LC_MESSAGES/sphinx.js +5 -1
  227. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  228. sphinx/locale/ro/LC_MESSAGES/sphinx.po +2226 -2271
  229. sphinx/locale/ru/LC_MESSAGES/sphinx.js +8 -3
  230. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  231. sphinx/locale/ru/LC_MESSAGES/sphinx.po +2841 -2885
  232. sphinx/locale/si/LC_MESSAGES/sphinx.js +4 -1
  233. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  234. sphinx/locale/si/LC_MESSAGES/sphinx.po +2294 -2340
  235. sphinx/locale/sk/LC_MESSAGES/sphinx.js +6 -1
  236. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  237. sphinx/locale/sk/LC_MESSAGES/sphinx.po +2497 -2541
  238. sphinx/locale/sl/LC_MESSAGES/sphinx.js +6 -1
  239. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  240. sphinx/locale/sl/LC_MESSAGES/sphinx.po +2331 -2375
  241. sphinx/locale/sphinx.pot +2121 -2167
  242. sphinx/locale/sq/LC_MESSAGES/sphinx.js +4 -1
  243. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  244. sphinx/locale/sq/LC_MESSAGES/sphinx.po +2855 -2901
  245. sphinx/locale/sr/LC_MESSAGES/sphinx.js +5 -1
  246. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  247. sphinx/locale/sr/LC_MESSAGES/sphinx.po +2203 -2248
  248. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
  249. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
  250. sphinx/locale/sv/LC_MESSAGES/sphinx.js +4 -1
  251. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  252. sphinx/locale/sv/LC_MESSAGES/sphinx.po +2423 -2469
  253. sphinx/locale/te/LC_MESSAGES/sphinx.js +4 -1
  254. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  255. sphinx/locale/te/LC_MESSAGES/sphinx.po +2113 -2159
  256. sphinx/locale/tr/LC_MESSAGES/sphinx.js +4 -1
  257. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  258. sphinx/locale/tr/LC_MESSAGES/sphinx.po +2443 -2489
  259. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.js +6 -1
  260. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  261. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +2329 -2373
  262. sphinx/locale/ur/LC_MESSAGES/sphinx.js +4 -1
  263. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  264. sphinx/locale/ur/LC_MESSAGES/sphinx.po +2113 -2159
  265. sphinx/locale/vi/LC_MESSAGES/sphinx.js +3 -1
  266. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  267. sphinx/locale/vi/LC_MESSAGES/sphinx.po +2199 -2246
  268. sphinx/locale/yue/LC_MESSAGES/sphinx.js +3 -1
  269. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  270. sphinx/locale/yue/LC_MESSAGES/sphinx.po +2112 -2159
  271. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.js +3 -1
  272. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  273. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +2112 -2159
  274. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.js +3 -1
  275. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  276. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +2845 -2892
  277. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.js +3 -1
  278. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  279. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +2112 -2159
  280. sphinx/parsers.py +3 -1
  281. sphinx/project.py +6 -2
  282. sphinx/pycode/__init__.py +11 -4
  283. sphinx/pycode/ast.py +58 -58
  284. sphinx/pycode/parser.py +49 -28
  285. sphinx/pygments_styles.py +49 -49
  286. sphinx/registry.py +8 -3
  287. sphinx/roles.py +133 -13
  288. sphinx/search/__init__.py +146 -87
  289. sphinx/search/da.py +2 -4
  290. sphinx/search/de.py +2 -4
  291. sphinx/search/en.py +4 -4
  292. sphinx/search/es.py +2 -4
  293. sphinx/search/fi.py +2 -4
  294. sphinx/search/fr.py +2 -4
  295. sphinx/search/hu.py +2 -4
  296. sphinx/search/it.py +2 -4
  297. sphinx/search/ja.py +55 -32
  298. sphinx/search/nl.py +2 -4
  299. sphinx/search/no.py +2 -4
  300. sphinx/search/pt.py +2 -4
  301. sphinx/search/ro.py +0 -2
  302. sphinx/search/ru.py +2 -4
  303. sphinx/search/sv.py +2 -4
  304. sphinx/search/tr.py +0 -2
  305. sphinx/search/zh.py +18 -13
  306. sphinx/templates/graphviz/graphviz.css +0 -7
  307. sphinx/testing/fixtures.py +6 -5
  308. sphinx/testing/path.py +7 -5
  309. sphinx/testing/util.py +63 -29
  310. sphinx/texinputs/sphinx.sty +107 -39
  311. sphinx/texinputs/sphinxlatexadmonitions.sty +51 -35
  312. sphinx/texinputs/sphinxlatexcontainers.sty +1 -1
  313. sphinx/texinputs/sphinxlatexgraphics.sty +3 -2
  314. sphinx/texinputs/sphinxlatexindbibtoc.sty +1 -1
  315. sphinx/texinputs/sphinxlatexlists.sty +1 -1
  316. sphinx/texinputs/sphinxlatexliterals.sty +4 -1
  317. sphinx/texinputs/sphinxlatexnumfig.sty +22 -9
  318. sphinx/texinputs/sphinxlatexobjects.sty +1 -1
  319. sphinx/texinputs/sphinxlatexshadowbox.sty +72 -10
  320. sphinx/texinputs/sphinxlatexstyleheadings.sty +7 -2
  321. sphinx/texinputs/sphinxlatexstylepage.sty +2 -8
  322. sphinx/texinputs/sphinxlatexstyletext.sty +2 -4
  323. sphinx/texinputs/sphinxlatextables.sty +1 -1
  324. sphinx/texinputs/sphinxoptionsgeometry.sty +1 -1
  325. sphinx/texinputs/sphinxoptionshyperref.sty +1 -1
  326. sphinx/themes/agogo/layout.html +1 -10
  327. sphinx/themes/agogo/static/agogo.css.jinja +0 -7
  328. sphinx/themes/basic/defindex.html +1 -8
  329. sphinx/themes/basic/domainindex.html +1 -9
  330. sphinx/themes/basic/genindex-single.html +1 -9
  331. sphinx/themes/basic/genindex-split.html +1 -9
  332. sphinx/themes/basic/genindex.html +1 -9
  333. sphinx/themes/basic/globaltoc.html +1 -9
  334. sphinx/themes/basic/layout.html +1 -9
  335. sphinx/themes/basic/localtoc.html +1 -9
  336. sphinx/themes/basic/page.html +1 -9
  337. sphinx/themes/basic/relations.html +1 -9
  338. sphinx/themes/basic/search.html +1 -9
  339. sphinx/themes/basic/searchbox.html +1 -9
  340. sphinx/themes/basic/searchfield.html +4 -10
  341. sphinx/themes/basic/sourcelink.html +1 -9
  342. sphinx/themes/basic/static/basic.css.jinja +2 -13
  343. sphinx/themes/basic/static/doctools.js +0 -7
  344. sphinx/themes/basic/static/language_data.js.jinja +0 -7
  345. sphinx/themes/basic/static/searchtools.js +25 -13
  346. sphinx/themes/bizstyle/layout.html +1 -9
  347. sphinx/themes/bizstyle/static/bizstyle.css.jinja +0 -7
  348. sphinx/themes/bizstyle/static/bizstyle.js.jinja +5 -11
  349. sphinx/themes/classic/layout.html +1 -9
  350. sphinx/themes/classic/static/classic.css.jinja +0 -7
  351. sphinx/themes/classic/static/sidebar.js.jinja +0 -6
  352. sphinx/themes/epub/epub-cover.html +1 -9
  353. sphinx/themes/epub/layout.html +1 -9
  354. sphinx/themes/epub/static/epub.css.jinja +0 -7
  355. sphinx/themes/haiku/layout.html +1 -9
  356. sphinx/themes/haiku/static/haiku.css.jinja +0 -6
  357. sphinx/themes/nature/static/nature.css.jinja +0 -7
  358. sphinx/themes/nonav/layout.html +1 -9
  359. sphinx/themes/nonav/static/nonav.css.jinja +0 -7
  360. sphinx/themes/pyramid/static/epub.css.jinja +0 -7
  361. sphinx/themes/pyramid/static/pyramid.css.jinja +0 -7
  362. sphinx/themes/scrolls/layout.html +1 -10
  363. sphinx/themes/scrolls/static/scrolls.css.jinja +0 -7
  364. sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja +2 -7
  365. sphinx/themes/traditional/static/traditional.css.jinja +0 -7
  366. sphinx/theming.py +18 -6
  367. sphinx/transforms/__init__.py +56 -35
  368. sphinx/transforms/compact_bullet_list.py +3 -2
  369. sphinx/transforms/i18n.py +132 -50
  370. sphinx/transforms/post_transforms/__init__.py +94 -43
  371. sphinx/transforms/post_transforms/code.py +7 -6
  372. sphinx/transforms/post_transforms/images.py +71 -54
  373. sphinx/transforms/references.py +1 -2
  374. sphinx/util/__init__.py +23 -194
  375. sphinx/util/_files.py +80 -0
  376. sphinx/util/_importer.py +27 -0
  377. sphinx/util/_io.py +1 -2
  378. sphinx/util/_lines.py +26 -0
  379. sphinx/util/_pathlib.py +5 -2
  380. sphinx/util/_serialise.py +53 -0
  381. sphinx/util/_timestamps.py +2 -1
  382. sphinx/util/_uri.py +16 -0
  383. sphinx/util/cfamily.py +48 -25
  384. sphinx/util/console.py +1 -0
  385. sphinx/util/display.py +1 -1
  386. sphinx/util/docfields.py +125 -45
  387. sphinx/util/docstrings.py +1 -1
  388. sphinx/util/docutils.py +118 -44
  389. sphinx/util/exceptions.py +11 -5
  390. sphinx/util/fileutil.py +53 -32
  391. sphinx/util/http_date.py +9 -7
  392. sphinx/util/i18n.py +49 -16
  393. sphinx/util/images.py +7 -6
  394. sphinx/util/inspect.py +29 -12
  395. sphinx/util/inventory.py +47 -29
  396. sphinx/util/logging.py +58 -85
  397. sphinx/util/matching.py +3 -3
  398. sphinx/util/math.py +1 -1
  399. sphinx/util/nodes.py +176 -108
  400. sphinx/util/osutil.py +13 -10
  401. sphinx/util/parallel.py +5 -4
  402. sphinx/util/parsing.py +5 -3
  403. sphinx/util/png.py +3 -3
  404. sphinx/util/requests.py +8 -4
  405. sphinx/util/rst.py +5 -3
  406. sphinx/util/tags.py +5 -2
  407. sphinx/util/template.py +26 -11
  408. sphinx/util/texescape.py +2 -2
  409. sphinx/util/typing.py +89 -38
  410. sphinx/versioning.py +3 -1
  411. sphinx/writers/html.py +22 -7
  412. sphinx/writers/html5.py +113 -64
  413. sphinx/writers/latex.py +408 -221
  414. sphinx/writers/manpage.py +25 -15
  415. sphinx/writers/texinfo.py +94 -82
  416. sphinx/writers/text.py +87 -53
  417. sphinx/writers/xml.py +5 -4
  418. sphinx-8.1.0.dist-info/LICENSE.rst +31 -0
  419. {sphinx-8.0.2.dist-info → sphinx-8.1.0.dist-info}/METADATA +13 -11
  420. sphinx-8.1.0.dist-info/RECORD +598 -0
  421. sphinx-8.0.2.dist-info/LICENSE.rst +0 -67
  422. sphinx-8.0.2.dist-info/RECORD +0 -590
  423. {sphinx-8.0.2.dist-info → sphinx-8.1.0.dist-info}/WHEEL +0 -0
  424. {sphinx-8.0.2.dist-info → sphinx-8.1.0.dist-info}/entry_points.txt +0 -0
@@ -8,14 +8,22 @@ import pickle
8
8
  from collections import defaultdict
9
9
  from copy import copy
10
10
  from os import path
11
- from typing import TYPE_CHECKING, Any, NoReturn
11
+ from typing import TYPE_CHECKING
12
12
 
13
13
  from sphinx import addnodes
14
+ from sphinx.domains._domains_container import _DomainsContainer
14
15
  from sphinx.environment.adapters import toctree as toctree_adapters
15
- from sphinx.errors import BuildEnvironmentError, DocumentError, ExtensionError, SphinxError
16
+ from sphinx.errors import (
17
+ BuildEnvironmentError,
18
+ DocumentError,
19
+ ExtensionError,
20
+ SphinxError,
21
+ )
16
22
  from sphinx.locale import __
17
23
  from sphinx.transforms import SphinxTransformer
18
- from sphinx.util import DownloadFiles, FilenameUniqDict, logging
24
+ from sphinx.util import logging
25
+ from sphinx.util._files import DownloadFiles, FilenameUniqDict
26
+ from sphinx.util._serialise import stable_str
19
27
  from sphinx.util._timestamps import _format_rfc3339_microseconds
20
28
  from sphinx.util.docutils import LoggingReporter
21
29
  from sphinx.util.i18n import CatalogRepository, docname_to_domain
@@ -23,8 +31,9 @@ from sphinx.util.nodes import is_translatable
23
31
  from sphinx.util.osutil import _last_modified_time, canon_path, os_path
24
32
 
25
33
  if TYPE_CHECKING:
26
- from collections.abc import Callable, Iterator
34
+ from collections.abc import Callable, Iterable, Iterator
27
35
  from pathlib import Path
36
+ from typing import Any, Literal
28
37
 
29
38
  from docutils import nodes
30
39
  from docutils.nodes import Node
@@ -45,6 +54,10 @@ default_settings: dict[str, Any] = {
45
54
  'image_loading': 'link',
46
55
  'embed_stylesheet': False,
47
56
  'cloak_email_addresses': True,
57
+ 'cve_base_url': 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-',
58
+ 'cve_references': None,
59
+ 'cwe_base_url': 'https://cwe.mitre.org/data/definitions/',
60
+ 'cwe_references': None,
48
61
  'pep_base_url': 'https://peps.python.org/',
49
62
  'pep_references': None,
50
63
  'rfc_base_url': 'https://datatracker.ietf.org/doc/html/',
@@ -60,7 +73,7 @@ default_settings: dict[str, Any] = {
60
73
 
61
74
  # This is increased every time an environment attribute is added
62
75
  # or changed to properly invalidate pickle files.
63
- ENV_VERSION = 63
76
+ ENV_VERSION = 64
64
77
 
65
78
  # config status
66
79
  CONFIG_UNSET = -1
@@ -81,61 +94,6 @@ versioning_conditions: dict[str, Literal[False] | Callable[[Node], bool]] = {
81
94
  'text': is_translatable,
82
95
  }
83
96
 
84
- if TYPE_CHECKING:
85
- from collections.abc import MutableMapping
86
- from typing import Literal, overload
87
-
88
- from sphinx.domains.c import CDomain
89
- from sphinx.domains.changeset import ChangeSetDomain
90
- from sphinx.domains.citation import CitationDomain
91
- from sphinx.domains.cpp import CPPDomain
92
- from sphinx.domains.index import IndexDomain
93
- from sphinx.domains.javascript import JavaScriptDomain
94
- from sphinx.domains.math import MathDomain
95
- from sphinx.domains.python import PythonDomain
96
- from sphinx.domains.rst import ReSTDomain
97
- from sphinx.domains.std import StandardDomain
98
- from sphinx.ext.duration import DurationDomain
99
- from sphinx.ext.todo import TodoDomain
100
-
101
- class _DomainsType(MutableMapping[str, Domain]):
102
- @overload
103
- def __getitem__(self, key: Literal["c"]) -> CDomain: ... # NoQA: E704
104
- @overload
105
- def __getitem__(self, key: Literal["cpp"]) -> CPPDomain: ... # NoQA: E704
106
- @overload
107
- def __getitem__(self, key: Literal["changeset"]) -> ChangeSetDomain: ... # NoQA: E704
108
- @overload
109
- def __getitem__(self, key: Literal["citation"]) -> CitationDomain: ... # NoQA: E704
110
- @overload
111
- def __getitem__(self, key: Literal["index"]) -> IndexDomain: ... # NoQA: E704
112
- @overload
113
- def __getitem__(self, key: Literal["js"]) -> JavaScriptDomain: ... # NoQA: E704
114
- @overload
115
- def __getitem__(self, key: Literal["math"]) -> MathDomain: ... # NoQA: E704
116
- @overload
117
- def __getitem__(self, key: Literal["py"]) -> PythonDomain: ... # NoQA: E704
118
- @overload
119
- def __getitem__(self, key: Literal["rst"]) -> ReSTDomain: ... # NoQA: E704
120
- @overload
121
- def __getitem__(self, key: Literal["std"]) -> StandardDomain: ... # NoQA: E704
122
- @overload
123
- def __getitem__(self, key: Literal["duration"]) -> DurationDomain: ... # NoQA: E704
124
- @overload
125
- def __getitem__(self, key: Literal["todo"]) -> TodoDomain: ... # NoQA: E704
126
- @overload
127
- def __getitem__(self, key: str) -> Domain: ... # NoQA: E704
128
- def __getitem__(self, _key: str) -> Domain: raise NotImplementedError # NoQA: E704
129
- def __setitem__( # NoQA: E301,E704
130
- self, key: str, value: Domain,
131
- ) -> NoReturn: raise NotImplementedError
132
- def __delitem__(self, key: str) -> NoReturn: raise NotImplementedError # NoQA: E704
133
- def __iter__(self) -> NoReturn: raise NotImplementedError # NoQA: E704
134
- def __len__(self) -> NoReturn: raise NotImplementedError # NoQA: E704
135
-
136
- else:
137
- _DomainsType = dict
138
-
139
97
 
140
98
  class BuildEnvironment:
141
99
  """
@@ -144,8 +102,6 @@ class BuildEnvironment:
144
102
  transformations to resolve links to them.
145
103
  """
146
104
 
147
- domains: _DomainsType
148
-
149
105
  # --------- ENVIRONMENT INITIALIZATION -------------------------------------
150
106
 
151
107
  def __init__(self, app: Sphinx) -> None:
@@ -163,9 +119,6 @@ class BuildEnvironment:
163
119
  self.versioning_condition: Literal[False] | Callable[[Node], bool] | None = None
164
120
  self.versioning_compare: bool | None = None
165
121
 
166
- # all the registered domains, set by the application
167
- self.domains = _DomainsType()
168
-
169
122
  # the docutils settings for building
170
123
  self.settings: dict[str, Any] = default_settings.copy()
171
124
  self.settings['env'] = self
@@ -242,7 +195,7 @@ class BuildEnvironment:
242
195
  self.dlfiles: DownloadFiles = DownloadFiles()
243
196
 
244
197
  # the original URI for images
245
- self.original_image_uri: dict[str, str] = {}
198
+ self.original_image_uri: dict[_StrPath, str] = {}
246
199
 
247
200
  # temporary data storage while reading a document
248
201
  self.temp_data: dict[str, Any] = {}
@@ -270,6 +223,9 @@ class BuildEnvironment:
270
223
  # objtype index -> (domain, type, objname (localized))
271
224
  self._search_index_objnames: dict[int, tuple[str, str, str]] = {}
272
225
 
226
+ # all the registered domains, set by the application
227
+ self.domains: _DomainsContainer = _DomainsContainer._from_environment(self)
228
+
273
229
  # set up environment
274
230
  self.setup(app)
275
231
 
@@ -277,7 +233,7 @@ class BuildEnvironment:
277
233
  """Obtains serializable data for pickling."""
278
234
  __dict__ = self.__dict__.copy()
279
235
  # clear unpickable attributes
280
- __dict__.update(app=None, domains={}, events=None)
236
+ __dict__.update(app=None, domains=None, events=None)
281
237
  # clear in-memory doctree caches, to reduce memory consumption and
282
238
  # ensure that, upon restoring the state, the most recent pickled files
283
239
  # on the disk are used instead of those from a possibly outdated state
@@ -304,51 +260,83 @@ class BuildEnvironment:
304
260
  self.project = app.project
305
261
  self.version = app.registry.get_envversion(app)
306
262
 
307
- # initialize domains
308
- self.domains = _DomainsType()
309
- for domain in app.registry.create_domains(self):
310
- self.domains[domain.name] = domain
311
-
263
+ # initialise domains
264
+ if self.domains is None:
265
+ # if we are unpickling an environment, we need to recreate the domains
266
+ self.domains = _DomainsContainer._from_environment(self)
312
267
  # setup domains (must do after all initialization)
313
- for domain in self.domains.values():
314
- domain.setup()
268
+ self.domains._setup()
315
269
 
316
- # initialize config
317
- self._update_config(app.config)
270
+ # Initialise config.
271
+ # The old config is self.config, restored from the pickled environment.
272
+ # The new config is app.config, always recreated from ``conf.py``
273
+ self.config_status, self.config_status_extra = self._config_status(
274
+ old_config=self.config, new_config=app.config, verbosity=app.verbosity
275
+ )
276
+ self.config = app.config
318
277
 
319
278
  # initialize settings
320
279
  self._update_settings(app.config)
321
280
 
322
- def _update_config(self, config: Config) -> None:
323
- """Update configurations by new one."""
324
- self.config_status = CONFIG_OK
325
- self.config_status_extra = ''
326
- if self.config is None:
327
- self.config_status = CONFIG_NEW
328
- elif self.config.extensions != config.extensions:
329
- self.config_status = CONFIG_EXTENSIONS_CHANGED
330
- extensions = sorted(
331
- set(self.config.extensions) ^ set(config.extensions))
281
+ @staticmethod
282
+ def _config_status(
283
+ *, old_config: Config | None, new_config: Config, verbosity: int
284
+ ) -> tuple[int, str]:
285
+ """Report the differences between two Config objects.
286
+
287
+ Returns a triple of:
288
+
289
+ 1. The new configuration
290
+ 2. A status code indicating how the configuration has changed.
291
+ 3. A status message indicating what has changed.
292
+ """
293
+ if old_config is None:
294
+ return CONFIG_NEW, ''
295
+
296
+ if old_config.extensions != new_config.extensions:
297
+ old_extensions = set(old_config.extensions)
298
+ new_extensions = set(new_config.extensions)
299
+ extensions = old_extensions ^ new_extensions
332
300
  if len(extensions) == 1:
333
- extension = extensions[0]
301
+ extension = extensions.pop()
334
302
  else:
335
- extension = '%d' % (len(extensions),)
336
- self.config_status_extra = f' ({extension!r})'
337
- else:
338
- # check if a config value was changed that affects how
339
- # doctrees are read
340
- for item in config.filter(frozenset({'env'})):
341
- if self.config[item.name] != item.value:
342
- self.config_status = CONFIG_CHANGED
343
- self.config_status_extra = f' ({item.name!r})'
344
- break
303
+ extension = f'{len(extensions)}'
304
+ return CONFIG_EXTENSIONS_CHANGED, f' ({extension!r})'
305
+
306
+ # Log any changes in configuration keys
307
+ if changed_keys := _differing_config_keys(old_config, new_config):
308
+ changed_num = len(changed_keys)
309
+ if changed_num == 1:
310
+ logger.info(
311
+ __('The configuration has changed (1 option: %r)'),
312
+ next(iter(changed_keys)),
313
+ )
314
+ elif changed_num <= 5 or verbosity >= 1:
315
+ logger.info(
316
+ __('The configuration has changed (%d options: %s)'),
317
+ changed_num,
318
+ ', '.join(map(repr, sorted(changed_keys))),
319
+ )
320
+ else:
321
+ logger.info(
322
+ __('The configuration has changed (%d options: %s, ...)'),
323
+ changed_num,
324
+ ', '.join(map(repr, sorted(changed_keys)[:5])),
325
+ )
326
+
327
+ # check if a config value was changed that affects how doctrees are read
328
+ for item in new_config.filter(frozenset({'env'})):
329
+ if old_config[item.name] != item.value:
330
+ return CONFIG_CHANGED, f' ({item.name!r})'
345
331
 
346
- self.config = config
332
+ return CONFIG_OK, ''
347
333
 
348
334
  def _update_settings(self, config: Config) -> None:
349
335
  """Update settings by new config."""
350
336
  self.settings['input_encoding'] = config.source_encoding
351
- self.settings['trim_footnote_reference_space'] = config.trim_footnote_reference_space
337
+ self.settings['trim_footnote_reference_space'] = (
338
+ config.trim_footnote_reference_space
339
+ )
352
340
  self.settings['language_code'] = config.language
353
341
 
354
342
  # Allow to disable by 3rd party extension (workaround)
@@ -373,9 +361,12 @@ class BuildEnvironment:
373
361
  condition = versioning_conditions[method]
374
362
 
375
363
  if self.versioning_condition not in {None, condition}:
376
- raise SphinxError(__('This environment is incompatible with the '
377
- 'selected builder, please choose another '
378
- 'doctree directory.'))
364
+ msg = __(
365
+ 'This environment is incompatible with the '
366
+ 'selected builder, please choose another '
367
+ 'doctree directory.'
368
+ )
369
+ raise SphinxError(msg)
379
370
  self.versioning_condition = condition
380
371
  self.versioning_compare = compare
381
372
 
@@ -386,25 +377,24 @@ class BuildEnvironment:
386
377
  self.included.pop(docname, None)
387
378
  self.reread_always.discard(docname)
388
379
 
389
- for domain in self.domains.values():
390
- domain.clear_doc(docname)
380
+ self.domains._clear_doc(docname)
391
381
 
392
- def merge_info_from(self, docnames: list[str], other: BuildEnvironment,
393
- app: Sphinx) -> None:
382
+ def merge_info_from(
383
+ self, docnames: Iterable[str], other: BuildEnvironment, app: Sphinx
384
+ ) -> None:
394
385
  """Merge global information gathered about *docnames* while reading them
395
386
  from the *other* environment.
396
387
 
397
388
  This possibly comes from a parallel build process.
398
389
  """
399
- docnames = set(docnames) # type: ignore[assignment]
390
+ docnames = frozenset(docnames)
400
391
  for docname in docnames:
401
392
  self.all_docs[docname] = other.all_docs[docname]
402
393
  self.included[docname] = other.included[docname]
403
394
  if docname in other.reread_always:
404
395
  self.reread_always.add(docname)
405
396
 
406
- for domainname, domain in self.domains.items():
407
- domain.merge_domaindata(docnames, other.domaindata[domainname])
397
+ self.domains._merge_domain_data(docnames, other.domaindata)
408
398
  self.events.emit('env-merge-info', self, docnames, other)
409
399
 
410
400
  def path2doc(self, filename: str | os.PathLike[str]) -> str | None:
@@ -434,12 +424,13 @@ class BuildEnvironment:
434
424
  if filename.startswith(('/', os.sep)):
435
425
  rel_fn = filename[1:]
436
426
  else:
437
- docdir = path.dirname(self.doc2path(docname or self.docname,
438
- base=False))
427
+ docdir = path.dirname(self.doc2path(docname or self.docname, base=False))
439
428
  rel_fn = path.join(docdir, filename)
440
429
 
441
- return (canon_path(path.normpath(rel_fn)),
442
- path.normpath(path.join(self.srcdir, rel_fn)))
430
+ return (
431
+ canon_path(path.normpath(rel_fn)),
432
+ path.normpath(path.join(self.srcdir, rel_fn)),
433
+ )
443
434
 
444
435
  @property
445
436
  def found_docs(self) -> set[str]:
@@ -451,9 +442,11 @@ class BuildEnvironment:
451
442
  self.found_docs.
452
443
  """
453
444
  try:
454
- exclude_paths = (self.config.exclude_patterns +
455
- self.config.templates_path +
456
- builder.get_asset_paths())
445
+ exclude_paths = (
446
+ self.config.exclude_patterns
447
+ + self.config.templates_path
448
+ + builder.get_asset_paths()
449
+ )
457
450
  self.project.discover(exclude_paths, self.config.include_patterns)
458
451
 
459
452
  # Current implementation is applying translated messages in the reading
@@ -464,18 +457,25 @@ class BuildEnvironment:
464
457
  # move i18n process into the writing phase, and remove these lines.
465
458
  if builder.use_message_catalog:
466
459
  # add catalog mo file dependency
467
- repo = CatalogRepository(self.srcdir, self.config.locale_dirs,
468
- self.config.language, self.config.source_encoding)
460
+ repo = CatalogRepository(
461
+ self.srcdir,
462
+ self.config.locale_dirs,
463
+ self.config.language,
464
+ self.config.source_encoding,
465
+ )
469
466
  mo_paths = {c.domain: c.mo_path for c in repo.catalogs}
470
467
  for docname in self.found_docs:
471
468
  domain = docname_to_domain(docname, self.config.gettext_compact)
472
469
  if domain in mo_paths:
473
470
  self.dependencies[docname].add(mo_paths[domain])
474
471
  except OSError as exc:
475
- raise DocumentError(__('Failed to scan documents in %s: %r') %
476
- (self.srcdir, exc)) from exc
472
+ raise DocumentError(
473
+ __('Failed to scan documents in %s: %r') % (self.srcdir, exc)
474
+ ) from exc
477
475
 
478
- def get_outdated_files(self, config_changed: bool) -> tuple[set[str], set[str], set[str]]:
476
+ def get_outdated_files(
477
+ self, config_changed: bool
478
+ ) -> tuple[set[str], set[str], set[str]]:
479
479
  """Return (added, changed, removed) sets."""
480
480
  # clear all files no longer present
481
481
  removed = set(self.all_docs) - self.found_docs
@@ -507,10 +507,12 @@ class BuildEnvironment:
507
507
  mtime = self.all_docs[docname]
508
508
  newmtime = _last_modified_time(self.doc2path(docname))
509
509
  if newmtime > mtime:
510
- logger.debug('[build target] outdated %r: %s -> %s',
511
- docname,
512
- _format_rfc3339_microseconds(mtime),
513
- _format_rfc3339_microseconds(newmtime))
510
+ logger.debug(
511
+ '[build target] outdated %r: %s -> %s',
512
+ docname,
513
+ _format_rfc3339_microseconds(mtime),
514
+ _format_rfc3339_microseconds(newmtime),
515
+ )
514
516
  changed.add(docname)
515
517
  continue
516
518
  # finally, check the mtime of dependencies
@@ -521,7 +523,8 @@ class BuildEnvironment:
521
523
  if not path.isfile(deppath):
522
524
  logger.debug(
523
525
  '[build target] changed %r missing dependency %r',
524
- docname, deppath,
526
+ docname,
527
+ deppath,
525
528
  )
526
529
  changed.add(docname)
527
530
  break
@@ -529,7 +532,8 @@ class BuildEnvironment:
529
532
  if depmtime > mtime:
530
533
  logger.debug(
531
534
  '[build target] outdated %r from dependency %r: %s -> %s',
532
- docname, deppath,
535
+ docname,
536
+ deppath,
533
537
  _format_rfc3339_microseconds(mtime),
534
538
  _format_rfc3339_microseconds(depmtime),
535
539
  )
@@ -557,8 +561,7 @@ class BuildEnvironment:
557
561
  self.temp_data['docname'] = docname
558
562
  # defaults to the global default, but can be re-set in a document
559
563
  self.temp_data['default_role'] = self.config.default_role
560
- self.temp_data['default_domain'] = \
561
- self.domains.get(self.config.primary_domain)
564
+ self.temp_data['default_domain'] = self.domains.get(self.config.primary_domain)
562
565
 
563
566
  # utilities to use while reading a document
564
567
 
@@ -616,7 +619,8 @@ class BuildEnvironment:
616
619
  try:
617
620
  return self.domains[domainname]
618
621
  except KeyError as exc:
619
- raise ExtensionError(__('Domain %r is not registered') % domainname) from exc
622
+ msg = __('Domain %r is not registered') % domainname
623
+ raise ExtensionError(msg) from exc
620
624
 
621
625
  # --------- RESOLVING REFERENCES AND TOCTREES ------------------------------
622
626
 
@@ -663,7 +667,10 @@ class BuildEnvironment:
663
667
  # now, resolve all toctree nodes
664
668
  for toctreenode in doctree.findall(addnodes.toctree):
665
669
  result = toctree_adapters._resolve_toctree(
666
- self, docname, builder, toctreenode,
670
+ self,
671
+ docname,
672
+ builder,
673
+ toctreenode,
667
674
  prune=prune_toctrees,
668
675
  includehidden=includehidden,
669
676
  )
@@ -674,9 +681,17 @@ class BuildEnvironment:
674
681
 
675
682
  return doctree
676
683
 
677
- def resolve_toctree(self, docname: str, builder: Builder, toctree: addnodes.toctree,
678
- prune: bool = True, maxdepth: int = 0, titles_only: bool = False,
679
- collapse: bool = False, includehidden: bool = False) -> Node | None:
684
+ def resolve_toctree(
685
+ self,
686
+ docname: str,
687
+ builder: Builder,
688
+ toctree: addnodes.toctree,
689
+ prune: bool = True,
690
+ maxdepth: int = 0,
691
+ titles_only: bool = False,
692
+ collapse: bool = False,
693
+ includehidden: bool = False,
694
+ ) -> Node | None:
680
695
  """Resolve a *toctree* node into individual bullet lists with titles
681
696
  as items, returning None (if no containing titles are found) or
682
697
  a new node.
@@ -689,7 +704,10 @@ class BuildEnvironment:
689
704
  be collapsed.
690
705
  """
691
706
  return toctree_adapters._resolve_toctree(
692
- self, docname, builder, toctree,
707
+ self,
708
+ docname,
709
+ builder,
710
+ toctree,
693
711
  prune=prune,
694
712
  maxdepth=maxdepth,
695
713
  titles_only=titles_only,
@@ -697,8 +715,9 @@ class BuildEnvironment:
697
715
  includehidden=includehidden,
698
716
  )
699
717
 
700
- def resolve_references(self, doctree: nodes.document, fromdocname: str,
701
- builder: Builder) -> None:
718
+ def resolve_references(
719
+ self, doctree: nodes.document, fromdocname: str, builder: Builder
720
+ ) -> None:
702
721
  self.apply_post_transforms(doctree, fromdocname)
703
722
 
704
723
  def apply_post_transforms(self, doctree: nodes.document, docname: str) -> None:
@@ -723,7 +742,7 @@ class BuildEnvironment:
723
742
 
724
743
  relations = {}
725
744
  docnames = _traverse_toctree(
726
- traversed, None, self.config.root_doc, self.toctree_includes,
745
+ traversed, None, self.config.root_doc, self.toctree_includes
727
746
  )
728
747
  prev_doc = None
729
748
  parent, docname = next(docnames)
@@ -750,15 +769,32 @@ class BuildEnvironment:
750
769
  continue
751
770
  if 'orphan' in self.metadata[docname]:
752
771
  continue
753
- logger.warning(__("document isn't included in any toctree"),
754
- location=docname)
772
+ logger.warning(
773
+ __("document isn't included in any toctree"), location=docname
774
+ )
775
+ # Call _check_toc_parents here rather than in _get_toctree_ancestors()
776
+ # because that method is called multiple times per document and would
777
+ # lead to duplicate warnings.
778
+ _check_toc_parents(self.toctree_includes)
755
779
 
756
780
  # call check-consistency for all extensions
757
- for domain in self.domains.values():
758
- domain.check_consistency()
781
+ self.domains._check_consistency()
759
782
  self.events.emit('env-check-consistency', self)
760
783
 
761
784
 
785
+ def _differing_config_keys(old: Config, new: Config) -> frozenset[str]:
786
+ """Return a set of keys that differ between two config objects."""
787
+ old_vals = {c.name: c.value for c in old}
788
+ new_vals = {c.name: c.value for c in new}
789
+ not_in_both = old_vals.keys() ^ new_vals.keys()
790
+ different_values = {
791
+ key
792
+ for key in old_vals.keys() & new_vals.keys()
793
+ if stable_str(old_vals[key]) != stable_str(new_vals[key])
794
+ }
795
+ return frozenset(not_in_both | different_values)
796
+
797
+
762
798
  def _traverse_toctree(
763
799
  traversed: set[str],
764
800
  parent: str | None,
@@ -766,9 +802,12 @@ def _traverse_toctree(
766
802
  toctree_includes: dict[str, list[str]],
767
803
  ) -> Iterator[tuple[str | None, str]]:
768
804
  if parent == docname:
769
- logger.warning(__('self referenced toctree found. Ignored.'),
770
- location=docname, type='toc',
771
- subtype='circular')
805
+ logger.warning(
806
+ __('self referenced toctree found. Ignored.'),
807
+ location=docname,
808
+ type='toc',
809
+ subtype='circular',
810
+ )
772
811
  return
773
812
 
774
813
  # traverse toctree by pre-order
@@ -777,8 +816,29 @@ def _traverse_toctree(
777
816
 
778
817
  for child in toctree_includes.get(docname, ()):
779
818
  for sub_parent, sub_docname in _traverse_toctree(
780
- traversed, docname, child, toctree_includes,
819
+ traversed, docname, child, toctree_includes
781
820
  ):
782
821
  if sub_docname not in traversed:
783
822
  yield sub_parent, sub_docname
784
823
  traversed.add(sub_docname)
824
+
825
+
826
+ def _check_toc_parents(toctree_includes: dict[str, list[str]]) -> None:
827
+ toc_parents: dict[str, list[str]] = {}
828
+ for parent, children in toctree_includes.items():
829
+ for child in children:
830
+ toc_parents.setdefault(child, []).append(parent)
831
+
832
+ for doc, parents in sorted(toc_parents.items()):
833
+ if len(parents) > 1:
834
+ logger.info(
835
+ __(
836
+ 'document is referenced in multiple toctrees: %s, selecting: %s <- %s'
837
+ ),
838
+ parents,
839
+ max(parents),
840
+ doc,
841
+ location=doc,
842
+ type='toc',
843
+ subtype='multiple_toc_parents',
844
+ )
@@ -1,6 +1,7 @@
1
1
  """Assets adapter for sphinx.environment."""
2
2
 
3
3
  from sphinx.environment import BuildEnvironment
4
+ from sphinx.util._pathlib import _StrPath
4
5
 
5
6
 
6
7
  class ImageAdapter:
@@ -9,7 +10,7 @@ class ImageAdapter:
9
10
 
10
11
  def get_original_image_uri(self, name: str) -> str:
11
12
  """Get the original image URI."""
12
- while name in self.env.original_image_uri:
13
- name = self.env.original_image_uri[name]
13
+ while _StrPath(name) in self.env.original_image_uri:
14
+ name = self.env.original_image_uri[_StrPath(name)]
14
15
 
15
16
  return name