Skip to content

Commit 882fde6

Browse files
committed
Rewrite transforms, merge post-transforms, fix pep-naming
Also fix bug with `hNode` not being reset in `LatexMasterDocTransforms`
1 parent 4b9c541 commit 882fde6

File tree

8 files changed

+165
-139
lines changed

8 files changed

+165
-139
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ repos:
3939
rev: 3.8.4
4040
hooks:
4141
- id: flake8
42-
additional_dependencies: [flake8-bugbear==21.3.1]
42+
additional_dependencies:
43+
- flake8-bugbear~=21.3.1
44+
- flake8-comprehensions~=3.4.0
45+
- pep8-naming~=0.11.1
4346

4447
- repo: https://github.com/pre-commit/mirrors-mypy
4548
rev: v0.790

jupyterbook_latex/events.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@
1111

1212
from . import __version__, theme
1313
from .transforms import (
14-
LatexMasterDocTransforms,
14+
LatexRootDocPostTransforms,
15+
LatexRootDocTransforms,
1516
MystNbPostTransform,
16-
ToctreeTransforms,
17-
handleSubSections,
1817
)
1918

2019
if sys.version_info < (3, 9):
@@ -81,10 +80,10 @@ def setup_latex_transforms(app: Sphinx) -> None:
8180
if MystNbPostTransform.check_dependency():
8281
app.add_post_transform(MystNbPostTransform)
8382

84-
TOC_PATH = Path(app.confdir or app.srcdir).joinpath("_toc.yml")
85-
if not os.path.exists(TOC_PATH):
83+
toc_path = Path(app.confdir or app.srcdir).joinpath("_toc.yml")
84+
if not os.path.exists(toc_path):
8685
logger.info(
87-
"Some features of this exetension will work only with a jupyter-book application" # noqa: E501
86+
"Some features of this extension will work only with a jupyter-book application" # noqa: E501
8887
)
8988
return
9089

@@ -99,6 +98,5 @@ def setup_latex_transforms(app: Sphinx) -> None:
9998
)
10099

101100
app.setup_extension("sphinx.ext.imgconverter")
102-
app.add_transform(LatexMasterDocTransforms)
103-
app.add_post_transform(ToctreeTransforms)
104-
app.add_post_transform(handleSubSections)
101+
app.add_transform(LatexRootDocTransforms)
102+
app.add_post_transform(LatexRootDocPostTransforms)

jupyterbook_latex/transforms.py

Lines changed: 134 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from pathlib import Path
2-
from typing import Any, List
2+
from typing import Any, Dict, List, Optional, Tuple, Type
33

44
import docutils
55
import yaml
66
from sphinx import addnodes
77
from sphinx.application import Sphinx
88
from sphinx.builders.latex.nodes import thebibliography
9+
from sphinx.environment import BuildEnvironment
910
from sphinx.transforms import SphinxTransform
1011
from sphinx.transforms.post_transforms import SphinxPostTransform
1112
from sphinx.util import logging
@@ -15,27 +16,38 @@
1516
logger = logging.getLogger(__name__)
1617

1718

18-
def replaceWithNode(srcNode, toReplace, copyChildren):
19-
node = toReplace("")
20-
node["classes"] = srcNode["classes"]
21-
if copyChildren:
22-
node.children = srcNode.children
23-
srcNode.replace_self([node])
19+
def replace_node_cls(
20+
src_node: docutils.nodes.Element,
21+
node_cls: Type[docutils.nodes.Element],
22+
copy_children: bool,
23+
) -> None:
24+
"""Replace the class of a node."""
25+
node = node_cls("")
26+
node["classes"] = src_node["classes"]
27+
if copy_children:
28+
node.children = src_node.children
29+
src_node.replace_self([node])
2430

2531

26-
def depth(node, parentId):
32+
def get_section_depth(node: docutils.nodes.section, root_ids: Tuple[str, ...]) -> int:
33+
"""Get the nesting depth of the section."""
2734
d = 0
28-
while node.attributes["ids"][0] != parentId:
35+
while tuple(node.attributes["ids"]) != root_ids:
2936
d += 1
30-
node = node.parent
37+
node = getattr(node, "parent", None)
38+
if not node:
39+
break
3140
return d
3241

