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/theming.py CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ __all__ = ('Theme', 'HTMLThemeFactory')
6
+
5
7
  import configparser
8
+ import contextlib
6
9
  import os
7
10
  import shutil
8
11
  import sys
@@ -11,117 +14,127 @@ from os import path
11
14
  from typing import TYPE_CHECKING, Any
12
15
  from zipfile import ZipFile
13
16
 
14
- if sys.version_info >= (3, 10):
15
- from importlib.metadata import entry_points
16
- else:
17
- from importlib_metadata import entry_points
18
-
19
- import contextlib
20
-
21
17
  from sphinx import package_dir
18
+ from sphinx.config import check_confval_types as _config_post_init
22
19
  from sphinx.errors import ThemeError
23
20
  from sphinx.locale import __
24
21
  from sphinx.util import logging
25
22
  from sphinx.util.osutil import ensuredir
26
23
 
24
+ if sys.version_info >= (3, 11):
25
+ import tomllib
26
+ else:
27
+ import tomli as tomllib
28
+
29
+ if sys.version_info >= (3, 10):
30
+ from importlib.metadata import entry_points
31
+ else:
32
+ from importlib_metadata import entry_points
33
+
27
34
  if TYPE_CHECKING:
35
+ from typing import TypedDict
36
+
37
+ from typing_extensions import Required
38
+
28
39
  from sphinx.application import Sphinx
29
40
 
41
+ class _ThemeToml(TypedDict, total=False):
42
+ theme: Required[_ThemeTomlTheme]
43
+ options: dict[str, str]
30
44
 
31
- logger = logging.getLogger(__name__)
45
+ class _ThemeTomlTheme(TypedDict, total=False):
46
+ inherit: Required[str]
47
+ stylesheets: list[str]
48
+ sidebars: list[str]
49
+ pygments_style: _ThemeTomlThemePygments
32
50
 
33
- NODEFAULT = object()
34
- THEMECONF = 'theme.conf'
51
+ class _ThemeTomlThemePygments(TypedDict, total=False):
52
+ default: str
53
+ dark: str
35
54
 
36
55
 
37
- def extract_zip(filename: str, targetdir: str) -> None:
38
- """Extract zip file to target directory."""
39
- ensuredir(targetdir)
56
+ logger = logging.getLogger(__name__)
40
57
 
41
- with ZipFile(filename) as archive:
42
- for name in archive.namelist():
43
- if name.endswith('/'):
44
- continue
45
- entry = path.join(targetdir, name)
46
- ensuredir(path.dirname(entry))
47
- with open(path.join(entry), 'wb') as fp:
48
- fp.write(archive.read(name))
58
+ _NO_DEFAULT = object()
59
+ _THEME_TOML = 'theme.toml'
60
+ _THEME_CONF = 'theme.conf'
49
61
 
50
62
 
51
63
  class Theme:
