diff options
-rw-r--r-- | sphinx/environment/managers/toctree.py | 580 |
1 files changed, 0 insertions, 580 deletions
diff --git a/sphinx/environment/managers/toctree.py b/sphinx/environment/managers/toctree.py deleted file mode 100644 index 26c8f385d..000000000 --- a/sphinx/environment/managers/toctree.py +++ /dev/null @@ -1,580 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sphinx.environment.managers.toctree - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Toctree manager for sphinx.environment. - - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -from six import iteritems - -from docutils import nodes - -from sphinx import addnodes -from sphinx.util import url_re -from sphinx.util.nodes import clean_astext, process_only_nodes -from sphinx.transforms import SphinxContentsFilter -from sphinx.environment.managers import EnvironmentManager - -if False: - # For type annotation - from typing import Any, Tuple # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA - - -class Toctree(EnvironmentManager): - name = 'toctree' - - def __init__(self, env): - # type: (BuildEnvironment) -> None - super(Toctree, self).__init__(env) - - self.tocs = env.tocs - self.toc_num_entries = env.toc_num_entries - self.toc_secnumbers = env.toc_secnumbers - self.toc_fignumbers = env.toc_fignumbers - self.toctree_includes = env.toctree_includes - self.files_to_rebuild = env.files_to_rebuild - self.glob_toctrees = env.glob_toctrees - self.numbered_toctrees = env.numbered_toctrees - - def clear_doc(self, docname): - # type: (unicode) -> None - self.tocs.pop(docname, None) - self.toc_secnumbers.pop(docname, None) - self.toc_fignumbers.pop(docname, None) - self.toc_num_entries.pop(docname, None) - self.toctree_includes.pop(docname, None) - self.glob_toctrees.discard(docname) - self.numbered_toctrees.discard(docname) - - for subfn, fnset in list(self.files_to_rebuild.items()): - fnset.discard(docname) - if not fnset: - del self.files_to_rebuild[subfn] - - def merge_other(self, docnames, other): - # type: (List[unicode], BuildEnvironment) -> None - for docname in docnames: - self.tocs[docname] = other.tocs[docname] - self.toc_num_entries[docname] = other.toc_num_entries[docname] - if docname in other.toctree_includes: - self.toctree_includes[docname] = other.toctree_includes[docname] - if docname in other.glob_toctrees: - self.glob_toctrees.add(docname) - if docname in other.numbered_toctrees: - self.numbered_toctrees.add(docname) - - for subfn, fnset in other.files_to_rebuild.items(): - self.files_to_rebuild.setdefault(subfn, set()).update(fnset & docnames) - - def process_doc(self, docname, doctree): - # type: (unicode, nodes.Node) -> None - """Build a TOC from the doctree and store it in the inventory.""" - numentries = [0] # nonlocal again... - - def traverse_in_section(node, cls): - """Like traverse(), but stay within the same section.""" - result = [] - if isinstance(node, cls): - result.append(node) - for child in node.children: - if isinstance(child, nodes.section): - continue - result.extend(traverse_in_section(child, cls)) - return result - - def build_toc(node, depth=1): - entries = [] - for sectionnode in node: - # find all toctree nodes in this section and add them - # to the toc (just copying the toctree node which is then - # resolved in self.get_and_resolve_doctree) - if isinstance(sectionnode, addnodes.only): - onlynode = addnodes.only(expr=sectionnode['expr']) - blist = build_toc(sectionnode, depth) - if blist: - onlynode += blist.children - entries.append(onlynode) - continue - if not isinstance(sectionnode, nodes.section): - for toctreenode in traverse_in_section(sectionnode, - addnodes.toctree): - item = toctreenode.copy() - entries.append(item) - # important: do the inventory stuff - self.note_toctree(docname, toctreenode) - continue - title = sectionnode[0] - # copy the contents of the section title, but without references - # and unnecessary stuff - visitor = SphinxContentsFilter(doctree) - title.walkabout(visitor) - nodetext = visitor.get_entry_text() - if not numentries[0]: - # for the very first toc entry, don't add an anchor - # as it is the file's title anyway - anchorname = '' - else: - anchorname = '#' + sectionnode['ids'][0] - numentries[0] += 1 - # make these nodes: - # list_item -> compact_paragraph -> reference - reference = nodes.reference( - '', '', internal=True, refuri=docname, - anchorname=anchorname, *nodetext) - para = addnodes.compact_paragraph('', '', reference) - item = nodes.list_item('', para) - sub_item = build_toc(sectionnode, depth + 1) - item += sub_item - entries.append(item) - if entries: - return nodes.bullet_list('', *entries) - return [] - toc = build_toc(doctree) - if toc: - self.tocs[docname] = toc - else: - self.tocs[docname] = nodes.bullet_list('') - self.toc_num_entries[docname] = numentries[0] - - def note_toctree(self, docname, toctreenode): - # type: (unicode, addnodes.toctree) -> None - """Note a TOC tree directive in a document and gather information about - file relations from it. - """ - if toctreenode['glob']: - self.glob_toctrees.add(docname) - if toctreenode.get('numbered'): - self.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.files_to_rebuild.setdefault(includefile, set()).add(docname) - self.toctree_includes.setdefault(docname, []).extend(includefiles) - - def get_toc_for(self, docname, builder): - # type: (unicode, Builder) -> None - """Return a TOC nodetree -- for use on the same page only!""" - tocdepth = self.env.metadata[docname].get('tocdepth', 0) - try: - toc = self.tocs[docname].deepcopy() - self._toctree_prune(toc, 2, tocdepth) - 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, warn_node=self.env.warn_node) - for node in toc.traverse(nodes.reference): - node['refuri'] = node['anchorname'] or '#' - return toc - - def get_toctree_for(self, docname, builder, collapse, **kwds): - # type: (unicode, Builder, bool, Any) -> nodes.Node - """Return the global TOC nodetree.""" - doctree = self.env.get_doctree(self.env.config.master_doc) - toctrees = [] - if 'includehidden' not in kwds: - kwds['includehidden'] = True - if 'maxdepth' not in kwds: - kwds['maxdepth'] = 0 - kwds['collapse'] = collapse - for toctreenode in doctree.traverse(addnodes.toctree): - toctree = self.env.resolve_toctree(docname, builder, toctreenode, - prune=True, **kwds) - if toctree: - 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(self, docname, builder, toctree, prune=True, maxdepth=0, - titles_only=False, collapse=False, includehidden=False): - # type: (unicode, Builder, addnodes.toctree, bool, int, bool, bool, bool) -> nodes.Node - """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): - # - # <ul> - # <li> - # <p><a></p> - # <p><a></p> - # ... - # <ul> - # ... - # </ul> - # </li> - # </ul> - # - # 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) - - def _toctree_add_classes(node, depth): - """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 <p> and <li>, indicate the depth level and recurse - subnode['classes'].append('toctree-l%d' % (depth-1)) - _toctree_add_classes(subnode, depth) - elif isinstance(subnode, nodes.bullet_list): - # for <ul>, just recurse - _toctree_add_classes(subnode, depth+1) - elif isinstance(subnode, nodes.reference): - # for <a>, 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 = 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, parents, - separate=False, subtree=False): - """Return TOC entries for a toctree node.""" - refs = [(e[0], e[1]) for e in toctreenode['entries']] - entries = [] - 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.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) - else: - if ref in parents: - self.env.warn(ref, 'circular toctree references ' - 'detected, ignoring: %s <- %s' % - (ref, ' <- '.join(parents))) - continue - refdoc = ref - toc = self.tocs[ref].deepcopy() - maxdepth = self.env.metadata[ref].get('tocdepth', 0) - if ref not in toctree_ancestors or (prune and maxdepth > 0): - self._toctree_prune(toc, 2, maxdepth, collapse) - process_only_nodes(toc, builder.tags, warn_node=self.env.warn_node) - if title and toc.children and len(toc.children) == 1: - child = toc.children[0] - for refnode in child.traverse(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 - self.env.warn_node( - 'toctree contains reference to document %r that ' - 'doesn\'t have a title: no link will be generated' - % ref, toctreenode) - except KeyError: - # this is raised if the included file does not exist - self.env.warn_node( - 'toctree contains reference to nonexisting document %r' - % ref, toctreenode) - else: - # 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 toc: - # nodes with length 1 don't have any children anyway - if len(toplevel) > 1: - subtrees = toplevel.traverse(addnodes.toctree) - if subtrees: - toplevel[1][:] = subtrees - else: - toplevel.pop(1) - # resolve all sub-toctrees - for subtocnode in toc.traverse(addnodes.toctree): - if not (subtocnode.get('hidden', False) and - not includehidden): - i = subtocnode.parent.index(subtocnode) + 1 - for item in _entries_from_toctree( - subtocnode, [refdoc] + parents, - subtree=True): - subtocnode.parent.insert(i, item) - i += 1 - subtocnode.parent.remove(subtocnode) - if separate: - entries.append(toc) - else: - entries.extend(toc.children) - if not subtree and not separate: - 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 - - # NOTE: previously, this was separate=True, but that leads to artificial - # separation when two or more toctree entries form a logical unit, so - # separating mode is no longer used -- it's kept here for history's sake - tocentries = _entries_from_toctree(toctree, [], separate=False) - if not tocentries: - return None - - newnode = addnodes.compact_paragraph('', '') - caption = toctree.attributes.get('caption') - if caption: - caption_node = nodes.caption(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 - 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) - self._toctree_prune(newnode, 1, prune and maxdepth or 0, collapse) - - if 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.traverse(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): - # type: (unicode) -> List[unicode] - parent = {} - for p, children in iteritems(self.toctree_includes): - for child in children: - parent[child] = p - ancestors = [] # type: List[unicode] - d = docname - while d in parent and d not in ancestors: - ancestors.append(d) - d = parent[d] - return ancestors - - def _toctree_prune(self, node, depth, maxdepth, collapse=False): - # type: (nodes.Node, int, int, bool) -> None - """Utility: Cut a TOC at a specified depth.""" - for subnode in node.children[:]: - if isinstance(subnode, (addnodes.compact_paragraph, - nodes.list_item)): - # for <p> and <li>, just recurse - self._toctree_prune(subnode, depth, maxdepth, collapse) - elif isinstance(subnode, nodes.bullet_list): - # for <ul>, determine if the depth is too large or if the - # entry is to be collapsed - if maxdepth > 0 and depth > maxdepth: - subnode.parent.replace(subnode, []) - else: - # cull sub-entries whose parents aren't 'current' - if (collapse and depth > 1 and - 'iscurrent' not in subnode.parent): - subnode.parent.remove(subnode) - else: - # recurse on visible children - self._toctree_prune(subnode, depth+1, maxdepth, collapse) - - def assign_section_numbers(self): - # type: () -> List[unicode] - """Assign a section number to each heading under a numbered toctree.""" - # a list of all docnames whose section numbers changed - rewrite_needed = [] - - assigned = set() # type: Set[unicode] - old_secnumbers = self.toc_secnumbers - self.toc_secnumbers = self.env.toc_secnumbers = {} - - def _walk_toc(node, secnums, depth, titlenode=None): - # titlenode is the title of the document, it will get assigned a - # secnumber too, so that it shows up in next/prev/parent rellinks - for subnode in node.children: - if isinstance(subnode, nodes.bullet_list): - numstack.append(0) - _walk_toc(subnode, secnums, depth-1, titlenode) - numstack.pop() - titlenode = None - elif isinstance(subnode, nodes.list_item): - _walk_toc(subnode, secnums, depth, titlenode) - titlenode = None - elif isinstance(subnode, addnodes.only): - # at this stage we don't know yet which sections are going - # to be included; just include all of them, even if it leads - # to gaps in the numbering - _walk_toc(subnode, secnums, depth, titlenode) - titlenode = None - elif isinstance(subnode, addnodes.compact_paragraph): - numstack[-1] += 1 - if depth > 0: - number = tuple(numstack) - else: - number = None - secnums[subnode[0]['anchorname']] = \ - subnode[0]['secnumber'] = number - if titlenode: - titlenode['secnumber'] = number - titlenode = None - elif isinstance(subnode, addnodes.toctree): - _walk_toctree(subnode, depth) - - def _walk_toctree(toctreenode, depth): - if depth == 0: - return - for (title, ref) in toctreenode['entries']: - if url_re.match(ref) or ref == 'self' or ref in assigned: - # don't mess with those - continue - if ref in self.tocs: - secnums = self.toc_secnumbers[ref] = {} - assigned.add(ref) - _walk_toc(self.tocs[ref], secnums, depth, - self.env.titles.get(ref)) - if secnums != old_secnumbers.get(ref): - rewrite_needed.append(ref) - - for docname in self.numbered_toctrees: - assigned.add(docname) - doctree = self.env.get_doctree(docname) - for toctreenode in doctree.traverse(addnodes.toctree): - depth = toctreenode.get('numbered', 0) - if depth: - # every numbered toctree gets new numbering - numstack = [0] - _walk_toctree(toctreenode, depth) - - return rewrite_needed - - def assign_figure_numbers(self): - # type: () -> List[unicode] - """Assign a figure number to each figure under a numbered toctree.""" - - rewrite_needed = [] - - assigned = set() # type: Set[unicode] - old_fignumbers = self.toc_fignumbers - self.toc_fignumbers = self.env.toc_fignumbers = {} - fignum_counter = {} # type: Dict[unicode, Dict[Tuple[int], int]] - - def get_section_number(docname, section): - anchorname = '#' + section['ids'][0] - secnumbers = self.toc_secnumbers.get(docname, {}) - if anchorname in secnumbers: - secnum = secnumbers.get(anchorname) - else: - secnum = secnumbers.get('') - - return secnum or tuple() - - def get_next_fignumber(figtype, secnum): - counter = fignum_counter.setdefault(figtype, {}) - - secnum = secnum[:self.env.config.numfig_secnum_depth] - counter[secnum] = counter.get(secnum, 0) + 1 - return secnum + (counter[secnum],) - - def register_fignumber(docname, secnum, figtype, fignode): - self.toc_fignumbers.setdefault(docname, {}) - fignumbers = self.toc_fignumbers[docname].setdefault(figtype, {}) - figure_id = fignode['ids'][0] - - fignumbers[figure_id] = get_next_fignumber(figtype, secnum) - - def _walk_doctree(docname, doctree, secnum): - for subnode in doctree.children: - if isinstance(subnode, nodes.section): - next_secnum = get_section_number(docname, subnode) - if next_secnum: - _walk_doctree(docname, subnode, next_secnum) - else: - _walk_doctree(docname, subnode, secnum) - continue - elif isinstance(subnode, addnodes.toctree): - for title, subdocname in subnode['entries']: - if url_re.match(subdocname) or subdocname == 'self': - # don't mess with those - continue - - _walk_doc(subdocname, secnum) - - continue - - figtype = self.env.get_domain('std').get_figtype(subnode) # type: ignore - if figtype and subnode['ids']: - register_fignumber(docname, secnum, figtype, subnode) - - _walk_doctree(docname, subnode, secnum) - - def _walk_doc(docname, secnum): - if docname not in assigned: - assigned.add(docname) - doctree = self.env.get_doctree(docname) - _walk_doctree(docname, doctree, secnum) - - if self.env.config.numfig: - _walk_doc(self.env.config.master_doc, tuple()) - for docname, fignums in iteritems(self.toc_fignumbers): - if fignums != old_fignumbers.get(docname): - rewrite_needed.append(docname) - - return rewrite_needed |