Sphinx 7.1.2__py3-none-any.whl → 7.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 (313) hide show
  1. sphinx/__init__.py +6 -6
  2. sphinx/__main__.py +3 -1
  3. sphinx/addnodes.py +35 -22
  4. sphinx/application.py +40 -38
  5. sphinx/builders/__init__.py +16 -12
  6. sphinx/builders/_epub_base.py +15 -11
  7. sphinx/builders/changes.py +6 -4
  8. sphinx/builders/dirhtml.py +4 -2
  9. sphinx/builders/dummy.py +6 -4
  10. sphinx/builders/epub3.py +16 -8
  11. sphinx/builders/gettext.py +40 -43
  12. sphinx/builders/html/__init__.py +166 -196
  13. sphinx/builders/html/_assets.py +116 -0
  14. sphinx/builders/html/transforms.py +4 -2
  15. sphinx/builders/latex/__init__.py +12 -7
  16. sphinx/builders/latex/theming.py +5 -2
  17. sphinx/builders/latex/transforms.py +6 -3
  18. sphinx/builders/linkcheck.py +18 -11
  19. sphinx/builders/manpage.py +6 -4
  20. sphinx/builders/singlehtml.py +16 -9
  21. sphinx/builders/texinfo.py +11 -6
  22. sphinx/builders/text.py +8 -3
  23. sphinx/builders/xml.py +9 -4
  24. sphinx/cmd/build.py +27 -14
  25. sphinx/cmd/make_mode.py +13 -4
  26. sphinx/cmd/quickstart.py +13 -4
  27. sphinx/config.py +17 -14
  28. sphinx/deprecation.py +4 -2
  29. sphinx/directives/__init__.py +44 -12
  30. sphinx/directives/code.py +5 -4
  31. sphinx/directives/other.py +92 -44
  32. sphinx/directives/patches.py +1 -1
  33. sphinx/domains/__init__.py +11 -8
  34. sphinx/domains/c.py +67 -57
  35. sphinx/domains/changeset.py +3 -2
  36. sphinx/domains/citation.py +2 -1
  37. sphinx/domains/cpp.py +136 -93
  38. sphinx/domains/index.py +9 -5
  39. sphinx/domains/javascript.py +32 -19
  40. sphinx/domains/math.py +5 -3
  41. sphinx/domains/python.py +69 -57
  42. sphinx/domains/rst.py +20 -11
  43. sphinx/domains/std.py +21 -15
  44. sphinx/environment/__init__.py +97 -65
  45. sphinx/environment/adapters/indexentries.py +13 -10
  46. sphinx/environment/adapters/toctree.py +485 -308
  47. sphinx/environment/collectors/__init__.py +3 -4
  48. sphinx/environment/collectors/asset.py +10 -4
  49. sphinx/environment/collectors/dependencies.py +7 -4
  50. sphinx/environment/collectors/metadata.py +7 -5
  51. sphinx/environment/collectors/title.py +5 -3
  52. sphinx/environment/collectors/toctree.py +13 -8
  53. sphinx/errors.py +1 -1
  54. sphinx/events.py +5 -5
  55. sphinx/ext/apidoc.py +49 -27
  56. sphinx/ext/autodoc/__init__.py +179 -161
  57. sphinx/ext/autodoc/directive.py +10 -6
  58. sphinx/ext/autodoc/importer.py +22 -13
  59. sphinx/ext/autodoc/mock.py +4 -1
  60. sphinx/ext/autodoc/preserve_defaults.py +80 -12
  61. sphinx/ext/autodoc/type_comment.py +14 -10
  62. sphinx/ext/autodoc/typehints.py +7 -3
  63. sphinx/ext/autosectionlabel.py +6 -3
  64. sphinx/ext/autosummary/__init__.py +21 -15
  65. sphinx/ext/autosummary/generate.py +176 -126
  66. sphinx/ext/coverage.py +93 -8
  67. sphinx/ext/doctest.py +28 -17
  68. sphinx/ext/duration.py +19 -17
  69. sphinx/ext/extlinks.py +11 -6
  70. sphinx/ext/githubpages.py +8 -7
  71. sphinx/ext/graphviz.py +61 -17
  72. sphinx/ext/ifconfig.py +7 -4
  73. sphinx/ext/imgconverter.py +4 -2
  74. sphinx/ext/imgmath.py +29 -23
  75. sphinx/ext/inheritance_diagram.py +41 -27
  76. sphinx/ext/intersphinx.py +45 -38
  77. sphinx/ext/linkcode.py +8 -5
  78. sphinx/ext/mathjax.py +13 -9
  79. sphinx/ext/napoleon/__init__.py +3 -3
  80. sphinx/ext/napoleon/docstring.py +40 -31
  81. sphinx/ext/todo.py +10 -7
  82. sphinx/ext/viewcode.py +46 -25
  83. sphinx/extension.py +1 -1
  84. sphinx/highlighting.py +20 -12
  85. sphinx/io.py +5 -4
  86. sphinx/jinja2glue.py +24 -19
  87. sphinx/locale/__init__.py +8 -2
  88. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  89. sphinx/locale/ar/LC_MESSAGES/sphinx.po +756 -740
  90. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  91. sphinx/locale/bg/LC_MESSAGES/sphinx.po +754 -738
  92. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  93. sphinx/locale/bn/LC_MESSAGES/sphinx.po +755 -739
  94. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  95. sphinx/locale/ca/LC_MESSAGES/sphinx.po +768 -752
  96. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  97. sphinx/locale/cak/LC_MESSAGES/sphinx.po +754 -738
  98. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/cs/LC_MESSAGES/sphinx.po +758 -742
  100. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  101. sphinx/locale/cy/LC_MESSAGES/sphinx.po +759 -743
  102. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/da/LC_MESSAGES/sphinx.po +760 -744
  104. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/de/LC_MESSAGES/sphinx.po +759 -743
  106. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  107. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +754 -738
  108. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/el/LC_MESSAGES/sphinx.po +763 -747
  110. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +754 -738
  112. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  113. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +754 -738
  114. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +768 -752
  116. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +754 -738
  118. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  119. sphinx/locale/eo/LC_MESSAGES/sphinx.po +754 -738
  120. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/es/LC_MESSAGES/sphinx.po +767 -751
  122. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +754 -738
  124. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  125. sphinx/locale/et/LC_MESSAGES/sphinx.po +762 -746
  126. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/eu/LC_MESSAGES/sphinx.po +755 -739
  128. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/fa/LC_MESSAGES/sphinx.po +766 -750
  130. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  131. sphinx/locale/fi/LC_MESSAGES/sphinx.po +754 -738
  132. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/fr/LC_MESSAGES/sphinx.po +768 -752
  134. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +754 -738
  136. sphinx/locale/gl/LC_MESSAGES/sphinx.js +60 -0
  137. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  138. sphinx/locale/gl/LC_MESSAGES/sphinx.po +3695 -0
  139. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  140. sphinx/locale/he/LC_MESSAGES/sphinx.po +755 -739
  141. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  142. sphinx/locale/hi/LC_MESSAGES/sphinx.po +763 -747
  143. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +754 -738
  145. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  146. sphinx/locale/hr/LC_MESSAGES/sphinx.po +760 -744
  147. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  148. sphinx/locale/hu/LC_MESSAGES/sphinx.po +759 -743
  149. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  150. sphinx/locale/id/LC_MESSAGES/sphinx.po +765 -749
  151. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  152. sphinx/locale/is/LC_MESSAGES/sphinx.po +760 -744
  153. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/it/LC_MESSAGES/sphinx.po +760 -744
  155. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/ja/LC_MESSAGES/sphinx.po +767 -751
  157. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  158. sphinx/locale/ka/LC_MESSAGES/sphinx.po +759 -743
  159. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/ko/LC_MESSAGES/sphinx.po +767 -751
  161. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/lt/LC_MESSAGES/sphinx.po +755 -739
  163. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/lv/LC_MESSAGES/sphinx.po +755 -739
  165. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/mk/LC_MESSAGES/sphinx.po +754 -738
  167. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +755 -739
  169. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/ne/LC_MESSAGES/sphinx.po +755 -739
  171. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/nl/LC_MESSAGES/sphinx.po +760 -744
  173. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/pl/LC_MESSAGES/sphinx.po +762 -745
  175. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/pt/LC_MESSAGES/sphinx.po +754 -738
  177. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +768 -752
  179. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  180. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +755 -739
  181. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  182. sphinx/locale/ro/LC_MESSAGES/sphinx.po +759 -743
  183. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  184. sphinx/locale/ru/LC_MESSAGES/sphinx.po +760 -744
  185. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  186. sphinx/locale/si/LC_MESSAGES/sphinx.po +754 -738
  187. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  188. sphinx/locale/sk/LC_MESSAGES/sphinx.po +765 -749
  189. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/sl/LC_MESSAGES/sphinx.po +755 -739
  191. sphinx/locale/sphinx.pot +748 -740
  192. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  193. sphinx/locale/sq/LC_MESSAGES/sphinx.po +768 -752
  194. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  195. sphinx/locale/sr/LC_MESSAGES/sphinx.po +754 -738
  196. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
  197. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.po +754 -738
  198. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
  199. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.po +754 -738
  200. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  201. sphinx/locale/sv/LC_MESSAGES/sphinx.po +755 -739
  202. sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
  203. sphinx/locale/ta/LC_MESSAGES/sphinx.po +754 -738
  204. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  205. sphinx/locale/te/LC_MESSAGES/sphinx.po +754 -738
  206. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/tr/LC_MESSAGES/sphinx.po +763 -747
  208. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  209. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +760 -749
  210. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/ur/LC_MESSAGES/sphinx.po +759 -748
  212. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/vi/LC_MESSAGES/sphinx.po +754 -738
  214. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  215. sphinx/locale/yue/LC_MESSAGES/sphinx.po +754 -738
  216. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.mo +0 -0
  217. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +768 -752
  218. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  219. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +754 -738
  220. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  221. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +767 -751
  222. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  223. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +754 -738
  224. sphinx/parsers.py +5 -4
  225. sphinx/project.py +52 -34
  226. sphinx/pycode/__init__.py +2 -1
  227. sphinx/pycode/ast.py +7 -13
  228. sphinx/pycode/parser.py +42 -38
  229. sphinx/registry.py +35 -29
  230. sphinx/roles.py +9 -4
  231. sphinx/search/__init__.py +5 -17
  232. sphinx/search/da.py +1 -1
  233. sphinx/search/de.py +1 -1
  234. sphinx/search/en.py +1 -1
  235. sphinx/search/es.py +1 -1
  236. sphinx/search/fi.py +1 -1
  237. sphinx/search/fr.py +1 -1
  238. sphinx/search/hu.py +1 -1
  239. sphinx/search/it.py +1 -1
  240. sphinx/search/ja.py +1 -1
  241. sphinx/search/nl.py +1 -1
  242. sphinx/search/no.py +1 -1
  243. sphinx/search/pt.py +1 -1
  244. sphinx/search/ro.py +1 -1
  245. sphinx/search/ru.py +1 -1
  246. sphinx/search/sv.py +1 -1
  247. sphinx/search/tr.py +1 -1
  248. sphinx/search/zh.py +1 -1
  249. sphinx/testing/fixtures.py +23 -30
  250. sphinx/testing/path.py +9 -0
  251. sphinx/testing/restructuredtext.py +13 -5
  252. sphinx/testing/util.py +20 -63
  253. sphinx/texinputs/sphinxlatexobjects.sty +15 -15
  254. sphinx/themes/agogo/static/agogo.css_t +10 -4
  255. sphinx/themes/basic/layout.html +1 -1
  256. sphinx/themes/basic/static/basic.css_t +4 -0
  257. sphinx/themes/basic/static/documentation_options.js_t +1 -2
  258. sphinx/themes/basic/static/searchtools.js +17 -9
  259. sphinx/themes/basic/static/sphinx_highlight.js +13 -3
  260. sphinx/themes/bizstyle/static/bizstyle.css_t +4 -0
  261. sphinx/themes/classic/theme.conf +1 -1
  262. sphinx/themes/epub/static/epub.css_t +6 -1
  263. sphinx/themes/haiku/theme.conf +1 -1
  264. sphinx/themes/nature/static/nature.css_t +4 -0
  265. sphinx/themes/nonav/static/nonav.css_t +6 -1
  266. sphinx/themes/pyramid/static/pyramid.css_t +4 -0
  267. sphinx/themes/scrolls/static/scrolls.css_t +4 -0
  268. sphinx/themes/scrolls/theme.conf +1 -1
  269. sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +4 -0
  270. sphinx/theming.py +9 -7
  271. sphinx/transforms/__init__.py +79 -3
  272. sphinx/transforms/compact_bullet_list.py +6 -3
  273. sphinx/transforms/i18n.py +26 -10
  274. sphinx/transforms/post_transforms/__init__.py +21 -8
  275. sphinx/transforms/post_transforms/code.py +6 -3
  276. sphinx/transforms/post_transforms/images.py +13 -9
  277. sphinx/util/__init__.py +21 -92
  278. sphinx/util/cfamily.py +7 -4
  279. sphinx/util/display.py +3 -2
  280. sphinx/util/docfields.py +7 -6
  281. sphinx/util/docstrings.py +1 -1
  282. sphinx/util/docutils.py +41 -31
  283. sphinx/util/fileutil.py +9 -6
  284. sphinx/util/i18n.py +21 -18
  285. sphinx/util/images.py +2 -1
  286. sphinx/util/index_entries.py +27 -0
  287. sphinx/util/inspect.py +83 -67
  288. sphinx/util/inventory.py +4 -2
  289. sphinx/util/logging.py +9 -6
  290. sphinx/util/matching.py +5 -2
  291. sphinx/util/math.py +6 -3
  292. sphinx/util/nodes.py +70 -31
  293. sphinx/util/osutil.py +22 -40
  294. sphinx/util/parallel.py +4 -1
  295. sphinx/util/rst.py +7 -3
  296. sphinx/util/tags.py +11 -4
  297. sphinx/util/template.py +17 -14
  298. sphinx/util/typing.py +61 -20
  299. sphinx/versioning.py +6 -4
  300. sphinx/writers/html.py +1 -1
  301. sphinx/writers/html5.py +32 -24
  302. sphinx/writers/latex.py +67 -53
  303. sphinx/writers/manpage.py +9 -5
  304. sphinx/writers/texinfo.py +11 -9
  305. sphinx/writers/text.py +14 -9
  306. sphinx/writers/xml.py +3 -2
  307. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/METADATA +7 -5
  308. sphinx-7.2.0.dist-info/RECORD +568 -0
  309. sphinx/testing/comparer.py +0 -97
  310. sphinx-7.1.2.dist-info/RECORD +0 -564
  311. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/LICENSE +0 -0
  312. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/WHEEL +0 -0
  313. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/entry_points.txt +0 -0
