Sphinx 7.2.5__py3-none-any.whl → 7.3.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 (388) hide show
  1. sphinx/__init__.py +8 -9
  2. sphinx/addnodes.py +31 -28
  3. sphinx/application.py +9 -15
  4. sphinx/builders/__init__.py +5 -6
  5. sphinx/builders/_epub_base.py +17 -9
  6. sphinx/builders/changes.py +10 -5
  7. sphinx/builders/dirhtml.py +4 -2
  8. sphinx/builders/dummy.py +3 -2
  9. sphinx/builders/epub3.py +5 -3
  10. sphinx/builders/gettext.py +24 -7
  11. sphinx/builders/html/__init__.py +88 -96
  12. sphinx/builders/html/_assets.py +16 -16
  13. sphinx/builders/html/transforms.py +4 -2
  14. sphinx/builders/latex/__init__.py +40 -33
  15. sphinx/builders/latex/nodes.py +6 -2
  16. sphinx/builders/latex/transforms.py +17 -8
  17. sphinx/builders/latex/util.py +1 -1
  18. sphinx/builders/linkcheck.py +86 -27
  19. sphinx/builders/manpage.py +8 -6
  20. sphinx/builders/singlehtml.py +5 -4
  21. sphinx/builders/texinfo.py +18 -14
  22. sphinx/builders/text.py +3 -2
  23. sphinx/builders/xml.py +5 -2
  24. sphinx/cmd/build.py +119 -76
  25. sphinx/cmd/make_mode.py +21 -20
  26. sphinx/cmd/quickstart.py +13 -16
  27. sphinx/config.py +432 -250
  28. sphinx/deprecation.py +23 -13
  29. sphinx/directives/__init__.py +8 -8
  30. sphinx/directives/code.py +7 -7
  31. sphinx/directives/other.py +23 -13
  32. sphinx/directives/patches.py +7 -6
  33. sphinx/domains/__init__.py +2 -2
  34. sphinx/domains/c/__init__.py +796 -0
  35. sphinx/domains/c/_ast.py +1421 -0
  36. sphinx/domains/c/_ids.py +65 -0
  37. sphinx/domains/c/_parser.py +1048 -0
  38. sphinx/domains/c/_symbol.py +700 -0
  39. sphinx/domains/changeset.py +11 -7
  40. sphinx/domains/citation.py +5 -2
  41. sphinx/domains/cpp/__init__.py +1089 -0
  42. sphinx/domains/cpp/_ast.py +3635 -0
  43. sphinx/domains/cpp/_ids.py +537 -0
  44. sphinx/domains/cpp/_parser.py +2117 -0
  45. sphinx/domains/cpp/_symbol.py +1092 -0
  46. sphinx/domains/index.py +6 -4
  47. sphinx/domains/javascript.py +16 -13
  48. sphinx/domains/math.py +9 -4
  49. sphinx/domains/python/__init__.py +890 -0
  50. sphinx/domains/python/_annotations.py +507 -0
  51. sphinx/domains/python/_object.py +426 -0
  52. sphinx/domains/rst.py +12 -7
  53. sphinx/domains/{std.py → std/__init__.py} +19 -16
  54. sphinx/environment/__init__.py +21 -19
  55. sphinx/environment/adapters/indexentries.py +2 -2
  56. sphinx/environment/adapters/toctree.py +10 -9
  57. sphinx/environment/collectors/__init__.py +6 -3
  58. sphinx/environment/collectors/asset.py +4 -3
  59. sphinx/environment/collectors/dependencies.py +3 -2
  60. sphinx/environment/collectors/metadata.py +6 -5
  61. sphinx/environment/collectors/title.py +3 -2
  62. sphinx/environment/collectors/toctree.py +5 -4
  63. sphinx/errors.py +13 -2
  64. sphinx/events.py +14 -9
  65. sphinx/ext/apidoc.py +9 -11
  66. sphinx/ext/autodoc/__init__.py +105 -71
  67. sphinx/ext/autodoc/directive.py +7 -6
  68. sphinx/ext/autodoc/importer.py +132 -52
  69. sphinx/ext/autodoc/mock.py +7 -5
  70. sphinx/ext/autodoc/preserve_defaults.py +4 -3
  71. sphinx/ext/autodoc/type_comment.py +2 -1
  72. sphinx/ext/autodoc/typehints.py +5 -4
  73. sphinx/ext/autosectionlabel.py +3 -2
  74. sphinx/ext/autosummary/__init__.py +21 -17
  75. sphinx/ext/autosummary/generate.py +9 -9
  76. sphinx/ext/coverage.py +26 -20
  77. sphinx/ext/doctest.py +38 -33
  78. sphinx/ext/duration.py +1 -0
  79. sphinx/ext/extlinks.py +4 -3
  80. sphinx/ext/githubpages.py +3 -2
  81. sphinx/ext/graphviz.py +10 -7
  82. sphinx/ext/ifconfig.py +5 -5
  83. sphinx/ext/imgconverter.py +6 -5
  84. sphinx/ext/imgmath.py +9 -8
  85. sphinx/ext/inheritance_diagram.py +31 -31
  86. sphinx/ext/intersphinx.py +140 -23
  87. sphinx/ext/linkcode.py +3 -2
  88. sphinx/ext/mathjax.py +2 -1
  89. sphinx/ext/napoleon/__init__.py +12 -7
  90. sphinx/ext/napoleon/docstring.py +34 -32
  91. sphinx/ext/todo.py +10 -7
  92. sphinx/ext/viewcode.py +12 -11
  93. sphinx/extension.py +18 -8
  94. sphinx/highlighting.py +39 -20
  95. sphinx/io.py +17 -8
  96. sphinx/jinja2glue.py +16 -15
  97. sphinx/locale/__init__.py +30 -23
  98. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/ar/LC_MESSAGES/sphinx.po +818 -761
  100. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  101. sphinx/locale/bg/LC_MESSAGES/sphinx.po +811 -754
  102. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/bn/LC_MESSAGES/sphinx.po +835 -778
  104. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/ca/LC_MESSAGES/sphinx.po +864 -807
  106. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  107. sphinx/locale/cak/LC_MESSAGES/sphinx.po +816 -759
  108. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/cs/LC_MESSAGES/sphinx.po +837 -780
  110. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/cy/LC_MESSAGES/sphinx.po +819 -762
  112. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  113. sphinx/locale/da/LC_MESSAGES/sphinx.po +838 -781
  114. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/de/LC_MESSAGES/sphinx.po +838 -781
  116. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +811 -754
  118. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  119. sphinx/locale/el/LC_MESSAGES/sphinx.po +853 -796
  120. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +811 -754
  122. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +811 -754
  124. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  125. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +856 -799
  126. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +811 -754
  128. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/eo/LC_MESSAGES/sphinx.po +820 -763
  130. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  131. sphinx/locale/es/LC_MESSAGES/sphinx.po +856 -799
  132. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +811 -754
  134. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/et/LC_MESSAGES/sphinx.po +845 -788
  136. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  137. sphinx/locale/eu/LC_MESSAGES/sphinx.po +837 -780
  138. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  139. sphinx/locale/fa/LC_MESSAGES/sphinx.po +854 -797
  140. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  141. sphinx/locale/fi/LC_MESSAGES/sphinx.po +816 -759
  142. sphinx/locale/fr/LC_MESSAGES/sphinx.js +1 -1
  143. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/fr/LC_MESSAGES/sphinx.po +904 -847
  145. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  146. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +811 -754
  147. sphinx/locale/gl/LC_MESSAGES/sphinx.js +54 -54
  148. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  149. sphinx/locale/gl/LC_MESSAGES/sphinx.po +1506 -1449
  150. sphinx/locale/he/LC_MESSAGES/sphinx.js +1 -1
  151. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  152. sphinx/locale/he/LC_MESSAGES/sphinx.po +823 -766
  153. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/hi/LC_MESSAGES/sphinx.po +853 -796
  155. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +811 -754
  157. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  158. sphinx/locale/hr/LC_MESSAGES/sphinx.po +844 -787
  159. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/hu/LC_MESSAGES/sphinx.po +837 -780
  161. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/id/LC_MESSAGES/sphinx.po +854 -797
  163. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/is/LC_MESSAGES/sphinx.po +811 -754
  165. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/it/LC_MESSAGES/sphinx.po +837 -780
  167. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/ja/LC_MESSAGES/sphinx.po +853 -796
  169. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/ka/LC_MESSAGES/sphinx.po +848 -791
  171. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/ko/LC_MESSAGES/sphinx.po +855 -798
  173. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/lt/LC_MESSAGES/sphinx.po +837 -780
  175. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/lv/LC_MESSAGES/sphinx.po +837 -780
  177. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/mk/LC_MESSAGES/sphinx.po +825 -768
  179. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.js +27 -27
  180. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  181. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +876 -818
  182. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  183. sphinx/locale/ne/LC_MESSAGES/sphinx.po +837 -780
  184. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  185. sphinx/locale/nl/LC_MESSAGES/sphinx.po +844 -787
  186. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  187. sphinx/locale/pl/LC_MESSAGES/sphinx.po +845 -788
  188. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  189. sphinx/locale/pt/LC_MESSAGES/sphinx.po +811 -754
  190. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  191. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +908 -851
  192. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  193. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +837 -780
  194. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  195. sphinx/locale/ro/LC_MESSAGES/sphinx.po +837 -780
  196. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  197. sphinx/locale/ru/LC_MESSAGES/sphinx.po +838 -781
  198. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  199. sphinx/locale/si/LC_MESSAGES/sphinx.po +823 -766
  200. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  201. sphinx/locale/sk/LC_MESSAGES/sphinx.po +854 -797
  202. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  203. sphinx/locale/sl/LC_MESSAGES/sphinx.po +832 -775
  204. sphinx/locale/sphinx.pot +813 -755
  205. sphinx/locale/sq/LC_MESSAGES/sphinx.js +1 -1
  206. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/sq/LC_MESSAGES/sphinx.po +865 -808
  208. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  209. sphinx/locale/sr/LC_MESSAGES/sphinx.po +835 -778
  210. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
  212. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/sv/LC_MESSAGES/sphinx.po +837 -780
  214. sphinx/locale/ta/LC_MESSAGES/sphinx.js +54 -54
  215. sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
  216. sphinx/locale/ta/LC_MESSAGES/sphinx.po +1530 -1473
  217. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  218. sphinx/locale/te/LC_MESSAGES/sphinx.po +811 -754
  219. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  220. sphinx/locale/tr/LC_MESSAGES/sphinx.po +853 -796
  221. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  222. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +833 -776
  223. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  224. sphinx/locale/ur/LC_MESSAGES/sphinx.po +811 -754
  225. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  226. sphinx/locale/vi/LC_MESSAGES/sphinx.po +837 -780
  227. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  228. sphinx/locale/yue/LC_MESSAGES/sphinx.po +811 -754
  229. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.mo +0 -0
  230. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +855 -798
  231. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  232. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +811 -754
  233. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.js +1 -1
  234. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  235. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +879 -822
  236. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  237. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +811 -754
  238. sphinx/parsers.py +7 -5
  239. sphinx/project.py +18 -11
  240. sphinx/pycode/__init__.py +6 -5
  241. sphinx/pycode/ast.py +23 -8
  242. sphinx/pycode/parser.py +6 -5
  243. sphinx/registry.py +12 -6
  244. sphinx/roles.py +103 -57
  245. sphinx/search/__init__.py +17 -18
  246. sphinx/search/da.py +2 -2
  247. sphinx/search/de.py +2 -2
  248. sphinx/search/en.py +1 -1
  249. sphinx/search/es.py +2 -2
  250. sphinx/search/fi.py +2 -2
  251. sphinx/search/fr.py +2 -2
  252. sphinx/search/hu.py +2 -2
  253. sphinx/search/it.py +2 -2
  254. sphinx/search/ja.py +13 -22
  255. sphinx/search/nl.py +2 -2
  256. sphinx/search/no.py +2 -2
  257. sphinx/search/pt.py +2 -2
  258. sphinx/search/ro.py +1 -1
  259. sphinx/search/ru.py +2 -2
  260. sphinx/search/sv.py +2 -2
  261. sphinx/search/tr.py +1 -1
  262. sphinx/search/zh.py +2 -3
  263. sphinx/templates/graphviz/graphviz.css +1 -1
  264. sphinx/testing/fixtures.py +41 -24
  265. sphinx/testing/path.py +1 -1
  266. sphinx/testing/util.py +142 -53
  267. sphinx/texinputs/sphinx.xdy +1 -1
  268. sphinx/texinputs/sphinxlatextables.sty +1 -1
  269. sphinx/texinputs/sphinxpackagesubstitutefont.sty +21 -0
  270. sphinx/themes/agogo/layout.html +4 -4
  271. sphinx/themes/agogo/static/agogo.css_t +1 -1
  272. sphinx/themes/agogo/theme.toml +22 -0
  273. sphinx/themes/basic/defindex.html +1 -1
  274. sphinx/themes/basic/domainindex.html +1 -1
  275. sphinx/themes/basic/genindex-single.html +1 -1
  276. sphinx/themes/basic/genindex-split.html +1 -1
  277. sphinx/themes/basic/genindex.html +1 -1
  278. sphinx/themes/basic/globaltoc.html +1 -1
  279. sphinx/themes/basic/layout.html +1 -1
  280. sphinx/themes/basic/localtoc.html +1 -1
  281. sphinx/themes/basic/page.html +1 -1
  282. sphinx/themes/basic/relations.html +1 -1
  283. sphinx/themes/basic/search.html +5 -20
  284. sphinx/themes/basic/searchbox.html +3 -3
  285. sphinx/themes/basic/searchfield.html +3 -3
  286. sphinx/themes/basic/sourcelink.html +1 -1
  287. sphinx/themes/basic/static/basic.css_t +1 -1
  288. sphinx/themes/basic/static/doctools.js +1 -1
  289. sphinx/themes/basic/static/language_data.js_t +2 -2
  290. sphinx/themes/basic/static/searchtools.js +105 -60
  291. sphinx/themes/basic/theme.toml +23 -0
  292. sphinx/themes/bizstyle/layout.html +1 -6
  293. sphinx/themes/bizstyle/static/bizstyle.css_t +1 -1
  294. sphinx/themes/bizstyle/static/bizstyle.js_t +1 -1
  295. sphinx/themes/bizstyle/static/css3-mediaqueries_src.js +3 -3
  296. sphinx/themes/bizstyle/theme.toml +12 -0
  297. sphinx/themes/classic/layout.html +1 -1
  298. sphinx/themes/classic/static/classic.css_t +1 -1
  299. sphinx/themes/classic/static/sidebar.js_t +1 -1
  300. sphinx/themes/classic/theme.toml +34 -0
  301. sphinx/themes/default/theme.toml +2 -0
  302. sphinx/themes/epub/epub-cover.html +1 -1
  303. sphinx/themes/epub/layout.html +1 -1
  304. sphinx/themes/epub/static/epub.css_t +1 -1
  305. sphinx/themes/epub/theme.toml +10 -0
  306. sphinx/themes/haiku/layout.html +3 -3
  307. sphinx/themes/haiku/static/haiku.css_t +2 -2
  308. sphinx/themes/haiku/theme.toml +16 -0
  309. sphinx/themes/nature/static/nature.css_t +1 -1
  310. sphinx/themes/nature/theme.toml +6 -0
  311. sphinx/themes/nonav/layout.html +1 -1
  312. sphinx/themes/nonav/static/nonav.css_t +1 -1
  313. sphinx/themes/nonav/theme.toml +10 -0
  314. sphinx/themes/pyramid/static/epub.css_t +1 -1
  315. sphinx/themes/pyramid/static/pyramid.css_t +1 -1
  316. sphinx/themes/pyramid/theme.toml +6 -0
  317. sphinx/themes/scrolls/artwork/logo.svg +1 -1
  318. sphinx/themes/scrolls/layout.html +2 -2
  319. sphinx/themes/scrolls/static/scrolls.css_t +1 -1
  320. sphinx/themes/scrolls/theme.toml +15 -0
  321. sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +1 -1
  322. sphinx/themes/sphinxdoc/theme.toml +6 -0
  323. sphinx/themes/traditional/static/traditional.css_t +1 -1
  324. sphinx/themes/traditional/theme.toml +9 -0
  325. sphinx/theming.py +427 -131
  326. sphinx/transforms/__init__.py +21 -24
  327. sphinx/transforms/compact_bullet_list.py +5 -5
  328. sphinx/transforms/i18n.py +30 -28
  329. sphinx/transforms/post_transforms/__init__.py +9 -7
  330. sphinx/transforms/post_transforms/code.py +4 -1
  331. sphinx/transforms/post_transforms/images.py +17 -13
  332. sphinx/transforms/references.py +3 -1
  333. sphinx/util/__init__.py +15 -11
  334. sphinx/util/_io.py +34 -0
  335. sphinx/util/_pathlib.py +23 -18
  336. sphinx/util/build_phase.py +1 -0
  337. sphinx/util/cfamily.py +19 -11
  338. sphinx/util/console.py +101 -21
  339. sphinx/util/display.py +3 -2
  340. sphinx/util/docfields.py +12 -8
  341. sphinx/util/docutils.py +21 -35
  342. sphinx/util/exceptions.py +3 -2
  343. sphinx/util/fileutil.py +5 -5
  344. sphinx/util/http_date.py +9 -2
  345. sphinx/util/i18n.py +40 -9
  346. sphinx/util/inspect.py +317 -245
  347. sphinx/util/inventory.py +22 -5
  348. sphinx/util/logging.py +81 -7
  349. sphinx/util/matching.py +2 -1
  350. sphinx/util/math.py +1 -2
  351. sphinx/util/nodes.py +39 -29
  352. sphinx/util/osutil.py +25 -6
  353. sphinx/util/parallel.py +6 -1
  354. sphinx/util/requests.py +8 -5
  355. sphinx/util/rst.py +8 -6
  356. sphinx/util/tags.py +3 -3
  357. sphinx/util/template.py +8 -3
  358. sphinx/util/typing.py +76 -42
  359. sphinx/versioning.py +6 -2
  360. sphinx/writers/html.py +1 -1
  361. sphinx/writers/html5.py +17 -13
  362. sphinx/writers/latex.py +12 -12
  363. sphinx/writers/manpage.py +13 -7
  364. sphinx/writers/texinfo.py +13 -10
  365. sphinx/writers/text.py +13 -23
  366. sphinx/writers/xml.py +1 -1
  367. sphinx-7.2.5.dist-info/LICENSE → sphinx-7.3.0.dist-info/LICENSE.rst +1 -1
  368. {sphinx-7.2.5.dist-info → sphinx-7.3.0.dist-info}/METADATA +13 -12
  369. sphinx-7.3.0.dist-info/RECORD +581 -0
  370. sphinx/domains/c.py +0 -3906
  371. sphinx/domains/cpp.py +0 -8233
  372. sphinx/domains/python.py +0 -1769
  373. sphinx/themes/agogo/theme.conf +0 -20
  374. sphinx/themes/basic/theme.conf +0 -16
  375. sphinx/themes/bizstyle/theme.conf +0 -10
  376. sphinx/themes/classic/theme.conf +0 -32
  377. sphinx/themes/default/theme.conf +0 -2
  378. sphinx/themes/epub/theme.conf +0 -8
  379. sphinx/themes/haiku/theme.conf +0 -14
  380. sphinx/themes/nature/theme.conf +0 -4
  381. sphinx/themes/nonav/theme.conf +0 -8
  382. sphinx/themes/pyramid/theme.conf +0 -4
  383. sphinx/themes/scrolls/theme.conf +0 -13
  384. sphinx/themes/sphinxdoc/theme.conf +0 -4
  385. sphinx/themes/traditional/theme.conf +0 -7
  386. sphinx-7.2.5.dist-info/RECORD +0 -569
  387. {sphinx-7.2.5.dist-info → sphinx-7.3.0.dist-info}/WHEEL +0 -0
  388. {sphinx-7.2.5.dist-info → sphinx-7.3.0.dist-info}/entry_points.txt +0 -0
