Skip to content

Commit cce689d

Browse files
authored
Merge pull request #62 from executablebooks/reset-directives
👌 IMPROVE: Resetting directive numbering between different directives
2 parents 60cd7f6 + 38b6da2 commit cce689d

20 files changed

+174
-48
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ __pycache__
55
.DS_Store
66
.vscode/
77
build/
8+
_build/
89
dist/
910

1011
# Unit test / coverage reports

sphinx_proof/__init__.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@
1010
from sphinx.config import Config
1111
from sphinx.application import Sphinx
1212
from sphinx.environment import BuildEnvironment
13-
from .nodes import enumerable_node, visit_enumerable_node, depart_enumerable_node
14-
from .nodes import unenumerable_node, visit_unenumerable_node, depart_unenumerable_node
13+
from .nodes import visit_enumerable_node, depart_enumerable_node
14+
from .nodes import (
15+
NODE_TYPES,
16+
unenumerable_node,
17+
visit_unenumerable_node,
18+
depart_unenumerable_node,
19+
)
1520
from .nodes import proof_node, visit_proof_node, depart_proof_node
1621
from .domain import ProofDomain
22+
from .proof_type import PROOF_TYPES
1723
from sphinx.util import logging
1824
from sphinx.util.fileutil import copy_asset
1925

@@ -46,9 +52,9 @@ def merge_proofs(
4652
def init_numfig(app: Sphinx, config: Config) -> None:
4753
"""Initialize proof numfig format."""
4854
config["numfig"] = True
49-
numfig_format = {
50-
"proof": "Proof %s",
51-
}
55+
numfig_format = {}
56+
for typ in NODE_TYPES.keys():
57+
numfig_format[typ] = typ + " %s"
5258
numfig_format.update(config.numfig_format)
5359
config.numfig_format = numfig_format
5460

@@ -83,14 +89,15 @@ def setup(app: Sphinx) -> Dict[str, Any]:
8389
html=(visit_unenumerable_node, depart_unenumerable_node),
8490
latex=(visit_unenumerable_node, depart_unenumerable_node),
8591
)
86-
app.add_enumerable_node(
87-
enumerable_node,
88-
"proof",
89-
None,
90-
singlehtml=(visit_enumerable_node, depart_enumerable_node),
91-
html=(visit_enumerable_node, depart_enumerable_node),
92-
latex=(visit_enumerable_node, depart_enumerable_node),
93-
)
92+
for node in PROOF_TYPES.keys():
93+
app.add_enumerable_node(
94+
NODE_TYPES[node],
95+
node,
96+
None,
97+
singlehtml=(visit_enumerable_node, depart_enumerable_node),
98+
html=(visit_enumerable_node, depart_enumerable_node),
99+
latex=(visit_enumerable_node, depart_enumerable_node),
100+
)
94101