52
64
  """A Theme is a set of HTML templates and configurations.
53
65
 
54
- This class supports both theme directory and theme archive (zipped theme)."""
55
-
56
- def __init__(self, name: str, theme_path: str, factory: HTMLThemeFactory) -> None:
66
+ This class supports both theme directory and theme archive (zipped theme).
67
+ """
68
+
69
+ def __init__(
70
+ self,
71
+ name: str,
72
+ *,
73
+ configs: dict[str, _ConfigFile],
74
+ paths: list[str],
75
+ tmp_dirs: list[str],
76
+ ) -> None:
57
77
  self.name = name
58
- self.base = None
59
- self.rootdir = None
60
-
61
- if path.isdir(theme_path):
62
- # already a directory, do nothing
63
- self.rootdir = None
64
- self.themedir = theme_path
65
- else:
66
- # extract the theme to a temp directory
67
- self.rootdir = tempfile.mkdtemp('sxt')
68
- self.themedir = path.join(self.rootdir, name)
69
- extract_zip(theme_path, self.themedir)
70
-
71
- self.config = configparser.RawConfigParser()
72
- self.config.read(path.join(self.themedir, THEMECONF), encoding='utf-8')
73
-
74
- try:
75
- inherit = self.config.get('theme', 'inherit')
76
- except configparser.NoSectionError as exc:
77
- raise ThemeError(__('theme %r doesn\'t have "theme" setting') % name) from exc
78
- except configparser.NoOptionError as exc:
79
- raise ThemeError(__('theme %r doesn\'t have "inherit" setting') % name) from exc
80
-
81
- if inherit != 'none':
82
- try:
83
- self.base = factory.create(inherit)
84
- except ThemeError as exc:
85
- raise ThemeError(__('no theme named %r found, inherited by %r') %
86
- (inherit, name)) from exc
78
+ self._dirs = tuple(paths)
79
+ self._tmp_dirs = tmp_dirs
80
+
81
+ options: dict[str, Any] = {}
82
+ self.stylesheets: tuple[str, ...] = ()
83
+ self.sidebar_templates: tuple[str, ...] = ()
84
+ self.pygments_style_default: str | None = None
85
+ self.pygments_style_dark: str | None = None
86
+ for config in reversed(configs.values()):
87
+ options |= config.options
88
+ if config.stylesheets is not None:
89
+ self.stylesheets = config.stylesheets
90
+ if config.sidebar_templates is not None:
91
+ self.sidebar_templates = config.sidebar_templates
92
+ if config.pygments_style_default is not None:
93
+ self.pygments_style_default = config.pygments_style_default
94
+ if config.pygments_style_dark is not None:
95
+ self.pygments_style_dark = config.pygments_style_dark
96
+
97
+ self._options = options
87
98
 
88
99
  def get_theme_dirs(self) -> list[str]:
89
100
  """Return a list of theme directories, beginning with this theme's,
90
101
  then the base theme's, then that one's base theme's, etc.
91
102
  """
92
- if self.base is None:
93
- return [self.themedir]
94
- else:
95
- return [self.themedir] + self.base.get_theme_dirs()
103
+ return list(self._dirs)
96
104
 
97
- def get_config(self, section: str, name: str, default: Any = NODEFAULT) -> Any:
105
+ def get_config(self, section: str, name: str, default: Any = _NO_DEFAULT) -> Any:
98
106
  """Return the value for a theme configuration setting, searching the
99
107
  base theme chain.