sphinx/config.py CHANGED
@@ -2,33 +2,45 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import sys
5
6
  import time
6
7
  import traceback
7
8
  import types
9
+ import warnings
8
10
  from os import getenv, path
9
- from typing import TYPE_CHECKING, Any, Callable, NamedTuple
11
+ from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Union
10
12
 
13
+ from sphinx.deprecation import RemovedInSphinx90Warning
11
14
  from sphinx.errors import ConfigError, ExtensionError
12
15
  from sphinx.locale import _, __
13
16
  from sphinx.util import logging
14
17
  from sphinx.util.osutil import fs_encoding
15
- from sphinx.util.typing import NoneType
18
+ from sphinx.util.typing import ExtensionMetadata, NoneType
16
19
 
17
- try:
18
- from contextlib import chdir # type: ignore[attr-defined]
19
- except ImportError:
20
+ if sys.version_info >= (3, 11):
21
+ from contextlib import chdir
22
+ else:
20
23
  from sphinx.util.osutil import _chdir as chdir
21
24
 
22
25
  if TYPE_CHECKING:
23
26
  import os
24
- from collections.abc import Generator, Iterator, Sequence
27
+ from collections.abc import Collection, Iterator, Sequence, Set
25
28
 
26
29
  from sphinx.application import Sphinx
