@@ -204,7 +204,7 @@ def _resolve_toctree(
204204
205205 # prune the tree to maxdepth, also set toc depth and current classes
206206 _toctree_add_classes (newnode , 1 , docname )
207- newnode = _toctree_copy (newnode , 1 , maxdepth if prune else 0 , collapse , tags )
207+ _toctree_prune (newnode , 1 , maxdepth if prune else 0 , collapse , tags )
208208
209209 if (
210210 isinstance (newnode [- 1 ], nodes .Element ) and len (newnode [- 1 ]) == 0
@@ -482,6 +482,7 @@ def _toctree_add_classes(node: Element, depth: int, docname: str) -> None:
482482 subnode = subnode .parent
483483
484484
485+ # Note: Equivalent to _toctree_prune, but prunes into a copy.
485486def _toctree_copy [ET : Element ](
486487 node : ET , depth : int , maxdepth : int , collapse : bool , tags : Tags
487488) -> ET :
@@ -493,6 +494,7 @@ def _toctree_copy[ET: Element](
493494 return copied [0 ] # type: ignore[return-value]
494495
495496
497+ # Note: Equivalent to _toctree_prune_seq, but prunes into a copy.
496498def _toctree_copy_seq (
497499 node : Node ,
498500 depth : int ,
@@ -559,6 +561,78 @@ def _toctree_copy_seq(
559561 raise ValueError (msg )
560562
561563
564+ # Note: Equivalent to _toctree_copy, but prunes in-place.
565+ def _toctree_prune (
566+ node : Node , depth : int , maxdepth : int , collapse : bool , tags : Tags
567+ ) -> None :
568+ """Utility: Cut and deep-copy a TOC at a specified depth."""
569+ assert not isinstance (node , addnodes .only )
570+ depth = max (depth - 1 , 1 )
571+ _toctree_prune_seq (node , depth , maxdepth , collapse , tags , initial_call = True )
572+
573+
574+ # Note: Equivalent to _toctree_copy_seq, but prunes in-place.
575+ def _toctree_prune_seq (
576+ node : Node ,
577+ depth : int ,
578+ maxdepth : int ,
579+ collapse : bool ,
580+ tags : Tags ,
581+ * ,
582+ initial_call : bool = False ,
583+ is_current : bool = False ,
584+ ) -> None :
585+ if isinstance (node , (addnodes .compact_paragraph , nodes .list_item )):
586+ # for <p> and <li>, just recurse
587+ for subnode in node .children :
588+ _toctree_prune_seq (
589+ subnode , depth , maxdepth , collapse , tags , is_current = 'iscurrent' in node
590+ )
591+ return
592+
593+ if isinstance (node , nodes .bullet_list ):
594+ # for <ul>, copy if the entry is top-level
595+ # or, copy if the depth is within bounds and;
596+ # collapsing is disabled or the sub-entry's parent is 'current'.
597+ # The boolean is constant so is calculated outwith the loop.
598+ keep_bullet_list_sub_nodes = depth <= 1 or (
599+ (depth <= maxdepth or maxdepth <= 0 )
600+ and (not collapse or is_current or 'iscurrent' in node )
601+ )
602+ if not keep_bullet_list_sub_nodes and not initial_call :
603+ node .replace_self ([])
604+ return
605+ depth += 1
606+ for subnode in node .children :
607+ _toctree_prune_seq (
608+ subnode , depth , maxdepth , collapse , tags , is_current = 'iscurrent' in node
609+ )
610+ return
611+
612+ if isinstance (node , addnodes .toctree ):
613+ return
614+
615+ if isinstance (node , addnodes .only ):
616+ # only keep children if the only node matches the tags
617+ if not _only_node_keep_children (node , tags ):
618+ node .replace_self ([])
619+ return
620+ for subnode in node .children :
621+ _toctree_prune_seq (
622+ subnode , depth , maxdepth , collapse , tags , is_current = 'iscurrent' in node
623+ )
624+ children = list (node .children )
625+ node .children = []
626+ node .replace_self (children )
627+ return
628+
629+ if isinstance (node , (nodes .reference , nodes .title )):
630+ return
631+
632+ msg = f'Unexpected node type { node .__class__ .__name__ !r} !'
633+ raise ValueError (msg )
634+
635+
562636def _get_toctree_ancestors (
563637 toctree_includes : dict [str , list [str ]],
564638 docname : str ,
0 commit comments