Skip to content

Commit d8ffc7f

Browse files
committed
move nested tokens to core
1 parent 8fdcc0d commit d8ffc7f

File tree

3 files changed

+138
-53
lines changed

3 files changed

+138
-53
lines changed

markdown_it/doc_renderer.py renamed to markdown_it/_doc_renderer.py

Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
"""NOTE: this will eventually be moved out of core"""
12
from contextlib import contextmanager
23
import json
3-
from typing import List, Optional, Union
4+
from typing import List
45

5-
import attr
66
import yaml
77

88
from docutils import nodes
@@ -17,54 +17,7 @@
1717
# from docutils.statemachine import StringList
1818
from docutils.utils import new_document, Reporter # noqa
1919

20-
from markdown_it.token import Token
21-
22-
23-
@attr.s(slots=True)
24-
class NestedTokens:
25-
opening: Token = attr.ib()
26-
closing: Optional[Token] = attr.ib()
27-
children: List[Union[Token, "NestedTokens"]] = attr.ib(factory=list)
28-
29-
def __getattr__(self, name):
30-
return getattr(self.opening, name)
31-
32-
def attrGet(self, name: str) -> str:
33-
""" Get the value of attribute `name`, or null if it does not exist."""
34-
return self.opening.attrGet(name)
35-
36-
37-
def get_nested(tokens: List[Token]) -> List[Union[Token, NestedTokens]]:
38-
"""
39-
"""
40-
output = []
41-
42-
tokens = list(reversed(tokens))
43-
while tokens:
44-
token = tokens.pop()
45-
46-
if token.nesting == 0:
47-
output.append(token)
48-
if token.children:
49-
token.children = get_nested(token.children)
50-
continue
51-
52-
assert token.nesting == 1, token.nesting
53-
54-
nested_tokens = [token]
55-
nesting = 1
56-
while tokens and nesting != 0:
57-
token = tokens.pop()
58-
nested_tokens.append(token)
59-
nesting += token.nesting
60-
if nesting != 0:
61-
raise ValueError(f"unclosed tokens starting {nested_tokens[0]}")
62-
63-
child = NestedTokens(nested_tokens[0], nested_tokens[-1])
64-
output.append(child)
65-
child.children = get_nested(nested_tokens[1:-1])
66-
67-
return output
20+
from markdown_it.token import Token, nest_tokens
6821

6922

7023
def make_document(source_path="notset") -> nodes.document:
@@ -90,7 +43,7 @@ def __init__(self, options=None, env=None):
9043
self._level_to_elem = {0: self.document}
9144

9245
def run_render(self, tokens: List[Token]):
93-
tokens = get_nested(tokens)
46+
tokens = nest_tokens(tokens)
9447
for i, token in enumerate(tokens):
9548
if f"render_{token.type}" in self.rules:
9649
self.rules[f"render_{token.type}"](self, token)
@@ -265,7 +218,7 @@ def render_heading_open(self, token):
265218
self.current_node = section
266219

267220
def render_link_open(self, token):
268-
# TODO I think this may be already handled?
221+
# TODO I think this is maybe already handled at this point?
269222
# refuri = escape_url(token.target)
270223
refuri = target = token.attrGet("href")
271224
ref_node = nodes.reference(target, target, refuri=refuri)
@@ -338,6 +291,25 @@ def render_myst_role(self, token):
338291
self.add_line_and_source_path(node, token)
339292
self.current_node.append(node)
340293

294+
# def render_table_open(self, token):
295+
# # print(token)
296+
# # raise
297+
298+
# table = nodes.table()
299+
# table["classes"] += ["colwidths-auto"]
300+
# self.add_line_and_source_path(table, token)
301+
302+
# thead = nodes.thead()
303+
# # TODO there can never be more than one header row (at least in mardown-it)
304+
# header = token.children[0].children[0]
305+
# for hrow in header.children:
306+
# nodes.t
307+
# style = hrow.attrGet("style")
308+
309+
# tgroup = nodes.tgroup(cols)
310+
# table += tgroup
311+
# tgroup += thead
312+
341313

342314
def dict_to_docinfo(data):
343315
"""Render a key/val pair as a docutils field node."""

markdown_it/token.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Optional, Tuple
1+
from typing import List, Optional, Tuple, Union
22