3342

34-
def find_parent(env, node, parentTag):
35-
while 1:
43+
def find_parent(
44+
env: BuildEnvironment, node: docutils.nodes.Element, parent_tag: str
45+
) -> Optional[docutils.nodes.Element]:
46+
"""Find the parent node."""
47+
while True:
3648
node = node.parent
3749
if node is None:
38-
return
50+
return None
3951
# parent should be a document in toc
4052
if (
4153
"docname" in node.attributes
@@ -44,7 +56,7 @@ def find_parent(env, node, parentTag):
4456
):
4557
return node
4658

47-
if node.tagname == parentTag:
59+
if node.tagname == parent_tag:
4860
return node
4961

5062
return None
@@ -63,54 +75,55 @@ def is_root_document(document: docutils.nodes.document, app: Sphinx) -> bool:
6375
return app.project.path2doc(document["source"]) == app.config.master_doc
6476

6577

66-
# Transforms and postTransforms
78+
class LatexRootDocTransforms(SphinxTransform):
79+
"""Arrange the toctrees and sections in the required structure."""
6780

68-
69-
class LatexMasterDocTransforms(SphinxTransform):
7081
default_priority = 500
7182

7283
def apply(self, **kwargs: Any) -> None:
73-
tocwrrapper = []
74-
75-
def getSectionDepth():
76-
levelDict = dict()
77-
for count, sect in enumerate(
78-
self.document.traverse(docutils.nodes.section)
79-
):
80-
if count == 0:
81-
parentSect = sect
82-
parentId = sect.attributes["ids"][0]
83-
levelDict[sect.attributes["ids"][0]] = depth(sect, parentId)
84-
return levelDict, parentSect
85-
86-
def alterNodes(sectLevelsDict, parentSect):
87-
hNode = None
88-
for sect in self.document.traverse(docutils.nodes.section):
89-
if parentSect != sect:
90-
for child in sect.children:
91-
if isinstance(child, docutils.nodes.title):
92-
if sectLevelsDict[sect.attributes["ids"][0]] == 1:
93-
hNode = H2Node("")
94-
elif sectLevelsDict[sect.attributes["ids"][0]] == 2:
95-
hNode = H3Node("")
96-
if hNode is not None:
97-
hNode.children = child.children
98-
child = hNode
99-
parentSect.append(child)
100-
replaceWithNode(sect, HiddenCellNode, False)
101-
102-
if is_root_document(self.document, self.app):
103-
# pull the toctree-wrapper and append it later to the topmost document level
104-
for node in self.document.traverse(docutils.nodes.compound):
105-
if "toctree-wrapper" in node["classes"]:
106-
tocwrrapper.append(node)
107-
replaceWithNode(node, HiddenCellNode, False)
108-
109-
sectLevelsDict, parentSect = getSectionDepth()
110-
alterNodes(sectLevelsDict, parentSect)
111-
# append the toctreewrapper to the topmost level of document
112-
if tocwrrapper:
113-
self.document.extend(tocwrrapper)
84+
85+
if not is_root_document(self.document, self.app):
86+
return
87+
88+
# pop the toctree-wrappers and append them to the topmost document level
89+
for node in self.document.traverse(docutils.nodes.compound):
90+
if "toctree-wrapper" in node["classes"]:
91+
replace_node_cls(node, HiddenCellNode, False)
92+
self.document.append(node)
93+
94+
# map the section ids to their depth in the tree (h1 == 0)
95+
section_levels: Dict[Tuple[str, ...], int] = {}
96+
for count, sect in enumerate(self.document.traverse(docutils.nodes.section)):
97+
sect_id = tuple(sect.attributes["ids"])
98+
if count == 0:
99+
top_level_section = sect
100+
top_level_id = sect_id
101+
section_levels[sect_id] = get_section_depth(sect, top_level_id)
102+
103+
if not section_levels:
104+
return
105+
106+
# flatten the AST sections under the top-level section
107+
for sect in self.document.traverse(docutils.nodes.section):
108+
if sect == top_level_section:
109+
continue
110+
# move section children to the top-level
111+
for child in sect.children:
112+
# replace H2 and H3 titles with nodes that can be custom rendered
113+
if isinstance(child, docutils.nodes.title):
114+
sect_id = tuple(sect.attributes["ids"])
115+
header_node = None
116+
if section_levels[sect_id] == 1:
117+
header_node = H2Node("")
118+
elif section_levels[sect_id] == 2:
119+
header_node = H3Node("")
120+
if header_node is not None:
121+
header_node.children = child.children
122+
child = header_node
123+
top_level_section.append(child)
124+
125+
# remove the section node
126+
replace_node_cls(sect, HiddenCellNode, False)
114127

