Sphinx 7.1.1__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 +21 -13
  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.1.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.1.dist-info/RECORD +0 -564
  311. {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/LICENSE +0 -0
  312. {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/WHEEL +0 -0
  313. {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/entry_points.txt +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any, Iterable, TypeVar, cast
5
+ from typing import TYPE_CHECKING, Any, TypeVar
6
6
 
7
7
  from docutils import nodes
8
8
  from docutils.nodes import Element, Node
@@ -11,330 +11,507 @@ from sphinx import addnodes
11
11
  from sphinx.locale import __
12
12
  from sphinx.util import logging, url_re
13
13
  from sphinx.util.matching import Matcher
14
- from sphinx.util.nodes import clean_astext, process_only_nodes
14
+ from sphinx.util.nodes import _only_node_keep_children, clean_astext
15
15
 
16
16
  if TYPE_CHECKING:
17
+ from collections.abc import Iterable, Set
18
+
17
19
  from sphinx.builders import Builder
18
20
  from sphinx.environment import BuildEnvironment
21
+ from sphinx.util.tags import Tags
19
22
 
20
23
 
21
24
  logger = logging.getLogger(__name__)
22
25
 
23
26
 
27
+ def note_toctree(env: BuildEnvironment, docname: str, toctreenode: addnodes.toctree) -> None:
28
+ """Note a TOC tree directive in a document and gather information about
29
+ file relations from it.
30
+ """
31
+ if toctreenode['glob']:
32
+ env.glob_toctrees.add(docname)
33
+ if toctreenode.get('numbered'):
34
+ env.numbered_toctrees.add(docname)
35
+ include_files = toctreenode['includefiles']
36
+ for include_file in include_files:
37
+ # note that if the included file is rebuilt, this one must be
38
+ # too (since the TOC of the included file could have changed)
39
+ env.files_to_rebuild.setdefault(include_file, set()).add(docname)
40
+ env.toctree_includes.setdefault(docname, []).extend(include_files)
41
+
42
+
43
+ def document_toc(env: BuildEnvironment, docname: str, tags: Tags) -> Node:
44
+ """Get the (local) table of contents for a document.
45
+
46
+ Note that this is only the sections within the document.
47
+ For a ToC tree that shows the document's place in the
48
+ ToC structure, use `get_toctree_for`.
49
+ """
50
+
51
+ tocdepth = env.metadata[docname].get('tocdepth', 0)
52
+ try:
53
+ toc = _toctree_copy(env.tocs[docname], 2, tocdepth, False, tags)
54
+ except KeyError:
55
+ # the document does not exist any more:
56
+ # return a dummy node that renders to nothing
57
+ return nodes.paragraph()
58
+
59
+ for node in toc.findall(nodes.reference):
60
+ node['refuri'] = node['anchorname'] or '#'
61
+ return toc
62
+
63
+
64
+ def global_toctree_for_doc(
65
+ env: BuildEnvironment,
66
+ docname: str,
67
+ builder: Builder,
68
+ collapse: bool = False,
69
+ includehidden: bool = True,
70
+ maxdepth: int = 0,
71
+ titles_only: bool = False,
72
+ ) -> Element | None:
73
+ """Get the global ToC tree at a given document.
74
+
75
+ This gives the global ToC, with all ancestors and their siblings.
76
+ """
77
+
78
+ toctrees: list[Element] = []
79
+ for toctree_node in env.master_doctree.findall(addnodes.toctree):
80
+ if toctree := _resolve_toctree(
81
+ env,
82
+ docname,
83
+ builder,
84
+ toctree_node,
85
+ prune=True,
86
+ maxdepth=int(maxdepth),
87
+ titles_only=titles_only,
88
+ collapse=collapse,
89
+ includehidden=includehidden,
90
+ ):
91
+ toctrees.append(toctree)
92
+ if not toctrees:
93
+ return None
94
+ result = toctrees[0]
95
+ for toctree in toctrees[1:]:
96
+ result.extend(toctree.children)
97
+ return result
98
+
99
+
100
+ def _resolve_toctree(
101
+ env: BuildEnvironment, docname: str, builder: Builder, toctree: addnodes.toctree, *,
102
+ prune: bool = True, maxdepth: int = 0, titles_only: bool = False,
103
+ collapse: bool = False, includehidden: bool = False,
104
+ ) -> Element | None:
105
+ """Resolve a *toctree* node into individual bullet lists with titles
106
+ as items, returning None (if no containing titles are found) or
107
+ a new node.
108
+
109
+ If *prune* is True, the tree is pruned to *maxdepth*, or if that is 0,
110
+ to the value of the *maxdepth* option on the *toctree* node.
111
+ If *titles_only* is True, only toplevel document titles will be in the
112
+ resulting tree.
113
+ If *collapse* is True, all branches not containing docname will
114
+ be collapsed.
115
+ """
116
+
117
+ if toctree.get('hidden', False) and not includehidden:
118
+ return None
119
+
120
+ # For reading the following two helper function, it is useful to keep
121
+ # in mind the node structure of a toctree (using HTML-like node names
122
+ # for brevity):
123
+ #
124
+ # <ul>
125
+ # <li>
126
+ # <p><a></p>
127
+ # <p><a></p>
128
+ # ...
129
+ # <ul>
130
+ # ...
131
+ # </ul>
132
+ # </li>
133
+ # </ul>
134
+ #
135
+ # The transformation is made in two passes in order to avoid
136
+ # interactions between marking and pruning the tree (see bug #1046).
137
+
138
+ toctree_ancestors = _get_toctree_ancestors(env.toctree_includes, docname)
139
+ included = Matcher(env.config.include_patterns)
140
+ excluded = Matcher(env.config.exclude_patterns)
141
+
142
+ maxdepth = maxdepth or toctree.get('maxdepth', -1)
143
+ if not titles_only and toctree.get('titlesonly', False):
144
+ titles_only = True
145
+ if not includehidden and toctree.get('includehidden', False):
146
+ includehidden = True
147
+
148
+ tocentries = _entries_from_toctree(
149
+ env,
150
+ prune,
151
+ titles_only,
152
+ collapse,
153
+ includehidden,
154
+ builder.tags,
155
+ toctree_ancestors,
156
+ included,
157
+ excluded,
158
+ toctree,
159
+ [],
160
+ )
161
+ if not tocentries:
162
+ return None
163
+
164
+ newnode = addnodes.compact_paragraph('', '')
165
+ if caption := toctree.attributes.get('caption'):
166
+ caption_node = nodes.title(caption, '', *[nodes.Text(caption)])
167
+ caption_node.line = toctree.line
168
+ caption_node.source = toctree.source
169
+ caption_node.rawsource = toctree['rawcaption']
170
+ if hasattr(toctree, 'uid'):
171
+ # move uid to caption_node to translate it
172
+ caption_node.uid = toctree.uid # type: ignore[attr-defined]
173
+ del toctree.uid
174
+ newnode.append(caption_node)
175
+ newnode.extend(tocentries)
176
+ newnode['toctree'] = True
177
+
178
+ # prune the tree to maxdepth, also set toc depth and current classes
179
+ _toctree_add_classes(newnode, 1, docname)
180
+ newnode = _toctree_copy(newnode, 1, maxdepth if prune else 0, collapse, builder.tags)
181
+
182
+ if isinstance(newnode[-1], nodes.Element) and len(newnode[-1]) == 0: # No titles found
183
+ return None
184
+
185
+ # set the target paths in the toctrees (they are not known at TOC
186
+ # generation time)
187
+ for refnode in newnode.findall(nodes.reference):
188
+ if url_re.match(refnode['refuri']) is None:
189
+ rel_uri = builder.get_relative_uri(docname, refnode['refuri'])
190
+ refnode['refuri'] = rel_uri + refnode['anchorname']
191
+ return newnode
192
+
193
+
194
+ def _entries_from_toctree(
195
+ env: BuildEnvironment,
196
+ prune: bool,
197
+ titles_only: bool,
198
+ collapse: bool,
199
+ includehidden: bool,
200
+ tags: Tags,
201
+ toctree_ancestors: Set[str],
202
+ included: Matcher,
203
+ excluded: Matcher,
204
+ toctreenode: addnodes.toctree,
205
+ parents: list[str],
206
+ subtree: bool = False,
207
+ ) -> list[Element]:
208
+ """Return TOC entries for a toctree node."""
209
+ entries: list[Element] = []
210
+ for (title, ref) in toctreenode['entries']:
211
+ try:
212
+ toc, refdoc = _toctree_entry(
213
+ title, ref, env, prune, collapse, tags, toctree_ancestors,
214
+ included, excluded, toctreenode, parents,
215
+ )
216
+ except LookupError:
217
+ continue
218
+
219
+ # children of toc are:
220
+ # - list_item + compact_paragraph + (reference and subtoc)
221
+ # - only + subtoc
222
+ # - toctree
223
+ children: Iterable[nodes.Element] = toc.children # type: ignore[assignment]
224
+
225
+ # if titles_only is given, only keep the main title and
226
+ # sub-toctrees
227
+ if titles_only:
228
+ # delete everything but the toplevel title(s)
229
+ # and toctrees
230
+ for top_level in children:
231
+ # nodes with length 1 don't have any children anyway
232
+ if len(top_level) > 1:
233
+ if subtrees := list(top_level.findall(addnodes.toctree)):
234
+ top_level[1][:] = subtrees # type: ignore[index]
235
+ else:
236
+ top_level.pop(1)
237
+ # resolve all sub-toctrees
238
+ for sub_toc_node in list(toc.findall(addnodes.toctree)):
239
+ if sub_toc_node.get('hidden', False) and not includehidden:
240
+ continue
241
+ for i, entry in enumerate(
242
+ _entries_from_toctree(
243
+ env,
244
+ prune,
245
+ titles_only,
246
+ collapse,
247
+ includehidden,
248
+ tags,
249
+ toctree_ancestors,
250
+ included,
251
+ excluded,
252
+ sub_toc_node,
253
+ [refdoc] + parents,
254
+ subtree=True,
255
+ ),
256
+ start=sub_toc_node.parent.index(sub_toc_node) + 1,
257
+ ):
258
+ sub_toc_node.parent.insert(i, entry)
259
+ sub_toc_node.parent.remove(sub_toc_node)
260
+
261
+ entries.extend(children)
262
+
263
+ if not subtree:
264
+ ret = nodes.bullet_list()
265
+ ret += entries
266
+ return [ret]
267
+
268
+ return entries
269
+
270
+
271
+ def _toctree_entry(
272
+ title: str,
273
+ ref: str,
274
+ env: BuildEnvironment,
275
+ prune: bool,
276
+ collapse: bool,
277
+ tags: Tags,
278
+ toctree_ancestors: Set[str],
279
+ included: Matcher,
280
+ excluded: Matcher,
281
+ toctreenode: addnodes.toctree,
282
+ parents: list[str],
283
+ ) -> tuple[Element, str]:
284
+ from sphinx.domains.std import StandardDomain
285
+
286
+ try:
287
+ refdoc = ''
288
+ if url_re.match(ref):
289
+ toc = _toctree_url_entry(title, ref)
290
+ elif ref == 'self':
291
+ toc = _toctree_self_entry(title, toctreenode['parent'], env.titles)
292
+ elif ref in StandardDomain._virtual_doc_names:
293
+ toc = _toctree_generated_entry(title, ref)
294
+ else:
295
+ if ref in parents:
296
+ logger.warning(__('circular toctree references '
297
+ 'detected, ignoring: %s <- %s'),
298
+ ref, ' <- '.join(parents),
299
+ location=ref, type='toc', subtype='circular')
300
+ msg = 'circular reference'
301
+ raise LookupError(msg)
302
+
303
+ toc, refdoc = _toctree_standard_entry(
304
+ title,
305
+ ref,
306
+ env.metadata[ref].get('tocdepth', 0),
307
+ env.tocs[ref],
308
+ toctree_ancestors,
309
+ prune,
310
+ collapse,
311
+ tags,
312
+ )
313
+
314
+ if not toc.children:
315
+ # empty toc means: no titles will show up in the toctree
316
+ logger.warning(__('toctree contains reference to document %r that '
317
+ "doesn't have a title: no link will be generated"),
318
+ ref, location=toctreenode)
319
+ except KeyError:
320
+ # this is raised if the included file does not exist
321
+ ref_path = env.doc2path(ref, False)
322
+ if excluded(ref_path):
323
+ message = __('toctree contains reference to excluded document %r')
324
+ elif not included(ref_path):
325
+ message = __('toctree contains reference to non-included document %r')
326
+ else:
327
+ message = __('toctree contains reference to nonexisting document %r')
328
+
329
+ logger.warning(message, ref, location=toctreenode)
330
+ raise
331
+ return toc, refdoc
332
+
333
+
334
+ def _toctree_url_entry(title: str, ref: str) -> nodes.bullet_list:
335
+ if title is None:
336
+ title = ref
337
+ reference = nodes.reference('', '', internal=False,
338
+ refuri=ref, anchorname='',
339
+ *[nodes.Text(title)])
340
+ para = addnodes.compact_paragraph('', '', reference)
341
+ item = nodes.list_item('', para)
342
+ toc = nodes.bullet_list('', item)
343
+ return toc
344
+
345
+
346
+ def _toctree_self_entry(
347
+ title: str, ref: str, titles: dict[str, nodes.title],
348
+ ) -> nodes.bullet_list:
349
+ # 'self' refers to the document from which this
350
+ # toctree originates
351
+ if not title:
352
+ title = clean_astext(titles[ref])
353
+ reference = nodes.reference('', '', internal=True,
354
+ refuri=ref,
355
+ anchorname='',
356
+ *[nodes.Text(title)])
357
+ para = addnodes.compact_paragraph('', '', reference)
358
+ item = nodes.list_item('', para)
359
+ # don't show subitems
360
+ toc = nodes.bullet_list('', item)
361
+ return toc
362
+
363
+
364
+ def _toctree_generated_entry(title: str, ref: str) -> nodes.bullet_list:
365
+ from sphinx.domains.std import StandardDomain
366
+
367
+ docname, sectionname = StandardDomain._virtual_doc_names[ref]
368
+ if not title:
369
+ title = sectionname
370
+ reference = nodes.reference('', title, internal=True,
371
+ refuri=docname, anchorname='')
372
+ para = addnodes.compact_paragraph('', '', reference)
373
+ item = nodes.list_item('', para)
374
+ # don't show subitems
375
+ toc = nodes.bullet_list('', item)
376
+ return toc
377
+
378
+
379
+ def _toctree_standard_entry(
380
+ title: str,
381
+ ref: str,
382
+ maxdepth: int,
383
+ toc: nodes.bullet_list,
384
+ toctree_ancestors: Set[str],
385
+ prune: bool,
386
+ collapse: bool,
387
+ tags: Tags,
388
+ ) -> tuple[nodes.bullet_list, str]:
389
+ refdoc = ref
390
+ if ref in toctree_ancestors and (not prune or maxdepth <= 0):
391
+ toc = toc.deepcopy()
392
+ else:
393
+ toc = _toctree_copy(toc, 2, maxdepth, collapse, tags)
394
+
395
+ if title and toc.children and len(toc.children) == 1:
396
+ child = toc.children[0]
397
+ for refnode in child.findall(nodes.reference):
398
+ if refnode['refuri'] == ref and not refnode['anchorname']:
399
+ refnode.children[:] = [nodes.Text(title)]
400
+ return toc, refdoc
401
+
402
+
403
+ def _toctree_add_classes(node: Element, depth: int, docname: str) -> None:
404
+ """Add 'toctree-l%d' and 'current' classes to the toctree."""
405
+ for subnode in node.children:
406
+ if isinstance(subnode, (addnodes.compact_paragraph, nodes.list_item)):
407
+ # for <p> and <li>, indicate the depth level and recurse
408
+ subnode['classes'].append(f'toctree-l{depth - 1}')
409
+ _toctree_add_classes(subnode, depth, docname)
410
+ elif isinstance(subnode, nodes.bullet_list):
411
+ # for <ul>, just recurse
412
+ _toctree_add_classes(subnode, depth + 1, docname)
413
+ elif isinstance(subnode, nodes.reference):
414
+ # for <a>, identify which entries point to the current
415
+ # document and therefore may not be collapsed
416
+ if subnode['refuri'] == docname:
417
+ if not subnode['anchorname']:
418
+ # give the whole branch a 'current' class
419
+ # (useful for styling it differently)
420
+ branchnode: Element = subnode
421
+ while branchnode:
422
+ branchnode['classes'].append('current')
423
+ branchnode = branchnode.parent
424
+ # mark the list_item as "on current page"
425
+ if subnode.parent.parent.get('iscurrent'):
426
+ # but only if it's not already done
427
+ return
428
+ while subnode:
429
+ subnode['iscurrent'] = True
430
+ subnode = subnode.parent
431
+
432
+
433
+ ET = TypeVar('ET', bound=Element)
434
+
435
+
436
+ def _toctree_copy(node: ET, depth: int, maxdepth: int, collapse: bool, tags: Tags) -> ET:
437
+ """Utility: Cut and deep-copy a TOC at a specified depth."""
438
+ keep_bullet_list_sub_nodes = (depth <= 1
439
+ or ((depth <= maxdepth or maxdepth <= 0)
440
+ and (not collapse or 'iscurrent' in node)))
441
+
442
+ copy = node.copy()
443
+ for subnode in node.children:
444
+ if isinstance(subnode, (addnodes.compact_paragraph, nodes.list_item)):
445
+ # for <p> and <li>, just recurse
446
+ copy.append(_toctree_copy(subnode, depth, maxdepth, collapse, tags))
447
+ elif isinstance(subnode, nodes.bullet_list):
448
+ # for <ul>, copy if the entry is top-level
449
+ # or, copy if the depth is within bounds and;
450
+ # collapsing is disabled or the sub-entry's parent is 'current'.
451
+ # The boolean is constant so is calculated outwith the loop.
452
+ if keep_bullet_list_sub_nodes:
453
+ copy.append(_toctree_copy(subnode, depth + 1, maxdepth, collapse, tags))
454
+ elif isinstance(subnode, addnodes.toctree):
455
+ # copy sub toctree nodes for later processing
456
+ copy.append(subnode.copy())
457
+ elif isinstance(subnode, addnodes.only):
458
+ # only keep children if the only node matches the tags
459
+ if _only_node_keep_children(subnode, tags):
460
+ for child in subnode.children:
461
+ copy.append(_toctree_copy(
462
+ child, depth, maxdepth, collapse, tags, # type: ignore[type-var]
463
+ ))
464
+ elif isinstance(subnode, (nodes.reference, nodes.title)):
465
+ # deep copy references and captions
466
+ sub_node_copy = subnode.copy()
467
+ sub_node_copy.children = [child.deepcopy() for child in subnode.children]
468
+ for child in sub_node_copy.children:
469
+ child.parent = sub_node_copy
470
+ copy.append(sub_node_copy)
471
+ else:
472
+ msg = f'Unexpected node type {subnode.__class__.__name__!r}!'
473
+ raise ValueError(msg)
474
+ return copy
475
+
476
+
477
+ def _get_toctree_ancestors(
478
+ toctree_includes: dict[str, list[str]], docname: str,
479
+ ) -> Set[str]:
480
+ parent: dict[str, str] = {}
481
+ for p, children in toctree_includes.items():
482
+ parent |= dict.fromkeys(children, p)
483
+ ancestors: list[str] = []
484
+ d = docname
485
+ while d in parent and d not in ancestors:
486
+ ancestors.append(d)
487
+ d = parent[d]
488
+ # use dict keys for ordered set operations
489
+ return dict.fromkeys(ancestors).keys()
490
+
491
+
24
492
  class TocTree:
25
493
  def __init__(self, env: BuildEnvironment) -> None:
26
494
  self.env = env
27
495
 
28
496
  def note(self, docname: str, toctreenode: addnodes.toctree) -> None:
29
- """Note a TOC tree directive in a document and gather information about
30
- file relations from it.
31
- """
32
- if toctreenode['glob']:
33
- self.env.glob_toctrees.add(docname)
34
- if toctreenode.get('numbered'):
35
- self.env.numbered_toctrees.add(docname)
36
- includefiles = toctreenode['includefiles']
37
- for includefile in includefiles:
38
- # note that if the included file is rebuilt, this one must be
39
- # too (since the TOC of the included file could have changed)
40
- self.env.files_to_rebuild.setdefault(includefile, set()).add(docname)
41
- self.env.toctree_includes.setdefault(docname, []).extend(includefiles)
497
+ note_toctree(self.env, docname, toctreenode)
42
498
 
43
499
  def resolve(self, docname: str, builder: Builder, toctree: addnodes.toctree,
44
500
  prune: bool = True, maxdepth: int = 0, titles_only: bool = False,
45
501
  collapse: bool = False, includehidden: bool = False) -> Element | None:
46
- """Resolve a *toctree* node into individual bullet lists with titles
47
- as items, returning None (if no containing titles are found) or
48
- a new node.
49
-
50
- If *prune* is True, the tree is pruned to *maxdepth*, or if that is 0,
51
- to the value of the *maxdepth* option on the *toctree* node.
52
- If *titles_only* is True, only toplevel document titles will be in the
53
- resulting tree.
54
- If *collapse* is True, all branches not containing docname will
55
- be collapsed.
56
- """
57
- if toctree.get('hidden', False) and not includehidden:
58
- return None
59
- generated_docnames: dict[str, tuple[str, str]] = self.env.domains['std']._virtual_doc_names.copy() # NoQA: E501
60
-
61
- # For reading the following two helper function, it is useful to keep
62
- # in mind the node structure of a toctree (using HTML-like node names
63
- # for brevity):
64
- #
65
- # <ul>
66
- # <li>
67
- # <p><a></p>
68
- # <p><a></p>
69
- # ...
70
- # <ul>
71
- # ...
72
- # </ul>
73
- # </li>
74
- # </ul>
75
- #
76
- # The transformation is made in two passes in order to avoid
77
- # interactions between marking and pruning the tree (see bug #1046).
78
-
79
- toctree_ancestors = self.get_toctree_ancestors(docname)
80
- included = Matcher(self.env.config.include_patterns)
81
- excluded = Matcher(self.env.config.exclude_patterns)
82
-
83
- def _toctree_add_classes(node: Element, depth: int) -> None:
84
- """Add 'toctree-l%d' and 'current' classes to the toctree."""
85
- for subnode in node.children:
86
- if isinstance(subnode, (addnodes.compact_paragraph,
87
- nodes.list_item)):
88
- # for <p> and <li>, indicate the depth level and recurse
89
- subnode['classes'].append(f'toctree-l{depth - 1}')
90
- _toctree_add_classes(subnode, depth)
91
- elif isinstance(subnode, nodes.bullet_list):
92
- # for <ul>, just recurse
93
- _toctree_add_classes(subnode, depth + 1)
94
- elif isinstance(subnode, nodes.reference):
95
- # for <a>, identify which entries point to the current
96
- # document and therefore may not be collapsed
97
- if subnode['refuri'] == docname:
98
- if not subnode['anchorname']:
99
- # give the whole branch a 'current' class
100
- # (useful for styling it differently)
101
- branchnode: Element = subnode
102
- while branchnode:
103
- branchnode['classes'].append('current')
104
- branchnode = branchnode.parent
105
- # mark the list_item as "on current page"
106
- if subnode.parent.parent.get('iscurrent'):
107
- # but only if it's not already done
108
- return
109
- while subnode:
110
- subnode['iscurrent'] = True
111
- subnode = subnode.parent
112
-
113
- def _entries_from_toctree(toctreenode: addnodes.toctree, parents: list[str],
114
- subtree: bool = False) -> list[Element]:
115
- """Return TOC entries for a toctree node."""
116
- refs = [(e[0], e[1]) for e in toctreenode['entries']]
117
- entries: list[Element] = []
118
- for (title, ref) in refs:
119
- try:
120
- refdoc = None
121
- if url_re.match(ref):
122
- if title is None:
123
- title = ref
124
- reference = nodes.reference('', '', internal=False,
125
- refuri=ref, anchorname='',
126
- *[nodes.Text(title)])
127
- para = addnodes.compact_paragraph('', '', reference)
128
- item = nodes.list_item('', para)
129
- toc = nodes.bullet_list('', item)
130
- elif ref == 'self':
131
- # 'self' refers to the document from which this
132
- # toctree originates
133
- ref = toctreenode['parent']
134
- if not title:
135
- title = clean_astext(self.env.titles[ref])
136
- reference = nodes.reference('', '', internal=True,
137
- refuri=ref,
138
- anchorname='',
139
- *[nodes.Text(title)])
140
- para = addnodes.compact_paragraph('', '', reference)
141
- item = nodes.list_item('', para)
142
- # don't show subitems
143
- toc = nodes.bullet_list('', item)
144
- elif ref in generated_docnames:
145
- docname, sectionname = generated_docnames[ref]
146
- if not title:
147
- title = sectionname
148
- reference = nodes.reference('', title, internal=True,
149
- refuri=docname, anchorname='')
150
- para = addnodes.compact_paragraph('', '', reference)
151
- item = nodes.list_item('', para)
152
- # don't show subitems
153
- toc = nodes.bullet_list('', item)
154
- else:
155
- if ref in parents:
156
- logger.warning(__('circular toctree references '
157
- 'detected, ignoring: %s <- %s'),
158
- ref, ' <- '.join(parents),
159
- location=ref, type='toc', subtype='circular')
160
- continue
161
- refdoc = ref
162
- maxdepth = self.env.metadata[ref].get('tocdepth', 0)
163
- toc = self.env.tocs[ref]
164
- if ref not in toctree_ancestors or (prune and maxdepth > 0):
165
- toc = self._toctree_copy(toc, 2, maxdepth, collapse)
166
- else:
167
- toc = toc.deepcopy()
168
- process_only_nodes(toc, builder.tags)
169
- if title and toc.children and len(toc.children) == 1:
170
- child = toc.children[0]
171
- for refnode in child.findall(nodes.reference):
172
- if refnode['refuri'] == ref and \
173
- not refnode['anchorname']:
174
- refnode.children = [nodes.Text(title)]
175
- if not toc.children:
176
- # empty toc means: no titles will show up in the toctree
177
- logger.warning(__('toctree contains reference to document %r that '
178
- "doesn't have a title: no link will be generated"),
179
- ref, location=toctreenode)
180
- except KeyError:
181
- # this is raised if the included file does not exist
182
- if excluded(self.env.doc2path(ref, False)):
183
- message = __('toctree contains reference to excluded document %r')
184
- elif not included(self.env.doc2path(ref, False)):
185
- message = __('toctree contains reference to non-included document %r')
186
- else:
187
- message = __('toctree contains reference to nonexisting document %r')
188
-
189
- logger.warning(message, ref, location=toctreenode)
190
- else:
191
- # children of toc are:
192
- # - list_item + compact_paragraph + (reference and subtoc)
193
- # - only + subtoc
194
- # - toctree
195
- children = cast(Iterable[nodes.Element], toc)
196
-
197
- # if titles_only is given, only keep the main title and
198
- # sub-toctrees
199
- if titles_only:
200
- # delete everything but the toplevel title(s)
201
- # and toctrees
202
- for toplevel in children:
203
- # nodes with length 1 don't have any children anyway
204
- if len(toplevel) > 1:
205
- subtrees = list(toplevel.findall(addnodes.toctree))
206
- if subtrees:
207
- toplevel[1][:] = subtrees # type: ignore
208
- else:
209
- toplevel.pop(1)
210
- # resolve all sub-toctrees
211
- for sub_toc_node in list(toc.findall(addnodes.toctree)):
212
- if sub_toc_node.get('hidden', False) and not includehidden:
213
- continue
214
- for i, entry in enumerate(
215
- _entries_from_toctree(sub_toc_node, [refdoc or ''] + parents,
216
- subtree=True),
217
- start=sub_toc_node.parent.index(sub_toc_node) + 1,
218
- ):
219
- sub_toc_node.parent.insert(i, entry)
220
- sub_toc_node.parent.remove(sub_toc_node)
221
-
222
- entries.extend(children)
223
- if not subtree:
224
- ret = nodes.bullet_list()
225
- ret += entries
226
- return [ret]
227
- return entries
228
-
229
- maxdepth = maxdepth or toctree.get('maxdepth', -1)
230
- if not titles_only and toctree.get('titlesonly', False):
231
- titles_only = True
232
- if not includehidden and toctree.get('includehidden', False):
233
- includehidden = True
234
-
235
- tocentries = _entries_from_toctree(toctree, [])
236
- if not tocentries:
237
- return None
238
-
239
- newnode = addnodes.compact_paragraph('', '')
240
- caption = toctree.attributes.get('caption')
241
- if caption:
242
- caption_node = nodes.title(caption, '', *[nodes.Text(caption)])
243
- caption_node.line = toctree.line
244
- caption_node.source = toctree.source
245
- caption_node.rawsource = toctree['rawcaption']
246
- if hasattr(toctree, 'uid'):
247
- # move uid to caption_node to translate it
248
- caption_node.uid = toctree.uid # type: ignore
249
- del toctree.uid
250
- newnode += caption_node
251
- newnode.extend(tocentries)
252
- newnode['toctree'] = True
253
-
254
- # prune the tree to maxdepth, also set toc depth and current classes
255
- _toctree_add_classes(newnode, 1)
256
- newnode = self._toctree_copy(newnode, 1, maxdepth if prune else 0, collapse)
257
-
258
- if isinstance(newnode[-1], nodes.Element) and len(newnode[-1]) == 0: # No titles found
259
- return None
260
-
261
- # set the target paths in the toctrees (they are not known at TOC
262
- # generation time)
263
- for refnode in newnode.findall(nodes.reference):
264
- if not url_re.match(refnode['refuri']):
265
- refnode['refuri'] = builder.get_relative_uri(
266
- docname, refnode['refuri']) + refnode['anchorname']
267
- return newnode
268
-
269
- def get_toctree_ancestors(self, docname: str) -> list[str]:
270
- parent = {}
271
- for p, children in self.env.toctree_includes.items():
272
- for child in children:
273
- parent[child] = p
274
- ancestors: list[str] = []
275
- d = docname
276
- while d in parent and d not in ancestors:
277
- ancestors.append(d)
278
- d = parent[d]
279
- return ancestors
280
-
281
- ET = TypeVar('ET', bound=Element)
282
-
283
- def _toctree_copy(self, node: ET, depth: int, maxdepth: int, collapse: bool) -> ET:
284
- """Utility: Cut and deep-copy a TOC at a specified depth."""
285
- keep_bullet_list_sub_nodes = (depth <= 1
286
- or ((depth <= maxdepth or maxdepth <= 0)
287
- and (not collapse or 'iscurrent' in node)))
288
-
289
- copy = node.copy()
290
- for subnode in node.children:
291
- if isinstance(subnode, (addnodes.compact_paragraph, nodes.list_item)):
292
- # for <p> and <li>, just recurse
293
- copy.append(self._toctree_copy(subnode, depth, maxdepth, collapse))
294
- elif isinstance(subnode, nodes.bullet_list):
295
- # for <ul>, copy if the entry is top-level
296
- # or, copy if the depth is within bounds and;
297
- # collapsing is disabled or the sub-entry's parent is 'current'.
298
- # The boolean is constant so is calculated outwith the loop.
299
- if keep_bullet_list_sub_nodes:
300
- copy.append(self._toctree_copy(subnode, depth + 1, maxdepth, collapse))
301
- else:
302
- copy.append(subnode.deepcopy())
303
- return copy
502
+ return _resolve_toctree(
503
+ self.env, docname, builder, toctree,
504
+ prune=prune,
505
+ maxdepth=maxdepth,
506
+ titles_only=titles_only,
507
+ collapse=collapse,
508
+ includehidden=includehidden,
509
+ )
304
510
 
305
511
  def get_toc_for(self, docname: str, builder: Builder) -> Node:
306
- """Return a TOC nodetree -- for use on the same page only!"""
307
- tocdepth = self.env.metadata[docname].get('tocdepth', 0)
308
- try:
309
- toc = self._toctree_copy(self.env.tocs[docname], 2, tocdepth, False)
310
- except KeyError:
311
- # the document does not exist anymore: return a dummy node that
312
- # renders to nothing
313
- return nodes.paragraph()
314
- process_only_nodes(toc, builder.tags)
315
- for node in toc.findall(nodes.reference):
316
- node['refuri'] = node['anchorname'] or '#'
317
- return toc
318
-
319
- def get_toctree_for(self, docname: str, builder: Builder, collapse: bool,
320
- **kwargs: Any) -> Element | None:
321
- """Return the global TOC nodetree."""
322
- doctree = self.env.master_doctree
323
- toctrees: list[Element] = []
324
- if 'includehidden' not in kwargs:
325
- kwargs['includehidden'] = True
326
- if 'maxdepth' not in kwargs or not kwargs['maxdepth']:
327
- kwargs['maxdepth'] = 0
328
- else:
329
- kwargs['maxdepth'] = int(kwargs['maxdepth'])
330
- kwargs['collapse'] = collapse
331
- for toctreenode in doctree.findall(addnodes.toctree):
332
- toctree = self.resolve(docname, builder, toctreenode, prune=True, **kwargs)
333
- if toctree:
334
- toctrees.append(toctree)
335
- if not toctrees:
336
- return None
337
- result = toctrees[0]
338
- for toctree in toctrees[1:]:
339
- result.extend(toctree.children)
340
- return result
512
+ return document_toc(self.env, docname, self.env.app.builder.tags)
513
+
514
+ def get_toctree_for(
515
+ self, docname: str, builder: Builder, collapse: bool, **kwargs: Any,
516
+ ) -> Element | None:
517
+ return global_toctree_for_doc(self.env, docname, builder, collapse=collapse, **kwargs)