Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 59 additions & 18 deletions sphinx_multitoc_numbering/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
A sphinx extension for continuous numbering of sections across toctrees.
"""

from typing import Dict, List, Set, Tuple
from typing import Dict, Iterable, List, Set, Tuple
from typing import cast

from docutils import nodes
Expand All @@ -27,17 +27,67 @@ def build_init_handler(app):

logger.info(bold("sphinx-multitoc-numbering v%s:") + " Loaded", __version__)


def find_numbered_toctree_nodes(
env, iterable: Iterable[addnodes.toctree]
) -> Tuple[Set[str], List[addnodes.toctree]]:
"""Recursively walk the toctree, recording docnames and numbered nodes"""
toctree_nodes = []
assigned = set([])
for node in iterable:
if node["numbered"]:
toctree_nodes.append(node)
else:
for _, ref in node["entries"]:
assigned.add(ref)
doctree = env.get_doctree(ref)
inner_toctree_nodes = doctree.traverse(addnodes.toctree)
# RECURSION
# Base case: All nodes in inner_toctree_nodes are numbered,
# or inner_toctree_nodes is empty
# This'll happen eventually as long as the toctree is acyclic,
# as we are guaranteed to get a level deeper with every call.
# Sphinx already requires an acyclic toctree, so we're fine.
inner_assigned, inner_toctree_nodes = find_numbered_toctree_nodes(
env, inner_toctree_nodes
)
assigned.update(inner_assigned)
toctree_nodes.extend(inner_toctree_nodes)
return assigned, toctree_nodes


def get_toctree_nodes(env: BuildEnvironment) -> Tuple[Set[str], List[addnodes.toctree]]:
"""Get all numbered toctrees, in the order they appear in the document

Walks the entire toctree, starting with the index, and records numbered
toctrees as it finds them."""
## Get the toctrees in the correct order
# Sphinx toctrees always start at index
index_doctree = env.get_doctree("index")
index_toctree_nodes = index_doctree.traverse(addnodes.toctree)

assigned, toctree_nodes = find_numbered_toctree_nodes(env, index_toctree_nodes)
assigned.add("index")

if assigned != env.numbered_toctrees:
logger.warning(
"Couldn't number some toctrees: {env.numbered_toctrees - assigned}",
type="toc",
)
return assigned, toctree_nodes


def assign_section_numbers(self, env: BuildEnvironment) -> List[str]:
"""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[str]
old_secnumbers = env.toc_secnumbers
env.toc_secnumbers = {}
self.last_chapter_number = 0

assigned, toctree_nodes = get_toctree_nodes(env)

def _walk_toc(
node: Element, secnums: Dict, depth: int, titlenode: nodes.title = None
) -> None:
Expand Down Expand Up @@ -81,7 +131,7 @@ def _walk_toc(
def _walk_toctree(toctreenode: addnodes.toctree, depth: int) -> None:
if depth == 0:
return
for (title, ref) in toctreenode["entries"]:
for _, ref in toctreenode["entries"]:
if url_re.match(ref) or ref == "self":
# don't mess with those
continue
Expand All @@ -104,21 +154,12 @@ def _walk_toctree(toctreenode: addnodes.toctree, depth: int) -> None:
if secnums != old_secnumbers.get(ref):
rewrite_needed.append(ref)

# rearrange it to respect ordering in toctree directives
rearranged_numbered_toctrees = []
for toc in env.tocs:
if toc in env.numbered_toctrees:
rearranged_numbered_toctrees.append(toc)

for docname in rearranged_numbered_toctrees:
assigned.add(docname)
doctree = env.get_doctree(docname)
for toctreenode in doctree.traverse(addnodes.toctree):
depth = toctreenode.get("numbered", 0)
if depth:
# every numbered toctree continues the numbering
numstack = [self.last_chapter_number]
_walk_toctree(toctreenode, depth)
for toctreenode in toctree_nodes:
depth = toctreenode.get("numbered", 0)
if depth:
# every numbered toctree continues the numbering
numstack = [self.last_chapter_number]
_walk_toctree(toctreenode, depth)

return rewrite_needed

Expand Down