Skip to content

Commit fb7623a

Browse files
committed
Fix definition list
Previously, definition list item terms were emitted to the AST as a RefRole (incorrect) child, leaving the term field of the DefinitionListItem node empty.
1 parent 4ca4b9a commit fb7623a

File tree

6 files changed

+85
-3
lines changed

6 files changed

+85
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Support for deprecated versions (DOP-908).
1313

14+
### Fixed
15+
16+
- Definition list AST format.
17+
1418
## [v0.4.3] - 2020-04-15
1519

1620
### Added

snooty/eventparser.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ def consume(self, d: Iterable[Tuple[FileId, Page]]) -> None:
6262
def _iterate(self, d: n.Node, filename: FileId) -> None:
6363
self._on_object_enter_event(d, filename)
6464
if isinstance(d, n.Parent):
65+
if isinstance(d, n.DefinitionListItem):
66+
for child in d.term:
67+
self._iterate(child, filename)
68+
6569
for child in d.children:
6670
self._iterate(child, filename)
6771
self._on_object_exit_event(d, filename)

snooty/n.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,12 @@ class Heading(Parent[InlineNode]):
203203
class DefinitionListItem(Parent[Node]):
204204
__slots__ = ("term",)
205205
type = "definitionListItem"
206-
term: List[InlineNode]
206+
term: MutableSequence[InlineNode]
207+
208+
def verify(self) -> None:
209+
super().verify()
210+
for part in self.term:
211+
part.verify()
207212

208213

209214
@dataclass

snooty/parser.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@
6262
logger = logging.getLogger(__name__)
6363

6464

65+
@dataclass
66+
class _DefinitionListTerm(n.InlineParent):
67+
"""A private node used for internal book-keeping that should not be exported to the AST."""
68+
69+
__slots__ = ()
70+
type = "definition_list_term"
71+
72+
def verify(self) -> None:
73+
assert (
74+
False
75+
), f"{self.__class__.__name__} is private and should have been removed from AST"
76+
77+
6578
class PendingLiteralInclude(PendingTask):
6679
"""Transform a literal-include directive AST node into a code node."""
6780

@@ -374,7 +387,7 @@ def dispatch_visit(self, node: docutils.nodes.Node) -> None:
374387
elif isinstance(node, rstparser.directive_argument):
375388
self.state.append(n.DirectiveArgument((line,), []))
376389
elif isinstance(node, docutils.nodes.term):
377-
self.state.append(n.RefRole((line,), [], "std", "term", "", "", None, None))
390+
self.state.append(_DefinitionListTerm((line,), []))
378391
elif isinstance(node, docutils.nodes.line_block):
379392
self.state.append(n.LineBlock((line,), []))
380393
elif isinstance(node, docutils.nodes.line):
@@ -398,6 +411,14 @@ def dispatch_departure(self, node: docutils.nodes.Node) -> None:
398411
popped = self.state.pop()
399412

400413
top_of_state = self.state[-1]
414+
415+
if isinstance(popped, _DefinitionListTerm):
416+
assert isinstance(
417+
top_of_state, n.DefinitionListItem
418+
), "Definition list terms must be children of definition list items"
419+
top_of_state.term = popped.children
420+
return
421+
401422
if not isinstance(top_of_state, NO_CHILDREN):
402423
if isinstance(top_of_state, n.Parent):
403424
top_of_state.children.append(popped)

snooty/test_parser.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,3 +1149,40 @@ def test_problematic() -> None:
11491149
page.finish(diagnostics)
11501150
assert len(diagnostics) == 1
11511151
check_ast_testing_string(page.ast, "<root><paragraph></paragraph></root>")
1152+
1153+
1154+
def test_definition_list() -> None:
1155+
path = ROOT_PATH.joinpath(Path("test.rst"))
1156+
project_config = ProjectConfig(ROOT_PATH, "", source="./")
1157+
parser = rstparser.Parser(project_config, JSONVisitor)
1158+
1159+
page, diagnostics = parse_rst(
1160+
parser,
1161+
path,
1162+
"""
1163+
A term
1164+
A definition for this term.
1165+
1166+
``Another term``
1167+
A definition for *THIS* term.""",
1168+
)
1169+
1170+
assert not diagnostics
1171+
1172+
check_ast_testing_string(
1173+
page.ast,
1174+
"""
1175+
<root>
1176+
<definitionList>
1177+
<definitionListItem>
1178+
<term><text>A term</text></term>
1179+
<paragraph><text>A definition for this term.</text></paragraph>
1180+
</definitionListItem>
1181+
<definitionListItem>
1182+
<term><literal><text>Another term</text></literal></term>
1183+
<paragraph><text>A definition for </text><emphasis><text>THIS</text></emphasis><text> term.</text></paragraph>
1184+
</definitionListItem>
1185+
</definitionList>
1186+
</root>
1187+
""",
1188+
)

snooty/util_test.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ def ast_to_testing_string(ast: Any) -> str:
2929
attr_pairs = [
3030
(k, v)
3131
for k, v in ast.items()
32-
if k not in ("argument", "value", "children", "type", "position", "options")
32+
if k
33+
not in ("argument", "term", "value", "children", "type", "position", "options")
3334
and v
3435
]
36+
3537
attr_pairs.extend((k, v) for k, v in ast.get("options", {}).items())
3638
attrs = " ".join('{}="{}"'.format(k, escape(str(v))) for k, v in attr_pairs)
3739
contents = (
@@ -47,6 +49,15 @@ def ast_to_testing_string(ast: Any) -> str:
4749
contents = (
4850
"".join(ast_to_testing_string(part) for part in ast["argument"]) + contents
4951
)
52+
53+
if "term" in ast:
54+
contents = (
55+
"<term>"
56+
+ "".join(ast_to_testing_string(part) for part in ast["term"])
57+
+ "</term>"
58+
+ contents
59+
)
60+
5061
return "<{}{}>{}</{}>".format(
5162
ast["type"], " " + attrs if attrs else "", contents, ast["type"]
5263
)

0 commit comments

Comments
 (0)