From ee6d771464896eda803b911bf542d80968b2cde3 Mon Sep 17 00:00:00 2001 From: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com> Date: Sat, 19 Feb 2022 11:31:54 +0200 Subject: [PATCH 1/2] Move from attrs to dataclasses --- .pre-commit-config.yaml | 1 - markdown_it/_compat.py | 10 +++ markdown_it/ruler.py | 16 ++-- markdown_it/rules_inline/state_inline.py | 22 +++--- markdown_it/token.py | 94 ++++++++---------------- setup.cfg | 1 - tests/test_api/test_token.py | 2 +- 7 files changed, 60 insertions(+), 86 deletions(-) create mode 100644 markdown_it/_compat.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da6ae0ac..66724e1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,4 +52,3 @@ repos: rev: v0.812 hooks: - id: mypy - additional_dependencies: [attrs] diff --git a/markdown_it/_compat.py b/markdown_it/_compat.py new file mode 100644 index 00000000..759a6aee --- /dev/null +++ b/markdown_it/_compat.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from collections.abc import Mapping +import sys +from typing import Any + +if sys.version_info >= (3, 10): + dataclass_kwargs: Mapping[str, Any] = {"slots": True} +else: + dataclass_kwargs: Mapping[str, Any] = {} diff --git a/markdown_it/ruler.py b/markdown_it/ruler.py index 6975d2be..58fa828d 100644 --- a/markdown_it/ruler.py +++ b/markdown_it/ruler.py @@ -18,8 +18,10 @@ class Ruler from __future__ import annotations from collections.abc import Callable, Iterable, MutableMapping +from dataclasses import dataclass, field from typing import TYPE_CHECKING -import attr + +from markdown_it._compat import dataclass_kwargs if TYPE_CHECKING: from markdown_it import MarkdownIt @@ -50,12 +52,12 @@ def src(self, value: str) -> None: RuleFunc = Callable -@attr.s(slots=True) +@dataclass(**dataclass_kwargs) class Rule: - name: str = attr.ib() - enabled: bool = attr.ib() - fn: RuleFunc = attr.ib(repr=False) - alt: list[str] = attr.ib() + name: str + enabled: bool + fn: RuleFunc = field(repr=False) + alt: list[str] class Ruler: @@ -105,7 +107,7 @@ def at(self, ruleName: str, fn: RuleFunc, options=None): options = options or {} if index == -1: raise KeyError(f"Parser rule not found: {ruleName}") - self.__rules__[index].fn = fn + self.__rules__[index].fn = fn # type: ignore[assignment] self.__rules__[index].alt = options.get("alt", []) self.__cache__ = None diff --git a/markdown_it/rules_inline/state_inline.py b/markdown_it/rules_inline/state_inline.py index a6f72a92..9dc1427e 100644 --- a/markdown_it/rules_inline/state_inline.py +++ b/markdown_it/rules_inline/state_inline.py @@ -3,9 +3,9 @@ from collections import namedtuple from collections.abc import MutableMapping from typing import TYPE_CHECKING +from dataclasses import dataclass -import attr - +from .._compat import dataclass_kwargs from ..token import Token from ..ruler import StateBase from ..common.utils import isWhiteSpace, isPunctChar, isMdAsciiPunct @@ -14,13 +14,13 @@ from markdown_it import MarkdownIt -@attr.s(slots=True) +@dataclass(**dataclass_kwargs) class Delimiter: # Char code of the starting marker (number). - marker: int = attr.ib() + marker: int # Total length of these series of delimiters. - length: int = attr.ib() + length: int # An amount of characters before this one that's equivalent to # current one. In plain English: if this delimiter does not open @@ -28,21 +28,21 @@ class Delimiter: # # Used to skip sequences like "*****" in one step, for 1st asterisk # value will be 0, for 2nd it's 1 and so on. - jump: int = attr.ib() + jump: int # A position of the token this delimiter corresponds to. - token: int = attr.ib() + token: int # If this delimiter is matched as a valid opener, `end` will be # equal to its position, otherwise it's `-1`. - end: int = attr.ib() + end: int # Boolean flags that determine if this delimiter could open or close # an emphasis. - open: bool = attr.ib() - close: bool = attr.ib() + open: bool + close: bool - level: bool = attr.ib(default=None) + level: bool | None = None Scanned = namedtuple("Scanned", ["can_open", "can_close", "length"]) diff --git a/markdown_it/token.py b/markdown_it/token.py index 8abf72c3..5195d0df 100644 --- a/markdown_it/token.py +++ b/markdown_it/token.py @@ -1,10 +1,13 @@ from __future__ import annotations from collections.abc import Callable, MutableMapping +import copy +import dataclasses +from dataclasses import dataclass, field from typing import Any import warnings -import attr +from markdown_it._compat import dataclass_kwargs def convert_attrs(value: Any) -> Any: @@ -19,43 +22,46 @@ def convert_attrs(value: Any) -> Any: return value -@attr.s(slots=True) +@dataclass(**dataclass_kwargs) class Token: # Type of the token (string, e.g. "paragraph_open") - type: str = attr.ib() + type: str # html tag name, e.g. "p" - tag: str = attr.ib() + tag: str # Level change (number in {-1, 0, 1} set), where: # - `1` means the tag is opening # - `0` means the tag is self-closing # - `-1` means the tag is closing - nesting: int = attr.ib() + nesting: int # Html attributes. Note this differs from the upstream "list of lists" format - attrs: dict[str, str | int | float] = attr.ib(factory=dict, converter=convert_attrs) + attrs: dict[str, str | int | float] = field(default_factory=dict) # Source map info. Format: `[ line_begin, line_end ]` - map: list[int] | None = attr.ib(default=None) + map: list[int] | None = None # nesting level, the same as `state.level` - level: int = attr.ib(default=0) + level: int = 0 # An array of child nodes (inline and img tokens) - children: list[Token] | None = attr.ib(default=None) + children: list[Token] | None = None # In a case of self-closing tag (code, html, fence, etc.), # it has contents of this tag. - content: str = attr.ib(default="") + content: str = "" # '*' or '_' for emphasis, fence string for fence, etc. - markup: str = attr.ib(default="") + markup: str = "" # Additional information: # - Info string for "fence" tokens # - The value "auto" for autolink "link_open" and "link_close" tokens # - The string value of the item marker for ordered-list "list_item_open" tokens - info: str = attr.ib(default="") + info: str = "" # A place for plugins to store any arbitrary data - meta: dict = attr.ib(factory=dict) + meta: dict = field(default_factory=dict) # True for block-level tokens, false for inline tokens. # Used in renderer to calculate line breaks - block: bool = attr.ib(default=False) + block: bool = False # If it's true, ignore this element when rendering. # Used for tight lists to hide paragraphs. - hidden: bool = attr.ib(default=False) + hidden: bool = False + + def __post_init__(self): + self.attrs = convert_attrs(self.attrs) def attrIndex(self, name: str) -> int: warnings.warn( @@ -100,55 +106,13 @@ def attrJoin(self, name: str, value: str) -> None: def copy(self) -> Token: """Return a shallow copy of the instance.""" - return attr.evolve(self) + return copy.copy(self) def as_dict( - self, - *, - children: bool = True, - as_upstream: bool = True, - meta_serializer: Callable[[dict], Any] | None = None, - filter: Callable[[attr.Attribute, Any], bool] | None = None, - dict_factory: Callable[..., MutableMapping[str, Any]] = dict, + self, *, dict_factory: Callable[..., MutableMapping[str, Any]] = dict ) -> MutableMapping[str, Any]: - """Return the token as a dictionary. - - :param children: Also convert children to dicts - :param as_upstream: Ensure the output dictionary is equal to that created by markdown-it - For example, attrs are converted to null or lists - :param meta_serializer: hook for serializing ``Token.meta`` - :param filter: A callable whose return code determines whether an - attribute or element is included (``True``) or dropped (``False``). - Is called with the `attr.Attribute` as the first argument and the - value as the second argument. - :param dict_factory: A callable to produce dictionaries from. - For example, to produce ordered dictionaries instead of normal Python - dictionaries, pass in ``collections.OrderedDict``. - - """ - mapping = attr.asdict( - self, recurse=False, filter=filter, dict_factory=dict_factory # type: ignore[arg-type] - ) - if as_upstream and "attrs" in mapping: - mapping["attrs"] = ( - None - if not mapping["attrs"] - else [[k, v] for k, v in mapping["attrs"].items()] - ) - if meta_serializer and "meta" in mapping: - mapping["meta"] = meta_serializer(mapping["meta"]) - if children and mapping.get("children", None): - mapping["children"] = [ - child.as_dict( - children=children, - filter=filter, - dict_factory=dict_factory, - as_upstream=as_upstream, - meta_serializer=meta_serializer, - ) - for child in mapping["children"] - ] - return mapping + """Return the token as a dictionary.""" + return dataclasses.asdict(self, dict_factory=dict_factory) @classmethod def from_dict(cls, dct: MutableMapping[str, Any]) -> Token: @@ -159,15 +123,15 @@ def from_dict(cls, dct: MutableMapping[str, Any]) -> Token: return token -@attr.s(slots=True) +@dataclass(**dataclass_kwargs) class NestedTokens: """A class that closely resembles a Token, but for a an opening/closing Token pair, and their containing children. """ - opening: Token = attr.ib() - closing: Token = attr.ib() - children: list[Token | NestedTokens] = attr.ib(factory=list) + opening: Token + closing: Token + children: list[Token | NestedTokens] = field(default_factory=list) def __getattr__(self, name): return getattr(self.opening, name) diff --git a/setup.cfg b/setup.cfg index 652b36a0..039e592d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,6 @@ project_urls = [options] packages = find: install_requires = - attrs>=19,<22 mdurl~=0.1 typing_extensions>=3.7.4;python_version<'3.8' python_requires = >=3.7 diff --git a/tests/test_api/test_token.py b/tests/test_api/test_token.py index df4a0390..cad88fd5 100644 --- a/tests/test_api/test_token.py +++ b/tests/test_api/test_token.py @@ -9,7 +9,7 @@ def test_token(): "type": "name", "tag": "tag", "nesting": 0, - "attrs": None, + "attrs": {}, "map": None, "level": 0, "children": None, From 388f78c02e8d733c2a7a7c35a05d38fa0257a95d Mon Sep 17 00:00:00 2001 From: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com> Date: Sat, 19 Feb 2022 11:32:13 +0200 Subject: [PATCH 2/2] Update regression test data --- .../test_api/test_main/test_table_tokens.yml | 72 +++++++++---------- .../test_inline_definitions.yml | 12 ++-- .../test_references/test_store_labels.yml | 27 ++++--- .../test_references/test_use_existing_env.yml | 13 ++-- 4 files changed, 60 insertions(+), 64 deletions(-) diff --git a/tests/test_api/test_main/test_table_tokens.yml b/tests/test_api/test_main/test_table_tokens.yml index 5bac0445..ecec5e55 100644 --- a/tests/test_api/test_main/test_table_tokens.yml +++ b/tests/test_api/test_main/test_table_tokens.yml @@ -1,4 +1,4 @@ -- attrs: null +- attrs: {} block: true children: null content: '' @@ -13,7 +13,7 @@ nesting: 1 tag: table type: table_open -- attrs: null +- attrs: {} block: true children: null content: '' @@ -28,7 +28,7 @@ nesting: 1 tag: thead type: thead_open -- attrs: null +- attrs: {} block: true children: null content: '' @@ -43,7 +43,7 @@ nesting: 1 tag: tr type: tr_open -- attrs: null +- attrs: {} block: true children: null content: '' @@ -56,10 +56,10 @@ nesting: 1 tag: th type: th_open -- attrs: null +- attrs: {} block: true children: - - attrs: null + - attrs: {} block: false children: null content: Heading 1 @@ -84,7 +84,7 @@ nesting: 0 tag: '' type: inline -- attrs: null +- attrs: {} block: true children: null content: '' @@ -97,7 +97,7 @@ nesting: -1 tag: th type: th_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -110,10 +110,10 @@ nesting: 1 tag: th type: th_open -- attrs: null +- attrs: {} block: true children: - - attrs: null + - attrs: {} block: false children: null content: Heading 2 @@ -138,7 +138,7 @@ nesting: 0 tag: '' type: inline -- attrs: null +- attrs: {} block: true children: null content: '' @@ -151,7 +151,7 @@ nesting: -1 tag: th type: th_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -164,7 +164,7 @@ nesting: -1 tag: tr type: tr_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -177,7 +177,7 @@ nesting: -1 tag: thead type: thead_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -192,7 +192,7 @@ nesting: 1 tag: tbody type: tbody_open -- attrs: null +- attrs: {} block: true children: null content: '' @@ -207,7 +207,7 @@ nesting: 1 tag: tr type: tr_open -- attrs: null +- attrs: {} block: true children: null content: '' @@ -220,10 +220,10 @@ nesting: 1 tag: td type: td_open -- attrs: null +- attrs: {} block: true children: - - attrs: null + - attrs: {} block: false children: null content: Cell 1 @@ -248,7 +248,7 @@ nesting: 0 tag: '' type: inline -- attrs: null +- attrs: {} block: true children: null content: '' @@ -261,7 +261,7 @@ nesting: -1 tag: td type: td_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -274,10 +274,10 @@ nesting: 1 tag: td type: td_open -- attrs: null +- attrs: {} block: true children: - - attrs: null + - attrs: {} block: false children: null content: Cell 2 @@ -302,7 +302,7 @@ nesting: 0 tag: '' type: inline -- attrs: null +- attrs: {} block: true children: null content: '' @@ -315,7 +315,7 @@ nesting: -1 tag: td type: td_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -328,7 +328,7 @@ nesting: -1 tag: tr type: tr_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -343,7 +343,7 @@ nesting: 1 tag: tr type: tr_open -- attrs: null +- attrs: {} block: true children: null content: '' @@ -356,10 +356,10 @@ nesting: 1 tag: td type: td_open -- attrs: null +- attrs: {} block: true children: - - attrs: null + - attrs: {} block: false children: null content: Cell 3 @@ -384,7 +384,7 @@ nesting: 0 tag: '' type: inline -- attrs: null +- attrs: {} block: true children: null content: '' @@ -397,7 +397,7 @@ nesting: -1 tag: td type: td_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -410,10 +410,10 @@ nesting: 1 tag: td type: td_open -- attrs: null +- attrs: {} block: true children: - - attrs: null + - attrs: {} block: false children: null content: Cell 4 @@ -438,7 +438,7 @@ nesting: 0 tag: '' type: inline -- attrs: null +- attrs: {} block: true children: null content: '' @@ -451,7 +451,7 @@ nesting: -1 tag: td type: td_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -464,7 +464,7 @@ nesting: -1 tag: tr type: tr_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -477,7 +477,7 @@ nesting: -1 tag: tbody type: tbody_close -- attrs: null +- attrs: {} block: true children: null content: '' diff --git a/tests/test_port/test_references/test_inline_definitions.yml b/tests/test_port/test_references/test_inline_definitions.yml index 5ec210b1..176d9ac8 100644 --- a/tests/test_port/test_references/test_inline_definitions.yml +++ b/tests/test_port/test_references/test_inline_definitions.yml @@ -1,4 +1,4 @@ -- attrs: null +- attrs: {} block: true children: null content: '' @@ -17,7 +17,7 @@ nesting: 0 tag: '' type: definition -- attrs: null +- attrs: {} block: true children: null content: '' @@ -32,7 +32,7 @@ nesting: 1 tag: ul type: bullet_list_open -- attrs: null +- attrs: {} block: true children: null content: '' @@ -47,7 +47,7 @@ nesting: 1 tag: li type: list_item_open -- attrs: null +- attrs: {} block: true children: null content: '' @@ -66,7 +66,7 @@ nesting: 0 tag: '' type: definition -- attrs: null +- attrs: {} block: true children: null content: '' @@ -79,7 +79,7 @@ nesting: -1 tag: li type: list_item_close -- attrs: null +- attrs: {} block: true children: null content: '' diff --git a/tests/test_port/test_references/test_store_labels.yml b/tests/test_port/test_references/test_store_labels.yml index 79f6f74a..04585d8b 100644 --- a/tests/test_port/test_references/test_store_labels.yml +++ b/tests/test_port/test_references/test_store_labels.yml @@ -1,4 +1,4 @@ -- attrs: null +- attrs: {} block: true children: null content: '' @@ -13,12 +13,11 @@ nesting: 1 tag: p type: paragraph_open -- attrs: null +- attrs: {} block: true children: - attrs: - - - href - - ijk + href: ijk block: false children: null content: '' @@ -32,7 +31,7 @@ nesting: 1 tag: a type: link_open - - attrs: null + - attrs: {} block: false children: null content: a @@ -45,7 +44,7 @@ nesting: 0 tag: '' type: text - - attrs: null + - attrs: {} block: false children: null content: '' @@ -70,7 +69,7 @@ nesting: 0 tag: '' type: inline -- attrs: null +- attrs: {} block: true children: null content: '' @@ -83,7 +82,7 @@ nesting: -1 tag: p type: paragraph_close -- attrs: null +- attrs: {} block: true children: null content: '' @@ -98,17 +97,15 @@ nesting: 1 tag: p type: paragraph_open -- attrs: null +- attrs: {} block: true children: - attrs: - - - src - - ijk - - - alt - - '' + alt: '' + src: ijk block: false children: - - attrs: null + - attrs: {} block: false children: null content: a @@ -144,7 +141,7 @@ nesting: 0 tag: '' type: inline -- attrs: null +- attrs: {} block: true children: null content: '' diff --git a/tests/test_port/test_references/test_use_existing_env.yml b/tests/test_port/test_references/test_use_existing_env.yml index 1a723379..f1e63635 100644 --- a/tests/test_port/test_references/test_use_existing_env.yml +++ b/tests/test_port/test_references/test_use_existing_env.yml @@ -1,4 +1,4 @@ -- attrs: null +- attrs: {} block: true children: null content: '' @@ -13,12 +13,11 @@ nesting: 1 tag: p type: paragraph_open -- attrs: null +- attrs: {} block: true children: - attrs: - - - href - - abc + href: abc block: false children: null content: '' @@ -31,7 +30,7 @@ nesting: 1 tag: a type: link_open - - attrs: null + - attrs: {} block: false children: null content: a @@ -44,7 +43,7 @@ nesting: 0 tag: '' type: text - - attrs: null + - attrs: {} block: false children: null content: '' @@ -69,7 +68,7 @@ nesting: 0 tag: '' type: inline -- attrs: null +- attrs: {} block: true children: null content: ''