diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 832b9a91a3d..273124eb32e 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -32,7 +32,7 @@
from sphinx.environment import BuildEnvironment
from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.environment.adapters.indexentries import IndexEntries
-from sphinx.environment.adapters.toctree import TocTree
+from sphinx.environment.adapters.toctree import document_toc, global_toctree_for_doc
from sphinx.errors import ConfigError, ThemeError
from sphinx.highlighting import PygmentsBridge
from sphinx.locale import _, __
@@ -638,7 +638,7 @@ def get_doc_context(self, docname: str, body: str, metatags: str) -> dict[str, A
meta = self.env.metadata.get(docname)
# local TOC and global TOC tree
- self_toc = TocTree(self.env).get_toc_for(docname, self)
+ self_toc = document_toc(self.env, docname, self.tags)
toc = self.render_partial(self_toc)['fragment']
return {
@@ -969,8 +969,8 @@ def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any)
kwargs['includehidden'] = False
if kwargs.get('maxdepth') == '':
kwargs.pop('maxdepth')
- return self.render_partial(TocTree(self.env).get_toctree_for(
- docname, self, collapse, **kwargs))['fragment']
+ toctree = global_toctree_for_doc(self.env, docname, self, collapse=collapse, **kwargs)
+ return self.render_partial(toctree)['fragment']
def get_outfilename(self, pagename: str) -> str:
return path.join(self.outdir, os_path(pagename) + self.out_suffix)
diff --git a/sphinx/builders/singlehtml.py b/sphinx/builders/singlehtml.py
index 70fe61a8cd7..45c0350cbff 100644
--- a/sphinx/builders/singlehtml.py
+++ b/sphinx/builders/singlehtml.py
@@ -10,7 +10,7 @@
from sphinx.application import Sphinx
from sphinx.builders.html import StandaloneHTMLBuilder
-from sphinx.environment.adapters.toctree import TocTree
+from sphinx.environment.adapters.toctree import global_toctree_for_doc
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.console import darkgreen # type: ignore
@@ -61,9 +61,13 @@ def fix_refuris(self, tree: Node) -> None:
refnode['refuri'] = fname + refuri[hashindex:]
def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any) -> str:
- if 'includehidden' not in kwargs:
+ if kwargs.get('includehidden', 'false').lower() == 'false':
kwargs['includehidden'] = False
- toctree = TocTree(self.env).get_toctree_for(docname, self, collapse, **kwargs)
+ elif kwargs['includehidden'].lower() == 'true':
+ kwargs['includehidden'] = True
+ if kwargs.get('maxdepth') == '':
+ kwargs.pop('maxdepth')
+ toctree = global_toctree_for_doc(self.env, docname, self, collapse=collapse, **kwargs)
if toctree is not None:
self.fix_refuris(toctree)
return self.render_partial(toctree)['fragment']
@@ -118,7 +122,7 @@ def assemble_toc_fignumbers(self) -> dict[str, dict[str, dict[str, tuple[int, ..
def get_doc_context(self, docname: str, body: str, metatags: str) -> dict[str, Any]:
# no relation links...
- toctree = TocTree(self.env).get_toctree_for(self.config.root_doc, self, False)
+ toctree = global_toctree_for_doc(self.env, self.config.root_doc, self, collapse=False)
# if there is no toctree, toc is None
if toctree:
self.fix_refuris(toctree)
diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py
index 7f9930e51ce..dec88b1d628 100644
--- a/sphinx/directives/other.py
+++ b/sphinx/directives/other.py
@@ -12,6 +12,7 @@
from sphinx import addnodes
from sphinx.domains.changeset import VersionChange # noqa: F401 # for compatibility
+from sphinx.domains.std import StandardDomain
from sphinx.locale import _, __
from sphinx.util import docname_join, logging, url_re
from sphinx.util.docutils import SphinxDirective
@@ -79,71 +80,81 @@ def run(self) -> list[Node]:
return ret
def parse_content(self, toctree: addnodes.toctree) -> list[Node]:
- generated_docnames = frozenset(self.env.domains['std']._virtual_doc_names)
+ generated_docnames = frozenset(StandardDomain._virtual_doc_names)
suffixes = self.config.source_suffix
+ current_docname = self.env.docname
+ glob = toctree['glob']
# glob target documents
all_docnames = self.env.found_docs.copy() | generated_docnames
- all_docnames.remove(self.env.docname) # remove current document
+ all_docnames.remove(current_docname) # remove current document
+ frozen_all_docnames = frozenset(all_docnames)
ret: list[Node] = []
excluded = Matcher(self.config.exclude_patterns)
for entry in self.content:
if not entry:
continue
+
# look for explicit titles ("Some Title ")
explicit = explicit_title_re.match(entry)
- if (toctree['glob'] and glob_re.match(entry) and
- not explicit and not url_re.match(entry)):
- patname = docname_join(self.env.docname, entry)
- docnames = sorted(patfilter(all_docnames, patname))
- for docname in docnames:
+ url_match = url_re.match(entry) is not None
+ if glob and glob_re.match(entry) and not explicit and not url_match:
+ pat_name = docname_join(current_docname, entry)
+ doc_names = sorted(patfilter(all_docnames, pat_name))
+ for docname in doc_names:
if docname in generated_docnames:
# don't include generated documents in globs
continue
all_docnames.remove(docname) # don't include it again
toctree['entries'].append((None, docname))
toctree['includefiles'].append(docname)
- if not docnames:
+ if not doc_names:
logger.warning(__("toctree glob pattern %r didn't match any documents"),
entry, location=toctree)
+ continue
+
+ if explicit:
+ ref = explicit.group(2)
+ title = explicit.group(1)
+ docname = ref
else:
- if explicit:
- ref = explicit.group(2)
- title = explicit.group(1)
- docname = ref
- else:
- ref = docname = entry
- title = None
- # remove suffixes (backwards compatibility)
- for suffix in suffixes:
- if docname.endswith(suffix):
- docname = docname[:-len(suffix)]
- break
- # absolutize filenames
- docname = docname_join(self.env.docname, docname)
- if url_re.match(ref) or ref == 'self':
- toctree['entries'].append((title, ref))
- elif docname not in self.env.found_docs | generated_docnames:
- if excluded(self.env.doc2path(docname, False)):
- message = __('toctree contains reference to excluded document %r')
- subtype = 'excluded'
- else:
- message = __('toctree contains reference to nonexisting document %r')
- subtype = 'not_readable'
-
- logger.warning(message, docname, type='toc', subtype=subtype,
- location=toctree)
- self.env.note_reread()
+ ref = docname = entry
+ title = None
+
+ # remove suffixes (backwards compatibility)
+ for suffix in suffixes:
+ if docname.endswith(suffix):
+ docname = docname.removesuffix(suffix)
+ break
+
+ # absolutise filenames
+ docname = docname_join(current_docname, docname)
+ if url_match or ref == 'self':
+ toctree['entries'].append((title, ref))
+ continue
+
+ if docname not in frozen_all_docnames:
+ if excluded(self.env.doc2path(docname, False)):
+ message = __('toctree contains reference to excluded document %r')
+ subtype = 'excluded'
else:
- if docname in all_docnames:
- all_docnames.remove(docname)
- else:
- logger.warning(__('duplicated entry found in toctree: %s'), docname,
- location=toctree)
+ message = __('toctree contains reference to nonexisting document %r')
+ subtype = 'not_readable'
- toctree['entries'].append((title, docname))
- toctree['includefiles'].append(docname)
+ logger.warning(message, docname, type='toc', subtype=subtype,
+ location=toctree)
+ self.env.note_reread()
+ continue
+
+ if docname in all_docnames:
+ all_docnames.remove(docname)
+ else:
+ logger.warning(__('duplicated entry found in toctree: %s'), docname,
+ location=toctree)
+
+ toctree['entries'].append((title, docname))
+ toctree['includefiles'].append(docname)
# entries contains all entries (self references, external links etc.)
if 'reversed' in self.options:
diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py
index 116840e5e9e..d3a9b7c1e47 100644
--- a/sphinx/environment/__init__.py
+++ b/sphinx/environment/__init__.py
@@ -17,7 +17,7 @@
from sphinx import addnodes
from sphinx.config import Config
from sphinx.domains import Domain
-from sphinx.environment.adapters.toctree import TocTree
+from sphinx.environment.adapters.toctree import _resolve_toctree
from sphinx.errors import BuildEnvironmentError, DocumentError, ExtensionError, SphinxError
from sphinx.events import EventManager
from sphinx.locale import __
@@ -58,7 +58,7 @@
# This is increased every time an environment attribute is added
# or changed to properly invalidate pickle files.
-ENV_VERSION = 58
+ENV_VERSION = 59
# config status
CONFIG_UNSET = -1
@@ -630,9 +630,9 @@ def get_and_resolve_doctree(
# now, resolve all toctree nodes
for toctreenode in doctree.findall(addnodes.toctree):
- result = TocTree(self).resolve(docname, builder, toctreenode,
- prune=prune_toctrees,
- includehidden=includehidden)
+ result = _resolve_toctree(self, docname, builder, toctreenode,
+ prune=prune_toctrees,
+ includehidden=includehidden)
if result is None:
toctreenode.parent.replace(toctreenode, [])
else:
@@ -654,9 +654,8 @@ def resolve_toctree(self, docname: str, builder: Builder, toctree: addnodes.toct
If *collapse* is True, all branches not containing docname will
be collapsed.
"""
- return TocTree(self).resolve(docname, builder, toctree, prune,
- maxdepth, titles_only, collapse,
- includehidden)
+ return _resolve_toctree(self, docname, builder, toctree, prune,
+ maxdepth, titles_only, collapse, includehidden)
def resolve_references(self, doctree: nodes.document, fromdocname: str,
builder: Builder) -> None:
@@ -680,38 +679,21 @@ def apply_post_transforms(self, doctree: nodes.document, docname: str) -> None:
self.events.emit('doctree-resolved', doctree, docname)
def collect_relations(self) -> dict[str, list[str | None]]:
- traversed = set()
-
- def traverse_toctree(
- parent: str | None, docname: str,
- ) -> Iterator[tuple[str | None, str]]:
- if parent == docname:
- logger.warning(__('self referenced toctree found. Ignored.'),
- location=docname, type='toc',
- subtype='circular')
- return
-
- # traverse toctree by pre-order
- yield parent, docname
- traversed.add(docname)
-
- for child in (self.toctree_includes.get(docname) or []):
- for subparent, subdocname in traverse_toctree(docname, child):
- if subdocname not in traversed:
- yield subparent, subdocname
- traversed.add(subdocname)
+ traversed: set[str] = set()
relations = {}
- docnames = traverse_toctree(None, self.config.root_doc)
- prevdoc = None
+ docnames = _traverse_toctree(
+ traversed, None, self.config.root_doc, self.toctree_includes,
+ )
+ prev_doc = None
parent, docname = next(docnames)
- for nextparent, nextdoc in docnames:
- relations[docname] = [parent, prevdoc, nextdoc]
- prevdoc = docname
- docname = nextdoc
- parent = nextparent
+ for next_parent, next_doc in docnames:
+ relations[docname] = [parent, prev_doc, next_doc]
+ prev_doc = docname
+ docname = next_doc
+ parent = next_parent
- relations[docname] = [parent, prevdoc, None]
+ relations[docname] = [parent, prev_doc, None]
return relations
@@ -750,3 +732,28 @@ def _last_modified_time(filename: str | os.PathLike[str]) -> int:
# upside-down floor division to get the ceiling
return -(os.stat(filename).st_mtime_ns // -1_000)
+
+
+def _traverse_toctree(
+ traversed: set[str],
+ parent: str | None,
+ docname: str,
+ toctree_includes: dict[str, list[str]],
+) -> Iterator[tuple[str | None, str]]:
+ if parent == docname:
+ logger.warning(__('self referenced toctree found. Ignored.'),
+ location=docname, type='toc',
+ subtype='circular')
+ return
+
+ # traverse toctree by pre-order
+ yield parent, docname
+ traversed.add(docname)
+
+ for child in toctree_includes.get(docname, ()):
+ for sub_parent, sub_docname in _traverse_toctree(
+ traversed, docname, child, toctree_includes,
+ ):
+ if sub_docname not in traversed:
+ yield sub_parent, sub_docname
+ traversed.add(sub_docname)
diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py
index edc085005ff..ef6361a186a 100644
--- a/sphinx/environment/adapters/toctree.py
+++ b/sphinx/environment/adapters/toctree.py
@@ -2,8 +2,7 @@
from __future__ import annotations
-from collections.abc import Iterable
-from typing import TYPE_CHECKING, Any, TypeVar, cast
+from typing import TYPE_CHECKING, Any, TypeVar
from docutils import nodes
from docutils.nodes import Element, Node
@@ -12,330 +11,501 @@
from sphinx.locale import __
from sphinx.util import logging, url_re
from sphinx.util.matching import Matcher
-from sphinx.util.nodes import clean_astext, process_only_nodes
+from sphinx.util.nodes import _only_node_keep_children, clean_astext
if TYPE_CHECKING:
+ from collections.abc import Iterable, Set
+
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
+ from sphinx.util.tags import Tags
logger = logging.getLogger(__name__)
+def note_toctree(env: BuildEnvironment, docname: str, toctreenode: addnodes.toctree) -> None:
+ """Note a TOC tree directive in a document and gather information about
+ file relations from it.
+ """
+ if toctreenode['glob']:
+ env.glob_toctrees.add(docname)
+ if toctreenode.get('numbered'):
+ env.numbered_toctrees.add(docname)
+ include_files = toctreenode['includefiles']
+ for include_file in include_files:
+ # note that if the included file is rebuilt, this one must be
+ # too (since the TOC of the included file could have changed)
+ env.files_to_rebuild.setdefault(include_file, set()).add(docname)
+ env.toctree_includes.setdefault(docname, []).extend(include_files)
+
+
+def document_toc(env: BuildEnvironment, docname: str, tags: Tags) -> Node:
+ """Get the (local) table of contents for a document.
+
+ Note that this is only the sections within the document.
+ For a ToC tree that shows the document's place in the
+ ToC structure, use `get_toctree_for`.
+ """
+
+ tocdepth = env.metadata[docname].get('tocdepth', 0)
+ try:
+ toc = _toctree_copy(env.tocs[docname], 2, tocdepth, False, tags)
+ except KeyError:
+ # the document does not exist any more:
+ # return a dummy node that renders to nothing
+ return nodes.paragraph()
+
+ for node in toc.findall(nodes.reference):
+ node['refuri'] = node['anchorname'] or '#'
+ return toc
+
+
+def global_toctree_for_doc(
+ env: BuildEnvironment,
+ docname: str,
+ builder: Builder,
+ maxdepth: int = 0,
+ titles_only: bool = False,
+ collapse: bool = False,
+ includehidden: bool = True,
+) -> Element | None:
+ """Get the global ToC tree at a given document.
+
+ This gives the global ToC, with all ancestors and their siblings.
+ """
+
+ toctrees: list[Element] = []
+ for toctree_node in env.master_doctree.findall(addnodes.toctree):
+ if toctree := _resolve_toctree(
+ env,
+ docname,
+ builder,
+ toctree_node,
+ prune=True,
+ maxdepth=int(maxdepth),
+ titles_only=titles_only,
+ collapse=collapse,
+ includehidden=includehidden,
+ ):
+ toctrees.append(toctree)
+ if not toctrees:
+ return None
+ result = toctrees[0]
+ for toctree in toctrees[1:]:
+ result.extend(toctree.children)
+ return result
+
+
+def _resolve_toctree(
+ env: BuildEnvironment, docname: str, builder: Builder, toctree: addnodes.toctree,
+ prune: bool = True, maxdepth: int = 0, titles_only: bool = False,
+ collapse: bool = False, includehidden: bool = False,
+) -> Element | None:
+ """Resolve a *toctree* node into individual bullet lists with titles
+ as items, returning None (if no containing titles are found) or
+ a new node.
+
+ If *prune* is True, the tree is pruned to *maxdepth*, or if that is 0,
+ to the value of the *maxdepth* option on the *toctree* node.
+ If *titles_only* is True, only toplevel document titles will be in the
+ resulting tree.
+ If *collapse* is True, all branches not containing docname will
+ be collapsed.
+ """
+
+ if toctree.get('hidden', False) and not includehidden:
+ return None
+
+ # For reading the following two helper function, it is useful to keep
+ # in mind the node structure of a toctree (using HTML-like node names
+ # for brevity):
+ #
+ #
+ # -
+ #
+ #
+ # ...
+ #
+ #
+ #
+ #
+ # The transformation is made in two passes in order to avoid
+ # interactions between marking and pruning the tree (see bug #1046).
+
+ toctree_ancestors = _get_toctree_ancestors(env.toctree_includes, docname)
+ included = Matcher(env.config.include_patterns)
+ excluded = Matcher(env.config.exclude_patterns)
+
+ maxdepth = maxdepth or toctree.get('maxdepth', -1)
+ if not titles_only and toctree.get('titlesonly', False):
+ titles_only = True
+ if not includehidden and toctree.get('includehidden', False):
+ includehidden = True
+
+ tocentries = _entries_from_toctree(
+ env,
+ prune,
+ titles_only,
+ collapse,
+ includehidden,
+ builder.tags,
+ toctree_ancestors,
+ included,
+ excluded,
+ toctree,
+ [],
+ )
+ if not tocentries:
+ return None
+
+ newnode = addnodes.compact_paragraph('', '')
+ if caption := toctree.attributes.get('caption'):
+ caption_node = nodes.title(caption, '', *[nodes.Text(caption)])
+ caption_node.line = toctree.line
+ caption_node.source = toctree.source
+ caption_node.rawsource = toctree['rawcaption']
+ if hasattr(toctree, 'uid'):
+ # move uid to caption_node to translate it
+ caption_node.uid = toctree.uid # type: ignore[attr-defined]
+ del toctree.uid
+ newnode.append(caption_node)
+ newnode.extend(tocentries)
+ newnode['toctree'] = True
+
+ # prune the tree to maxdepth, also set toc depth and current classes
+ _toctree_add_classes(newnode, 1, docname)
+ newnode = _toctree_copy(newnode, 1, maxdepth if prune else 0, collapse, builder.tags)
+
+ if isinstance(newnode[-1], nodes.Element) and len(newnode[-1]) == 0: # No titles found
+ return None
+
+ # set the target paths in the toctrees (they are not known at TOC
+ # generation time)
+ for refnode in newnode.findall(nodes.reference):
+ if url_re.match(refnode['refuri']) is None:
+ rel_uri = builder.get_relative_uri(docname, refnode['refuri'])
+ refnode['refuri'] = rel_uri + refnode['anchorname']
+ return newnode
+
+
+def _entries_from_toctree(
+ env: BuildEnvironment,
+ prune: bool,
+ titles_only: bool,
+ collapse: bool,
+ includehidden: bool,
+ tags: Tags,
+ toctree_ancestors: Set[str],
+ included: Matcher,
+ excluded: Matcher,
+ toctreenode: addnodes.toctree,
+ parents: list[str],
+ subtree: bool = False,
+) -> list[Element]:
+ """Return TOC entries for a toctree node."""
+ entries: list[Element] = []
+ for (title, ref) in toctreenode['entries']:
+ try:
+ toc, refdoc = _toctree_entry(
+ title, ref, env, prune, collapse, tags, toctree_ancestors,
+ included, excluded, toctreenode, parents,
+ )
+ except LookupError:
+ continue
+
+ # children of toc are:
+ # - list_item + compact_paragraph + (reference and subtoc)
+ # - only + subtoc
+ # - toctree
+ children: Iterable[nodes.Element] = toc.children # type: ignore[assignment]
+
+ # if titles_only is given, only keep the main title and
+ # sub-toctrees
+ if titles_only:
+ # delete everything but the toplevel title(s)
+ # and toctrees
+ for top_level in children:
+ # nodes with length 1 don't have any children anyway
+ if len(top_level) > 1:
+ if subtrees := list(top_level.findall(addnodes.toctree)):
+ top_level[1][:] = subtrees # type: ignore
+ else:
+ top_level.pop(1)
+ # resolve all sub-toctrees
+ for sub_toc_node in list(toc.findall(addnodes.toctree)):
+ if sub_toc_node.get('hidden', False) and not includehidden:
+ continue
+ for i, entry in enumerate(
+ _entries_from_toctree(
+ env,
+ prune,
+ titles_only,
+ collapse,
+ includehidden,
+ tags,
+ toctree_ancestors,
+ included,
+ excluded,
+ sub_toc_node,
+ [refdoc] + parents,
+ subtree=True,
+ ),
+ start=sub_toc_node.parent.index(sub_toc_node) + 1,
+ ):
+ sub_toc_node.parent.insert(i, entry)
+ sub_toc_node.parent.remove(sub_toc_node)
+
+ entries.extend(children)
+
+ if not subtree:
+ ret = nodes.bullet_list()
+ ret += entries
+ return [ret]
+
+ return entries
+
+
+def _toctree_entry(
+ title: str,
+ ref: str,
+ env: BuildEnvironment,
+ prune: bool,
+ collapse: bool,
+ tags: Tags,
+ toctree_ancestors: Set[str],
+ included: Matcher,
+ excluded: Matcher,
+ toctreenode: addnodes.toctree,
+ parents: list[str],
+) -> tuple[Element, str]:
+ from sphinx.domains.std import StandardDomain
+
+ try:
+ refdoc = ''
+ if url_re.match(ref):
+ toc = _toctree_url_entry(title, ref)
+ elif ref == 'self':
+ toc = _toctree_self_entry(title, toctreenode['parent'], env.titles)
+ elif ref in StandardDomain._virtual_doc_names:
+ toc = _toctree_generated_entry(title, ref)
+ else:
+ if ref in parents:
+ logger.warning(__('circular toctree references '
+ 'detected, ignoring: %s <- %s'),
+ ref, ' <- '.join(parents),
+ location=ref, type='toc', subtype='circular')
+ raise LookupError('circular reference')
+
+ toc, refdoc = _toctree_standard_entry(
+ title,
+ ref,
+ env.metadata[ref].get('tocdepth', 0),
+ env.tocs[ref],
+ toctree_ancestors,
+ prune,
+ collapse,
+ tags,
+ )
+
+ if not toc.children:
+ # empty toc means: no titles will show up in the toctree
+ logger.warning(__('toctree contains reference to document %r that '
+ "doesn't have a title: no link will be generated"),
+ ref, location=toctreenode)
+ except KeyError:
+ # this is raised if the included file does not exist
+ ref_path = env.doc2path(ref, False)
+ if excluded(ref_path):
+ message = __('toctree contains reference to excluded document %r')
+ elif not included(ref_path):
+ message = __('toctree contains reference to non-included document %r')
+ else:
+ message = __('toctree contains reference to nonexisting document %r')
+
+ logger.warning(message, ref, location=toctreenode)
+ raise
+ return toc, refdoc
+
+
+def _toctree_url_entry(title: str, ref: str) -> nodes.bullet_list:
+ if title is None:
+ title = ref
+ reference = nodes.reference('', '', internal=False,
+ refuri=ref, anchorname='',
+ *[nodes.Text(title)])
+ para = addnodes.compact_paragraph('', '', reference)
+ item = nodes.list_item('', para)
+ toc = nodes.bullet_list('', item)
+ return toc
+
+
+def _toctree_self_entry(
+ title: str, ref: str, titles: dict[str, nodes.title],
+) -> nodes.bullet_list:
+ # 'self' refers to the document from which this
+ # toctree originates
+ if not title:
+ title = clean_astext(titles[ref])
+ reference = nodes.reference('', '', internal=True,
+ refuri=ref,
+ anchorname='',
+ *[nodes.Text(title)])
+ para = addnodes.compact_paragraph('', '', reference)
+ item = nodes.list_item('', para)
+ # don't show subitems
+ toc = nodes.bullet_list('', item)
+ return toc
+
+
+def _toctree_generated_entry(title: str, ref: str, ) -> nodes.bullet_list:
+ from sphinx.domains.std import StandardDomain
+
+ docname, sectionname = StandardDomain._virtual_doc_names[ref]
+ if not title:
+ title = sectionname
+ reference = nodes.reference('', title, internal=True,
+ refuri=docname, anchorname='')
+ para = addnodes.compact_paragraph('', '', reference)
+ item = nodes.list_item('', para)
+ # don't show subitems
+ toc = nodes.bullet_list('', item)
+ return toc
+
+
+def _toctree_standard_entry(
+ title: str,
+ ref: str,
+ maxdepth: int,
+ toc: nodes.bullet_list,
+ toctree_ancestors: Set[str],
+ prune: bool,
+ collapse: bool,
+ tags: Tags,
+) -> tuple[nodes.bullet_list, str]:
+ refdoc = ref
+ if ref in toctree_ancestors and (not prune or maxdepth <= 0):
+ toc = toc.deepcopy()
+ else:
+ toc = _toctree_copy(toc, 2, maxdepth, collapse, tags)
+
+ if title and toc.children and len(toc.children) == 1:
+ child = toc.children[0]
+ for refnode in child.findall(nodes.reference):
+ if refnode['refuri'] == ref and not refnode['anchorname']:
+ refnode.children[:] = [nodes.Text(title)]
+ return toc, refdoc
+
+
+def _toctree_add_classes(node: Element, depth: int, docname: str) -> None:
+ """Add 'toctree-l%d' and 'current' classes to the toctree."""
+ for subnode in node.children:
+ if isinstance(subnode, (addnodes.compact_paragraph, nodes.list_item)):
+ # for and
, indicate the depth level and recurse
+ subnode['classes'].append(f'toctree-l{depth - 1}')
+ _toctree_add_classes(subnode, depth, docname)
+ elif isinstance(subnode, nodes.bullet_list):
+ # for , just recurse
+ _toctree_add_classes(subnode, depth + 1, docname)
+ elif isinstance(subnode, nodes.reference):
+ # for , identify which entries point to the current
+ # document and therefore may not be collapsed
+ if subnode['refuri'] == docname:
+ if not subnode['anchorname']:
+ # give the whole branch a 'current' class
+ # (useful for styling it differently)
+ branchnode: Element = subnode
+ while branchnode:
+ branchnode['classes'].append('current')
+ branchnode = branchnode.parent
+ # mark the list_item as "on current page"
+ if subnode.parent.parent.get('iscurrent'):
+ # but only if it's not already done
+ return
+ while subnode:
+ subnode['iscurrent'] = True
+ subnode = subnode.parent
+
+
+ET = TypeVar('ET', bound=Element)
+
+
+def _toctree_copy(node: ET, depth: int, maxdepth: int, collapse: bool, tags: Tags) -> ET:
+ """Utility: Cut and deep-copy a TOC at a specified depth."""
+ keep_bullet_list_sub_nodes = (depth <= 1
+ or ((depth <= maxdepth or maxdepth <= 0)
+ and (not collapse or 'iscurrent' in node)))
+
+ copy = node.copy()
+ for subnode in node.children:
+ if isinstance(subnode, (addnodes.compact_paragraph, nodes.list_item)):
+ # for and
- , just recurse
+ copy.append(_toctree_copy(subnode, depth, maxdepth, collapse, tags))
+ elif isinstance(subnode, nodes.bullet_list):
+ # for
, copy if the entry is top-level
+ # or, copy if the depth is within bounds and;
+ # collapsing is disabled or the sub-entry's parent is 'current'.
+ # The boolean is constant so is calculated outwith the loop.
+ if keep_bullet_list_sub_nodes:
+ copy.append(_toctree_copy(subnode, depth + 1, maxdepth, collapse, tags))
+ elif isinstance(subnode, addnodes.toctree):
+ # copy sub toctree nodes for later processing
+ copy.append(subnode.copy())
+ elif isinstance(subnode, addnodes.only):
+ # only keep children if the only node matches the tags
+ if _only_node_keep_children(subnode, tags):
+ for child in subnode.children:
+ copy.append(_toctree_copy(
+ child, depth, maxdepth, collapse, tags, # type: ignore[type-var]
+ ))
+ elif isinstance(subnode, (nodes.reference, nodes.title)):
+ # deep copy references and captions
+ sub_node_copy = subnode.copy()
+ sub_node_copy.children = [child.deepcopy() for child in subnode.children]
+ for child in sub_node_copy.children:
+ child.parent = sub_node_copy
+ copy.append(sub_node_copy)
+ else:
+ raise ValueError(f"Unexpected node type {subnode.__class__.__name__!r}!")
+ return copy
+
+
+def _get_toctree_ancestors(
+ toctree_includes: dict[str, list[str]], docname: str,
+) -> Set[str]:
+ parent: dict[str, str] = {}
+ for p, children in toctree_includes.items():
+ parent |= dict.fromkeys(children, p)
+ ancestors: list[str] = []
+ d = docname
+ while d in parent and d not in ancestors:
+ ancestors.append(d)
+ d = parent[d]
+ # use dict keys for ordered set operations
+ return dict.fromkeys(ancestors).keys()
+
+
class TocTree:
def __init__(self, env: BuildEnvironment) -> None:
self.env = env
def note(self, docname: str, toctreenode: addnodes.toctree) -> None:
- """Note a TOC tree directive in a document and gather information about
- file relations from it.
- """
- if toctreenode['glob']:
- self.env.glob_toctrees.add(docname)
- if toctreenode.get('numbered'):
- self.env.numbered_toctrees.add(docname)
- includefiles = toctreenode['includefiles']
- for includefile in includefiles:
- # note that if the included file is rebuilt, this one must be
- # too (since the TOC of the included file could have changed)
- self.env.files_to_rebuild.setdefault(includefile, set()).add(docname)
- self.env.toctree_includes.setdefault(docname, []).extend(includefiles)
+ note_toctree(self.env, docname, toctreenode)
def resolve(self, docname: str, builder: Builder, toctree: addnodes.toctree,
prune: bool = True, maxdepth: int = 0, titles_only: bool = False,
collapse: bool = False, includehidden: bool = False) -> Element | None:
- """Resolve a *toctree* node into individual bullet lists with titles
- as items, returning None (if no containing titles are found) or
- a new node.
-
- If *prune* is True, the tree is pruned to *maxdepth*, or if that is 0,
- to the value of the *maxdepth* option on the *toctree* node.
- If *titles_only* is True, only toplevel document titles will be in the
- resulting tree.
- If *collapse* is True, all branches not containing docname will
- be collapsed.
- """
- if toctree.get('hidden', False) and not includehidden:
- return None
- generated_docnames: dict[str, tuple[str, str]] = self.env.domains['std']._virtual_doc_names.copy() # NoQA: E501
-
- # For reading the following two helper function, it is useful to keep
- # in mind the node structure of a toctree (using HTML-like node names
- # for brevity):
- #
- #
- # -
- #
- #
- # ...
- #
- #
- #
- #
- # The transformation is made in two passes in order to avoid
- # interactions between marking and pruning the tree (see bug #1046).
-
- toctree_ancestors = self.get_toctree_ancestors(docname)
- included = Matcher(self.env.config.include_patterns)
- excluded = Matcher(self.env.config.exclude_patterns)
-
- def _toctree_add_classes(node: Element, depth: int) -> None:
- """Add 'toctree-l%d' and 'current' classes to the toctree."""
- for subnode in node.children:
- if isinstance(subnode, (addnodes.compact_paragraph,
- nodes.list_item)):
- # for and
- , indicate the depth level and recurse
- subnode['classes'].append(f'toctree-l{depth - 1}')
- _toctree_add_classes(subnode, depth)
- elif isinstance(subnode, nodes.bullet_list):
- # for
, just recurse
- _toctree_add_classes(subnode, depth + 1)
- elif isinstance(subnode, nodes.reference):
- # for , identify which entries point to the current
- # document and therefore may not be collapsed
- if subnode['refuri'] == docname:
- if not subnode['anchorname']:
- # give the whole branch a 'current' class
- # (useful for styling it differently)
- branchnode: Element = subnode
- while branchnode:
- branchnode['classes'].append('current')
- branchnode = branchnode.parent
- # mark the list_item as "on current page"
- if subnode.parent.parent.get('iscurrent'):
- # but only if it's not already done
- return
- while subnode:
- subnode['iscurrent'] = True
- subnode = subnode.parent
-
- def _entries_from_toctree(toctreenode: addnodes.toctree, parents: list[str],
- subtree: bool = False) -> list[Element]:
- """Return TOC entries for a toctree node."""
- refs = [(e[0], e[1]) for e in toctreenode['entries']]
- entries: list[Element] = []
- for (title, ref) in refs:
- try:
- refdoc = None
- if url_re.match(ref):
- if title is None:
- title = ref
- reference = nodes.reference('', '', internal=False,
- refuri=ref, anchorname='',
- *[nodes.Text(title)])
- para = addnodes.compact_paragraph('', '', reference)
- item = nodes.list_item('', para)
- toc = nodes.bullet_list('', item)
- elif ref == 'self':
- # 'self' refers to the document from which this
- # toctree originates
- ref = toctreenode['parent']
- if not title:
- title = clean_astext(self.env.titles[ref])
- reference = nodes.reference('', '', internal=True,
- refuri=ref,
- anchorname='',
- *[nodes.Text(title)])
- para = addnodes.compact_paragraph('', '', reference)
- item = nodes.list_item('', para)
- # don't show subitems
- toc = nodes.bullet_list('', item)
- elif ref in generated_docnames:
- docname, sectionname = generated_docnames[ref]
- if not title:
- title = sectionname
- reference = nodes.reference('', title, internal=True,
- refuri=docname, anchorname='')
- para = addnodes.compact_paragraph('', '', reference)
- item = nodes.list_item('', para)
- # don't show subitems
- toc = nodes.bullet_list('', item)
- else:
- if ref in parents:
- logger.warning(__('circular toctree references '
- 'detected, ignoring: %s <- %s'),
- ref, ' <- '.join(parents),
- location=ref, type='toc', subtype='circular')
- continue
- refdoc = ref
- maxdepth = self.env.metadata[ref].get('tocdepth', 0)
- toc = self.env.tocs[ref]
- if ref not in toctree_ancestors or (prune and maxdepth > 0):
- toc = self._toctree_copy(toc, 2, maxdepth, collapse)
- else:
- toc = toc.deepcopy()
- process_only_nodes(toc, builder.tags)
- if title and toc.children and len(toc.children) == 1:
- child = toc.children[0]
- for refnode in child.findall(nodes.reference):
- if refnode['refuri'] == ref and \
- not refnode['anchorname']:
- refnode.children = [nodes.Text(title)]
- if not toc.children:
- # empty toc means: no titles will show up in the toctree
- logger.warning(__('toctree contains reference to document %r that '
- "doesn't have a title: no link will be generated"),
- ref, location=toctreenode)
- except KeyError:
- # this is raised if the included file does not exist
- if excluded(self.env.doc2path(ref, False)):
- message = __('toctree contains reference to excluded document %r')
- elif not included(self.env.doc2path(ref, False)):
- message = __('toctree contains reference to non-included document %r')
- else:
- message = __('toctree contains reference to nonexisting document %r')
-
- logger.warning(message, ref, location=toctreenode)
- else:
- # children of toc are:
- # - list_item + compact_paragraph + (reference and subtoc)
- # - only + subtoc
- # - toctree
- children = cast(Iterable[nodes.Element], toc)
-
- # if titles_only is given, only keep the main title and
- # sub-toctrees
- if titles_only:
- # delete everything but the toplevel title(s)
- # and toctrees
- for toplevel in children:
- # nodes with length 1 don't have any children anyway
- if len(toplevel) > 1:
- subtrees = list(toplevel.findall(addnodes.toctree))
- if subtrees:
- toplevel[1][:] = subtrees # type: ignore
- else:
- toplevel.pop(1)
- # resolve all sub-toctrees
- for sub_toc_node in list(toc.findall(addnodes.toctree)):
- if sub_toc_node.get('hidden', False) and not includehidden:
- continue
- for i, entry in enumerate(
- _entries_from_toctree(sub_toc_node, [refdoc or ''] + parents,
- subtree=True),
- start=sub_toc_node.parent.index(sub_toc_node) + 1,
- ):
- sub_toc_node.parent.insert(i, entry)
- sub_toc_node.parent.remove(sub_toc_node)
-
- entries.extend(children)
- if not subtree:
- ret = nodes.bullet_list()
- ret += entries
- return [ret]
- return entries
-
- maxdepth = maxdepth or toctree.get('maxdepth', -1)
- if not titles_only and toctree.get('titlesonly', False):
- titles_only = True
- if not includehidden and toctree.get('includehidden', False):
- includehidden = True
-
- tocentries = _entries_from_toctree(toctree, [])
- if not tocentries:
- return None
-
- newnode = addnodes.compact_paragraph('', '')
- caption = toctree.attributes.get('caption')
- if caption:
- caption_node = nodes.title(caption, '', *[nodes.Text(caption)])
- caption_node.line = toctree.line
- caption_node.source = toctree.source
- caption_node.rawsource = toctree['rawcaption']
- if hasattr(toctree, 'uid'):
- # move uid to caption_node to translate it
- caption_node.uid = toctree.uid # type: ignore
- del toctree.uid
- newnode += caption_node
- newnode.extend(tocentries)
- newnode['toctree'] = True
-
- # prune the tree to maxdepth, also set toc depth and current classes
- _toctree_add_classes(newnode, 1)
- newnode = self._toctree_copy(newnode, 1, maxdepth if prune else 0, collapse)
-
- if isinstance(newnode[-1], nodes.Element) and len(newnode[-1]) == 0: # No titles found
- return None
-
- # set the target paths in the toctrees (they are not known at TOC
- # generation time)
- for refnode in newnode.findall(nodes.reference):
- if not url_re.match(refnode['refuri']):
- refnode['refuri'] = builder.get_relative_uri(
- docname, refnode['refuri']) + refnode['anchorname']
- return newnode
-
- def get_toctree_ancestors(self, docname: str) -> list[str]:
- parent = {}
- for p, children in self.env.toctree_includes.items():
- for child in children:
- parent[child] = p
- ancestors: list[str] = []
- d = docname
- while d in parent and d not in ancestors:
- ancestors.append(d)
- d = parent[d]
- return ancestors
-
- ET = TypeVar('ET', bound=Element)
-
- def _toctree_copy(self, node: ET, depth: int, maxdepth: int, collapse: bool) -> ET:
- """Utility: Cut and deep-copy a TOC at a specified depth."""
- keep_bullet_list_sub_nodes = (depth <= 1
- or ((depth <= maxdepth or maxdepth <= 0)
- and (not collapse or 'iscurrent' in node)))
-
- copy = node.copy()
- for subnode in node.children:
- if isinstance(subnode, (addnodes.compact_paragraph, nodes.list_item)):
- # for and
- , just recurse
- copy.append(self._toctree_copy(subnode, depth, maxdepth, collapse))
- elif isinstance(subnode, nodes.bullet_list):
- # for
, copy if the entry is top-level
- # or, copy if the depth is within bounds and;
- # collapsing is disabled or the sub-entry's parent is 'current'.
- # The boolean is constant so is calculated outwith the loop.
- if keep_bullet_list_sub_nodes:
- copy.append(self._toctree_copy(subnode, depth + 1, maxdepth, collapse))
- else:
- copy.append(subnode.deepcopy())
- return copy
+ return _resolve_toctree(
+ self.env, docname, builder, toctree, prune,
+ maxdepth, titles_only, collapse, includehidden,
+ )
def get_toc_for(self, docname: str, builder: Builder) -> Node:
- """Return a TOC nodetree -- for use on the same page only!"""
- tocdepth = self.env.metadata[docname].get('tocdepth', 0)
- try:
- toc = self._toctree_copy(self.env.tocs[docname], 2, tocdepth, False)
- except KeyError:
- # the document does not exist anymore: return a dummy node that
- # renders to nothing
- return nodes.paragraph()
- process_only_nodes(toc, builder.tags)
- for node in toc.findall(nodes.reference):
- node['refuri'] = node['anchorname'] or '#'
- return toc
-
- def get_toctree_for(self, docname: str, builder: Builder, collapse: bool,
- **kwargs: Any) -> Element | None:
- """Return the global TOC nodetree."""
- doctree = self.env.master_doctree
- toctrees: list[Element] = []
- if 'includehidden' not in kwargs:
- kwargs['includehidden'] = True
- if 'maxdepth' not in kwargs or not kwargs['maxdepth']:
- kwargs['maxdepth'] = 0
- else:
- kwargs['maxdepth'] = int(kwargs['maxdepth'])
- kwargs['collapse'] = collapse
- for toctreenode in doctree.findall(addnodes.toctree):
- toctree = self.resolve(docname, builder, toctreenode, prune=True, **kwargs)
- if toctree:
- toctrees.append(toctree)
- if not toctrees:
- return None
- result = toctrees[0]
- for toctree in toctrees[1:]:
- result.extend(toctree.children)
- return result
+ return document_toc(self.env, docname, self.env.app.builder.tags)
+
+ def get_toctree_for(
+ self, docname: str, builder: Builder, collapse: bool, **kwargs: Any,
+ ) -> Element | None:
+ return global_toctree_for_doc(self.env, docname, builder, collapse=collapse, **kwargs)
diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py
index b68cd10ba33..2e42bbb7dfc 100644
--- a/sphinx/environment/collectors/toctree.py
+++ b/sphinx/environment/collectors/toctree.py
@@ -10,7 +10,7 @@
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
-from sphinx.environment.adapters.toctree import TocTree
+from sphinx.environment.adapters.toctree import note_toctree
from sphinx.environment.collectors import EnvironmentCollector
from sphinx.locale import __
from sphinx.transforms import SphinxContentsFilter
@@ -108,7 +108,7 @@ def build_toc(
item = toctreenode.copy()
entries.append(item)
# important: do the inventory stuff
- TocTree(app.env).note(docname, toctreenode)
+ note_toctree(app.env, docname, toctreenode)
# add object signatures within a section to the ToC
elif isinstance(toctreenode, addnodes.desc):
for sig_node in toctreenode:
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py
index 105ecb38ccc..9485ce1eb2a 100644
--- a/sphinx/util/__init__.py
+++ b/sphinx/util/__init__.py
@@ -51,8 +51,7 @@
# High-level utility functions.
def docname_join(basedocname: str, docname: str) -> str:
- return posixpath.normpath(
- posixpath.join('/' + basedocname, '..', docname))[1:]
+ return posixpath.normpath(posixpath.join('/' + basedocname, '..', docname))[1:]
def get_filetype(source_suffix: dict[str, str], filename: str) -> str:
diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py
index 8aff570d65e..e2c4ae28c5d 100644
--- a/sphinx/util/nodes.py
+++ b/sphinx/util/nodes.py
@@ -603,33 +603,65 @@ def is_smartquotable(node: Node) -> bool:
def process_only_nodes(document: Node, tags: Tags) -> None:
"""Filter ``only`` nodes which do not match *tags*."""
for node in document.findall(addnodes.only):
- try:
- ret = tags.eval_condition(node['expr'])
- except Exception as err:
- logger.warning(__('exception while evaluating only directive expression: %s'), err,
- location=node)
+ if _only_node_keep_children(node, tags):
node.replace_self(node.children or nodes.comment())
else:
- if ret:
- node.replace_self(node.children or nodes.comment())
- else:
- # A comment on the comment() nodes being inserted: replacing by [] would
- # result in a "Losing ids" exception if there is a target node before
- # the only node, so we make sure docutils can transfer the id to
- # something, even if it's just a comment and will lose the id anyway...
- node.replace_self(nodes.comment())
+ # A comment on the comment() nodes being inserted: replacing by [] would
+ # result in a "Losing ids" exception if there is a target node before
+ # the only node, so we make sure docutils can transfer the id to
+ # something, even if it's just a comment and will lose the id anyway...
+ node.replace_self(nodes.comment())
+
+
+def _only_node_keep_children(node: addnodes.only, tags: Tags) -> bool:
+ """Keep children if tags match or error."""
+ try:
+ return tags.eval_condition(node['expr'])
+ except Exception as err:
+ logger.warning(
+ __('exception while evaluating only directive expression: %s'),
+ err,
+ location=node)
+ return True
-def _copy_except__document(self: Element) -> Element:
+def _copy_except__document(el: Element) -> Element:
"""Monkey-patch ```nodes.Element.copy``` to not copy the ``_document``
attribute.
xref: https://github.com/sphinx-doc/sphinx/issues/11116#issuecomment-1376767086
"""
- newnode = self.__class__(rawsource=self.rawsource, **self.attributes)
- newnode.source = self.source
- newnode.line = self.line
+ newnode = object.__new__(el.__class__)
+ # set in Element.__init__()
+ newnode.children = []
+ newnode.rawsource = el.rawsource
+ newnode.tagname = el.tagname
+ # copied in Element.copy()
+ newnode.attributes = {k: (v
+ if k not in {'ids', 'classes', 'names', 'dupnames', 'backrefs'}
+ else v[:])
+ for k, v in el.attributes.items()}
+ newnode.line = el.line
+ newnode.source = el.source
return newnode
nodes.Element.copy = _copy_except__document # type: ignore
+
+
+def _deepcopy(el: Element) -> Element:
+ """Monkey-patch ```nodes.Element.deepcopy``` for speed."""
+ newnode = el.copy()
+ newnode.children = [child.deepcopy() for child in el.children]
+ for child in newnode.children:
+ child.parent = newnode
+ if el.document:
+ child.document = el.document
+ if child.source is None:
+ child.source = el.document.current_source
+ if child.line is None:
+ child.line = el.document.current_line
+ return newnode
+
+
+nodes.Element.deepcopy = _deepcopy # type: ignore
diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py
index 9b0047bedd3..5123715f540 100644
--- a/tests/test_environment_toctree.py
+++ b/tests/test_environment_toctree.py
@@ -2,12 +2,12 @@
import pytest
from docutils import nodes
-from docutils.nodes import bullet_list, comment, list_item, literal, reference, title
+from docutils.nodes import bullet_list, list_item, literal, reference, title
from sphinx import addnodes
from sphinx.addnodes import compact_paragraph, only
from sphinx.builders.html import StandaloneHTMLBuilder
-from sphinx.environment.adapters.toctree import TocTree
+from sphinx.environment.adapters.toctree import document_toc, global_toctree_for_doc
from sphinx.testing.util import assert_node
@@ -128,15 +128,12 @@ def test_glob(app):
@pytest.mark.sphinx('dummy', testroot='toctree-domain-objects')
def test_domain_objects(app):
- includefiles = ['domains']
-
app.build()
assert app.env.toc_num_entries['index'] == 0
assert app.env.toc_num_entries['domains'] == 9
- assert app.env.toctree_includes['index'] == includefiles
- for file in includefiles:
- assert 'index' in app.env.files_to_rebuild[file]
+ assert app.env.toctree_includes['index'] == ['domains']
+ assert 'index' in app.env.files_to_rebuild['domains']
assert app.env.glob_toctrees == set()
assert app.env.numbered_toctrees == {'index'}
@@ -166,22 +163,21 @@ def test_domain_objects(app):
@pytest.mark.sphinx('xml', testroot='toctree')
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
-def test_get_toc_for(app):
+def test_document_toc(app):
app.build()
- toctree = TocTree(app.env).get_toc_for('index', app.builder)
+ toctree = document_toc(app.env, 'index', app.builder.tags)
assert_node(toctree,
[bullet_list, ([list_item, (compact_paragraph, # [0][0]
[bullet_list, (addnodes.toctree, # [0][1][0]
- comment, # [0][1][1]
- list_item)])], # [0][1][2]
+ list_item)])], # [0][1][1]
[list_item, (compact_paragraph, # [1][0]
[bullet_list, (addnodes.toctree,
addnodes.toctree)])],
[list_item, compact_paragraph])]) # [2][0]
assert_node(toctree[0][0],
[compact_paragraph, reference, "Welcome to Sphinx Tests’s documentation!"])
- assert_node(toctree[0][1][2],
+ assert_node(toctree[0][1][1],
([compact_paragraph, reference, "subsection"],
[bullet_list, list_item, compact_paragraph, reference, "subsubsection"]))
assert_node(toctree[1][0],
@@ -192,10 +188,10 @@ def test_get_toc_for(app):
@pytest.mark.sphinx('xml', testroot='toctree')
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
-def test_get_toc_for_only(app):
+def test_document_toc_only(app):
app.build()
builder = StandaloneHTMLBuilder(app, app.env)
- toctree = TocTree(app.env).get_toc_for('index', builder)
+ toctree = document_toc(app.env, 'index', builder.tags)
assert_node(toctree,
[bullet_list, ([list_item, (compact_paragraph, # [0][0]
@@ -222,9 +218,9 @@ def test_get_toc_for_only(app):
@pytest.mark.sphinx('xml', testroot='toctree')
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
-def test_get_toc_for_tocdepth(app):
+def test_document_toc_tocdepth(app):
app.build()
- toctree = TocTree(app.env).get_toc_for('tocdepth', app.builder)
+ toctree = document_toc(app.env, 'tocdepth', app.builder.tags)
assert_node(toctree,
[bullet_list, list_item, (compact_paragraph, # [0][0]
@@ -237,9 +233,9 @@ def test_get_toc_for_tocdepth(app):
@pytest.mark.sphinx('xml', testroot='toctree')
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
-def test_get_toctree_for(app):
+def test_global_toctree_for_doc(app):
app.build()
- toctree = TocTree(app.env).get_toctree_for('index', app.builder, collapse=False)
+ toctree = global_toctree_for_doc(app.env, 'index', app.builder, collapse=False)
assert_node(toctree,
[compact_paragraph, ([title, "Table of Contents"],
bullet_list,
@@ -277,9 +273,9 @@ def test_get_toctree_for(app):
@pytest.mark.sphinx('xml', testroot='toctree')
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
-def test_get_toctree_for_collapse(app):
+def test_global_toctree_for_doc_collapse(app):
app.build()
- toctree = TocTree(app.env).get_toctree_for('index', app.builder, collapse=True)
+ toctree = global_toctree_for_doc(app.env, 'index', app.builder, collapse=True)
assert_node(toctree,
[compact_paragraph, ([title, "Table of Contents"],
bullet_list,
@@ -308,10 +304,10 @@ def test_get_toctree_for_collapse(app):
@pytest.mark.sphinx('xml', testroot='toctree')
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
-def test_get_toctree_for_maxdepth(app):
+def test_global_toctree_for_doc_maxdepth(app):
app.build()
- toctree = TocTree(app.env).get_toctree_for('index', app.builder,
- collapse=False, maxdepth=3)
+ toctree = global_toctree_for_doc(app.env, 'index', app.builder,
+ collapse=False, maxdepth=3)
assert_node(toctree,
[compact_paragraph, ([title, "Table of Contents"],
bullet_list,
@@ -354,10 +350,10 @@ def test_get_toctree_for_maxdepth(app):
@pytest.mark.sphinx('xml', testroot='toctree')
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
-def test_get_toctree_for_includehidden(app):
+def test_global_toctree_for_doc_includehidden(app):
app.build()
- toctree = TocTree(app.env).get_toctree_for('index', app.builder, collapse=False,
- includehidden=False)
+ toctree = global_toctree_for_doc(app.env, 'index', app.builder,
+ collapse=False, includehidden=False)
assert_node(toctree,
[compact_paragraph, ([title, "Table of Contents"],
bullet_list,