Skip to content

Commit 87411b8

Browse files
committed
🔧 MAINTAIN: add support for using all placeholders in numref and reference math in ref and numref roles
1 parent 766be15 commit 87411b8

File tree

3 files changed

+65
-51
lines changed

3 files changed

+65
-51
lines changed

sphinx_exercise/__init__.py

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from sphinx.util import logging
2020
from sphinx.util.fileutil import copy_asset
2121
from .directive import ExerciseDirective, SolutionDirective
22-
import docutils
2322
from .local_nodes import (
2423
enumerable_node,
2524
unenumerable_node,
@@ -97,31 +96,12 @@ def doctree_read(app: Sphinx, document: Node) -> None:
9796

9897
# If solution node
9998
if is_linked_node(node):
100-
target_labelid = node.get("target_label", "")
101-
target_attr = app.env.exercise_list.get(target_labelid, {})
102-
target_node = target_attr.get("node", Node)
103-
target_title = target_attr.get("title", "")
10499
sectname = "Solution to "
105-
106-
# If target node is unenumerable, fix title
107-
if is_unenumerable_node(target_node):
108-
# If title exists, append to sectname
109-
# note that math role has already been
110-
# introduced
111-
if target_title:
112-
sectname += target_title
113-
else:
114-
# If node doesn't have a title,
115-
# append "Exercise"
116-
sectname += "Exercise"
117-
# # If node is enumerable, target node number hasn't been assigned
118-
# yet so the replacing of the sectname of enumerable_targets
119-
# should take place during doctree-resolve.
120100
else:
121101
# If other node, simply add :math: to title
122102
# to allow for easy parsing in ref_node
123103
for item in node[0]:
124-
if item.__class__ == docutils.nodes.math:
104+
if isinstance(item, nodes.math):
125105
sectname += f":math:`{item.astext()}` "
126106
continue
127107
sectname += f"{item.astext()} "
@@ -186,7 +166,7 @@ def _update_linked_node_title(self, node):
186166
# Remove parans
187167
title = target_node[0]
188168

189-
if len(title) == 1 and self._is_node_type(title[0], nodes.Text):
169+
if len(title) == 1 and isinstance(title[0], nodes.Text):
190170
_ = (
191171
title[0]
192172
.replace("Exercise", "")
@@ -224,17 +204,17 @@ def _update_linked_node_title(self, node):
224204
def _update_title(self, title):
225205
inline = nodes.inline()
226206

227-
if len(title) == 1 and self._is_node_type(title[0], nodes.Text):
207+
if len(title) == 1 and isinstance(title[0], nodes.Text):
228208
_ = title[0][0].replace("(", "").replace(")", "")
229209
inline += nodes.Text(_, _)
230210
else:
231211
for ii in range(len(title)):
232212
item = title[ii]
233213

234-
if ii == 0 and self._is_node_type(item, nodes.Text):
214+
if ii == 0 and isinstance(item, nodes.Text):
235215
_ = item.replace("Exercise", "").replace("(", "").lstrip()
236216
title.replace(title[ii], nodes.Text(_, _))
237-
elif ii == len(title) - 1 and self._is_node_type(item, nodes.Text):
217+
elif ii == len(title) - 1 and isinstance(item, nodes.Text):
238218
_ = item.replace(")", "").rstrip()
239219
if _:
240220
title.replace(title[ii], nodes.Text(_, _))
@@ -244,6 +224,12 @@ def _update_title(self, title):
244224

245225
return inline
246226

227+
def _has_math_child(self, node):
228+
for item in node:
229+
if isinstance(item, nodes.math):
230+
return True
231+
return False
232+
247233
def _update_ref(self, node: Node, labelid: str) -> None:
248234
source_attr = self.env.exercise_list[labelid]
249235
source_node = source_attr.get("node", Node)
@@ -254,28 +240,28 @@ def _update_ref(self, node: Node, labelid: str) -> None:
254240
target_attr = self.env.exercise_list[target_labelid]
255241
target_node = target_attr.get("node", Node)
256242

257-
# TODO: fix node.astext()
258243
if is_enumerable_node(target_node) and node.astext() == default_title:
259244
node.replace(node[0], source_node[0][0][0])
260245
return
261246

262-
# TODO: fix ":math:"
263-
if (
264-
is_unenumerable_node(target_node)
265-
and target_attr
266-
and ":math:" in node.astext()
267-
):
268-
title = self._update_title(target_node[0])
269-
title.insert(0, nodes.Text(default_title, default_title))
270-
node.replace(node[0], title)
247+
if is_unenumerable_node(target_node) and node.astext() == default_title:
248+
if target_attr.get("title"):
249+
if self._has_math_child(target_node[0]):
250+
title = self._update_title(target_node[0])
251+
title.insert(0, nodes.Text(default_title, default_title))
252+
node.replace(node[0], title)
253+
else:
254+
text = target_attr.get("title", "")
255+
node[0].insert(len(node[0]), nodes.Text(text, text))
256+
else:
257+
node[0].insert(len(node[0]), nodes.Text("Exercise", "Exercise"))
271258
else:
272259
# If no node.astext() simply add "Exercise"
273260
if is_enumerable_node(source_node) and not node.astext():
274261
text = nodes.Text("Exercise", "Exercise")
275262
node[0].insert(0, text)
276263
return
277264

278-
# TODO: fix ":math:"
279265
if ":math:" in node.astext():
280266
title = self._update_title(source_node[0])
281267
node.replace(node[0], title)
@@ -285,11 +271,38 @@ def _update_numref(self, node, labelid):
285271
source_node = source_attr.get("node", Node)
286272
node_title = node.get("title", "")
287273

288-
if node_title and ":math:" in node.astext() and "{name}" in node_title:
289-
title = self._update_title(source_node[0])
290-
_ = node_title[: node_title.find("{name}")]
291-
title.insert(0, nodes.Text(_, _))
292-
node.replace(node[0], title)
274+
if "{name}" in node_title and self._has_math_child(source_node[0]):
275+
276+
newtitle = nodes.inline()
277+
for item in node_title.split():
278+
if item == "{name}":
279+
for _ in self._update_title(source_node[0]):
280+
newtitle += _
281+
elif item == "{number}" or item == "%s":
282+
source_docname = source_attr.get("docname", "")
283+
source_type = self.domain.get_enumerable_node_type(source_node)
284+
source_number = self.domain.get_fignumber(
285+
self.env, self.builder, source_type, source_docname, source_node
286+
)
287+
source_num = ".".join(map(str, source_number))
288+
newtitle += nodes.Text(source_num, source_num)
289+
else:
290+
newtitle += nodes.Text(item, item)
291+
newtitle += nodes.Text(" ", " ")
292+
293+
if newtitle[len(newtitle) - 1].astext() == " ":
294+
newtitle.pop()
295+
node.replace(node[0], newtitle)
296+
297+
def _get_refuri(self, node):
298+
id_ = ""
299+
if node.get("refuri", ""):
300+
id_ = node.get("refuri", "")
301+
302+
if node.get("refid", ""):
303+
id_ = node.get("refid", "")
304+
305+
return id_.split("#")[-1]
293306

294307
def process(self, doctree: nodes.document, docname: str) -> None:
295308

@@ -301,18 +314,17 @@ def process(self, doctree: nodes.document, docname: str) -> None:
301314
for node in doctree.traverse():
302315

303316
# If node type is ref
304-
if self._is_node_type(node, nodes.reference):
305-
labelid = node.get("refuri", "").split("#")[-1]
317+
if isinstance(node, nodes.reference):
318+
labelid = self._get_refuri(node)
306319

307320
# If extension directive referenced
308321
if labelid in self.env.exercise_list:
309-
310322
# Update displayed href text
311323
self._update_ref(node, labelid)
312324

313325
# If node type is numref
314-
if self._is_node_type(node, number_reference):
315-
labelid = node.get("refuri", "").split("#")[-1]
326+
if isinstance(node, number_reference):
327+
labelid = self._get_refuri(node)
316328

317329
# If extension directive referenced
318330
if labelid in self.env.exercise_list:

sphinx_exercise/directive.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ def run(self) -> List[Node]:
7474

7575
# Duplicate label warning
7676
if not label == "" and label in self.env.exercise_list.keys():
77-
path = self.env.doc2path(self.env.docname)[:-3]
77+
docpath = self.env.doc2path(self.env.docname)
78+
path = docpath[: docpath.rfind(".")]
7879
other_path = self.env.doc2path(self.env.exercise_list[label]["docname"])
7980
msg = f"duplicate label: {label}; other instance in {other_path}"
8081
logger.warning(msg, location=path, color="red")
@@ -146,7 +147,8 @@ def run(self):
146147

147148
# Duplicate label warning
148149
if not label == "" and label in self.env.exercise_list.keys():
149-
path = self.env.doc2path(self.env.docname)[:-3]
150+
docpath = self.env.doc2path(self.env.docname)
151+
path = docpath[: docpath.rfind(".")]
150152
other_path = self.env.doc2path(self.env.exercise_list[label]["docname"])
151153
msg = f"duplicate label: {label}; other instance in {other_path}"
152154
logger.warning(msg, location=path, color="red")

sphinx_exercise/local_nodes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616

1717

1818
def is_enumerable_node(node):
19-
return node.__class__ == enumerable_node
19+
return isinstance(node, enumerable_node)
2020

2121

2222
def is_unenumerable_node(node):
23-
return node.__class__ == unenumerable_node
23+
return isinstance(node, unenumerable_node)
2424

2525

2626
def is_linked_node(node):
27-
return node.__class__ == linked_node
27+
return isinstance(node, linked_node)
2828

2929

3030
def is_extension_node(node):

0 commit comments

Comments
 (0)