mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Reimplement ToctreeManager as a collector
This commit is contained in:
parent
105951cdb2
commit
0b0637deb2
@ -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'
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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 []
|
||||||
|
@ -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)
|
Loading…
Reference in New Issue
Block a user