100
108
  """
101
- try:
102
- return self.config.get(section, name)
103
- except (configparser.NoOptionError, configparser.NoSectionError) as exc:
104
- if self.base:
105
- return self.base.get_config(section, name, default)
106
-
107
- if default is NODEFAULT:
108
- raise ThemeError(__('setting %s.%s occurs in none of the '
109
- 'searched theme configs') % (section, name)) from exc
110
- return default
109
+ if section == 'theme':
110
+ if name == 'stylesheet':
111
+ value = ', '.join(self.stylesheets) or default
112
+ elif name == 'sidebars':
113
+ value = ', '.join(self.sidebar_templates) or default
114
+ elif name == 'pygments_style':
115
+ value = self.pygments_style_default or default
116
+ elif name == 'pygments_dark_style':
117
+ value = self.pygments_style_dark or default
118
+ else:
119
+ value = default
120
+ elif section == 'options':
121
+ value = self._options.get(name, default)
122
+ else:
123
+ value = _NO_DEFAULT
124
+ if value is _NO_DEFAULT:
125
+ msg = __('setting %s.%s occurs in none of the searched theme configs') % (
126
+ section,
127
+ name,
128
+ )
129
+ raise ThemeError(msg)
130
+ return value
111
131
 
112
132
  def get_options(self, overrides: dict[str, Any] | None = None) -> dict[str, Any]:
113
133
  """Return a dictionary of theme options and their values."""
114
134
  if overrides is None:
115
135
  overrides = {}
116
136
 
117
- if self.base:
118
- options = self.base.get_options()
119
- else:
120
- options = {}
121
-
122
- with contextlib.suppress(configparser.NoSectionError):
123
- options.update(self.config.items('options'))
124
-
137
+ options = self._options.copy()
125
138
  for option, value in overrides.items():
126
139
  if option not in options:
127
140
  logger.warning(__('unsupported theme option %r given') % option)
@@ -130,77 +143,53 @@ class Theme:
130
143
 
131
144
  return options
132
145
 
133
- def cleanup(self) -> None:
146
+ def _cleanup(self) -> None:
134
147
  """Remove temporary directories."""
135
- if self.rootdir:
148
+ for tmp_dir in self._tmp_dirs:
136
149
  with contextlib.suppress(Exception):
137
- shutil.rmtree(self.rootdir)
138
-
139
- if self.base:
140
- self.base.cleanup()
141
-
142
-
143
- def is_archived_theme(filename: str) -> bool:
144
- """Check whether the specified file is an archived theme file or not."""
145
- try:
146
- with ZipFile(filename) as f:
147
- return THEMECONF in f.namelist()
148
- except Exception:
149
- return False
150
+ shutil.rmtree(tmp_dir)
150
151
 
151
152
 
152
153
  class HTMLThemeFactory:
153
154
  """A factory class for HTML Themes."""
154
155
 
155
156
  def __init__(self, app: Sphinx) -> None:
156
- self.app = app
157
- self.themes = app.registry.html_themes
158
- self.load_builtin_themes()
157
+ self._app = app
158
+ self._themes = app.registry.html_themes
159
+ self._load_builtin_themes()
159
160
  if getattr(app.config, 'html_theme_path', None):
160
- self.load_additional_themes(app.config.html_theme_path)
161
+ self._load_additional_themes(app.config.html_theme_path)
161
162
 
162
- def load_builtin_themes(self) -> None:
163
+ def _load_builtin_themes(self) -> None:
163
164
  """Load built-in themes."""
164
- themes = self.find_themes(path.join(package_dir, 'themes'))
165
+ themes = self._find_themes(path.join(package_dir, 'themes'))
165
166
  for name, theme in themes.items():
166
- self.themes[name] = theme
167
+ self._themes[name] = theme
167
168
 
168
- def load_additional_themes(self, theme_paths: str) -> None:
169
+ def _load_additional_themes(self, theme_paths: str) -> None:
169
170
  """Load additional themes placed at specified directories."""
170
171
  for theme_path in theme_paths:
171
- abs_theme_path = path.abspath(path.join(self.app.confdir, theme_path))
172
- themes = self.find_themes(abs_theme_path)
172
+ abs_theme_path = path.abspath(path.join(self._app.confdir, theme_path))
173
+ themes = self._find_themes(abs_theme_path)
173
174
  for name, theme in themes.items():
174
- self.themes[name] = theme
175
-
176
- def load_extra_theme(self, name: str) -> None:
177
- """Try to load a theme with the specified name."""
178
- if name == 'alabaster':
179
- self.load_alabaster_theme()
180
- else:
181
- self.load_external_theme(name)
175
+ self._themes[name] = theme
182
176
 
183
- def load_alabaster_theme(self) -> None:
184
- """Load alabaster theme."""
185
- import alabaster
186
- self.themes['alabaster'] = path.join(alabaster.get_path(), 'alabaster')
177
+ def _load_extra_theme(self, name: str) -> None:
178
+ """Try to load a theme with the specified name.
187
179
 
188
- def load_external_theme(self, name: str) -> None:
189
- """Try to load a theme using entry_points.
190
-
191
- Sphinx refers to ``sphinx_themes`` entry_points.
180
+ This uses the ``sphinx.html_themes`` entry point from package metadata.
192
181
  """
193
- # look up for new styled entry_points at first
194
182
  theme_entry_points = entry_points(group='sphinx.html_themes')
195
183
  try:
196
184
  entry_point = theme_entry_points[name]
197
- self.app.registry.load_extension(self.app, entry_point.module)
198
- self.app.config.post_init_values()
199
- return
200
185
  except KeyError:
201
186
  pass
187
+ else:
188
+ self._app.registry.load_extension(self._app, entry_point.module)
189
+ _config_post_init(self._app, self._app.config)
202
190
 