@@ -15,6 +15,7 @@ Example Makefile rule::
15
15
  from __future__ import annotations
16
16
 
17
17
  import argparse
18
+ import importlib
18
19
  import inspect
19
20
  import locale
20
21
  import os
@@ -23,17 +24,15 @@ import pydoc
23
24
  import re
24
25
  import sys
25
26
  from os import path
26
- from typing import TYPE_CHECKING, Any, NamedTuple, Sequence
27
+ from typing import TYPE_CHECKING, Any, NamedTuple
27
28
 
28
29
  from jinja2 import TemplateNotFound
29
30
  from jinja2.sandbox import SandboxedEnvironment
30
31
 
31
32
  import sphinx.locale
32
33
  from sphinx import __display_version__, package_dir
33
- from sphinx.application import Sphinx
34
34
  from sphinx.builders import Builder
35
35
  from sphinx.config import Config
36
- from sphinx.ext.autodoc import Documenter
37
36
  from sphinx.ext.autodoc.importer import import_module
38
37
  from sphinx.ext.autosummary import (
39
38
  ImportExceptionGroup,
@@ -44,14 +43,18 @@ from sphinx.ext.autosummary import (
44
43
  from sphinx.locale import __
45
44
  from sphinx.pycode import ModuleAnalyzer, PycodeError
46
45
  from sphinx.registry import SphinxComponentRegistry
47
- from sphinx.util import logging, rst, split_full_qualified_name
46
+ from sphinx.util import logging, rst
48
47
  from sphinx.util.inspect import getall, safe_getattr
49
48
  from sphinx.util.osutil import ensuredir
50
49
  from sphinx.util.template import SphinxTemplateLoader
51
50
 
52
51
  if TYPE_CHECKING:
52
+ from collections.abc import Sequence, Set
53
53
  from gettext import NullTranslations
54
54
 
55
+ from sphinx.application import Sphinx
56
+ from sphinx.ext.autodoc import Documenter
57
+
55
58
  logger = logging.getLogger(__name__)
56
59
 
57
60
 
@@ -79,7 +82,7 @@ class DummyApplication:
79
82
 
80
83
  class AutosummaryEntry(NamedTuple):
81
84
  name: str
82
- path: str
85
+ path: str | None
83
86
  template: str
84
87
  recursive: bool
85
88
 
@@ -107,7 +110,8 @@ def setup_documenters(app: Any) -> None:
107
110
 
108
111
  def _underline(title: str, line: str = '=') -> str:
109
112
  if '\n' in title:
110
- raise ValueError('Can only underline single lines')
113
+ msg = 'Can only underline single lines'
114
+ raise ValueError(msg)
111
115
  return title + '\n' + line * len(title)
112
116
 
113
117
 
@@ -116,7 +120,8 @@ class AutosummaryRenderer:
116
120
 
117
121
  def __init__(self, app: Sphinx) -> None:
118
122
  if isinstance(app, Builder):
119
- raise ValueError('Expected a Sphinx application object!')
123
+ msg = 'Expected a Sphinx application object!'
124
+ raise ValueError(msg)
120
125
 
121
126
  system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')]
122
127
  loader = SphinxTemplateLoader(app.srcdir, app.config.templates_path,
@@ -146,6 +151,36 @@ class AutosummaryRenderer:
146
151
  return template.render(context)
147
152
 
148
153
 
154
+ def _split_full_qualified_name(name: str) -> tuple[str | None, str]:
155
+ """Split full qualified name to a pair of modname and qualname.
156
+
157
+ A qualname is an abbreviation for "Qualified name" introduced at PEP-3155
158
+ (https://peps.python.org/pep-3155/). It is a dotted path name
159
+ from the module top-level.
160
+
161
+ A "full" qualified name means a string containing both module name and
162
+ qualified name.
163
+
164
+ .. note:: This function actually imports the module to check its existence.
165
+ Therefore you need to mock 3rd party modules if needed before
166
+ calling this function.
167
+ """
168
+ parts = name.split('.')
169
+ for i, _part in enumerate(parts, 1):
170
+ try:
171
+ modname = ".".join(parts[:i])
172
+ importlib.import_module(modname)
173
+ except ImportError:
174
+ if parts[:i - 1]:
175
+ return ".".join(parts[:i - 1]), ".".join(parts[i - 1:])
176
+ else:
177
+ return None, ".".join(parts)
178
+ except IndexError:
179
+ pass
180
+
181
+ return name, ""
182
+
183
+
149
184
  # -- Generating output ---------------------------------------------------------
150
185
 
151
186
 
@@ -230,104 +265,6 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
230
265
  qualname: str | None = None) -> str:
231
266
  doc = get_documenter(app, obj, parent)
232
267
 
233
- def skip_member(obj: Any, name: str, objtype: str) -> bool:
234
- try:
235
- return app.emit_firstresult('autodoc-skip-member', objtype, name,
236
- obj, False, {})
237
- except Exception as exc:
238
- logger.warning(__('autosummary: failed to determine %r to be documented, '
239
- 'the following exception was raised:\n%s'),
240
- name, exc, type='autosummary')
241
- return False
242
-
243
- def get_class_members(obj: Any) -> dict[str, Any]:
244
- members = sphinx.ext.autodoc.get_class_members(obj, [qualname], safe_getattr)
245
- return {name: member.object for name, member in members.items()}
246
-
247
- def get_module_members(obj: Any) -> dict[str, Any]:
248
- members = {}
249
- for name in members_of(obj, app.config):
250
- try:
251
- members[name] = safe_getattr(obj, name)
252
- except AttributeError:
253
- continue
254
- return members
255
-
256
- def get_all_members(obj: Any) -> dict[str, Any]:
257
- if doc.objtype == "module":
258
- return get_module_members(obj)
259
- elif doc.objtype == "class":
260
- return get_class_members(obj)
261
- return {}
262
-
263
- def get_members(obj: Any, types: set[str], include_public: list[str] = [],
264
- imported: bool = True) -> tuple[list[str], list[str]]:
265
- items: list[str] = []
266
- public: list[str] = []
267
-
268
- all_members = get_all_members(obj)
269
- for name, value in all_members.items():
270
- documenter = get_documenter(app, value, obj)
271
- if documenter.objtype in types:
272
- # skip imported members if expected
273
- if imported or getattr(value, '__module__', None) == obj.__name__:
274
- skipped = skip_member(value, name, documenter.objtype)
275
- if skipped is True:
276
- pass
277
- elif skipped is False:
278
- # show the member forcedly
279
- items.append(name)
280
- public.append(name)
281
- else:
282
- items.append(name)
283
- if name in include_public or not name.startswith('_'):
284
- # considers member as public
285
- public.append(name)
286
- return public, items
287
-
288
- def get_module_attrs(members: Any) -> tuple[list[str], list[str]]:
289
- """Find module attributes with docstrings."""
290
- attrs, public = [], []
291
- try:
292
- analyzer = ModuleAnalyzer.for_module(name)
293
- attr_docs = analyzer.find_attr_docs()
294
- for namespace, attr_name in attr_docs:
295
- if namespace == '' and attr_name in members:
296
- attrs.append(attr_name)
297
- if not attr_name.startswith('_'):
298
- public.append(attr_name)
299
- except PycodeError:
300
- pass # give up if ModuleAnalyzer fails to parse code
301
- return public, attrs
302
-
303
- def get_modules(
304
- obj: Any,
305
- skip: Sequence[str],
306
- public_members: Sequence[str] | None = None) -> tuple[list[str], list[str]]:
307
- items: list[str] = []
308
- public: list[str] = []
309
- for _, modname, _ispkg in pkgutil.iter_modules(obj.__path__):
310
-
311
- if modname in skip:
312
- # module was overwritten in __init__.py, so not accessible
313
- continue
314
- fullname = name + '.' + modname
315
- try:
316
- module = import_module(fullname)
317
- if module and hasattr(module, '__sphinx_mock__'):
318
- continue
319
- except ImportError:
320
- pass
321
-
322
- items.append(fullname)
323
- if public_members is not None:
324
- if modname in public_members:
325
- public.append(fullname)
326
- else:
327
- if not modname.startswith('_'):
328
- public.append(fullname)
329
- return public, items
330
-
331
268
  ns: dict[str, Any] = {}
332
269
  ns.update(context)
333
270
 
@@ -339,13 +276,13 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
339
276
  imported_members = imported_members or ('__all__' in dir(obj) and respect_module_all)
340
277
 
341
278
  ns['functions'], ns['all_functions'] = \
342
- get_members(obj, {'function'}, imported=imported_members)
279
+ _get_members(doc, app, obj, {'function'}, imported=imported_members)
343
280
  ns['classes'], ns['all_classes'] = \
344
- get_members(obj, {'class'}, imported=imported_members)
281
+ _get_members(doc, app, obj, {'class'}, imported=imported_members)
345
282
  ns['exceptions'], ns['all_exceptions'] = \
346
- get_members(obj, {'exception'}, imported=imported_members)
283
+ _get_members(doc, app, obj, {'exception'}, imported=imported_members)
347
284
  ns['attributes'], ns['all_attributes'] = \
348
- get_module_attrs(ns['members'])
285
+ _get_module_attrs(name, ns['members'])
349
286
  ispackage = hasattr(obj, '__path__')
350
287
  if ispackage and recursive:
351
288
  # Use members that are not modules as skip list, because it would then mean
@@ -365,7 +302,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
365
302
  # Otherwise, use get_modules method normally
366
303
  if respect_module_all and '__all__' in dir(obj):
367
304
  imported_modules, all_imported_modules = \
368
- get_members(obj, {'module'}, imported=True)
305
+ _get_members(doc, app, obj, {'module'}, imported=True)
369
306
  skip += all_imported_modules
370
307
  imported_modules = [name + '.' + modname for modname in imported_modules]
371
308
  all_imported_modules = \
@@ -375,7 +312,8 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
375
312
  imported_modules, all_imported_modules = [], []
376
313
  public_members = None
377
314
 
378
- modules, all_modules = get_modules(obj, skip=skip, public_members=public_members)
315
+ modules, all_modules = _get_modules(obj, skip=skip, name=name,
316
+ public_members=public_members)
379
317
  ns['modules'] = imported_modules + modules
380
318
  ns["all_modules"] = all_imported_modules + all_modules
381
319
  elif doc.objtype == 'class':
@@ -383,12 +321,12 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
383
321
  ns['inherited_members'] = \
384
322
  set(dir(obj)) - set(obj.__dict__.keys())
385
323
  ns['methods'], ns['all_methods'] = \
386
- get_members(obj, {'method'}, ['__init__'])
324
+ _get_members(doc, app, obj, {'method'}, include_public={'__init__'})
387
325
  ns['attributes'], ns['all_attributes'] = \
388
- get_members(obj, {'attribute', 'property'})
326
+ _get_members(doc, app, obj, {'attribute', 'property'})
389
327
 
390
328
  if modname is None or qualname is None:
391
- modname, qualname = split_full_qualified_name(name)
329
+ modname, qualname = _split_full_qualified_name(name)
392
330
 
393
331
  if doc.objtype in ('method', 'attribute', 'property'):
394
332
  ns['class'] = qualname.rsplit(".", 1)[0]
@@ -412,8 +350,118 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
412
350
  return template.render(doc.objtype, ns)
413
351
 
414
352
 
415
- def generate_autosummary_docs(sources: list[str], output_dir: str | None = None,
416
- suffix: str = '.rst', base_path: str | None = None,
353
+ def _skip_member(app: Sphinx, obj: Any, name: str, objtype: str) -> bool:
354
+ try:
355
+ return app.emit_firstresult('autodoc-skip-member', objtype, name,
356
+ obj, False, {})
357
+ except Exception as exc:
358
+ logger.warning(__('autosummary: failed to determine %r to be documented, '
359
+ 'the following exception was raised:\n%s'),
360
+ name, exc, type='autosummary')
361
+ return False
362
+
363
+
364
+ def _get_class_members(obj: Any) -> dict[str, Any]:
365
+ members = sphinx.ext.autodoc.get_class_members(obj, None, safe_getattr)
366
+ return {name: member.object for name, member in members.items()}
367
+
368
+
369
+ def _get_module_members(app: Sphinx, obj: Any) -> dict[str, Any]:
370
+ members = {}
371
+ for name in members_of(obj, app.config):
372
+ try:
373
+ members[name] = safe_getattr(obj, name)
374
+ except AttributeError:
375
+ continue
376
+ return members
377
+
378
+
379
+ def _get_all_members(doc: type[Documenter], app: Sphinx, obj: Any) -> dict[str, Any]:
380
+ if doc.objtype == 'module':
381
+ return _get_module_members(app, obj)
382
+ elif doc.objtype == 'class':
383
+ return _get_class_members(obj)
384
+ return {}
385
+
386
+
387
+ def _get_members(doc: type[Documenter], app: Sphinx, obj: Any, types: set[str], *,
388
+ include_public: Set[str] = frozenset(),
389
+ imported: bool = True) -> tuple[list[str], list[str]]:
390
+ items: list[str] = []
391
+ public: list[str] = []
392
+
393
+ all_members = _get_all_members(doc, app, obj)
394
+ for name, value in all_members.items():
395
+ documenter = get_documenter(app, value, obj)
396
+ if documenter.objtype in types:
397
+ # skip imported members if expected
398
+ if imported or getattr(value, '__module__', None) == obj.__name__:
399
+ skipped = _skip_member(app, value, name, documenter.objtype)
400
+ if skipped is True:
401
+ pass
402
+ elif skipped is False:
403
+ # show the member forcedly
404
+ items.append(name)
405
+ public.append(name)
406
+ else:
407
+ items.append(name)
408
+ if name in include_public or not name.startswith('_'):
409
+ # considers member as public
410
+ public.append(name)
411
+ return public, items
412
+
413
+
414
+ def _get_module_attrs(name: str, members: Any) -> tuple[list[str], list[str]]:
415
+ """Find module attributes with docstrings."""
416
+ attrs, public = [], []
417
+ try:
418
+ analyzer = ModuleAnalyzer.for_module(name)
419
+ attr_docs = analyzer.find_attr_docs()
420
+ for namespace, attr_name in attr_docs:
421
+ if namespace == '' and attr_name in members:
422
+ attrs.append(attr_name)
423
+ if not attr_name.startswith('_'):
424
+ public.append(attr_name)
425
+ except PycodeError:
426
+ pass # give up if ModuleAnalyzer fails to parse code
427
+ return public, attrs
428
+
429
+
430
+ def _get_modules(
431
+ obj: Any,
432
+ *,
433
+ skip: Sequence[str],
434
+ name: str,
435
+ public_members: Sequence[str] | None = None) -> tuple[list[str], list[str]]:
436
+ items: list[str] = []
437
+ public: list[str] = []
438
+ for _, modname, _ispkg in pkgutil.iter_modules(obj.__path__):
439
+
440
+ if modname in skip:
441
+ # module was overwritten in __init__.py, so not accessible
442
+ continue
443
+ fullname = name + '.' + modname
444
+ try:
445
+ module = import_module(fullname)
446
+ if module and hasattr(module, '__sphinx_mock__'):
447
+ continue
448
+ except ImportError:
449
+ pass
450
+
451
+ items.append(fullname)
452
+ if public_members is not None:
453
+ if modname in public_members:
454
+ public.append(fullname)
455
+ else:
456
+ if not modname.startswith('_'):
457
+ public.append(fullname)
458
+ return public, items
459
+
460
+
461
+ def generate_autosummary_docs(sources: list[str],
462
+ output_dir: str | os.PathLike[str] | None = None,
463
+ suffix: str = '.rst',
464
+ base_path: str | os.PathLike[str] | None = None,
417
465
  imported_members: bool = False, app: Any = None,
418
466
  overwrite: bool = True, encoding: str = 'utf-8') -> None:
419
467
  showed_sources = sorted(sources)
@@ -532,10 +580,10 @@ def find_autosummary_in_docstring(
532
580
  pass
533
581
  except ImportExceptionGroup as exc:
534
582
  errors = '\n'.join({f"* {type(e).__name__}: {e}" for e in exc.exceptions})
535
- print(f'Failed to import {name}.\nPossible hints:\n{errors}')
583
+ logger.warning(f'Failed to import {name}.\nPossible hints:\n{errors}') # NoQA: G004
536
584
  except SystemExit:
537
- print("Failed to import '%s'; the module executes module level "
538
- "statement and it might call sys.exit()." % name)
585
+ logger.warning("Failed to import '%s'; the module executes module level "
586
+ 'statement and it might call sys.exit().', name)
539
587
  return []
540
588
 
541
589
 
@@ -566,7 +614,7 @@ def find_autosummary_in_lines(
566
614
 
567
615
  recursive = False
568
616
  toctree: str | None = None
569
- template = None
617
+ template = ''
570
618
  current_module = module
571
619
  in_autosummary = False
572
620
  base_indent = ""
@@ -616,7 +664,7 @@ def find_autosummary_in_lines(
616
664
  base_indent = m.group(1)
617
665
  recursive = False
618
666
  toctree = None
619
- template = None
667
+ template = ''
620
668
  continue
621
669
 
622
670
  m = automodule_re.search(line)
@@ -681,18 +729,20 @@ The format of the autosummary directive is documented in the
681
729
  return parser
682
730
 
683
731
 
684
- def main(argv: list[str] = sys.argv[1:]) -> None:
732
+ def main(argv: Sequence[str] = (), /) -> None:
685
733
  locale.setlocale(locale.LC_ALL, '')
686
734
  sphinx.locale.init_console()
687
735
 
688
736
  app = DummyApplication(sphinx.locale.get_translator())
689
- logging.setup(app, sys.stdout, sys.stderr) # type: ignore
737
+ logging.setup(app, sys.stdout, sys.stderr) # type: ignore[arg-type]
690
738
  setup_documenters(app)
691
- args = get_parser().parse_args(argv)
739
+ args = get_parser().parse_args(argv or sys.argv[1:])
692
740
 
693
741
  if args.templates:
694
742
  app.config.templates_path.append(path.abspath(args.templates))
695
- app.config.autosummary_ignore_module_all = not args.respect_module_all # type: ignore
743
+ app.config.autosummary_ignore_module_all = ( # type: ignore[attr-defined]
744
+ not args.respect_module_all
745
+ )
696
746
 
697
747
  generate_autosummary_docs(args.source_file, args.output_dir,
698
748
  '.' + args.suffix,
@@ -701,4 +751,4 @@ def main(argv: list[str] = sys.argv[1:]) -> None:
701
751
 
702
752
 
703
753
  if __name__ == '__main__':
704
- main()
754
+ main(sys.argv[1:])
sphinx/ext/coverage.py CHANGED
@@ -10,25 +10,30 @@ import glob
10
10
  import inspect
11
11
  import pickle
12
12
  import re
13
+ import sys
13
14
  from importlib import import_module
14
15
  from os import path
15
- from typing import IO, Any
16
+ from typing import IO, TYPE_CHECKING, Any, TextIO
16
17
 
17
18
  import sphinx
18
- from sphinx.application import Sphinx
19
19
  from sphinx.builders import Builder
20
20
  from sphinx.locale import __
21
21
  from sphinx.util import logging
22
- from sphinx.util.console import red # type: ignore
22
+ from sphinx.util.console import red # type: ignore[attr-defined]
23
23
  from sphinx.util.inspect import safe_getattr
24
24
 
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Iterator
27
+
28
+ from sphinx.application import Sphinx
29
+
25
30
  logger = logging.getLogger(__name__)
26
31
 
27
32
 
28
33
  # utility
29
34
  def write_header(f: IO[str], text: str, char: str = '-') -> None:
30
35
  f.write(text + '\n')
31
- f.write(char * len(text) + '\n')
36
+ f.write(char * len(text) + '\n\n')
32
37
 
33
38
 
34
39
  def compile_regex_list(name: str, exps: str) -> list[re.Pattern[str]]:
@@ -41,6 +46,25 @@ def compile_regex_list(name: str, exps: str) -> list[re.Pattern[str]]:
41
46
  return lst
42
47
 
43
48
 
49
+ def _write_table(table: list[list[str]]) -> Iterator[str]:
50
+ sizes = [max(len(x[column]) for x in table) + 1 for column in range(len(table[0]))]
51
+
52
+ yield _add_line(sizes, '-')
53
+ yield from _add_row(sizes, table[0], '=')
54
+
55
+ for row in table[1:]:
56
+ yield from _add_row(sizes, row, '-')
57
+
58
+
59
+ def _add_line(sizes: list[int], separator: str) -> str:
60
+ return '+' + ''.join((separator * (size + 1)) + '+' for size in sizes)
61
+
62
+
63
+ def _add_row(col_widths: list[int], columns: list[str], separator: str) -> Iterator[str]:
64
+ yield ''.join(f'| {column: <{col_widths[i]}}' for i, column in enumerate(columns)) + '|'
65
+ yield _add_line(col_widths, separator)
66
+
67
+
44
68
  class CoverageBuilder(Builder):
45
69
  """
46
70
  Evaluates coverage of code in the documentation.
@@ -80,6 +104,8 @@ class CoverageBuilder(Builder):
80
104
 
81
105
  def write(self, *ignored: Any) -> None:
82
106
  self.py_undoc: dict[str, dict[str, Any]] = {}
107
+ self.py_undocumented: dict[str, set[str]] = {}
108
+ self.py_documented: dict[str, set[str]] = {}
83
109
  self.build_py_coverage()
84
110
  self.write_py_coverage()
85
111
 
@@ -88,8 +114,9 @@ class CoverageBuilder(Builder):
88
114
  self.write_c_coverage()
89
115
 
90
116
  def build_c_coverage(self) -> None:
91
- # Fetch all the info from the header files
92
- c_objects = self.env.domaindata['c']['objects']
117
+ c_objects = {}
118
+ for obj in self.env.domains['c'].get_objects():
119
+ c_objects[obj[2]] = obj[1]
93
120
  for filename in self.c_sourcefiles:
94
121
  undoc: set[tuple[str, str]] = set()
95
122
  with open(filename, encoding="utf-8") as f:
@@ -98,7 +125,11 @@ class CoverageBuilder(Builder):
98
125
  match = regex.match(line)
99
126
  if match:
100
127
  name = match.groups()[0]
101
- if name not in c_objects:
128
+ if key not in c_objects:
129
+ undoc.add((key, name))
130
+ continue
131
+
132
+ if name not in c_objects[key]:
102
133
  for exp in self.c_ignorexps.get(key, []):
103
134
  if exp.match(name):
104
135
  break
@@ -157,6 +188,9 @@ class CoverageBuilder(Builder):
157
188
  self.py_undoc[mod_name] = {'error': err}
158
189
  continue
159
190
 
191
+ documented_objects: set[str] = set()
192
+ undocumented_objects: set[str] = set()
193
+
160
194
  funcs = []
161
195
  classes: dict[str, list[str]] = {}
162
196
 
@@ -185,6 +219,9 @@ class CoverageBuilder(Builder):
185
219
  if skip_undoc and not obj.__doc__:
186
220
  continue
187
221
  funcs.append(name)
222
+ undocumented_objects.add(full_name)
223
+ else:
224
+ documented_objects.add(full_name)
188
225
  elif inspect.isclass(obj):
189
226
  for exp in self.cls_ignorexps:
190
227
  if exp.match(name):
@@ -220,11 +257,47 @@ class CoverageBuilder(Builder):
220
257
  continue
221
258
  if full_attr_name not in objects:
222
259
  attrs.append(attr_name)
260
+ undocumented_objects.add(full_attr_name)
261
+ else:
262
+ documented_objects.add(full_attr_name)
263
+
223
264
  if attrs:
224
265
  # some attributes are undocumented
225
266
  classes[name] = attrs
226
267
 
227
268
  self.py_undoc[mod_name] = {'funcs': funcs, 'classes': classes}
269
+ self.py_undocumented[mod_name] = undocumented_objects
270
+ self.py_documented[mod_name] = documented_objects
271
+
272
+ def _write_py_statistics(self, op: TextIO) -> None:
273
+ """ Outputs the table of ``op``."""
274
+ all_modules = set(self.py_documented.keys()).union(
275
+ set(self.py_undocumented.keys()))
276
+ all_objects: set[str] = set()
277
+ all_documented_objects: set[str] = set()
278
+ for module in all_modules:
279
+ all_module_objects = self.py_documented[module].union(self.py_undocumented[module])
280
+ all_objects = all_objects.union(all_module_objects)
281
+ all_documented_objects = all_documented_objects.union(self.py_documented[module])
282
+
283
+ # prepare tabular
284
+ table = [['Module', 'Coverage', 'Undocumented']]
285
+ for module in all_modules:
286
+ module_objects = self.py_documented[module].union(self.py_undocumented[module])
287
+ if len(module_objects):
288
+ value = 100.0 * len(self.py_documented[module]) / len(module_objects)
289
+ else:
290
+ value = 100.0
291
+
292
+ table.append([module, '%.2f%%' % value, '%d' % len(self.py_undocumented[module])])
293
+ table.append([
294
+ 'TOTAL',
295
+ f'{100 * len(all_documented_objects) / len(all_objects):.2f}%',
296
+ f'{len(all_objects) - len(all_documented_objects)}',
297
+ ])
298
+
299
+ for line in _write_table(table):
300
+ op.write(f'{line}\n')
228
301
 
229
302
  def write_py_coverage(self) -> None:
230
303
  output_file = path.join(self.outdir, 'python.txt')
@@ -232,6 +305,15 @@ class CoverageBuilder(Builder):
232
305
  with open(output_file, 'w', encoding="utf-8") as op:
233
306
  if self.config.coverage_write_headline:
234
307
  write_header(op, 'Undocumented Python objects', '=')
308
+
309
+ if self.config.coverage_statistics_to_stdout:
310
+ self._write_py_statistics(sys.stdout)
311
+
312
+ if self.config.coverage_statistics_to_report:
313
+ write_header(op, 'Statistics')
314
+ self._write_py_statistics(op)
315
+ op.write('\n')
316
+
235
317
  keys = sorted(self.py_undoc.keys())
236
318
  for name in keys:
237
319
  undoc = self.py_undoc[name]
@@ -297,7 +379,8 @@ class CoverageBuilder(Builder):
297
379
  # dump the coverage data to a pickle file too
298
380
  picklepath = path.join(self.outdir, 'undoc.pickle')
299
381
  with open(picklepath, 'wb') as dumpfile:
300
- pickle.dump((self.py_undoc, self.c_undoc), dumpfile)
382
+ pickle.dump((self.py_undoc, self.c_undoc,
383
+ self.py_undocumented, self.py_documented), dumpfile)
301
384
 
302
385
 
303
386
  def setup(app: Sphinx) -> dict[str, Any]:
@@ -310,6 +393,8 @@ def setup(app: Sphinx) -> dict[str, Any]:
310
393
  app.add_config_value('coverage_c_regexes', {}, False)
311
394
  app.add_config_value('coverage_ignore_c_items', {}, False)
312
395
  app.add_config_value('coverage_write_headline', True, False)
396
+ app.add_config_value('coverage_statistics_to_report', True, False, (bool,))
397
+ app.add_config_value('coverage_statistics_to_stdout', True, False, (bool,))
313
398
  app.add_config_value('coverage_skip_undoc_in_source', False, False)
314
399
  app.add_config_value('coverage_show_missing_items', False, False)
315
400
  return {'version': sphinx.__display_version__, 'parallel_read_safe': True}