115128

116129
class MystNbPostTransform(SphinxPostTransform):
@@ -140,80 +153,76 @@ def apply(self, **kwargs: Any) -> None:
140153

141154
for node in self.document.traverse(CellNode):
142155
if "tag_hide-cell" in node["classes"]:
143-
replaceWithNode(node, HiddenCellNode, True)
156+
replace_node_cls(node, HiddenCellNode, True)
144157
if "tag_hide-input" in node["classes"]:
145-
inputNode = node.traverse(CellInputNode)
146-
for node in inputNode:
147-
replaceWithNode(node, HiddenCellNode, True)
158+
for input_node in node.traverse(CellInputNode):
159+
replace_node_cls(input_node, HiddenCellNode, True)
148160
if "tag_hide-output" in node["classes"]:
149-
outputNode = node.traverse(CellOutputNode)
150-
for node in outputNode:
151-
replaceWithNode(node, HiddenCellNode, True)
161+
for output_node in node.traverse(CellOutputNode):
162+
replace_node_cls(output_node, HiddenCellNode, True)
152163

153164

154-
class handleSubSections(SphinxPostTransform):
155-
default_priority = 700
165+
def check_node_in_part(part, node, app: Sphinx) -> bool:
166+
""" """
167+
if "chapters" in part:
168+
nodefile = node.children[0].attributes["docname"]
169+
chapfiles = part["chapters"]
170+
for chap in chapfiles:
171+
chapname = remove_suffix(list(chap.values())[0], app.config.source_suffix)
172+
if nodefile in chapname:
173+
return True
174+
return False
156175

157-
def apply(self, **kwargs: Any) -> None:
158-
if is_root_document(self.document, self.app):
159-
for compound in self.document.traverse(docutils.nodes.compound):
160-
if "toctree-wrapper" in compound["classes"]:
161-
nodecopy = compound
162-
node = find_parent(self.app.env, nodecopy, "section")
163-
if node:
164-
replaceWithNode(compound, HiddenCellNode, False)
165-
node.append(nodecopy)
166176

177+
class LatexRootDocPostTransforms(SphinxPostTransform):
178+
"""Arrange the toctrees and bibliographies into the required structure."""
167179

168-
class ToctreeTransforms(SphinxPostTransform):
169-
default_priority = 800
180+
default_priority = 700
170181

171182
def apply(self, **kwargs: Any) -> None:
172-
def checkNodeIsInPart(part, node):
173-
if "chapters" in part:
174-
nodefile = node.children[0].attributes["docname"]
175-
chapfiles = part["chapters"]
176-
for chap in chapfiles:
177-
chapname = remove_suffix(
178-
list(chap.values())[0], self.app.config.source_suffix
179-
)
180-
if nodefile in chapname:
181-
return True
182-
return False
183+
if not is_root_document(self.document, self.app):
184+
return
183185