27
30
  from sphinx.environment import BuildEnvironment
28
31
  from sphinx.util.tags import Tags
32
+ from sphinx.util.typing import _ExtensionSetupFunc
29
33
 
30
34
  logger = logging.getLogger(__name__)
31
35
 
36
+ _ConfigRebuild = Literal[
37
+ '', 'env', 'epub', 'gettext', 'html',
38
+ # sphinxcontrib-applehelp
39
+ 'applehelp',
40
+ # sphinxcontrib-devhelp
41
+ 'devhelp',
42
+ ]
43
+
32
44
  CONFIG_FILENAME = 'conf.py'
33
45
  UNSERIALIZABLE_TYPES = (type, types.ModuleType, types.FunctionType)
34
46
 
@@ -36,20 +48,33 @@ UNSERIALIZABLE_TYPES = (type, types.ModuleType, types.FunctionType)
36
48
  class ConfigValue(NamedTuple):
37
49
  name: str
38
50
  value: Any
39
- rebuild: bool | str
51
+ rebuild: _ConfigRebuild
40
52
 
41
53
 
42
- def is_serializable(obj: Any) -> bool:
54
+ def is_serializable(obj: object, *, _recursive_guard: frozenset[int] = frozenset()) -> bool:
43
55
  """Check if object is serializable or not."""