203
- def find_themes(self, theme_path: str) -> dict[str, str]:
191
+ @staticmethod
192
+ def _find_themes(theme_path: str) -> dict[str, str]:
204
193
  """Search themes from specified directory."""
205
194
  themes: dict[str, str] = {}
206
195
  if not path.isdir(theme_path):
@@ -209,24 +198,331 @@ class HTMLThemeFactory:
209
198
  for entry in os.listdir(theme_path):
210
199
  pathname = path.join(theme_path, entry)
211
200
  if path.isfile(pathname) and entry.lower().endswith('.zip'):
212
- if is_archived_theme(pathname):
201
+ if _is_archived_theme(pathname):
213
202
  name = entry[:-4]
214
203
  themes[name] = pathname
215
204
  else:
216
- logger.warning(__('file %r on theme path is not a valid '
217
- 'zipfile or contains no theme'), entry)
205
+ logger.warning(
206
+ __(
207
+ 'file %r on theme path is not a valid '
208
+ 'zipfile or contains no theme'
209
+ ),
210
+ entry,
211
+ )
218
212
  else:
219
- if path.isfile(path.join(pathname, THEMECONF)):
213
+ toml_path = path.join(pathname, _THEME_TOML)
214
+ conf_path = path.join(pathname, _THEME_CONF)
215
+ if path.isfile(toml_path) or path.isfile(conf_path):
220
216
  themes[entry] = pathname
221
217
 
222
218
  return themes
223
219
 
224
220
  def create(self, name: str) -> Theme:
225
221
  """Create an instance of theme."""
226
- if name not in self.themes:
227
- self.load_extra_theme(name)
222
+ if name not in self._themes:
223
+ self._load_extra_theme(name)
224
+
225
+ if name not in self._themes:
226
+ raise ThemeError(__('no theme named %r found (missing theme.toml?)') % name)
227
+
228
+ themes, theme_dirs, tmp_dirs = _load_theme_with_ancestors(self._themes, name)
229
+ return Theme(name, configs=themes, paths=theme_dirs, tmp_dirs=tmp_dirs)
230
+
231
+
232
+ def _is_archived_theme(filename: str, /) -> bool:
233
+ """Check whether the specified file is an archived theme file or not."""
234
+ try:
235
+ with ZipFile(filename) as f:
236
+ namelist = frozenset(f.namelist())
237
+ return _THEME_TOML in namelist or _THEME_CONF in namelist
238
+ except Exception:
239
+ return False
240
+
241
+
242
+ def _load_theme_with_ancestors(
243
+ theme_paths: dict[str, str], name: str, /
244
+ ) -> tuple[dict[str, _ConfigFile], list[str], list[str]]:
245
+ themes: dict[str, _ConfigFile] = {}
246
+ theme_dirs: list[str] = []
247
+ tmp_dirs: list[str] = []
248
+
249
+ # having 10+ theme ancestors is ludicrous
250
+ for _ in range(10):
251
+ inherit, theme_dir, tmp_dir, config = _load_theme(name, theme_paths[name])
252
+ theme_dirs.append(theme_dir)
253
+ if tmp_dir is not None:
254
+ tmp_dirs.append(tmp_dir)
255
+ themes[name] = config
256
+ if inherit == 'none':
257
+ break
258
+ if inherit in themes:
259
+ msg = __('The %r theme has circular inheritance') % name
260
+ raise ThemeError(msg)
261
+ if inherit not in theme_paths:
262
+ msg = __(
263
+ 'The %r theme inherits from %r, which is not a loaded theme. '
264
+ 'Loaded themes are: %s'
265
+ ) % (name, inherit, ', '.join(sorted(theme_paths)))
266
+ raise ThemeError(msg)
267
+ name = inherit
268
+ else:
269
+ msg = __('The %r theme has too many ancestors') % name
270
+ raise ThemeError(msg)
271
+
272
+ return themes, theme_dirs, tmp_dirs
273
+
274
+
275
+ def _load_theme(name: str, theme_path: str, /) -> tuple[str, str, str | None, _ConfigFile]:
276
+ if path.isdir(theme_path):
277
+ # already a directory, do nothing
278
+ tmp_dir = None
279
+ theme_dir = theme_path
280
+ else:
281
+ # extract the theme to a temp directory
282
+ tmp_dir = tempfile.mkdtemp('sxt')
283
+ theme_dir = path.join(tmp_dir, name)
284
+ _extract_zip(theme_path, theme_dir)
285
+
286
+ if path.isfile(toml_path := path.join(theme_dir, _THEME_TOML)):
287
+ _cfg_table = _load_theme_toml(toml_path)
288
+ inherit = _validate_theme_toml(_cfg_table, name)
289
+ config = _convert_theme_toml(_cfg_table)
290
+ elif path.isfile(conf_path := path.join(theme_dir, _THEME_CONF)):
291
+ _cfg_parser = _load_theme_conf(conf_path)
292
+ inherit = _validate_theme_conf(_cfg_parser, name)
293
+ config = _convert_theme_conf(_cfg_parser)
294
+ else:
295
+ raise ThemeError(__('no theme configuration file found in %r') % theme_dir)
296
+
297
+ return inherit, theme_dir, tmp_dir, config
298
+
299
+
300
+ def _extract_zip(filename: str, target_dir: str, /) -> None:
301
+ """Extract zip file to target directory."""
302
+ ensuredir(target_dir)
303
+
304
+ with ZipFile(filename) as archive:
305
+ for name in archive.namelist():
306
+ if name.endswith('/'):
307
+ continue
308
+ entry = path.join(target_dir, name)
309
+ ensuredir(path.dirname(entry))
310
+ with open(path.join(entry), 'wb') as fp:
311
+ fp.write(archive.read(name))
228
312
 
