9
9
from bs4 import BeautifulSoup
10
10
from docutils import nodes
11
11
from docutils .nodes import Node
12
- from sphinx import addnodes
13
- from sphinx .addnodes import toctree as toctree_node
12
+ from sphinx .addnodes import toctree as TocTreeNodeClass
14
13
from sphinx .application import Sphinx
15
14
from sphinx .environment .adapters .toctree import TocTree
16
15
from sphinx .locale import _
@@ -32,15 +31,8 @@ def add_inline_math(node: Node) -> str:
32
31
)
33
32
34
33
35
- def get_unrendered_local_toctree (
36
- app : Sphinx , pagename : str , startdepth : int , collapse : bool = True , ** kwargs
37
- ):
38
- """."""
39
- if "includehidden" not in kwargs :
40
- kwargs ["includehidden" ] = False
41
- if kwargs .get ("maxdepth" ) == "" :
42
- kwargs .pop ("maxdepth" )
43
-
34
+ def _get_ancestor_section (app : Sphinx , pagename : str , startdepth : int ) -> str :
35
+ """Get the TocTree node `startdepth` levels below the root that dominates `pagename`."""
44
36
toctree = TocTree (app .env )
45
37
if sphinx .version_info [:2 ] >= (7 , 2 ):
46
38
from sphinx .environment .adapters .toctree import _get_toctree_ancestors
@@ -49,16 +41,30 @@ def get_unrendered_local_toctree(
49
41
else :
50
42
ancestors = toctree .get_toctree_ancestors (pagename )
51
43
try :
52
- indexname = ancestors [- startdepth ]
44
+ return ancestors [- startdepth ] # will be a pagename (string)?
53
45
except IndexError :
54
46
# eg for index.rst, but also special pages such as genindex, py-modindex, search
55
47
# those pages don't have a "current" element in the toctree, so we can
56
48
# directly return an empty string instead of using the default sphinx
57
49
# toctree.get_toctree_for(pagename, app.builder, collapse, **kwargs)
58
- return ""
50
+ return None
59
51
60
- return get_local_toctree_for (
61
- toctree , indexname , pagename , app .builder , collapse , ** kwargs
52
+
53
+ def get_unrendered_local_toctree (app : Sphinx , pagename : str , startdepth : int , ** kwargs ):
54
+ """Get the "local" (starting at `startdepth`) TocTree containing `pagename`.
55
+
56
+ This is similar to `context["toctree"](**kwargs)` in sphinx templating,
57
+ but using the startdepth-local instead of global TOC tree.
58
+ """
59
+ kwargs .setdefault ("collapse" , True )
60
+ if kwargs .get ("maxdepth" ) == "" :
61
+ kwargs .pop ("maxdepth" )
62
+ toctree = TocTree (app .env )
63
+ indexname = _get_ancestor_section (app = app , pagename = pagename , startdepth = startdepth )
64
+ if indexname is None :
65
+ return None
66
+ return get_local_toctree_for_doc (
67
+ toctree , indexname , pagename , app .builder , ** kwargs
62
68
)
63
69
64
70
@@ -67,11 +73,18 @@ def add_toctree_functions(
67
73
) -> None :
68
74
"""Add functions so Jinja templates can add toctree objects."""
69
75
70
- def get_sidebar_toctree_length (
71
- startdepth : int = 1 , show_nav_level : int = 1 , ** kwargs
72
- ):
73
- toctree = get_unrendered_local_toctree (app , pagename , startdepth )
74
- return 0 if toctree is None else len (toctree )
76
+ def missing_sidebar_toctree (startdepth : int = 1 , ** kwargs ):
77
+ """Check if there's a sidebar TocTree that needs to be rendered.
78
+
79
+ Parameters:
80
+ startdepth : The level of the TocTree at which to start. 0 includes the
81
+ entire TocTree for the site; 1 (default) gets the TocTree for the current
82
+ top-level section.
83
+
84
+ kwargs: passed to the Sphinx `toctree` template function.
85
+ """
86
+ toctree = get_unrendered_local_toctree (app , pagename , startdepth , ** kwargs )
87
+ return toctree is None
75
88
76
89
@cache
77
90
def get_or_create_id_generator (base_id : str ) -> Iterator [str ]:
@@ -120,8 +133,8 @@ def generate_header_nav_before_dropdown(n_links_before_dropdown):
120
133
# Iterate through each toctree node in the root document
121
134
# Grab the toctree pages and find the relative link + title.
122
135
links_html = []
123
- # TODO: use `root.findall(toctree_node )` once docutils min version >=0.18.1
124
- for toc in traverse_or_findall (root , toctree_node ):
136
+ # TODO: use `root.findall(TocTreeNodeClass )` once docutils min version >=0.18.1
137
+ for toc in traverse_or_findall (root , TocTreeNodeClass ):
125
138
for title , page in toc .attributes ["entries" ]:
126
139
# if the page is using "self" use the correct link
127
140
page = toc .attributes ["parent" ] if page == "self" else page
@@ -255,12 +268,15 @@ def generate_toctree_html(
255
268
HTML string (if kind == "sidebar") OR BeautifulSoup object (if kind == "raw")
256
269
"""
257
270
if startdepth == 0 :
258
- toc_sphinx = context ["toctree" ](** kwargs )
271
+ html_toctree = context ["toctree" ](** kwargs )
259
272
else :
260
273
# select the "active" subset of the navigation tree for the sidebar
261
- toc_sphinx = index_toctree (app , pagename , startdepth , ** kwargs )
274
+ toctree_element = get_unrendered_local_toctree (
275
+ app , pagename , startdepth , ** kwargs
276
+ )
277
+ html_toctree = app .builder .render_partial (toctree_element )["fragment" ]
262
278
263
- soup = BeautifulSoup (toc_sphinx , "html.parser" )
279
+ soup = BeautifulSoup (html_toctree , "html.parser" )
264
280
265
281
# pair "current" with "active" since that's what we use w/ bootstrap
266
282
for li in soup ("li" , {"class" : "current" }):
@@ -378,7 +394,7 @@ def navbar_align_class() -> List[str]:
378
394
379
395
context ["unique_html_id" ] = unique_html_id
380
396
context ["generate_header_nav_html" ] = generate_header_nav_html
381
- context ["get_sidebar_toctree_length " ] = get_sidebar_toctree_length
397
+ context ["missing_sidebar_toctree " ] = missing_sidebar_toctree
382
398
context ["generate_toctree_html" ] = generate_toctree_html
383
399
context ["generate_toc_html" ] = generate_toc_html
384
400
context ["navbar_align_class" ] = navbar_align_class
@@ -443,56 +459,36 @@ def add_collapse_checkboxes(soup: BeautifulSoup) -> None:
443
459
element .insert (1 , checkbox )
444
460
445
461
446
- def get_local_toctree_for (
447
- self : TocTree , indexname : str , docname : str , builder , collapse : bool , ** kwargs
462
+ def get_local_toctree_for_doc (
463
+ toctree : TocTree , indexname : str , pagename : str , builder , collapse : bool , ** kwargs
448
464
) -> List [BeautifulSoup ]:
449
- """Return the "local" TOC nodetree (relative to `indexname`)."""
450
- # this is a copy of `TocTree.get_toctree_for`, but where the sphinx version
451
- # always uses the "root" doctree:
452
- # doctree = self.env.get_doctree(self.env.config.root_doc)
453
- # we here use the `indexname` additional argument to be able to use a subset
454
- # of the doctree (e.g. starting at a second level for the sidebar):
455
- # doctree = app.env.tocs[indexname].deepcopy()
465
+ """Get the "local" TocTree containing `pagename` rooted at `indexname`.
456
466
457
- doctree = self .env .tocs [indexname ].deepcopy ()
467
+ The Sphinx equivalent is TocTree.get_toctree_for(), which always uses the "root"
468
+ or "global" TocTree:
469
+
470
+ doctree = self.env.get_doctree(self.env.config.root_doc)
471
+
472
+ Whereas here we return a subset of the global toctree, rooted at `indexname`
473
+ (e.g. starting at a second level for the sidebar).
474
+ """
475
+ partial_doctree = toctree .env .tocs [indexname ].deepcopy ()
458
476
459
477
toctrees = []
460
- if "includehidden" not in kwargs :
461
- kwargs ["includehidden" ] = True
462
478
if "maxdepth" not in kwargs or not kwargs ["maxdepth" ]:
463
479
kwargs ["maxdepth" ] = 0
464
- else :
465
- kwargs ["maxdepth" ] = int (kwargs ["maxdepth" ])
480
+ kwargs ["maxdepth" ] = int (kwargs ["maxdepth" ])
466
481
kwargs ["collapse" ] = collapse
467
482
468
- # TODO: use `doctree.findall(addnodes.toctree)` once docutils min version >=0.18.1
469
- for toctreenode in traverse_or_findall (doctree , addnodes .toctree ):
470
- toctree = self .resolve (docname , builder , toctreenode , prune = True , ** kwargs )
471
- if toctree :
472
- toctrees .append (toctree )
483
+ # TODO: use `doctree.findall(TocTreeNodeClass)` once docutils min version >=0.18.1
484
+ for _node in traverse_or_findall (partial_doctree , TocTreeNodeClass ):
485
+ # defaults for resolve: prune=True, maxdepth=0, titles_only=False, collapse=False, includehidden=False
486
+ _toctree = toctree .resolve (pagename , builder , _node , ** kwargs )
487
+ if _toctree :
488
+ toctrees .append (_toctree )
473
489
if not toctrees :
474
490
return None
475
491
result = toctrees [0 ]
476
492
for toctree in toctrees [1 :]:
477
493
result .extend (toctree .children )
478
494
return result
479
-
480
-
481
- def index_toctree (
482
- app : Sphinx , pagename : str , startdepth : int , collapse : bool = True , ** kwargs
483
- ):
484
- """Returns the "local" (starting at `startdepth`) TOC tree containing the current page, rendered as HTML bullet lists.
485
-
486
- This is the equivalent of `context["toctree"](**kwargs)` in sphinx
487
- templating, but using the startdepth-local instead of global TOC tree.
488
- """
489
- # this is a variant of the function stored in `context["toctree"]`, which is
490
- # defined as `lambda **kwargs: self._get_local_toctree(pagename, **kwargs)`
491
- # with `self` being the HMTLBuilder and the `_get_local_toctree` basically
492
- # returning:
493
- # return self.render_partial(TocTree(self.env).get_toctree_for(
494
- # pagename, self, collapse, **kwargs))['fragment']
495
- toctree_element = get_unrendered_local_toctree (
496
- app , pagename , startdepth , collapse , ** kwargs
497
- )
498
- return app .builder .render_partial (toctree_element )["fragment" ]
0 commit comments