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/domains/python.py DELETED
@@ -1,1769 +0,0 @@
1
- """The Python domain."""
2
-
3
- from __future__ import annotations
4
-
5
- import ast
6
- import builtins
7
- import contextlib
8
- import inspect
9
- import re
10
- import token
11
- import typing
12
- from inspect import Parameter
13
- from typing import TYPE_CHECKING, Any, NamedTuple, cast
14
-
15
- from docutils import nodes
16
- from docutils.parsers.rst import directives
17
-
18
- from sphinx import addnodes
19
- from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition
20
- from sphinx.directives import ObjectDescription
21
- from sphinx.domains import Domain, Index, IndexEntry, ObjType
22
- from sphinx.locale import _, __
23
- from sphinx.pycode.parser import Token, TokenProcessor
24
- from sphinx.roles import XRefRole
25
- from sphinx.util import logging
26
- from sphinx.util.docfields import Field, GroupedField, TypedField
27
- from sphinx.util.docutils import SphinxDirective
28
- from sphinx.util.inspect import signature_from_str
29
- from sphinx.util.nodes import (
30
- find_pending_xref_condition,
31
- make_id,
32
- make_refnode,
33
- nested_parse_with_titles,
34
- )
35
-
36
- if TYPE_CHECKING:
37
- from collections.abc import Iterable, Iterator
38
-
39
- from docutils.nodes import Element, Node
40
- from docutils.parsers.rst.states import Inliner
41
-
42
- from sphinx.application import Sphinx
43
- from sphinx.builders import Builder
44
- from sphinx.environment import BuildEnvironment
45
- from sphinx.util.typing import OptionSpec, TextlikeNode
46
-
47
- logger = logging.getLogger(__name__)
48
-
49
-
50
- # REs for Python signatures
51
- py_sig_re = re.compile(
52
- r'''^ ([\w.]*\.)? # class name(s)
53
- (\w+) \s* # thing name
54
- (?: \[\s*(.*)\s*])? # optional: type parameters list
55
- (?: \(\s*(.*)\s*\) # optional: arguments
56
- (?:\s* -> \s* (.*))? # return annotation
57
- )? $ # and nothing more
58
- ''', re.VERBOSE)
59
-
60
-
61
- pairindextypes = {
62
- 'module': 'module',
63
- 'keyword': 'keyword',
64
- 'operator': 'operator',
65
- 'object': 'object',
66
- 'exception': 'exception',
67
- 'statement': 'statement',
68
- 'builtin': 'built-in function',
69
- }
70
-
71
-
72
- class ObjectEntry(NamedTuple):
73
- docname: str
74
- node_id: str
75
- objtype: str
76
- aliased: bool
77
-
78
-
79
- class ModuleEntry(NamedTuple):
80
- docname: str
81
- node_id: str
82
- synopsis: str
83
- platform: str
84
- deprecated: bool
85
-
86
-
87
- def parse_reftarget(reftarget: str, suppress_prefix: bool = False,
88
- ) -> tuple[str, str, str, bool]:
89
- """Parse a type string and return (reftype, reftarget, title, refspecific flag)"""
90
- refspecific = False
91
- if reftarget.startswith('.'):
92
- reftarget = reftarget[1:]
93
- title = reftarget
94
- refspecific = True
95
- elif reftarget.startswith('~'):
96
- reftarget = reftarget[1:]
97
- title = reftarget.split('.')[-1]
98
- elif suppress_prefix:
99
- title = reftarget.split('.')[-1]
100
- elif reftarget.startswith('typing.'):
101
- title = reftarget[7:]
102
- else:
103
- title = reftarget
104
-
105
- if reftarget == 'None' or reftarget.startswith('typing.'):
106
- # typing module provides non-class types. Obj reference is good to refer them.
107
- reftype = 'obj'
108
- else:
109
- reftype = 'class'
110
-
111
- return reftype, reftarget, title, refspecific
112
-
113
-
114
- def type_to_xref(target: str, env: BuildEnvironment, *,
115
- suppress_prefix: bool = False) -> addnodes.pending_xref:
116
- """Convert a type string to a cross reference node."""
117
- if env:
118
- kwargs = {'py:module': env.ref_context.get('py:module'),
119
- 'py:class': env.ref_context.get('py:class')}
120
- else:
121
- kwargs = {}
122
-
123
- reftype, target, title, refspecific = parse_reftarget(target, suppress_prefix)
124
-
125
- if env.config.python_use_unqualified_type_names:
126
- # Note: It would be better to use qualname to describe the object to support support
127
- # nested classes. But python domain can't access the real python object because this
128
- # module should work not-dynamically.
129
- shortname = title.split('.')[-1]
130
- contnodes: list[Node] = [pending_xref_condition('', shortname, condition='resolved'),
131
- pending_xref_condition('', title, condition='*')]
132
- else:
133
- contnodes = [nodes.Text(title)]
134
-
135
- return pending_xref('', *contnodes,
136
- refdomain='py', reftype=reftype, reftarget=target,
137
- refspecific=refspecific, **kwargs)
138
-
139
-
140
- def _parse_annotation(annotation: str, env: BuildEnvironment) -> list[Node]:
141
- """Parse type annotation."""
142
- short_literals = env.config.python_display_short_literal_types
143
-
144
- def unparse(node: ast.AST) -> list[Node]:
145
- if isinstance(node, ast.Attribute):
146
- return [nodes.Text(f"{unparse(node.value)[0]}.{node.attr}")]
147
- if isinstance(node, ast.BinOp):
148
- result: list[Node] = unparse(node.left)
149
- result.extend(unparse(node.op))
150
- result.extend(unparse(node.right))
151
- return result
152
- if isinstance(node, ast.BitOr):
153
- return [addnodes.desc_sig_space(),
154
- addnodes.desc_sig_punctuation('', '|'),
155
- addnodes.desc_sig_space()]
156
- if isinstance(node, ast.Constant):
157
- if node.value is Ellipsis:
158
- return [addnodes.desc_sig_punctuation('', "...")]
159
- if isinstance(node.value, bool):
160
- return [addnodes.desc_sig_keyword('', repr(node.value))]
161
- if isinstance(node.value, int):
162
- return [addnodes.desc_sig_literal_number('', repr(node.value))]
163
- if isinstance(node.value, str):
164
- return [addnodes.desc_sig_literal_string('', repr(node.value))]
165
- else:
166
- # handles None, which is further handled by type_to_xref later
167
- # and fallback for other types that should be converted
168
- return [nodes.Text(repr(node.value))]
169
- if isinstance(node, ast.Expr):
170
- return unparse(node.value)
171
- if isinstance(node, ast.Invert):
172
- return [addnodes.desc_sig_punctuation('', '~')]
173
- if isinstance(node, ast.List):
174
- result = [addnodes.desc_sig_punctuation('', '[')]
175
- if node.elts:
176
- # check if there are elements in node.elts to only pop the
177
- # last element of result if the for-loop was run at least
178
- # once
179
- for elem in node.elts:
180
- result.extend(unparse(elem))
181
- result.append(addnodes.desc_sig_punctuation('', ','))
182
- result.append(addnodes.desc_sig_space())
183
- result.pop()
184
- result.pop()
185
- result.append(addnodes.desc_sig_punctuation('', ']'))
186
- return result
187
- if isinstance(node, ast.Module):
188
- return sum((unparse(e) for e in node.body), [])
189
- if isinstance(node, ast.Name):
190
- return [nodes.Text(node.id)]
191
- if isinstance(node, ast.Subscript):
192
- if getattr(node.value, 'id', '') in {'Optional', 'Union'}:
193
- return _unparse_pep_604_annotation(node)
194
- if short_literals and getattr(node.value, 'id', '') == 'Literal':
195
- return _unparse_pep_604_annotation(node)
196
- result = unparse(node.value)
197
- result.append(addnodes.desc_sig_punctuation('', '['))
198
- result.extend(unparse(node.slice))
199
- result.append(addnodes.desc_sig_punctuation('', ']'))
200
-
201
- # Wrap the Text nodes inside brackets by literal node if the subscript is a Literal
202
- if result[0] in ('Literal', 'typing.Literal'):
203
- for i, subnode in enumerate(result[1:], start=1):
204
- if isinstance(subnode, nodes.Text):
205
- result[i] = nodes.literal('', '', subnode)
206
- return result
207
- if isinstance(node, ast.UnaryOp):
208
- return unparse(node.op) + unparse(node.operand)
209
- if isinstance(node, ast.Tuple):
210
- if node.elts:
211
- result = []
212
- for elem in node.elts:
213
- result.extend(unparse(elem))
214
- result.append(addnodes.desc_sig_punctuation('', ','))
215
- result.append(addnodes.desc_sig_space())
216
- result.pop()
217
- result.pop()
218
- else:
219
- result = [addnodes.desc_sig_punctuation('', '('),
220
- addnodes.desc_sig_punctuation('', ')')]
221
-
222
- return result
223
- raise SyntaxError # unsupported syntax
224
-
225
- def _unparse_pep_604_annotation(node: ast.Subscript) -> list[Node]:
226
- subscript = node.slice
227
-
228
- flattened: list[Node] = []
229
- if isinstance(subscript, ast.Tuple):
230
- flattened.extend(unparse(subscript.elts[0]))
231
- for elt in subscript.elts[1:]:
232
- flattened.extend(unparse(ast.BitOr()))
233
- flattened.extend(unparse(elt))
234
- else:
235
- # e.g. a Union[] inside an Optional[]
236
- flattened.extend(unparse(subscript))
237
-
238
- if getattr(node.value, 'id', '') == 'Optional':
239
- flattened.extend(unparse(ast.BitOr()))
240
- flattened.append(nodes.Text('None'))
241
-
242
- return flattened
243
-
244
- try:
245
- tree = ast.parse(annotation, type_comments=True)
246
- result: list[Node] = []
247
- for node in unparse(tree):
248
- if isinstance(node, nodes.literal):
249
- result.append(node[0])
250
- elif isinstance(node, nodes.Text) and node.strip():
251
- if (result and isinstance(result[-1], addnodes.desc_sig_punctuation) and
252
- result[-1].astext() == '~'):
253
- result.pop()
254
- result.append(type_to_xref(str(node), env, suppress_prefix=True))
255
- else:
256
- result.append(type_to_xref(str(node), env))
257
- else:
258
- result.append(node)
259
- return result
260
- except SyntaxError:
261
- return [type_to_xref(annotation, env)]
262
-
263
-
264
- class _TypeParameterListParser(TokenProcessor):
265
- def __init__(self, sig: str) -> None:
266
- signature = sig.replace('\n', '').strip()
267
- super().__init__([signature])
268
- # Each item is a tuple (name, kind, default, annotation) mimicking
269
- # ``inspect.Parameter`` to allow default values on VAR_POSITIONAL
270
- # or VAR_KEYWORD parameters.
271
- self.type_params: list[tuple[str, int, Any, Any]] = []
272
-
273
- def fetch_type_param_spec(self) -> list[Token]:
274
- tokens = []
275
- while current := self.fetch_token():
276
- tokens.append(current)
277
- for ldelim, rdelim in ('(', ')'), ('{', '}'), ('[', ']'):
278
- if current == [token.OP, ldelim]:
279
- tokens += self.fetch_until([token.OP, rdelim])
280
- break
281
- else:
282
- if current == token.INDENT:
283
- tokens += self.fetch_until(token.DEDENT)
284
- elif current.match(
285
- [token.OP, ':'], [token.OP, '='], [token.OP, ',']):
286
- tokens.pop()
287
- break
288
- return tokens
289
-
290
- def parse(self) -> None:
291
- while current := self.fetch_token():
292
- if current == token.NAME:
293
- tp_name = current.value.strip()
294
- if self.previous and self.previous.match([token.OP, '*'], [token.OP, '**']):
295
- if self.previous == [token.OP, '*']:
296
- tp_kind = Parameter.VAR_POSITIONAL
297
- else:
298
- tp_kind = Parameter.VAR_KEYWORD # type: ignore[assignment]
299
- else:
300
- tp_kind = Parameter.POSITIONAL_OR_KEYWORD # type: ignore[assignment]
301
-
302
- tp_ann: Any = Parameter.empty
303
- tp_default: Any = Parameter.empty
304
-
305
- current = self.fetch_token()
306
- if current and current.match([token.OP, ':'], [token.OP, '=']):
307
- if current == [token.OP, ':']:
308
- tokens = self.fetch_type_param_spec()
309
- tp_ann = self._build_identifier(tokens)
310
-
311
- if self.current and self.current == [token.OP, '=']:
312
- tokens = self.fetch_type_param_spec()
313
- tp_default = self._build_identifier(tokens)
314
-
315
- if tp_kind != Parameter.POSITIONAL_OR_KEYWORD and tp_ann != Parameter.empty:
316
- msg = ('type parameter bound or constraint is not allowed '
317
- f'for {tp_kind.description} parameters')
318
- raise SyntaxError(msg)
319
-
320
- type_param = (tp_name, tp_kind, tp_default, tp_ann)
321
- self.type_params.append(type_param)
322
-
323
- def _build_identifier(self, tokens: list[Token]) -> str:
324
- from itertools import chain, tee
325
-
326
- def pairwise(iterable):
327
- a, b = tee(iterable)
328
- next(b, None)
329
- return zip(a, b)
330
-
331
- def triplewise(iterable):
332
- for (a, _z), (b, c) in pairwise(pairwise(iterable)):
333
- yield a, b, c
334
-
335
- idents: list[str] = []
336
- tokens: Iterable[Token] = iter(tokens) # type: ignore[no-redef]
337
- # do not format opening brackets
338
- for tok in tokens:
339
- if not tok.match([token.OP, '('], [token.OP, '['], [token.OP, '{']):
340
- # check if the first non-delimiter character is an unpack operator
341
- is_unpack_operator = tok.match([token.OP, '*'], [token.OP, ['**']])
342
- idents.append(self._pformat_token(tok, native=is_unpack_operator))
343
- break
344
- idents.append(tok.value)
345
-
346
- # check the remaining tokens
347
- stop = Token(token.ENDMARKER, '', (-1, -1), (-1, -1), '<sentinel>')
348
- is_unpack_operator = False
349
- for tok, op, after in triplewise(chain(tokens, [stop, stop])):
350
- ident = self._pformat_token(tok, native=is_unpack_operator)
351
- idents.append(ident)
352
- # determine if the next token is an unpack operator depending
353
- # on the left and right hand side of the operator symbol
354
- is_unpack_operator = (
355
- op.match([token.OP, '*'], [token.OP, '**']) and not (
356
- tok.match(token.NAME, token.NUMBER, token.STRING,
357
- [token.OP, ')'], [token.OP, ']'], [token.OP, '}'])
358
- and after.match(token.NAME, token.NUMBER, token.STRING,
359
- [token.OP, '('], [token.OP, '['], [token.OP, '{'])
360
- )
361
- )
362
-
363
- return ''.join(idents).strip()
364
-
365
- def _pformat_token(self, tok: Token, native: bool = False) -> str:
366
- if native:
367
- return tok.value
368
-
369
- if tok.match(token.NEWLINE, token.ENDMARKER):
370
- return ''
371
-
372
- if tok.match([token.OP, ':'], [token.OP, ','], [token.OP, '#']):
373
- return f'{tok.value} '
374
-
375
- # Arithmetic operators are allowed because PEP 695 specifies the
376
- # default type parameter to be *any* expression (so "T1 << T2" is
377
- # allowed if it makes sense). The caller is responsible to ensure
378
- # that a multiplication operator ("*") is not to be confused with
379
- # an unpack operator (which will not be surrounded by spaces).
380
- #
381
- # The operators are ordered according to how likely they are to
382
- # be used and for (possible) future implementations (e.g., "&" for
383
- # an intersection type).
384
- if tok.match(
385
- # Most likely operators to appear
386
- [token.OP, '='], [token.OP, '|'],
387
- # Type composition (future compatibility)
388
- [token.OP, '&'], [token.OP, '^'], [token.OP, '<'], [token.OP, '>'],
389
- # Unlikely type composition
390
- [token.OP, '+'], [token.OP, '-'], [token.OP, '*'], [token.OP, '**'],
391
- # Unlikely operators but included for completeness
392
- [token.OP, '@'], [token.OP, '/'], [token.OP, '//'], [token.OP, '%'],
393
- [token.OP, '<<'], [token.OP, '>>'], [token.OP, '>>>'],
394
- [token.OP, '<='], [token.OP, '>='], [token.OP, '=='], [token.OP, '!='],
395
- ):
396
- return f' {tok.value} '
397
-
398
- return tok.value
399
-
400
-
401
- def _parse_type_list(
402
- tp_list: str, env: BuildEnvironment,
403
- multi_line_parameter_list: bool = False,
404
- ) -> addnodes.desc_type_parameter_list:
405
- """Parse a list of type parameters according to PEP 695."""
406
- type_params = addnodes.desc_type_parameter_list(tp_list)
407
- type_params['multi_line_parameter_list'] = multi_line_parameter_list
408
- # formal parameter names are interpreted as type parameter names and
409
- # type annotations are interpreted as type parameter bound or constraints
410
- parser = _TypeParameterListParser(tp_list)
411
- parser.parse()
412
- for (tp_name, tp_kind, tp_default, tp_ann) in parser.type_params:
413
- # no positional-only or keyword-only allowed in a type parameters list
414
- if tp_kind in {Parameter.POSITIONAL_ONLY, Parameter.KEYWORD_ONLY}:
415
- msg = ('positional-only or keyword-only parameters '
416
- 'are prohibited in type parameter lists')
417
- raise SyntaxError(msg)
418
-
419
- node = addnodes.desc_type_parameter()
420
- if tp_kind == Parameter.VAR_POSITIONAL:
421
- node += addnodes.desc_sig_operator('', '*')
422
- elif tp_kind == Parameter.VAR_KEYWORD:
423
- node += addnodes.desc_sig_operator('', '**')
424
- node += addnodes.desc_sig_name('', tp_name)
425
-
426
- if tp_ann is not Parameter.empty:
427
- annotation = _parse_annotation(tp_ann, env)
428
- if not annotation:
429
- continue
430
-
431
- node += addnodes.desc_sig_punctuation('', ':')
432
- node += addnodes.desc_sig_space()
433
-
434
- type_ann_expr = addnodes.desc_sig_name('', '',
435
- *annotation) # type: ignore[arg-type]
436
- # a type bound is ``T: U`` whereas type constraints
437
- # must be enclosed with parentheses. ``T: (U, V)``
438
- if tp_ann.startswith('(') and tp_ann.endswith(')'):
439
- type_ann_text = type_ann_expr.astext()
440
- if type_ann_text.startswith('(') and type_ann_text.endswith(')'):
441
- node += type_ann_expr
442
- else:
443
- # surrounding braces are lost when using _parse_annotation()
444
- node += addnodes.desc_sig_punctuation('', '(')
445
- node += type_ann_expr # type constraint
446
- node += addnodes.desc_sig_punctuation('', ')')
447
- else:
448
- node += type_ann_expr # type bound
449
-
450
- if tp_default is not Parameter.empty:
451
- # Always surround '=' with spaces, even if there is no annotation
452
- node += addnodes.desc_sig_space()
453
- node += addnodes.desc_sig_operator('', '=')
454
- node += addnodes.desc_sig_space()
455
- node += nodes.inline('', tp_default,
456
- classes=['default_value'],
457
- support_smartquotes=False)
458
-
459
- type_params += node
460
- return type_params
461
-
462
-
463
- def _parse_arglist(
464
- arglist: str, env: BuildEnvironment, multi_line_parameter_list: bool = False,
465
- ) -> addnodes.desc_parameterlist:
466
- """Parse a list of arguments using AST parser"""
467
- params = addnodes.desc_parameterlist(arglist)
468
- params['multi_line_parameter_list'] = multi_line_parameter_list
469
- sig = signature_from_str('(%s)' % arglist)
470
- last_kind = None
471
- for param in sig.parameters.values():
472
- if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY:
473
- # PEP-570: Separator for Positional Only Parameter: /
474
- params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/'))
475
- if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
476
- param.POSITIONAL_ONLY,
477
- None):
478
- # PEP-3102: Separator for Keyword Only Parameter: *
479
- params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '*'))
480
-
481
- node = addnodes.desc_parameter()
482
- if param.kind == param.VAR_POSITIONAL:
483
- node += addnodes.desc_sig_operator('', '*')
484
- node += addnodes.desc_sig_name('', param.name)
485
- elif param.kind == param.VAR_KEYWORD:
486
- node += addnodes.desc_sig_operator('', '**')
487
- node += addnodes.desc_sig_name('', param.name)
488
- else:
489
- node += addnodes.desc_sig_name('', param.name)
490
-
491
- if param.annotation is not param.empty:
492
- children = _parse_annotation(param.annotation, env)
493
- node += addnodes.desc_sig_punctuation('', ':')
494
- node += addnodes.desc_sig_space()
495
- node += addnodes.desc_sig_name('', '', *children) # type: ignore[arg-type]
496
- if param.default is not param.empty:
497
- if param.annotation is not param.empty:
498
- node += addnodes.desc_sig_space()
499
- node += addnodes.desc_sig_operator('', '=')
500
- node += addnodes.desc_sig_space()
501
- else:
502
- node += addnodes.desc_sig_operator('', '=')
503
- node += nodes.inline('', param.default, classes=['default_value'],
504
- support_smartquotes=False)
505
-
506
- params += node
507
- last_kind = param.kind
508
-
509
- if last_kind == Parameter.POSITIONAL_ONLY:
510
- # PEP-570: Separator for Positional Only Parameter: /
511
- params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/'))
512
-
513
- return params
514
-
515
-
516
- def _pseudo_parse_arglist(
517
- signode: desc_signature, arglist: str, multi_line_parameter_list: bool = False,
518
- ) -> None:
519
- """"Parse" a list of arguments separated by commas.
520
-
521
- Arguments can have "optional" annotations given by enclosing them in
522
- brackets. Currently, this will split at any comma, even if it's inside a
523
- string literal (e.g. default argument value).
524
- """
525
- paramlist = addnodes.desc_parameterlist()
526
- paramlist['multi_line_parameter_list'] = multi_line_parameter_list
527
- stack: list[Element] = [paramlist]
528
- try:
529
- for argument in arglist.split(','):
530
- argument = argument.strip()
531
- ends_open = ends_close = 0
532
- while argument.startswith('['):
533
- stack.append(addnodes.desc_optional())
534
- stack[-2] += stack[-1]
535
- argument = argument[1:].strip()
536
- while argument.startswith(']'):
537
- stack.pop()
538
- argument = argument[1:].strip()
539
- while argument.endswith(']') and not argument.endswith('[]'):
540
- ends_close += 1
541
- argument = argument[:-1].strip()
542
- while argument.endswith('['):
543
- ends_open += 1
544
- argument = argument[:-1].strip()
545
- if argument:
546
- stack[-1] += addnodes.desc_parameter(
547
- '', '', addnodes.desc_sig_name(argument, argument))
548
- while ends_open:
549
- stack.append(addnodes.desc_optional())
550
- stack[-2] += stack[-1]
551
- ends_open -= 1
552
- while ends_close:
553
- stack.pop()
554
- ends_close -= 1
555
- if len(stack) != 1:
556
- raise IndexError
557
- except IndexError:
558
- # if there are too few or too many elements on the stack, just give up
559
- # and treat the whole argument list as one argument, discarding the
560
- # already partially populated paramlist node
561
- paramlist = addnodes.desc_parameterlist()
562
- paramlist += addnodes.desc_parameter(arglist, arglist)
563
- signode += paramlist
564
- else:
565
- signode += paramlist
566
-
567
-
568
- # This override allows our inline type specifiers to behave like :class: link
569
- # when it comes to handling "." and "~" prefixes.
570
- class PyXrefMixin:
571
- def make_xref(
572
- self,
573
- rolename: str,
574
- domain: str,
575
- target: str,
576
- innernode: type[TextlikeNode] = nodes.emphasis,
577
- contnode: Node | None = None,
578
- env: BuildEnvironment | None = None,
579
- inliner: Inliner | None = None,
580
- location: Node | None = None,
581
- ) -> Node:
582
- # we use inliner=None to make sure we get the old behaviour with a single
583
- # pending_xref node
584
- result = super().make_xref(rolename, domain, target, # type: ignore[misc]
585
- innernode, contnode,
586
- env, inliner=None, location=None)
587
- if isinstance(result, pending_xref):
588
- assert env is not None
589
- result['refspecific'] = True
590
- result['py:module'] = env.ref_context.get('py:module')
591
- result['py:class'] = env.ref_context.get('py:class')
592
-
593
- reftype, reftarget, reftitle, _ = parse_reftarget(target)
594
- if reftarget != reftitle:
595
- result['reftype'] = reftype
596
- result['reftarget'] = reftarget
597
-
598
- result.clear()
599
- result += innernode(reftitle, reftitle)
600
- elif env.config.python_use_unqualified_type_names:
601
- children = result.children
602
- result.clear()
603
-
604
- shortname = target.split('.')[-1]
605
- textnode = innernode('', shortname)
606
- contnodes = [pending_xref_condition('', '', textnode, condition='resolved'),
607
- pending_xref_condition('', '', *children, condition='*')]
608
- result.extend(contnodes)
609
-
610
- return result
611
-
612
- def make_xrefs(
613
- self,
614
- rolename: str,
615
- domain: str,
616
- target: str,
617
- innernode: type[TextlikeNode] = nodes.emphasis,
618
- contnode: Node | None = None,
619
- env: BuildEnvironment | None = None,
620
- inliner: Inliner | None = None,
621
- location: Node | None = None,
622
- ) -> list[Node]:
623
- delims = r'(\s*[\[\]\(\),](?:\s*o[rf]\s)?\s*|\s+o[rf]\s+|\s*\|\s*|\.\.\.)'
624
- delims_re = re.compile(delims)
625
- sub_targets = re.split(delims, target)
626
-
627
- split_contnode = bool(contnode and contnode.astext() == target)
628
-
629
- in_literal = False
630
- results = []
631
- for sub_target in filter(None, sub_targets):
632
- if split_contnode:
633
- contnode = nodes.Text(sub_target)
634
-
635
- if in_literal or delims_re.match(sub_target):
636
- results.append(contnode or innernode(sub_target, sub_target))
637
- else:
638
- results.append(self.make_xref(rolename, domain, sub_target,
639
- innernode, contnode, env, inliner, location))
640
-
641
- if sub_target in ('Literal', 'typing.Literal', '~typing.Literal'):
642
- in_literal = True
643
-
644
- return results
645
-
646
-
647
- class PyField(PyXrefMixin, Field):
648
- pass
649
-
650
-
651
- class PyGroupedField(PyXrefMixin, GroupedField):
652
- pass
653
-
654
-
655
- class PyTypedField(PyXrefMixin, TypedField):
656
- pass
657
-
658
-
659
- class PyObject(ObjectDescription[tuple[str, str]]):
660
- """
661
- Description of a general Python object.
662
-
663
- :cvar allow_nesting: Class is an object that allows for nested namespaces
664
- :vartype allow_nesting: bool
665
- """
666
- option_spec: OptionSpec = {
667
- 'no-index': directives.flag,
668
- 'no-index-entry': directives.flag,
669
- 'no-contents-entry': directives.flag,
670
- 'no-typesetting': directives.flag,
671
- 'noindex': directives.flag,
672
- 'noindexentry': directives.flag,
673
- 'nocontentsentry': directives.flag,
674
- 'single-line-parameter-list': directives.flag,
675
- 'single-line-type-parameter-list': directives.flag,
676
- 'module': directives.unchanged,
677
- 'canonical': directives.unchanged,
678
- 'annotation': directives.unchanged,
679
- }
680
-
681
- doc_field_types = [
682
- PyTypedField('parameter', label=_('Parameters'),
683
- names=('param', 'parameter', 'arg', 'argument',
684
- 'keyword', 'kwarg', 'kwparam'),
685
- typerolename='class', typenames=('paramtype', 'type'),
686
- can_collapse=True),
687
- PyTypedField('variable', label=_('Variables'),
688
- names=('var', 'ivar', 'cvar'),
689
- typerolename='class', typenames=('vartype',),
690
- can_collapse=True),
691
- PyGroupedField('exceptions', label=_('Raises'), rolename='exc',
692
- names=('raises', 'raise', 'exception', 'except'),
693
- can_collapse=True),
694
- Field('returnvalue', label=_('Returns'), has_arg=False,
695
- names=('returns', 'return')),
696
- PyField('returntype', label=_('Return type'), has_arg=False,
697
- names=('rtype',), bodyrolename='class'),
698
- ]
699
-
700
- allow_nesting = False
701
-
702
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
703
- """May return a prefix to put before the object name in the
704
- signature.
705
- """
706
- return []
707
-
708
- def needs_arglist(self) -> bool:
709
- """May return true if an empty argument list is to be generated even if
710
- the document contains none.
711
- """
712
- return False
713
-
714
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
715
- """Transform a Python signature into RST nodes.
716
-
717
- Return (fully qualified name of the thing, classname if any).
718
-
719
- If inside a class, the current class name is handled intelligently:
720
- * it is stripped from the displayed name if present
721
- * it is added to the full name (return value) if not present
722
- """
723
- m = py_sig_re.match(sig)
724
- if m is None:
725
- raise ValueError
726
- prefix, name, tp_list, arglist, retann = m.groups()
727
-
728
- # determine module and class name (if applicable), as well as full name
729
- modname = self.options.get('module', self.env.ref_context.get('py:module'))
730
- classname = self.env.ref_context.get('py:class')
731
- if classname:
732
- add_module = False
733
- if prefix and (prefix == classname or
734
- prefix.startswith(classname + ".")):
735
- fullname = prefix + name
736
- # class name is given again in the signature
737
- prefix = prefix[len(classname):].lstrip('.')
738
- elif prefix:
739
- # class name is given in the signature, but different
740
- # (shouldn't happen)
741
- fullname = classname + '.' + prefix + name
742
- else:
743
- # class name is not given in the signature
744
- fullname = classname + '.' + name
745
- else:
746
- add_module = True
747
- if prefix:
748
- classname = prefix.rstrip('.')
749
- fullname = prefix + name
750
- else:
751
- classname = ''
752
- fullname = name
753
-
754
- signode['module'] = modname
755
- signode['class'] = classname
756
- signode['fullname'] = fullname
757
-
758
- max_len = (self.env.config.python_maximum_signature_line_length
759
- or self.env.config.maximum_signature_line_length
760
- or 0)
761
-
762
- # determine if the function arguments (without its type parameters)
763
- # should be formatted on a multiline or not by removing the width of
764
- # the type parameters list (if any)
765
- sig_len = len(sig)
766
- tp_list_span = m.span(3)
767
- multi_line_parameter_list = (
768
- 'single-line-parameter-list' not in self.options
769
- and (sig_len - (tp_list_span[1] - tp_list_span[0])) > max_len > 0
770
- )
771
-
772
- # determine whether the type parameter list must be wrapped or not
773
- arglist_span = m.span(4)
774
- multi_line_type_parameter_list = (
775
- 'single-line-type-parameter-list' not in self.options
776
- and (sig_len - (arglist_span[1] - arglist_span[0])) > max_len > 0
777
- )
778
-
779
- sig_prefix = self.get_signature_prefix(sig)
780
- if sig_prefix:
781
- if type(sig_prefix) is str:
782
- msg = ("Python directive method get_signature_prefix()"
783
- " must return a list of nodes."
784
- f" Return value was '{sig_prefix}'.")
785
- raise TypeError(msg)
786
- signode += addnodes.desc_annotation(str(sig_prefix), '', *sig_prefix)
787
-
788
- if prefix:
789
- signode += addnodes.desc_addname(prefix, prefix)
790
- elif modname and add_module and self.env.config.add_module_names:
791
- nodetext = modname + '.'
792
- signode += addnodes.desc_addname(nodetext, nodetext)
793
-
794
- signode += addnodes.desc_name(name, name)
795
-
796
- if tp_list:
797
- try:
798
- signode += _parse_type_list(tp_list, self.env, multi_line_type_parameter_list)
799
- except Exception as exc:
800
- logger.warning("could not parse tp_list (%r): %s", tp_list, exc,
801
- location=signode)
802
-
803
- if arglist:
804
- try:
805
- signode += _parse_arglist(arglist, self.env, multi_line_parameter_list)
806
- except SyntaxError:
807
- # fallback to parse arglist original parser
808
- # (this may happen if the argument list is incorrectly used
809
- # as a list of bases when documenting a class)
810
- # it supports to represent optional arguments (ex. "func(foo [, bar])")
811
- _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
812
- except (NotImplementedError, ValueError) as exc:
813
- # duplicated parameter names raise ValueError and not a SyntaxError
814
- logger.warning("could not parse arglist (%r): %s", arglist, exc,
815
- location=signode)
816
- _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
817
- else:
818
- if self.needs_arglist():
819
- # for callables, add an empty parameter list
820
- signode += addnodes.desc_parameterlist()
821
-
822
- if retann:
823
- children = _parse_annotation(retann, self.env)
824
- signode += addnodes.desc_returns(retann, '', *children)
825
-
826
- anno = self.options.get('annotation')
827
- if anno:
828
- signode += addnodes.desc_annotation(' ' + anno, '',
829
- addnodes.desc_sig_space(),
830
- nodes.Text(anno))
831
-
832
- return fullname, prefix
833
-
834
- def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
835
- if 'fullname' not in sig_node:
836
- return ()
837
- modname = sig_node.get('module')
838
- fullname = sig_node['fullname']
839
-
840
- if modname:
841
- return (modname, *fullname.split('.'))
842
- else:
843
- return tuple(fullname.split('.'))
844
-
845
- def get_index_text(self, modname: str, name: tuple[str, str]) -> str:
846
- """Return the text for the index entry of the object."""
847
- msg = 'must be implemented in subclasses'
848
- raise NotImplementedError(msg)
849
-
850
- def add_target_and_index(self, name_cls: tuple[str, str], sig: str,
851
- signode: desc_signature) -> None:
852
- modname = self.options.get('module', self.env.ref_context.get('py:module'))
853
- fullname = (modname + '.' if modname else '') + name_cls[0]
854
- node_id = make_id(self.env, self.state.document, '', fullname)
855
- signode['ids'].append(node_id)
856
- self.state.document.note_explicit_target(signode)
857
-
858
- domain = cast(PythonDomain, self.env.get_domain('py'))
859
- domain.note_object(fullname, self.objtype, node_id, location=signode)
860
-
861
- canonical_name = self.options.get('canonical')
862
- if canonical_name:
863
- domain.note_object(canonical_name, self.objtype, node_id, aliased=True,
864
- location=signode)
865
-
866
- if 'no-index-entry' not in self.options:
867
- indextext = self.get_index_text(modname, name_cls)
868
- if indextext:
869
- self.indexnode['entries'].append(('single', indextext, node_id, '', None))
870
-
871
- def before_content(self) -> None:
872
- """Handle object nesting before content
873
-
874
- :py:class:`PyObject` represents Python language constructs. For
875
- constructs that are nestable, such as a Python classes, this method will
876
- build up a stack of the nesting hierarchy so that it can be later
877
- de-nested correctly, in :py:meth:`after_content`.
878
-
879
- For constructs that aren't nestable, the stack is bypassed, and instead
880
- only the most recent object is tracked. This object prefix name will be
881
- removed with :py:meth:`after_content`.
882
- """
883
- prefix = None
884
- if self.names:
885
- # fullname and name_prefix come from the `handle_signature` method.
886
- # fullname represents the full object name that is constructed using
887
- # object nesting and explicit prefixes. `name_prefix` is the
888
- # explicit prefix given in a signature
889
- (fullname, name_prefix) = self.names[-1]
890
- if self.allow_nesting:
891
- prefix = fullname
892
- elif name_prefix:
893
- prefix = name_prefix.strip('.')
894
- if prefix:
895
- self.env.ref_context['py:class'] = prefix
896
- if self.allow_nesting:
897
- classes = self.env.ref_context.setdefault('py:classes', [])
898
- classes.append(prefix)
899
- if 'module' in self.options:
900
- modules = self.env.ref_context.setdefault('py:modules', [])
901
- modules.append(self.env.ref_context.get('py:module'))
902
- self.env.ref_context['py:module'] = self.options['module']
903
-
904
- def after_content(self) -> None:
905
- """Handle object de-nesting after content
906
-
907
- If this class is a nestable object, removing the last nested class prefix
908
- ends further nesting in the object.
909
-
910
- If this class is not a nestable object, the list of classes should not
911
- be altered as we didn't affect the nesting levels in
912
- :py:meth:`before_content`.
913
- """
914
- classes = self.env.ref_context.setdefault('py:classes', [])
915
- if self.allow_nesting:
916
- with contextlib.suppress(IndexError):
917
- classes.pop()
918
-
919
- self.env.ref_context['py:class'] = (classes[-1] if len(classes) > 0
920
- else None)
921
- if 'module' in self.options:
922
- modules = self.env.ref_context.setdefault('py:modules', [])
923
- if modules:
924
- self.env.ref_context['py:module'] = modules.pop()
925
- else:
926
- self.env.ref_context.pop('py:module')
927
-
928
- def _toc_entry_name(self, sig_node: desc_signature) -> str:
929
- if not sig_node.get('_toc_parts'):
930
- return ''
931
-
932
- config = self.env.app.config
933
- objtype = sig_node.parent.get('objtype')
934
- if config.add_function_parentheses and objtype in {'function', 'method'}:
935
- parens = '()'
936
- else:
937
- parens = ''
938
- *parents, name = sig_node['_toc_parts']
939
- if config.toc_object_entries_show_parents == 'domain':
940
- return sig_node.get('fullname', name) + parens
941
- if config.toc_object_entries_show_parents == 'hide':
942
- return name + parens
943
- if config.toc_object_entries_show_parents == 'all':
944
- return '.'.join(parents + [name + parens])
945
- return ''
946
-
947
-
948
- class PyFunction(PyObject):
949
- """Description of a function."""
950
-
951
- option_spec: OptionSpec = PyObject.option_spec.copy()
952
- option_spec.update({
953
- 'async': directives.flag,
954
- })
955
-
956
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
957
- if 'async' in self.options:
958
- return [addnodes.desc_sig_keyword('', 'async'),
959
- addnodes.desc_sig_space()]
960
- else:
961
- return []
962
-
963
- def needs_arglist(self) -> bool:
964
- return True
965
-
966
- def add_target_and_index(self, name_cls: tuple[str, str], sig: str,
967
- signode: desc_signature) -> None:
968
- super().add_target_and_index(name_cls, sig, signode)
969
- if 'no-index-entry' not in self.options:
970
- modname = self.options.get('module', self.env.ref_context.get('py:module'))
971
- node_id = signode['ids'][0]
972
-
973
- name, cls = name_cls
974
- if modname:
975
- text = _('%s() (in module %s)') % (name, modname)
976
- self.indexnode['entries'].append(('single', text, node_id, '', None))
977
- else:
978
- text = f'built-in function; {name}()'
979
- self.indexnode['entries'].append(('pair', text, node_id, '', None))
980
-
981
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
982
- # add index in own add_target_and_index() instead.
983
- return ''
984
-
985
-
986
- class PyDecoratorFunction(PyFunction):
987
- """Description of a decorator."""
988
-
989
- def run(self) -> list[Node]:
990
- # a decorator function is a function after all
991
- self.name = 'py:function'
992
- return super().run()
993
-
994
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
995
- ret = super().handle_signature(sig, signode)
996
- signode.insert(0, addnodes.desc_addname('@', '@'))
997
- return ret
998
-
999
- def needs_arglist(self) -> bool:
1000
- return False
1001
-
1002
-
1003
- class PyVariable(PyObject):
1004
- """Description of a variable."""
1005
-
1006
- option_spec: OptionSpec = PyObject.option_spec.copy()
1007
- option_spec.update({
1008
- 'type': directives.unchanged,
1009
- 'value': directives.unchanged,
1010
- })
1011
-
1012
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
1013
- fullname, prefix = super().handle_signature(sig, signode)
1014
-
1015
- typ = self.options.get('type')
1016
- if typ:
1017
- annotations = _parse_annotation(typ, self.env)
1018
- signode += addnodes.desc_annotation(typ, '',
1019
- addnodes.desc_sig_punctuation('', ':'),
1020
- addnodes.desc_sig_space(), *annotations)
1021
-
1022
- value = self.options.get('value')
1023
- if value:
1024
- signode += addnodes.desc_annotation(value, '',
1025
- addnodes.desc_sig_space(),
1026
- addnodes.desc_sig_punctuation('', '='),
1027
- addnodes.desc_sig_space(),
1028
- nodes.Text(value))
1029
-
1030
- return fullname, prefix
1031
-
1032
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
1033
- name, cls = name_cls
1034
- if modname:
1035
- return _('%s (in module %s)') % (name, modname)
1036
- else:
1037
- return _('%s (built-in variable)') % name
1038
-
1039
-
1040
- class PyClasslike(PyObject):
1041
- """
1042
- Description of a class-like object (classes, interfaces, exceptions).
1043
- """
1044
-
1045
- option_spec: OptionSpec = PyObject.option_spec.copy()
1046
- option_spec.update({
1047
- 'final': directives.flag,
1048
- })
1049
-
1050
- allow_nesting = True
1051
-
1052
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
1053
- if 'final' in self.options:
1054
- return [nodes.Text('final'), addnodes.desc_sig_space(),
1055
- nodes.Text(self.objtype), addnodes.desc_sig_space()]
1056
- else:
1057
- return [nodes.Text(self.objtype), addnodes.desc_sig_space()]
1058
-
1059
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
1060
- if self.objtype == 'class':
1061
- if not modname:
1062
- return _('%s (built-in class)') % name_cls[0]
1063
- return _('%s (class in %s)') % (name_cls[0], modname)
1064
- elif self.objtype == 'exception':
1065
- return name_cls[0]
1066
- else:
1067
- return ''
1068
-
1069
-
1070
- class PyMethod(PyObject):
1071
- """Description of a method."""
1072
-
1073
- option_spec: OptionSpec = PyObject.option_spec.copy()
1074
- option_spec.update({
1075
- 'abstractmethod': directives.flag,
1076
- 'async': directives.flag,
1077
- 'classmethod': directives.flag,
1078
- 'final': directives.flag,
1079
- 'staticmethod': directives.flag,
1080
- })
1081
-
1082
- def needs_arglist(self) -> bool:
1083
- return True
1084
-
1085
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
1086
- prefix: list[nodes.Node] = []
1087
- if 'final' in self.options:
1088
- prefix.append(nodes.Text('final'))
1089
- prefix.append(addnodes.desc_sig_space())
1090
- if 'abstractmethod' in self.options:
1091
- prefix.append(nodes.Text('abstract'))
1092
- prefix.append(addnodes.desc_sig_space())
1093
- if 'async' in self.options:
1094
- prefix.append(nodes.Text('async'))
1095
- prefix.append(addnodes.desc_sig_space())
1096
- if 'classmethod' in self.options:
1097
- prefix.append(nodes.Text('classmethod'))
1098
- prefix.append(addnodes.desc_sig_space())
1099
- if 'staticmethod' in self.options:
1100
- prefix.append(nodes.Text('static'))
1101
- prefix.append(addnodes.desc_sig_space())
1102
- return prefix
1103
-
1104
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
1105
- name, cls = name_cls
1106
- try:
1107
- clsname, methname = name.rsplit('.', 1)
1108
- if modname and self.env.config.add_module_names:
1109
- clsname = '.'.join([modname, clsname])
1110
- except ValueError:
1111
- if modname:
1112
- return _('%s() (in module %s)') % (name, modname)
1113
- else:
1114
- return '%s()' % name
1115
-
1116
- if 'classmethod' in self.options:
1117
- return _('%s() (%s class method)') % (methname, clsname)
1118
- elif 'staticmethod' in self.options:
1119
- return _('%s() (%s static method)') % (methname, clsname)
1120
- else:
1121
- return _('%s() (%s method)') % (methname, clsname)
1122
-
1123
-
1124
- class PyClassMethod(PyMethod):
1125
- """Description of a classmethod."""
1126
-
1127
- option_spec: OptionSpec = PyObject.option_spec.copy()
1128
-
1129
- def run(self) -> list[Node]:
1130
- self.name = 'py:method'
1131
- self.options['classmethod'] = True
1132
-
1133
- return super().run()
1134
-
1135
-
1136
- class PyStaticMethod(PyMethod):
1137
- """Description of a staticmethod."""
1138
-
1139
- option_spec: OptionSpec = PyObject.option_spec.copy()
1140
-
1141
- def run(self) -> list[Node]:
1142
- self.name = 'py:method'
1143
- self.options['staticmethod'] = True
1144
-
1145
- return super().run()
1146
-
1147
-
1148
- class PyDecoratorMethod(PyMethod):
1149
- """Description of a decoratormethod."""
1150
-
1151
- def run(self) -> list[Node]:
1152
- self.name = 'py:method'
1153
- return super().run()
1154
-
1155
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
1156
- ret = super().handle_signature(sig, signode)
1157
- signode.insert(0, addnodes.desc_addname('@', '@'))
1158
- return ret
1159
-
1160
- def needs_arglist(self) -> bool:
1161
- return False
1162
-
1163
-
1164
- class PyAttribute(PyObject):
1165
- """Description of an attribute."""
1166
-
1167
- option_spec: OptionSpec = PyObject.option_spec.copy()
1168
- option_spec.update({
1169
- 'type': directives.unchanged,
1170
- 'value': directives.unchanged,
1171
- })
1172
-
1173
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
1174
- fullname, prefix = super().handle_signature(sig, signode)
1175
-
1176
- typ = self.options.get('type')
1177
- if typ:
1178
- annotations = _parse_annotation(typ, self.env)
1179
- signode += addnodes.desc_annotation(typ, '',
1180
- addnodes.desc_sig_punctuation('', ':'),
1181
- addnodes.desc_sig_space(),
1182
- *annotations)
1183
-
1184
- value = self.options.get('value')
1185
- if value:
1186
- signode += addnodes.desc_annotation(value, '',
1187
- addnodes.desc_sig_space(),
1188
- addnodes.desc_sig_punctuation('', '='),
1189
- addnodes.desc_sig_space(),
1190
- nodes.Text(value))
1191
-
1192
- return fullname, prefix
1193
-
1194
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
1195
- name, cls = name_cls
1196
- try:
1197
- clsname, attrname = name.rsplit('.', 1)
1198
- if modname and self.env.config.add_module_names:
1199
- clsname = '.'.join([modname, clsname])
1200
- except ValueError:
1201
- if modname:
1202
- return _('%s (in module %s)') % (name, modname)
1203
- else:
1204
- return name
1205
-
1206
- return _('%s (%s attribute)') % (attrname, clsname)
1207
-
1208
-
1209
- class PyProperty(PyObject):
1210
- """Description of an attribute."""
1211
-
1212
- option_spec = PyObject.option_spec.copy()
1213
- option_spec.update({
1214
- 'abstractmethod': directives.flag,
1215
- 'classmethod': directives.flag,
1216
- 'type': directives.unchanged,
1217
- })
1218
-
1219
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
1220
- fullname, prefix = super().handle_signature(sig, signode)
1221
-
1222
- typ = self.options.get('type')
1223
- if typ:
1224
- annotations = _parse_annotation(typ, self.env)
1225
- signode += addnodes.desc_annotation(typ, '',
1226
- addnodes.desc_sig_punctuation('', ':'),
1227
- addnodes.desc_sig_space(),
1228
- *annotations)
1229
-
1230
- return fullname, prefix
1231
-
1232
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
1233
- prefix: list[nodes.Node] = []
1234
- if 'abstractmethod' in self.options:
1235
- prefix.append(nodes.Text('abstract'))
1236
- prefix.append(addnodes.desc_sig_space())
1237
- if 'classmethod' in self.options:
1238
- prefix.append(nodes.Text('class'))
1239
- prefix.append(addnodes.desc_sig_space())
1240
-
1241
- prefix.append(nodes.Text('property'))
1242
- prefix.append(addnodes.desc_sig_space())
1243
- return prefix
1244
-
1245
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
1246
- name, cls = name_cls
1247
- try:
1248
- clsname, attrname = name.rsplit('.', 1)
1249
- if modname and self.env.config.add_module_names:
1250
- clsname = '.'.join([modname, clsname])
1251
- except ValueError:
1252
- if modname:
1253
- return _('%s (in module %s)') % (name, modname)
1254
- else:
1255
- return name
1256
-
1257
- return _('%s (%s property)') % (attrname, clsname)
1258
-
1259
-
1260
- class PyModule(SphinxDirective):
1261
- """
1262
- Directive to mark description of a new module.
1263
- """
1264
-
1265
- has_content = True
1266
- required_arguments = 1
1267
- optional_arguments = 0
1268
- final_argument_whitespace = False
1269
- option_spec: OptionSpec = {
1270
- 'platform': lambda x: x,
1271
- 'synopsis': lambda x: x,
1272
- 'no-index': directives.flag,
1273
- 'no-contents-entry': directives.flag,
1274
- 'no-typesetting': directives.flag,
1275
- 'noindex': directives.flag,
1276
- 'nocontentsentry': directives.flag,
1277
- 'deprecated': directives.flag,
1278
- }
1279
-
1280
- def run(self) -> list[Node]:
1281
- domain = cast(PythonDomain, self.env.get_domain('py'))
1282
-
1283
- modname = self.arguments[0].strip()
1284
- no_index = 'no-index' in self.options or 'noindex' in self.options
1285
- self.env.ref_context['py:module'] = modname
1286
-
1287
- content_node: Element = nodes.section()
1288
- # necessary so that the child nodes get the right source/line set
1289
- content_node.document = self.state.document
1290
- nested_parse_with_titles(self.state, self.content, content_node, self.content_offset)
1291
-
1292
- ret: list[Node] = []
1293
- if not no_index:
1294
- # note module to the domain
1295
- node_id = make_id(self.env, self.state.document, 'module', modname)
1296
- target = nodes.target('', '', ids=[node_id], ismod=True)
1297
- self.set_source_info(target)
1298
- self.state.document.note_explicit_target(target)
1299
-
1300
- domain.note_module(modname,
1301
- node_id,
1302
- self.options.get('synopsis', ''),
1303
- self.options.get('platform', ''),
1304
- 'deprecated' in self.options)
1305
- domain.note_object(modname, 'module', node_id, location=target)
1306
-
1307
- # the platform and synopsis aren't printed; in fact, they are only
1308
- # used in the modindex currently
1309
- indextext = f'module; {modname}'
1310
- inode = addnodes.index(entries=[('pair', indextext, node_id, '', None)])
1311
- # The node order is: index node first, then target node.
1312
- ret.append(inode)
1313
- ret.append(target)
1314
- ret.extend(content_node.children)
1315
- return ret
1316
-
1317
-
1318
- class PyCurrentModule(SphinxDirective):
1319
- """
1320
- This directive is just to tell Sphinx that we're documenting
1321
- stuff in module foo, but links to module foo won't lead here.
1322
- """
1323
-
1324
- has_content = False
1325
- required_arguments = 1
1326
- optional_arguments = 0
1327
- final_argument_whitespace = False
1328
- option_spec: OptionSpec = {}
1329
-
1330
- def run(self) -> list[Node]:
1331
- modname = self.arguments[0].strip()
1332
- if modname == 'None':
1333
- self.env.ref_context.pop('py:module', None)
1334
- else:
1335
- self.env.ref_context['py:module'] = modname
1336
- return []
1337
-
1338
-
1339
- class PyXRefRole(XRefRole):
1340
- def process_link(self, env: BuildEnvironment, refnode: Element,
1341
- has_explicit_title: bool, title: str, target: str) -> tuple[str, str]:
1342
- refnode['py:module'] = env.ref_context.get('py:module')
1343
- refnode['py:class'] = env.ref_context.get('py:class')
1344
- if not has_explicit_title:
1345
- title = title.lstrip('.') # only has a meaning for the target
1346
- target = target.lstrip('~') # only has a meaning for the title
1347
- # if the first character is a tilde, don't display the module/class
1348
- # parts of the contents
1349
- if title[0:1] == '~':
1350
- title = title[1:]
1351
- dot = title.rfind('.')
1352
- if dot != -1:
1353
- title = title[dot + 1:]
1354
- # if the first character is a dot, search more specific namespaces first
1355
- # else search builtins first
1356
- if target[0:1] == '.':
1357
- target = target[1:]
1358
- refnode['refspecific'] = True
1359
- return title, target
1360
-
1361
-
1362
- def filter_meta_fields(app: Sphinx, domain: str, objtype: str, content: Element) -> None:
1363
- """Filter ``:meta:`` field from its docstring."""
1364
- if domain != 'py':
1365
- return
1366
-
1367
- for node in content:
1368
- if isinstance(node, nodes.field_list):
1369
- fields = cast(list[nodes.field], node)
1370
- # removing list items while iterating the list needs reversed()
1371
- for field in reversed(fields):
1372
- field_name = cast(nodes.field_body, field[0]).astext().strip()
1373
- if field_name == 'meta' or field_name.startswith('meta '):
1374
- node.remove(field)
1375
-
1376
-
1377
- class PythonModuleIndex(Index):
1378
- """
1379
- Index subclass to provide the Python module index.
1380
- """
1381
-
1382
- name = 'modindex'
1383
- localname = _('Python Module Index')
1384
- shortname = _('modules')
1385
-
1386
- def generate(self, docnames: Iterable[str] | None = None,
1387
- ) -> tuple[list[tuple[str, list[IndexEntry]]], bool]:
1388
- content: dict[str, list[IndexEntry]] = {}
1389
- # list of prefixes to ignore
1390
- ignores: list[str] = self.domain.env.config['modindex_common_prefix']
1391
- ignores = sorted(ignores, key=len, reverse=True)
1392
- # list of all modules, sorted by module name
1393
- modules = sorted(self.domain.data['modules'].items(),
1394
- key=lambda x: x[0].lower())
1395
- # sort out collapsible modules
1396
- prev_modname = ''
1397
- num_toplevels = 0
1398
- for modname, (docname, node_id, synopsis, platforms, deprecated) in modules:
1399
- if docnames and docname not in docnames:
1400
- continue
1401
-
1402
- for ignore in ignores:
1403
- if modname.startswith(ignore):
1404
- modname = modname[len(ignore):]
1405
- stripped = ignore
1406
- break
1407
- else:
1408
- stripped = ''
1409
-
1410
- # we stripped the whole module name?
1411
- if not modname:
1412
- modname, stripped = stripped, ''
1413
-
1414
- entries = content.setdefault(modname[0].lower(), [])
1415
-
1416
- package = modname.split('.')[0]
1417
- if package != modname:
1418
- # it's a submodule
1419
- if prev_modname == package:
1420
- # first submodule - make parent a group head
1421
- if entries:
1422
- last = entries[-1]
1423
- entries[-1] = IndexEntry(last[0], 1, last[2], last[3],
1424
- last[4], last[5], last[6])
1425
- elif not prev_modname.startswith(package):
1426
- # submodule without parent in list, add dummy entry
1427
- entries.append(IndexEntry(stripped + package, 1, '', '', '', '', ''))
1428
- subtype = 2
1429
- else:
1430
- num_toplevels += 1
1431
- subtype = 0
1432
-
1433
- qualifier = _('Deprecated') if deprecated else ''
1434
- entries.append(IndexEntry(stripped + modname, subtype, docname,
1435
- node_id, platforms, qualifier, synopsis))
1436
- prev_modname = modname
1437
-
1438
- # apply heuristics when to collapse modindex at page load:
1439
- # only collapse if number of toplevel modules is larger than
1440
- # number of submodules
1441
- collapse = len(modules) - num_toplevels < num_toplevels
1442
-
1443
- # sort by first letter
1444
- sorted_content = sorted(content.items())
1445
-
1446
- return sorted_content, collapse
1447
-
1448
-
1449
- class PythonDomain(Domain):
1450
- """Python language domain."""
1451
- name = 'py'
1452
- label = 'Python'
1453
- object_types: dict[str, ObjType] = {
1454
- 'function': ObjType(_('function'), 'func', 'obj'),
1455
- 'data': ObjType(_('data'), 'data', 'obj'),
1456
- 'class': ObjType(_('class'), 'class', 'exc', 'obj'),
1457
- 'exception': ObjType(_('exception'), 'exc', 'class', 'obj'),
1458
- 'method': ObjType(_('method'), 'meth', 'obj'),
1459
- 'classmethod': ObjType(_('class method'), 'meth', 'obj'),
1460
- 'staticmethod': ObjType(_('static method'), 'meth', 'obj'),
1461
- 'attribute': ObjType(_('attribute'), 'attr', 'obj'),
1462
- 'property': ObjType(_('property'), 'attr', '_prop', 'obj'),
1463
- 'module': ObjType(_('module'), 'mod', 'obj'),
1464
- }
1465
-
1466
- directives = {
1467
- 'function': PyFunction,
1468
- 'data': PyVariable,
1469
- 'class': PyClasslike,
1470
- 'exception': PyClasslike,
1471
- 'method': PyMethod,
1472
- 'classmethod': PyClassMethod,
1473
- 'staticmethod': PyStaticMethod,
1474
- 'attribute': PyAttribute,
1475
- 'property': PyProperty,
1476
- 'module': PyModule,
1477
- 'currentmodule': PyCurrentModule,
1478
- 'decorator': PyDecoratorFunction,
1479
- 'decoratormethod': PyDecoratorMethod,
1480
- }
1481
- roles = {
1482
- 'data': PyXRefRole(),
1483
- 'exc': PyXRefRole(),
1484
- 'func': PyXRefRole(fix_parens=True),
1485
- 'class': PyXRefRole(),
1486
- 'const': PyXRefRole(),
1487
- 'attr': PyXRefRole(),
1488
- 'meth': PyXRefRole(fix_parens=True),
1489
- 'mod': PyXRefRole(),
1490
- 'obj': PyXRefRole(),
1491
- }
1492
- initial_data: dict[str, dict[str, tuple[Any]]] = {
1493
- 'objects': {}, # fullname -> docname, objtype
1494
- 'modules': {}, # modname -> docname, synopsis, platform, deprecated
1495
- }
1496
- indices = [
1497
- PythonModuleIndex,
1498
- ]
1499
-
1500
- @property
1501
- def objects(self) -> dict[str, ObjectEntry]:
1502
- return self.data.setdefault('objects', {}) # fullname -> ObjectEntry
1503
-
1504
- def note_object(self, name: str, objtype: str, node_id: str,
1505
- aliased: bool = False, location: Any = None) -> None:
1506
- """Note a python object for cross reference.
1507
-
1508
- .. versionadded:: 2.1
1509
- """
1510
- if name in self.objects:
1511
- other = self.objects[name]
1512
- if other.aliased and aliased is False:
1513
- # The original definition found. Override it!
1514
- pass
1515
- elif other.aliased is False and aliased:
1516
- # The original definition is already registered.
1517
- return
1518
- else:
1519
- # duplicated
1520
- logger.warning(__('duplicate object description of %s, '
1521
- 'other instance in %s, use :no-index: for one of them'),
1522
- name, other.docname, location=location)
1523
- self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype, aliased)
1524
-
1525
- @property
1526
- def modules(self) -> dict[str, ModuleEntry]:
1527
- return self.data.setdefault('modules', {}) # modname -> ModuleEntry
1528
-
1529
- def note_module(self, name: str, node_id: str, synopsis: str,
1530
- platform: str, deprecated: bool) -> None:
1531
- """Note a python module for cross reference.
1532
-
1533
- .. versionadded:: 2.1
1534
- """
1535
- self.modules[name] = ModuleEntry(self.env.docname, node_id,
1536
- synopsis, platform, deprecated)
1537
-
1538
- def clear_doc(self, docname: str) -> None:
1539
- for fullname, obj in list(self.objects.items()):
1540
- if obj.docname == docname:
1541
- del self.objects[fullname]
1542
- for modname, mod in list(self.modules.items()):
1543
- if mod.docname == docname:
1544
- del self.modules[modname]
1545
-
1546
- def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> None:
1547
- # XXX check duplicates?
1548
- for fullname, obj in otherdata['objects'].items():
1549
- if obj.docname in docnames:
1550
- self.objects[fullname] = obj
1551
- for modname, mod in otherdata['modules'].items():
1552
- if mod.docname in docnames:
1553
- self.modules[modname] = mod
1554
-
1555
- def find_obj(self, env: BuildEnvironment, modname: str, classname: str,
1556
- name: str, type: str | None, searchmode: int = 0,
1557
- ) -> list[tuple[str, ObjectEntry]]:
1558
- """Find a Python object for "name", perhaps using the given module
1559
- and/or classname. Returns a list of (name, object entry) tuples.
1560
- """
1561
- # skip parens
1562
- if name[-2:] == '()':
1563
- name = name[:-2]
1564
-
1565
- if not name:
1566
- return []
1567
-
1568
- matches: list[tuple[str, ObjectEntry]] = []
1569
-
1570
- newname = None
1571
- if searchmode == 1:
1572
- if type is None:
1573
- objtypes: list[str] | None = list(self.object_types)
1574
- else:
1575
- objtypes = self.objtypes_for_role(type)
1576
- if objtypes is not None:
1577
- if modname and classname:
1578
- fullname = modname + '.' + classname + '.' + name
1579
- if fullname in self.objects and self.objects[fullname].objtype in objtypes:
1580
- newname = fullname
1581
- if not newname:
1582
- if modname and modname + '.' + name in self.objects and \
1583
- self.objects[modname + '.' + name].objtype in objtypes:
1584
- newname = modname + '.' + name
1585
- elif name in self.objects and self.objects[name].objtype in objtypes:
1586
- newname = name
1587
- else:
1588
- # "fuzzy" searching mode
1589
- searchname = '.' + name
1590
- matches = [(oname, self.objects[oname]) for oname in self.objects
1591
- if oname.endswith(searchname) and
1592
- self.objects[oname].objtype in objtypes]
1593
- else:
1594
- # NOTE: searching for exact match, object type is not considered
1595
- if name in self.objects:
1596
- newname = name
1597
- elif type == 'mod':
1598
- # only exact matches allowed for modules
1599
- return []
1600
- elif classname and classname + '.' + name in self.objects:
1601
- newname = classname + '.' + name
1602
- elif modname and modname + '.' + name in self.objects:
1603
- newname = modname + '.' + name
1604
- elif modname and classname and \
1605
- modname + '.' + classname + '.' + name in self.objects:
1606
- newname = modname + '.' + classname + '.' + name
1607
- if newname is not None:
1608
- matches.append((newname, self.objects[newname]))
1609
- return matches
1610
-
1611
- def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
1612
- type: str, target: str, node: pending_xref, contnode: Element,
1613
- ) -> Element | None:
1614
- modname = node.get('py:module')
1615
- clsname = node.get('py:class')
1616
- searchmode = 1 if node.hasattr('refspecific') else 0
1617
- matches = self.find_obj(env, modname, clsname, target,
1618
- type, searchmode)
1619
-
1620
- if not matches and type == 'attr':
1621
- # fallback to meth (for property; Sphinx 2.4.x)
1622
- # this ensures that `:attr:` role continues to refer to the old property entry
1623
- # that defined by ``method`` directive in old reST files.
1624
- matches = self.find_obj(env, modname, clsname, target, 'meth', searchmode)
1625
- if not matches and type == 'meth':
1626
- # fallback to attr (for property)
1627
- # this ensures that `:meth:` in the old reST files can refer to the property
1628
- # entry that defined by ``property`` directive.
1629
- #
1630
- # Note: _prop is a secret role only for internal look-up.
1631
- matches = self.find_obj(env, modname, clsname, target, '_prop', searchmode)
1632
-
1633
- if not matches:
1634
- return None
1635
- elif len(matches) > 1:
1636
- canonicals = [m for m in matches if not m[1].aliased]
1637
- if len(canonicals) == 1:
1638
- matches = canonicals
1639
- else:
1640
- logger.warning(__('more than one target found for cross-reference %r: %s'),
1641
- target, ', '.join(match[0] for match in matches),
1642
- type='ref', subtype='python', location=node)
1643
- name, obj = matches[0]
1644
-
1645
- if obj[2] == 'module':
1646
- return self._make_module_refnode(builder, fromdocname, name, contnode)
1647
- else:
1648
- # determine the content of the reference by conditions
1649
- content = find_pending_xref_condition(node, 'resolved')
1650
- if content:
1651
- children = content.children
1652
- else:
1653
- # if not found, use contnode
1654
- children = [contnode]
1655
-
1656
- return make_refnode(builder, fromdocname, obj[0], obj[1], children, name)
1657
-
1658
- def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
1659
- target: str, node: pending_xref, contnode: Element,
1660
- ) -> list[tuple[str, Element]]:
1661
- modname = node.get('py:module')
1662
- clsname = node.get('py:class')
1663
- results: list[tuple[str, Element]] = []
1664
-
1665
- # always search in "refspecific" mode with the :any: role
1666
- matches = self.find_obj(env, modname, clsname, target, None, 1)
1667
- multiple_matches = len(matches) > 1
1668
-
1669
- for name, obj in matches:
1670
-
1671
- if multiple_matches and obj.aliased:
1672
- # Skip duplicated matches
1673
- continue
1674
-
1675
- if obj[2] == 'module':
1676
- results.append(('py:mod',
1677
- self._make_module_refnode(builder, fromdocname,
1678
- name, contnode)))
1679
- else:
1680
- # determine the content of the reference by conditions
1681
- content = find_pending_xref_condition(node, 'resolved')
1682
- if content:
1683
- children = content.children
1684
- else:
1685
- # if not found, use contnode
1686
- children = [contnode]
1687
-
1688
- role = 'py:' + self.role_for_objtype(obj[2]) # type: ignore[operator]
1689
- results.append((role, make_refnode(builder, fromdocname, obj[0], obj[1],
1690
- children, name)))
1691
- return results
1692
-
1693
- def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str,
1694
- contnode: Node) -> Element:
1695
- # get additional info for modules
1696
- module = self.modules[name]
1697
- title = name
1698
- if module.synopsis:
1699
- title += ': ' + module.synopsis
1700
- if module.deprecated:
1701
- title += _(' (deprecated)')
1702
- if module.platform:
1703
- title += ' (' + module.platform + ')'
1704
- return make_refnode(builder, fromdocname, module.docname, module.node_id,
1705
- contnode, title)
1706
-
1707
- def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:
1708
- for modname, mod in self.modules.items():
1709
- yield (modname, modname, 'module', mod.docname, mod.node_id, 0)
1710
- for refname, obj in self.objects.items():
1711
- if obj.objtype != 'module': # modules are already handled
1712
- if obj.aliased:
1713
- # aliased names are not full-text searchable.
1714
- yield (refname, refname, obj.objtype, obj.docname, obj.node_id, -1)
1715
- else:
1716
- yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
1717
-
1718
- def get_full_qualified_name(self, node: Element) -> str | None:
1719
- modname = node.get('py:module')
1720
- clsname = node.get('py:class')
1721
- target = node.get('reftarget')
1722
- if target is None:
1723
- return None
1724
- else:
1725
- return '.'.join(filter(None, [modname, clsname, target]))
1726
-
1727
-
1728
- def builtin_resolver(app: Sphinx, env: BuildEnvironment,
1729
- node: pending_xref, contnode: Element) -> Element | None:
1730
- """Do not emit nitpicky warnings for built-in types."""
1731
- def istyping(s: str) -> bool:
1732
- if s.startswith('typing.'):
1733
- s = s.split('.', 1)[1]
1734
-
1735
- return s in typing.__all__
1736
-
1737
- if node.get('refdomain') != 'py':
1738
- return None
1739
- elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None':
1740
- return contnode
1741
- elif node.get('reftype') in ('class', 'obj', 'exc'):
1742
- reftarget = node.get('reftarget')
1743
- if inspect.isclass(getattr(builtins, reftarget, None)):
1744
- # built-in class
1745
- return contnode
1746
- if istyping(reftarget):
1747
- # typing class
1748
- return contnode
1749
-
1750
- return None
1751
-
1752
-
1753
- def setup(app: Sphinx) -> dict[str, Any]:
1754
- app.setup_extension('sphinx.directives')
1755
-
1756
- app.add_domain(PythonDomain)
1757
- app.add_config_value('python_use_unqualified_type_names', False, 'env')
1758
- app.add_config_value('python_maximum_signature_line_length', None, 'env',
1759
- types={int, None})
1760
- app.add_config_value('python_display_short_literal_types', False, 'env')
1761
- app.connect('object-description-transform', filter_meta_fields)
1762
- app.connect('missing-reference', builtin_resolver, priority=900)
1763
-
1764
- return {
1765
- 'version': 'builtin',
1766
- 'env_version': 4,
1767
- 'parallel_read_safe': True,
1768
- 'parallel_write_safe': True,
1769
- }