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.
- sphinx/__init__.py +6 -6
- sphinx/__main__.py +3 -1
- sphinx/addnodes.py +35 -22
- sphinx/application.py +40 -38
- sphinx/builders/__init__.py +16 -12
- sphinx/builders/_epub_base.py +15 -11
- sphinx/builders/changes.py +6 -4
- sphinx/builders/dirhtml.py +4 -2
- sphinx/builders/dummy.py +6 -4
- sphinx/builders/epub3.py +16 -8
- sphinx/builders/gettext.py +40 -43
- sphinx/builders/html/__init__.py +166 -196
- sphinx/builders/html/_assets.py +116 -0
- sphinx/builders/html/transforms.py +4 -2
- sphinx/builders/latex/__init__.py +12 -7
- sphinx/builders/latex/theming.py +5 -2
- sphinx/builders/latex/transforms.py +6 -3
- sphinx/builders/linkcheck.py +21 -13
- sphinx/builders/manpage.py +6 -4
- sphinx/builders/singlehtml.py +16 -9
- sphinx/builders/texinfo.py +11 -6
- sphinx/builders/text.py +8 -3
- sphinx/builders/xml.py +9 -4
- sphinx/cmd/build.py +27 -14
- sphinx/cmd/make_mode.py +13 -4
- sphinx/cmd/quickstart.py +13 -4
- sphinx/config.py +17 -14
- sphinx/deprecation.py +4 -2
- sphinx/directives/__init__.py +44 -12
- sphinx/directives/code.py +5 -4
- sphinx/directives/other.py +92 -44
- sphinx/directives/patches.py +1 -1
- sphinx/domains/__init__.py +11 -8
- sphinx/domains/c.py +67 -57
- sphinx/domains/changeset.py +3 -2
- sphinx/domains/citation.py +2 -1
- sphinx/domains/cpp.py +136 -93
- sphinx/domains/index.py +9 -5
- sphinx/domains/javascript.py +32 -19
- sphinx/domains/math.py +5 -3
- sphinx/domains/python.py +69 -57
- sphinx/domains/rst.py +20 -11
- sphinx/domains/std.py +21 -15
- sphinx/environment/__init__.py +97 -65
- sphinx/environment/adapters/indexentries.py +13 -10
- sphinx/environment/adapters/toctree.py +485 -308
- sphinx/environment/collectors/__init__.py +3 -4
- sphinx/environment/collectors/asset.py +10 -4
- sphinx/environment/collectors/dependencies.py +7 -4
- sphinx/environment/collectors/metadata.py +7 -5
- sphinx/environment/collectors/title.py +5 -3
- sphinx/environment/collectors/toctree.py +13 -8
- sphinx/errors.py +1 -1
- sphinx/events.py +5 -5
- sphinx/ext/apidoc.py +49 -27
- sphinx/ext/autodoc/__init__.py +179 -161
- sphinx/ext/autodoc/directive.py +10 -6
- sphinx/ext/autodoc/importer.py +22 -13
- sphinx/ext/autodoc/mock.py +4 -1
- sphinx/ext/autodoc/preserve_defaults.py +80 -12
- sphinx/ext/autodoc/type_comment.py +14 -10
- sphinx/ext/autodoc/typehints.py +7 -3
- sphinx/ext/autosectionlabel.py +6 -3
- sphinx/ext/autosummary/__init__.py +21 -15
- sphinx/ext/autosummary/generate.py +176 -126
- sphinx/ext/coverage.py +93 -8
- sphinx/ext/doctest.py +28 -17
- sphinx/ext/duration.py +19 -17
- sphinx/ext/extlinks.py +11 -6
- sphinx/ext/githubpages.py +8 -7
- sphinx/ext/graphviz.py +61 -17
- sphinx/ext/ifconfig.py +7 -4
- sphinx/ext/imgconverter.py +4 -2
- sphinx/ext/imgmath.py +29 -23
- sphinx/ext/inheritance_diagram.py +41 -27
- sphinx/ext/intersphinx.py +45 -38
- sphinx/ext/linkcode.py +8 -5
- sphinx/ext/mathjax.py +13 -9
- sphinx/ext/napoleon/__init__.py +3 -3
- sphinx/ext/napoleon/docstring.py +40 -31
- sphinx/ext/todo.py +10 -7
- sphinx/ext/viewcode.py +46 -25
- sphinx/extension.py +1 -1
- sphinx/highlighting.py +20 -12
- sphinx/io.py +5 -4
- sphinx/jinja2glue.py +24 -19
- sphinx/locale/__init__.py +8 -2
- sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ar/LC_MESSAGES/sphinx.po +756 -740
- sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bg/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bn/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ca/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cak/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cs/LC_MESSAGES/sphinx.po +758 -742
- sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cy/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/da/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/el/LC_MESSAGES/sphinx.po +763 -747
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eo/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es/LC_MESSAGES/sphinx.po +767 -751
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/et/LC_MESSAGES/sphinx.po +762 -746
- sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eu/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fa/LC_MESSAGES/sphinx.po +766 -750
- sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fi/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/gl/LC_MESSAGES/sphinx.js +60 -0
- sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/gl/LC_MESSAGES/sphinx.po +3695 -0
- sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/he/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi/LC_MESSAGES/sphinx.po +763 -747
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hr/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hu/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/id/LC_MESSAGES/sphinx.po +765 -749
- sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/is/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/it/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ja/LC_MESSAGES/sphinx.po +767 -751
- sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ka/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ko/LC_MESSAGES/sphinx.po +767 -751
- sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lt/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lv/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/mk/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ne/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nl/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pl/LC_MESSAGES/sphinx.po +762 -745
- sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ro/LC_MESSAGES/sphinx.po +759 -743
- sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ru/LC_MESSAGES/sphinx.po +760 -744
- sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/si/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sk/LC_MESSAGES/sphinx.po +765 -749
- sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sl/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/sphinx.pot +748 -740
- sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sq/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr@latin/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr_RS/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sv/LC_MESSAGES/sphinx.po +755 -739
- sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ta/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/te/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/tr/LC_MESSAGES/sphinx.po +763 -747
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +760 -749
- sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ur/LC_MESSAGES/sphinx.po +759 -748
- sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/vi/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/yue/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/zh_CN/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +768 -752
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +754 -738
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +767 -751
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +754 -738
- sphinx/parsers.py +5 -4
- sphinx/project.py +52 -34
- sphinx/pycode/__init__.py +2 -1
- sphinx/pycode/ast.py +7 -13
- sphinx/pycode/parser.py +42 -38
- sphinx/registry.py +35 -29
- sphinx/roles.py +9 -4
- sphinx/search/__init__.py +5 -17
- sphinx/search/da.py +1 -1
- sphinx/search/de.py +1 -1
- sphinx/search/en.py +1 -1
- sphinx/search/es.py +1 -1
- sphinx/search/fi.py +1 -1
- sphinx/search/fr.py +1 -1
- sphinx/search/hu.py +1 -1
- sphinx/search/it.py +1 -1
- sphinx/search/ja.py +1 -1
- sphinx/search/nl.py +1 -1
- sphinx/search/no.py +1 -1
- sphinx/search/pt.py +1 -1
- sphinx/search/ro.py +1 -1
- sphinx/search/ru.py +1 -1
- sphinx/search/sv.py +1 -1
- sphinx/search/tr.py +1 -1
- sphinx/search/zh.py +1 -1
- sphinx/testing/fixtures.py +23 -30
- sphinx/testing/path.py +9 -0
- sphinx/testing/restructuredtext.py +13 -5
- sphinx/testing/util.py +20 -63
- sphinx/texinputs/sphinxlatexobjects.sty +15 -15
- sphinx/themes/agogo/static/agogo.css_t +10 -4
- sphinx/themes/basic/layout.html +1 -1
- sphinx/themes/basic/static/basic.css_t +4 -0
- sphinx/themes/basic/static/documentation_options.js_t +1 -2
- sphinx/themes/basic/static/searchtools.js +17 -9
- sphinx/themes/basic/static/sphinx_highlight.js +13 -3
- sphinx/themes/bizstyle/static/bizstyle.css_t +4 -0
- sphinx/themes/classic/theme.conf +1 -1
- sphinx/themes/epub/static/epub.css_t +6 -1
- sphinx/themes/haiku/theme.conf +1 -1
- sphinx/themes/nature/static/nature.css_t +4 -0
- sphinx/themes/nonav/static/nonav.css_t +6 -1
- sphinx/themes/pyramid/static/pyramid.css_t +4 -0
- sphinx/themes/scrolls/static/scrolls.css_t +4 -0
- sphinx/themes/scrolls/theme.conf +1 -1
- sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +4 -0
- sphinx/theming.py +9 -7
- sphinx/transforms/__init__.py +79 -3
- sphinx/transforms/compact_bullet_list.py +6 -3
- sphinx/transforms/i18n.py +26 -10
- sphinx/transforms/post_transforms/__init__.py +21 -8
- sphinx/transforms/post_transforms/code.py +6 -3
- sphinx/transforms/post_transforms/images.py +13 -9
- sphinx/util/__init__.py +21 -92
- sphinx/util/cfamily.py +7 -4
- sphinx/util/display.py +3 -2
- sphinx/util/docfields.py +7 -6
- sphinx/util/docstrings.py +1 -1
- sphinx/util/docutils.py +41 -31
- sphinx/util/fileutil.py +9 -6
- sphinx/util/i18n.py +21 -18
- sphinx/util/images.py +2 -1
- sphinx/util/index_entries.py +27 -0
- sphinx/util/inspect.py +83 -67
- sphinx/util/inventory.py +4 -2
- sphinx/util/logging.py +9 -6
- sphinx/util/matching.py +5 -2
- sphinx/util/math.py +6 -3
- sphinx/util/nodes.py +70 -31
- sphinx/util/osutil.py +22 -40
- sphinx/util/parallel.py +4 -1
- sphinx/util/rst.py +7 -3
- sphinx/util/tags.py +11 -4
- sphinx/util/template.py +17 -14
- sphinx/util/typing.py +61 -20
- sphinx/versioning.py +6 -4
- sphinx/writers/html.py +1 -1
- sphinx/writers/html5.py +32 -24
- sphinx/writers/latex.py +67 -53
- sphinx/writers/manpage.py +9 -5
- sphinx/writers/texinfo.py +11 -9
- sphinx/writers/text.py +14 -9
- sphinx/writers/xml.py +3 -2
- {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/METADATA +7 -5
- sphinx-7.2.0.dist-info/RECORD +568 -0
- sphinx/testing/comparer.py +0 -97
- sphinx-7.1.1.dist-info/RECORD +0 -564
- {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/LICENSE +0 -0
- {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/WHEEL +0 -0
- {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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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)
|