Reimplement ToctreeManager as a collector

This commit is contained in:
Takeshi KOMIYA 2017-01-10 21:43:31 +09:00
parent 105951cdb2
commit 0b0637deb2
5 changed files with 87 additions and 104 deletions

View File

@ -61,6 +61,7 @@ if False:
events = { events = {
'builder-inited': '', 'builder-inited': '',
'env-get-outdated': 'env, added, changed, removed', 'env-get-outdated': 'env, added, changed, removed',
'env-get-updated': 'env',
'env-purge-doc': 'env, docname', 'env-purge-doc': 'env, docname',
'env-before-read-docs': 'env, docnames', 'env-before-read-docs': 'env, docnames',
'source-read': 'docname, source text', 'source-read': 'docname, source text',
@ -107,6 +108,7 @@ builtin_extensions = (
'sphinx.environment.collectors.asset', 'sphinx.environment.collectors.asset',
'sphinx.environment.collectors.metadata', 'sphinx.environment.collectors.metadata',
'sphinx.environment.collectors.title', 'sphinx.environment.collectors.title',
'sphinx.environment.collectors.toctree',
) # type: Tuple[unicode, ...] ) # type: Tuple[unicode, ...]
CONFIG_FILENAME = 'conf.py' CONFIG_FILENAME = 'conf.py'

View File

@ -292,7 +292,7 @@ class Builder(object):
doccount = len(updated_docnames) doccount = len(updated_docnames)
logger.info(bold('looking for now-outdated files... '), nonl=1) logger.info(bold('looking for now-outdated files... '), nonl=1)
for docname in self.env.check_dependents(updated_docnames): for docname in self.env.check_dependents(self.app, updated_docnames):
updated_docnames.add(docname) updated_docnames.add(docname)
outdated = len(updated_docnames) - doccount outdated = len(updated_docnames) - doccount
if outdated: if outdated:

View File

@ -49,7 +49,6 @@ from sphinx.versioning import add_uids, merge_doctrees
from sphinx.deprecation import RemovedInSphinx20Warning from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.environment.adapters.toctree import TocTree from sphinx.environment.adapters.toctree import TocTree
from sphinx.environment.managers.indexentries import IndexEntries from sphinx.environment.managers.indexentries import IndexEntries
from sphinx.environment.managers.toctree import Toctree
if False: if False:
# For type annotation # For type annotation
@ -248,7 +247,7 @@ class BuildEnvironment(object):
# type: () -> None # type: () -> None
managers = {} managers = {}
manager_class = None # type: Type[EnvironmentManager] manager_class = None # type: Type[EnvironmentManager]
for manager_class in [IndexEntries, Toctree]: # type: ignore for manager_class in [IndexEntries]: # type: ignore
managers[manager_class.name] = manager_class(self) managers[manager_class.name] = manager_class(self)
self.attach_managers(managers) self.attach_managers(managers)
@ -645,11 +644,13 @@ class BuildEnvironment(object):
logger.info(bold('waiting for workers...')) logger.info(bold('waiting for workers...'))
tasks.join() tasks.join()
def check_dependents(self, already): def check_dependents(self, app, already):
# type: (Set[unicode]) -> Iterator[unicode] # type: (Sphinx, Set[unicode]) -> Iterator[unicode]
to_rewrite = [] to_rewrite = []
for manager in itervalues(self.managers): for manager in itervalues(self.managers):
to_rewrite.extend(manager.get_updated_docs()) to_rewrite.extend(manager.get_updated_docs())
for docnames in app.emit('env-get-updated', self):
to_rewrite.extend(docnames)
for docname in set(to_rewrite): for docname in set(to_rewrite):
if docname not in already: if docname not in already:
yield docname yield docname

View File

@ -26,10 +26,13 @@ class EnvironmentCollector(object):
def enable(self, app): def enable(self, app):
# type: (Sphinx) -> None # type: (Sphinx) -> None
assert self.listener_ids is None assert self.listener_ids is None
self.listener_ids = {} self.listener_ids = {
self.listener_ids['doctree-read'] = app.connect('doctree-read', self.process_doc) 'doctree-read': app.connect('doctree-read', self.process_doc),
self.listener_ids['env-merge-info'] = app.connect('env-merge-info', self.merge_other) 'env-merge-info': app.connect('env-merge-info', self.merge_other),
self.listener_ids['env-purge-doc'] = app.connect('env-purge-doc', self.clear_doc) 'env-purge-doc': app.connect('env-purge-doc', self.clear_doc),
'env-get-updated': app.connect('env-get-updated', self.get_updated_docs),
'env-get-outdated': app.connect('env-get-outdated', self.get_outdated_docs),
}
def disable(self, app): def disable(self, app):
# type: (Sphinx) -> None # type: (Sphinx) -> None
@ -49,3 +52,11 @@ class EnvironmentCollector(object):
def process_doc(self, app, doctree): def process_doc(self, app, doctree):
# type: (Sphinx, nodes.Node) -> None # type: (Sphinx, nodes.Node) -> None
raise NotImplementedError raise NotImplementedError
def get_updated_docs(self, app, env):
# type: (Sphinx, BuildEnvironment) -> List[unicode]
return []
def get_outdated_docs(self, app, env, added, changed, removed):
# type: (Sphinx, BuildEnvironment, unicode, Set[unicode], Set[unicode], Set[unicode]) -> List[unicode] # NOQA
return []

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
sphinx.environment.managers.toctree sphinx.environment.collectors.toctree
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Toctree manager for sphinx.environment. Toctree collector for sphinx.environment.
:copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
@ -16,67 +16,54 @@ from docutils import nodes
from sphinx import addnodes from sphinx import addnodes
from sphinx.util import url_re, logging from sphinx.util import url_re, logging
from sphinx.transforms import SphinxContentsFilter from sphinx.transforms import SphinxContentsFilter
from sphinx.environment.adapters.toctree import TocTree as TocTreeAdapter from sphinx.environment.adapters.toctree import TocTree
from sphinx.environment.managers import EnvironmentManager from sphinx.environment.collectors import EnvironmentCollector
if False: if False:
# For type annotation # For type annotation
from typing import Any, Tuple # NOQA from typing import Any, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Toctree(EnvironmentManager): class TocTreeCollector(EnvironmentCollector):
name = 'toctree' def clear_doc(self, app, env, docname):
# type: (Sphinx, BuildEnvironment, unicode) -> None
env.tocs.pop(docname, None)
env.toc_secnumbers.pop(docname, None)
env.toc_fignumbers.pop(docname, None)
env.toc_num_entries.pop(docname, None)
env.toctree_includes.pop(docname, None)
env.glob_toctrees.discard(docname)
env.numbered_toctrees.discard(docname)
def __init__(self, env): for subfn, fnset in list(env.files_to_rebuild.items()):
# 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) fnset.discard(docname)
if not fnset: if not fnset:
del self.files_to_rebuild[subfn] del env.files_to_rebuild[subfn]
def merge_other(self, docnames, other): def merge_other(self, app, env, docnames, other):
# type: (List[unicode], BuildEnvironment) -> None # type: (Sphinx, BuildEnvironment, Set[unicode], BuildEnvironment) -> None
for docname in docnames: for docname in docnames:
self.tocs[docname] = other.tocs[docname] env.tocs[docname] = other.tocs[docname]
self.toc_num_entries[docname] = other.toc_num_entries[docname] env.toc_num_entries[docname] = other.toc_num_entries[docname]
if docname in other.toctree_includes: if docname in other.toctree_includes:
self.toctree_includes[docname] = other.toctree_includes[docname] env.toctree_includes[docname] = other.toctree_includes[docname]
if docname in other.glob_toctrees: if docname in other.glob_toctrees:
self.glob_toctrees.add(docname) env.glob_toctrees.add(docname)
if docname in other.numbered_toctrees: if docname in other.numbered_toctrees:
self.numbered_toctrees.add(docname) env.numbered_toctrees.add(docname)
for subfn, fnset in other.files_to_rebuild.items(): for subfn, fnset in other.files_to_rebuild.items():
self.files_to_rebuild.setdefault(subfn, set()).update(fnset & set(docnames)) env.files_to_rebuild.setdefault(subfn, set()).update(fnset & set(docnames))
def process_doc(self, docname, doctree): def process_doc(self, app, doctree):
# type: (unicode, nodes.Node) -> None # type: (Sphinx, nodes.Node) -> None
"""Build a TOC from the doctree and store it in the inventory.""" """Build a TOC from the doctree and store it in the inventory."""
docname = app.env.docname
numentries = [0] # nonlocal again... numentries = [0] # nonlocal again...
def traverse_in_section(node, cls): def traverse_in_section(node, cls):
@ -109,7 +96,7 @@ class Toctree(EnvironmentManager):
item = toctreenode.copy() item = toctreenode.copy()
entries.append(item) entries.append(item)
# important: do the inventory stuff # important: do the inventory stuff
self.note_toctree(docname, toctreenode) TocTree(app.env).note(docname, toctreenode)
continue continue
title = sectionnode[0] title = sectionnode[0]
# copy the contents of the section title, but without references # copy the contents of the section title, but without references
@ -139,47 +126,24 @@ class Toctree(EnvironmentManager):
return [] return []
toc = build_toc(doctree) toc = build_toc(doctree)
if toc: if toc:
self.tocs[docname] = toc app.env.tocs[docname] = toc
else: else:
self.tocs[docname] = nodes.bullet_list('') app.env.tocs[docname] = nodes.bullet_list('')
self.toc_num_entries[docname] = numentries[0] app.env.toc_num_entries[docname] = numentries[0]
def get_updated_docs(self): def get_updated_docs(self, app, env):
# type: () -> List[unicode] # type: (Sphinx, BuildEnvironment) -> List[unicode]
return self.assign_section_numbers() + self.assign_figure_numbers() return self.assign_section_numbers(env) + self.assign_figure_numbers(env)
def note_toctree(self, docname, toctreenode): def assign_section_numbers(self, env):
# type: (unicode, addnodes.toctree) -> None # type: (BuildEnvironment) -> List[unicode]
"""Note a TOC tree directive in a document and gather information about
file relations from it.
"""
TocTreeAdapter(self.env).note(docname, toctreenode)
def get_toc_for(self, docname, builder):
# type: (unicode, Builder) -> Dict[unicode, nodes.Node]
"""Return a TOC nodetree -- for use on the same page only!"""
return TocTreeAdapter(self.env).get_toc_for(docname, builder)
def get_toctree_for(self, docname, builder, collapse, **kwds):
# type: (unicode, Builder, bool, Any) -> nodes.Node
"""Return the global TOC nodetree."""
return TocTreeAdapter(self.env).get_toctree_for(docname, builder, collapse, **kwds)
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
return TocTreeAdapter(self.env).resolve(docname, builder, toctree, prune, maxdepth,
titles_only, collapse, includehidden)
def assign_section_numbers(self):
# type: () -> List[unicode]
"""Assign a section number to each heading under a numbered toctree.""" """Assign a section number to each heading under a numbered toctree."""
# a list of all docnames whose section numbers changed # a list of all docnames whose section numbers changed
rewrite_needed = [] rewrite_needed = []
assigned = set() # type: Set[unicode] assigned = set() # type: Set[unicode]
old_secnumbers = self.toc_secnumbers old_secnumbers = env.toc_secnumbers
self.toc_secnumbers = self.env.toc_secnumbers = {} env.toc_secnumbers = {}
def _walk_toc(node, secnums, depth, titlenode=None): def _walk_toc(node, secnums, depth, titlenode=None):
# titlenode is the title of the document, it will get assigned a # titlenode is the title of the document, it will get assigned a
@ -224,17 +188,17 @@ class Toctree(EnvironmentManager):
logger.warning('%s is already assigned section numbers ' logger.warning('%s is already assigned section numbers '
'(nested numbered toctree?)', ref, '(nested numbered toctree?)', ref,
location=toctreenode, type='toc', subtype='secnum') location=toctreenode, type='toc', subtype='secnum')
elif ref in self.tocs: elif ref in env.tocs:
secnums = self.toc_secnumbers[ref] = {} secnums = env.toc_secnumbers[ref] = {}
assigned.add(ref) assigned.add(ref)
_walk_toc(self.tocs[ref], secnums, depth, _walk_toc(env.tocs[ref], secnums, depth,
self.env.titles.get(ref)) env.titles.get(ref))
if secnums != old_secnumbers.get(ref): if secnums != old_secnumbers.get(ref):
rewrite_needed.append(ref) rewrite_needed.append(ref)
for docname in self.numbered_toctrees: for docname in env.numbered_toctrees:
assigned.add(docname) assigned.add(docname)
doctree = self.env.get_doctree(docname) doctree = env.get_doctree(docname)
for toctreenode in doctree.traverse(addnodes.toctree): for toctreenode in doctree.traverse(addnodes.toctree):
depth = toctreenode.get('numbered', 0) depth = toctreenode.get('numbered', 0)
if depth: if depth:
@ -244,20 +208,20 @@ class Toctree(EnvironmentManager):
return rewrite_needed return rewrite_needed
def assign_figure_numbers(self): def assign_figure_numbers(self, env):
# type: () -> List[unicode] # type: (BuildEnvironment) -> List[unicode]
"""Assign a figure number to each figure under a numbered toctree.""" """Assign a figure number to each figure under a numbered toctree."""
rewrite_needed = [] rewrite_needed = []
assigned = set() # type: Set[unicode] assigned = set() # type: Set[unicode]
old_fignumbers = self.toc_fignumbers old_fignumbers = env.toc_fignumbers
self.toc_fignumbers = self.env.toc_fignumbers = {} env.toc_fignumbers = {}
fignum_counter = {} # type: Dict[unicode, Dict[Tuple[int], int]] fignum_counter = {} # type: Dict[unicode, Dict[Tuple[int], int]]
def get_section_number(docname, section): def get_section_number(docname, section):
anchorname = '#' + section['ids'][0] anchorname = '#' + section['ids'][0]
secnumbers = self.toc_secnumbers.get(docname, {}) secnumbers = env.toc_secnumbers.get(docname, {})
if anchorname in secnumbers: if anchorname in secnumbers:
secnum = secnumbers.get(anchorname) secnum = secnumbers.get(anchorname)
else: else:
@ -268,13 +232,13 @@ class Toctree(EnvironmentManager):
def get_next_fignumber(figtype, secnum): def get_next_fignumber(figtype, secnum):
counter = fignum_counter.setdefault(figtype, {}) counter = fignum_counter.setdefault(figtype, {})
secnum = secnum[:self.env.config.numfig_secnum_depth] secnum = secnum[:env.config.numfig_secnum_depth]
counter[secnum] = counter.get(secnum, 0) + 1 counter[secnum] = counter.get(secnum, 0) + 1
return secnum + (counter[secnum],) return secnum + (counter[secnum],)
def register_fignumber(docname, secnum, figtype, fignode): def register_fignumber(docname, secnum, figtype, fignode):
self.toc_fignumbers.setdefault(docname, {}) env.toc_fignumbers.setdefault(docname, {})
fignumbers = self.toc_fignumbers[docname].setdefault(figtype, {}) fignumbers = env.toc_fignumbers[docname].setdefault(figtype, {})
figure_id = fignode['ids'][0] figure_id = fignode['ids'][0]
fignumbers[figure_id] = get_next_fignumber(figtype, secnum) fignumbers[figure_id] = get_next_fignumber(figtype, secnum)
@ -298,7 +262,7 @@ class Toctree(EnvironmentManager):
continue continue
figtype = self.env.get_domain('std').get_figtype(subnode) # type: ignore figtype = env.get_domain('std').get_figtype(subnode) # type: ignore
if figtype and subnode['ids']: if figtype and subnode['ids']:
register_fignumber(docname, secnum, figtype, subnode) register_fignumber(docname, secnum, figtype, subnode)
@ -307,13 +271,18 @@ class Toctree(EnvironmentManager):
def _walk_doc(docname, secnum): def _walk_doc(docname, secnum):
if docname not in assigned: if docname not in assigned:
assigned.add(docname) assigned.add(docname)
doctree = self.env.get_doctree(docname) doctree = env.get_doctree(docname)
_walk_doctree(docname, doctree, secnum) _walk_doctree(docname, doctree, secnum)
if self.env.config.numfig: if env.config.numfig:
_walk_doc(self.env.config.master_doc, tuple()) _walk_doc(env.config.master_doc, tuple())
for docname, fignums in iteritems(self.toc_fignumbers): for docname, fignums in iteritems(env.toc_fignumbers):
if fignums != old_fignumbers.get(docname): if fignums != old_fignumbers.get(docname):
rewrite_needed.append(docname) rewrite_needed.append(docname)
return rewrite_needed return rewrite_needed
def setup(app):
# type: (Sphinx) -> None
app.add_env_collector(TocTreeCollector)