Sphinx 8.1.3__py3-none-any.whl → 8.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Sphinx might be problematic. Click here for more details.

Files changed (328) hide show
  1. sphinx/__init__.py +8 -4
  2. sphinx/__main__.py +2 -0
  3. sphinx/_cli/__init__.py +2 -5
  4. sphinx/_cli/util/colour.py +34 -11
  5. sphinx/_cli/util/errors.py +128 -61
  6. sphinx/addnodes.py +51 -35
  7. sphinx/application.py +362 -230
  8. sphinx/builders/__init__.py +87 -64
  9. sphinx/builders/_epub_base.py +65 -56
  10. sphinx/builders/changes.py +17 -23
  11. sphinx/builders/dirhtml.py +8 -13
  12. sphinx/builders/epub3.py +70 -38
  13. sphinx/builders/gettext.py +93 -73
  14. sphinx/builders/html/__init__.py +240 -186
  15. sphinx/builders/html/_assets.py +9 -2
  16. sphinx/builders/html/_build_info.py +3 -0
  17. sphinx/builders/latex/__init__.py +64 -54
  18. sphinx/builders/latex/constants.py +14 -11
  19. sphinx/builders/latex/nodes.py +2 -0
  20. sphinx/builders/latex/theming.py +8 -9
  21. sphinx/builders/latex/transforms.py +7 -5
  22. sphinx/builders/linkcheck.py +193 -149
  23. sphinx/builders/manpage.py +17 -17
  24. sphinx/builders/singlehtml.py +28 -16
  25. sphinx/builders/texinfo.py +28 -21
  26. sphinx/builders/text.py +10 -15
  27. sphinx/builders/xml.py +10 -19
  28. sphinx/cmd/build.py +49 -119
  29. sphinx/cmd/make_mode.py +35 -31
  30. sphinx/cmd/quickstart.py +78 -62
  31. sphinx/config.py +265 -163
  32. sphinx/directives/__init__.py +51 -54
  33. sphinx/directives/admonitions.py +107 -0
  34. sphinx/directives/code.py +24 -19
  35. sphinx/directives/other.py +21 -42
  36. sphinx/directives/patches.py +28 -16
  37. sphinx/domains/__init__.py +54 -31
  38. sphinx/domains/_domains_container.py +22 -17
  39. sphinx/domains/_index.py +5 -8
  40. sphinx/domains/c/__init__.py +366 -245
  41. sphinx/domains/c/_ast.py +378 -256
  42. sphinx/domains/c/_ids.py +89 -31
  43. sphinx/domains/c/_parser.py +283 -214
  44. sphinx/domains/c/_symbol.py +269 -198
  45. sphinx/domains/changeset.py +39 -24
  46. sphinx/domains/citation.py +54 -24
  47. sphinx/domains/cpp/__init__.py +517 -362
  48. sphinx/domains/cpp/_ast.py +999 -682
  49. sphinx/domains/cpp/_ids.py +133 -65
  50. sphinx/domains/cpp/_parser.py +746 -588
  51. sphinx/domains/cpp/_symbol.py +692 -489
  52. sphinx/domains/index.py +10 -8
  53. sphinx/domains/javascript.py +152 -74
  54. sphinx/domains/math.py +50 -40
  55. sphinx/domains/python/__init__.py +402 -211
  56. sphinx/domains/python/_annotations.py +134 -61
  57. sphinx/domains/python/_object.py +155 -68
  58. sphinx/domains/rst.py +94 -49
  59. sphinx/domains/std/__init__.py +510 -249
  60. sphinx/environment/__init__.py +345 -61
  61. sphinx/environment/adapters/asset.py +7 -1
  62. sphinx/environment/adapters/indexentries.py +15 -20
  63. sphinx/environment/adapters/toctree.py +19 -9
  64. sphinx/environment/collectors/__init__.py +3 -1
  65. sphinx/environment/collectors/asset.py +18 -15
  66. sphinx/environment/collectors/dependencies.py +8 -10
  67. sphinx/environment/collectors/metadata.py +6 -4
  68. sphinx/environment/collectors/title.py +3 -1
  69. sphinx/environment/collectors/toctree.py +4 -4
  70. sphinx/errors.py +1 -3
  71. sphinx/events.py +4 -4
  72. sphinx/ext/apidoc/__init__.py +66 -0
  73. sphinx/ext/apidoc/__main__.py +9 -0
  74. sphinx/ext/apidoc/_cli.py +356 -0
  75. sphinx/ext/apidoc/_extension.py +262 -0
  76. sphinx/ext/apidoc/_generate.py +356 -0
  77. sphinx/ext/apidoc/_shared.py +99 -0
  78. sphinx/ext/autodoc/__init__.py +829 -480
  79. sphinx/ext/autodoc/directive.py +57 -21
  80. sphinx/ext/autodoc/importer.py +184 -67
  81. sphinx/ext/autodoc/mock.py +25 -10
  82. sphinx/ext/autodoc/preserve_defaults.py +17 -9
  83. sphinx/ext/autodoc/type_comment.py +56 -29
  84. sphinx/ext/autodoc/typehints.py +49 -26
  85. sphinx/ext/autosectionlabel.py +28 -11
  86. sphinx/ext/autosummary/__init__.py +281 -142
  87. sphinx/ext/autosummary/generate.py +121 -51
  88. sphinx/ext/coverage.py +152 -91
  89. sphinx/ext/doctest.py +169 -101
  90. sphinx/ext/duration.py +12 -6
  91. sphinx/ext/extlinks.py +33 -21
  92. sphinx/ext/githubpages.py +8 -8
  93. sphinx/ext/graphviz.py +175 -109
  94. sphinx/ext/ifconfig.py +11 -6
  95. sphinx/ext/imgconverter.py +48 -25
  96. sphinx/ext/imgmath.py +127 -97
  97. sphinx/ext/inheritance_diagram.py +177 -103
  98. sphinx/ext/intersphinx/__init__.py +22 -13
  99. sphinx/ext/intersphinx/__main__.py +3 -1
  100. sphinx/ext/intersphinx/_cli.py +18 -14
  101. sphinx/ext/intersphinx/_load.py +91 -82
  102. sphinx/ext/intersphinx/_resolve.py +108 -74
  103. sphinx/ext/intersphinx/_shared.py +2 -2
  104. sphinx/ext/linkcode.py +28 -12
  105. sphinx/ext/mathjax.py +60 -29
  106. sphinx/ext/napoleon/__init__.py +19 -7
  107. sphinx/ext/napoleon/docstring.py +229 -231
  108. sphinx/ext/todo.py +44 -49
  109. sphinx/ext/viewcode.py +105 -57
  110. sphinx/extension.py +3 -1
  111. sphinx/highlighting.py +13 -7
  112. sphinx/io.py +9 -13
  113. sphinx/jinja2glue.py +29 -26
  114. sphinx/locale/__init__.py +8 -9
  115. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  116. sphinx/locale/ar/LC_MESSAGES/sphinx.po +2155 -2050
  117. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  118. sphinx/locale/bg/LC_MESSAGES/sphinx.po +2045 -1940
  119. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  120. sphinx/locale/bn/LC_MESSAGES/sphinx.po +2175 -2070
  121. sphinx/locale/ca/LC_MESSAGES/sphinx.js +3 -3
  122. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/ca/LC_MESSAGES/sphinx.po +2690 -2585
  124. sphinx/locale/ca@valencia/LC_MESSAGES/sphinx.js +63 -0
  125. sphinx/locale/ca@valencia/LC_MESSAGES/sphinx.mo +0 -0
  126. sphinx/locale/ca@valencia/LC_MESSAGES/sphinx.po +4216 -0
  127. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  128. sphinx/locale/cak/LC_MESSAGES/sphinx.po +2096 -1991
  129. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  130. sphinx/locale/cs/LC_MESSAGES/sphinx.po +2248 -2143
  131. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  132. sphinx/locale/cy/LC_MESSAGES/sphinx.po +2201 -2096
  133. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  134. sphinx/locale/da/LC_MESSAGES/sphinx.po +2282 -2177
  135. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  136. sphinx/locale/de/LC_MESSAGES/sphinx.po +2261 -2156
  137. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  138. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +2045 -1940
  139. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  140. sphinx/locale/el/LC_MESSAGES/sphinx.po +2604 -2499
  141. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  142. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +2045 -1940
  143. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +2045 -1940
  145. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  146. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +2631 -2526
  147. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  148. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +2045 -1940
  149. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  150. sphinx/locale/eo/LC_MESSAGES/sphinx.po +2078 -1973
  151. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  152. sphinx/locale/es/LC_MESSAGES/sphinx.po +2633 -2528
  153. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +2045 -1940
  155. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/et/LC_MESSAGES/sphinx.po +2449 -2344
  157. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  158. sphinx/locale/eu/LC_MESSAGES/sphinx.po +2241 -2136
  159. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/fa/LC_MESSAGES/sphinx.po +504 -500
  161. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/fi/LC_MESSAGES/sphinx.po +499 -495
  163. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/fr/LC_MESSAGES/sphinx.po +513 -509
  165. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +499 -495
  167. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/gl/LC_MESSAGES/sphinx.po +2644 -2539
  169. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/he/LC_MESSAGES/sphinx.po +499 -495
  171. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/hi/LC_MESSAGES/sphinx.po +504 -500
  173. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +499 -495
  175. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/hr/LC_MESSAGES/sphinx.po +501 -497
  177. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/hu/LC_MESSAGES/sphinx.po +499 -495
  179. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  180. sphinx/locale/id/LC_MESSAGES/sphinx.po +2609 -2504
  181. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  182. sphinx/locale/is/LC_MESSAGES/sphinx.po +499 -495
  183. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  184. sphinx/locale/it/LC_MESSAGES/sphinx.po +2265 -2160
  185. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  186. sphinx/locale/ja/LC_MESSAGES/sphinx.po +2621 -2516
  187. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  188. sphinx/locale/ka/LC_MESSAGES/sphinx.po +2567 -2462
  189. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/ko/LC_MESSAGES/sphinx.po +2631 -2526
  191. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  192. sphinx/locale/lt/LC_MESSAGES/sphinx.po +2214 -2109
  193. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  194. sphinx/locale/lv/LC_MESSAGES/sphinx.po +2218 -2113
  195. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  196. sphinx/locale/mk/LC_MESSAGES/sphinx.po +2088 -1983
  197. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  198. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +2247 -2142
  199. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  200. sphinx/locale/ne/LC_MESSAGES/sphinx.po +2227 -2122
  201. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  202. sphinx/locale/nl/LC_MESSAGES/sphinx.po +2316 -2211
  203. sphinx/locale/pl/LC_MESSAGES/sphinx.js +2 -2
  204. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  205. sphinx/locale/pl/LC_MESSAGES/sphinx.po +2442 -2336
  206. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/pt/LC_MESSAGES/sphinx.po +2045 -1940
  208. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  209. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +2657 -2552
  210. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +2243 -2138
  212. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/ro/LC_MESSAGES/sphinx.po +2244 -2139
  214. sphinx/locale/ru/LC_MESSAGES/sphinx.js +1 -1
  215. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  216. sphinx/locale/ru/LC_MESSAGES/sphinx.po +2660 -2555
  217. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  218. sphinx/locale/si/LC_MESSAGES/sphinx.po +2134 -2029
  219. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  220. sphinx/locale/sk/LC_MESSAGES/sphinx.po +2614 -2509
  221. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  222. sphinx/locale/sl/LC_MESSAGES/sphinx.po +2167 -2062
  223. sphinx/locale/sphinx.pot +2069 -1964
  224. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  225. sphinx/locale/sq/LC_MESSAGES/sphinx.po +2661 -2556
  226. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  227. sphinx/locale/sr/LC_MESSAGES/sphinx.po +2213 -2108
  228. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  229. sphinx/locale/sv/LC_MESSAGES/sphinx.po +2229 -2124
  230. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  231. sphinx/locale/te/LC_MESSAGES/sphinx.po +2045 -1940
  232. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  233. sphinx/locale/tr/LC_MESSAGES/sphinx.po +2608 -2503
  234. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  235. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +2167 -2062
  236. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  237. sphinx/locale/ur/LC_MESSAGES/sphinx.po +2045 -1940
  238. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  239. sphinx/locale/vi/LC_MESSAGES/sphinx.po +2204 -2099
  240. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  241. sphinx/locale/yue/LC_MESSAGES/sphinx.po +2045 -1940
  242. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  243. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +2045 -1940
  244. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  245. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +2659 -2554
  246. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  247. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +2045 -1940
  248. sphinx/parsers.py +8 -7
  249. sphinx/project.py +2 -2
  250. sphinx/pycode/__init__.py +31 -21
  251. sphinx/pycode/ast.py +6 -3
  252. sphinx/pycode/parser.py +14 -8
  253. sphinx/pygments_styles.py +4 -5
  254. sphinx/registry.py +192 -92
  255. sphinx/roles.py +58 -7
  256. sphinx/search/__init__.py +75 -54
  257. sphinx/search/en.py +11 -13
  258. sphinx/search/fi.py +1 -1
  259. sphinx/search/ja.py +8 -6
  260. sphinx/search/nl.py +1 -1
  261. sphinx/search/zh.py +19 -21
  262. sphinx/testing/fixtures.py +26 -29
  263. sphinx/testing/path.py +26 -62
  264. sphinx/testing/restructuredtext.py +14 -8
  265. sphinx/testing/util.py +21 -19
  266. sphinx/texinputs/make.bat.jinja +50 -50
  267. sphinx/texinputs/sphinx.sty +4 -3
  268. sphinx/texinputs/sphinxlatexadmonitions.sty +1 -1
  269. sphinx/texinputs/sphinxlatexobjects.sty +29 -10
  270. sphinx/themes/basic/static/searchtools.js +8 -5
  271. sphinx/theming.py +49 -61
  272. sphinx/transforms/__init__.py +17 -38
  273. sphinx/transforms/compact_bullet_list.py +5 -3
  274. sphinx/transforms/i18n.py +8 -21
  275. sphinx/transforms/post_transforms/__init__.py +142 -93
  276. sphinx/transforms/post_transforms/code.py +5 -5
  277. sphinx/transforms/post_transforms/images.py +28 -24
  278. sphinx/transforms/references.py +3 -1
  279. sphinx/util/__init__.py +109 -60
  280. sphinx/util/_files.py +39 -23
  281. sphinx/util/_importer.py +4 -1
  282. sphinx/util/_inventory_file_reader.py +76 -0
  283. sphinx/util/_io.py +2 -2
  284. sphinx/util/_lines.py +6 -3
  285. sphinx/util/_pathlib.py +40 -2
  286. sphinx/util/build_phase.py +2 -0
  287. sphinx/util/cfamily.py +19 -14
  288. sphinx/util/console.py +44 -179
  289. sphinx/util/display.py +9 -10
  290. sphinx/util/docfields.py +140 -122
  291. sphinx/util/docstrings.py +1 -1
  292. sphinx/util/docutils.py +118 -77
  293. sphinx/util/fileutil.py +25 -26
  294. sphinx/util/http_date.py +2 -0
  295. sphinx/util/i18n.py +77 -64
  296. sphinx/util/images.py +8 -6
  297. sphinx/util/inspect.py +147 -38
  298. sphinx/util/inventory.py +215 -116
  299. sphinx/util/logging.py +33 -33
  300. sphinx/util/matching.py +12 -4
  301. sphinx/util/nodes.py +18 -13
  302. sphinx/util/osutil.py +38 -39
  303. sphinx/util/parallel.py +22 -13
  304. sphinx/util/parsing.py +2 -1
  305. sphinx/util/png.py +6 -2
  306. sphinx/util/requests.py +33 -2
  307. sphinx/util/rst.py +3 -2
  308. sphinx/util/tags.py +1 -1
  309. sphinx/util/template.py +18 -10
  310. sphinx/util/texescape.py +8 -6
  311. sphinx/util/typing.py +148 -122
  312. sphinx/versioning.py +3 -3
  313. sphinx/writers/html.py +3 -1
  314. sphinx/writers/html5.py +63 -52
  315. sphinx/writers/latex.py +83 -67
  316. sphinx/writers/manpage.py +19 -38
  317. sphinx/writers/texinfo.py +47 -47
  318. sphinx/writers/text.py +50 -32
  319. sphinx/writers/xml.py +11 -8
  320. {sphinx-8.1.3.dist-info → sphinx-8.2.0.dist-info}/LICENSE.rst +1 -1
  321. {sphinx-8.1.3.dist-info → sphinx-8.2.0.dist-info}/METADATA +25 -15
  322. sphinx-8.2.0.dist-info/RECORD +606 -0
  323. {sphinx-8.1.3.dist-info → sphinx-8.2.0.dist-info}/WHEEL +1 -1
  324. sphinx/builders/html/transforms.py +0 -90
  325. sphinx/ext/apidoc.py +0 -721
  326. sphinx/util/exceptions.py +0 -74
  327. sphinx-8.1.3.dist-info/RECORD +0 -598
  328. {sphinx-8.1.3.dist-info → sphinx-8.2.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,356 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import fnmatch
5
+ import locale
6
+ import re
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING
10
+
11
+ import sphinx.locale
12
+ from sphinx import __display_version__
13
+ from sphinx.cmd.quickstart import EXTENSIONS
14
+ from sphinx.ext.apidoc._generate import create_modules_toc_file, recurse_tree
15
+ from sphinx.ext.apidoc._shared import LOGGER, ApidocOptions, _remove_old_files
16
+ from sphinx.locale import __
17
+ from sphinx.util.osutil import ensuredir
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Sequence
21
+ from typing import Any
22
+
23
+
24
+ def get_parser() -> argparse.ArgumentParser:
25
+ parser = argparse.ArgumentParser(
26
+ usage='%(prog)s [OPTIONS] -o <OUTPUT_PATH> <MODULE_PATH> [EXCLUDE_PATTERN, ...]',
27
+ epilog=__('For more information, visit <https://www.sphinx-doc.org/>.'),
28
+ description=__("""
29
+ Look recursively in <MODULE_PATH> for Python modules and packages and create
30
+ one reST file with automodule directives per package in the <OUTPUT_PATH>.
31
+
32
+ The <EXCLUDE_PATTERN>s can be file and/or directory patterns that will be
33
+ excluded from generation.
34
+
35
+ Note: By default this script will not overwrite already created files."""),
36
+ )
37
+
38
+ parser.add_argument(
39
+ '--version',
40
+ action='version',
41
+ dest='show_version',
42
+ version=f'%(prog)s {__display_version__}',
43
+ )
44
+
45
+ parser.add_argument('module_path', help=__('path to module to document'))
46
+ parser.add_argument(
47
+ 'exclude_pattern',
48
+ nargs='*',
49
+ help=__(
50
+ 'fnmatch-style file and/or directory patterns to exclude from generation'
51
+ ),
52
+ )
53
+
54
+ parser.add_argument(
55
+ '-o',
56
+ '--output-dir',
57
+ action='store',
58
+ dest='dest_dir',
59
+ required=True,
60
+ help=__('directory to place all output'),
61
+ )
62
+ parser.add_argument(
63
+ '-q',
64
+ action='store_true',
65
+ dest='quiet',
66
+ help=__('no output on stdout, just warnings on stderr'),
67
+ )
68
+ parser.add_argument(
69
+ '-d',
70
+ '--maxdepth',
71
+ action='store',
72
+ dest='max_depth',
73
+ type=int,
74
+ default=4,
75
+ help=__('maximum depth of submodules to show in the TOC (default: 4)'),
76
+ )
77
+ parser.add_argument(
78
+ '-f',
79
+ '--force',
80
+ action='store_true',
81
+ dest='force',
82
+ help=__('overwrite existing files'),
83
+ )
84
+ parser.add_argument(
85
+ '-l',
86
+ '--follow-links',
87
+ action='store_true',
88
+ dest='follow_links',
89
+ default=False,
90
+ help=__(
91
+ 'follow symbolic links. Powerful when combined with collective.recipe.omelette.'
92
+ ),
93
+ )
94
+ parser.add_argument(
95
+ '-n',
96
+ '--dry-run',
97
+ action='store_true',
98
+ dest='dry_run',
99
+ help=__('run the script without creating files'),
100
+ )
101
+ parser.add_argument(
102
+ '-e',
103
+ '--separate',
104
+ action='store_true',
105
+ dest='separate_modules',
106
+ help=__('put documentation for each module on its own page'),
107
+ )
108
+ parser.add_argument(
109
+ '-P',
110
+ '--private',
111
+ action='store_true',
112
+ dest='include_private',
113
+ help=__('include "_private" modules'),
114
+ )
115
+ parser.add_argument(
116
+ '--tocfile',
117
+ action='store',
118
+ dest='toc_file',
119
+ default='modules',
120
+ help=__('filename of table of contents (default: modules)'),
121
+ )
122
+ parser.add_argument(
123
+ '-T',
124
+ '--no-toc',
125
+ action='store_false',
126
+ dest='toc_file',
127
+ help=__("don't create a table of contents file"),
128
+ )
129
+ parser.add_argument(
130
+ '-E',
131
+ '--no-headings',
132
+ action='store_true',
133
+ dest='no_headings',
134
+ help=__(
135
+ "don't create headings for the module/package "
136
+ 'packages (e.g. when the docstrings already '
137
+ 'contain them)'
138
+ ),
139
+ )
140
+ parser.add_argument(
141
+ '-M',
142
+ '--module-first',
143
+ action='store_true',
144
+ dest='module_first',
145
+ help=__('put module documentation before submodule documentation'),
146
+ )
147
+ parser.add_argument(
148
+ '--implicit-namespaces',
149
+ action='store_true',
150
+ dest='implicit_namespaces',
151
+ help=__(
152
+ 'interpret module paths according to PEP-0420 implicit namespaces specification'
153
+ ),
154
+ )
155
+ parser.add_argument(
156
+ '--automodule-options',
157
+ dest='automodule_options',
158
+ default='',
159
+ help=__(
160
+ 'Comma-separated list of options to pass to automodule directive '
161
+ '(or use SPHINX_APIDOC_OPTIONS).'
162
+ ),
163
+ )
164
+ parser.add_argument(
165
+ '-s',
166
+ '--suffix',
167
+ action='store',
168
+ dest='suffix',
169
+ default='rst',
170
+ help=__('file suffix (default: rst)'),
171
+ )
172
+ exclusive_group = parser.add_mutually_exclusive_group()
173
+ exclusive_group.add_argument(
174
+ '--remove-old',
175
+ action='store_true',
176
+ dest='remove_old',
177
+ help=__(
178
+ 'Remove existing files in the output directory that were not generated'
179
+ ),
180
+ )
181
+ exclusive_group.add_argument(
182
+ '-F',
183
+ '--full',
184
+ action='store_true',
185
+ dest='full',
186
+ help=__('generate a full project with sphinx-quickstart'),
187
+ )
188
+ parser.add_argument(
189
+ '-a',
190
+ '--append-syspath',
191
+ action='store_true',
192
+ dest='append_syspath',
193
+ help=__('append module_path to sys.path, used when --full is given'),
194
+ )
195
+ parser.add_argument(
196
+ '-H',
197
+ '--doc-project',
198
+ action='store',
199
+ dest='header',
200
+ help=__('project name (default: root module name)'),
201
+ )
202
+ parser.add_argument(
203
+ '-A',
204
+ '--doc-author',
205
+ action='store',
206
+ dest='author',
207
+ help=__('project author(s), used when --full is given'),
208
+ )
209
+ parser.add_argument(
210
+ '-V',
211
+ '--doc-version',
212
+ action='store',
213
+ dest='version',
214
+ help=__('project version, used when --full is given'),
215
+ )
216
+ parser.add_argument(
217
+ '-R',
218
+ '--doc-release',
219
+ action='store',
220
+ dest='release',
221
+ help=__(
222
+ 'project release, used when --full is given, defaults to --doc-version'
223
+ ),
224
+ )
225
+
226
+ group = parser.add_argument_group(__('extension options'))
227
+ group.add_argument(
228
+ '--extensions',
229
+ metavar='EXTENSIONS',
230
+ dest='extensions',
231
+ action='append',
232
+ help=__('enable arbitrary extensions, used when --full is given'),
233
+ )
234
+ for ext in EXTENSIONS:
235
+ group.add_argument(
236
+ f'--ext-{ext}',
237
+ action='append_const',
238
+ const=f'sphinx.ext.{ext}',
239
+ dest='extensions',
240
+ help=__('enable %s extension, used when --full is given') % ext,
241
+ )
242
+
243
+ group = parser.add_argument_group(__('Project templating'))
244
+ group.add_argument(
245
+ '-t',
246
+ '--templatedir',
247
+ metavar='TEMPLATEDIR',
248
+ dest='template_dir',
249
+ help=__('template directory for template files'),
250
+ )
251
+
252
+ return parser
253
+
254
+
255
+ def main(argv: Sequence[str] = (), /) -> int:
256
+ """Run the apidoc CLI."""
257
+ locale.setlocale(locale.LC_ALL, '')
258
+ sphinx.locale.init_console()
259
+
260
+ opts = _parse_args(argv)
261
+ rootpath = opts.module_path
262
+ excludes = tuple(
263
+ re.compile(fnmatch.translate(str(Path(exclude).resolve())))
264
+ for exclude in dict.fromkeys(opts.exclude_pattern)
265
+ )
266
+
267
+ written_files, modules = recurse_tree(rootpath, excludes, opts, opts.template_dir)
268
+
269
+ if opts.full:
270
+ _full_quickstart(opts, modules=modules)
271
+ elif opts.toc_file:
272
+ written_files.append(
273
+ create_modules_toc_file(modules, opts, opts.toc_file, opts.template_dir)
274
+ )
275
+
276
+ if opts.remove_old and not opts.dry_run:
277
+ _remove_old_files(written_files, opts.dest_dir, opts.suffix)
278
+
279
+ return 0
280
+
281
+
282
+ def _parse_args(argv: Sequence[str], /) -> ApidocOptions:
283
+ parser = get_parser()
284
+ args = parser.parse_args(argv or sys.argv[1:])
285
+
286
+ # normalise options
287
+
288
+ args.module_path = root_path = Path(args.module_path).resolve()
289
+ args.dest_dir = Path(args.dest_dir)
290
+ if not root_path.is_dir():
291
+ LOGGER.error(__('%s is not a directory.'), root_path)
292
+ raise SystemExit(1)
293
+
294
+ if args.header is None:
295
+ args.header = root_path.name
296
+ args.suffix = args.suffix.removeprefix('.')
297
+
298
+ if not args.dry_run:
299
+ ensuredir(args.dest_dir)
300
+
301
+ if not args.automodule_options:
302
+ args.automodule_options = frozenset()
303
+ elif isinstance(args.automodule_options, str):
304
+ args.automodule_options = frozenset(args.automodule_options.split(','))
305
+
306
+ return ApidocOptions(**args.__dict__)
307
+
308
+
309
+ def _full_quickstart(opts: ApidocOptions, /, *, modules: list[str]) -> None:
310
+ from sphinx.cmd import quickstart as qs
311
+
312
+ modules.sort()
313
+ prev_module = ''
314
+ text = ''
315
+ for module in modules:
316
+ if module.startswith(prev_module + '.'):
317
+ continue
318
+ prev_module = module
319
+ text += f' {module}\n'
320
+ d: dict[str, Any] = {
321
+ 'path': str(opts.dest_dir),
322
+ 'sep': False,
323
+ 'dot': '_',
324
+ 'project': opts.header,
325
+ 'author': opts.author or 'Author',
326
+ 'version': opts.version or '',
327
+ 'release': opts.release or opts.version or '',
328
+ 'suffix': '.' + opts.suffix,
329
+ 'master': 'index',
330
+ 'epub': True,
331
+ 'extensions': [
332
+ 'sphinx.ext.autodoc',
333
+ 'sphinx.ext.viewcode',
334
+ 'sphinx.ext.todo',
335
+ ],
336
+ 'makefile': True,
337
+ 'batchfile': True,
338
+ 'make_mode': True,
339
+ 'mastertocmaxdepth': opts.max_depth,
340
+ 'mastertoctree': text,
341
+ 'language': 'en',
342
+ 'module_path': str(opts.module_path),
343
+ 'append_syspath': opts.append_syspath,
344
+ }
345
+ if opts.extensions:
346
+ d['extensions'].extend(opts.extensions)
347
+ if opts.quiet:
348
+ d['quiet'] = True
349
+
350
+ for ext in d['extensions'][:]:
351
+ if ',' in ext:
352
+ d['extensions'].remove(ext)
353
+ d['extensions'].extend(ext.split(','))
354
+
355
+ if not opts.dry_run:
356
+ qs.generate(d, silent=True, overwrite=opts.force, templatedir=opts.template_dir)
@@ -0,0 +1,262 @@
1
+ """Sphinx extension for auto-generating API documentation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import fnmatch
6
+ import re
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING
9
+
10
+ from sphinx._cli.util.colour import bold
11
+ from sphinx.ext.apidoc._generate import create_modules_toc_file, recurse_tree
12
+ from sphinx.ext.apidoc._shared import (
13
+ LOGGER,
14
+ ApidocDefaults,
15
+ ApidocOptions,
16
+ _remove_old_files,
17
+ )
18
+ from sphinx.locale import __
19
+
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Collection, Sequence
22
+ from typing import Any
23
+
24
+ from sphinx.application import Sphinx
25
+
26
+ _BOOL_KEYS = frozenset({
27
+ 'follow_links',
28
+ 'separate_modules',
29
+ 'include_private',
30
+ 'no_headings',
31
+ 'module_first',
32
+ 'implicit_namespaces',
33
+ })
34
+ _ALLOWED_KEYS = _BOOL_KEYS | frozenset({
35
+ 'path',
36
+ 'destination',
37
+ 'exclude_patterns',
38
+ 'automodule_options',
39
+ 'max_depth',
40
+ })
41
+
42
+
43
+ def run_apidoc(app: Sphinx) -> None:
44
+ """Run the apidoc extension."""
45
+ defaults = ApidocDefaults.from_config(app.config)
46
+ apidoc_modules: Sequence[dict[str, Any]] = app.config.apidoc_modules
47
+ srcdir: Path = app.srcdir
48
+ confdir: Path = app.confdir
49
+
50
+ LOGGER.info(bold(__('Running apidoc')))
51
+
52
+ module_options: dict[str, Any]
53
+ for i, module_options in enumerate(apidoc_modules):
54
+ _run_apidoc_module(
55
+ i,
56
+ options=module_options,
57
+ defaults=defaults,
58
+ srcdir=srcdir,
59
+ confdir=confdir,
60
+ )
61
+
62
+
63
+ def _run_apidoc_module(
64
+ i: int,
65
+ *,
66
+ options: dict[str, Any],
67
+ defaults: ApidocDefaults,
68
+ srcdir: Path,
69
+ confdir: Path,
70
+ ) -> None:
71
+ """Run apidoc for a single module."""
72
+ args = _parse_module_options(
73
+ i, options=options, defaults=defaults, srcdir=srcdir, confdir=confdir
74
+ )
75
+ if args is None:
76
+ return
77
+
78
+ exclude_patterns_compiled: list[re.Pattern[str]] = [
79
+ re.compile(fnmatch.translate(exclude)) for exclude in args.exclude_pattern
80
+ ]
81
+
82
+ written_files, modules = recurse_tree(
83
+ args.module_path, exclude_patterns_compiled, args, args.template_dir
84
+ )
85
+ if args.toc_file:
86
+ written_files.append(
87
+ create_modules_toc_file(modules, args, args.toc_file, args.template_dir)
88
+ )
89
+ if args.remove_old:
90
+ _remove_old_files(written_files, args.dest_dir, args.suffix)
91
+
92
+
93
+ def _parse_module_options(
94
+ i: int,
95
+ *,
96
+ options: dict[str, Any],
97
+ defaults: ApidocDefaults,
98
+ srcdir: Path,
99
+ confdir: Path,
100
+ ) -> ApidocOptions | None:
101
+ if not isinstance(options, dict):
102
+ LOGGER.warning(__('apidoc_modules item %i must be a dict'), i, type='apidoc')
103
+ return None
104
+
105
+ # module path should be absolute or relative to the conf directory
106
+ try:
107
+ path = Path(options['path'])
108
+ except KeyError:
109
+ LOGGER.warning(
110
+ __("apidoc_modules item %i must have a 'path' key"), i, type='apidoc'
111
+ )
112
+ return None
113
+ except TypeError:
114
+ LOGGER.warning(
115
+ __("apidoc_modules item %i 'path' must be a string"), i, type='apidoc'
116
+ )
117
+ return None
118
+ module_path = confdir / path
119
+ if not module_path.is_dir():
120
+ LOGGER.warning(
121
+ __("apidoc_modules item %i 'path' is not an existing folder: %s"),
122
+ i,
123
+ module_path,
124
+ type='apidoc',
125
+ )
126
+ return None
127
+
128
+ # destination path should be relative to the source directory
129
+ try:
130
+ destination = Path(options['destination'])
131
+ except KeyError:
132
+ LOGGER.warning(
133
+ __("apidoc_modules item %i must have a 'destination' key"),
134
+ i,
135
+ type='apidoc',
136
+ )
137
+ return None
138
+ except TypeError:
139
+ LOGGER.warning(
140
+ __("apidoc_modules item %i 'destination' must be a string"),
141
+ i,
142
+ type='apidoc',
143
+ )
144
+ return None
145
+ if destination.is_absolute():
146
+ LOGGER.warning(
147
+ __("apidoc_modules item %i 'destination' should be a relative path"),
148
+ i,
149
+ type='apidoc',
150
+ )
151
+ return None
152
+ dest_path = srcdir / destination
153
+ try:
154
+ dest_path.mkdir(parents=True, exist_ok=True)
155
+ except OSError as exc:
156
+ LOGGER.warning(
157
+ __('apidoc_modules item %i cannot create destination directory: %s'),
158
+ i,
159
+ exc.strerror,
160
+ type='apidoc',
161
+ )
162
+ return None
163
+
164
+ # exclude patterns should be absolute or relative to the conf directory
165
+ exclude_patterns: list[str] = [
166
+ str(confdir / pattern)
167
+ for pattern in _check_collection_of_strings(
168
+ i, options, key='exclude_patterns', default=defaults.exclude_patterns
169
+ )
170
+ ]
171
+
172
+ # TODO template_dir
173
+
174
+ max_depth = defaults.max_depth
175
+ if 'max_depth' in options:
176
+ if not isinstance(options['max_depth'], int):
177
+ LOGGER.warning(
178
+ __("apidoc_modules item %i '%s' must be an int"),
179
+ i,
180
+ 'max_depth',
181
+ type='apidoc',
182
+ )
183
+ else:
184
+ max_depth = options['max_depth']
185
+
186
+ bool_options: dict[str, bool] = {}
187
+ for key in sorted(_BOOL_KEYS):
188
+ if key not in options:
189
+ bool_options[key] = getattr(defaults, key)
190
+ elif not isinstance(options[key], bool):
191
+ LOGGER.warning(
192
+ __("apidoc_modules item %i '%s' must be a boolean"),
193
+ i,
194
+ key,
195
+ type='apidoc',
196
+ )
197
+ bool_options[key] = getattr(defaults, key)
198
+ else:
199
+ bool_options[key] = options[key]
200
+
201
+ # TODO per-module automodule_options
202
+ automodule_options = frozenset(
203
+ _check_collection_of_strings(
204
+ i, options, key='automodule_options', default=defaults.automodule_options
205
+ )
206
+ )
207
+
208
+ if diff := options.keys() - _ALLOWED_KEYS:
209
+ LOGGER.warning(
210
+ __('apidoc_modules item %i has unexpected keys: %s'),
211
+ i,
212
+ ', '.join(sorted(diff)),
213
+ type='apidoc',
214
+ )
215
+
216
+ return ApidocOptions(
217
+ dest_dir=dest_path,
218
+ module_path=module_path,
219
+ exclude_pattern=exclude_patterns,
220
+ automodule_options=automodule_options,
221
+ max_depth=max_depth,
222
+ quiet=True,
223
+ follow_links=bool_options['follow_links'],
224
+ separate_modules=bool_options['separate_modules'],
225
+ include_private=bool_options['include_private'],
226
+ no_headings=bool_options['no_headings'],
227
+ module_first=bool_options['module_first'],
228
+ implicit_namespaces=bool_options['implicit_namespaces'],
229
+ )
230
+
231
+
232
+ def _check_collection_of_strings(
233
+ index: int,
234
+ options: dict[str, Any],
235
+ *,
236
+ key: str,
237
+ default: Collection[str],
238
+ ) -> Collection[str]:
239
+ """Check that a key's value is a collection of strings in the options.
240
+
241
+ :returns: The value of the key, or None if invalid.
242
+ """
243
+ if key not in options:
244
+ return default
245
+ if not isinstance(options[key], list | tuple | set | frozenset):
246
+ LOGGER.warning(
247
+ __("apidoc_modules item %i '%s' must be a sequence"),
248
+ index,
249
+ key,
250
+ type='apidoc',
251
+ )
252
+ return default
253
+ for item in options[key]:
254
+ if not isinstance(item, str):
255
+ LOGGER.warning(
256
+ __("apidoc_modules item %i '%s' must contain strings"),
257
+ index,
258
+ key,
259
+ type='apidoc',
260
+ )
261
+ return default
262
+ return options[key]