184-
if is_root_document(self.document, self.app):
185-
TOC_PATH = Path(self.app.confdir or self.app.srcdir).joinpath("_toc.yml")
186-
tocfile = yaml.safe_load(TOC_PATH.read_text("utf8"))
187-
188-
# store bibliography nodes to append it at the end of doc
189-
bibNodes = []
190-
for bibnode in self.document.traverse(thebibliography):
191-
bibNodes.append(bibnode)
192-
replaceWithNode(bibnode, HiddenCellNode, False)
193-
194-
for f in tocfile:
195-
if "part" in f:
196-
self.app.config["latex_toplevel_sectioning"] = "part"
197-
partname = f["part"]
198-
compoundParent = docutils.nodes.compound("")
199-
compoundParent["classes"] = "toctree-wrapper"
200-
startOfFile = addnodes.start_of_file("")
201-
startOfFile["docname"] = partname
202-
title = docutils.nodes.title(text=partname)
203-
sectionName = docutils.nodes.section("")
204-
sectionName["docname"] = partname
205-
startOfFile.append(sectionName)
206-
sectionName.append(title)
207-
compoundParent.append(startOfFile)
208-
for node in self.document.traverse(docutils.nodes.compound):
209-
if "toctree-wrapper" in node["classes"]:
210-
flag = checkNodeIsInPart(f, node)
211-
if flag:
212-
nodecopy = node
213-
replaceWithNode(node, HiddenCellNode, False)
214-
sectionName.append(nodecopy)
215-
self.document.append(compoundParent)
216-
217-
# append bib at the end
218-
if len(bibNodes):
219-
self.document.extend(bibNodes)
186+
# move toctrees to the end of their parent section
187+
for original_node in self.document.traverse(docutils.nodes.compound):
188+
if "toctree-wrapper" in original_node["classes"]:
189+
parent_node = find_parent(self.app.env, original_node, "section")
190+
if parent_node:
191+
replace_node_cls(original_node, HiddenCellNode, False)
192+
parent_node.append(original_node)
193+
194+
# store bibliography nodes to append it at the end of doc
195+
bib_nodes = []
196+
for bib_node in self.document.traverse(thebibliography):
197+
bib_nodes.append(bib_node)
198+
replace_node_cls(bib_node, HiddenCellNode, False)
199+
200+
toc_path = Path(self.app.confdir or self.app.srcdir).joinpath("_toc.yml")
201+
tocfile = yaml.safe_load(toc_path.read_text("utf8"))
202+
203+
for f in tocfile:
204+
if "part" in f:
205+
self.app.config["latex_toplevel_sectioning"] = "part"
206+
partname = f["part"]
207+
compound_parent = docutils.nodes.compound("")
208+
compound_parent["classes"] = "toctree-wrapper"
209+
start_of_file = addnodes.start_of_file("")
210+
start_of_file["docname"] = partname
211+
title = docutils.nodes.title(text=partname)
212+
section_node = docutils.nodes.section("")
213+
section_node["docname"] = partname
214+
start_of_file.append(section_node)
215+
section_node.append(title)
216+
compound_parent.append(start_of_file)
217+
for node in self.document.traverse(docutils.nodes.compound):
218+
if "toctree-wrapper" in node["classes"]:
219+
flag = check_node_in_part(f, node, self.app)
220+
if flag:
221+
original_node = node
222+
replace_node_cls(node, HiddenCellNode, False)
223+
section_node.append(original_node)
224+
self.document.append(compound_parent)
225+
226+
# append bib at the end
227+
if bib_nodes:
228+
self.document.extend(bib_nodes)

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,5 @@ strict_equality = True
6060
[flake8]
6161
max-line-length = 100
6262
extend-ignore = E203
63+
per-file-ignores =
64+
jupyterbook_latex/nodes.py:N802

tests/roots/test-partsToc/intro.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ Etiam interdum tempor augue at volutpat. Nulla sit amet volutpat elit. In vehicu
99
### This is H3
1010

1111
Sed elit mi, vestibulum sit amet porttitor in, elementum a sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec gravida faucibus enim, at venenatis ante. Curabitur viverra neque vel nulla condimentum ultrices quis a ipsum.
12+
13+
#### This is H4
14+
15+
Ipso facto

0 commit comments

Comments
 (0)