229
- if name not in self.themes:
230
- raise ThemeError(__('no theme named %r found (missing theme.conf?)') % name)
231
313
 
232
- return Theme(name, self.themes[name], factory=self)
314
+ def _load_theme_toml(config_file_path: str, /) -> _ThemeToml:
315
+ with open(config_file_path, encoding='utf-8') as f:
316
+ config_text = f.read()
317
+ c = tomllib.loads(config_text)
318
+ return {s: c[s] for s in ('theme', 'options') if s in c} # type: ignore[return-value]
319
+
320
+
321
+ def _validate_theme_toml(cfg: _ThemeToml, name: str) -> str:
322
+ if 'theme' not in cfg:
323
+ msg = __('theme %r doesn\'t have the "theme" table') % name
324
+ raise ThemeError(msg)
325
+ theme = cfg['theme']
326
+ if not isinstance(theme, dict):
327
+ msg = __('The %r theme "[theme]" table is not a table') % name
328
+ raise ThemeError(msg)
329
+ inherit = theme.get('inherit', '')
330
+ if not inherit:
331
+ msg = __('The %r theme must define the "theme.inherit" setting') % name
332
+ raise ThemeError(msg)
333
+ if 'options' in cfg:
334
+ if not isinstance(cfg['options'], dict):
335
+ msg = __('The %r theme "[options]" table is not a table') % name
336
+ raise ThemeError(msg)
337
+ return inherit
338
+
339
+
340
+ def _convert_theme_toml(cfg: _ThemeToml, /) -> _ConfigFile:
341
+ theme = cfg['theme']
342
+ if 'stylesheets' in theme:
343
+ stylesheets: tuple[str, ...] | None = tuple(theme['stylesheets'])
344
+ else:
345
+ stylesheets = None
346
+ if 'sidebars' in theme:
347
+ sidebar_templates: tuple[str, ...] | None = tuple(theme['sidebars'])
348
+ else:
349
+ sidebar_templates = None
350
+ pygments_table = theme.get('pygments_style', {})
351
+ if isinstance(pygments_table, str):
352
+ hint = f'pygments_style = {{ default = "{pygments_table}" }}'
353
+ msg = __('The "theme.pygments_style" setting must be a table. Hint: "%s"') % hint
354
+ raise ThemeError(msg)
355
+ pygments_style_default: str | None = pygments_table.get('default')
356
+ pygments_style_dark: str | None = pygments_table.get('dark')
357
+ return _ConfigFile(
358
+ stylesheets=stylesheets,
359
+ sidebar_templates=sidebar_templates,
360
+ pygments_style_default=pygments_style_default,
361
+ pygments_style_dark=pygments_style_dark,
362
+ options=cfg.get('options', {}),
363
+ )
364
+
365
+
366
+ def _load_theme_conf(config_file_path: str, /) -> configparser.RawConfigParser:
367
+ c = configparser.RawConfigParser()
368
+ c.read(config_file_path, encoding='utf-8')
369
+ return c
370
+
371
+
372
+ def _validate_theme_conf(cfg: configparser.RawConfigParser, name: str) -> str:
373
+ if not cfg.has_section('theme'):
374
+ raise ThemeError(__('theme %r doesn\'t have the "theme" table') % name)
375
+ if inherit := cfg.get('theme', 'inherit', fallback=None):
376
+ return inherit
377
+ msg = __('The %r theme must define the "theme.inherit" setting') % name
378
+ raise ThemeError(msg)
379
+
380
+
381
+ def _convert_theme_conf(cfg: configparser.RawConfigParser, /) -> _ConfigFile:
382
+ if stylesheet := cfg.get('theme', 'stylesheet', fallback=''):
383
+ stylesheets: tuple[str, ...] | None = tuple(map(str.strip, stylesheet.split(',')))
384
+ else:
385
+ stylesheets = None
386
+ if sidebar := cfg.get('theme', 'sidebars', fallback=''):
387
+ sidebar_templates: tuple[str, ...] | None = tuple(map(str.strip, sidebar.split(',')))
388
+ else:
389
+ sidebar_templates = None
390
+ pygments_style_default: str | None = cfg.get('theme', 'pygments_style', fallback=None)
391
+ pygments_style_dark: str | None = cfg.get('theme', 'pygments_dark_style', fallback=None)
392
+ options = dict(cfg.items('options')) if cfg.has_section('options') else {}
393
+ return _ConfigFile(
394
+ stylesheets=stylesheets,
395
+ sidebar_templates=sidebar_templates,
396
+ pygments_style_default=pygments_style_default,
397
+ pygments_style_dark=pygments_style_dark,
398
+ options=options,
399
+ )
400
+
401
+
402
+ class _ConfigFile:
403
+ __slots__ = (
404
+ 'stylesheets',
405
+ 'sidebar_templates',
406
+ 'pygments_style_default',
407
+ 'pygments_style_dark',
408
+ 'options',
409
+ )
410
+
411
+ def __init__(
412
+ self,
413
+ stylesheets: tuple[str, ...] | None,
414
+ sidebar_templates: tuple[str, ...] | None,
415
+ pygments_style_default: str | None,
416
+ pygments_style_dark: str | None,
417
+ options: dict[str, str],
418
+ ) -> None:
419
+ self.stylesheets: tuple[str, ...] | None = stylesheets
420
+ self.sidebar_templates: tuple[str, ...] | None = sidebar_templates
421
+ self.pygments_style_default: str | None = pygments_style_default
422
+ self.pygments_style_dark: str | None = pygments_style_dark
423
+ self.options: dict[str, str] = options.copy()
424
+
425
+ def __repr__(self) -> str:
426
+ return (
427
+ f'{self.__class__.__qualname__}('
428
+ f'stylesheets={self.stylesheets!r}, '
429
+ f'sidebar_templates={self.sidebar_templates!r}, '
430
+ f'pygments_style_default={self.pygments_style_default!r}, '
431
+ f'pygments_style_dark={self.pygments_style_dark!r}, '
432
+ f'options={self.options!r})'
433
+ )
434
+
435
+ def __eq__(self, other: object) -> bool:
436
+ if isinstance(other, _ConfigFile):
437
+ return (
438
+ self.stylesheets == other.stylesheets
439
+ and self.sidebar_templates == other.sidebar_templates
440
+ and self.pygments_style_default == other.pygments_style_default
441
+ and self.pygments_style_dark == other.pygments_style_dark
442
+ and self.options == other.options
443
+ )
444
+ return NotImplemented
445
+
446
+ def __hash__(self) -> int:
447
+ return hash((
448
+ self.__class__.__qualname__,
449
+ self.stylesheets,
450
+ self.sidebar_templates,
451
+ self.pygments_style_default,
452
+ self.pygments_style_dark,
453
+ self.options,
454
+ ))
455
+
456
+
457
+ def _migrate_conf_to_toml(argv: list[str]) -> int:
458
+ if argv[:1] != ['conf_to_toml']:
459
+ raise SystemExit(0)
460
+ argv = argv[1:]
461
+ if len(argv) != 1:
462
+ print('Usage: python -m sphinx.theming conf_to_toml <theme path>') # NoQA: T201
463
+ raise SystemExit(1)
464
+ theme_dir = path.realpath(argv[0])
465
+ conf_path = path.join(theme_dir, _THEME_CONF)
466
+ if not path.isdir(theme_dir) or not path.isfile(conf_path):
467
+ print( # NoQA: T201
468
+ f'{theme_dir!r} must be a path to a theme directory containing a "theme.conf" file'
469
+ )
470
+ return 1
471
+ _cfg_parser = _load_theme_conf(conf_path)
472
+ if not _cfg_parser.has_section('theme'):
473
+ print('The "theme" table is missing.') # NoQA: T201
474
+ return 1
475
+ inherit = _cfg_parser.get('theme', 'inherit', fallback=None)
476
+ if not inherit:
477
+ print('The "theme.inherit" setting is missing.') # NoQA: T201
478
+ return 1
479
+
480
+ toml_lines = [
481
+ '[theme]',
482
+ f'inherit = "{inherit}"',
483
+ ]
484
+
485
+ stylesheet = _cfg_parser.get('theme', 'stylesheet', fallback=...)
486
+ if stylesheet == '':
487
+ toml_lines.append('stylesheets = []')
488
+ elif stylesheet is not ...:
489
+ toml_lines.append('stylesheets = [')
490
+ toml_lines.extend(f' "{s}",' for s in map(str.strip, stylesheet.split(',')))
491
+ toml_lines.append(']')
492
+
493
+ sidebar = _cfg_parser.get('theme', 'sidebars', fallback=...)
494
+ if sidebar == '':
495
+ toml_lines.append('sidebars = []')
496
+ elif sidebar is not ...:
497
+ toml_lines.append('sidebars = [')
498
+ toml_lines += [f' "{s}",' for s in map(str.strip, sidebar.split(','))]
499
+ toml_lines.append(']')
500
+
501
+ styles = []
502
+ default = _cfg_parser.get('theme', 'pygments_style', fallback=...)
503
+ if default is not ...:
504
+ styles.append(f'default = "{default}"')
505
+ dark = _cfg_parser.get('theme', 'pygments_dark_style', fallback=...)
506
+ if dark is not ...:
507
+ styles.append(f'dark = "{dark}"')
508
+ if styles:
509
+ toml_lines.append('pygments_style = { ' + ', '.join(styles) + ' }')
510
+
511
+ if _cfg_parser.has_section('options'):
512
+ toml_lines.append('')
513
+ toml_lines.append('[options]')
514
+ toml_lines += [
515
+ f'{key} = "{d}"'
516
+ for key, default in _cfg_parser.items('options')
517
+ if (d := default.replace('"', r'\"')) or True
518
+ ]
519
+
520
+ toml_path = path.join(theme_dir, _THEME_TOML)
521
+ with open(toml_path, 'w', encoding='utf-8') as f:
522
+ f.write('\n'.join(toml_lines) + '\n')
523
+ print(f'Written converted settings to {toml_path!r}') # NoQA: T201
524
+ return 0
525
+
526
+
527
+ if __name__ == '__main__':
528
+ raise SystemExit(_migrate_conf_to_toml(sys.argv[1:]))