95102
return {
96103
"version": "builtin",

sphinx_proof/directive.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from sphinx.util import logging
1414
from docutils.parsers.rst import directives
1515
from sphinx.util.docutils import SphinxDirective
16-
from .nodes import enumerable_node, unenumerable_node
16+
from .nodes import unenumerable_node, NODE_TYPES
1717
from .nodes import proof_node
1818

1919
logger = logging.getLogger(__name__)
@@ -37,7 +37,6 @@ def run(self) -> List[Node]:
3737
env = self.env
3838
typ = self.name.split(":")[1]
3939
serial_no = env.new_serialno()
40-
4140
if not hasattr(env, "proof_list"):
4241
env.proof_list = {}
4342

@@ -76,7 +75,8 @@ def run(self) -> List[Node]:
7675
if "nonumber" in self.options:
7776
node = unenumerable_node()
7877
else:
79-
node = enumerable_node()
78+
node_type = NODE_TYPES[typ]
79+
node = node_type()
8080

8181
node.document = self.state.document
8282
node += nodes.title(title_text, "", *textnodes)

sphinx_proof/domain.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,12 @@ def resolve_xref(
119119

120120
todocname = match["docname"]
121121
title = contnode[0]
122+
122123
if target in contnode[0]:
123124
number = ""
124125
if not env.proof_list[target]["nonumber"]:
125-
number = ".".join(
126-
map(str, env.toc_fignumbers[todocname]["proof"][target])
127-
)
126+
typ = env.proof_list[target]["type"]
127+
number = ".".join(map(str, env.toc_fignumbers[todocname][typ][target]))
128128
title = nodes.Text(f"{match['type'].title()} {number}")
129129
# builder, fromdocname, todocname, targetid, child, title=None
130130
return make_refnode(builder, fromdocname, todocname, target, title)

sphinx_proof/nodes.py

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,6 @@
1616
latex_admonition_end = "\\end{sphinxadmonition}" + CR
1717

1818

19-
class proof_node(nodes.Admonition, nodes.Element):
20-
pass
21-
22-
23-
class enumerable_node(nodes.Admonition, nodes.Element):
24-
pass
25-
26-
27-
class unenumerable_node(nodes.Admonition, nodes.Element):
28-
pass
29-
30-
3119
def visit_enumerable_node(self, node: Node) -> None:
3220
if isinstance(self, LaTeXTranslator):
3321
docname = find_parent(self.builder.env, node, "section")
@@ -40,14 +28,14 @@ def visit_enumerable_node(self, node: Node) -> None:
4028
def depart_enumerable_node(self, node: Node) -> None:
4129
typ = node.attributes.get("type", "")
4230
if isinstance(self, LaTeXTranslator):
43-
number = get_node_number(self, node)
31+
number = get_node_number(self, node, typ)
4432
idx = list_rindex(self.body, latex_admonition_start) + 2
4533
self.body.insert(idx, f"{typ.title()} {number}")
4634
self.body.append(latex_admonition_end)
4735
else:
4836
# Find index in list of 'Proof #'
49-
number = get_node_number(self, node)
50-
idx = self.body.index(f"Proof {number} ")
37+
number = get_node_number(self, node, typ)
38+
idx = self.body.index(f"{typ} {number} ")
5139
self.body[idx] = f"{typ.title()} {number} "
5240
self.body.append("</div>")
5341

@@ -86,9 +74,8 @@ def depart_proof_node(self, node: Node) -> None:
8674
pass
8775

8876

89-
def get_node_number(self, node: Node) -> str:
77+
def get_node_number(self, node: Node, typ) -> str:
9078
"""Get the number for the directive node for HTML."""
91-
key = "proof"
9279
ids = node.attributes.get("ids", [])[0]
9380
if isinstance(self, LaTeXTranslator):
9481
docname = find_parent(self.builder.env, node, "section")
@@ -97,7 +84,7 @@ def get_node_number(self, node: Node) -> str:
9784
) # Latex does not have builder.fignumbers
9885
else:
9986
fignumbers = self.builder.fignumbers
100-
number = fignumbers.get(key, {}).get(ids, ())
87+
number = fignumbers.get(typ, {}).get(ids, ())
10188
return ".".join(map(str, number))
10289

10390

@@ -127,3 +114,80 @@ def list_rindex(li, x) -> int:
127114
if li[i] == x:
128115
return i
129116
raise ValueError("{} is not in list".format(x))
117+
118+
119+
class proof_node(nodes.Admonition, nodes.Element):
120+
pass
121+
122+
123+
class axiom_node(nodes.Admonition, nodes.Element):
124+
pass
125+
126+
127+
class theorem_node(nodes.Admonition, nodes.Element):
128+
pass
129+
130+
131+
class lemma_node(nodes.Admonition, nodes.Element):
132+
pass
133+
134+
135+
class algorithm_node(nodes.Admonition, nodes.Element):
136+
pass
137+
138+
139+
class definition_node(nodes.Admonition, nodes.Element):
140+
pass
141+
142+
143+
class remark_node(nodes.Admonition, nodes.Element):
144+
pass
145+
146+
147+
class conjecture_node(nodes.Admonition, nodes.Element):
148+
pass
149+
150+
151+
class corollary_node(nodes.Admonition, nodes.Element):
152+
pass
153+
154+
155+
class criterion_node(nodes.Admonition, nodes.Element):
156+
pass
157+
158+
159+
class example_node(nodes.Admonition, nodes.Element):
160+
pass
161+
162+
163+
class property_node(nodes.Admonition, nodes.Element):
164+
pass
165+
166+
167+
class observation_node(nodes.Admonition, nodes.Element):
168+
pass
169+
170+
171+
class proposition_node(nodes.Admonition, nodes.Element):
172+
pass
173+
174+
175+
class unenumerable_node(nodes.Admonition, nodes.Element):
176+
pass
177+
178+
179+
NODE_TYPES = {
180+
"axiom": axiom_node,
181+
"theorem": theorem_node,
182+
"lemma": lemma_node,
183+
"algorithm": algorithm_node,
184+
"definition": definition_node,
185+
"remark": remark_node,
186+
"conjecture": conjecture_node,
187+
"corollary": corollary_node,
188+
"criterion": criterion_node,
189+
"example": example_node,
190+
"property": property_node,
191+
"observation": observation_node,
192+
"proposition": proposition_node,
193+
}

sphinx_proof/proof_type.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,11 @@ class PropositionDirective(ElementDirective):
9393
"axiom": AxiomDirective,
9494
"theorem": TheoremDirective,
9595
"lemma": LemmaDirective,
96+
"algorithm": AlgorithmDirective,
9697
"definition": DefinitionDirective,
9798
"remark": RemarkDirective,
9899
"conjecture": ConjectureDirective,
99100
"corollary": CorollaryDirective,
100-
"algorithm": AlgorithmDirective,
101101
"criterion": CriterionDirective,
102102
"example": ExampleDirective,
103103
"property": PropertyDirective,

tests/books/test-mybook/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ A Test Program!
99
algorithm/_algo_labeled_titled_with_classname
1010
algorithm/_algo_numbered_reference
1111
algorithm/_algo_text_reference
12+
theorem/_theorems_with_number
1213
proof/_proof_with_classname
1314
proof/_proof_with_labeled_math
1415
proof/_proof_no_classname
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
theorem 1
2+
=========
3+
4+
.. prf:theorem:: Important Theorem
5+
:label: theorem-one
6+
7+
It's true.

tests/test_html.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ def test_missing_ref(app, warnings):
1212
# Tests for algorithms
1313
@pytest.mark.sphinx("html", testroot="mybook")
1414
@pytest.mark.parametrize(
15-
"idir", ["_algo_labeled_titled_with_classname.html", "_algo_nonumber.html"]
15+
"idir",
16+
[
17+
"algorithm/_algo_labeled_titled_with_classname.html",
18+
"algorithm/_algo_nonumber.html",
19+
],
1620
)
1721
def test_algorithm(app, idir, file_regression):
1822
"""Test algorithm directive markup."""
1923
app.build()
20-
path_algo_directive = app.outdir / "algorithm" / idir
24+
path_algo_directive = app.outdir / idir
2125
assert path_algo_directive.exists()
2226

2327
# get content markup
@@ -28,12 +32,13 @@ def test_algorithm(app, idir, file_regression):
2832

2933
@pytest.mark.sphinx("html", testroot="mybook")
3034
@pytest.mark.parametrize(
31-
"idir", ["_algo_numbered_reference.html", "_algo_text_reference.html"]
35+
"idir",
36+
["algorithm/_algo_numbered_reference.html", "algorithm/_algo_text_reference.html"],
3237
)
3338
def test_reference(app, idir, file_regression):
3439
"""Test algorithm ref role markup."""
3540
app.builder.build_all()
36-
path_algo_directive = app.outdir / "algorithm" / idir
41+
path_algo_directive = app.outdir / idir
3742
assert path_algo_directive.exists()
3843
# get content markup
3944
soup = BeautifulSoup(path_algo_directive.read_text(encoding="utf8"), "html.parser")
@@ -53,21 +58,42 @@ def test_duplicate_label(app, warnings):
5358
@pytest.mark.parametrize(
5459
"idir",
5560
[
56-
"_proof_with_classname.html",
57-
"_proof_no_classname.html",
58-
"_proof_with_argument_content.html",
59-
"_proof_with_labeled_math.html",
60-
"_proof_with_unlabeled_math.html",
61+
"proof/_proof_with_classname.html",
62+
"proof/_proof_no_classname.html",
63+
"proof/_proof_with_argument_content.html",
64+
"proof/_proof_with_labeled_math.html",
65+
"proof/_proof_with_unlabeled_math.html",
6166
],
6267
)
6368
def test_proof(app, idir, file_regression):
6469
"""Test proof directive markup."""
6570
app.build()
66-
path_proof_directive = app.outdir / "proof" / idir
71+
path_proof_directive = app.outdir / idir
6772
assert path_proof_directive.exists()
6873

6974
# get content markup
7075
soup = BeautifulSoup(path_proof_directive.read_text(encoding="utf8"), "html.parser")
7176

7277
proof = soup.select("div.proof")[0]
7378
file_regression.check(str(proof), basename=idir.split(".")[0], extension=".html")
79+
80+
81+
# Tests for numbering
82+
@pytest.mark.sphinx("html", testroot="mybook")
83+
@pytest.mark.parametrize(
84+
"idir",
85+
[
86+
"algorithm/_algo_labeled_titled_with_classname.html",
87+
"theorem/_theorems_with_number.html",
88+
],
89+
)
90+
def test_numbering(app, idir, file_regression):
91+
"""Test numbering of different directives."""
92+
app.build()
93+
path_directive = app.outdir / idir
94+
assert path_directive.exists()
95+
96+
# get content markup
97+
soup = BeautifulSoup(path_directive.read_text(encoding="utf8"), "html.parser")
98+
proof = soup.select("div.proof")[0]
99+
file_regression.check(str(proof), basename=idir.split(".")[0], extension=".html")

0 commit comments

Comments
 (0)