33
import attr
44

@@ -95,3 +95,57 @@ def as_dict(self, children=True, filter=None, dict_factory=dict):
9595
return attr.asdict(
9696
self, recurse=children, filter=filter, dict_factory=dict_factory
9797
)
98+
99+
100+
@attr.s(slots=True)
101+
class NestedTokens:
102+
"""A class that closely resembles a Token,
103+
but for a an opening/closing Token pair, and their containing children.
104+
"""
105+
106+
opening: Token = attr.ib()
107+
closing: Optional[Token] = attr.ib()
108+
children: List[Union[Token, "NestedTokens"]] = attr.ib(factory=list)
109+
110+
def __getattr__(self, name):
111+
return getattr(self.opening, name)
112+
113+
def attrGet(self, name: str) -> str:
114+
""" Get the value of attribute `name`, or null if it does not exist."""
115+
return self.opening.attrGet(name)
116+
117+
118+
def nest_tokens(tokens: List[Token]) -> List[Union[Token, NestedTokens]]:
119+
"""Convert the token stream to a list of tokens and nested tokens.
120+
121+
``NestedTokens`` contain the open and close tokens and a list of children
122+
of all tokens in between (recursively nested)
123+
"""
124+
output = []
125+
126+
tokens = list(reversed(tokens))
127+
while tokens:
128+
token = tokens.pop()
129+
130+
if token.nesting == 0:
131+
output.append(token)
132+
if token.children:
133+
token.children = nest_tokens(token.children)
134+
continue
135+
136+
assert token.nesting == 1, token.nesting
137+
138+
nested_tokens = [token]
139+
nesting = 1
140+
while tokens and nesting != 0:
141+
token = tokens.pop()
142+
nested_tokens.append(token)
143+
nesting += token.nesting
144+
if nesting != 0:
145+
raise ValueError(f"unclosed tokens starting {nested_tokens[0]}")
146+
147+
child = NestedTokens(nested_tokens[0], nested_tokens[-1])
148+
output.append(child)
149+
child.children = nest_tokens(nested_tokens[1:-1])
150+
151+
return output

tests/test_api/test_token.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from markdown_it.token import Token, nest_tokens, NestedTokens
2+
3+
4+
def test_token():
5+
token = Token("name", "tag", 0)
6+
assert token.as_dict() == {
7+
"type": "name",
8+
"tag": "tag",
9+
"nesting": 0,
10+
"attrs": None,
11+
"map": None,
12+
"level": 0,
13+
"children": None,
14+
"content": "",
15+
"markup": "",
16+
"info": "",
17+
"meta": None,
18+
"block": False,
19+
"hidden": False,
20+
}
21+
token.attrSet("a", "b")
22+
assert token.attrGet("a") == "b"
23+
token.attrJoin("a", "c")
24+
assert token.attrGet("a") == "b c"
25+
token.attrPush(["x", "y"])
26+
assert token.attrGet("x") == "y"
27+
assert token.attrIndex("a") == 0
28+
assert token.attrIndex("x") == 1
29+
assert token.attrIndex("j") == -1
30+
31+
32+
def test_nest_tokens():
33+
tokens = nest_tokens(
34+
[
35+
Token("start", "", 0),
36+
Token("open", "", 1),
37+
Token("open_inner", "", 1),
38+
Token("inner", "", 0),
39+
Token("close_inner", "", -1),
40+
Token("close", "", -1),
41+
Token("end", "", 0),
42+
]
43+
)
44+
assert [t.type for t in tokens] == ["start", "open", "end"]
45+
assert isinstance(tokens[0], Token)
46+
assert isinstance(tokens[1], NestedTokens)
47+
assert isinstance(tokens[2], Token)
48+
49+
nested = tokens[1]
50+
assert nested.opening.type == "open"
51+
assert nested.closing.type == "close"
52+
assert len(nested.children) == 1
53+
assert nested.children[0].type == "open_inner"
54+
55+
nested2 = nested.children[0]
56+
assert nested2.opening.type == "open_inner"
57+
assert nested2.closing.type == "close_inner"
58+
assert len(nested2.children) == 1
59+
assert nested2.children[0].type == "inner"

0 commit comments

Comments
 (0)