44
56
  if isinstance(obj, UNSERIALIZABLE_TYPES):
45
57
  return False
46
- elif isinstance(obj, dict):
58
+
59
+ # use id() to handle un-hashable objects
60
+ if id(obj) in _recursive_guard:
61
+ return True
62
+
63
+ if isinstance(obj, dict):
64
+ guard = _recursive_guard | {id(obj)}
47
65
  for key, value in obj.items():
48
- if not is_serializable(key) or not is_serializable(value):
66
+ if (
67
+ not is_serializable(key, _recursive_guard=guard)
68
+ or not is_serializable(value, _recursive_guard=guard)
69
+ ):
49
70
  return False
50
- elif isinstance(obj, (list, tuple, set)):
51
- return all(is_serializable(i) for i in obj)
71
+ elif isinstance(obj, (list, tuple, set, frozenset)):
72
+ guard = _recursive_guard | {id(obj)}
73
+ return all(is_serializable(item, _recursive_guard=guard) for item in obj)
52
74
 
75
+ # if an issue occurs for a non-serializable type, pickle will complain
76
+ # since the object is likely coming from a third-party extension (we
77
+ # natively expect 'simple' types and not weird ones)
53
78
  return True
54
79
 
55
80
 
@@ -59,6 +84,7 @@ class ENUM:
59
84
  Example:
60
85
  app.add_config_value('latex_show_urls', 'no', None, ENUM('no', 'footnote', 'inline'))
61
86
  """
87
+
62
88
  def __init__(self, *candidates: str | bool | None) -> None:
63
89
  self.candidates = candidates
64
90
 
@@ -69,10 +95,92 @@ class ENUM:
69
95
  return value in self.candidates
70
96
 
71
97
 
98
+ _OptValidTypes = Union[tuple[()], tuple[type, ...], frozenset[type], ENUM]
99
+
100
+
101
+ class _Opt:
102
+ __slots__ = 'default', 'rebuild', 'valid_types'
103
+
104
+ default: Any
105
+ rebuild: _ConfigRebuild
106
+ valid_types: _OptValidTypes
107
+
108
+ def __init__(
109
+ self,
110
+ default: Any,
111
+ rebuild: _ConfigRebuild,
112
+ valid_types: _OptValidTypes,
113
+ ) -> None:
114
+ """Configuration option type for Sphinx.
115
+
116
+ The type is intended to be immutable; changing the field values
117
+ is an unsupported action.
118
+ No validation is performed on the values, though consumers will
119
+ likely expect them to be of the types advertised.
120
+ The old tuple-based interface will be removed in Sphinx 9.
121
+ """
122
+ super().__setattr__('default', default)
123
+ super().__setattr__('rebuild', rebuild)
124
+ super().__setattr__('valid_types', valid_types)
125
+
126
+ def __repr__(self) -> str:
127
+ return (
128
+ f'{self.__class__.__qualname__}('
129
+ f'default={self.default!r}, '
130
+ f'rebuild={self.rebuild!r}, '
131
+ f'valid_types={self.valid_types!r})'
132
+ )
133
+
134
+ def __eq__(self, other: object) -> bool:
135
+ if isinstance(other, _Opt):
136
+ self_tpl = (self.default, self.rebuild, self.valid_types)
137
+ other_tpl = (other.default, other.rebuild, other.valid_types)
138
+ return self_tpl == other_tpl
139
+ return NotImplemented
140
+
141
+ def __lt__(self, other: _Opt) -> bool:
142
+ if self.__class__ is other.__class__:
143
+ self_tpl = (self.default, self.rebuild, self.valid_types)
144
+ other_tpl = (other.default, other.rebuild, other.valid_types)
145
+ return self_tpl > other_tpl
146
+ return NotImplemented
147
+
148
+ def __hash__(self) -> int:
149
+ return hash((self.default, self.rebuild, self.valid_types))
150
+
151
+ def __setattr__(self, key: str, value: Any) -> None:
152
+ if key in {'default', 'rebuild', 'valid_types'}:
153
+ msg = f'{self.__class__.__name__!r} object does not support assignment to {key!r}'
154
+ raise TypeError(msg)
155
+ super().__setattr__(key, value)
156
+
157
+ def __delattr__(self, key: str) -> None:
158
+ if key in {'default', 'rebuild', 'valid_types'}:
159
+ msg = f'{self.__class__.__name__!r} object does not support deletion of {key!r}'
160
+ raise TypeError(msg)
161
+ super().__delattr__(key)
162
+
163
+ def __getstate__(self) -> tuple[Any, _ConfigRebuild, _OptValidTypes]:
164
+ return self.default, self.rebuild, self.valid_types
165
+
166
+ def __setstate__(self, state: tuple[Any, _ConfigRebuild, _OptValidTypes]) -> None:
167
+ default, rebuild, valid_types = state
168
+ super().__setattr__('default', default)
169
+ super().__setattr__('rebuild', rebuild)
170
+ super().__setattr__('valid_types', valid_types)
171
+
172
+ def __getitem__(self, item: int | slice) -> Any:
173
+ warnings.warn(
174
+ f'The {self.__class__.__name__!r} object tuple interface is deprecated, '
175
+ "use attribute access instead for 'default', 'rebuild', and 'valid_types'.",
176
+ RemovedInSphinx90Warning, stacklevel=2)
177
+ return (self.default, self.rebuild, self.valid_types)[item]
178
+
179
+
72
180
  class Config:
73
181
  r"""Configuration file abstraction.
74
182
 
75
- The config object makes the values of all config values available as
183
+ The Config object makes the values of all config options available as
76
184
  attributes.
77
185
 
78
186
  It is exposed via the :py:class:`~sphinx.application.Sphinx`\ ``.config``
@@ -81,97 +189,119 @@ class Config:
81
189
  ``app.config.language`` or ``env.config.language``.
