Skip to content

Commit e61922e

Browse files
committed
♻️ REFACTOR: Move Token to dataclass
In order to remove `attrs` dependency.
1 parent cbca541 commit e61922e

File tree

2 files changed

+66
-47
lines changed

2 files changed

+66
-47
lines changed

markdown_it/token.py

Lines changed: 66 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from __future__ import annotations
22

33
from collections.abc import Callable, MutableMapping
4+
import dataclasses as dc
45
from typing import Any
56
import warnings
67

7-
import attr
8-
98

109
def convert_attrs(value: Any) -> Any:
1110
"""Convert Token.attrs set as ``None`` or ``[[key, value], ...]`` to a dict.
@@ -19,43 +18,65 @@ def convert_attrs(value: Any) -> Any:
1918
return value
2019

2120

22-
@attr.s(slots=True)
21+
@dc.dataclass()
2322
class Token:
24-
# Type of the token (string, e.g. "paragraph_open")
25-
type: str = attr.ib()
26-
# html tag name, e.g. "p"
27-
tag: str = attr.ib()
28-
# Level change (number in {-1, 0, 1} set), where:
29-
# - `1` means the tag is opening
30-
# - `0` means the tag is self-closing
31-
# - `-1` means the tag is closing
32-
nesting: int = attr.ib()
33-
# Html attributes. Note this differs from the upstream "list of lists" format
34-
attrs: dict[str, str | int | float] = attr.ib(factory=dict, converter=convert_attrs)
35-
# Source map info. Format: `[ line_begin, line_end ]`
36-
map: list[int] | None = attr.ib(default=None)
37-
# nesting level, the same as `state.level`
38-
level: int = attr.ib(default=0)
39-
# An array of child nodes (inline and img tokens)
40-
children: list[Token] | None = attr.ib(default=None)
41-
# In a case of self-closing tag (code, html, fence, etc.),
42-
# it has contents of this tag.
43-
content: str = attr.ib(default="")
44-
# '*' or '_' for emphasis, fence string for fence, etc.
45-
markup: str = attr.ib(default="")
46-
# Additional information:
47-
# - Info string for "fence" tokens
48-
# - The value "auto" for autolink "link_open" and "link_close" tokens
49-
# - The string value of the item marker for ordered-list "list_item_open" tokens
50-
info: str = attr.ib(default="")
51-
# A place for plugins to store any arbitrary data
52-
meta: dict = attr.ib(factory=dict)
53-
# True for block-level tokens, false for inline tokens.
54-
# Used in renderer to calculate line breaks
55-
block: bool = attr.ib(default=False)
56-
# If it's true, ignore this element when rendering.
57-
# Used for tight lists to hide paragraphs.
58-
hidden: bool = attr.ib(default=False)
23+
24+
type: str
25+
"""Type of the token (string, e.g. "paragraph_open")"""
26+
27+
tag: str
28+
"""HTML tag name, e.g. 'p'"""
29+
30+
nesting: int
31+
"""Level change (number in {-1, 0, 1} set), where:
32+
- `1` means the tag is opening
33+
- `0` means the tag is self-closing
34+
- `-1` means the tag is closing
35+
"""
36+
37+
attrs: dict[str, str | int | float] = dc.field(default_factory=dict)
38+
"""HTML attributes.
39+
Note this differs from the upstream "list of lists" format,
40+
although than an instance can still be initialised with this format.
41+
"""
42+
43+
map: list[int] | None = None
44+
"""Source map info. Format: `[ line_begin, line_end ]`"""
45+
46+
level: int = 0
47+
"""Nesting level, the same as `state.level`"""
48+
49+
children: list[Token] | None = None
50+
"""Array of child nodes (inline and img tokens)."""
51+
52+
content: str = ""
53+
"""Inner content, in the case of a self-closing tag (code, html, fence, etc.),"""
54+
55+
markup: str = ""
56+
"""'*' or '_' for emphasis, fence string for fence, etc."""
57+
58+
info: str = ""
59+
"""Additional information:
60+
- Info string for "fence" tokens
61+
- The value "auto" for autolink "link_open" and "link_close" tokens
62+
- The string value of the item marker for ordered-list "list_item_open" tokens
63+
"""
64+
65+
meta: dict = dc.field(default_factory=dict)
66+
"""A place for plugins to store any arbitrary data"""
67+
68+
block: bool = False
69+
"""True for block-level tokens, false for inline tokens.
70+
Used in renderer to calculate line breaks
71+
"""
72+
73+
hidden: bool = False
74+
"""If true, ignore this element when rendering.
75+
Used for tight lists to hide paragraphs.
76+
"""
77+
78+
def __post_init__(self):
79+
self.attrs = convert_attrs(self.attrs)
5980

6081
def attrIndex(self, name: str) -> int:
6182
warnings.warn(
@@ -98,17 +119,17 @@ def attrJoin(self, name: str, value: str) -> None:
98119
else:
99120
self.attrs[name] = value
100121

101-
def copy(self) -> Token:
122+
def copy(self, **changes: Any) -> Token:
102123
"""Return a shallow copy of the instance."""
103-
return attr.evolve(self)
124+
return dc.replace(self, **changes)
104125

105126
def as_dict(
106127
self,
107128
*,
108129
children: bool = True,
109130
as_upstream: bool = True,
110131
meta_serializer: Callable[[dict], Any] | None = None,
111-
filter: Callable[[attr.Attribute, Any], bool] | None = None,
132+
filter: Callable[[str, Any], bool] | None = None,
112133
dict_factory: Callable[..., MutableMapping[str, Any]] = dict,
113134
) -> MutableMapping[str, Any]:
114135
"""Return the token as a dictionary.
@@ -119,16 +140,15 @@ def as_dict(
119140
:param meta_serializer: hook for serializing ``Token.meta``
120141
:param filter: A callable whose return code determines whether an
121142
attribute or element is included (``True``) or dropped (``False``).
122-
Is called with the `attr.Attribute` as the first argument and the
123-
value as the second argument.
143+
Is called with the (key, value) pair.
124144
:param dict_factory: A callable to produce dictionaries from.
125145
For example, to produce ordered dictionaries instead of normal Python
126146
dictionaries, pass in ``collections.OrderedDict``.
127147
128148
"""
129-
mapping = attr.asdict(
130-
self, recurse=False, filter=filter, dict_factory=dict_factory # type: ignore[arg-type]
131-
)
149+
mapping = dict_factory((f.name, getattr(self, f.name)) for f in dc.fields(self))
150+
if filter:
151+
mapping = dict_factory((k, v) for k, v in mapping.items() if filter(k, v))
132152
if as_upstream and "attrs" in mapping:
133153
mapping["attrs"] = (
134154
None

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ classifiers = [
2626
keywords = ["markdown", "lexer", "parser", "commonmark", "markdown-it"]
2727
requires-python = ">=3.7"
2828
dependencies = [
29-
"attrs>=19,<22",
3029
"mdurl~=0.1",
3130
"typing_extensions>=3.7.4;python_version<'3.8'",
3231
]

0 commit comments

Comments
 (0)