diff --git a/sphinx/application.py b/sphinx/application.py index 5466970a4..29ebd454a 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -293,6 +293,7 @@ class Sphinx(object): self.env = BuildEnvironment.frompickle( self.srcdir, self.config, path.join(self.doctreedir, ENV_PICKLE_FILENAME)) self.env.set_warnfunc(self.warn) + self.env.init_managers() self.env.domains = {} for domain in self.domains.keys(): # this can raise if the data version doesn't fit diff --git a/sphinx/environment.py b/sphinx/environment/__init__.py similarity index 61% rename from sphinx/environment.py rename to sphinx/environment/__init__.py index 02b041050..d750b0284 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,9 +31,9 @@ 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.nodes import clean_astext, WarningStream, is_translatable +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 from sphinx.util.images import guess_mimetype from sphinx.util.i18n import find_catalog_files, get_image_filename_for_language, \ @@ -48,9 +44,10 @@ 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 +from sphinx.environment.managers.toctree import Toctree default_settings = { @@ -114,6 +111,7 @@ class BuildEnvironment(object): del self.config.values domains = self.domains del self.domains + managers = self.detach_managers() # remove potentially pickling-problematic values from config for key, val in list(vars(self.config).items()): if key.startswith('_') or \ @@ -124,6 +122,7 @@ class BuildEnvironment(object): with open(filename, 'wb') as picklefile: pickle.dump(self, picklefile, pickle.HIGHEST_PROTOCOL) # reset attributes + self.attach_managers(managers) self.domains = domains self.config.values = values self.set_warnfunc(warnfunc) @@ -208,6 +207,27 @@ class BuildEnvironment(object): # attributes of "any" cross references self.ref_context = {} + self.managers = {} + self.init_managers() + + def init_managers(self): + managers = {} + for manager_class in [IndexEntries, Toctree]: + managers[manager_class.name] = manager_class(self) + self.attach_managers(managers) + + def attach_managers(self, managers): + for name, manager in iteritems(managers): + self.managers[name] = manager + manager.attach(self) + + def detach_managers(self): + managers = self.managers + self.managers = {} + for _, manager in iteritems(managers): + manager.detach(self) + return managers + def set_warnfunc(self, func): self._warnfunc = func self.settings['warning_stream'] = WarningStream(func) @@ -253,25 +273,16 @@ 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.indexentries.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 + for manager in itervalues(self.managers): + manager.clear_doc(docname) + for domain in self.domains.values(): domain.clear_doc(docname) @@ -291,26 +302,16 @@ 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] - self.indexentries[docname] = other.indexentries[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) + 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) @@ -607,7 +608,8 @@ class BuildEnvironment(object): self._warnfunc(*warning, **kwargs) def check_dependents(self, already): - to_rewrite = self.assign_section_numbers() + self.assign_figure_numbers() + to_rewrite = (self.toctree.assign_section_numbers() + + self.toctree.assign_figure_numbers()) for docname in set(to_rewrite): if docname not in already: yield docname @@ -681,8 +683,8 @@ 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) @@ -947,142 +949,19 @@ 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. """ - 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.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() - self.process_only_nodes(toc, builder, docname) - for node in toc.traverse(nodes.reference): - node['refuri'] = node['anchorname'] or '#' - return toc + return self.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.toctree.get_toctree_for(docname, builder, collapse, **kwds) def get_domain(self, domainname): """Return the domain instance with the specified name. @@ -1129,39 +1008,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