82
190
  """
83
191
 
84
- # the values are: (default, what needs to be rebuilt if changed)
192
+ # The values are:
193
+ # 1. Default
194
+ # 2. What needs to be rebuilt if changed
195
+ # 3. Valid types
85
196
 
86
- # If you add a value here, don't forget to include it in the
87
- # quickstart.py file template as well as in the docs!
197
+ # If you add a value here, remember to include it in the docs!
88
198
 
89
- config_values: dict[str, tuple] = {
199
+ config_values: dict[str, _Opt] = {
90
200
  # general options
91
- 'project': ('Python', 'env', []),
92
- 'author': ('unknown', 'env', []),
93
- 'project_copyright': ('', 'html', [str, tuple, list]),
94
- 'copyright': (lambda c: c.project_copyright, 'html', [str, tuple, list]),
95
- 'version': ('', 'env', []),
96
- 'release': ('', 'env', []),
97
- 'today': ('', 'env', []),
201
+ 'project': _Opt('Python', 'env', ()),
202
+ 'author': _Opt('unknown', 'env', ()),
203
+ 'project_copyright': _Opt('', 'html', frozenset((str, tuple, list))),
204
+ 'copyright': _Opt(
205
+ lambda c: c.project_copyright, 'html', frozenset((str, tuple, list))),
206
+ 'version': _Opt('', 'env', ()),
207
+ 'release': _Opt('', 'env', ()),
208
+ 'today': _Opt('', 'env', ()),
98
209
  # the real default is locale-dependent
99
- 'today_fmt': (None, 'env', [str]),
100
-
101
- 'language': ('en', 'env', [str]),
102
- 'locale_dirs': (['locales'], 'env', []),
103
- 'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]),
104
- 'gettext_allow_fuzzy_translations': (False, 'gettext', []),
105
- 'translation_progress_classes': (False, 'env',
106
- ENUM(True, False, 'translated', 'untranslated')),
107
-
108
- 'master_doc': ('index', 'env', []),
109
- 'root_doc': (lambda config: config.master_doc, 'env', []),
110
- 'source_suffix': ({'.rst': 'restructuredtext'}, 'env', Any),
111
- 'source_encoding': ('utf-8-sig', 'env', []),
112
- 'exclude_patterns': ([], 'env', [str]),
113
- 'include_patterns': (["**"], 'env', [str]),
114
- 'default_role': (None, 'env', [str]),
115
- 'add_function_parentheses': (True, 'env', []),
116
- 'add_module_names': (True, 'env', []),
117
- 'toc_object_entries': (True, 'env', [bool]),
118
- 'toc_object_entries_show_parents': ('domain', 'env',
119
- ENUM('domain', 'all', 'hide')),
120
- 'trim_footnote_reference_space': (False, 'env', []),
121
- 'show_authors': (False, 'env', []),
122
- 'pygments_style': (None, 'html', [str]),
123
- 'highlight_language': ('default', 'env', []),
124
- 'highlight_options': ({}, 'env', []),
125
- 'templates_path': ([], 'html', []),
126
- 'template_bridge': (None, 'html', [str]),
127
- 'keep_warnings': (False, 'env', []),
128
- 'suppress_warnings': ([], 'env', []),
129
- 'modindex_common_prefix': ([], 'html', []),
130
- 'rst_epilog': (None, 'env', [str]),
131
- 'rst_prolog': (None, 'env', [str]),
132
- 'trim_doctest_flags': (True, 'env', []),
133
- 'primary_domain': ('py', 'env', [NoneType]),
134
- 'needs_sphinx': (None, None, [str]),
135
- 'needs_extensions': ({}, None, []),
136
- 'manpages_url': (None, 'env', []),
137
- 'nitpicky': (False, None, []),
138
- 'nitpick_ignore': ([], None, [set, list, tuple]),
139
- 'nitpick_ignore_regex': ([], None, [set, list, tuple]),
140
- 'numfig': (False, 'env', []),
141
- 'numfig_secnum_depth': (1, 'env', []),
142
- 'numfig_format': ({}, 'env', []), # will be initialized in init_numfig_format()
143
- 'maximum_signature_line_length': (None, 'env', {int, None}),
144
- 'math_number_all': (False, 'env', []),
145
- 'math_eqref_format': (None, 'env', [str]),
146
- 'math_numfig': (True, 'env', []),
147
- 'tls_verify': (True, 'env', []),
148
- 'tls_cacerts': (None, 'env', []),
149
- 'user_agent': (None, 'env', [str]),
150
- 'smartquotes': (True, 'env', []),
151
- 'smartquotes_action': ('qDe', 'env', []),
152
- 'smartquotes_excludes': ({'languages': ['ja'],
153
- 'builders': ['man', 'text']},
154
- 'env', []),
155
- 'option_emphasise_placeholders': (False, 'env', []),
210
+ 'today_fmt': _Opt(None, 'env', frozenset((str,))),
211
+
212
+ 'language': _Opt('en', 'env', frozenset((str,))),
213
+ 'locale_dirs': _Opt(['locales'], 'env', ()),
214
+ 'figure_language_filename': _Opt('{root}.{language}{ext}', 'env', frozenset((str,))),
215
+ 'gettext_allow_fuzzy_translations': _Opt(False, 'gettext', ()),
216
+ 'translation_progress_classes': _Opt(
217
+ False, 'env', ENUM(True, False, 'translated', 'untranslated')),
218
+
219
+ 'master_doc': _Opt('index', 'env', ()),
220
+ 'root_doc': _Opt(lambda config: config.master_doc, 'env', ()),
221
+ # ``source_suffix`` type is actually ``dict[str, str | None]``:
222
+ # see ``convert_source_suffix()`` below.
223
+ 'source_suffix': _Opt(
224
+ {'.rst': 'restructuredtext'}, 'env', Any), # type: ignore[arg-type]
225
+ 'source_encoding': _Opt('utf-8-sig', 'env', ()),
226
+ 'exclude_patterns': _Opt([], 'env', frozenset((str,))),
227
+ 'include_patterns': _Opt(["**"], 'env', frozenset((str,))),
228
+ 'default_role': _Opt(None, 'env', frozenset((str,))),
229
+ 'add_function_parentheses': _Opt(True, 'env', ()),
230
+ 'add_module_names': _Opt(True, 'env', ()),
231
+ 'toc_object_entries': _Opt(True, 'env', frozenset((bool,))),
232
+ 'toc_object_entries_show_parents': _Opt(
233
+ 'domain', 'env', ENUM('domain', 'all', 'hide')),
234
+ 'trim_footnote_reference_space': _Opt(False, 'env', ()),
235
+ 'show_authors': _Opt(False, 'env', ()),
236
+ 'pygments_style': _Opt(None, 'html', frozenset((str,))),
237
+ 'highlight_language': _Opt('default', 'env', ()),
238
+ 'highlight_options': _Opt({}, 'env', ()),
239
+ 'templates_path': _Opt([], 'html', ()),
240
+ 'template_bridge': _Opt(None, 'html', frozenset((str,))),
241
+ 'keep_warnings': _Opt(False, 'env', ()),
242
+ 'suppress_warnings': _Opt([], 'env', ()),
243
+ 'show_warning_types': _Opt(False, 'env', frozenset((bool,))),
244
+ 'modindex_common_prefix': _Opt([], 'html', ()),
245
+ 'rst_epilog': _Opt(None, 'env', frozenset((str,))),
246
+ 'rst_prolog': _Opt(None, 'env', frozenset((str,))),
247
+ 'trim_doctest_flags': _Opt(True, 'env', ()),
248
+ 'primary_domain': _Opt('py', 'env', frozenset((NoneType,))),
249
+ 'needs_sphinx': _Opt(None, '', frozenset((str,))),
250
+ 'needs_extensions': _Opt({}, '', ()),
251
+ 'manpages_url': _Opt(None, 'env', ()),
252
+ 'nitpicky': _Opt(False, '', ()),
253
+ 'nitpick_ignore': _Opt([], '', frozenset((set, list, tuple))),
254
+ 'nitpick_ignore_regex': _Opt([], '', frozenset((set, list, tuple))),
255
+ 'numfig': _Opt(False, 'env', ()),
256
+ 'numfig_secnum_depth': _Opt(1, 'env', ()),
257
+ 'numfig_format': _Opt({}, 'env', ()), # will be initialized in init_numfig_format()
258
+ 'maximum_signature_line_length': _Opt(
259
+ None, 'env', frozenset((int, NoneType))),
260
+ 'math_number_all': _Opt(False, 'env', ()),
261
+ 'math_eqref_format': _Opt(None, 'env', frozenset((str,))),
262
+ 'math_numfig': _Opt(True, 'env', ()),
263
+ 'tls_verify': _Opt(True, 'env', ()),
264
+ 'tls_cacerts': _Opt(None, 'env', ()),
265
+ 'user_agent': _Opt(None, 'env', frozenset((str,))),
266
+ 'smartquotes': _Opt(True, 'env', ()),
267
+ 'smartquotes_action': _Opt('qDe', 'env', ()),
268
+ 'smartquotes_excludes': _Opt(
269
+ {'languages': ['ja'], 'builders': ['man', 'text']}, 'env', ()),
270
+ 'option_emphasise_placeholders': _Opt(False, 'env', ()),
156
271
  }
157
272
 
158
273
  def __init__(self, config: dict[str, Any] | None = None,
159
274
  overrides: dict[str, Any] | None = None) -> None:
160
- config = config or {}
161
- self.overrides = dict(overrides) if overrides is not None else {}
162
- self.values = Config.config_values.copy()
163
- self._raw_config = config
164
- self.setup: Callable | None = config.get('setup', None)
165
-
166
- if 'extensions' in self.overrides:
167
- if isinstance(self.overrides['extensions'], str):
168
- config['extensions'] = self.overrides.pop('extensions').split(',')
275
+ raw_config: dict[str, Any] = config or {}
276
+ self._overrides = dict(overrides) if overrides is not None else {}
277
+ self._options = Config.config_values.copy()
278
+ self._raw_config = raw_config
279
+
280
+ for name in list(self._overrides.keys()):
281
+ if '.' in name:
282
+ real_name, key = name.split('.', 1)
283
+ raw_config.setdefault(real_name, {})[key] = self._overrides.pop(name)
284
+
285
+ self.setup: _ExtensionSetupFunc | None = raw_config.get('setup')
286
+
287
+ if 'extensions' in self._overrides:
288
+ extensions = self._overrides.pop('extensions')
289
+ if isinstance(extensions, str):
290
+ raw_config['extensions'] = extensions.split(',')
169
291
  else:
170
- config['extensions'] = self.overrides.pop('extensions')
171
- self.extensions: list[str] = config.get('extensions', [])
292
+ raw_config['extensions'] = extensions
293
+ self.extensions: list[str] = raw_config.get('extensions', [])
294
+
295
+ @property
296
+ def values(self) -> dict[str, _Opt]:
297
+ return self._options
298
+
299
+ @property
300
+ def overrides(self) -> dict[str, Any]:
301
+ return self._overrides
172
302
 
173
303
  @classmethod
174
- def read(cls, confdir: str | os.PathLike[str], overrides: dict | None = None,
304
+ def read(cls: type[Config], confdir: str | os.PathLike[str], overrides: dict | None = None,
175
305
  tags: Tags | None = None) -> Config:
176
306
  """Create a Config object from configuration file."""
177
307
  filename = path.join(confdir, CONFIG_FILENAME)
@@ -190,106 +320,103 @@ class Config:
190
320
  "Falling back to 'en' (English)."))
191
321
  namespace["language"] = "en"
192
322
 
193
- return cls(namespace, overrides or {})
323
+ return cls(namespace, overrides)
194
324
 
195
- def convert_overrides(self, name: str, value: Any) -> Any:
196
- if not isinstance(value, str):
325
+ def convert_overrides(self, name: str, value: str) -> Any:
326
+ opt = self._options[name]
327
+ default = opt.default
328
+ valid_types = opt.valid_types
329
+ if valid_types == Any:
197
330
  return value
198
- else:
199
- defvalue = self.values[name][0]
200
- if self.values[name][2] == Any:
201
- return value
202
- elif self.values[name][2] == {bool, str}:
203
- if value == '0':
204
- # given falsy string from command line option
205
- return False
206
- elif value == '1':
207
- return True
208
- else:
209
- return value
210
- elif type(defvalue) is bool or self.values[name][2] == [bool]:
211
- if value == '0':
212
- # given falsy string from command line option
213
- return False
214
- else:
215
- return bool(value)
216
- elif isinstance(defvalue, dict):
217
- raise ValueError(__('cannot override dictionary config setting %r, '
218
- 'ignoring (use %r to set individual elements)') %
219
- (name, name + '.key=value'))
220
- elif isinstance(defvalue, list):
221
- return value.split(',')
222
- elif isinstance(defvalue, int):
223
- try:
224
- return int(value)
225
- except ValueError as exc:
226
- raise ValueError(__('invalid number %r for config value %r, ignoring') %
227
- (value, name)) from exc
228
- elif callable(defvalue):
331
+ elif (type(default) is bool
332
+ or (not isinstance(valid_types, ENUM)
333
+ and len(valid_types) == 1 and bool in valid_types)):
334
+ if isinstance(valid_types, ENUM) or len(valid_types) > 1:
335
+ # if valid_types are given, and non-bool valid types exist,
336
+ # return the value without coercing to a Boolean.
229
337
  return value
230
- elif defvalue is not None and not isinstance(defvalue, str):
231
- raise ValueError(__('cannot override config setting %r with unsupported '
232
- 'type, ignoring') % name)
233
- else:
234
- return value
235
-
236
- def pre_init_values(self) -> None:
237
- """
238
- Initialize some limited config variables before initializing i18n and loading
239
- extensions.
240
- """
241
- variables = ['needs_sphinx', 'suppress_warnings', 'language', 'locale_dirs']
242
- for name in variables:
338
+ # given falsy string from a command line option
339
+ return value not in {'0', ''}
340
+ elif isinstance(default, dict):
341
+ raise ValueError(__('cannot override dictionary config setting %r, '
342
+ 'ignoring (use %r to set individual elements)') %
343
+ (name, f'{name}.key=value'))
344
+ elif isinstance(default, list):
345
+ return value.split(',')
346
+ elif isinstance(default, int):
243
347
  try:
244
- if name in self.overrides:
245
- self.__dict__[name] = self.convert_overrides(name, self.overrides[name])
246
- elif name in self._raw_config:
247
- self.__dict__[name] = self._raw_config[name]
348
+ return int(value)
248
349
  except ValueError as exc:
249
- logger.warning("%s", exc)
350
+ raise ValueError(__('invalid number %r for config value %r, ignoring') %
351
+ (value, name)) from exc
352
+ elif callable(default):
353
+ return value
354
+ elif default is not None and not isinstance(default, str):
355
+ raise ValueError(__('cannot override config setting %r with unsupported '
356
+ 'type, ignoring') % name)
357
+ else:
358
+ return value
359
+
360
+ @staticmethod
361
+ def pre_init_values() -> None:
362
+ # method only retained for compatibility
363
+ pass
364
+ # warnings.warn(
365
+ # 'Config.pre_init_values() will be removed in Sphinx 9.0 or later',
366
+ # RemovedInSphinx90Warning, stacklevel=2)
250
367
 
251
368
  def init_values(self) -> None:
252
- config = self._raw_config
253
- for valname, value in self.overrides.items():
369
+ # method only retained for compatibility
370
+ self._report_override_warnings()
371
+ # warnings.warn(
372
+ # 'Config.init_values() will be removed in Sphinx 9.0 or later',
373
+ # RemovedInSphinx90Warning, stacklevel=2)
374
+
375
+ def _report_override_warnings(self) -> None:
376
+ for name in self._overrides:
377
+ if name not in self._options:
378
+ logger.warning(__('unknown config value %r in override, ignoring'), name)
379
+
380
+ def __repr__(self) -> str:
381
+ values = []
382
+ for opt_name in self._options:
254
383
  try:
255
- if '.' in valname:
256
- realvalname, key = valname.split('.', 1)
257
- config.setdefault(realvalname, {})[key] = value
258
- continue
259
- if valname not in self.values:
260
- logger.warning(__('unknown config value %r in override, ignoring'),
261
- valname)
262
- continue
263
- if isinstance(value, str):
264
- config[valname] = self.convert_overrides(valname, value)
265
- else:
266
- config[valname] = value
267
- except ValueError as exc:
268
- logger.warning("%s", exc)
269
- for name in config:
270
- if name in self.values:
271
- self.__dict__[name] = config[name]
272
-
273
- def post_init_values(self) -> None:
274
- """
275
- Initialize additional config variables that are added after init_values() called.
276
- """
277
- config = self._raw_config
278
- for name in config:
279
- if name not in self.__dict__ and name in self.values:
280
- self.__dict__[name] = config[name]
281
-
282
- check_confval_types(None, self)
384
+ opt_value = getattr(self, opt_name)
385
+ except Exception:
386
+ opt_value = '<error!>'
387
+ values.append(f"{opt_name}={opt_value!r}")
388
+ return self.__class__.__qualname__ + '(' + ', '.join(values) + ')'
283
389
 
284
390
  def __getattr__(self, name: str) -> Any:
391
+ if name in self._options:
392
+ # first check command-line overrides
393
+ if name in self._overrides:
394
+ value = self._overrides[name]
395
+ if not isinstance(value, str):
396
+ self.__dict__[name] = value
397
+ return value
398
+ try:
399
+ value = self.convert_overrides(name, value)
400
+ except ValueError as exc:
401
+ logger.warning("%s", exc)
402
+ else:
403
+ self.__dict__[name] = value
404
+ return value
405
+ # then check values from 'conf.py'
406
+ if name in self._raw_config:
407
+ self.__dict__[name] = value = self._raw_config[name]
408
+ return value
409
+ # finally, fall back to the default value
410
+ default = self._options[name].default
411
+ if callable(default):
412
+ return default(self)
413
+ self.__dict__[name] = default
414
+ return default
285
415
  if name.startswith('_'):
286
- raise AttributeError(name)
287
- if name not in self.values:
288
- raise AttributeError(__('No such config value: %s') % name)
289
- default = self.values[name][0]
290
- if callable(default):
291
- return default(self)
292
- return default
416
+ msg = f'{self.__class__.__name__!r} object has no attribute {name!r}'
417
+ raise AttributeError(msg)
418
+ msg = __('No such config value: %r') % name
419
+ raise AttributeError(msg)
293
420
 
294
421
  def __getitem__(self, name: str) -> Any:
295
422
  return getattr(self, name)
@@ -301,46 +428,68 @@ class Config:
301
428
  delattr(self, name)
302
429
 
303
430
  def __contains__(self, name: str) -> bool:
304
- return name in self.values
431
+ return name in self._options
305
432
 
306
- def __iter__(self) -> Generator[ConfigValue, None, None]:
307
- for name, value in self.values.items():
308
- yield ConfigValue(name, getattr(self, name), value[1])
433
+ def __iter__(self) -> Iterator[ConfigValue]:
434
+ for name, opt in self._options.items():
435
+ yield ConfigValue(name, getattr(self, name), opt.rebuild)
309
436
 
310
- def add(self, name: str, default: Any, rebuild: bool | str, types: Any) -> None:
311
- if name in self.values:
437
+ def add(self, name: str, default: Any, rebuild: _ConfigRebuild,
438
+ types: type | Collection[type] | ENUM) -> None:
439
+ if name in self._options:
312
440
  raise ExtensionError(__('Config value %r already present') % name)
313
- self.values[name] = (default, rebuild, types)
314
441
 
315
- def filter(self, rebuild: str | Sequence[str]) -> Iterator[ConfigValue]:
442
+ # standardise rebuild
443
+ if isinstance(rebuild, bool):
444
+ rebuild = 'env' if rebuild else ''
445
+
446
+ # standardise valid_types
447
+ valid_types = _validate_valid_types(types)
448
+ self._options[name] = _Opt(default, rebuild, valid_types)
449
+
450
+ def filter(self, rebuild: Set[_ConfigRebuild]) -> Iterator[ConfigValue]:
316
451
  if isinstance(rebuild, str):
317
- rebuild = [rebuild]
452
+ return (value for value in self if value.rebuild == rebuild)
318
453
  return (value for value in self if value.rebuild in rebuild)
319
454
 
320
455
  def __getstate__(self) -> dict:
321
456
  """Obtains serializable data for pickling."""
322
457
  # remove potentially pickling-problematic values from config
323
- __dict__ = {}
324
- for key, value in self.__dict__.items():
325
- if key.startswith('_') or not is_serializable(value):
326
- pass
327
- else:
328
- __dict__[key] = value
329
-
330
- # create a picklable copy of values list
331
- __dict__['values'] = {}
332
- for key, value in self.values.items():
333
- real_value = getattr(self, key)
458
+ __dict__ = {
459
+ key: value
460
+ for key, value in self.__dict__.items()
461
+ if not key.startswith('_') and is_serializable(value)
462
+ }
463
+ # create a picklable copy of ``self._options``
464
+ __dict__['_options'] = _options = {}
465
+ for name, opt in self._options.items():
466
+ real_value = getattr(self, name)
334
467
  if not is_serializable(real_value):
468
+ if opt.rebuild:
469
+ # if the value is not cached, then any build that utilises this cache
470
+ # will always mark the config value as changed,
471
+ # and thus always invalidate the cache and perform a rebuild.
472
+ logger.warning(
473
+ __('cannot cache unpickable configuration value: %r'),
474
+ name,
475
+ type='config',
476
+ subtype='cache',
477
+ once=True,
478
+ )
335
479
  # omit unserializable value
336
480
  real_value = None
337
-
338
- # types column is also omitted
339
- __dict__['values'][key] = (real_value, value[1], None)
481
+ # valid_types is also omitted
482
+ _options[name] = real_value, opt.rebuild
340
483
 
341
484
  return __dict__
342
485
 
343
486
  def __setstate__(self, state: dict) -> None:
487
+ self._overrides = {}
488
+ self._options = {
489
+ name: _Opt(real_value, rebuild, ())
490
+ for name, (real_value, rebuild) in state.pop('_options').items()
491
+ }
492
+ self._raw_config = {}
344
493
  self.__dict__.update(state)
345
494
 
346
495
 
@@ -373,6 +522,29 @@ def eval_config_file(filename: str, tags: Tags | None) -> dict[str, Any]:
373
522
  return namespace
374
523
 
375
524
 
525
+ def _validate_valid_types(
526
+ valid_types: type | Collection[type] | ENUM, /,
527
+ ) -> tuple[()] | tuple[type, ...] | frozenset[type] | ENUM:
528
+ if not valid_types:
529
+ return ()
530
+ if isinstance(valid_types, (frozenset, ENUM)):
531
+ return valid_types
532
+ if isinstance(valid_types, type):
533
+ return frozenset((valid_types,))
534
+ if isinstance(valid_types, set):
535
+ return frozenset(valid_types)
536
+ if not isinstance(valid_types, tuple):
537
+ try:
538
+ valid_types = tuple(valid_types)
539
+ except TypeError:
540
+ logger.warning(__('Failed to convert %r to a set or tuple'), valid_types)
541
+ return valid_types # type: ignore[return-value]
542
+ try:
543
+ return frozenset(valid_types)
544
+ except TypeError:
545
+ return valid_types
546
+
547
+
376
548
  def convert_source_suffix(app: Sphinx, config: Config) -> None:
377
549
  """Convert old styled source_suffix to new styled one.
