Skip to content

Commit cb49b2a

Browse files
authored
feat: Added possibility for shared numbering of theorems, fixing issue #64 (#161)
Added optioin to have directives share numbering. Reflected also in docs.
1 parent 97cfc7b commit cb49b2a

6 files changed

Lines changed: 123 additions & 33 deletions

File tree

docs/source/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,5 @@
5656
# MyST Parser Configuration
5757

5858
myst_enable_extensions = ["dollarmath", "amsmath"]
59+
60+
prf_realtyp_to_countertyp = {}

docs/source/options.md

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Options
22

3+
## Minimal color scheme
4+
35
This package has the option to choose a more **minimal** color scheme.
46

57
The aim is to create admonitions that are clearly different to the core text with
@@ -15,7 +17,7 @@ compared to the current default
1517

1618
To enable the `minimal` color scheme you can use the following.
1719

18-
## Jupyter Book Project
20+
### Jupyter Book Project
1921

2022
Add `proof_minimal_theme = True` to your `_config.yml`
2123

@@ -25,6 +27,62 @@ sphinx:
2527
proof_minimal_theme: true
2628
```
2729
28-
## Sphinx Project
30+
### Sphinx Project
2931
3032
Add `proof_minimal_theme = True` to your `conf.py`
33+
34+
35+
## Shared numbering
36+
37+
By default, each type of (prf-)directive has their own numbering and counter. This can be changed by setting the option `prf_realtyp_to_countertyp` to a dictionary associating to a directive which the counter of which directive it should use.
38+
39+
### Sphinx Project
40+
41+
In `conf.py`, e.g. to have a shared counter for all directives:
42+
43+
```
44+
prf_realtyp_to_countertyp = {
45+
"axiom": "theorem",
46+
"theorem": "theorem",
47+
"lemma": "theorem",
48+
"algorithm": "theorem",
49+
"definition": "theorem",
50+
"remark": "theorem",
51+
"conjecture": "theorem",
52+
"corollary": "theorem",
53+
"criterion": "theorem",
54+
"example": "theorem",
55+
"property": "theorem",
56+
"observation": "theorem",
57+
"proposition": "theorem",
58+
"assumption": "theorem",
59+
"notation": "theorem",
60+
}
61+
```
62+
63+
In the following case, the directives `lemma`, `conjecture`, `corollary` and `proposition` will share the counter with `theorem`, while `axiom` and `assumption` will share the counter with `definition`. All other directives would use their original counter.
64+
65+
66+
```
67+
prf_realtyp_to_countertyp = {
68+
"lemma": "theorem",
69+
"conjecture": "theorem",
70+
"corollary": "theorem",
71+
"proposition": "theorem",
72+
"axiom": "definition",
73+
"assumption": "definition",
74+
}
75+
```
76+
77+
````{warning}
78+
The association of a counter to a directive is not transitive: Let us consider the following configuration:
79+
80+
```
81+
prf_realtyp_to_countertyp = {
82+
"lemma": "theorem",
83+
"conjecture": "lemma",
84+
}
85+
```
86+
87+
The `lemma` and `theorem` directives share a counter, however the `conjecture` directive has a separate counter (the `lemma` counter which is **not** used by `lemma` directives).
88+
````

sphinx_proof/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def copy_asset_files(app: Sphinx, exc: Union[bool, Exception]):
8181
def setup(app: Sphinx) -> Dict[str, Any]:
8282

8383
app.add_config_value("proof_minimal_theme", False, "html")
84+
app.add_config_value("prf_realtyp_to_countertyp", {}, "html")
8485

8586
app.add_css_file("proof.css")
8687
app.connect("build-finished", copy_asset_files)

sphinx_proof/directive.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,25 @@
2020
logger = logging.getLogger(__name__)
2121

2222

23+
DEFAULT_REALTYP_TO_COUNTERTYP = {
24+
"axiom": "axiom",
25+
"theorem": "theorem",
26+
"lemma": "lemma",
27+
"algorithm": "algorithm",
28+
"definition": "definition",
29+
"remark": "remark",
30+
"conjecture": "conjecture",
31+
"corollary": "corollary",
32+
"criterion": "criterion",
33+
"example": "example",
34+
"property": "property",
35+
"observation": "observation",
36+
"proposition": "proposition",
37+
"assumption": "assumption",
38+
"notation": "notation",
39+
}
40+
41+
2342
class ElementDirective(SphinxDirective):
2443
"""A custom Sphinx Directive"""
2544

@@ -36,13 +55,16 @@ class ElementDirective(SphinxDirective):
3655

3756
def run(self) -> List[Node]:
3857
env = self.env
39-
typ = self.name.split(":")[1]
58+
realtyp = self.name.split(":")[1]
59+
countertyp = env.config.prf_realtyp_to_countertyp.get(
60+
realtyp, DEFAULT_REALTYP_TO_COUNTERTYP[realtyp]
61+
)
4062
serial_no = env.new_serialno()
4163
if not hasattr(env, "proof_list"):
4264
env.proof_list = {}
4365

4466
# If class in options add to class array
45-
classes, class_name = ["proof", typ], self.options.get("class", [])
67+
classes, class_name = ["proof", realtyp], self.options.get("class", [])
4668
if class_name:
4769
classes.extend(class_name)
4870

@@ -53,15 +75,15 @@ def run(self) -> List[Node]:
5375
node_id = f"{label}"
5476
else:
5577
self.options["noindex"] = True
56-
label = f"{typ}-{serial_no}"
57-
node_id = f"{typ}-{serial_no}"
78+
label = f"{realtyp}-{serial_no}"
79+
node_id = f"{realtyp}-{serial_no}"
5880
ids = [node_id]
5981

6082
# Duplicate label warning
6183
if not label == "" and label in env.proof_list.keys():
6284
path = env.doc2path(env.docname)[:-3]
6385
other_path = env.doc2path(env.proof_list[label]["docname"])
64-
msg = f"duplicate {typ} label '{label}', other instance in {other_path}"
86+
msg = f"duplicate {realtyp} label '{label}', other instance in {other_path}"
6587
logger.warning(msg, location=path, color="red")
6688

6789
title_text = ""
@@ -70,13 +92,13 @@ def run(self) -> List[Node]:
7092

7193
textnodes, messages = self.state.inline_text(title_text, self.lineno)
7294

73-
section = nodes.section(classes=[f"{typ}-content"], ids=["proof-content"])
95+
section = nodes.section(classes=[f"{realtyp}-content"], ids=["proof-content"])
7496
self.state.nested_parse(self.content, self.content_offset, section)
7597

7698
if "nonumber" in self.options:
7799
node = unenumerable_node()
78100
else:
79-
node_type = NODE_TYPES[typ]
101+
node_type = NODE_TYPES[countertyp]
80102
node = node_type()
81103

82104
node.document = self.state.document
@@ -88,17 +110,18 @@ def run(self) -> List[Node]:
88110
node["classes"].extend(classes)
89111
node["title"] = title_text
90112
node["label"] = label
91-
node["type"] = typ
113+
node["countertype"] = countertyp
114+
node["realtype"] = realtyp
92115

93116
env.proof_list[label] = {
94117
"docname": env.docname,
95-
"type": typ,
118+
"countertype": countertyp,
119+
"realtype": realtyp,
96120
"ids": ids,
97121
"label": label,
98122
"prio": 0,
99123
"nonumber": True if "nonumber" in self.options else False,
100124
}
101-
102125
return [node]
103126

104127

@@ -115,16 +138,16 @@ class ProofDirective(SphinxDirective):
115138
}
116139

117140
def run(self) -> List[Node]:
118-
typ = self.name.split(":")[1]
141+
realtyp = self.name.split(":")[1]
119142

120143
# If class in options add to class array
121-
classes, class_name = ["proof", typ], self.options.get("class", [])
144+
classes, class_name = ["proof", realtyp], self.options.get("class", [])
122145
if class_name:
123146
classes.extend(class_name)
124147

125-
section = nodes.admonition(classes=classes, ids=[typ])
148+
section = nodes.admonition(classes=classes, ids=[realtyp])
126149

127-
self.content[0] = "{}. ".format(typ.title()) + self.content[0]
150+
self.content[0] = "{}. ".format(realtyp.title()) + self.content[0]
128151
self.state.nested_parse(self.content, 0, section)
129152

130153
node = proof_node()

sphinx_proof/domain.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def generate(self, docnames=None) -> Tuple[Dict[str, Any], bool]:
4545
return content, True
4646

4747
proofs = self.domain.env.proof_list
48-
# {'theorem-0': {'docname': 'start/overview', 'type': 'theorem', 'ids': ['theorem-0'], 'label': 'theorem-0', 'prio': 0, 'nonumber': False}} # noqa: E501
48+
# {'theorem-0': {'docname': 'start/overview', 'realtype': 'theorem', 'countertype': 'theorem', 'ids': ['theorem-0'], 'label': 'theorem-0', 'prio': 0, 'nonumber': False}} # noqa: E501
4949

5050
# name, subtype, docname, typ, anchor, extra, qualifier, description
5151
for anchor, values in proofs.items():
@@ -57,7 +57,7 @@ def generate(self, docnames=None) -> Tuple[Dict[str, Any], bool]:
5757
anchor,
5858
values["docname"],
5959
"",
60-
values["type"],
60+
values["realtype"],
6161
)
6262
)
6363

@@ -157,11 +157,11 @@ def resolve_xref(
157157
if target in contnode[0]:
158158
number = ""
159159
if not env.proof_list[target]["nonumber"]:
160-
typ = env.proof_list[target]["type"]
160+
countertyp = env.proof_list[target]["countertype"]
161161
number = ".".join(
162-
map(str, env.toc_fignumbers[todocname][typ][target])
162+
map(str, env.toc_fignumbers[todocname][countertyp][target])
163163
)
164-
title = nodes.Text(f"{translate(match['type'].title())} {number}")
164+
title = nodes.Text(f"{translate(match["realtype"].title())} {number}")
165165
# builder, fromdocname, todocname, targetid, child, title=None
166166
return make_refnode(builder, fromdocname, todocname, target, title)
167167
else:

sphinx_proof/nodes.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
from sphinx.writers.latex import LaTeXTranslator
1414
from sphinx.locale import get_translation
1515

16+
17+
from sphinx.util import logging
18+
19+
logger = logging.getLogger(__name__)
20+
1621
MESSAGE_CATALOG_NAME = "proof"
1722
_ = get_translation(MESSAGE_CATALOG_NAME)
1823

@@ -31,17 +36,18 @@ def visit_enumerable_node(self, node: Node) -> None:
3136

3237

3338
def depart_enumerable_node(self, node: Node) -> None:
34-
typ = node.attributes.get("type", "")
39+
countertyp = node.attributes.get("countertype", "")
40+
realtyp = node.attributes.get("realtype", "")
3541
if isinstance(self, LaTeXTranslator):
36-
number = get_node_number(self, node, typ)
42+
number = get_node_number(self, node, countertyp)
3743
idx = list_rindex(self.body, latex_admonition_start) + 2
38-
self.body.insert(idx, f"{typ.title()} {number}")
44+
self.body.insert(idx, f"{realtyp.title()} {number}")
3945
self.body.append(latex_admonition_end)
4046
else:
4147
# Find index in list of 'Proof #'
42-
number = get_node_number(self, node, typ)
43-
idx = self.body.index(f"{typ} {number} ")
44-
self.body[idx] = f"{_(typ.title())} {number} "
48+
number = get_node_number(self, node, countertyp)
49+
idx = self.body.index(f"{countertyp} {number} ")
50+
self.body[idx] = f"{_(realtyp.title())} {number} "
4551
self.body.append("</div>")
4652

4753

@@ -55,18 +61,18 @@ def visit_unenumerable_node(self, node: Node) -> None:
5561

5662

5763
def depart_unenumerable_node(self, node: Node) -> None:
58-
typ = node.attributes.get("type", "")
64+
realtyp = node.attributes.get("realtype", "")
5965
title = node.attributes.get("title", "")
6066
if isinstance(self, LaTeXTranslator):
6167
idx = list_rindex(self.body, latex_admonition_start) + 2
62-
self.body.insert(idx, f"{typ.title()}")
68+
self.body.insert(idx, f"{realtyp.title()}")
6369
self.body.append(latex_admonition_end)
6470
else:
6571
if title == "":
6672
idx = list_rindex(self.body, '<p class="admonition-title">') + 1
6773
else:
6874
idx = list_rindex(self.body, title)
69-
element = f"<span>{_(typ.title())} </span>"
75+
element = f"<span>{_(realtyp.title())} </span>"
7076
self.body.insert(idx, element)
7177
self.body.append("</div>")
7278

@@ -79,10 +85,10 @@ def depart_proof_node(self, node: Node) -> None:
7985
pass
8086

8187

82-
def get_node_number(self, node: Node, typ) -> str:
88+
def get_node_number(self, node: Node, countertyp) -> str:
8389
"""Get the number for the directive node for HTML."""
8490
ids = node.attributes.get("ids", [])[0]
85-
key = typ
91+
key = countertyp
8692
if isinstance(self, LaTeXTranslator):
8793
docname = find_parent(self.builder.env, node, "section")
8894
fignumbers = self.builder.env.toc_fignumbers.get(
@@ -91,7 +97,7 @@ def get_node_number(self, node: Node, typ) -> str:
9197
else:
9298
fignumbers = self.builder.fignumbers
9399
if self.builder.name == "singlehtml":
94-
key = "%s/%s" % (self.docnames[-1], typ)
100+
key = "%s/%s" % (self.docnames[-1], countertyp)
95101
number = fignumbers.get(key, {}).get(ids, ())
96102
return ".".join(map(str, number))
97103

0 commit comments

Comments
 (0)