From 6027bb75a58e33bb32c8c395f8ac959fd664365f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 8 Sep 2016 17:53:23 +0900 Subject: [PATCH 1/7] Refactor: move process_only_nodes() to sphinx.util.nodes --- sphinx/environment.py | 27 +++++---------------------- sphinx/util/nodes.py | 21 +++++++++++++++++++++ tests/test_directive_only.py | 3 ++- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index 02b041050..c90566eea 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -37,7 +37,8 @@ from sphinx import addnodes from sphinx.io import SphinxStandaloneReader, SphinxDummyWriter, SphinxFileInput from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ FilenameUniqDict, split_index_msg -from sphinx.util.nodes import clean_astext, WarningStream, is_translatable +from sphinx.util.nodes import clean_astext, WarningStream, is_translatable, \ + process_only_nodes from sphinx.util.osutil import SEP, getcwd, fs_encoding, ensuredir from sphinx.util.images import guess_mimetype from sphinx.util.i18n import find_catalog_files, get_image_filename_for_language, \ @@ -1058,7 +1059,7 @@ class BuildEnvironment(object): # the document does not exist anymore: return a dummy node that # renders to nothing return nodes.paragraph() - self.process_only_nodes(toc, builder, docname) + process_only_nodes(toc, builder.tags, warn_node=self.warn_node) for node in toc.traverse(nodes.reference): node['refuri'] = node['anchorname'] or '#' return toc @@ -1270,7 +1271,7 @@ class BuildEnvironment(object): maxdepth = self.metadata[ref].get('tocdepth', 0) if ref not in toctree_ancestors or (prune and maxdepth > 0): self._toctree_prune(toc, 2, maxdepth, collapse) - self.process_only_nodes(toc, builder, ref) + process_only_nodes(toc, builder.tags, warn_node=self.warn_node) if title and toc.children and len(toc.children) == 1: child = toc.children[0] for refnode in child.traverse(nodes.reference): @@ -1403,7 +1404,7 @@ class BuildEnvironment(object): node.replace_self(newnode or contnode) # remove only-nodes that do not belong to our builder - self.process_only_nodes(doctree, builder, fromdocname) + process_only_nodes(doctree, builder.tags, warn_node=self.warn_node) # allow custom references to be resolved builder.app.emit('doctree-resolved', doctree, fromdocname) @@ -1492,24 +1493,6 @@ class BuildEnvironment(object): newnode[0]['classes'].append(res_role.replace(':', '-')) return newnode - def process_only_nodes(self, doctree, builder, fromdocname=None): - # 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... - for node in doctree.traverse(addnodes.only): - try: - ret = builder.tags.eval_condition(node['expr']) - except Exception as err: - self.warn_node('exception while evaluating only ' - 'directive expression: %s' % err, node) - node.replace_self(node.children or nodes.comment()) - else: - if ret: - node.replace_self(node.children or nodes.comment()) - else: - node.replace_self(nodes.comment()) - def assign_section_numbers(self): """Assign a section number to each heading under a numbered toctree.""" # a list of all docnames whose section numbers changed diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 0fb1b1e12..2b899afce 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -321,6 +321,27 @@ def set_role_source_info(inliner, lineno, node): node.source, node.line = inliner.reporter.get_source_and_line(lineno) +def process_only_nodes(doctree, tags, warn_node=None): + # 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... + for node in doctree.traverse(addnodes.only): + try: + ret = tags.eval_condition(node['expr']) + except Exception as err: + if warn_node is None: + raise err + warn_node('exception while evaluating only ' + 'directive expression: %s' % err, node) + node.replace_self(node.children or nodes.comment()) + else: + if ret: + node.replace_self(node.children or nodes.comment()) + else: + node.replace_self(nodes.comment()) + + # monkey-patch Element.copy to copy the rawsource and line def _new_copy(self): diff --git a/tests/test_directive_only.py b/tests/test_directive_only.py index 7e499a3a1..def064c5a 100644 --- a/tests/test_directive_only.py +++ b/tests/test_directive_only.py @@ -12,6 +12,7 @@ import re from docutils import nodes +from sphinx.util.nodes import process_only_nodes from util import with_app @@ -46,7 +47,7 @@ def test_sectioning(app, status, warning): app.builder.build(['only']) doctree = app.env.get_doctree('only') - app.env.process_only_nodes(doctree, app.builder) + process_only_nodes(doctree, app.builder.tags) parts = [getsects(n) for n in [_n for _n in doctree.children if isinstance(_n, nodes.section)]] From 17c222a7069e340d4683fd5ad7eac68f4aa972e6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 8 Sep 2016 13:02:37 +0900 Subject: [PATCH 2/7] Separate indexentries manager to sphinx.environment.manager.indexentries --- .../__init__.py} | 161 ++-------------- sphinx/environment/managers/__init__.py | 27 +++ sphinx/environment/managers/indexentries.py | 172 ++++++++++++++++++ 3 files changed, 219 insertions(+), 141 deletions(-) rename sphinx/{environment.py => environment/__init__.py} (91%) create mode 100644 sphinx/environment/managers/__init__.py create mode 100644 sphinx/environment/managers/indexentries.py diff --git a/sphinx/environment.py b/sphinx/environment/__init__.py similarity index 91% rename from sphinx/environment.py rename to sphinx/environment/__init__.py index c90566eea..8fa580c01 100644 --- a/sphinx/environment.py +++ b/sphinx/environment/__init__.py @@ -14,16 +14,12 @@ import os import sys import time import types -import bisect import codecs -import string import fnmatch -import unicodedata from os import path from glob import glob -from itertools import groupby -from six import iteritems, itervalues, text_type, class_types, next +from six import iteritems, itervalues, class_types, next from six.moves import cPickle as pickle from docutils import nodes from docutils.io import NullOutput @@ -35,8 +31,7 @@ from docutils.frontend import OptionParser from sphinx import addnodes from sphinx.io import SphinxStandaloneReader, SphinxDummyWriter, SphinxFileInput -from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ - FilenameUniqDict, split_index_msg +from sphinx.util import url_re, get_matching_docs, docname_join, FilenameUniqDict from sphinx.util.nodes import clean_astext, WarningStream, is_translatable, \ process_only_nodes from sphinx.util.osutil import SEP, getcwd, fs_encoding, ensuredir @@ -49,9 +44,9 @@ from sphinx.util.matching import compile_matchers from sphinx.util.parallel import ParallelTasks, parallel_available, make_chunks from sphinx.util.websupport import is_commentable from sphinx.errors import SphinxError, ExtensionError -from sphinx.locale import _ from sphinx.versioning import add_uids, merge_doctrees from sphinx.transforms import SphinxContentsFilter +from sphinx.environment.managers.indexentries import IndexEntries default_settings = { @@ -209,6 +204,14 @@ class BuildEnvironment(object): # attributes of "any" cross references self.ref_context = {} + self.init_managers() + + def init_managers(self): + self.managers = {} + for manager_class in [IndexEntries]: + manager = manager_class(self) + self.managers[manager.name] = manager + def set_warnfunc(self, func): self._warnfunc = func self.settings['warning_stream'] = WarningStream(func) @@ -259,7 +262,6 @@ class BuildEnvironment(object): self.toc_fignumbers.pop(docname, None) self.toc_num_entries.pop(docname, None) self.toctree_includes.pop(docname, None) - self.indexentries.pop(docname, None) self.glob_toctrees.discard(docname) self.numbered_toctrees.discard(docname) self.images.purge_doc(docname) @@ -273,6 +275,9 @@ class BuildEnvironment(object): new = [change for change in changes if change[1] != docname] changes[:] = new + for manager in itervalues(self.managers): + manager.clear_doc(docname) + for domain in self.domains.values(): domain.clear_doc(docname) @@ -297,7 +302,6 @@ class BuildEnvironment(object): # toc_secnumbers and toc_fignumbers are not assigned during read if docname in other.toctree_includes: self.toctree_includes[docname] = other.toctree_includes[docname] - self.indexentries[docname] = other.indexentries[docname] if docname in other.glob_toctrees: self.glob_toctrees.add(docname) if docname in other.numbered_toctrees: @@ -312,6 +316,8 @@ class BuildEnvironment(object): self.versionchanges.setdefault(version, []).extend( change for change in changes if change[1] in docnames) + for manager in itervalues(self.managers): + manager.merge_other(docnames, other) for domainname, domain in self.domains.items(): domain.merge_domaindata(docnames, other.domaindata[domainname]) app.emit('env-merge-info', self, docnames, other) @@ -682,8 +688,9 @@ class BuildEnvironment(object): self.process_downloads(docname, doctree) self.process_metadata(docname, doctree) self.create_title_from(docname, doctree) - self.note_indexentries_from(docname, doctree) self.build_toc_from(docname, doctree) + for manager in itervalues(self.managers): + manager.process_doc(docname, doctree) for domain in itervalues(self.domains): domain.process_doc(self, docname, doctree) @@ -948,23 +955,6 @@ class BuildEnvironment(object): self.titles[docname] = titlenode self.longtitles[docname] = longtitlenode - def note_indexentries_from(self, docname, document): - entries = self.indexentries[docname] = [] - for node in document.traverse(addnodes.index): - try: - for entry in node['entries']: - split_index_msg(entry[0], entry[1]) - except ValueError as exc: - self.warn_node(exc, node) - node.parent.remove(node) - else: - for entry in node['entries']: - if len(entry) == 5: - # Since 1.4: new index structure including index_key (5th column) - entries.append(entry) - else: - entries.append(entry + (None,)) - def note_toctree(self, docname, toctreenode): """Note a TOC tree directive in a document and gather information about file relations from it. @@ -1636,119 +1626,8 @@ class BuildEnvironment(object): def create_index(self, builder, group_entries=True, _fixre=re.compile(r'(.*) ([(][^()]*[)])')): - """Create the real index from the collected index entries.""" - new = {} - - def add_entry(word, subword, link=True, dic=new, key=None): - # Force the word to be unicode if it's a ASCII bytestring. - # This will solve problems with unicode normalization later. - # For instance the RFC role will add bytestrings at the moment - word = text_type(word) - entry = dic.get(word) - if not entry: - dic[word] = entry = [[], {}, key] - if subword: - add_entry(subword, '', link=link, dic=entry[1], key=key) - elif link: - try: - uri = builder.get_relative_uri('genindex', fn) + '#' + tid - except NoUri: - pass - else: - # maintain links in sorted/deterministic order - bisect.insort(entry[0], (main, uri)) - - for fn, entries in iteritems(self.indexentries): - # new entry types must be listed in directives/other.py! - for type, value, tid, main, index_key in entries: - try: - if type == 'single': - try: - entry, subentry = split_into(2, 'single', value) - except ValueError: - entry, = split_into(1, 'single', value) - subentry = '' - add_entry(entry, subentry, key=index_key) - elif type == 'pair': - first, second = split_into(2, 'pair', value) - add_entry(first, second, key=index_key) - add_entry(second, first, key=index_key) - elif type == 'triple': - first, second, third = split_into(3, 'triple', value) - add_entry(first, second+' '+third, key=index_key) - add_entry(second, third+', '+first, key=index_key) - add_entry(third, first+' '+second, key=index_key) - elif type == 'see': - first, second = split_into(2, 'see', value) - add_entry(first, _('see %s') % second, link=False, - key=index_key) - elif type == 'seealso': - first, second = split_into(2, 'see', value) - add_entry(first, _('see also %s') % second, link=False, - key=index_key) - else: - self.warn(fn, 'unknown index entry type %r' % type) - except ValueError as err: - self.warn(fn, str(err)) - - # sort the index entries; put all symbols at the front, even those - # following the letters in ASCII, this is where the chr(127) comes from - def keyfunc(entry, lcletters=string.ascii_lowercase + '_'): - lckey = unicodedata.normalize('NFD', entry[0].lower()) - if lckey[0:1] in lcletters: - lckey = chr(127) + lckey - # ensure a determinstic order *within* letters by also sorting on - # the entry itself - return (lckey, entry[0]) - newlist = sorted(new.items(), key=keyfunc) - - if group_entries: - # fixup entries: transform - # func() (in module foo) - # func() (in module bar) - # into - # func() - # (in module foo) - # (in module bar) - oldkey = '' - oldsubitems = None - i = 0 - while i < len(newlist): - key, (targets, subitems, _key) = newlist[i] - # cannot move if it has subitems; structure gets too complex - if not subitems: - m = _fixre.match(key) - if m: - if oldkey == m.group(1): - # prefixes match: add entry as subitem of the - # previous entry - oldsubitems.setdefault(m.group(2), [[], {}, _key])[0].\ - extend(targets) - del newlist[i] - continue - oldkey = m.group(1) - else: - oldkey = key - oldsubitems = subitems - i += 1 - - # group the entries by letter - def keyfunc2(item, letters=string.ascii_uppercase + '_'): - # hack: mutating the subitems dicts to a list in the keyfunc - k, v = item - v[1] = sorted((si, se) for (si, (se, void, void)) in iteritems(v[1])) - if v[2] is None: - # now calculate the key - letter = unicodedata.normalize('NFD', k[0])[0].upper() - if letter in letters: - return letter - else: - # get all other symbols under one heading - return _('Symbols') - else: - return v[2] - return [(key_, list(group)) - for (key_, group) in groupby(newlist, keyfunc2)] + entries = self.managers['indexentries'] + return entries.create_index(builder, group_entries=group_entries, _fixre=_fixre) def collect_relations(self): traversed = set() diff --git a/sphinx/environment/managers/__init__.py b/sphinx/environment/managers/__init__.py new file mode 100644 index 000000000..ba8bce51b --- /dev/null +++ b/sphinx/environment/managers/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" + sphinx.environment.managers + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Manager components for sphinx.environment. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + + +class EnvironmentManager(object): + """Base class for sphinx.environment managers.""" + name = None + + def __init__(self, env): + self.env = env + + def clear_doc(self, docname): + raise NotImplementedError + + def merge_other(self, docnames, other): + raise NotImplementedError + + def process_doc(self, docname, doctree): + raise NotImplementedError diff --git a/sphinx/environment/managers/indexentries.py b/sphinx/environment/managers/indexentries.py new file mode 100644 index 000000000..946f5a7ed --- /dev/null +++ b/sphinx/environment/managers/indexentries.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +""" + sphinx.environment.managers.indexentries + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Index entries manager for sphinx.environment. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +import re +import bisect +import unicodedata +import string +from itertools import groupby + +from six import text_type + +from sphinx import addnodes +from sphinx.util import iteritems, split_index_msg, split_into +from sphinx.locale import _ +from sphinx.environment.managers import EnvironmentManager + + +class IndexEntries(EnvironmentManager): + name = 'indexentries' + + def __init__(self, env): + super(IndexEntries, self).__init__(env) + self.data = env.indexentries + + def clear_doc(self, docname): + self.data.pop(docname, None) + + def merge_other(self, docnames, other): + for docname in docnames: + self.data[docname] = other.indexentries[docname] + + def process_doc(self, docname, doctree): + entries = self.data[docname] = [] + for node in doctree.traverse(addnodes.index): + try: + for entry in node['entries']: + split_index_msg(entry[0], entry[1]) + except ValueError as exc: + self.env.warn_node(exc, node) + node.parent.remove(node) + else: + for entry in node['entries']: + if len(entry) == 5: + # Since 1.4: new index structure including index_key (5th column) + entries.append(entry) + else: + entries.append(entry + (None,)) + + def create_index(self, builder, group_entries=True, + _fixre=re.compile(r'(.*) ([(][^()]*[)])')): + """Create the real index from the collected index entries.""" + from sphinx.environment import NoUri + + new = {} + + def add_entry(word, subword, link=True, dic=new, key=None): + # Force the word to be unicode if it's a ASCII bytestring. + # This will solve problems with unicode normalization later. + # For instance the RFC role will add bytestrings at the moment + word = text_type(word) + entry = dic.get(word) + if not entry: + dic[word] = entry = [[], {}, key] + if subword: + add_entry(subword, '', link=link, dic=entry[1], key=key) + elif link: + try: + uri = builder.get_relative_uri('genindex', fn) + '#' + tid + except NoUri: + pass + else: + # maintain links in sorted/deterministic order + bisect.insort(entry[0], (main, uri)) + + for fn, entries in iteritems(self.data): + # new entry types must be listed in directives/other.py! + for type, value, tid, main, index_key in entries: + try: + if type == 'single': + try: + entry, subentry = split_into(2, 'single', value) + except ValueError: + entry, = split_into(1, 'single', value) + subentry = '' + add_entry(entry, subentry, key=index_key) + elif type == 'pair': + first, second = split_into(2, 'pair', value) + add_entry(first, second, key=index_key) + add_entry(second, first, key=index_key) + elif type == 'triple': + first, second, third = split_into(3, 'triple', value) + add_entry(first, second+' '+third, key=index_key) + add_entry(second, third+', '+first, key=index_key) + add_entry(third, first+' '+second, key=index_key) + elif type == 'see': + first, second = split_into(2, 'see', value) + add_entry(first, _('see %s') % second, link=False, + key=index_key) + elif type == 'seealso': + first, second = split_into(2, 'see', value) + add_entry(first, _('see also %s') % second, link=False, + key=index_key) + else: + self.env.warn(fn, 'unknown index entry type %r' % type) + except ValueError as err: + self.env.warn(fn, str(err)) + + # sort the index entries; put all symbols at the front, even those + # following the letters in ASCII, this is where the chr(127) comes from + def keyfunc(entry, lcletters=string.ascii_lowercase + '_'): + lckey = unicodedata.normalize('NFD', entry[0].lower()) + if lckey[0:1] in lcletters: + lckey = chr(127) + lckey + # ensure a determinstic order *within* letters by also sorting on + # the entry itself + return (lckey, entry[0]) + newlist = sorted(new.items(), key=keyfunc) + + if group_entries: + # fixup entries: transform + # func() (in module foo) + # func() (in module bar) + # into + # func() + # (in module foo) + # (in module bar) + oldkey = '' + oldsubitems = None + i = 0 + while i < len(newlist): + key, (targets, subitems, _key) = newlist[i] + # cannot move if it has subitems; structure gets too complex + if not subitems: + m = _fixre.match(key) + if m: + if oldkey == m.group(1): + # prefixes match: add entry as subitem of the + # previous entry + oldsubitems.setdefault(m.group(2), [[], {}, _key])[0].\ + extend(targets) + del newlist[i] + continue + oldkey = m.group(1) + else: + oldkey = key + oldsubitems = subitems + i += 1 + + # group the entries by letter + def keyfunc2(item, letters=string.ascii_uppercase + '_'): + # hack: mutating the subitems dicts to a list in the keyfunc + k, v = item + v[1] = sorted((si, se) for (si, (se, void, void)) in iteritems(v[1])) + if v[2] is None: + # now calculate the key + letter = unicodedata.normalize('NFD', k[0])[0].upper() + if letter in letters: + return letter + else: + # get all other symbols under one heading + return _('Symbols') + else: + return v[2] + return [(key_, list(group)) + for (key_, group) in groupby(newlist, keyfunc2)] From dc985ed479f9a5ac6baaf8820c5f1b19354772a6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 9 Sep 2016 16:31:14 +0900 Subject: [PATCH 3/7] Separate toctree manager to sphinx.environment.managers.toctree --- sphinx/environment/__init__.py | 510 +--------------------- sphinx/environment/managers/toctree.py | 561 +++++++++++++++++++++++++ 2 files changed, 572 insertions(+), 499 deletions(-) create mode 100644 sphinx/environment/managers/toctree.py diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 8fa580c01..3181d8f74 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -31,7 +31,7 @@ from docutils.frontend import OptionParser from sphinx import addnodes from sphinx.io import SphinxStandaloneReader, SphinxDummyWriter, SphinxFileInput -from sphinx.util import url_re, get_matching_docs, docname_join, FilenameUniqDict +from sphinx.util import get_matching_docs, docname_join, FilenameUniqDict from sphinx.util.nodes import clean_astext, WarningStream, is_translatable, \ process_only_nodes from sphinx.util.osutil import SEP, getcwd, fs_encoding, ensuredir @@ -47,6 +47,7 @@ from sphinx.errors import SphinxError, ExtensionError from sphinx.versioning import add_uids, merge_doctrees from sphinx.transforms import SphinxContentsFilter from sphinx.environment.managers.indexentries import IndexEntries +from sphinx.environment.managers.toctree import Toctree default_settings = { @@ -208,7 +209,7 @@ class BuildEnvironment(object): def init_managers(self): self.managers = {} - for manager_class in [IndexEntries]: + for manager_class in [IndexEntries, Toctree]: manager = manager_class(self) self.managers[manager.name] = manager @@ -257,20 +258,9 @@ class BuildEnvironment(object): self.dependencies.pop(docname, None) self.titles.pop(docname, None) self.longtitles.pop(docname, 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) self.images.purge_doc(docname) self.dlfiles.purge_doc(docname) - for subfn, fnset in list(self.files_to_rebuild.items()): - fnset.discard(docname) - if not fnset: - del self.files_to_rebuild[subfn] for version, changes in self.versionchanges.items(): new = [change for change in changes if change[1] != docname] changes[:] = new @@ -297,21 +287,10 @@ class BuildEnvironment(object): self.dependencies[docname] = other.dependencies[docname] self.titles[docname] = other.titles[docname] self.longtitles[docname] = other.longtitles[docname] - self.tocs[docname] = other.tocs[docname] - self.toc_num_entries[docname] = other.toc_num_entries[docname] - # toc_secnumbers and toc_fignumbers are not assigned during read - 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) self.images.merge_other(docnames, other.images) self.dlfiles.merge_other(docnames, other.dlfiles) - for subfn, fnset in other.files_to_rebuild.items(): - self.files_to_rebuild.setdefault(subfn, set()).update(fnset & docnames) for version, changes in other.versionchanges.items(): self.versionchanges.setdefault(version, []).extend( change for change in changes if change[1] in docnames) @@ -614,7 +593,8 @@ class BuildEnvironment(object): self._warnfunc(*warning, **kwargs) def check_dependents(self, already): - to_rewrite = self.assign_section_numbers() + self.assign_figure_numbers() + toctree = self.managers['toctree'] + to_rewrite = toctree.assign_section_numbers() + toctree.assign_figure_numbers() for docname in set(to_rewrite): if docname not in already: yield docname @@ -688,7 +668,6 @@ class BuildEnvironment(object): self.process_downloads(docname, doctree) self.process_metadata(docname, doctree) self.create_title_from(docname, doctree) - self.build_toc_from(docname, doctree) for manager in itervalues(self.managers): manager.process_doc(docname, doctree) for domain in itervalues(self.domains): @@ -959,121 +938,15 @@ class BuildEnvironment(object): """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 build_toc_from(self, docname, document): - """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(document) - 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(document) - if toc: - self.tocs[docname] = toc - else: - self.tocs[docname] = nodes.bullet_list('') - self.toc_num_entries[docname] = numentries[0] + self.managers['toctree'].note_toctree(docname, toctreenode) def get_toc_for(self, docname, builder): """Return a TOC nodetree -- for use on the same page only!""" - tocdepth = self.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.warn_node) - for node in toc.traverse(nodes.reference): - node['refuri'] = node['anchorname'] or '#' - return toc + return self.managers['toctree'].get_toc_for(docname, builder) def get_toctree_for(self, docname, builder, collapse, **kwds): """Return the global TOC nodetree.""" - doctree = self.get_doctree(self.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.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 + return self.managers['toctree'].get_toctree_for(docname, builder, collapse, **kwds) def get_domain(self, domainname): """Return the domain instance with the specified name. @@ -1120,39 +993,6 @@ class BuildEnvironment(object): return doctree - def _toctree_prune(self, node, depth, maxdepth, collapse=False): - """Utility: Cut a TOC at a specified depth.""" - for subnode in node.children[:]: - if isinstance(subnode, (addnodes.compact_paragraph, - nodes.list_item)): - # for

and

  • , just recurse - self._toctree_prune(subnode, depth, maxdepth, collapse) - elif isinstance(subnode, nodes.bullet_list): - # for