378
550
 
@@ -388,7 +560,7 @@ def convert_source_suffix(app: Sphinx, config: Config) -> None:
388
560
  config.source_suffix = {source_suffix: None} # type: ignore[attr-defined]
389
561
  elif isinstance(source_suffix, (list, tuple)):
390
562
  # if list, considers as all of them are default filetype
391
- config.source_suffix = {s: None for s in source_suffix} # type: ignore[attr-defined]
563
+ config.source_suffix = dict.fromkeys(source_suffix, None) # type: ignore[attr-defined]
392
564
  elif not isinstance(source_suffix, dict):
393
565
  logger.warning(__("The config value `source_suffix' expects "
394
566
  "a string, list of strings, or dictionary. "
@@ -463,7 +635,7 @@ def _substitute_copyright_year(copyright_line: str, replace_year: str) -> str:
463
635
  if copyright_line[4] != '-':
464
636
  return copyright_line
465
637
 
466
- if copyright_line[5:9].isdigit() and copyright_line[9] in ' ,':
638
+ if copyright_line[5:9].isdigit() and copyright_line[9:10] in {'', ' ', ','}:
467
639
  return copyright_line[:5] + replace_year + copyright_line[9:]
468
640
 
469
641
  return copyright_line
@@ -473,54 +645,64 @@ def check_confval_types(app: Sphinx | None, config: Config) -> None:
473
645
  """Check all values for deviation from the default value's type, since
474
646
  that can result in TypeErrors all over the place NB.
475
647
  """
476
- for confval in config:
477
- default, rebuild, annotations = config.values[confval.name]
648
+ for name, opt in config._options.items():
649
+ default = opt.default
650
+ valid_types = opt.valid_types
651
+ value = getattr(config, name)
478
652
 
479
653
  if callable(default):
480
654
  default = default(config) # evaluate default value
481
- if default is None and not annotations:
482
- continue # neither inferable nor expliclitly annotated types
483
-
484
- if annotations is Any:
485
- # any type of value is accepted
486
- pass
487
- elif isinstance(annotations, ENUM):
488
- if not annotations.match(confval.value):
655
+ if default is None and not valid_types:
656
+ continue # neither inferable nor explicitly annotated types
657
+
658
+ if valid_types is Any: # any type of value is accepted
659
+ continue
660
+
661
+ if isinstance(valid_types, ENUM):
662
+ if not valid_types.match(value):
489
663
  msg = __("The config value `{name}` has to be a one of {candidates}, "
490
664
  "but `{current}` is given.")
491
- logger.warning(msg.format(name=confval.name,
492
- current=confval.value,
493
- candidates=annotations.candidates), once=True)
494
- else:
495
- if type(confval.value) is type(default):
496
- continue
497
- if type(confval.value) in annotations:
498
- continue
499
-
500
- common_bases = (set(type(confval.value).__bases__ + (type(confval.value),)) &
501
- set(type(default).__bases__))
502
- common_bases.discard(object)
503
- if common_bases:
504
- continue # at least we share a non-trivial base class
505
-
506
- if annotations:
507
- msg = __("The config value `{name}' has type `{current.__name__}'; "
508
- "expected {permitted}.")
509
- wrapped_annotations = [f"`{c.__name__}'" for c in annotations]
510
- if len(wrapped_annotations) > 2:
511
- permitted = (", ".join(wrapped_annotations[:-1])
512
- + f", or {wrapped_annotations[-1]}")
513
- else:
514
- permitted = " or ".join(wrapped_annotations)
515
- logger.warning(msg.format(name=confval.name,
516
- current=type(confval.value),
517
- permitted=permitted), once=True)
665
+ logger.warning(
666
+ msg.format(name=name, current=value, candidates=valid_types.candidates),
667
+ once=True,
668
+ )
669
+ continue
670
+
671
+ type_value = type(value)
672
+ type_default = type(default)
673
+
674
+ if type_value is type_default: # attempt to infer the type
675
+ continue
676
+
677
+ if type_value in valid_types: # check explicitly listed types
678
+ continue
679
+
680
+ common_bases = ({*type_value.__bases__, type_value}
681
+ & set(type_default.__bases__))
682
+ common_bases.discard(object)
683
+ if common_bases:
684
+ continue # at least we share a non-trivial base class
685
+
686
+ if valid_types:
687
+ msg = __("The config value `{name}' has type `{current.__name__}'; "
688
+ "expected {permitted}.")
689
+ wrapped_valid_types = sorted(f"`{c.__name__}'" for c in valid_types)
690
+ if len(wrapped_valid_types) > 2:
691
+ permitted = (", ".join(wrapped_valid_types[:-1])
692
+ + f", or {wrapped_valid_types[-1]}")
518
693
  else:
519
- msg = __("The config value `{name}' has type `{current.__name__}', "
520
- "defaults to `{default.__name__}'.")
521
- logger.warning(msg.format(name=confval.name,
522
- current=type(confval.value),
523
- default=type(default)), once=True)
694
+ permitted = " or ".join(wrapped_valid_types)
695
+ logger.warning(
696
+ msg.format(name=name, current=type_value, permitted=permitted),
697
+ once=True,
698
+ )
699
+ else:
700
+ msg = __("The config value `{name}' has type `{current.__name__}', "
701
+ "defaults to `{default.__name__}'.")
702
+ logger.warning(
703
+ msg.format(name=name, current=type_value, default=type_default),
704
+ once=True,
705
+ )
524
706
 
525
707
 
526
708
  def check_primary_domain(app: Sphinx, config: Config) -> None:
@@ -545,7 +727,7 @@ def check_root_doc(app: Sphinx, env: BuildEnvironment, added: set[str],
545
727
  return changed
546
728
 
547
729
 
548
- def setup(app: Sphinx) -> dict[str, Any]:
730
+ def setup(app: Sphinx) -> ExtensionMetadata:
549
731
  app.connect('config-inited', convert_source_suffix, priority=800)
550
732
  app.connect('config-inited', convert_highlight_options, priority=800)
551
733
  app.connect('config-inited', init_numfig_format, priority=800)