From 022d69cc1d0e44b0cfed0bca23f55aadca9ffd33 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sat, 20 Apr 2024 22:43:43 -0400 Subject: [PATCH 01/13] Drop support for Python < 3.10 --- .github/workflows/test.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 50014ef..252674d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/pyproject.toml b/pyproject.toml index 2944ef6..4bd069d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "prosemirror" version = "0.4.0" description = "Python implementation of core ProseMirror modules for collaborative editing" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.10" authors = [ { name = "Samuel Cormier-Iijima", email = "sam@fellow.co" }, { name = "Shen Li", email = "dustet@gmail.com" }, From 04d2f67cd384b211bb8b7760f8545ef16936fa1a Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sat, 20 Apr 2024 23:28:31 -0400 Subject: [PATCH 02/13] Enable UP rules and use 3.10 idioms for typechecking --- prosemirror/model/content.py | 75 +++++----- prosemirror/model/diff.py | 8 +- prosemirror/model/fragment.py | 38 +++-- prosemirror/model/from_dom.py | 177 +++++++++++------------ prosemirror/model/mark.py | 18 +-- prosemirror/model/node.py | 47 +++--- prosemirror/model/replace.py | 18 +-- prosemirror/model/resolvedpos.py | 27 ++-- prosemirror/model/schema.py | 102 +++++++------ prosemirror/model/to_dom.py | 38 ++--- prosemirror/schema/basic/schema_basic.py | 6 +- prosemirror/schema/list/schema_list.py | 6 +- prosemirror/test_builder/build.py | 11 +- prosemirror/transform/attr_step.py | 8 +- prosemirror/transform/doc_attr_step.py | 8 +- prosemirror/transform/map.py | 27 ++-- prosemirror/transform/mark_step.py | 33 ++--- prosemirror/transform/replace.py | 46 +++--- prosemirror/transform/replace_step.py | 16 +- prosemirror/transform/step.py | 10 +- prosemirror/transform/structure.py | 38 +++-- prosemirror/transform/transform.py | 52 ++++--- prosemirror/utils.py | 7 +- pyproject.toml | 2 +- 24 files changed, 390 insertions(+), 428 deletions(-) diff --git a/prosemirror/model/content.py b/prosemirror/model/content.py index 3bf6217..f136061 100644 --- a/prosemirror/model/content.py +++ b/prosemirror/model/content.py @@ -3,14 +3,11 @@ from typing import ( TYPE_CHECKING, ClassVar, - Dict, - List, Literal, NamedTuple, NoReturn, Optional, TypedDict, - Union, cast, ) @@ -32,11 +29,9 @@ def __init__(self, type: "NodeType", next: "ContentMatch") -> None: class WrapCacheEntry: target: "NodeType" - computed: Optional[List["NodeType"]] + computed: list["NodeType"] | None - def __init__( - self, target: "NodeType", computed: Optional[List["NodeType"]] - ) -> None: + def __init__(self, target: "NodeType", computed: list["NodeType"] | None) -> None: self.target = target self.computed = computed @@ -57,8 +52,8 @@ class ContentMatch: empty: ClassVar["ContentMatch"] valid_end: bool - next: List[MatchEdge] - wrap_cache: List[WrapCacheEntry] + next: list[MatchEdge] + wrap_cache: list[WrapCacheEntry] def __init__(self, valid_end: bool) -> None: self.valid_end = valid_end @@ -66,7 +61,7 @@ def __init__(self, valid_end: bool) -> None: self.wrap_cache = [] @classmethod - def parse(cls, string: str, node_types: Dict[str, "NodeType"]) -> "ContentMatch": + def parse(cls, string: str, node_types: dict[str, "NodeType"]) -> "ContentMatch": stream = TokenStream(string, node_types) if stream.next() is None: return ContentMatch.empty @@ -84,11 +79,11 @@ def match_type(self, type: "NodeType") -> Optional["ContentMatch"]: return None def match_fragment( - self, frag: Fragment, start: int = 0, end: Optional[int] = None + self, frag: Fragment, start: int = 0, end: int | None = None ) -> Optional["ContentMatch"]: if end is None: end = frag.child_count - cur: Optional["ContentMatch"] = self + cur: "ContentMatch" | None = self i = start while cur and i < end: cur = cur.match_type(frag.child(i).type) @@ -116,10 +111,10 @@ def compatible(self, other: "ContentMatch") -> bool: def fill_before( self, after: Fragment, to_end: bool = False, start_index: int = 0 - ) -> Optional[Fragment]: + ) -> Fragment | None: seen = [self] - def search(match: ContentMatch, types: List["NodeType"]) -> Optional[Fragment]: + def search(match: ContentMatch, types: list["NodeType"]) -> Fragment | None: nonlocal seen finished = match.match_fragment(after, start_index) if finished and (not to_end or finished.valid_end): @@ -138,7 +133,7 @@ def search(match: ContentMatch, types: List["NodeType"]) -> Optional[Fragment]: return search(self, []) - def find_wrapping(self, target: "NodeType") -> Optional[List["NodeType"]]: + def find_wrapping(self, target: "NodeType") -> list["NodeType"] | None: for entry in self.wrap_cache: if entry.target.name == target.name: return entry.computed @@ -146,9 +141,9 @@ def find_wrapping(self, target: "NodeType") -> Optional[List["NodeType"]]: self.wrap_cache.append(WrapCacheEntry(target, computed)) return computed - def compute_wrapping(self, target: "NodeType") -> Optional[List["NodeType"]]: + def compute_wrapping(self, target: "NodeType") -> list["NodeType"] | None: seen = {} - active: List[Active] = [{"match": self, "type": None, "via": None}] + active: list[Active] = [{"match": self, "type": None, "via": None}] while len(active): current = active.pop(0) match = current["match"] @@ -217,23 +212,23 @@ def iteratee(m: "ContentMatch", i: int) -> str: class TokenStream: - inline: Optional[bool] - tokens: List[str] + inline: bool | None + tokens: list[str] - def __init__(self, string: str, node_types: Dict[str, "NodeType"]) -> None: + def __init__(self, string: str, node_types: dict[str, "NodeType"]) -> None: self.string = string self.node_types = node_types self.inline = None self.pos = 0 self.tokens = [i for i in TOKEN_REGEX.findall(string) if i.strip()] - def next(self) -> Optional[str]: + def next(self) -> str | None: try: return self.tokens[self.pos] except IndexError: return None - def eat(self, tok: str) -> Union[int, bool]: + def eat(self, tok: str) -> int | bool: if self.next() == tok: pos = self.pos self.pos += 1 @@ -247,12 +242,12 @@ def err(self, str: str) -> NoReturn: class ChoiceExpr(TypedDict): type: Literal["choice"] - exprs: List["Expr"] + exprs: list["Expr"] class SeqExpr(TypedDict): type: Literal["seq"] - exprs: List["Expr"] + exprs: list["Expr"] class PlusExpr(TypedDict): @@ -282,7 +277,7 @@ class NameExpr(TypedDict): value: "NodeType" -Expr = Union[ChoiceExpr, SeqExpr, PlusExpr, StarExpr, OptExpr, RangeExpr, NameExpr] +Expr = ChoiceExpr | SeqExpr | PlusExpr | StarExpr | OptExpr | RangeExpr | NameExpr def parse_expr(stream: TokenStream) -> Expr: @@ -350,7 +345,7 @@ def parse_expr_range(stream: TokenStream, expr: Expr) -> Expr: return {"type": "range", "min": min_, "max": max_, "expr": expr} -def resolve_name(stream: TokenStream, name: str) -> List["NodeType"]: +def resolve_name(stream: TokenStream, name: str) -> list["NodeType"]: types = stream.node_types type = types.get(name) if type: @@ -395,13 +390,13 @@ def iteratee(type: "NodeType") -> Expr: class Edge(TypedDict): term: Optional["NodeType"] - to: Optional[int] + to: int | None def nfa( expr: Expr, -) -> List[List[Edge]]: - nfa_: List[List[Edge]] = [[]] +) -> list[list[Edge]]: + nfa_: list[list[Edge]] = [[]] def node() -> int: nonlocal nfa_ @@ -409,24 +404,24 @@ def node() -> int: return len(nfa_) - 1 def edge( - from_: int, to: Optional[int] = None, term: Optional["NodeType"] = None + from_: int, to: int | None = None, term: Optional["NodeType"] = None ) -> Edge: nonlocal nfa_ edge: Edge = {"term": term, "to": to} nfa_[from_].append(edge) return edge - def connect(edges: List[Edge], to: int) -> None: + def connect(edges: list[Edge], to: int) -> None: for edge in edges: edge["to"] = to - def compile(expr: Expr, from_: int) -> List[Edge]: + def compile(expr: Expr, from_: int) -> list[Edge]: if expr["type"] == "choice": return list( reduce( lambda out, expr: [*out, *compile(expr, from_)], expr["exprs"], - cast(List[Edge], []), + cast(list[Edge], []), ) ) elif expr["type"] == "seq": @@ -477,9 +472,9 @@ def cmp(a: int, b: int) -> int: def null_from( - nfa: List[List[Edge]], + nfa: list[list[Edge]], node: int, -) -> List[int]: +) -> list[int]: result = [] def scan(n: int) -> None: @@ -499,21 +494,21 @@ def scan(n: int) -> None: class DFAState(NamedTuple): state: "NodeType" - next: List[int] + next: list[int] -def dfa(nfa: List[List[Edge]]) -> ContentMatch: +def dfa(nfa: list[list[Edge]]) -> ContentMatch: labeled = {} - def explore(states: List[int]) -> ContentMatch: + def explore(states: list[int]) -> ContentMatch: nonlocal labeled - out: List[DFAState] = [] + out: list[DFAState] = [] for node in states: for item in nfa[node]: term, to = item.get("term"), item.get("to") if not term: continue - set: Optional[List[int]] = None + set: list[int] | None = None for t in out: if t[0] == term: set = t[1] diff --git a/prosemirror/model/diff.py b/prosemirror/model/diff.py index e4f7c1a..098988f 100644 --- a/prosemirror/model/diff.py +++ b/prosemirror/model/diff.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional, TypedDict +from typing import TYPE_CHECKING, TypedDict from prosemirror.utils import text_length @@ -13,7 +13,7 @@ class Diff(TypedDict): b: int -def find_diff_start(a: "Fragment", b: "Fragment", pos: int) -> Optional[int]: +def find_diff_start(a: "Fragment", b: "Fragment", pos: int) -> int | None: i = 0 while True: if a.child_count == i or b.child_count == i: @@ -52,9 +52,7 @@ def find_diff_start(a: "Fragment", b: "Fragment", pos: int) -> Optional[int]: i += 1 -def find_diff_end( - a: "Fragment", b: "Fragment", pos_a: int, pos_b: int -) -> Optional[Diff]: +def find_diff_end(a: "Fragment", b: "Fragment", pos_a: int, pos_b: int) -> Diff | None: i_a, i_b = a.child_count, b.child_count while True: if i_a == 0 or i_b == 0: diff --git a/prosemirror/model/fragment.py b/prosemirror/model/fragment.py index 3e2b72e..47353cd 100644 --- a/prosemirror/model/fragment.py +++ b/prosemirror/model/fragment.py @@ -1,13 +1,9 @@ +from collections.abc import Callable, Iterable, Sequence from typing import ( TYPE_CHECKING, Any, - Callable, ClassVar, - Dict, - Iterable, - List, Optional, - Sequence, Union, cast, ) @@ -21,16 +17,16 @@ from .node import Node, TextNode -def retIndex(index: int, offset: int) -> Dict[str, int]: +def retIndex(index: int, offset: int) -> dict[str, int]: return {"index": index, "offset": offset} class Fragment: empty: ClassVar["Fragment"] - content: List["Node"] + content: list["Node"] size: int - def __init__(self, content: List["Node"], size: Optional[int] = None) -> None: + def __init__(self, content: list["Node"], size: int | None = None) -> None: self.content = content self.size = size if size is not None else sum(c.node_size for c in content) @@ -38,7 +34,7 @@ def nodes_between( self, from_: int, to: int, - f: Callable[["Node", int, Optional["Node"], int], Optional[bool]], + f: Callable[["Node", int, Optional["Node"], int], bool | None], node_start: int = 0, parent: Optional["Node"] = None, ) -> None: @@ -63,7 +59,7 @@ def nodes_between( i += 1 def descendants( - self, f: Callable[["Node", int, Optional["Node"], int], Optional[bool]] + self, f: Callable[["Node", int, Optional["Node"], int], bool | None] ) -> None: self.nodes_between(0, self.size, f) @@ -72,7 +68,7 @@ def text_between( from_: int, to: int, block_separator: str = "", - leaf_text: Union[Callable[["Node"], str], str] = "", + leaf_text: Callable[["Node"], str] | str = "", ) -> str: text = [] separated = True @@ -120,12 +116,12 @@ def append(self, other: "Fragment") -> "Fragment": i += 1 return Fragment(content, self.size + other.size) - def cut(self, from_: int, to: Optional[int] = None) -> "Fragment": + def cut(self, from_: int, to: int | None = None) -> "Fragment": if to is None: to = self.size if from_ == 0 and to == self.size: return self - result: List["Node"] = [] + result: list["Node"] = [] size = 0 if to <= from_: return Fragment(result, size) @@ -150,7 +146,7 @@ def cut(self, from_: int, to: Optional[int] = None) -> "Fragment": i += 1 return Fragment(result, size) - def cut_by_index(self, from_: int, to: Optional[int] = None) -> "Fragment": + def cut_by_index(self, from_: int, to: int | None = None) -> "Fragment": if from_ == to: return Fragment.empty if from_ == 0 and to == len(self.content): @@ -207,7 +203,7 @@ def for_each(self, f: Callable[["Node", int, int], Any]) -> None: p += child.node_size i += 1 - def find_diff_start(self, other: "Fragment", pos: int = 0) -> Optional[int]: + def find_diff_start(self, other: "Fragment", pos: int = 0) -> int | None: from .diff import find_diff_start return find_diff_start(self, other, pos) @@ -215,8 +211,8 @@ def find_diff_start(self, other: "Fragment", pos: int = 0) -> Optional[int]: def find_diff_end( self, other: "Fragment", - pos: Optional[int] = None, - other_pos: Optional[int] = None, + pos: int | None = None, + other_pos: int | None = None, ) -> Optional["Diff"]: from .diff import find_diff_end @@ -226,7 +222,7 @@ def find_diff_end( other_pos = other.size return find_diff_end(self, other, pos, other_pos) - def find_index(self, pos: int, round: int = -1) -> Dict[str, int]: + def find_index(self, pos: int, round: int = -1) -> dict[str, int]: if pos == 0: return retIndex(0, pos) if pos == self.size: @@ -245,7 +241,7 @@ def find_index(self, pos: int, round: int = -1) -> Dict[str, int]: i += 1 cur_pos = end - def to_json(self) -> Optional[JSONList]: + def to_json(self) -> JSONList | None: if self.content: return [item.to_json() for item in self.content] return None @@ -266,10 +262,10 @@ def from_json(cls, schema: "Schema[Any, Any]", value: Any) -> "Fragment": return cls([schema.node_from_json(item) for item in value]) @classmethod - def from_array(cls, array: List["Node"]) -> "Fragment": + def from_array(cls, array: list["Node"]) -> "Fragment": if not array: return cls.empty - joined: Optional[List["Node"]] = None + joined: list["Node"] | None = None size = 0 for i in range(len(array)): node = array[i] diff --git a/prosemirror/model/from_dom.py b/prosemirror/model/from_dom.py index e1760a1..3678331 100644 --- a/prosemirror/model/from_dom.py +++ b/prosemirror/model/from_dom.py @@ -1,7 +1,8 @@ import itertools import re +from collections.abc import Callable from dataclasses import dataclass -from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union, cast +from typing import Any, Literal, cast import lxml from lxml.cssselect import CSSSelector @@ -17,51 +18,51 @@ from .resolvedpos import ResolvedPos from .schema import MarkType, NodeType, Schema -WSType = Union[bool, Literal["full"], None] +WSType = bool | Literal["full"] | None @dataclass class DOMPosition: node: DOMNode offset: int - pos: Optional[int] = None + pos: int | None = None @dataclass(frozen=True) class ParseOptions: preserve_whitespace: WSType = None - find_positions: Optional[List[DOMPosition]] = None - from_: Optional[int] = None - to_: Optional[int] = None - top_node: Optional[Node] = None - top_match: Optional[ContentMatch] = None - context: Optional[ResolvedPos] = None - rule_from_node: Optional[Callable[[DOMNode], "ParseRule"]] = None - top_open: Optional[bool] = None + find_positions: list[DOMPosition] | None = None + from_: int | None = None + to_: int | None = None + top_node: Node | None = None + top_match: ContentMatch | None = None + context: ResolvedPos | None = None + rule_from_node: Callable[[DOMNode], "ParseRule"] | None = None + top_open: bool | None = None @dataclass class ParseRule: - tag: Optional[str] - namespace: Optional[str] - style: Optional[str] - priority: Optional[int] - consuming: Optional[bool] - context: Optional[str] - node: Optional[str] - mark: Optional[str] - clear_mark: Optional[Callable[[Mark], bool]] - ignore: Optional[bool] - close_parent: Optional[bool] - skip: Optional[bool] - attrs: Optional[Attrs] - get_attrs: Optional[Callable[[DOMNode], Union[Attrs, Literal[False], None]]] - content_element: Union[str, DOMNode, Callable[[DOMNode], DOMNode], None] - get_content: Optional[Callable[[DOMNode, Schema[Any, Any]], Fragment]] + tag: str | None + namespace: str | None + style: str | None + priority: int | None + consuming: bool | None + context: str | None + node: str | None + mark: str | None + clear_mark: Callable[[Mark], bool] | None + ignore: bool | None + close_parent: bool | None + skip: bool | None + attrs: Attrs | None + get_attrs: Callable[[DOMNode], Attrs | Literal[False] | None] | None + content_element: str | DOMNode | Callable[[DOMNode], DOMNode] | None + get_content: Callable[[DOMNode, Schema[Any, Any]], Fragment] | None preserve_whitespace: WSType @classmethod - def from_json(cls, data: Dict[str, Any]) -> "ParseRule": + def from_json(cls, data: dict[str, Any]) -> "ParseRule": return ParseRule( data.get("tag"), data.get("namespace"), @@ -84,14 +85,14 @@ def from_json(cls, data: Dict[str, Any]) -> "ParseRule": class DOMParser: - _tags: List[ParseRule] - _styles: List[ParseRule] + _tags: list[ParseRule] + _styles: list[ParseRule] _normalize_lists: bool schema: Schema[Any, Any] - rules: List[ParseRule] + rules: list[ParseRule] - def __init__(self, schema: Schema[Any, Any], rules: List[ParseRule]) -> None: + def __init__(self, schema: Schema[Any, Any], rules: list[ParseRule]) -> None: self.schema = schema self.rules = rules self._tags = [rule for rule in rules if rule.tag is not None] @@ -107,7 +108,7 @@ def __init__(self, schema: Schema[Any, Any], rules: List[ParseRule]) -> None: ]) def parse( - self, dom_: lxml.html.HtmlElement, options: Optional[ParseOptions] = None + self, dom_: lxml.html.HtmlElement, options: ParseOptions | None = None ) -> Node: if options is None: options = ParseOptions() @@ -132,9 +133,7 @@ def parse( return cast(Node, context.finish()) - def parse_slice( - self, dom_: DOMNode, options: Optional[ParseOptions] = None - ) -> Slice: + def parse_slice(self, dom_: DOMNode, options: ParseOptions | None = None) -> Slice: if options is None: options = ParseOptions(preserve_whitespace=True) @@ -145,8 +144,8 @@ def parse_slice( return Slice.max_open(cast(Fragment, context.finish())) def match_tag( - self, dom_: DOMNode, context: "ParseContext", after: Optional[ParseRule] = None - ) -> Optional[ParseRule]: + self, dom_: DOMNode, context: "ParseContext", after: ParseRule | None = None + ) -> ParseRule | None: try: i = self._tags.index(after) + 1 if after is not None else 0 except ValueError: @@ -177,8 +176,8 @@ def match_style( prop: str, value: str, context: "ParseContext", - after: Optional[ParseRule] = None, - ) -> Optional[ParseRule]: + after: ParseRule | None = None, + ) -> ParseRule | None: i = self._styles.index(after) + 1 if after is not None else 0 for rule in self._styles[i:]: @@ -208,8 +207,8 @@ def match_style( return None @classmethod - def schema_rules(cls, schema: Schema[Any, Any]) -> List[ParseRule]: - result: List[ParseRule] = [] + def schema_rules(cls, schema: Schema[Any, Any]) -> list[ParseRule]: + result: list[ParseRule] = [] def insert(rule: ParseRule) -> None: priority = rule.priority if rule.priority is not None else 50 @@ -261,7 +260,7 @@ def from_schema(cls, schema: Schema[Any, Any]) -> "DOMParser": return cast("DOMParser", schema.cached["dom_parser"]) -BLOCK_TAGS: Dict[str, bool] = { +BLOCK_TAGS: dict[str, bool] = { "address": True, "article": True, "aside": True, @@ -296,7 +295,7 @@ def from_schema(cls, schema: Schema[Any, Any]) -> "DOMParser": "ul": True, } -IGNORE_TAGS: Dict[str, bool] = { +IGNORE_TAGS: dict[str, bool] = { "head": True, "noscript": True, "object": True, @@ -305,7 +304,7 @@ def from_schema(cls, schema: Schema[Any, Any]) -> "DOMParser": "title": True, } -LIST_TAGS: Dict[str, bool] = {"ol": True, "ul": True} +LIST_TAGS: dict[str, bool] = {"ol": True, "ul": True} OPT_PRESERVE_WS = 1 @@ -314,7 +313,7 @@ def from_schema(cls, schema: Schema[Any, Any]) -> "DOMParser": def ws_options_for( - _type: Optional[NodeType], preserve_whitespace: WSType, base: int + _type: NodeType | None, preserve_whitespace: WSType, base: int ) -> int: if preserve_whitespace is not None: return (OPT_PRESERVE_WS if preserve_whitespace else 0) | ( @@ -329,29 +328,29 @@ def ws_options_for( class NodeContext: - match: Optional[ContentMatch] - content: List[Node] + match: ContentMatch | None + content: list[Node] - active_marks: List[Mark] - stash_marks: List[Mark] + active_marks: list[Mark] + stash_marks: list[Mark] - type: Optional[NodeType] + type: NodeType | None options: int - attrs: Optional[Attrs] - marks: List[Mark] - pending_marks: List[Mark] + attrs: Attrs | None + marks: list[Mark] + pending_marks: list[Mark] solid: bool def __init__( self, - _type: Optional[NodeType], - attrs: Optional[Attrs], - marks: List[Mark], - pending_marks: List[Mark], + _type: NodeType | None, + attrs: Attrs | None, + marks: list[Mark], + pending_marks: list[Mark], solid: bool, - match: Optional[ContentMatch], + match: ContentMatch | None, options: int, ) -> None: self.type = _type @@ -375,7 +374,7 @@ def __init__( self.active_marks = Mark.none self.stash_marks = [] - def find_wrapping(self, node: Node) -> Optional[List[NodeType]]: + def find_wrapping(self, node: Node) -> list[NodeType] | None: if not self.match: if not self.type: return [] @@ -399,10 +398,10 @@ def find_wrapping(self, node: Node) -> Optional[List[NodeType]]: return self.match.find_wrapping(node.type) - def finish(self, open_end: bool) -> Union[Node, Fragment]: + def finish(self, open_end: bool) -> Node | Fragment: if not self.options & OPT_PRESERVE_WS: try: - last: Optional[Node] = self.content[-1] + last: Node | None = self.content[-1] except IndexError: last = None @@ -426,8 +425,8 @@ def finish(self, open_end: bool) -> Union[Node, Fragment]: self.type.create(self.attrs, content, self.marks) if self.type else content ) - def pop_from_stash_mark(self, mark: Mark) -> Optional[Mark]: - found_mark: Optional[Mark] = None + def pop_from_stash_mark(self, mark: Mark) -> Mark | None: + found_mark: Mark | None = None for stash_mark in self.stash_marks[::-1]: if mark.eq(stash_mark): found_mark = stash_mark @@ -458,9 +457,9 @@ def inline_context(self, node: DOMNode) -> bool: class ParseContext: open: int = 0 - find: Optional[List[DOMPosition]] + find: list[DOMPosition] | None needs_block: bool - nodes: List[NodeContext] + nodes: list[NodeContext] options: ParseOptions is_open: bool parser: DOMParser @@ -585,9 +584,7 @@ def add_text_node(self, dom_: DOMNode) -> None: else: self.find_inside(dom_) - def add_element( - self, dom_: DOMNode, match_after: Optional[ParseRule] = None - ) -> None: + def add_element(self, dom_: DOMNode, match_after: ParseRule | None = None) -> None: name = dom_.tag.lower() if name in LIST_TAGS and self.parser.normalize_lists: @@ -650,12 +647,12 @@ def ignore_fallback(self, dom_: DOMNode) -> None: ): self.find_place(self.parser.schema.text("-")) - def read_styles(self, styles: List[str]) -> Optional[Tuple[List[Mark], List[Mark]]]: - add: List[Mark] = Mark.none - remove: List[Mark] = Mark.none + def read_styles(self, styles: list[str]) -> tuple[list[Mark], list[Mark]] | None: + add: list[Mark] = Mark.none + remove: list[Mark] = Mark.none for i in range(0, len(styles), 2): - after: Optional[ParseRule] = None + after: ParseRule | None = None while True: rule = self.parser.match_style(styles[i], styles[i + 1], self, after) if not rule: @@ -681,11 +678,11 @@ def read_styles(self, styles: List[str]) -> Optional[Tuple[List[Mark], List[Mark return add, remove def add_element_by_rule( - self, dom_: DOMNode, rule: ParseRule, continue_after: Optional[ParseRule] = None + self, dom_: DOMNode, rule: ParseRule, continue_after: ParseRule | None = None ) -> None: sync: bool = False - mark: Optional[Mark] = None - node_type: Optional[NodeType] = None + mark: Mark | None = None + node_type: NodeType | None = None if rule.node is not None: node_type = self.parser.schema.nodes[rule.node] @@ -731,8 +728,8 @@ def add_element_by_rule( def add_all( self, parent: DOMNode, - start_index: Optional[int] = None, - end_index: Optional[int] = None, + start_index: int | None = None, + end_index: int | None = None, ) -> None: index = start_index if start_index is not None else 0 @@ -753,8 +750,8 @@ def add_all( self.find_at_point(parent, index) def find_place(self, node: Node) -> bool: - route: Optional[List[NodeType]] = None - sync: Optional[NodeContext] = None + route: list[NodeType] | None = None + sync: NodeContext | None = None depth = self.open while depth >= 0: @@ -810,7 +807,7 @@ def insert_node(self, node: Node) -> bool: return False def enter( - self, type_: NodeType, attrs: Optional[Attrs] = None, preserve_ws: WSType = None + self, type_: NodeType, attrs: Attrs | None = None, preserve_ws: WSType = None ) -> bool: ok = self.find_place(type_.create(attrs)) if ok: @@ -821,7 +818,7 @@ def enter( def enter_inner( self, type_: NodeType, - attrs: Optional[Attrs] = None, + attrs: Attrs | None = None, solid: bool = False, preserve_ws: WSType = None, ) -> None: @@ -858,7 +855,7 @@ def close_extra(self, open_end: bool = False) -> None: self.nodes = self.nodes[: self.open + 1] - def finish(self) -> Union[Node, Fragment]: + def finish(self) -> Node | Fragment: self.open = 0 self.close_extra(self.is_open) return self.nodes[0].finish(self.is_open or bool(self.options.top_open)) @@ -953,7 +950,7 @@ def match(i: int, depth: int) -> bool: return False else: if depth > 0 or (depth == 0 and use_root): - next: Optional[NodeType] = self.nodes[depth].type + next: NodeType | None = self.nodes[depth].type elif option is not None and depth >= min_depth: next = option.node(depth - min_depth).type else: @@ -974,7 +971,7 @@ def match(i: int, depth: int) -> bool: return match(len(parts) - 1, self.open) - def textblock_from_context(self) -> Optional[NodeType]: + def textblock_from_context(self) -> NodeType | None: context = self.options.context if context: @@ -1036,7 +1033,7 @@ def remove_pending_mark(self, mark: Mark, upto: NodeContext) -> None: def normalize_list(dom_: DOMNode) -> None: child = next(iter(dom_)) - prev_item: Optional[DOMNode] = None + prev_item: DOMNode | None = None while child is not None: name = child.tag.lower() if get_node_type(child) == 1 else None @@ -1058,9 +1055,9 @@ def matches(dom_: DOMNode, selector_str: str) -> bool: return bool(dom_ in selector(dom_)) # type: ignore[operator] -def parse_styles(style: str) -> List[str]: +def parse_styles(style: str) -> list[str]: regex = r"\s*([\w-]+)\s*:\s*([^;]+)" - result: List[str] = [] + result: list[str] = [] for m in re.findall(regex, style): result.append(m[0]) @@ -1076,7 +1073,7 @@ def mark_may_apply(mark_type: MarkType, node_type: NodeType) -> bool: if not parent.allows_mark_type(mark_type): continue - seen: List[ContentMatch] = [] + seen: list[ContentMatch] = [] def scan(match: ContentMatch) -> bool: seen.append(match) @@ -1100,7 +1097,7 @@ def scan(match: ContentMatch) -> bool: return False -def find_same_mark_in_set(mark: Mark, mark_set: List[Mark]) -> Optional[Mark]: +def find_same_mark_in_set(mark: Mark, mark_set: list[Mark]) -> Mark | None: for comp in mark_set: if mark.eq(comp): return comp diff --git a/prosemirror/model/mark.py b/prosemirror/model/mark.py index fabc247..cabbadc 100644 --- a/prosemirror/model/mark.py +++ b/prosemirror/model/mark.py @@ -1,5 +1,5 @@ import copy -from typing import TYPE_CHECKING, Any, Final, List, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Final, Union, cast from prosemirror.utils import Attrs, JSONDict @@ -8,14 +8,14 @@ class Mark: - none: Final[List["Mark"]] = [] + none: Final[list["Mark"]] = [] def __init__(self, type: "MarkType", attrs: Attrs) -> None: self.type = type self.attrs = attrs - def add_to_set(self, set: List["Mark"]) -> List["Mark"]: - copy: Optional[List["Mark"]] = None + def add_to_set(self, set: list["Mark"]) -> list["Mark"]: + copy: list["Mark"] | None = None placed = False for i in range(len(set)): other = set[i] @@ -40,10 +40,10 @@ def add_to_set(self, set: List["Mark"]) -> List["Mark"]: copy.append(self) return copy - def remove_from_set(self, set: List["Mark"]) -> List["Mark"]: + def remove_from_set(self, set: list["Mark"]) -> list["Mark"]: return [item for item in set if not item.eq(self)] - def is_in_set(self, set: List["Mark"]) -> bool: + def is_in_set(self, set: list["Mark"]) -> bool: return any(item.eq(self) for item in set) def eq(self, other: "Mark") -> bool: @@ -66,10 +66,10 @@ def from_json( type = schema.marks.get(name) if not type: raise ValueError(f"There is no mark type {name} in this schema") - return type.create(cast(Optional[JSONDict], json_data.get("attrs"))) + return type.create(cast(JSONDict | None, json_data.get("attrs"))) @classmethod - def same_set(cls, a: List["Mark"], b: List["Mark"]) -> bool: + def same_set(cls, a: list["Mark"], b: list["Mark"]) -> bool: if a == b: return True if len(a) != len(b): @@ -77,7 +77,7 @@ def same_set(cls, a: List["Mark"], b: List["Mark"]) -> bool: return all(item_a.eq(item_b) for (item_a, item_b) in zip(a, b)) @classmethod - def set_from(cls, marks: Union[List["Mark"], "Mark", None]) -> List["Mark"]: + def set_from(cls, marks: Union[list["Mark"], "Mark", None]) -> list["Mark"]: if not marks: return cls.none if isinstance(marks, Mark): diff --git a/prosemirror/model/node.py b/prosemirror/model/node.py index 55d17bc..dd793e9 100644 --- a/prosemirror/model/node.py +++ b/prosemirror/model/node.py @@ -1,7 +1,6 @@ import copy -from typing import TYPE_CHECKING, Any, Callable, List, Optional, TypedDict, Union, cast - -from typing_extensions import TypeGuard +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Optional, TypedDict, TypeGuard, Union, cast from prosemirror.utils import Attrs, JSONDict, text_length @@ -30,8 +29,8 @@ def __init__( self, type: "NodeType", attrs: "Attrs", - content: Optional[Fragment], - marks: List[Mark], + content: Fragment | None, + marks: list[Mark], ) -> None: self.type = type self.attrs = attrs @@ -59,13 +58,13 @@ def nodes_between( self, from_: int, to: int, - f: Callable[["Node", int, Optional["Node"], int], Optional[bool]], + f: Callable[["Node", int, Optional["Node"], int], bool | None], start_pos: int = 0, ) -> None: self.content.nodes_between(from_, to, f, start_pos, self) def descendants( - self, f: Callable[["Node", int, Optional["Node"], int], Optional[bool]] + self, f: Callable[["Node", int, Optional["Node"], int], bool | None] ) -> None: self.nodes_between(0, self.content.size, f) @@ -80,7 +79,7 @@ def text_between( from_: int, to: int, block_separator: str = "", - leaf_text: Union[Callable[["Node"], str], str] = "", + leaf_text: Callable[["Node"], str] | str = "", ) -> str: return self.content.text_between(from_, to, block_separator, leaf_text) @@ -104,7 +103,7 @@ def has_markup( self, type: "NodeType", attrs: Optional["Attrs"] = None, - marks: Optional[List[Mark]] = None, + marks: list[Mark] | None = None, ) -> bool: return ( self.type.name == type.name @@ -112,23 +111,23 @@ def has_markup( and (Mark.same_set(self.marks, marks or Mark.none)) ) - def copy(self, content: Optional[Fragment] = None) -> "Node": + def copy(self, content: Fragment | None = None) -> "Node": if content == self.content: return self return self.__class__(self.type, self.attrs, content, self.marks) - def mark(self, marks: List[Mark]) -> "Node": + def mark(self, marks: list[Mark]) -> "Node": if marks == self.marks: return self return self.__class__(self.type, self.attrs, self.content, marks) - def cut(self, from_: int, to: Optional[int] = None) -> "Node": + def cut(self, from_: int, to: int | None = None) -> "Node": if from_ == 0 and to == self.content.size: return self return self.copy(self.content.cut(from_, to)) def slice( - self, from_: int, to: Optional[int] = None, include_parents: bool = False + self, from_: int, to: int | None = None, include_parents: bool = False ) -> Slice: if to is None: to = self.content.size @@ -256,12 +255,12 @@ def can_replace( to: int, replacement: Fragment = Fragment.empty, start: int = 0, - end: Optional[int] = None, + end: int | None = None, ) -> bool: if end is None: end = replacement.child_count one = self.content_match_at(from_).match_fragment(replacement, start, end) - two: Optional["ContentMatch"] = None + two: "ContentMatch" | None = None if one: two = one.match_fragment(self.content, to) if not two or not two.valid_end: @@ -272,12 +271,12 @@ def can_replace( return True def can_replace_with( - self, from_: int, to: int, type: "NodeType", marks: Optional[List[Mark]] = None + self, from_: int, to: int, type: "NodeType", marks: list[Mark] | None = None ) -> bool: if marks and not self.type.allows_marks(marks): return False start = self.content_match_at(from_).match_type(type) - end: Optional["ContentMatch"] = None + end: "ContentMatch" | None = None if start: end = start.match_fragment(self.content, to) return end.valid_end if end else False @@ -327,9 +326,7 @@ def to_json(self) -> JSONDict: return obj @classmethod - def from_json( - cls, schema: "Schema[Any, Any]", json_data: Union[JSONDict, str] - ) -> "Node": + def from_json(cls, schema: "Schema[Any, Any]", json_data: JSONDict | str) -> "Node": if isinstance(json_data, str): import json @@ -356,7 +353,7 @@ def __init__( type: "NodeType", attrs: "Attrs", content: str, - marks: List[Mark], + marks: list[Mark], ) -> None: super().__init__(type, attrs, None, marks) if not content: @@ -384,7 +381,7 @@ def text_between( from_: int, to: int, block_separator: str = "", - leaf_text: Union[Callable[["Node"], str], str] = "", + leaf_text: Callable[["Node"], str] | str = "", ) -> str: return self.text[from_:to] @@ -392,7 +389,7 @@ def text_between( def node_size(self) -> int: return text_length(self.text) - def mark(self, marks: List[Mark]) -> "TextNode": + def mark(self, marks: list[Mark]) -> "TextNode": return ( self if marks == self.marks @@ -404,7 +401,7 @@ def with_text(self, text: str) -> "TextNode": return self return TextNode(self.type, self.attrs, text, self.marks) - def cut(self, from_: int = 0, to: Optional[int] = None) -> "TextNode": + def cut(self, from_: int = 0, to: int | None = None) -> "TextNode": if to is None: to = text_length(self.text) if from_ == 0 and to == text_length(self.text): @@ -423,7 +420,7 @@ def to_json( return {**super().to_json(), "text": self.text} -def wrap_marks(marks: List[Mark], str: str) -> str: +def wrap_marks(marks: list[Mark], str: str) -> str: i = len(marks) - 1 while i >= 0: str = marks[i].type.name + "(" + str + ")" diff --git a/prosemirror/model/replace.py b/prosemirror/model/replace.py index a6cc4dd..8457735 100644 --- a/prosemirror/model/replace.py +++ b/prosemirror/model/replace.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, cast +from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast from prosemirror.utils import JSONDict @@ -35,7 +35,7 @@ def remove_range(content: Fragment, from_: int, to: int) -> Fragment: def insert_into( content: Fragment, dist: int, insert: Fragment, parent: Optional["Node"] -) -> Optional[Fragment]: +) -> Fragment | None: a = content.find_index(dist) index, offset = a["index"], a["offset"] child = content.maybe_child(index) @@ -85,7 +85,7 @@ def eq(self, other: "Slice") -> bool: def __str__(self) -> str: return f"{self.content}({self.open_start},{self.open_end})" - def to_json(self) -> Optional[JSONDict]: + def to_json(self) -> JSONDict | None: if not self.content.size: return None json: JSONDict = {"content": self.content.to_json()} @@ -105,7 +105,7 @@ def to_json(self) -> Optional[JSONDict]: def from_json( cls, schema: "Schema[Any, Any]", - json_data: Optional[JSONDict], + json_data: JSONDict | None, ) -> "Slice": if not json_data: return cls.empty @@ -186,7 +186,7 @@ def joinable(before: "ResolvedPos", after: "ResolvedPos", depth: int) -> "Node": return node -def add_node(child: "Node", target: List["Node"]) -> None: +def add_node(child: "Node", target: list["Node"]) -> None: last = len(target) - 1 if last >= 0 and pm_node.is_text(child) and child.same_markup(target[last]): target[last] = child.with_text(cast("TextNode", target[last]).text + child.text) @@ -198,7 +198,7 @@ def add_range( start: Optional["ResolvedPos"], end: Optional["ResolvedPos"], depth: int, - target: List["Node"], + target: list["Node"], ) -> None: node = cast("ResolvedPos", end or start).node(depth) start_index = 0 @@ -233,7 +233,7 @@ def replace_three_way( ) -> Fragment: open_start = joinable(from_, start, depth + 1) if from_.depth > depth else None open_end = joinable(end, to, depth + 1) if to.depth > depth else None - content: List["Node"] = [] + content: list["Node"] = [] add_range(None, from_, depth, content) if open_start and open_end and start.index(depth) == end.index(depth): check_join(open_start, open_end) @@ -254,7 +254,7 @@ def replace_three_way( def replace_two_way(from_: "ResolvedPos", to: "ResolvedPos", depth: int) -> Fragment: - content: List["Node"] = [] + content: list["Node"] = [] add_range(None, from_, depth, content) if from_.depth > depth: type = joinable(from_, to, depth + 1) @@ -265,7 +265,7 @@ def replace_two_way(from_: "ResolvedPos", to: "ResolvedPos", depth: int) -> Frag def prepare_slice_for_replace( slice: Slice, along: "ResolvedPos" -) -> Dict[str, "ResolvedPos"]: +) -> dict[str, "ResolvedPos"]: extra = along.depth - slice.open_start parent = along.node(extra) node = parent.copy(slice.content) diff --git a/prosemirror/model/resolvedpos.py b/prosemirror/model/resolvedpos.py index dc5e7ec..082d5d0 100644 --- a/prosemirror/model/resolvedpos.py +++ b/prosemirror/model/resolvedpos.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Callable, List, Optional, Union, cast +from collections.abc import Callable +from typing import TYPE_CHECKING, Optional, Union, cast from .mark import Mark @@ -8,14 +9,14 @@ class ResolvedPos: def __init__( - self, pos: int, path: List[Union["Node", int]], parent_offset: int + self, pos: int, path: list[Union["Node", int]], parent_offset: int ) -> None: self.pos = pos self.path = path self.depth = int(len(path) / 3 - 1) self.parent_offset = parent_offset - def resolve_depth(self, val: Optional[int] = None) -> int: + def resolve_depth(self, val: int | None = None) -> int: if val is None: return self.depth return self.depth + val if val < 0 else val @@ -31,7 +32,7 @@ def doc(self) -> "Node": def node(self, depth: int) -> "Node": return cast("Node", self.path[self.resolve_depth(depth) * 3]) - def index(self, depth: Optional[int] = None) -> int: + def index(self, depth: int | None = None) -> int: return cast(int, self.path[self.resolve_depth(depth) * 3 + 1]) def index_after(self, depth: int) -> int: @@ -40,15 +41,15 @@ def index_after(self, depth: int) -> int: 0 if depth == self.depth and not self.text_offset else 1 ) - def start(self, depth: Optional[int] = None) -> int: + def start(self, depth: int | None = None) -> int: depth = self.resolve_depth(depth) return 0 if depth == 0 else cast(int, self.path[depth * 3 - 1]) + 1 - def end(self, depth: Optional[int] = None) -> int: + def end(self, depth: int | None = None) -> int: depth = self.resolve_depth(depth) return self.start(depth) + self.node(depth).content.size - def before(self, depth: Optional[int] = None) -> int: + def before(self, depth: int | None = None) -> int: depth = self.resolve_depth(depth) if not depth: raise ValueError("There is no position before the top level node") @@ -56,7 +57,7 @@ def before(self, depth: Optional[int] = None) -> int: self.pos if depth == self.depth + 1 else cast(int, self.path[depth * 3 - 1]) ) - def after(self, depth: Optional[int] = None) -> int: + def after(self, depth: int | None = None) -> int: depth = self.resolve_depth(depth) if not depth: raise ValueError("There is no position after the top level node") @@ -89,7 +90,7 @@ def node_before(self) -> Optional["Node"]: return self.parent.child(index).cut(0, d_off) return None if index == 0 else self.parent.child(index - 1) - def pos_at_index(self, index: int, depth: Optional[int] = None) -> int: + def pos_at_index(self, index: int, depth: int | None = None) -> int: depth = self.resolve_depth(depth) node = cast("Node", self.path[depth * 3]) pos = 0 if depth == 0 else cast(int, self.path[depth * 3 - 1]) + 1 @@ -97,7 +98,7 @@ def pos_at_index(self, index: int, depth: Optional[int] = None) -> int: pos += node.child(i).node_size return pos - def marks(self) -> List["Mark"]: + def marks(self) -> list["Mark"]: parent = self.parent index = self.index() if parent.content.size == 0: @@ -119,7 +120,7 @@ def marks(self) -> List["Mark"]: i += 1 return marks - def marks_across(self, end: "ResolvedPos") -> Optional[List["Mark"]]: + def marks_across(self, end: "ResolvedPos") -> list["Mark"] | None: after = self.parent.maybe_child(self.index()) if not after or not after.is_inline: return None @@ -146,7 +147,7 @@ def shared_depth(self, pos: int) -> int: def block_range( self, other: Optional["ResolvedPos"] = None, - pred: Optional[Callable[["Node"], bool]] = None, + pred: Callable[["Node"], bool] | None = None, ) -> Optional["NodeRange"]: if other is None: other = self @@ -181,7 +182,7 @@ def __str__(self) -> str: def resolve(cls, doc: "Node", pos: int) -> "ResolvedPos": if not (pos >= 0 and pos <= doc.content.size): raise ValueError(f"Position {pos} out of range") - path: List[Union["Node", int]] = [] + path: list["Node" | int] = [] start = 0 parent_offset = pos node = doc diff --git a/prosemirror/model/schema.py b/prosemirror/model/schema.py index f21914f..1b5a1b9 100644 --- a/prosemirror/model/schema.py +++ b/prosemirror/model/schema.py @@ -1,17 +1,15 @@ +from collections.abc import Callable from typing import ( Any, - Callable, - Dict, Generic, - List, Literal, Optional, + TypeAlias, TypeVar, - Union, cast, ) -from typing_extensions import NotRequired, TypeAlias, TypedDict +from typing_extensions import NotRequired, TypedDict from prosemirror.model.content import ContentMatch from prosemirror.model.fragment import Fragment @@ -20,7 +18,7 @@ from prosemirror.utils import JSON, Attrs, JSONDict -def default_attrs(attrs: "Attributes") -> Optional[Attrs]: +def default_attrs(attrs: "Attributes") -> Attrs | None: defaults = {} for attr_name, attr in attrs.items(): if not attr.has_default: @@ -29,7 +27,7 @@ def default_attrs(attrs: "Attributes") -> Optional[Attrs]: return defaults -def compute_attrs(attrs: "Attributes", value: Optional[Attrs]) -> Attrs: +def compute_attrs(attrs: "Attributes", value: Attrs | None) -> Attrs: built = {} for name in attrs: given = None @@ -69,7 +67,7 @@ class NodeType: inline_content: bool - mark_set: Optional[List["MarkType"]] + mark_set: list["MarkType"] | None def __init__(self, name: str, schema: "Schema[Any, Any]", spec: "NodeSpec") -> None: self.name = name @@ -78,7 +76,7 @@ def __init__(self, name: str, schema: "Schema[Any, Any]", spec: "NodeSpec") -> N self.groups = spec["group"].split(" ") if "group" in spec else [] self.attrs = init_attrs(spec.get("attrs")) self.default_attrs = default_attrs(self.attrs) - self._content_match: Optional[ContentMatch] = None + self._content_match: ContentMatch | None = None self.mark_set = None self.inline_content = False self.is_block = not (spec.get("inline") or name == "text") @@ -124,16 +122,16 @@ def has_required_attrs(self) -> bool: def compatible_content(self, other: "NodeType") -> bool: return self == other or (self.content_match.compatible(other.content_match)) - def compute_attrs(self, attrs: Optional[Attrs]) -> Attrs: + def compute_attrs(self, attrs: Attrs | None) -> Attrs: if attrs is None and self.default_attrs is not None: return self.default_attrs return compute_attrs(self.attrs, attrs) def create( self, - attrs: Optional[Attrs] = None, - content: Union[Fragment, Node, List[Node], None] = None, - marks: Optional[List[Mark]] = None, + attrs: Attrs | None = None, + content: Fragment | Node | list[Node] | None = None, + marks: list[Mark] | None = None, ) -> Node: if self.is_text: raise ValueError("NodeType.create cannot construct text nodes") @@ -146,9 +144,9 @@ def create( def create_checked( self, - attrs: Optional[Attrs] = None, - content: Union[Fragment, Node, List[Node], None] = None, - marks: Optional[List[Mark]] = None, + attrs: Attrs | None = None, + content: Fragment | Node | list[Node] | None = None, + marks: list[Mark] | None = None, ) -> Node: content = Fragment.from_(content) if not self.valid_content(content): @@ -157,10 +155,10 @@ def create_checked( def create_and_fill( self, - attrs: Optional[Attrs] = None, - content: Union[Fragment, Node, List[Node], None] = None, - marks: Optional[List[Mark]] = None, - ) -> Optional[Node]: + attrs: Attrs | None = None, + content: Fragment | Node | list[Node] | None = None, + marks: list[Mark] | None = None, + ) -> Node | None: attrs = self.compute_attrs(attrs) frag = Fragment.from_(content) if frag.size: @@ -188,15 +186,15 @@ def valid_content(self, content: Fragment) -> bool: def allows_mark_type(self, mark_type: "MarkType") -> bool: return self.mark_set is None or mark_type in self.mark_set - def allows_marks(self, marks: List[Mark]) -> bool: + def allows_marks(self, marks: list[Mark]) -> bool: if self.mark_set is None: return True return all(self.allows_mark_type(mark.type) for mark in marks) - def allowed_marks(self, marks: List[Mark]) -> List[Mark]: + def allowed_marks(self, marks: list[Mark]) -> list[Mark]: if self.mark_set is None: return marks - copy: Optional[List[Mark]] = None + copy: list[Mark] | None = None for i, mark in enumerate(marks): if not self.allows_mark_type(mark.type): if not copy: @@ -212,9 +210,9 @@ def allowed_marks(self, marks: List[Mark]) -> List[Mark]: @classmethod def compile( - cls, nodes: Dict["Nodes", "NodeSpec"], schema: "Schema[Nodes, Marks]" - ) -> Dict["Nodes", "NodeType"]: - result: Dict["Nodes", "NodeType"] = {} + cls, nodes: dict["Nodes", "NodeSpec"], schema: "Schema[Nodes, Marks]" + ) -> dict["Nodes", "NodeType"]: + result: dict["Nodes", "NodeType"] = {} for name, spec in nodes.items(): result[name] = NodeType(name, schema, spec) @@ -235,7 +233,7 @@ def __repr__(self) -> str: return self.__str__() -Attributes: TypeAlias = Dict[str, "Attribute"] +Attributes: TypeAlias = dict[str, "Attribute"] class Attribute: @@ -249,8 +247,8 @@ def is_required(self) -> bool: class MarkType: - excluded: List["MarkType"] - instance: Optional[Mark] + excluded: list["MarkType"] + instance: Mark | None def __init__( self, name: str, rank: int, schema: "Schema[Any, Any]", spec: "MarkSpec" @@ -268,7 +266,7 @@ def __init__( def create( self, - attrs: Optional[Attrs] = None, + attrs: Attrs | None = None, ) -> Mark: if not attrs and self.instance: return self.instance @@ -276,8 +274,8 @@ def create( @classmethod def compile( - cls, marks: Dict["Marks", "MarkSpec"], schema: "Schema[Nodes, Marks]" - ) -> Dict["Marks", "MarkType"]: + cls, marks: dict["Marks", "MarkSpec"], schema: "Schema[Nodes, Marks]" + ) -> dict["Marks", "MarkType"]: result = {} rank = 0 for name, spec in marks.items(): @@ -285,10 +283,10 @@ def compile( rank += 1 return result - def remove_from_set(self, set_: List["Mark"]) -> List["Mark"]: + def remove_from_set(self, set_: list["Mark"]) -> list["Mark"]: return [item for item in set_ if item.type != self] - def is_in_set(self, set: List[Mark]) -> Optional[Mark]: + def is_in_set(self, set: list[Mark]) -> Mark | None: return next((item for item in set if item.type == self), None) def excludes(self, other: "MarkType") -> bool: @@ -311,13 +309,13 @@ class SchemaSpec(TypedDict, Generic[Nodes, Marks]): # determines which [parse rules](#model.NodeSpec.parseDOM) take # precedence by default, and which nodes come first in a given # [group](#model.NodeSpec.group). - nodes: Dict[Nodes, "NodeSpec"] + nodes: dict[Nodes, "NodeSpec"] # The mark types that exist in this schema. The order in which they # are provided determines the order in which [mark # sets](#model.Mark.addToSet) are sorted and in which [parse # rules](#model.MarkSpec.parseDOM) are tried. - marks: NotRequired[Dict[Marks, "MarkSpec"]] + marks: NotRequired[dict[Marks, "MarkSpec"]] # The name of the default top-level node for the schema. Defaults # to `"doc"`. @@ -344,12 +342,12 @@ class NodeSpec(TypedDict, total=False): defining: bool isolating: bool toDOM: Callable[[Node], Any] # FIXME: add types - parseDOM: List[Dict[str, Any]] # FIXME: add types + parseDOM: list[dict[str, Any]] # FIXME: add types toDebugString: Callable[[Node], str] leafText: Callable[[Node], str] -AttributeSpecs: TypeAlias = Dict[str, "AttributeSpec"] +AttributeSpecs: TypeAlias = dict[str, "AttributeSpec"] class MarkSpec(TypedDict, total=False): @@ -359,7 +357,7 @@ class MarkSpec(TypedDict, total=False): group: str spanning: bool toDOM: Callable[[Mark, bool], Any] # FIXME: add types - parseDOM: List[Dict[str, Any]] # FIXME: add types + parseDOM: list[dict[str, Any]] # FIXME: add types class AttributeSpec(TypedDict, total=False): @@ -369,9 +367,9 @@ class AttributeSpec(TypedDict, total=False): class Schema(Generic[Nodes, Marks]): spec: SchemaSpec[Nodes, Marks] - nodes: Dict[Nodes, "NodeType"] + nodes: dict[Nodes, "NodeType"] - marks: Dict[Marks, "MarkType"] + marks: dict[Marks, "MarkType"] def __init__(self, spec: SchemaSpec[Nodes, Marks]) -> None: self.spec = spec @@ -386,7 +384,7 @@ def __init__(self, spec: SchemaSpec[Nodes, Marks]) -> None: mark_expr = type.spec.get("marks") if content_expr not in content_expr_cache: content_expr_cache[content_expr] = ContentMatch.parse( - content_expr, cast(Dict[str, "NodeType"], self.nodes) + content_expr, cast(dict[str, "NodeType"], self.nodes) ) type.content_match = content_expr_cache[content_expr] @@ -408,15 +406,15 @@ def __init__(self, spec: SchemaSpec[Nodes, Marks]) -> None: ) self.top_node_type = self.nodes[cast(Nodes, self.spec.get("topNode") or "doc")] - self.cached: Dict[str, Any] = {} + self.cached: dict[str, Any] = {} self.cached["wrappings"] = {} def node( self, - type: Union[str, NodeType], - attrs: Optional[Attrs] = None, - content: Union[Fragment, Node, List[Node], None] = None, - marks: Optional[List[Mark]] = None, + type: str | NodeType, + attrs: Attrs | None = None, + content: Fragment | Node | list[Node] | None = None, + marks: list[Mark] | None = None, ) -> Node: if isinstance(type, str): type = self.node_type(type) @@ -426,7 +424,7 @@ def node( raise ValueError(f"Node type from different schema used ({type.name})") return type.create_checked(attrs, content, marks) - def text(self, text: str, marks: Optional[List[Mark]] = None) -> TextNode: + def text(self, text: str, marks: list[Mark] | None = None) -> TextNode: type = self.nodes[cast(Nodes, "text")] return TextNode( type, cast(Attrs, type.default_attrs), text, Mark.set_from(marks) @@ -434,14 +432,14 @@ def text(self, text: str, marks: Optional[List[Mark]] = None) -> TextNode: def mark( self, - type: Union[str, MarkType], - attrs: Optional[Attrs] = None, + type: str | MarkType, + attrs: Attrs | None = None, ) -> Mark: if isinstance(type, str): type = self.marks[cast(Marks, type)] return type.create(attrs) - def node_from_json(self, json_data: JSONDict) -> Union[Node, TextNode]: + def node_from_json(self, json_data: JSONDict) -> Node | TextNode: return Node.from_json(self, json_data) def mark_from_json( @@ -457,7 +455,7 @@ def node_type(self, name: str) -> NodeType: return found -def gather_marks(schema: Schema[Any, Any], marks: List[str]) -> List[MarkType]: +def gather_marks(schema: Schema[Any, Any], marks: list[str]) -> list[MarkType]: found = [] for name in marks: mark = schema.marks.get(name) diff --git a/prosemirror/model/to_dom.py b/prosemirror/model/to_dom.py index 2c4ff43..3ca3d6c 100644 --- a/prosemirror/model/to_dom.py +++ b/prosemirror/model/to_dom.py @@ -1,13 +1,7 @@ import html +from collections.abc import Callable, Mapping, Sequence from typing import ( Any, - Callable, - Dict, - List, - Mapping, - Optional, - Sequence, - Tuple, Union, cast, ) @@ -21,7 +15,7 @@ class DocumentFragment: - def __init__(self, children: List[HTMLNode]) -> None: + def __init__(self, children: list[HTMLNode]) -> None: self.children = children def __str__(self) -> str: @@ -49,7 +43,7 @@ def __str__(self) -> str: class Element(DocumentFragment): def __init__( - self, name: str, attrs: Dict[str, str], children: List[HTMLNode] + self, name: str, attrs: dict[str, str], children: list[HTMLNode] ) -> None: self.name = name self.attrs = attrs @@ -65,25 +59,25 @@ def __str__(self) -> str: return f"<{open_tag_str}>{children_str}" -HTMLOutputSpec = Union[str, Sequence[Any], Element] +HTMLOutputSpec = str | Sequence[Any] | Element class DOMSerializer: def __init__( self, - nodes: Dict[str, Callable[[Node], HTMLOutputSpec]], - marks: Dict[str, Callable[[Mark, bool], HTMLOutputSpec]], + nodes: dict[str, Callable[[Node], HTMLOutputSpec]], + marks: dict[str, Callable[[Mark, bool], HTMLOutputSpec]], ) -> None: self.nodes = nodes self.marks = marks def serialize_fragment( - self, fragment: Fragment, target: Union[Element, DocumentFragment, None] = None + self, fragment: Fragment, target: Element | DocumentFragment | None = None ) -> DocumentFragment: tgt: DocumentFragment = target or DocumentFragment(children=[]) top = tgt - active: Optional[List[Tuple[Mark, DocumentFragment]]] = None + active: list[tuple[Mark, DocumentFragment]] | None = None def each(node: Node, offset: int, index: int) -> None: nonlocal top, active @@ -140,16 +134,14 @@ def serialize_node(self, node: Node) -> HTMLNode: def serialize_mark( self, mark: Mark, inline: bool - ) -> Optional[Tuple[HTMLNode, Optional[Element]]]: + ) -> tuple[HTMLNode, Element | None] | None: to_dom = self.marks.get(mark.type.name) if to_dom: return type(self).render_spec(to_dom(mark, inline)) return None @classmethod - def render_spec( - cls, structure: HTMLOutputSpec - ) -> Tuple[HTMLNode, Optional[Element]]: + def render_spec(cls, structure: HTMLOutputSpec) -> tuple[HTMLNode, Element | None]: if isinstance(structure, str): return html.escape(structure), None if isinstance(structure, Element): @@ -157,7 +149,7 @@ def render_spec( tag_name = structure[0] if " " in tag_name[1:]: raise NotImplementedError("XML namespaces are not supported") - content_dom: Optional[Element] = None + content_dom: Element | None = None dom = Element(name=tag_name, attrs={}, children=[]) attrs = structure[1] if len(structure) > 1 else None start = 1 @@ -192,7 +184,7 @@ def from_schema(cls, schema: Schema[Any, Any]) -> "DOMSerializer": @classmethod def nodes_from_schema( cls, schema: Schema[str, Any] - ) -> Dict[str, Callable[["Node"], HTMLOutputSpec]]: + ) -> dict[str, Callable[["Node"], HTMLOutputSpec]]: result = gather_to_dom(schema.nodes) if "text" not in result: result["text"] = lambda node: node.text @@ -201,13 +193,13 @@ def nodes_from_schema( @classmethod def marks_from_schema( cls, schema: Schema[Any, Any] - ) -> Dict[str, Callable[["Mark", bool], HTMLOutputSpec]]: + ) -> dict[str, Callable[["Mark", bool], HTMLOutputSpec]]: return gather_to_dom(schema.marks) def gather_to_dom( - obj: Mapping[str, Union[NodeType, MarkType]], -) -> Dict[str, Callable[..., Any]]: + obj: Mapping[str, NodeType | MarkType], +) -> dict[str, Callable[..., Any]]: result = {} for name in obj: to_dom = obj[name].spec.get("toDOM") diff --git a/prosemirror/schema/basic/schema_basic.py b/prosemirror/schema/basic/schema_basic.py index 2c022a3..39857af 100644 --- a/prosemirror/schema/basic/schema_basic.py +++ b/prosemirror/schema/basic/schema_basic.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from prosemirror.model import Schema from prosemirror.model.schema import MarkSpec, NodeSpec @@ -9,7 +9,7 @@ pre_dom = ["pre", ["code", 0]] br_dom = ["br"] -nodes: Dict[str, NodeSpec] = { +nodes: dict[str, NodeSpec] = { "doc": {"content": "block+"}, "paragraph": { "content": "inline*", @@ -90,7 +90,7 @@ strong_dom = ["strong", 0] code_dom = ["code", 0] -marks: Dict[str, MarkSpec] = { +marks: dict[str, MarkSpec] = { "link": { "attrs": {"href": {}, "title": {"default": None}}, "inclusive": False, diff --git a/prosemirror/schema/list/schema_list.py b/prosemirror/schema/list/schema_list.py index 8d3ee87..1d84000 100644 --- a/prosemirror/schema/list/schema_list.py +++ b/prosemirror/schema/list/schema_list.py @@ -1,4 +1,4 @@ -from typing import Dict, cast +from typing import cast from prosemirror.model.schema import Nodes, NodeSpec @@ -27,8 +27,8 @@ def add(obj: "NodeSpec", props: "NodeSpec") -> "NodeSpec": def add_list_nodes( - nodes: Dict["Nodes", "NodeSpec"], item_content: str, list_group: str -) -> Dict["Nodes", "NodeSpec"]: + nodes: dict["Nodes", "NodeSpec"], item_content: str, list_group: str +) -> dict["Nodes", "NodeSpec"]: copy = nodes.copy() copy.update({ cast(Nodes, "ordered_list"): add( diff --git a/prosemirror/test_builder/build.py b/prosemirror/test_builder/build.py index fa5ecd6..0ef6767 100644 --- a/prosemirror/test_builder/build.py +++ b/prosemirror/test_builder/build.py @@ -1,7 +1,8 @@ # type: ignore import re -from typing import Any, Callable, Dict, List, Tuple, Union +from collections.abc import Callable +from typing import Any from prosemirror.model import Node, Schema from prosemirror.utils import JSONDict @@ -11,9 +12,9 @@ def flatten( schema: Schema[Any, Any], - children: List[Union[Node, JSONDict, str]], + children: list[Node | JSONDict | str], f: Callable[[Node], Node], -) -> Tuple[List[Node], Dict[str, int]]: +) -> tuple[list[Node], dict[str, int]]: result, pos, tag = [], 0, NO_TAG for child in children: @@ -68,7 +69,7 @@ def result(*args): if ( args and args[0] - and not isinstance(args[0], (str, Node)) + and not isinstance(args[0], str | Node) and not getattr(args[0], "flat", None) and "flat" not in args[0] ): @@ -95,7 +96,7 @@ def result(*args): if ( args and args[0] - and not isinstance(args[0], (str, Node)) + and not isinstance(args[0], str | Node) and not getattr(args[0], "flat", None) and "flat" not in args[0] ): diff --git a/prosemirror/transform/attr_step.py b/prosemirror/transform/attr_step.py index cb6ef01..8d984ba 100644 --- a/prosemirror/transform/attr_step.py +++ b/prosemirror/transform/attr_step.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union, cast +from typing import Any, cast from prosemirror.model import Fragment, Node, Schema, Slice from prosemirror.transform.map import Mappable, StepMap @@ -37,7 +37,7 @@ def invert(self, doc: Node) -> Step: assert node_at_pos is not None return AttrStep(self.pos, self.attr, node_at_pos.attrs[self.attr]) - def map(self, mapping: Mappable) -> Optional[Step]: + def map(self, mapping: Mappable) -> Step | None: pos = mapping.map_result(self.pos, 1) return None if pos.deleted_after else AttrStep(pos.pos, self.attr, self.value) @@ -50,9 +50,7 @@ def to_json(self) -> JSONDict: } @staticmethod - def from_json( - schema: Schema[Any, Any], json_data: Union[JSONDict, str] - ) -> "AttrStep": + def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "AttrStep": if isinstance(json_data, str): import json diff --git a/prosemirror/transform/doc_attr_step.py b/prosemirror/transform/doc_attr_step.py index b7fb63b..381e823 100644 --- a/prosemirror/transform/doc_attr_step.py +++ b/prosemirror/transform/doc_attr_step.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union, cast +from typing import Any, cast from prosemirror.model import Node, Schema from prosemirror.transform.map import Mappable, StepMap @@ -26,7 +26,7 @@ def get_map(self) -> StepMap: def invert(self, doc: Node) -> Step: return DocAttrStep(self.attr, doc.attrs[self.attr]) - def map(self, mapping: Mappable) -> Optional[Step]: + def map(self, mapping: Mappable) -> Step | None: return self def to_json(self) -> JSONDict: @@ -39,9 +39,7 @@ def to_json(self) -> JSONDict: return json_data @staticmethod - def from_json( - schema: Schema[Any, Any], json_data: Union[JSONDict, str] - ) -> "DocAttrStep": + def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "DocAttrStep": if isinstance(json_data, str): import json diff --git a/prosemirror/transform/map.py b/prosemirror/transform/map.py index e875bed..351f836 100644 --- a/prosemirror/transform/map.py +++ b/prosemirror/transform/map.py @@ -1,5 +1,6 @@ import abc -from typing import Callable, ClassVar, List, Literal, Optional, Union, overload +from collections.abc import Callable +from typing import ClassVar, Literal, overload lower16 = 0xFFFF factor16 = 2**16 @@ -24,9 +25,7 @@ def recover_offset(value: int) -> int: class MapResult: - def __init__( - self, pos: int, del_info: int = 0, recover: Optional[int] = None - ) -> None: + def __init__(self, pos: int, del_info: int = 0, recover: int | None = None) -> None: self.pos = pos self.del_info = del_info self.recover = recover @@ -67,7 +66,7 @@ def map_result(self, pos: int, assoc: int = 1) -> MapResult: ... class StepMap(Mappable): empty: ClassVar["StepMap"] - def __init__(self, ranges: List[int], inverted: bool = False) -> None: + def __init__(self, ranges: list[int], inverted: bool = False) -> None: # prosemirror-transform overrides the constructor to return the # StepMap.empty singleton when ranges are empty. # It is not easy to do in Python, and the intent of that is to make sure @@ -95,7 +94,7 @@ def _map(self, pos: int, assoc: int, simple: Literal[True]) -> int: ... @overload def _map(self, pos: int, assoc: int, simple: Literal[False]) -> MapResult: ... - def _map(self, pos: int, assoc: int, simple: bool) -> Union[MapResult, int]: + def _map(self, pos: int, assoc: int, simple: bool) -> MapResult | int: diff = 0 old_index = 2 if self.inverted else 1 new_index = 1 if self.inverted else 2 @@ -177,17 +176,17 @@ def __str__(self) -> str: class Mapping(Mappable): def __init__( self, - maps: Optional[List[StepMap]] = None, - mirror: Optional[List[int]] = None, - from_: Optional[int] = None, - to: Optional[int] = None, + maps: list[StepMap] | None = None, + mirror: list[int] | None = None, + from_: int | None = None, + to: int | None = None, ) -> None: self.maps = maps or [] self.from_ = from_ or 0 self.to = len(self.maps) if to is None else to self.mirror = mirror - def slice(self, from_: int = 0, to: Optional[int] = None) -> "Mapping": + def slice(self, from_: int = 0, to: int | None = None) -> "Mapping": if to is None: to = len(self.maps) return Mapping(self.maps, self.mirror, from_, to) @@ -197,7 +196,7 @@ def copy(self) -> "Mapping": self.maps[:], (self.mirror[:] if self.mirror else None), self.from_, self.to ) - def append_map(self, map: StepMap, mirrors: Optional[int] = None) -> None: + def append_map(self, map: StepMap, mirrors: int | None = None) -> None: self.maps.append(map) self.to = len(self.maps) if mirrors is not None: @@ -214,7 +213,7 @@ def append_mapping(self, mapping: "Mapping") -> None: (start_size + mirr) if (mirr is not None and mirr < i) else None, ) - def get_mirror(self, n: int) -> Optional[int]: + def get_mirror(self, n: int) -> int | None: if self.mirror: for i in range(len(self.mirror)): if (self.mirror[i]) == n: @@ -258,7 +257,7 @@ def _map(self, pos: int, assoc: int, simple: Literal[True]) -> int: ... @overload def _map(self, pos: int, assoc: int, simple: Literal[False]) -> MapResult: ... - def _map(self, pos: int, assoc: int, simple: bool) -> Union[MapResult, int]: + def _map(self, pos: int, assoc: int, simple: bool) -> MapResult | int: del_info = 0 i = self.from_ diff --git a/prosemirror/transform/mark_step.py b/prosemirror/transform/mark_step.py index 7bb897e..383a2bb 100644 --- a/prosemirror/transform/mark_step.py +++ b/prosemirror/transform/mark_step.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Optional, Union, cast +from collections.abc import Callable +from typing import Any, cast from prosemirror.model import Fragment, Mark, Node, Schema, Slice from prosemirror.transform.map import Mappable @@ -34,7 +35,7 @@ def apply(self, doc: Node) -> StepResult: from__ = doc.resolve(self.from_) parent = from__.node(from__.shared_depth(self.to)) - def iteratee(node: Node, parent: Optional[Node], i: int) -> Node: + def iteratee(node: Node, parent: Node | None, i: int) -> Node: if parent and ( not node.is_atom or not parent.type.allows_mark_type(self.mark.type) ): @@ -48,17 +49,17 @@ def iteratee(node: Node, parent: Optional[Node], i: int) -> Node: ) return StepResult.from_replace(doc, self.from_, self.to, slice) - def invert(self, doc: Optional[Node] = None) -> Step: + def invert(self, doc: Node | None = None) -> Step: return RemoveMarkStep(self.from_, self.to, self.mark) - def map(self, mapping: Mappable) -> Optional[Step]: + def map(self, mapping: Mappable) -> Step | None: from_ = mapping.map_result(self.from_, 1) to = mapping.map_result(self.to, -1) if (from_.deleted and to.deleted) or from_.pos > to.pos: return None return AddMarkStep(from_.pos, to.pos, self.mark) - def merge(self, other: Step) -> Optional[Step]: + def merge(self, other: Step) -> Step | None: if ( isinstance(other, AddMarkStep) and other.mark.eq(self.mark) @@ -79,9 +80,7 @@ def to_json(self) -> JSONDict: } @staticmethod - def from_json( - schema: Schema[Any, Any], json_data: Union[JSONDict, str] - ) -> "AddMarkStep": + def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "AddMarkStep": if isinstance(json_data, str): import json @@ -111,7 +110,7 @@ def __init__(self, from_: int, to: int, mark: Mark) -> None: def apply(self, doc: Node) -> StepResult: old_slice = doc.slice(self.from_, self.to) - def iteratee(node: Node, parent: Optional[Node], i: int) -> Node: + def iteratee(node: Node, parent: Node | None, i: int) -> Node: return node.mark(self.mark.remove_from_set(node.marks)) slice = Slice( @@ -121,17 +120,17 @@ def iteratee(node: Node, parent: Optional[Node], i: int) -> Node: ) return StepResult.from_replace(doc, self.from_, self.to, slice) - def invert(self, doc: Optional[Node] = None) -> Step: + def invert(self, doc: Node | None = None) -> Step: return AddMarkStep(self.from_, self.to, self.mark) - def map(self, mapping: Mappable) -> Optional[Step]: + def map(self, mapping: Mappable) -> Step | None: from_ = mapping.map_result(self.from_, 1) to = mapping.map_result(self.to, -1) if (from_.deleted and to.deleted) or (from_.pos > to.pos): return None return RemoveMarkStep(from_.pos, to.pos, self.mark) - def merge(self, other: Step) -> Optional[Step]: + def merge(self, other: Step) -> Step | None: if ( isinstance(other, RemoveMarkStep) and (other.mark.eq(self.mark)) @@ -152,7 +151,7 @@ def to_json(self) -> JSONDict: } @staticmethod - def from_json(schema: Schema[Any, Any], json_data: Union[JSONDict, str]) -> Step: + def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> Step: if isinstance(json_data, str): import json @@ -201,7 +200,7 @@ def invert(self, doc: Node) -> Step: return AddNodeMarkStep(self.pos, self.mark) return RemoveNodeMarkStep(self.pos, self.mark) - def map(self, mapping: Mappable) -> Optional[Step]: + def map(self, mapping: Mappable) -> Step | None: pos = mapping.map_result(self.pos, 1) return None if pos.deleted_after else AddNodeMarkStep(pos.pos, self.mark) @@ -213,7 +212,7 @@ def to_json(self) -> JSONDict: } @staticmethod - def from_json(schema: Schema[Any, Any], json_data: Union[JSONDict, str]) -> Step: + def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> Step: if isinstance(json_data, str): import json @@ -255,7 +254,7 @@ def invert(self, doc: Node) -> Step: return self return AddNodeMarkStep(self.pos, self.mark) - def map(self, mapping: Mappable) -> Optional[Step]: + def map(self, mapping: Mappable) -> Step | None: pos = mapping.map_result(self.pos, 1) return None if pos.deleted_after else RemoveNodeMarkStep(pos.pos, self.mark) @@ -267,7 +266,7 @@ def to_json(self) -> JSONDict: } @staticmethod - def from_json(schema: Schema[Any, Any], json_data: Union[JSONDict, str]) -> Step: + def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> Step: if isinstance(json_data, str): import json diff --git a/prosemirror/transform/replace.py b/prosemirror/transform/replace.py index 931554d..8f41303 100644 --- a/prosemirror/transform/replace.py +++ b/prosemirror/transform/replace.py @@ -1,4 +1,4 @@ -from typing import List, Optional, cast +from typing import cast from prosemirror.model import ( ContentMatch, @@ -16,9 +16,9 @@ def replace_step( doc: Node, from_: int, - to: Optional[int] = None, - slice: Optional[Slice] = None, -) -> Optional[Step]: + to: int | None = None, + slice: Slice | None = None, +) -> Step | None: if to is None: to = from_ if slice is None: @@ -58,9 +58,9 @@ def __init__( self, slice_depth: int, frontier_depth: int, - parent: Optional[Node], - inject: Optional[Fragment] = None, - wrap: Optional[List[NodeType]] = None, + parent: Node | None, + inject: Fragment | None = None, + wrap: list[NodeType] | None = None, ) -> None: self.slice_depth = slice_depth self.frontier_depth = frontier_depth @@ -91,7 +91,7 @@ def __init__(self, from__: ResolvedPos, to_: ResolvedPos, slice: Slice) -> None: self.from__ = from__ self.unplaced = slice - self.frontier: List[_FrontierItem] = [] + self.frontier: list[_FrontierItem] = [] for i in range(from__.depth + 1): node = from__.node(i) self.frontier.append( @@ -106,7 +106,7 @@ def __init__(self, from__: ResolvedPos, to_: ResolvedPos, slice: Slice) -> None: def depth(self) -> int: return len(self.frontier) - 1 - def fit(self) -> Optional[Step]: + def fit(self) -> Step | None: while self.unplaced.size: fit = self.find_fittable() if fit: @@ -147,7 +147,7 @@ def fit(self) -> Optional[Step]: return ReplaceStep(from__.pos, to_.pos, slice) return None - def find_fittable(self) -> Optional[_Fittable]: + def find_fittable(self) -> _Fittable | None: start_depth = self.unplaced.open_start cur = self.unplaced.content open_end = self.unplaced.open_end @@ -182,18 +182,18 @@ def find_fittable(self) -> Optional[_Fittable]: inject = _nothing wrap = _nothing - def _lazy_inject() -> Optional[Fragment]: + def _lazy_inject() -> Fragment | None: nonlocal inject if inject is _nothing: inject = match.fill_before(Fragment.from_(first), False) - return cast(Optional[Fragment], inject) + return cast(Fragment | None, inject) - def _lazy_wrap() -> Optional[List[NodeType]]: + def _lazy_wrap() -> list[NodeType] | None: nonlocal wrap assert first is not None if wrap is _nothing: wrap = match.find_wrapping(first.type) - return cast(Optional[List[NodeType]], wrap) + return cast(list[NodeType] | None, wrap) if pass_ == 1 and ( (match.match_type(first.type) or _lazy_inject()) @@ -355,11 +355,11 @@ def must_move_inline(self) -> int: _nothing = object() level = _nothing - def _lazy_level() -> Optional[_CloseLevel]: + def _lazy_level() -> _CloseLevel | None: nonlocal level if level is _nothing: level = self.find_close_level(self.to_) - return cast(Optional[_CloseLevel], level) + return cast(_CloseLevel | None, level) if ( not top.type.is_text_block @@ -383,7 +383,7 @@ def _lazy_level() -> Optional[_CloseLevel]: after += 1 return after - def find_close_level(self, to_: ResolvedPos) -> Optional[_CloseLevel]: + def find_close_level(self, to_: ResolvedPos) -> _CloseLevel | None: for i in range(min(self.depth, to_.depth), -1, -1): match = self.frontier[i].match type_ = self.frontier[i].type @@ -406,7 +406,7 @@ def find_close_level(self, to_: ResolvedPos) -> Optional[_CloseLevel]: ) return None - def close(self, to_: ResolvedPos) -> Optional[ResolvedPos]: + def close(self, to_: ResolvedPos) -> ResolvedPos | None: close = self.find_close_level(to_) if not close: return None @@ -425,8 +425,8 @@ def close(self, to_: ResolvedPos) -> Optional[ResolvedPos]: def open_frontier_node( self, type_: NodeType, - attrs: Optional[Attrs] = None, - content: Optional[Fragment] = None, + attrs: Attrs | None = None, + content: Fragment | None = None, ) -> None: top = self.frontier[self.depth] top_match = top.match.match_type(type_) @@ -505,7 +505,7 @@ def content_after_fits( type_: NodeType, match: ContentMatch, open_: bool, -) -> Optional[Fragment]: +) -> Fragment | None: node = to_.node(depth) index = to_.index_after(depth) if open_ else to_.index(depth) if index == node.child_count and not type_.compatible_content(node.type): @@ -526,7 +526,7 @@ def close_fragment( depth: int, old_open: int, new_open: int, - parent: Optional[Node], + parent: Node | None, ) -> Fragment: if depth < old_open: first = fragment.first_child @@ -557,7 +557,7 @@ def close_fragment( def covered_depths( from__: ResolvedPos, to_: ResolvedPos, -) -> List[int]: +) -> list[int]: result = [] min_depth = min(from__.depth, to_.depth) for d in range(min_depth, -1, -1): diff --git a/prosemirror/transform/replace_step.py b/prosemirror/transform/replace_step.py index d45386b..78c7a9a 100644 --- a/prosemirror/transform/replace_step.py +++ b/prosemirror/transform/replace_step.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union, cast +from typing import Any, Optional, cast from prosemirror.model import Node, Schema, Slice from prosemirror.transform.map import Mappable, StepMap @@ -8,7 +8,7 @@ class ReplaceStep(Step): def __init__( - self, from_: int, to: int, slice: Slice, structure: Optional[bool] = None + self, from_: int, to: int, slice: Slice, structure: bool | None = None ) -> None: super().__init__() self.from_ = from_ @@ -86,9 +86,7 @@ def to_json(self) -> JSONDict: return json_data @staticmethod - def from_json( - schema: Schema[Any, Any], json_data: Union[JSONDict, str] - ) -> "ReplaceStep": + def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "ReplaceStep": if isinstance(json_data, str): import json @@ -101,7 +99,7 @@ def from_json( return ReplaceStep( json_data["from"], json_data["to"], - Slice.from_json(schema, cast(Optional[JSONDict], json_data.get("slice"))), + Slice.from_json(schema, cast(JSONDict | None, json_data.get("slice"))), bool(json_data.get("structure")), ) @@ -118,7 +116,7 @@ def __init__( gap_to: int, slice: Slice, insert: int, - structure: Optional[bool] = None, + structure: bool | None = None, ) -> None: super().__init__() self.from_ = from_ @@ -201,7 +199,7 @@ def to_json(self) -> JSONDict: @staticmethod def from_json( - schema: Schema[Any, Any], json_data: Union[JSONDict, str] + schema: Schema[Any, Any], json_data: JSONDict | str ) -> "ReplaceAroundStep": if isinstance(json_data, str): import json @@ -221,7 +219,7 @@ def from_json( json_data["to"], json_data["gapFrom"], json_data["gapTo"], - Slice.from_json(schema, cast(Optional[JSONDict], json_data.get("slice"))), + Slice.from_json(schema, cast(JSONDict | None, json_data.get("slice"))), json_data["insert"], bool(json_data.get("structure")), ) diff --git a/prosemirror/transform/step.py b/prosemirror/transform/step.py index fd039d6..0ad5e87 100644 --- a/prosemirror/transform/step.py +++ b/prosemirror/transform/step.py @@ -1,12 +1,12 @@ import abc -from typing import Any, Dict, Literal, Optional, Type, TypeVar, Union, cast, overload +from typing import Any, Literal, Optional, TypeVar, cast, overload from prosemirror.model import Node, ReplaceError, Schema, Slice from prosemirror.transform.map import Mappable, StepMap from prosemirror.utils import JSONDict # like a registry -STEPS_BY_ID: Dict[str, Type["Step"]] = {} +STEPS_BY_ID: dict[str, type["Step"]] = {} StepSubclass = TypeVar("StepSubclass", bound="Step") @@ -32,7 +32,7 @@ def merge(self, _other: "Step") -> Optional["Step"]: def to_json(self) -> JSONDict: ... @staticmethod - def from_json(schema: Schema[Any, Any], json_data: Union[JSONDict, str]) -> "Step": + def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "Step": if isinstance(json_data, str): import json @@ -46,7 +46,7 @@ def from_json(schema: Schema[Any, Any], json_data: Union[JSONDict, str]) -> "Ste return type.from_json(schema, json_data) -def step_json_id(id: str, step_class: Type[StepSubclass]) -> Type[StepSubclass]: +def step_json_id(id: str, step_class: type[StepSubclass]) -> type[StepSubclass]: if id in STEPS_BY_ID: raise ValueError(f"Duplicated JSON ID for step type: {id}") @@ -63,7 +63,7 @@ def __init__(self, doc: Node, failed: Literal[None]) -> None: ... @overload def __init__(self, doc: None, failed: str) -> None: ... - def __init__(self, doc: Optional[Node], failed: Optional[str]) -> None: + def __init__(self, doc: Node | None, failed: str | None) -> None: self.doc = doc self.failed = failed diff --git a/prosemirror/transform/structure.py b/prosemirror/transform/structure.py index 15d10ca..3307da6 100644 --- a/prosemirror/transform/structure.py +++ b/prosemirror/transform/structure.py @@ -1,4 +1,4 @@ -from typing import List, Optional, TypedDict, Union +from typing import TypedDict from prosemirror.model import ContentMatch, Node, NodeRange, NodeType, Slice from prosemirror.utils import Attrs @@ -10,7 +10,7 @@ def can_cut(node: Node, start: int, end: int) -> bool: return False -def lift_target(range_: NodeRange) -> Optional[int]: +def lift_target(range_: NodeRange) -> int | None: parent = range_.parent content = parent.content.cut_by_index(range_.start_index, range_.end_index) depth = range_.depth @@ -33,15 +33,15 @@ def lift_target(range_: NodeRange) -> Optional[int]: class NodeTypeWithAttrs(TypedDict): type: NodeType - attrs: Optional[Attrs] + attrs: Attrs | None def find_wrapping( range_: NodeRange, node_type: NodeType, - attrs: Optional[Attrs] = None, - inner_range: Optional[NodeRange] = None, -) -> Optional[List[NodeTypeWithAttrs]]: + attrs: Attrs | None = None, + inner_range: NodeRange | None = None, +) -> list[NodeTypeWithAttrs] | None: if inner_range is None: inner_range = range_ @@ -67,9 +67,7 @@ def with_attrs(type: NodeType) -> NodeTypeWithAttrs: return NodeTypeWithAttrs(type=type, attrs=None) -def find_wrapping_outside( - range_: NodeRange, type: NodeType -) -> Optional[List[NodeType]]: +def find_wrapping_outside(range_: NodeRange, type: NodeType) -> list[NodeType] | None: parent = range_.parent start_index = range_.start_index end_index = range_.end_index @@ -80,7 +78,7 @@ def find_wrapping_outside( return around if parent.can_replace_with(start_index, end_index, outer) else None -def find_wrapping_inside(range_: NodeRange, type: NodeType) -> Optional[List[NodeType]]: +def find_wrapping_inside(range_: NodeRange, type: NodeType) -> list[NodeType] | None: parent = range_.parent start_index = range_.start_index end_index = range_.end_index @@ -91,7 +89,7 @@ def find_wrapping_inside(range_: NodeRange, type: NodeType) -> Optional[List[Nod return None last_type = inside[-1] if len(inside) else type - inner_match: Optional[ContentMatch] = last_type.content_match + inner_match: ContentMatch | None = last_type.content_match i = start_index while inner_match and i < end_index: @@ -113,14 +111,14 @@ def can_change_type(doc: Node, pos: int, type: NodeType) -> bool: def can_split( doc: Node, pos: int, - depth: Optional[int] = None, - types_after: Optional[List[NodeTypeWithAttrs]] = None, + depth: int | None = None, + types_after: list[NodeTypeWithAttrs] | None = None, ) -> bool: if depth is None: depth = 1 pos_ = doc.resolve(pos) base = pos_.depth - depth - inner_type: Union[NodeTypeWithAttrs, Node, None] = None + inner_type: NodeTypeWithAttrs | Node | None = None if types_after: inner_type = types_after[-1] @@ -165,7 +163,7 @@ def can_split( rest = rest.replace_child( 0, override_child["type"].create(override_child.get("attrs")) ) - after: Union[NodeTypeWithAttrs, Node, None] = None + after: NodeTypeWithAttrs | Node | None = None if types_after and len(types_after) > i: after = types_after[i] if not after: @@ -193,7 +191,7 @@ def can_split( ) -def can_join(doc: Node, pos: int) -> Optional[bool]: +def can_join(doc: Node, pos: int) -> bool | None: pos_ = doc.resolve(pos) index = pos_.index() return ( @@ -203,13 +201,13 @@ def can_join(doc: Node, pos: int) -> Optional[bool]: ) -def joinable(a: Optional[Node], b: Optional[Node]) -> bool: +def joinable(a: Node | None, b: Node | None) -> bool: if a and b and not a.is_leaf: return a.can_append(b) return False -def join_point(doc: Node, pos: int, dir: int = -1) -> Optional[int]: +def join_point(doc: Node, pos: int, dir: int = -1) -> int | None: pos_ = doc.resolve(pos) for d in range(pos_.depth, -1, -1): before = None @@ -239,7 +237,7 @@ def join_point(doc: Node, pos: int, dir: int = -1) -> Optional[int]: return None -def insert_point(doc: Node, pos: int, node_type: NodeType) -> Optional[int]: +def insert_point(doc: Node, pos: int, node_type: NodeType) -> int | None: pos_ = doc.resolve(pos) if pos_.parent.can_replace_with(pos_.index(), pos_.index(), node_type): return pos @@ -261,7 +259,7 @@ def insert_point(doc: Node, pos: int, node_type: NodeType) -> Optional[int]: return None -def drop_point(doc: Node, pos: int, slice: Slice) -> Optional[int]: +def drop_point(doc: Node, pos: int, slice: Slice) -> int | None: pos_ = doc.resolve(pos) if not slice.content.size: return pos diff --git a/prosemirror/transform/transform.py b/prosemirror/transform/transform.py index 107f524..918c6ca 100644 --- a/prosemirror/transform/transform.py +++ b/prosemirror/transform/transform.py @@ -1,5 +1,5 @@ import re -from typing import List, Optional, TypedDict, Union +from typing import Optional, TypedDict from prosemirror.model import ( ContentMatch, @@ -34,7 +34,7 @@ from .doc_attr_step import DocAttrStep -def defines_content(type: Union[NodeType, MarkType]) -> Optional[bool]: +def defines_content(type: NodeType | MarkType) -> bool | None: if isinstance(type, NodeType): return type.spec.get("defining") or type.spec.get("definingForContent") return False @@ -57,8 +57,8 @@ class Transform: def __init__(self, doc: Node) -> None: self.doc = doc - self.steps: List[Step] = [] - self.docs: List[Node] = [] + self.steps: list[Step] = [] + self.docs: list[Node] = [] self.mapping = Mapping() @property @@ -90,10 +90,10 @@ def add_step(self, step: Step, doc: Node) -> None: def add_mark(self, from_: int, to: int, mark: Mark) -> "Transform": removed = [] added = [] - removing: Optional[RemoveMarkStep] = None - adding: Optional[AddMarkStep] = None + removing: RemoveMarkStep | None = None + adding: AddMarkStep | None = None - def iteratee(node: Node, pos: int, parent: Optional[Node], i: int) -> None: + def iteratee(node: Node, pos: int, parent: Node | None, i: int) -> None: nonlocal removing nonlocal adding if not node.is_inline: @@ -136,7 +136,7 @@ def remove_mark( self, from_: int, to: int, - mark: Union[Mark, MarkType, None] = None, + mark: Mark | MarkType | None = None, ) -> "Transform": class MatchedTypedDict(TypedDict): style: Mark @@ -144,12 +144,10 @@ class MatchedTypedDict(TypedDict): to: int step: int - matched: List[MatchedTypedDict] = [] + matched: list[MatchedTypedDict] = [] step = 0 - def iteratee( - node: Node, pos: int, parent: Optional[Node], i: int - ) -> Optional[bool]: + def iteratee(node: Node, pos: int, parent: Node | None, i: int) -> bool | None: nonlocal step if not node.is_inline: return None @@ -198,7 +196,7 @@ def clear_incompatible( self, pos: int, parent_type: NodeType, - match: Optional[ContentMatch] = None, + match: ContentMatch | None = None, ) -> "Transform": if match is None: match = parent_type.content_match @@ -252,8 +250,8 @@ def clear_incompatible( def replace( self, from_: int, - to: Optional[int] = None, - slice: Optional[Slice] = None, + to: int | None = None, + slice: Slice | None = None, ) -> "Transform": if to is None: to = from_ @@ -268,7 +266,7 @@ def replace_with( self, from_: int, to: int, - content: Union[Fragment, Node, List[Node]], + content: Fragment | Node | list[Node], ) -> "Transform": return self.replace(from_, to, Slice(Fragment.from_(content), 0, 0)) @@ -278,7 +276,7 @@ def delete(self, from_: int, to: int) -> "Transform": def insert( self, pos: int, - content: Union[Fragment, Node, List[Node]], + content: Fragment | Node | list[Node], ) -> "Transform": return self.replace_with(pos, pos, content) @@ -471,7 +469,7 @@ def lift(self, range_: NodeRange, target: int) -> "Transform": ) def wrap( - self, range_: NodeRange, wrappers: List[structure.NodeTypeWithAttrs] + self, range_: NodeRange, wrappers: list[structure.NodeTypeWithAttrs] ) -> "Transform": content = Fragment.empty i = len(wrappers) - 1 @@ -498,9 +496,9 @@ def wrap( def set_block_type( self, from_: int, - to: Optional[int], + to: int | None, type: NodeType, - attrs: Optional[Attrs], + attrs: Attrs | None, ) -> "Transform": if to is None: to = from_ @@ -510,7 +508,7 @@ def set_block_type( def iteratee( node: "Node", pos: int, parent: Optional["Node"], i: int - ) -> Optional[bool]: + ) -> bool | None: if ( node.is_text_block and not node.has_markup(type, attrs) @@ -544,9 +542,9 @@ def iteratee( def set_node_markup( self, pos: int, - type: Optional[NodeType], - attrs: Optional[Attrs], - marks: Optional[List[Mark]] = None, + type: NodeType | None, + attrs: Attrs | None, + marks: list[Mark] | None = None, ) -> "Transform": node = self.doc.node_at(pos) if not node: @@ -579,7 +577,7 @@ def set_doc_attribute(self, attr: str, value: JSON) -> "Transform": def add_node_mark(self, pos: int, mark: Mark) -> "Transform": return self.step(AddNodeMarkStep(pos, mark)) - def remove_node_mark(self, pos: int, mark: Union[Mark, MarkType]) -> "Transform": + def remove_node_mark(self, pos: int, mark: Mark | MarkType) -> "Transform": if isinstance(mark, MarkType): node = self.doc.node_at(pos) @@ -597,8 +595,8 @@ def remove_node_mark(self, pos: int, mark: Union[Mark, MarkType]) -> "Transform" def split( self, pos: int, - depth: Optional[int] = None, - types_after: Optional[List[structure.NodeTypeWithAttrs]] = None, + depth: int | None = None, + types_after: list[structure.NodeTypeWithAttrs] | None = None, ) -> "Transform": if depth is None: depth = 1 diff --git a/prosemirror/utils.py b/prosemirror/utils.py index 8dd0c35..0b2b4d5 100644 --- a/prosemirror/utils.py +++ b/prosemirror/utils.py @@ -1,11 +1,10 @@ -from typing import Mapping, Sequence, Union - -from typing_extensions import TypeAlias +from collections.abc import Mapping, Sequence +from typing import TypeAlias JSONDict: TypeAlias = Mapping[str, "JSON"] JSONList: TypeAlias = Sequence["JSON"] -JSON: TypeAlias = Union[JSONDict, JSONList, str, int, float, bool, None] +JSON: TypeAlias = JSONDict | JSONList | str | int | float | bool | None Attrs: TypeAlias = JSONDict diff --git a/pyproject.toml b/pyproject.toml index 4bd069d..65e1504 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dev = [ ] [tool.ruff.lint] -select = ["E", "F", "W", "I", "RUF"] +select = ["E", "F", "W", "I", "RUF", "UP"] preview = true [tool.ruff.format] From bfb15393f63e2b23ba68662e65f0a160e3237ecb Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sat, 20 Apr 2024 23:38:23 -0400 Subject: [PATCH 03/13] Add B ruff rules --- prosemirror/model/content.py | 4 ++-- prosemirror/model/diff.py | 4 +++- prosemirror/model/fragment.py | 2 +- prosemirror/model/from_dom.py | 8 +++---- prosemirror/model/mark.py | 2 +- prosemirror/transform/replace.py | 37 ++++++++++++++---------------- prosemirror/transform/structure.py | 2 +- pyproject.toml | 2 +- 8 files changed, 30 insertions(+), 31 deletions(-) diff --git a/prosemirror/model/content.py b/prosemirror/model/content.py index f136061..e5fa077 100644 --- a/prosemirror/model/content.py +++ b/prosemirror/model/content.py @@ -447,14 +447,14 @@ def compile(expr: Expr, from_: int) -> list[Edge]: return [edge(from_), *compile(expr["expr"], from_)] elif expr["type"] == "range": cur = from_ - for i in range(expr["min"]): + for _i in range(expr["min"]): next = node() connect(compile(expr["expr"], cur), next) cur = next if expr["max"] == -1: connect(compile(expr["expr"], cur), cur) else: - for i in range(expr["min"], expr["max"]): + for _i in range(expr["min"], expr["max"]): next = node() edge(cur, next) connect(compile(expr["expr"], cur), next) diff --git a/prosemirror/model/diff.py b/prosemirror/model/diff.py index 098988f..b84d780 100644 --- a/prosemirror/model/diff.py +++ b/prosemirror/model/diff.py @@ -36,7 +36,9 @@ def find_diff_start(a: "Fragment", b: "Fragment", pos: int) -> int | None: ( index_a for ((index_a, char_a), (_, char_b)) in zip( - enumerate(child_a.text), enumerate(child_b.text) + enumerate(child_a.text), + enumerate(child_b.text), + strict=True, ) if char_a != char_b ), diff --git a/prosemirror/model/fragment.py b/prosemirror/model/fragment.py index 47353cd..0fd8566 100644 --- a/prosemirror/model/fragment.py +++ b/prosemirror/model/fragment.py @@ -171,7 +171,7 @@ def add_to_end(self, node: "Node") -> "Fragment": def eq(self, other: "Fragment") -> bool: if len(self.content) != len(other.content): return False - return all(a.eq(b) for (a, b) in zip(self.content, other.content)) + return all(a.eq(b) for (a, b) in zip(self.content, other.content, strict=True)) @property def first_child(self) -> Optional["Node"]: diff --git a/prosemirror/model/from_dom.py b/prosemirror/model/from_dom.py index 3678331..873b62c 100644 --- a/prosemirror/model/from_dom.py +++ b/prosemirror/model/from_dom.py @@ -992,7 +992,7 @@ def textblock_from_context(self) -> NodeType | None: d -= 1 - for name, type_ in self.parser.schema.nodes.items(): + for type_ in self.parser.schema.nodes.values(): if type_.is_text_block and type_.default_attrs: return type_ @@ -1069,14 +1069,14 @@ def parse_styles(style: str) -> list[str]: def mark_may_apply(mark_type: MarkType, node_type: NodeType) -> bool: nodes = node_type.schema.nodes - for name, parent in nodes.items(): + for parent in nodes.values(): if not parent.allows_mark_type(mark_type): continue seen: list[ContentMatch] = [] def scan(match: ContentMatch) -> bool: - seen.append(match) + seen.append(match) # noqa: B023 i = 0 while i < match.edge_count: result = match.edge(i) @@ -1085,7 +1085,7 @@ def scan(match: ContentMatch) -> bool: if _type == node_type: return True - if _next not in seen and scan(_next): + if _next not in seen and scan(_next): # noqa: B023 return True i += 1 diff --git a/prosemirror/model/mark.py b/prosemirror/model/mark.py index cabbadc..89cce11 100644 --- a/prosemirror/model/mark.py +++ b/prosemirror/model/mark.py @@ -74,7 +74,7 @@ def same_set(cls, a: list["Mark"], b: list["Mark"]) -> bool: return True if len(a) != len(b): return False - return all(item_a.eq(item_b) for (item_a, item_b) in zip(a, b)) + return all(item_a.eq(item_b) for (item_a, item_b) in zip(a, b, strict=True)) @classmethod def set_from(cls, marks: Union[list["Mark"], "Mark", None]) -> list["Mark"]: diff --git a/prosemirror/transform/replace.py b/prosemirror/transform/replace.py index 8f41303..67b5586 100644 --- a/prosemirror/transform/replace.py +++ b/prosemirror/transform/replace.py @@ -178,25 +178,18 @@ def find_fittable(self) -> _Fittable | None: type_ = self.frontier[frontier_depth].type match = self.frontier[frontier_depth].match - _nothing = object() - inject = _nothing - wrap = _nothing - - def _lazy_inject() -> Fragment | None: - nonlocal inject - if inject is _nothing: - inject = match.fill_before(Fragment.from_(first), False) - return cast(Fragment | None, inject) - - def _lazy_wrap() -> list[NodeType] | None: - nonlocal wrap - assert first is not None - if wrap is _nothing: - wrap = match.find_wrapping(first.type) - return cast(list[NodeType] | None, wrap) + inject = None + wrap = None if pass_ == 1 and ( - (match.match_type(first.type) or _lazy_inject()) + ( + match.match_type(first.type) + or ( + inject := match.fill_before( + Fragment.from_(first), False + ) + ) + ) if first else parent and type_.compatible_content(parent.type) ): @@ -204,14 +197,18 @@ def _lazy_wrap() -> list[NodeType] | None: slice_depth, frontier_depth, parent, - inject=_lazy_inject(), + inject=inject, ) - elif pass_ == 2 and first and _lazy_wrap(): + elif ( + pass_ == 2 + and first + and (wrap := match.find_wrapping(first.type)) + ): return _Fittable( slice_depth, frontier_depth, parent, - wrap=_lazy_wrap(), + wrap=wrap, ) if parent and match.match_type(parent.type): break diff --git a/prosemirror/transform/structure.py b/prosemirror/transform/structure.py index 3307da6..dfca7ed 100644 --- a/prosemirror/transform/structure.py +++ b/prosemirror/transform/structure.py @@ -264,7 +264,7 @@ def drop_point(doc: Node, pos: int, slice: Slice) -> int | None: if not slice.content.size: return pos content = slice.content - for i in range(slice.open_start): + for _i in range(slice.open_start): assert content.first_child is not None content = content.first_child.content pass_ = 1 diff --git a/pyproject.toml b/pyproject.toml index 65e1504..725c24f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dev = [ ] [tool.ruff.lint] -select = ["E", "F", "W", "I", "RUF", "UP"] +select = ["E", "F", "W", "I", "RUF", "UP", "B"] preview = true [tool.ruff.format] From 790c8542d9a3034439f1d7fa533a0e339571e924 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sat, 20 Apr 2024 23:44:09 -0400 Subject: [PATCH 04/13] Add I and SIM ruff rules SIM suggests some patterns (like using any()) which aren't used upstream, but are simple enough visually to compare that I think it's warranted. --- prosemirror/model/content.py | 5 +--- prosemirror/model/from_dom.py | 6 +---- prosemirror/model/node.py | 12 ++-------- prosemirror/model/schema.py | 9 ++------ prosemirror/test_builder/build.py | 5 ++-- prosemirror/transform/structure.py | 23 +++++++++---------- pyproject.toml | 2 +- .../tests/test_structure.py | 5 +--- 8 files changed, 21 insertions(+), 46 deletions(-) diff --git a/prosemirror/model/content.py b/prosemirror/model/content.py index e5fa077..1ee0512 100644 --- a/prosemirror/model/content.py +++ b/prosemirror/model/content.py @@ -336,10 +336,7 @@ def parse_expr_range(stream: TokenStream, expr: Expr) -> Expr: min_ = parse_num(stream) max_ = min_ if stream.eat(","): - if stream.next() != "}": - max_ = parse_num(stream) - else: - max_ = -1 + max_ = parse_num(stream) if stream.next() != "}" else -1 if not stream.eat("}"): stream.err("Unclosed braced range") return {"type": "range", "min": min_, "max": max_, "expr": expr} diff --git a/prosemirror/model/from_dom.py b/prosemirror/model/from_dom.py index 873b62c..d9a2630 100644 --- a/prosemirror/model/from_dom.py +++ b/prosemirror/model/from_dom.py @@ -1106,11 +1106,7 @@ def find_same_mark_in_set(mark: Mark, mark_set: list[Mark]) -> Mark | None: def node_contains(node: DOMNode, find: DOMNode) -> bool: - for child_node in node.iterdescendants(): - if child_node == find: - return True - - return False + return any(child_node == find for child_node in node.iterdescendants()) def compare_document_position(node1: DOMNode, node2: DOMNode) -> int: diff --git a/prosemirror/model/node.py b/prosemirror/model/node.py index dd793e9..7d1d914 100644 --- a/prosemirror/model/node.py +++ b/prosemirror/model/node.py @@ -228,11 +228,7 @@ def is_atom(self) -> bool: return self.type.is_atom def __str__(self) -> str: - to_debug_string = ( - self.type.spec["toDebugString"] - if "toDebugString" in self.type.spec - else None - ) + to_debug_string = self.type.spec.get("toDebugString", None) if to_debug_string: return to_debug_string(self) name = self.type.name @@ -363,11 +359,7 @@ def __init__( def __str__(self) -> str: import json - to_debug_string = ( - self.type.spec["toDebugString"] - if "toDebugString" in self.type.spec - else None - ) + to_debug_string = self.type.spec.get("toDebugString", None) if to_debug_string: return to_debug_string(self) return wrap_marks(self.marks, json.dumps(self.text)) diff --git a/prosemirror/model/schema.py b/prosemirror/model/schema.py index 1b5a1b9..6168f89 100644 --- a/prosemirror/model/schema.py +++ b/prosemirror/model/schema.py @@ -114,10 +114,7 @@ def whitespace(self) -> Literal["pre", "normal"]: ) def has_required_attrs(self) -> bool: - for n in self.attrs: - if self.attrs[n].is_required: - return True - return False + return any(self.attrs[n].is_required for n in self.attrs) def compatible_content(self, other: "NodeType") -> bool: return self == other or (self.content_match.compatible(other.content_match)) @@ -277,10 +274,8 @@ def compile( cls, marks: dict["Marks", "MarkSpec"], schema: "Schema[Nodes, Marks]" ) -> dict["Marks", "MarkType"]: result = {} - rank = 0 - for name, spec in marks.items(): + for rank, (name, spec) in enumerate(marks.items()): result[name] = MarkType(name, rank, schema, spec) - rank += 1 return result def remove_from_set(self, set_: list["Mark"]) -> list["Mark"]: diff --git a/prosemirror/test_builder/build.py b/prosemirror/test_builder/build.py index 0ef6767..7829cc6 100644 --- a/prosemirror/test_builder/build.py +++ b/prosemirror/test_builder/build.py @@ -1,5 +1,6 @@ # type: ignore +import contextlib import re from collections.abc import Callable from typing import Any @@ -82,10 +83,8 @@ def result(*args): return node if type.is_leaf: - try: + with contextlib.suppress(ValueError): result.flat = [type.create(attrs)] - except ValueError: - pass return result diff --git a/prosemirror/transform/structure.py b/prosemirror/transform/structure.py index dfca7ed..a6b33b5 100644 --- a/prosemirror/transform/structure.py +++ b/prosemirror/transform/structure.py @@ -137,16 +137,15 @@ def can_split( ): return False - elif isinstance(inner_type, dict): - if ( - base < 0 - or pos_.parent.type.spec.get("isolating") - or not pos_.parent.can_replace(pos_.index(), pos_.parent.child_count) - or not inner_type["type"].valid_content( - pos_.parent.content.cut_by_index(pos_.index(), pos_.parent.child_count) - ) - ): - return False + elif isinstance(inner_type, dict) and ( + base < 0 + or pos_.parent.type.spec.get("isolating") + or not pos_.parent.can_replace(pos_.index(), pos_.parent.child_count) + or not inner_type["type"].valid_content( + pos_.parent.content.cut_by_index(pos_.index(), pos_.parent.child_count) + ) + ): + return False d = pos_.depth - 1 i = depth - 2 @@ -158,7 +157,7 @@ def can_split( return False rest = node.content.cut_by_index(index, node.child_count) - if types_after and len(types_after) > i: + if types_after and len(types_after) > i + 1: override_child = types_after[i + 1] rest = rest.replace_child( 0, override_child["type"].create(override_child.get("attrs")) @@ -169,7 +168,7 @@ def can_split( if not after: after = node - if isinstance(after, dict): + if isinstance(after, dict): # noqa: SIM102 if not node.can_replace(index + 1, node.child_count) or not after[ "type" ].valid_content(rest): diff --git a/pyproject.toml b/pyproject.toml index 725c24f..b6e3d23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dev = [ ] [tool.ruff.lint] -select = ["E", "F", "W", "I", "RUF", "UP", "B"] +select = ["E", "F", "W", "I", "RUF", "UP", "B", "I", "SIM"] preview = true [tool.ruff.format] diff --git a/tests/prosemirror_transform/tests/test_structure.py b/tests/prosemirror_transform/tests/test_structure.py index 61205ef..7d6183a 100644 --- a/tests/prosemirror_transform/tests/test_structure.py +++ b/tests/prosemirror_transform/tests/test_structure.py @@ -263,9 +263,6 @@ def test_find_wrapping(self, pass_, pos, end, type): ], ) def test_replace(doc, from_, to, content, open_start, open_end, result): - if content: - slice = Slice(content.content, open_start, open_end) - else: - slice = Slice.empty + slice = Slice(content.content, open_start, open_end) if content else Slice.empty tr = Transform(doc).replace(from_, to, slice) assert tr.doc.eq(result) From 178d5e0a7eb96a636179f4649b094f4ce582df8d Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sun, 21 Apr 2024 00:30:55 -0400 Subject: [PATCH 05/13] Rework NodeTypeWithAttrs to avoid simplify code --- prosemirror/transform/structure.py | 66 ++++++------------- prosemirror/transform/transform.py | 6 +- .../tests/test_structure.py | 8 ++- .../prosemirror_transform/tests/test_trans.py | 3 +- 4 files changed, 32 insertions(+), 51 deletions(-) diff --git a/prosemirror/transform/structure.py b/prosemirror/transform/structure.py index a6b33b5..0c96097 100644 --- a/prosemirror/transform/structure.py +++ b/prosemirror/transform/structure.py @@ -1,4 +1,5 @@ -from typing import TypedDict +from dataclasses import dataclass +from typing import cast from prosemirror.model import ContentMatch, Node, NodeRange, NodeType, Slice from prosemirror.utils import Attrs @@ -31,9 +32,10 @@ def lift_target(range_: NodeRange) -> int | None: return None -class NodeTypeWithAttrs(TypedDict): +@dataclass +class NodeTypeWithAttrs: type: NodeType - attrs: Attrs | None + attrs: Attrs | None = None def find_wrapping( @@ -58,7 +60,7 @@ def find_wrapping( return ( [with_attrs(item) for item in around] - + [{"type": node_type, "attrs": attrs}] + + [NodeTypeWithAttrs(type=node_type, attrs=attrs)] + [with_attrs(item) for item in inner] ) @@ -118,30 +120,15 @@ def can_split( depth = 1 pos_ = doc.resolve(pos) base = pos_.depth - depth - inner_type: NodeTypeWithAttrs | Node | None = None - - if types_after: - inner_type = types_after[-1] - - if not inner_type: - inner_type = pos_.parent - - if isinstance(inner_type, Node): - if ( - base < 0 - or pos_.parent.type.spec.get("isolating") - or not pos_.parent.can_replace(pos_.index(), pos_.parent.child_count) - or not inner_type.type.valid_content( - pos_.parent.content.cut_by_index(pos_.index(), pos_.parent.child_count) - ) - ): - return False + inner_type: NodeTypeWithAttrs = cast( + NodeTypeWithAttrs, (types_after and types_after[-1]) or pos_.parent + ) - elif isinstance(inner_type, dict) and ( + if ( base < 0 or pos_.parent.type.spec.get("isolating") or not pos_.parent.can_replace(pos_.index(), pos_.parent.child_count) - or not inner_type["type"].valid_content( + or not inner_type.type.valid_content( pos_.parent.content.cut_by_index(pos_.index(), pos_.parent.child_count) ) ): @@ -160,33 +147,22 @@ def can_split( if types_after and len(types_after) > i + 1: override_child = types_after[i + 1] rest = rest.replace_child( - 0, override_child["type"].create(override_child.get("attrs")) + 0, override_child.type.create(override_child.attrs) ) - after: NodeTypeWithAttrs | Node | None = None - if types_after and len(types_after) > i: - after = types_after[i] - if not after: - after = node - - if isinstance(after, dict): # noqa: SIM102 - if not node.can_replace(index + 1, node.child_count) or not after[ - "type" - ].valid_content(rest): - return False - - if isinstance(after, Node): - if after != node: - rest = rest.replace_child(0, after.type.create(after.attrs)) - if not node.can_replace( - index + 1, node.child_count - ) or not after.type.valid_content(rest): - return False + after: NodeTypeWithAttrs = cast( + NodeTypeWithAttrs, + (types_after and len(types_after) > i and types_after[i]) or node, + ) + if not node.can_replace( + index + 1, node.child_count + ) or not after.type.valid_content(rest): + return False d -= 1 i -= 1 index = pos_.index_after(base) base_type = types_after[0] if types_after else None return pos_.node(base).can_replace_with( - index, index, base_type["type"] if base_type else pos_.node(base + 1).type + index, index, base_type.type if base_type else pos_.node(base + 1).type ) diff --git a/prosemirror/transform/transform.py b/prosemirror/transform/transform.py index 918c6ca..fbd8539 100644 --- a/prosemirror/transform/transform.py +++ b/prosemirror/transform/transform.py @@ -475,14 +475,14 @@ def wrap( i = len(wrappers) - 1 while i >= 0: if content.size: - match = wrappers[i]["type"].content_match.match_fragment(content) + match = wrappers[i].type.content_match.match_fragment(content) if not match or not match.valid_end: raise TransformError( "Wrapper type given to Transform.wrap does not form valid " "content of its parent wrapper" ) content = Fragment.from_( - wrappers[i]["type"].create(wrappers[i].get("attrs"), content) + wrappers[i].type.create(wrappers[i].attrs, content) ) i -= 1 start = range_.start @@ -612,7 +612,7 @@ def split( if types_after and len(types_after) > i: type_after = types_after[i] after = Fragment.from_( - type_after["type"].create(type_after.get("attrs"), after) + type_after.type.create(type_after.attrs, after) if type_after else pos_.node(d).copy(after) ) diff --git a/tests/prosemirror_transform/tests/test_structure.py b/tests/prosemirror_transform/tests/test_structure.py index 7d6183a..79ea9ea 100644 --- a/tests/prosemirror_transform/tests/test_structure.py +++ b/tests/prosemirror_transform/tests/test_structure.py @@ -2,6 +2,7 @@ from prosemirror.model import Schema, Slice from prosemirror.transform import Transform, can_split, find_wrapping, lift_target +from prosemirror.transform.structure import NodeTypeWithAttrs schema = Schema({ "nodes": { @@ -94,7 +95,10 @@ class TestCanSplit: ) def test_can_split(self, pass_, pos, depth, after): res = can_split( - doc, pos, depth, [{"type": schema.nodes[after]}] if after else None + doc, + pos, + depth, + [NodeTypeWithAttrs(type=schema.nodes[after])] if after else None, ) if pass_: assert res @@ -137,7 +141,7 @@ def test_doesnt_return_true_when_split_content_doesnt_fit_in_given_node_type( ), 4, 1, - [{"type": s.nodes["scene"]}], + [NodeTypeWithAttrs(s.nodes["scene"])], ) diff --git a/tests/prosemirror_transform/tests/test_trans.py b/tests/prosemirror_transform/tests/test_trans.py index 9e10508..0dce106 100644 --- a/tests/prosemirror_transform/tests/test_trans.py +++ b/tests/prosemirror_transform/tests/test_trans.py @@ -4,6 +4,7 @@ from prosemirror.test_builder import builders, out from prosemirror.test_builder import test_schema as schema from prosemirror.transform import Transform, TransformError, find_wrapping, lift_target +from prosemirror.transform.structure import NodeTypeWithAttrs doc = out["doc"] docMetaOne = out["docMetaOne"] @@ -333,7 +334,7 @@ def test_join(doc, expect, test_transform): ( doc(h1("hello!")), doc(h1("hell"), p("o!")), - [None, [{"type": schema.nodes["paragraph"]}]], + [None, [NodeTypeWithAttrs(schema.nodes["paragraph"])]], ), (doc(blockquote("", p("x"))), "fail", []), (doc(blockquote(p("x"), "")), "fail", []), From f5adfe2f30baa9f007d180c9d2fb453ae42a5137 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sun, 21 Apr 2024 10:30:49 -0400 Subject: [PATCH 06/13] Add PT ruff rules --- prosemirror/model/fragment.py | 3 +- pyproject.toml | 2 +- tests/conftest.py | 2 +- tests/prosemirror_model/tests/test_content.py | 6 +- tests/prosemirror_model/tests/test_diff.py | 4 +- tests/prosemirror_model/tests/test_dom.py | 6 +- tests/prosemirror_model/tests/test_mark.py | 8 +-- tests/prosemirror_model/tests/test_resolve.py | 8 +-- tests/prosemirror_model/tests/test_slice.py | 2 +- tests/prosemirror_transform/tests/conftest.py | 12 ++-- .../tests/test_mapping.py | 71 +++++++++---------- .../prosemirror_transform/tests/test_step.py | 2 +- .../tests/test_structure.py | 8 +-- .../prosemirror_transform/tests/test_trans.py | 41 +++++------ 14 files changed, 85 insertions(+), 90 deletions(-) diff --git a/prosemirror/model/fragment.py b/prosemirror/model/fragment.py index 0fd8566..4d70ffe 100644 --- a/prosemirror/model/fragment.py +++ b/prosemirror/model/fragment.py @@ -106,7 +106,8 @@ def append(self, other: "Fragment") -> "Fragment": self.content.copy(), 0, ) - assert last is not None and first is not None + assert last is not None + assert first is not None if pm_node.is_text(last) and last.same_markup(first): assert isinstance(first, pm_node.TextNode) content[len(content) - 1] = last.with_text(last.text + first.text) diff --git a/pyproject.toml b/pyproject.toml index b6e3d23..7b22a1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dev = [ ] [tool.ruff.lint] -select = ["E", "F", "W", "I", "RUF", "UP", "B", "I", "SIM"] +select = ["E", "F", "W", "I", "RUF", "UP", "B", "I", "SIM", "PT"] preview = true [tool.ruff.format] diff --git a/tests/conftest.py b/tests/conftest.py index 032c924..33f7dd0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ import pytest -@pytest.fixture +@pytest.fixture() def ist(): def ist(a, b=None, key=None): if key is None: diff --git a/tests/prosemirror_model/tests/test_content.py b/tests/prosemirror_model/tests/test_content.py index a5fa7c0..97a313f 100644 --- a/tests/prosemirror_model/tests/test_content.py +++ b/tests/prosemirror_model/tests/test_content.py @@ -30,7 +30,7 @@ def match(expr, types): @pytest.mark.parametrize( - "expr,types,valid", + ("expr", "types", "valid"), [ ("", "", True), ("", "image", False), @@ -100,7 +100,7 @@ def test_match_type(expr, types, valid): @pytest.mark.parametrize( - "expr,before,after,result", + ("expr", "before", "after", "result"), [ ( "paragraph horizontal_rule paragraph", @@ -218,7 +218,7 @@ def test_fill_before(expr, before, after, result): @pytest.mark.parametrize( - "expr,before,mid,after,left,right", + ("expr", "before", "mid", "after", "left", "right"), [ ( "paragraph horizontal_rule paragraph horizontal_rule paragraph", diff --git a/tests/prosemirror_model/tests/test_diff.py b/tests/prosemirror_model/tests/test_diff.py index 7472b4a..ee92523 100644 --- a/tests/prosemirror_model/tests/test_diff.py +++ b/tests/prosemirror_model/tests/test_diff.py @@ -12,7 +12,7 @@ @pytest.mark.parametrize( - "a,b", + ("a", "b"), [ ( doc(p("a", em("b")), p("hello"), blockquote(h1("bye"))), @@ -39,7 +39,7 @@ def test_find_diff_start(a, b): @pytest.mark.parametrize( - "a,b", + ("a", "b"), [ ( doc(p("a", em("b")), p("hello"), blockquote(h1("bye"))), diff --git a/tests/prosemirror_model/tests/test_dom.py b/tests/prosemirror_model/tests/test_dom.py index 2033248..ccd690d 100644 --- a/tests/prosemirror_model/tests/test_dom.py +++ b/tests/prosemirror_model/tests/test_dom.py @@ -29,7 +29,7 @@ @pytest.mark.parametrize( - "desc,doc,html", + ("desc", "doc", "html"), [ ( "it can represent simple node", @@ -119,7 +119,7 @@ def test_serializer_first(doc, html, desc): @pytest.mark.parametrize( - "desc,serializer,doc,expect", + ("desc", "serializer", "doc", "expect"), [ ( "it can omit a mark", @@ -161,7 +161,7 @@ def test_html_is_escaped(): @pytest.mark.parametrize( - "desc,doc,expect", + ("desc", "doc", "expect"), [ ( "Basic text node", diff --git a/tests/prosemirror_model/tests/test_mark.py b/tests/prosemirror_model/tests/test_mark.py index 504986b..3d7995e 100644 --- a/tests/prosemirror_model/tests/test_mark.py +++ b/tests/prosemirror_model/tests/test_mark.py @@ -44,7 +44,7 @@ def link(href, title=None): @pytest.mark.parametrize( - "a,b,res", + ("a", "b", "res"), [ ([em_, strong], [em_, strong], True), ([em_, strong], [em_, code], False), @@ -58,7 +58,7 @@ def test_same_set(a, b, res): @pytest.mark.parametrize( - "a,b,res", + ("a", "b", "res"), [ (link("http://foo"), (link("http://foo")), True), (link("http://foo"), link("http://bar"), False), @@ -171,7 +171,7 @@ class TestResolvedPosMarks: ) @pytest.mark.parametrize( - "doc,mark,result", + ("doc", "mark", "result"), [ (doc(p(em("foo"))), em_, True), (doc(p(em("foo"))), strong, False), @@ -185,7 +185,7 @@ def test_is_at(self, doc, mark, result): assert mark.is_in_set(doc.resolve(doc.tag["a"]).marks()) is result @pytest.mark.parametrize( - "a,b", + ("a", "b"), [ (custom_doc.resolve(4).marks(), [custom_strong]), (custom_doc.resolve(3).marks(), [remark1, custom_strong]), diff --git a/tests/prosemirror_model/tests/test_resolve.py b/tests/prosemirror_model/tests/test_resolve.py index 98ce8d5..13c1447 100644 --- a/tests/prosemirror_model/tests/test_resolve.py +++ b/tests/prosemirror_model/tests/test_resolve.py @@ -15,7 +15,7 @@ @pytest.mark.parametrize( - "pos,exp", + ("pos", "exp"), list( enumerate([ [_doc, 0, None, _p1["node"]], @@ -60,7 +60,7 @@ def test_node_resolve(pos, exp): @pytest.mark.parametrize( - "pos,result", + ("pos", "result"), [ (0, ":0"), (1, "paragraph_0:0"), @@ -71,13 +71,13 @@ def test_resolvedpos_str(pos, result): assert str(test_doc.resolve(pos)) == result -@pytest.fixture +@pytest.fixture() def doc_for_pos_at_index(): return doc(blockquote(p("one"), blockquote(p("two ", em("three")), p("four")))) @pytest.mark.parametrize( - "index,depth,pos", + ("index", "depth", "pos"), [ (0, None, 8), (1, None, 12), diff --git a/tests/prosemirror_model/tests/test_slice.py b/tests/prosemirror_model/tests/test_slice.py index 1460cdd..637ccbe 100644 --- a/tests/prosemirror_model/tests/test_slice.py +++ b/tests/prosemirror_model/tests/test_slice.py @@ -12,7 +12,7 @@ @pytest.mark.parametrize( - "doc,expect,open_start,open_end", + ("doc", "expect", "open_start", "open_end"), [ (doc(p("hello world")), doc(p("hello")), 0, 1), (doc(p("hello")), doc(p("hello")), 0, 1), diff --git a/tests/prosemirror_transform/tests/conftest.py b/tests/prosemirror_transform/tests/conftest.py index da35a52..751cb13 100644 --- a/tests/prosemirror_transform/tests/conftest.py +++ b/tests/prosemirror_transform/tests/conftest.py @@ -17,7 +17,7 @@ p = out["p"] -@pytest.fixture +@pytest.fixture() def test_mapping(): def t_mapping(mapping, *cases): inverted = mapping.invert() @@ -32,7 +32,7 @@ def t_mapping(mapping, *cases): return t_mapping -@pytest.fixture +@pytest.fixture() def make_mapping(): def mk(*args): mapping = Mapping() @@ -47,7 +47,7 @@ def mk(*args): return mk -@pytest.fixture +@pytest.fixture() def test_del(): def t_del(mapping: Mapping, pos: int, side: int, flags: str): r = mapping.map_result(pos, side) @@ -65,7 +65,7 @@ def t_del(mapping: Mapping, pos: int, side: int, flags: str): return t_del -@pytest.fixture +@pytest.fixture() def make_step(): return _make_step @@ -82,7 +82,7 @@ def _make_step(from_, to, val): ) -@pytest.fixture +@pytest.fixture() def test_doc(): return doc(p("foobar")) @@ -90,7 +90,7 @@ def test_doc(): _test_doc = doc(p("foobar")) -@pytest.fixture +@pytest.fixture() def test_transform(): def invert(transform): out = Transform(transform.doc) diff --git a/tests/prosemirror_transform/tests/test_mapping.py b/tests/prosemirror_transform/tests/test_mapping.py index 35b4eeb..5fb1ffe 100644 --- a/tests/prosemirror_transform/tests/test_mapping.py +++ b/tests/prosemirror_transform/tests/test_mapping.py @@ -2,23 +2,23 @@ @pytest.mark.parametrize( - "mapping_info,cases", + ("mapping_info", "cases"), [ - [[[2, 0, 4]], [[0, 0], [2, 6], [2, 2, -1], [3, 7]]], - [ + ([[2, 0, 4]], [[0, 0], [2, 6], [2, 2, -1], [3, 7]]), + ( [[2, 4, 0]], [[0, 0], [2, 2, -1], [3, 2, 1, True], [6, 2, 1], [6, 2, -1, True], [7, 3]], - ], - [ + ), + ( [[2, 4, 4]], [[0, 0], [2, 2, 1], [4, 6, 1, True], [4, 2, -1, True], [6, 6, -1], [8, 8]], - ], - [[[2, 4, 0], [2, 0, 4], {0: 1}], [[0, 0], [2, 2], [4, 4], [6, 6], [7, 7]]], - [[[2, 0, 4], [2, 4, 0], {0: 1}], [[0, 0], [2, 2], [3, 3]]], - [ + ), + ([[2, 4, 0], [2, 0, 4], {0: 1}], [[0, 0], [2, 2], [4, 4], [6, 6], [7, 7]]), + ([[2, 0, 4], [2, 4, 0], {0: 1}], [[0, 0], [2, 2], [3, 3]]), + ( [[2, 4, 0], [1, 0, 1], [3, 0, 4], {0: 2}], [[0, 0], [1, 2], [4, 5], [6, 7], [7, 8]], - ], + ), ], ) def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): @@ -26,12 +26,12 @@ def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): @pytest.mark.parametrize( - "mapping_info,pos,side,flags", + ("mapping_info", "pos", "side", "flags"), [ - [([0, 2, 0],), 2, -1, "db"], - [([0, 2, 0],), 2, 1, "b"], - [([0, 2, 2],), 2, -1, "db"], - [ + (([0, 2, 0],), 2, -1, "db"), + (([0, 2, 0],), 2, 1, "b"), + (([0, 2, 2],), 2, -1, "db"), + ( ( [0, 1, 0], [0, 1, 0], @@ -39,12 +39,12 @@ def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): 2, -1, "db", - ], - [([0, 1, 0],), 2, -1, ""], - [([2, 2, 0],), 2, -1, "a"], - [([2, 2, 0],), 2, 1, "da"], - [([2, 2, 2],), 2, 1, "da"], - [ + ), + (([0, 1, 0],), 2, -1, ""), + (([2, 2, 0],), 2, -1, "a"), + (([2, 2, 0],), 2, 1, "da"), + (([2, 2, 2],), 2, 1, "da"), + ( ( [2, 1, 0], [2, 1, 0], @@ -52,12 +52,11 @@ def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): 2, 1, "da", - ], - [([3, 2, 0],), 2, -1, ""], - [([0, 4, 0],), 2, -1, "dbax"], - [([0, 4, 0],), 2, 1, "dbax"], - [([0, 4, 0],), 2, 1, "dbax"], - [ + ), + (([3, 2, 0],), 2, -1, ""), + (([0, 4, 0],), 2, -1, "dbax"), + (([0, 4, 0],), 2, 1, "dbax"), + ( ( [0, 1, 0], [4, 1, 0], @@ -66,8 +65,8 @@ def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): 2, 1, "dbax", - ], - [ + ), + ( ( [4, 1, 0], [0, 1, 0], @@ -75,8 +74,8 @@ def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): 2, -1, "", - ], - [ + ), + ( ( [2, 1, 0], [0, 2, 0], @@ -84,8 +83,8 @@ def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): 2, -1, "dba", - ], - [ + ), + ( ( [2, 1, 0], [0, 1, 0], @@ -93,8 +92,8 @@ def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): 2, -1, "a", - ], - [ + ), + ( ( [3, 1, 0], [0, 2, 0], @@ -102,7 +101,7 @@ def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): 2, -1, "db", - ], + ), ], ) def test_all_del_cases(mapping_info, pos, side, flags, test_del, make_mapping): diff --git a/tests/prosemirror_transform/tests/test_step.py b/tests/prosemirror_transform/tests/test_step.py index 9d1b8dd..95f4315 100644 --- a/tests/prosemirror_transform/tests/test_step.py +++ b/tests/prosemirror_transform/tests/test_step.py @@ -27,7 +27,7 @@ def inner(): @pytest.mark.parametrize( - "pass_,from1,to1,val1,from2,to2,val2", + ("pass_", "from1", "to1", "val1", "from2", "to2", "val2"), [ (yes, 2, 2, "a", 3, 3, "b"), (yes, 2, 2, "a", 2, 2, "b"), diff --git a/tests/prosemirror_transform/tests/test_structure.py b/tests/prosemirror_transform/tests/test_structure.py index 79ea9ea..245af98 100644 --- a/tests/prosemirror_transform/tests/test_structure.py +++ b/tests/prosemirror_transform/tests/test_structure.py @@ -69,7 +69,7 @@ def fill(params, length): class TestCanSplit: @pytest.mark.parametrize( - "pass_,pos,depth,after", + ("pass_", "pos", "depth", "after"), fill( [ (False, 0), @@ -147,7 +147,7 @@ def test_doesnt_return_true_when_split_content_doesnt_fit_in_given_node_type( class TestLiftTarget: @pytest.mark.parametrize( - "pass_,pos", + ("pass_", "pos"), [(False, 0), (False, 3), (False, 52), (False, 70), (True, 76), (False, 86)], ) def test_lift_target(self, pass_, pos): @@ -160,7 +160,7 @@ def test_lift_target(self, pass_, pos): class TestFindWrapping: @pytest.mark.parametrize( - "pass_,pos,end,type", + ("pass_", "pos", "end", "type"), [ (True, 0, 92, "sect"), (False, 4, 4, "sect"), @@ -179,7 +179,7 @@ def test_find_wrapping(self, pass_, pos, end, type): @pytest.mark.parametrize( - "doc,from_,to,content,open_start,open_end,result", + ("doc", "from_", "to", "content", "open_start", "open_end", "result"), [ ( n("doc", n("sect", n("head", t("foo")), n("para", t("bar")))), diff --git a/tests/prosemirror_transform/tests/test_trans.py b/tests/prosemirror_transform/tests/test_trans.py index 0dce106..d5f1137 100644 --- a/tests/prosemirror_transform/tests/test_trans.py +++ b/tests/prosemirror_transform/tests/test_trans.py @@ -27,7 +27,7 @@ @pytest.mark.parametrize( - "doc,mark,expect", + ("doc", "mark", "expect"), [ ( doc(p("hello there!")), @@ -108,7 +108,7 @@ def test_can_remote_multiple_excluded_marks(): @pytest.mark.parametrize( - "doc,mark,expect", + ("doc", "mark", "expect"), [ ( doc(p(em("hello world!"))), @@ -130,11 +130,6 @@ def test_can_remote_multiple_excluded_marks(): schema.mark("link", {"href": "foo"}), doc(p("hello link")), ), - ( - doc(p("hello ", a("link"))), - schema.mark("link", {"href": "foo"}), - doc(p("hello link")), - ), ( doc(p("hello ", a("link"))), schema.mark("link", {"href": "bar"}), @@ -193,7 +188,7 @@ def test_remove_more_than_one_mark_of_same_type_from_block(): @pytest.mark.parametrize( - "doc,nodes,expect", + ("doc", "nodes", "expect"), [ ( doc(p("hellothere")), @@ -235,7 +230,7 @@ def test_insert(doc, nodes, expect, test_transform): @pytest.mark.parametrize( - "doc,expect", + ("doc", "expect"), [ ( doc(p("<1>one"), "", p("tw<2>o"), "", p("<3>three")), @@ -256,7 +251,7 @@ def test_delete(doc, expect, test_transform): @pytest.mark.parametrize( - "doc,expect", + ("doc", "expect"), [ ( doc( @@ -296,7 +291,7 @@ def test_join(doc, expect, test_transform): @pytest.mark.parametrize( - "doc,expect,args", + ("doc", "expect", "args"), [ ( doc(p("<1>a"), p("<2>foobar<3>"), p("<4>b")), @@ -350,7 +345,7 @@ def test_split(doc, expect, args, test_transform): @pytest.mark.parametrize( - "doc,expect", + ("doc", "expect"), [ ( doc(blockquote(p("one"), p("two"), p("three"))), @@ -416,7 +411,7 @@ def test_lift(doc, expect, test_transform): @pytest.mark.parametrize( - "doc,expect,type,attrs", + ("doc", "expect", "type", "attrs"), [ ( doc(p("one"), p("two"), p("three")), @@ -471,7 +466,7 @@ def test_wrap(doc, expect, type, attrs, test_transform): @pytest.mark.parametrize( - "doc,expect,node_type,attrs", + ("doc", "expect", "node_type", "attrs"), [ (doc(p("am i")), doc(h2("am i")), "heading", {"level": 2}), ( @@ -532,7 +527,7 @@ def test_set_block_type_works_after_another_step(test_transform): @pytest.mark.parametrize( - "doc,expect,type,attrs", + ("doc", "expect", "type", "attrs"), [ (doc("", p("foo")), doc(h1("foo")), "heading", {"level": 1}), ( @@ -549,7 +544,7 @@ def test_set_node_markup(doc, expect, type, attrs, test_transform): @pytest.mark.parametrize( - "doc,expect,attr,value", + ("doc", "expect", "attr", "value"), [ (doc("", h1("foo")), doc(h2("foo")), "level", 2), ( @@ -566,7 +561,7 @@ def test_set_node_attribute(doc, expect, attr, value, test_transform): @pytest.mark.parametrize( - "doc,expect,attr,value", + ("doc", "expect", "attr", "value"), [ (doc(h1("foo")), docMetaOne(h1("foo")), "meta", 1), (docMetaOne(h1("foo")), docMetaTwo(h1("foo")), "meta", 2), @@ -579,7 +574,7 @@ def test_set_doc_attribute(doc, expect, attr, value, test_transform): @pytest.mark.parametrize( - "doc,source,expect", + ("doc", "source", "expect"), [ (doc(p("hello you")), None, doc(p("hellou"))), (doc(p("hello"), p("you")), None, doc(p("hellou"))), @@ -1008,7 +1003,7 @@ def test_keeps_isolating_nodes_together(): @pytest.mark.parametrize( - "doc,source,expect", + ("doc", "source", "expect"), [ (doc(p("foobar")), p("xx"), doc(p("fooxxar"))), (doc(p("")), doc(h1("text")), doc(h1("text"))), @@ -1056,7 +1051,7 @@ def test_replace_range(doc, source, expect, test_transform): @pytest.mark.parametrize( - "doc,node,expect", + ("doc", "node", "expect"), [ (doc(p("foo")), img(), doc(p("fo", img(), "o"))), (doc(p("foo")), img(), doc(p("", img(), "o"))), @@ -1080,7 +1075,7 @@ def test_replace_range_with(doc, node, expect, test_transform): @pytest.mark.parametrize( - "doc,expect", + ("doc", "expect"), [ (doc(p("foo"), p("bar")), doc(p("foar"))), ( @@ -1112,7 +1107,7 @@ def test_delete_range(doc, expect, test_transform): @pytest.mark.parametrize( - "doc,mark,expect", + ("doc", "mark", "expect"), [ # adds a mark (doc(p("", img())), schema.mark("em"), doc(p("", em(img())))), @@ -1131,7 +1126,7 @@ def test_add_node_mark(doc, mark, expect, test_transform): @pytest.mark.parametrize( - "doc,mark,expect", + ("doc", "mark", "expect"), [ # removes a mark (doc(p("", em(img()))), schema.mark("em"), doc(p("", img()))), From a03fb5f950d0bf53da2070e29c7e408cfa8beb33 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sun, 21 Apr 2024 10:31:28 -0400 Subject: [PATCH 07/13] Fix issue where step merging tests were not actually being run --- tests/prosemirror_transform/tests/conftest.py | 8 ++++---- tests/prosemirror_transform/tests/test_step.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/prosemirror_transform/tests/conftest.py b/tests/prosemirror_transform/tests/conftest.py index 751cb13..8161c6a 100644 --- a/tests/prosemirror_transform/tests/conftest.py +++ b/tests/prosemirror_transform/tests/conftest.py @@ -70,15 +70,15 @@ def make_step(): return _make_step -def _make_step(from_, to, val): +def _make_step(from_: int, to: int, val: str | None) -> Step: if val == "+em": - return AddMarkStep(from_, to, schema.marks["em"].create) + return AddMarkStep(from_, to, schema.marks["em"].create()) elif val == "-em": - return RemoveMarkStep(from_, to, schema.marks["em"].create) + return RemoveMarkStep(from_, to, schema.marks["em"].create()) return ReplaceStep( from_, to, - Slice.empty if val is None else Slice(Fragment.from_(schema.text(val), 0, 0)), + Slice.empty if val is None else Slice(Fragment.from_(schema.text(val)), 0, 0), ) diff --git a/tests/prosemirror_transform/tests/test_step.py b/tests/prosemirror_transform/tests/test_step.py index 95f4315..0b342e6 100644 --- a/tests/prosemirror_transform/tests/test_step.py +++ b/tests/prosemirror_transform/tests/test_step.py @@ -20,8 +20,8 @@ def no(from1, to1, val1, from2, to2, val2): def inner(): step1 = _make_step(from1, to1, val1) step2 = _make_step(from2, to2, val2) - with pytest.raises(ValueError): - step1.merge(step2) + merged = step1.merge(step2) + assert merged is None return inner @@ -51,4 +51,4 @@ def inner(): ], ) def test_all_cases(pass_, from1, to1, val1, from2, to2, val2): - pass_(from1, to1, val1, from2, to2, val2) + pass_(from1, to1, val1, from2, to2, val2)() From 17bb1a681d4eac1ad7d1736adcb3af4fdf8f726c Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sun, 21 Apr 2024 10:56:47 -0400 Subject: [PATCH 08/13] Relax lxml and typing-extensions dependencies --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7b22a1b..1ed269a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ authors = [ ] license = { text = "BSD-3-Clause" } keywords = ["prosemirror", "collaborative", "editing"] -dependencies = ["typing-extensions>=4.4", "lxml>=5.2", "cssselect>=1.2"] +dependencies = ["typing-extensions>=4.1", "lxml>=4.9", "cssselect>=1.2"] [project.optional-dependencies] dev = [ @@ -24,7 +24,7 @@ dev = [ "mypy~=1.9", "pytest~=8.1", "pytest-cov~=5.0", - "ruff~=0.3", + "ruff~=0.4", ] [tool.ruff.lint] From ef8fc22b265c4398867d081b680a99ffa86dbfb1 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sun, 21 Apr 2024 10:58:47 -0400 Subject: [PATCH 09/13] Rename is_text_block to is_textblock to match upstream --- prosemirror/model/from_dom.py | 4 ++-- prosemirror/model/node.py | 4 ++-- prosemirror/model/schema.py | 2 +- prosemirror/transform/replace.py | 4 ++-- prosemirror/transform/structure.py | 2 +- prosemirror/transform/transform.py | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/prosemirror/model/from_dom.py b/prosemirror/model/from_dom.py index d9a2630..c8253b4 100644 --- a/prosemirror/model/from_dom.py +++ b/prosemirror/model/from_dom.py @@ -985,7 +985,7 @@ def textblock_from_context(self) -> NodeType | None: if ( default is not None - and default.is_text_block + and default.is_textblock and default.default_attrs ): return default @@ -993,7 +993,7 @@ def textblock_from_context(self) -> NodeType | None: d -= 1 for type_ in self.parser.schema.nodes.values(): - if type_.is_text_block and type_.default_attrs: + if type_.is_textblock and type_.default_attrs: return type_ return None diff --git a/prosemirror/model/node.py b/prosemirror/model/node.py index 7d1d914..f9a065b 100644 --- a/prosemirror/model/node.py +++ b/prosemirror/model/node.py @@ -204,8 +204,8 @@ def is_block(self) -> bool: return self.type.is_block @property - def is_text_block(self) -> bool: - return self.type.is_text_block + def is_textblock(self) -> bool: + return self.type.is_textblock @property def inline_content(self) -> bool: diff --git a/prosemirror/model/schema.py b/prosemirror/model/schema.py index 6168f89..f8b384c 100644 --- a/prosemirror/model/schema.py +++ b/prosemirror/model/schema.py @@ -96,7 +96,7 @@ def is_inline(self) -> bool: return not self.is_block @property - def is_text_block(self) -> bool: # FIXME: name is wrong, should be is_textblock + def is_textblock(self) -> bool: return self.is_block and self.inline_content @property diff --git a/prosemirror/transform/replace.py b/prosemirror/transform/replace.py index 67b5586..8c692f2 100644 --- a/prosemirror/transform/replace.py +++ b/prosemirror/transform/replace.py @@ -345,7 +345,7 @@ def place_nodes(self, fittable: _Fittable) -> None: ) def must_move_inline(self) -> int: - if not self.to_.parent.is_text_block: + if not self.to_.parent.is_textblock: return -1 top = self.frontier[self.depth] @@ -359,7 +359,7 @@ def _lazy_level() -> _CloseLevel | None: return cast(_CloseLevel | None, level) if ( - not top.type.is_text_block + not top.type.is_textblock or not content_after_fits( self.to_, self.to_.depth, top.type, top.match, False ) diff --git a/prosemirror/transform/structure.py b/prosemirror/transform/structure.py index 0c96097..7061aba 100644 --- a/prosemirror/transform/structure.py +++ b/prosemirror/transform/structure.py @@ -200,7 +200,7 @@ def join_point(doc: Node, pos: int, dir: int = -1) -> int | None: after = pos_.node(d + 1) if ( before - and not before.is_text_block + and not before.is_textblock and joinable(before, after) and pos_.node(d).can_replace(index, index + 1) ): diff --git a/prosemirror/transform/transform.py b/prosemirror/transform/transform.py index fbd8539..6c5d05e 100644 --- a/prosemirror/transform/transform.py +++ b/prosemirror/transform/transform.py @@ -332,7 +332,7 @@ def replace_range(self, from_: int, to: int, slice: Slice) -> "Transform": from__.node(abs(preferred_target) - 1) ): preferred_depth = d - elif def_ or not left_node.type.is_text_block: + elif def_ or not left_node.type.is_textblock: break d -= 1 @@ -502,7 +502,7 @@ def set_block_type( ) -> "Transform": if to is None: to = from_ - if not type.is_text_block: + if not type.is_textblock: raise ValueError("Type given to set_block_type should be a textblock") map_from = len(self.steps) @@ -510,7 +510,7 @@ def iteratee( node: "Node", pos: int, parent: Optional["Node"], i: int ) -> bool | None: if ( - node.is_text_block + node.is_textblock and not node.has_markup(type, attrs) and structure.can_change_type( self.doc, self.mapping.slice(map_from).map(pos), type From 115ce06862c0465d9f43a8a57e74b53881b2f7e3 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sun, 21 Apr 2024 11:01:52 -0400 Subject: [PATCH 10/13] Enable EM and RSE rules --- prosemirror/model/content.py | 6 +++-- prosemirror/model/fragment.py | 9 +++++--- prosemirror/model/from_dom.py | 6 +++-- prosemirror/model/mark.py | 6 +++-- prosemirror/model/node.py | 20 +++++++++------- prosemirror/model/replace.py | 21 +++++++++++------ prosemirror/model/resolvedpos.py | 9 +++++--- prosemirror/model/schema.py | 27 ++++++++++++++-------- prosemirror/model/to_dom.py | 17 ++++++++------ prosemirror/transform/attr_step.py | 3 ++- prosemirror/transform/doc_attr_step.py | 3 ++- prosemirror/transform/mark_step.py | 12 ++++++---- prosemirror/transform/replace_step.py | 6 +++-- prosemirror/transform/step.py | 9 +++++--- prosemirror/transform/transform.py | 15 ++++++++---- pyproject.toml | 2 +- tests/prosemirror_model/tests/test_node.py | 11 +++++---- 17 files changed, 117 insertions(+), 65 deletions(-) diff --git a/prosemirror/model/content.py b/prosemirror/model/content.py index 1ee0512..1af658d 100644 --- a/prosemirror/model/content.py +++ b/prosemirror/model/content.py @@ -176,7 +176,8 @@ def edge_count(self) -> int: def edge(self, n: int) -> MatchEdge: if n >= len(self.next): - raise ValueError(f"There's no {n}th edge in this content match") + msg = f"There's no {n}th edge in this content match" + raise ValueError(msg) return self.next[n] def __str__(self) -> str: @@ -237,7 +238,8 @@ def eat(self, tok: str) -> int | bool: return False def err(self, str: str) -> NoReturn: - raise SyntaxError(f'{str} (in content expression) "{self.string}"') + msg = f'{str} (in content expression) "{self.string}"' + raise SyntaxError(msg) class ChoiceExpr(TypedDict): diff --git a/prosemirror/model/fragment.py b/prosemirror/model/fragment.py index 4d70ffe..3e268a0 100644 --- a/prosemirror/model/fragment.py +++ b/prosemirror/model/fragment.py @@ -229,7 +229,8 @@ def find_index(self, pos: int, round: int = -1) -> dict[str, int]: if pos == self.size: return retIndex(len(self.content), pos) if pos > self.size or pos < 0: - raise ValueError(f"Position {pos} outside of fragment ({self})") + msg = f"Position {pos} outside of fragment ({self})" + raise ValueError(msg) i = 0 cur_pos = 0 while True: @@ -258,7 +259,8 @@ def from_json(cls, schema: "Schema[Any, Any]", value: Any) -> "Fragment": value = json.loads(value) if not isinstance(value, list): - raise ValueError("Invalid input for Fragment.from_json") + msg = "Invalid input for Fragment.from_json" + raise ValueError(msg) return cls([schema.node_from_json(item) for item in value]) @@ -293,7 +295,8 @@ def from_( return cls.from_array(list(nodes)) if hasattr(nodes, "attrs"): return cls([nodes], nodes.node_size) - raise ValueError(f"cannot convert {nodes!r} to a fragment") + msg = f"cannot convert {nodes!r} to a fragment" + raise ValueError(msg) def to_string_inner(self) -> str: return ", ".join([str(i) for i in self.content]) diff --git a/prosemirror/model/from_dom.py b/prosemirror/model/from_dom.py index c8253b4..be7f9e8 100644 --- a/prosemirror/model/from_dom.py +++ b/prosemirror/model/from_dom.py @@ -1113,7 +1113,8 @@ def compare_document_position(node1: DOMNode, node2: DOMNode) -> int: if not isinstance(node1, lxml.etree._Element) or not isinstance( node2, lxml.etree._Element ): - raise ValueError("Both arguments must be lxml Element objects.") + msg = "Both arguments must be lxml Element objects." + raise ValueError(msg) tree = lxml.etree.ElementTree(node1) @@ -1144,7 +1145,8 @@ def compare_document_position(node1: DOMNode, node2: DOMNode) -> int: def get_node_type(element: DOMNode) -> int: if not isinstance(element, lxml.etree._Element): - raise ValueError("The provided element is not an lxml HtmlElement.") + msg = "The provided element is not an lxml HtmlElement." + raise ValueError(msg) if isinstance(element, lxml.etree._Comment): return 8 # Comment node type diff --git a/prosemirror/model/mark.py b/prosemirror/model/mark.py index 89cce11..41e878d 100644 --- a/prosemirror/model/mark.py +++ b/prosemirror/model/mark.py @@ -61,11 +61,13 @@ def from_json( json_data: JSONDict, ) -> "Mark": if not json_data: - raise ValueError("Invalid input for Mark.fromJSON") + msg = "Invalid input for Mark.fromJSON" + raise ValueError(msg) name = json_data["type"] type = schema.marks.get(name) if not type: - raise ValueError(f"There is no mark type {name} in this schema") + msg = f"There is no mark type {name} in this schema" + raise ValueError(msg) return type.create(cast(JSONDict | None, json_data.get("attrs"))) @classmethod diff --git a/prosemirror/model/node.py b/prosemirror/model/node.py index f9a065b..298e327 100644 --- a/prosemirror/model/node.py +++ b/prosemirror/model/node.py @@ -242,7 +242,8 @@ def __repr__(self) -> str: def content_match_at(self, index: int) -> "ContentMatch": match = self.type.content_match.match_fragment(self.content, 0, index) if not match: - raise ValueError("Called contentMatchAt on a node with invalid content") + msg = "Called contentMatchAt on a node with invalid content" + raise ValueError(msg) return match def can_replace( @@ -285,17 +286,17 @@ def can_append(self, other: "Node") -> bool: def check(self) -> None: if not self.type.valid_content(self.content): - raise ValueError( - f"Invalid content for node {self.type.name}: {str(self.content)[:50]}" - ) + msg = f"Invalid content for node {self.type.name}: {str(self.content)[:50]}" + raise ValueError(msg) copy = Mark.none for mark in self.marks: copy = mark.add_to_set(copy) if not Mark.same_set(copy, self.marks): - raise ValueError( + msg = ( f"Invalid collection of marks for node {self.type.name}:" f" {[m.type.name for m in self.marks]!r}" ) + raise ValueError(msg) def iteratee(node: "Node", offset: int, index: int) -> None: node.check() @@ -329,11 +330,13 @@ def from_json(cls, schema: "Schema[Any, Any]", json_data: JSONDict | str) -> "No json_data = cast(JSONDict, json.loads(json_data)) if not json_data: - raise ValueError("Invalid input for Node.from_json") + msg = "Invalid input for Node.from_json" + raise ValueError(msg) marks = None if json_data.get("marks"): if not isinstance(json_data["marks"], list): - raise ValueError("Invalid mark data for Node.fromJSON") + msg = "Invalid mark data for Node.fromJSON" + raise ValueError(msg) marks = [schema.mark_from_json(item) for item in json_data["marks"]] if json_data["type"] == "text": return schema.text(str(json_data["text"]), marks) @@ -353,7 +356,8 @@ def __init__( ) -> None: super().__init__(type, attrs, None, marks) if not content: - raise ValueError("Empty text nodes are not allowed") + msg = "Empty text nodes are not allowed" + raise ValueError(msg) self.text = content def __str__(self) -> str: diff --git a/prosemirror/model/replace.py b/prosemirror/model/replace.py index 8457735..01eb2f4 100644 --- a/prosemirror/model/replace.py +++ b/prosemirror/model/replace.py @@ -22,11 +22,13 @@ def remove_range(content: Fragment, from_: int, to: int) -> Fragment: index_to, offset_to = to_index_info["index"], to_index_info["offset"] if offset == from_ or cast("Node", child).is_text: if offset_to != to and not content.child(index_to).is_text: - raise ValueError("removing non-flat range") + msg = "removing non-flat range" + raise ValueError(msg) return content.cut(0, from_).append(content.cut(to)) assert child if index != index_to: - raise ValueError("removing non-flat range") + msg = "removing non-flat range" + raise ValueError(msg) return content.replace_child( index, child.copy(remove_range(child.content, from_ - offset - 1, to - offset - 1)), @@ -112,7 +114,8 @@ def from_json( open_start = json_data.get("openStart", 0) or 0 open_end = json_data.get("openEnd", 0) or 0 if not isinstance(open_start, int) or not isinstance(open_end, int): - raise ValueError("invalid input for Slice.from_json") + msg = "invalid input for Slice.from_json" + raise ValueError(msg) return cls( Fragment.from_json(schema, json_data.get("content")), open_start, @@ -139,9 +142,11 @@ def max_open(cls, fragment: Fragment, open_isolating: bool = True) -> "Slice": def replace(from_: "ResolvedPos", to: "ResolvedPos", slice: Slice) -> "Node": if slice.open_start > from_.depth: - raise ReplaceError("Inserted content deeper than insertion position") + msg = "Inserted content deeper than insertion position" + raise ReplaceError(msg) if from_.depth - slice.open_start != to.depth - slice.open_end: - raise ReplaceError("Inconsistent open depths") + msg = "Inconsistent open depths" + raise ReplaceError(msg) return replace_outer(from_, to, slice, 0) @@ -177,7 +182,8 @@ def replace_outer( def check_join(main: "Node", sub: "Node") -> None: if not sub.type.compatible_content(main.type): - raise ReplaceError(f"Cannot join {sub.type.name} onto {main.type.name}") + msg = f"Cannot join {sub.type.name} onto {main.type.name}" + raise ReplaceError(msg) def joinable(before: "ResolvedPos", after: "ResolvedPos", depth: int) -> "Node": @@ -220,7 +226,8 @@ def add_range( def close(node: "Node", content: Fragment) -> "Node": if not node.type.valid_content(content): - raise ReplaceError(f"Invalid content for node {node.type.name}") + msg = f"Invalid content for node {node.type.name}" + raise ReplaceError(msg) return node.copy(content) diff --git a/prosemirror/model/resolvedpos.py b/prosemirror/model/resolvedpos.py index 082d5d0..3b241ca 100644 --- a/prosemirror/model/resolvedpos.py +++ b/prosemirror/model/resolvedpos.py @@ -52,7 +52,8 @@ def end(self, depth: int | None = None) -> int: def before(self, depth: int | None = None) -> int: depth = self.resolve_depth(depth) if not depth: - raise ValueError("There is no position before the top level node") + msg = "There is no position before the top level node" + raise ValueError(msg) return ( self.pos if depth == self.depth + 1 else cast(int, self.path[depth * 3 - 1]) ) @@ -60,7 +61,8 @@ def before(self, depth: int | None = None) -> int: def after(self, depth: int | None = None) -> int: depth = self.resolve_depth(depth) if not depth: - raise ValueError("There is no position after the top level node") + msg = "There is no position after the top level node" + raise ValueError(msg) return ( self.pos if depth == self.depth + 1 @@ -181,7 +183,8 @@ def __str__(self) -> str: @classmethod def resolve(cls, doc: "Node", pos: int) -> "ResolvedPos": if not (pos >= 0 and pos <= doc.content.size): - raise ValueError(f"Position {pos} out of range") + msg = f"Position {pos} out of range" + raise ValueError(msg) path: list["Node" | int] = [] start = 0 parent_offset = pos diff --git a/prosemirror/model/schema.py b/prosemirror/model/schema.py index f8b384c..157bf0e 100644 --- a/prosemirror/model/schema.py +++ b/prosemirror/model/schema.py @@ -131,7 +131,8 @@ def create( marks: list[Mark] | None = None, ) -> Node: if self.is_text: - raise ValueError("NodeType.create cannot construct text nodes") + msg = "NodeType.create cannot construct text nodes" + raise ValueError(msg) return Node( self, self.compute_attrs(attrs), @@ -216,11 +217,14 @@ def compile( top_node = cast(Nodes, schema.spec.get("topNode") or "doc") if not result.get(top_node): - raise ValueError(f"Schema is missing its top node type {top_node}") + msg = f"Schema is missing its top node type {top_node}" + raise ValueError(msg) if not result.get(cast(Nodes, "text")): - raise ValueError("every schema needs a 'text' type") + msg = "every schema needs a 'text' type" + raise ValueError(msg) if result[cast(Nodes, "text")].attrs: - raise ValueError("the text node type should not have attributes") + msg = "the text node type should not have attributes" + raise ValueError(msg) return result def __str__(self) -> str: @@ -373,7 +377,8 @@ def __init__(self, spec: SchemaSpec[Nodes, Marks]) -> None: content_expr_cache = {} for prop in self.nodes: if prop in self.marks: - raise ValueError(f"{prop} can not be both a node and a mark") + msg = f"{prop} can not be both a node and a mark" + raise ValueError(msg) type = self.nodes[prop] content_expr = type.spec.get("content", "") mark_expr = type.spec.get("marks") @@ -414,9 +419,11 @@ def node( if isinstance(type, str): type = self.node_type(type) elif not isinstance(type, NodeType): - raise ValueError(f"Invalid node type: {type}") + msg = f"Invalid node type: {type}" + raise ValueError(msg) elif type.schema != self: - raise ValueError(f"Node type from different schema used ({type.name})") + msg = f"Node type from different schema used ({type.name})" + raise ValueError(msg) return type.create_checked(attrs, content, marks) def text(self, text: str, marks: list[Mark] | None = None) -> TextNode: @@ -446,7 +453,8 @@ def mark_from_json( def node_type(self, name: str) -> NodeType: found = self.nodes.get(cast(Nodes, name)) if not found: - raise ValueError(f"Unknown node type: {name}") + msg = f"Unknown node type: {name}" + raise ValueError(msg) return found @@ -465,5 +473,6 @@ def gather_marks(schema: Schema[Any, Any], marks: list[str]) -> list[MarkType]: ok = mark found.append(mark) if not ok: - raise SyntaxError(f"unknow mark type: '{mark}'") + msg = f"unknow mark type: '{mark}'" + raise SyntaxError(msg) return found diff --git a/prosemirror/model/to_dom.py b/prosemirror/model/to_dom.py index 3ca3d6c..db884b9 100644 --- a/prosemirror/model/to_dom.py +++ b/prosemirror/model/to_dom.py @@ -118,7 +118,8 @@ def serialize_node_inner(self, node: Node) -> HTMLNode: dom, content_dom = type(self).render_spec(self.nodes[node.type.name](node)) if content_dom: if node.is_leaf: - raise Exception("Content hole not allowed in a leaf node spec") + msg = "Content hole not allowed in a leaf node spec" + raise Exception(msg) self.serialize_fragment(node.content, content_dom) return dom @@ -148,7 +149,8 @@ def render_spec(cls, structure: HTMLOutputSpec) -> tuple[HTMLNode, Element | Non return structure, None tag_name = structure[0] if " " in tag_name[1:]: - raise NotImplementedError("XML namespaces are not supported") + msg = "XML namespaces are not supported" + raise NotImplementedError(msg) content_dom: Element | None = None dom = Element(name=tag_name, attrs={}, children=[]) attrs = structure[1] if len(structure) > 1 else None @@ -159,21 +161,22 @@ def render_spec(cls, structure: HTMLOutputSpec) -> tuple[HTMLNode, Element | Non if value is None: continue if " " in name[1:]: - raise NotImplementedError("XML namespaces are not supported") + msg = "XML namespaces are not supported" + raise NotImplementedError(msg) dom.attrs[name] = value for i in range(start, len(structure)): child = structure[i] if child == 0: if i < len(structure) - 1 or i > start: - raise Exception( - "Content hole must be the only child of its parent node" - ) + msg = "Content hole must be the only child of its parent node" + raise Exception(msg) return dom, dom inner, inner_content = cls.render_spec(child) dom.children.append(inner) if inner_content: if content_dom: - raise Exception("Multiple content holes") + msg = "Multiple content holes" + raise Exception(msg) content_dom = inner_content return dom, content_dom diff --git a/prosemirror/transform/attr_step.py b/prosemirror/transform/attr_step.py index 8d984ba..3e26f56 100644 --- a/prosemirror/transform/attr_step.py +++ b/prosemirror/transform/attr_step.py @@ -59,7 +59,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "AttrStep" if not isinstance(json_data["pos"], int) or not isinstance( json_data["attr"], str ): - raise ValueError("Invalid input for AttrStep.from_json") + msg = "Invalid input for AttrStep.from_json" + raise ValueError(msg) return AttrStep(json_data["pos"], json_data["attr"], json_data["value"]) diff --git a/prosemirror/transform/doc_attr_step.py b/prosemirror/transform/doc_attr_step.py index 381e823..2a33d19 100644 --- a/prosemirror/transform/doc_attr_step.py +++ b/prosemirror/transform/doc_attr_step.py @@ -46,7 +46,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "DocAttrSt json_data = cast(JSONDict, json.loads(json_data)) if not isinstance(json_data["attr"], str): - raise ValueError("Invalid input for DocAttrStep.from_json") + msg = "Invalid input for DocAttrStep.from_json" + raise ValueError(msg) return DocAttrStep(json_data["attr"], json_data["value"]) diff --git a/prosemirror/transform/mark_step.py b/prosemirror/transform/mark_step.py index 383a2bb..611a06b 100644 --- a/prosemirror/transform/mark_step.py +++ b/prosemirror/transform/mark_step.py @@ -89,7 +89,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "AddMarkSt if not isinstance(json_data["from"], int) or not isinstance( json_data["to"], int ): - raise ValueError("Invalid input for AddMarkStep.from_json") + msg = "Invalid input for AddMarkStep.from_json" + raise ValueError(msg) return AddMarkStep( json_data["from"], json_data["to"], @@ -160,7 +161,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> Step: if not isinstance(json_data["from"], int) or not isinstance( json_data["to"], int ): - raise ValueError("Invalid input for RemoveMarkStep.from_json") + msg = "Invalid input for RemoveMarkStep.from_json" + raise ValueError(msg) return RemoveMarkStep( json_data["from"], json_data["to"], @@ -219,7 +221,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> Step: json_data = cast(JSONDict, json.loads(json_data)) if not isinstance(json_data["pos"], int): - raise ValueError("Invalid input for AddNodeMarkStep.from_json") + msg = "Invalid input for AddNodeMarkStep.from_json" + raise ValueError(msg) return AddNodeMarkStep( json_data["pos"], schema.mark_from_json(cast(JSONDict, json_data["mark"])) ) @@ -273,7 +276,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> Step: json_data = cast(JSONDict, json.loads(json_data)) if not isinstance(json_data["pos"], int): - raise ValueError("Invalid input for RemoveNodeMarkStep.from_json") + msg = "Invalid input for RemoveNodeMarkStep.from_json" + raise ValueError(msg) return RemoveNodeMarkStep( json_data["pos"], schema.mark_from_json(cast(JSONDict, json_data["mark"])) ) diff --git a/prosemirror/transform/replace_step.py b/prosemirror/transform/replace_step.py index 78c7a9a..7fb7e5c 100644 --- a/prosemirror/transform/replace_step.py +++ b/prosemirror/transform/replace_step.py @@ -95,7 +95,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "ReplaceSt if not isinstance(json_data["from"], int) or not isinstance( json_data["to"], int ): - raise ValueError("Invlid input for ReplaceStep.from_json") + msg = "Invlid input for ReplaceStep.from_json" + raise ValueError(msg) return ReplaceStep( json_data["from"], json_data["to"], @@ -213,7 +214,8 @@ def from_json( or not isinstance(json_data["gapTo"], int) or not isinstance(json_data["insert"], int) ): - raise ValueError("Invlid input for ReplaceAroundStep.from_json") + msg = "Invlid input for ReplaceAroundStep.from_json" + raise ValueError(msg) return ReplaceAroundStep( json_data["from"], json_data["to"], diff --git a/prosemirror/transform/step.py b/prosemirror/transform/step.py index 0ad5e87..ebf1432 100644 --- a/prosemirror/transform/step.py +++ b/prosemirror/transform/step.py @@ -39,16 +39,19 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "Step": json_data = cast(JSONDict, json.loads(json_data)) if not json_data or not json_data.get("stepType"): - raise ValueError("Invalid inpit for Step.from_json") + msg = "Invalid inpit for Step.from_json" + raise ValueError(msg) type = STEPS_BY_ID.get(cast(str, json_data["stepType"])) if not type: - raise ValueError(f'no step type {json_data["stepType"]} defined') + msg = f'no step type {json_data["stepType"]} defined' + raise ValueError(msg) return type.from_json(schema, json_data) def step_json_id(id: str, step_class: type[StepSubclass]) -> type[StepSubclass]: if id in STEPS_BY_ID: - raise ValueError(f"Duplicated JSON ID for step type: {id}") + msg = f"Duplicated JSON ID for step type: {id}" + raise ValueError(msg) STEPS_BY_ID[id] = step_class step_class.json_id = id diff --git a/prosemirror/transform/transform.py b/prosemirror/transform/transform.py index 6c5d05e..474fbfe 100644 --- a/prosemirror/transform/transform.py +++ b/prosemirror/transform/transform.py @@ -477,10 +477,11 @@ def wrap( if content.size: match = wrappers[i].type.content_match.match_fragment(content) if not match or not match.valid_end: - raise TransformError( + msg = ( "Wrapper type given to Transform.wrap does not form valid " "content of its parent wrapper" ) + raise TransformError(msg) content = Fragment.from_( wrappers[i].type.create(wrappers[i].attrs, content) ) @@ -503,7 +504,8 @@ def set_block_type( if to is None: to = from_ if not type.is_textblock: - raise ValueError("Type given to set_block_type should be a textblock") + msg = "Type given to set_block_type should be a textblock" + raise ValueError(msg) map_from = len(self.steps) def iteratee( @@ -548,14 +550,16 @@ def set_node_markup( ) -> "Transform": node = self.doc.node_at(pos) if not node: - raise ValueError("No node at given position") + msg = "No node at given position" + raise ValueError(msg) if not type: type = node.type new_node = type.create(attrs, None, marks or node.marks) if node.is_leaf: return self.replace_with(pos, pos + node.node_size, new_node) if not type.valid_content(node.content): - raise ValueError(f"Invalid content for node type {type.name}") + msg = f"Invalid content for node type {type.name}" + raise ValueError(msg) return self.step( ReplaceAroundStep( pos, @@ -582,7 +586,8 @@ def remove_node_mark(self, pos: int, mark: Mark | MarkType) -> "Transform": node = self.doc.node_at(pos) if not node: - raise ValueError(f"No node at position {pos}") + msg = f"No node at position {pos}" + raise ValueError(msg) mark_in_set = mark.is_in_set(node.marks) diff --git a/pyproject.toml b/pyproject.toml index 1ed269a..e5c7bb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dev = [ ] [tool.ruff.lint] -select = ["E", "F", "W", "I", "RUF", "UP", "B", "I", "SIM", "PT"] +select = ["B", "E", "EM", "F", "I", "I", "PT", "RSE", "RUF", "SIM", "UP", "W"] preview = true [tool.ruff.format] diff --git a/tests/prosemirror_model/tests/test_node.py b/tests/prosemirror_model/tests/test_node.py index a1e4410..32bc1ee 100644 --- a/tests/prosemirror_model/tests/test_node.py +++ b/tests/prosemirror_model/tests/test_node.py @@ -128,15 +128,16 @@ def iteratee(node, pos, *args): nonlocal i if i == len(nodes): - raise Exception(f"More nodes iterated than list ({node.type.name})") + msg = f"More nodes iterated than list ({node.type.name})" + raise Exception(msg) compare = node.text if node.is_text else node.type.name if compare != nodes[i]: - raise Exception(f"Expected {nodes[i]!r}, got {compare!r}") + msg = f"Expected {nodes[i]!r}, got {compare!r}" + raise Exception(msg) i += 1 if not node.is_text and doc.node_at(pos) != node: - raise Exception( - f"Pos {pos} does not point at node {node!r} {doc.nodeAt(pos)!r}" - ) + msg = f"Pos {pos} does not point at node {node!r} {doc.nodeAt(pos)!r}" + raise Exception(msg) doc.nodes_between(doc.tag["a"], doc.tag["b"], iteratee) From ca698cc5b53ec58de68de13d7cc44e1f2cb85489 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sun, 21 Apr 2024 11:04:05 -0400 Subject: [PATCH 11/13] Add COM ruff rule --- example.py | 4 +- prosemirror/model/content.py | 20 +++-- prosemirror/model/diff.py | 5 +- prosemirror/model/fragment.py | 14 ++- prosemirror/model/from_dom.py | 60 +++++++++---- prosemirror/model/node.py | 30 +++++-- prosemirror/model/replace.py | 16 +++- prosemirror/model/resolvedpos.py | 5 +- prosemirror/model/schema.py | 22 +++-- prosemirror/model/to_dom.py | 19 ++-- prosemirror/schema/basic/schema_basic.py | 2 +- prosemirror/schema/list/schema_list.py | 10 ++- prosemirror/test_builder/__init__.py | 2 +- prosemirror/transform/attr_step.py | 3 +- prosemirror/transform/map.py | 5 +- prosemirror/transform/mark_step.py | 24 +++-- prosemirror/transform/replace.py | 31 ++++--- prosemirror/transform/replace_step.py | 32 +++++-- prosemirror/transform/structure.py | 21 +++-- prosemirror/transform/transform.py | 57 ++++++++---- pyproject.toml | 16 +++- tests/prosemirror_model/tests/test_content.py | 2 +- tests/prosemirror_model/tests/test_dom.py | 25 +++--- tests/prosemirror_model/tests/test_mark.py | 2 +- tests/prosemirror_model/tests/test_node.py | 19 ++-- tests/prosemirror_model/tests/test_resolve.py | 2 +- tests/prosemirror_model/tests/test_slice.py | 6 +- .../prosemirror_transform/tests/test_step.py | 2 +- .../tests/test_structure.py | 6 +- .../prosemirror_transform/tests/test_trans.py | 89 ++++++++++++------- 30 files changed, 385 insertions(+), 166 deletions(-) diff --git a/example.py b/example.py index dea19e6..82fd81c 100644 --- a/example.py +++ b/example.py @@ -75,10 +75,10 @@ "parseDOM": [{"tag": "a[href]"}], }, "em": { - "parseDOM": [{"tag": "i"}, {"tag": "em"}, {"style": "font-style=italic"}] + "parseDOM": [{"tag": "i"}, {"tag": "em"}, {"style": "font-style=italic"}], }, "strong": { - "parseDOM": [{"tag": "strong"}, {"tag": "b"}, {"style": "font-weight"}] + "parseDOM": [{"tag": "strong"}, {"tag": "b"}, {"style": "font-weight"}], }, "code": {"parseDOM": [{"tag": "code"}]}, }, diff --git a/prosemirror/model/content.py b/prosemirror/model/content.py index 1af658d..31d4b84 100644 --- a/prosemirror/model/content.py +++ b/prosemirror/model/content.py @@ -79,7 +79,10 @@ def match_type(self, type: "NodeType") -> Optional["ContentMatch"]: return None def match_fragment( - self, frag: Fragment, start: int = 0, end: int | None = None + self, + frag: Fragment, + start: int = 0, + end: int | None = None, ) -> Optional["ContentMatch"]: if end is None: end = frag.child_count @@ -110,7 +113,10 @@ def compatible(self, other: "ContentMatch") -> bool: return False def fill_before( - self, after: Fragment, to_end: bool = False, start_index: int = 0 + self, + after: Fragment, + to_end: bool = False, + start_index: int = 0, ) -> Fragment | None: seen = [self] @@ -403,7 +409,9 @@ def node() -> int: return len(nfa_) - 1 def edge( - from_: int, to: int | None = None, term: Optional["NodeType"] = None + from_: int, + to: int | None = None, + term: Optional["NodeType"] = None, ) -> Edge: nonlocal nfa_ edge: Edge = {"term": term, "to": to} @@ -421,7 +429,7 @@ def compile(expr: Expr, from_: int) -> list[Edge]: lambda out, expr: [*out, *compile(expr, from_)], expr["exprs"], cast(list[Edge], []), - ) + ), ) elif expr["type"] == "seq": i = 0 @@ -524,7 +532,7 @@ def explore(states: list[int]) -> ContentMatch: states = out[i][1] find_by_key = ",".join(str(s) for s in states) state.next.append( - MatchEdge(out[i][0], labeled.get(find_by_key) or explore(states)) + MatchEdge(out[i][0], labeled.get(find_by_key) or explore(states)), ) return state @@ -549,6 +557,6 @@ def check_for_dead_ends(match: ContentMatch, stream: TokenStream) -> None: if dead: stream.err( f'Only non-generatable nodes ({", ".join(nodes)}) in a required ' - "position (see https://prosemirror.net/docs/guide/#generatable)" + "position (see https://prosemirror.net/docs/guide/#generatable)", ) i += 1 diff --git a/prosemirror/model/diff.py b/prosemirror/model/diff.py index b84d780..2dbef41 100644 --- a/prosemirror/model/diff.py +++ b/prosemirror/model/diff.py @@ -94,7 +94,10 @@ def find_diff_end(a: "Fragment", b: "Fragment", pos_a: int, pos_b: int) -> Diff if child_a.content.size or child_b.content.size: inner = find_diff_end( - child_a.content, child_b.content, pos_a - 1, pos_b - 1 + child_a.content, + child_b.content, + pos_a - 1, + pos_b - 1, ) if inner: return inner diff --git a/prosemirror/model/fragment.py b/prosemirror/model/fragment.py index 3e268a0..4e14c67 100644 --- a/prosemirror/model/fragment.py +++ b/prosemirror/model/fragment.py @@ -59,7 +59,8 @@ def nodes_between( i += 1 def descendants( - self, f: Callable[["Node", int, Optional["Node"], int], bool | None] + self, + f: Callable[["Node", int, Optional["Node"], int], bool | None], ) -> None: self.nodes_between(0, self.size, f) @@ -74,7 +75,10 @@ def text_between( separated = True def iteratee( - node: "Node", pos: int, _parent: Optional["Node"], _to: int + node: "Node", + pos: int, + _parent: Optional["Node"], + _to: int, ) -> None: nonlocal text nonlocal separated @@ -134,7 +138,8 @@ def cut(self, from_: int, to: int | None = None) -> "Fragment": if pos < from_ or end > to: if pm_node.is_text(child): child = child.cut( - max(0, from_ - pos), min(text_length(child.text), to - pos) + max(0, from_ - pos), + min(text_length(child.text), to - pos), ) else: child = child.cut( @@ -285,7 +290,8 @@ def from_array(cls, array: list["Node"]) -> "Fragment": @classmethod def from_( - cls, nodes: Union["Fragment", "Node", Sequence["Node"], None] + cls, + nodes: Union["Fragment", "Node", Sequence["Node"], None], ) -> "Fragment": if not nodes: return cls.empty diff --git a/prosemirror/model/from_dom.py b/prosemirror/model/from_dom.py index be7f9e8..994a2e9 100644 --- a/prosemirror/model/from_dom.py +++ b/prosemirror/model/from_dom.py @@ -108,7 +108,9 @@ def __init__(self, schema: Schema[Any, Any], rules: list[ParseRule]) -> None: ]) def parse( - self, dom_: lxml.html.HtmlElement, options: ParseOptions | None = None + self, + dom_: lxml.html.HtmlElement, + options: ParseOptions | None = None, ) -> Node: if options is None: options = ParseOptions() @@ -144,7 +146,10 @@ def parse_slice(self, dom_: DOMNode, options: ParseOptions | None = None) -> Sli return Slice.max_open(cast(Fragment, context.finish())) def match_tag( - self, dom_: DOMNode, context: "ParseContext", after: ParseRule | None = None + self, + dom_: DOMNode, + context: "ParseContext", + after: ParseRule | None = None, ) -> ParseRule | None: try: i = self._tags.index(after) + 1 if after is not None else 0 @@ -254,7 +259,8 @@ def insert(rule: ParseRule) -> None: def from_schema(cls, schema: Schema[Any, Any]) -> "DOMParser": if "dom_parser" not in schema.cached: schema.cached["dom_parser"] = DOMParser( - schema, DOMParser.schema_rules(schema) + schema, + DOMParser.schema_rules(schema), ) return cast("DOMParser", schema.cached["dom_parser"]) @@ -313,7 +319,9 @@ def from_schema(cls, schema: Schema[Any, Any]) -> "DOMParser": def ws_options_for( - _type: NodeType | None, preserve_whitespace: WSType, base: int + _type: NodeType | None, + preserve_whitespace: WSType, + base: int, ) -> int: if preserve_whitespace is not None: return (OPT_PRESERVE_WS if preserve_whitespace else 0) | ( @@ -418,7 +426,7 @@ def finish(self, open_end: bool) -> Node | Fragment: content = Fragment.from_(self.content) if not open_end and self.match is not None: content = content.append( - cast(Fragment, self.match.fill_before(Fragment.empty, True)) + cast(Fragment, self.match.fill_before(Fragment.empty, True)), ) return ( @@ -486,7 +494,13 @@ def __init__(self, parser: DOMParser, options: ParseOptions, is_open: bool) -> N ) elif is_open: top_context = NodeContext( - None, None, Mark.none, Mark.none, True, None, top_options + None, + None, + Mark.none, + Mark.none, + True, + None, + top_options, ) else: top_context = NodeContext( @@ -565,7 +579,8 @@ def add_text_node(self, dom_: DOMNode) -> None: or ( node_before.is_text and re.search( - r"[ \t\r\n\u000c]$", cast(TextNode, node_before).text + r"[ \t\r\n\u000c]$", + cast(TextNode, node_before).text, ) is not None ) @@ -632,7 +647,9 @@ def add_element(self, dom_: DOMNode, match_after: ParseRule | None = None) -> No else: self.add_element_by_rule( - dom_, rule, rule_id if rule.consuming is False else None + dom_, + rule, + rule_id if rule.consuming is False else None, ) def leaf_fallback(self, dom_: DOMNode) -> None: @@ -678,7 +695,10 @@ def read_styles(self, styles: list[str]) -> tuple[list[Mark], list[Mark]] | None return add, remove def add_element_by_rule( - self, dom_: DOMNode, rule: ParseRule, continue_after: ParseRule | None = None + self, + dom_: DOMNode, + rule: ParseRule, + continue_after: ParseRule | None = None, ) -> None: sync: bool = False mark: Mark | None = None @@ -704,7 +724,7 @@ def add_element_by_rule( elif rule.get_content is not None: self.find_inside(dom_) rule.get_content(dom_, self.parser.schema).for_each( - lambda node, offset, index: self.insert_node(node) + lambda node, offset, index: self.insert_node(node), ) else: content_dom = dom_ @@ -807,7 +827,10 @@ def insert_node(self, node: Node) -> bool: return False def enter( - self, type_: NodeType, attrs: Attrs | None = None, preserve_ws: WSType = None + self, + type_: NodeType, + attrs: Attrs | None = None, + preserve_ws: WSType = None, ) -> bool: ok = self.find_place(type_.create(attrs)) if ok: @@ -837,8 +860,14 @@ def enter_inner( self.nodes.append( NodeContext( - type_, attrs, top.active_marks, top.pending_marks, solid, None, options - ) + type_, + attrs, + top.active_marks, + top.pending_marks, + solid, + None, + options, + ), ) self.open += 1 @@ -849,7 +878,7 @@ def close_extra(self, open_end: bool = False) -> None: if i > self.open: while i > self.open: self.nodes[i - 1].content.append( - cast(Node, self.nodes[i].finish(open_end)) + cast(Node, self.nodes[i].finish(open_end)), ) i -= 1 @@ -1111,7 +1140,8 @@ def node_contains(node: DOMNode, find: DOMNode) -> bool: def compare_document_position(node1: DOMNode, node2: DOMNode) -> int: if not isinstance(node1, lxml.etree._Element) or not isinstance( - node2, lxml.etree._Element + node2, + lxml.etree._Element, ): msg = "Both arguments must be lxml Element objects." raise ValueError(msg) diff --git a/prosemirror/model/node.py b/prosemirror/model/node.py index 298e327..1135d4e 100644 --- a/prosemirror/model/node.py +++ b/prosemirror/model/node.py @@ -64,7 +64,8 @@ def nodes_between( self.content.nodes_between(from_, to, f, start_pos, self) def descendants( - self, f: Callable[["Node", int, Optional["Node"], int], bool | None] + self, + f: Callable[["Node", int, Optional["Node"], int], bool | None], ) -> None: self.nodes_between(0, self.content.size, f) @@ -127,7 +128,10 @@ def cut(self, from_: int, to: int | None = None) -> "Node": return self.copy(self.content.cut(from_, to)) def slice( - self, from_: int, to: int | None = None, include_parents: bool = False + self, + from_: int, + to: int | None = None, + include_parents: bool = False, ) -> Slice: if to is None: to = self.content.size @@ -183,13 +187,19 @@ def resolve_no_cache(self, pos: int) -> ResolvedPos: return ResolvedPos.resolve(self, pos) def range_has_mark( - self, from_: int, to: int, type: Union["Mark", "MarkType"] + self, + from_: int, + to: int, + type: Union["Mark", "MarkType"], ) -> bool: found = False if to > from_: def iteratee( - node: "Node", pos: int, parent: Optional["Node"], index: int + node: "Node", + pos: int, + parent: Optional["Node"], + index: int, ) -> bool: nonlocal found if type.is_in_set(node.marks): @@ -268,7 +278,11 @@ def can_replace( return True def can_replace_with( - self, from_: int, to: int, type: "NodeType", marks: list[Mark] | None = None + self, + from_: int, + to: int, + type: "NodeType", + marks: list[Mark] | None = None, ) -> bool: if marks and not self.type.allows_marks(marks): return False @@ -342,7 +356,9 @@ def from_json(cls, schema: "Schema[Any, Any]", json_data: JSONDict | str) -> "No return schema.text(str(json_data["text"]), marks) content = Fragment.from_json(schema, json_data.get("content")) return schema.node_type(str(json_data["type"])).create( - cast("Attrs", json_data.get("attrs")), content, marks + cast("Attrs", json_data.get("attrs")), + content, + marks, ) @@ -403,7 +419,7 @@ def cut(self, from_: int = 0, to: int | None = None) -> "TextNode": if from_ == 0 and to == text_length(self.text): return self substring = self.text.encode("utf-16-le")[2 * from_ : 2 * to].decode( - "utf-16-le" + "utf-16-le", ) return self.with_text(substring) diff --git a/prosemirror/model/replace.py b/prosemirror/model/replace.py index 01eb2f4..f1922a3 100644 --- a/prosemirror/model/replace.py +++ b/prosemirror/model/replace.py @@ -36,7 +36,10 @@ def remove_range(content: Fragment, from_: int, to: int) -> Fragment: def insert_into( - content: Fragment, dist: int, insert: Fragment, parent: Optional["Node"] + content: Fragment, + dist: int, + insert: Fragment, + parent: Optional["Node"], ) -> Fragment | None: a = content.find_index(dist) index, offset = a["index"], a["offset"] @@ -151,7 +154,10 @@ def replace(from_: "ResolvedPos", to: "ResolvedPos", slice: Slice) -> "Node": def replace_outer( - from_: "ResolvedPos", to: "ResolvedPos", slice: Slice, depth: int + from_: "ResolvedPos", + to: "ResolvedPos", + slice: Slice, + depth: int, ) -> "Node": index = from_.index(depth) node = from_.node(depth) @@ -251,7 +257,8 @@ def replace_three_way( else: if open_start: add_node( - close(open_start, replace_two_way(from_, start, depth + 1)), content + close(open_start, replace_two_way(from_, start, depth + 1)), + content, ) add_range(start, end, depth, content) if open_end: @@ -271,7 +278,8 @@ def replace_two_way(from_: "ResolvedPos", to: "ResolvedPos", depth: int) -> Frag def prepare_slice_for_replace( - slice: Slice, along: "ResolvedPos" + slice: Slice, + along: "ResolvedPos", ) -> dict[str, "ResolvedPos"]: extra = along.depth - slice.open_start parent = along.node(extra) diff --git a/prosemirror/model/resolvedpos.py b/prosemirror/model/resolvedpos.py index 3b241ca..2aeba5a 100644 --- a/prosemirror/model/resolvedpos.py +++ b/prosemirror/model/resolvedpos.py @@ -9,7 +9,10 @@ class ResolvedPos: def __init__( - self, pos: int, path: list[Union["Node", int]], parent_offset: int + self, + pos: int, + path: list[Union["Node", int]], + parent_offset: int, ) -> None: self.pos = pos self.path = path diff --git a/prosemirror/model/schema.py b/prosemirror/model/schema.py index 157bf0e..1ab5ab2 100644 --- a/prosemirror/model/schema.py +++ b/prosemirror/model/schema.py @@ -208,7 +208,9 @@ def allowed_marks(self, marks: list[Mark]) -> list[Mark]: @classmethod def compile( - cls, nodes: dict["Nodes", "NodeSpec"], schema: "Schema[Nodes, Marks]" + cls, + nodes: dict["Nodes", "NodeSpec"], + schema: "Schema[Nodes, Marks]", ) -> dict["Nodes", "NodeType"]: result: dict["Nodes", "NodeType"] = {} @@ -252,7 +254,11 @@ class MarkType: instance: Mark | None def __init__( - self, name: str, rank: int, schema: "Schema[Any, Any]", spec: "MarkSpec" + self, + name: str, + rank: int, + schema: "Schema[Any, Any]", + spec: "MarkSpec", ) -> None: self.name = name self.schema = schema @@ -275,7 +281,9 @@ def create( @classmethod def compile( - cls, marks: dict["Marks", "MarkSpec"], schema: "Schema[Nodes, Marks]" + cls, + marks: dict["Marks", "MarkSpec"], + schema: "Schema[Nodes, Marks]", ) -> dict["Marks", "MarkType"]: result = {} for rank, (name, spec) in enumerate(marks.items()): @@ -384,7 +392,8 @@ def __init__(self, spec: SchemaSpec[Nodes, Marks]) -> None: mark_expr = type.spec.get("marks") if content_expr not in content_expr_cache: content_expr_cache[content_expr] = ContentMatch.parse( - content_expr, cast(dict[str, "NodeType"], self.nodes) + content_expr, + cast(dict[str, "NodeType"], self.nodes), ) type.content_match = content_expr_cache[content_expr] @@ -429,7 +438,10 @@ def node( def text(self, text: str, marks: list[Mark] | None = None) -> TextNode: type = self.nodes[cast(Nodes, "text")] return TextNode( - type, cast(Attrs, type.default_attrs), text, Mark.set_from(marks) + type, + cast(Attrs, type.default_attrs), + text, + Mark.set_from(marks), ) def mark( diff --git a/prosemirror/model/to_dom.py b/prosemirror/model/to_dom.py index db884b9..fa81861 100644 --- a/prosemirror/model/to_dom.py +++ b/prosemirror/model/to_dom.py @@ -43,7 +43,10 @@ def __str__(self) -> str: class Element(DocumentFragment): def __init__( - self, name: str, attrs: dict[str, str], children: list[HTMLNode] + self, + name: str, + attrs: dict[str, str], + children: list[HTMLNode], ) -> None: self.name = name self.attrs = attrs @@ -72,7 +75,9 @@ def __init__( self.marks = marks def serialize_fragment( - self, fragment: Fragment, target: Element | DocumentFragment | None = None + self, + fragment: Fragment, + target: Element | DocumentFragment | None = None, ) -> DocumentFragment: tgt: DocumentFragment = target or DocumentFragment(children=[]) @@ -134,7 +139,9 @@ def serialize_node(self, node: Node) -> HTMLNode: return dom def serialize_mark( - self, mark: Mark, inline: bool + self, + mark: Mark, + inline: bool, ) -> tuple[HTMLNode, Element | None] | None: to_dom = self.marks.get(mark.type.name) if to_dom: @@ -186,7 +193,8 @@ def from_schema(cls, schema: Schema[Any, Any]) -> "DOMSerializer": @classmethod def nodes_from_schema( - cls, schema: Schema[str, Any] + cls, + schema: Schema[str, Any], ) -> dict[str, Callable[["Node"], HTMLOutputSpec]]: result = gather_to_dom(schema.nodes) if "text" not in result: @@ -195,7 +203,8 @@ def nodes_from_schema( @classmethod def marks_from_schema( - cls, schema: Schema[Any, Any] + cls, + schema: Schema[Any, Any], ) -> dict[str, Callable[["Mark", bool], HTMLOutputSpec]]: return gather_to_dom(schema.marks) diff --git a/prosemirror/schema/basic/schema_basic.py b/prosemirror/schema/basic/schema_basic.py index 39857af..d7a35f8 100644 --- a/prosemirror/schema/basic/schema_basic.py +++ b/prosemirror/schema/basic/schema_basic.py @@ -66,7 +66,7 @@ "src": dom_.get("src"), "title": dom_.get("title"), }, - } + }, ], "toDOM": lambda node: [ "img", diff --git a/prosemirror/schema/list/schema_list.py b/prosemirror/schema/list/schema_list.py index 1d84000..eca4318 100644 --- a/prosemirror/schema/list/schema_list.py +++ b/prosemirror/schema/list/schema_list.py @@ -27,15 +27,19 @@ def add(obj: "NodeSpec", props: "NodeSpec") -> "NodeSpec": def add_list_nodes( - nodes: dict["Nodes", "NodeSpec"], item_content: str, list_group: str + nodes: dict["Nodes", "NodeSpec"], + item_content: str, + list_group: str, ) -> dict["Nodes", "NodeSpec"]: copy = nodes.copy() copy.update({ cast(Nodes, "ordered_list"): add( - orderd_list, NodeSpec(content="list_item+", group=list_group) + orderd_list, + NodeSpec(content="list_item+", group=list_group), ), cast(Nodes, "bullet_list"): add( - bullet_list, NodeSpec(content="list_item+", group=list_group) + bullet_list, + NodeSpec(content="list_item+", group=list_group), ), cast(Nodes, "list_item"): add(list_item, NodeSpec(content=item_content)), }) diff --git a/prosemirror/test_builder/__init__.py b/prosemirror/test_builder/__init__.py index aea745a..ba7e245 100644 --- a/prosemirror/test_builder/__init__.py +++ b/prosemirror/test_builder/__init__.py @@ -14,7 +14,7 @@ "doc": { "content": "block+", "attrs": {"meta": {"default": None}}, - } + }, }) test_schema: Schema[Any, Any] = Schema({ diff --git a/prosemirror/transform/attr_step.py b/prosemirror/transform/attr_step.py index 3e26f56..42c1190 100644 --- a/prosemirror/transform/attr_step.py +++ b/prosemirror/transform/attr_step.py @@ -57,7 +57,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "AttrStep" json_data = cast(JSONDict, json.loads(json_data)) if not isinstance(json_data["pos"], int) or not isinstance( - json_data["attr"], str + json_data["attr"], + str, ): msg = "Invalid input for AttrStep.from_json" raise ValueError(msg) diff --git a/prosemirror/transform/map.py b/prosemirror/transform/map.py index 351f836..9325394 100644 --- a/prosemirror/transform/map.py +++ b/prosemirror/transform/map.py @@ -193,7 +193,10 @@ def slice(self, from_: int = 0, to: int | None = None) -> "Mapping": def copy(self) -> "Mapping": return Mapping( - self.maps[:], (self.mirror[:] if self.mirror else None), self.from_, self.to + self.maps[:], + (self.mirror[:] if self.mirror else None), + self.from_, + self.to, ) def append_map(self, map: StepMap, mirrors: int | None = None) -> None: diff --git a/prosemirror/transform/mark_step.py b/prosemirror/transform/mark_step.py index 611a06b..8945ede 100644 --- a/prosemirror/transform/mark_step.py +++ b/prosemirror/transform/mark_step.py @@ -67,7 +67,9 @@ def merge(self, other: Step) -> Step | None: and self.to >= other.from_ ): return AddMarkStep( - min(self.from_, other.from_), max(self.to, other.to), self.mark + min(self.from_, other.from_), + max(self.to, other.to), + self.mark, ) return None @@ -87,7 +89,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "AddMarkSt json_data = cast(JSONDict, json.loads(json_data)) if not isinstance(json_data["from"], int) or not isinstance( - json_data["to"], int + json_data["to"], + int, ): msg = "Invalid input for AddMarkStep.from_json" raise ValueError(msg) @@ -139,7 +142,9 @@ def merge(self, other: Step) -> Step | None: and self.to >= other.from_ ): return RemoveMarkStep( - min(self.from_, other.from_), max(self.to, other.to), self.mark + min(self.from_, other.from_), + max(self.to, other.to), + self.mark, ) return None @@ -159,7 +164,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> Step: json_data = cast(JSONDict, json.loads(json_data)) if not isinstance(json_data["from"], int) or not isinstance( - json_data["to"], int + json_data["to"], + int, ): msg = "Invalid input for RemoveMarkStep.from_json" raise ValueError(msg) @@ -224,7 +230,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> Step: msg = "Invalid input for AddNodeMarkStep.from_json" raise ValueError(msg) return AddNodeMarkStep( - json_data["pos"], schema.mark_from_json(cast(JSONDict, json_data["mark"])) + json_data["pos"], + schema.mark_from_json(cast(JSONDict, json_data["mark"])), ) @@ -242,7 +249,9 @@ def apply(self, doc: Node) -> StepResult: if not node: return StepResult.fail("No node at mark step's position") updated = node.type.create( - node.attrs, None, self.mark.remove_from_set(node.marks) + node.attrs, + None, + self.mark.remove_from_set(node.marks), ) return StepResult.from_replace( doc, @@ -279,7 +288,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> Step: msg = "Invalid input for RemoveNodeMarkStep.from_json" raise ValueError(msg) return RemoveNodeMarkStep( - json_data["pos"], schema.mark_from_json(cast(JSONDict, json_data["mark"])) + json_data["pos"], + schema.mark_from_json(cast(JSONDict, json_data["mark"])), ) diff --git a/prosemirror/transform/replace.py b/prosemirror/transform/replace.py index 8c692f2..c301651 100644 --- a/prosemirror/transform/replace.py +++ b/prosemirror/transform/replace.py @@ -118,7 +118,7 @@ def fit(self) -> Step | None: placed_size = self.placed.size - self.depth - self.from__.depth from__ = self.from__ to_ = self.close( - self.to_ if move_inline < 0 else from__.doc.resolve(move_inline) + self.to_ if move_inline < 0 else from__.doc.resolve(move_inline), ) if not to_: return None @@ -162,11 +162,14 @@ def find_fittable(self) -> _Fittable | None: for pass_ in [1, 2]: for slice_depth in range( - start_depth if pass_ == 1 else self.unplaced.open_start, -1, -1 + start_depth if pass_ == 1 else self.unplaced.open_start, + -1, + -1, ): if slice_depth: parent = content_at( - self.unplaced.content, slice_depth - 1 + self.unplaced.content, + slice_depth - 1, ).first_child assert parent fragment = parent.content @@ -186,7 +189,8 @@ def find_fittable(self) -> _Fittable | None: match.match_type(first.type) or ( inject := match.fill_before( - Fragment.from_(first), False + Fragment.from_(first), + False, ) ) ) @@ -297,7 +301,7 @@ def place_nodes(self, fittable: _Fittable) -> None: next_.mark(type_.allowed_marks(next_.marks)), open_start if taken == 1 else 0, open_end_count if taken == fragment.child_count else -1, - ) + ), ) to_end = taken == fragment.child_count @@ -325,7 +329,7 @@ def place_nodes(self, fittable: _Fittable) -> None: node = cur.last_child assert node is not None self.frontier.append( - _FrontierItem(node.type, node.content_match_at(node.child_count)) + _FrontierItem(node.type, node.content_match_at(node.child_count)), ) cur = node.content @@ -361,7 +365,11 @@ def _lazy_level() -> _CloseLevel | None: if ( not top.type.is_textblock or not content_after_fits( - self.to_, self.to_.depth, top.type, top.match, False + self.to_, + self.to_.depth, + top.type, + top.match, + False, ) or ( self.to_.depth == self.depth @@ -430,7 +438,9 @@ def open_frontier_node( assert top_match is not None top.match = top_match self.placed = add_to_fragment( - self.placed, self.depth, Fragment.from_(type_.create(attrs, content)) + self.placed, + self.depth, + Fragment.from_(type_.create(attrs, content)), ) self.frontier.append(_FrontierItem(type_, type_.content_match)) @@ -531,7 +541,7 @@ def close_fragment( fragment = fragment.replace_child( 0, first.copy( - close_fragment(first.content, depth + 1, old_open, new_open, first) + close_fragment(first.content, depth + 1, old_open, new_open, first), ), ) if depth > new_open: @@ -543,7 +553,8 @@ def close_fragment( matched_fragment = match.match_fragment(start) assert matched_fragment is not None matched_fragment_fill_before = matched_fragment.fill_before( - Fragment.empty, True + Fragment.empty, + True, ) assert matched_fragment_fill_before is not None fragment = start.append(matched_fragment_fill_before) diff --git a/prosemirror/transform/replace_step.py b/prosemirror/transform/replace_step.py index 7fb7e5c..bbfc3cd 100644 --- a/prosemirror/transform/replace_step.py +++ b/prosemirror/transform/replace_step.py @@ -8,7 +8,11 @@ class ReplaceStep(Step): def __init__( - self, from_: int, to: int, slice: Slice, structure: bool | None = None + self, + from_: int, + to: int, + slice: Slice, + structure: bool | None = None, ) -> None: super().__init__() self.from_ = from_ @@ -26,7 +30,9 @@ def get_map(self) -> StepMap: def invert(self, doc: Node) -> "ReplaceStep": return ReplaceStep( - self.from_, self.from_ + self.slice.size, doc.slice(self.from_, self.to) + self.from_, + self.from_ + self.slice.size, + doc.slice(self.from_, self.to), ) def map(self, mapping: Mappable) -> Optional["ReplaceStep"]: @@ -53,7 +59,10 @@ def merge(self, other: "Step") -> Optional["ReplaceStep"]: other.slice.open_end, ) return ReplaceStep( - self.from_, self.to + (other.to - other.from_), slice, self.structure + self.from_, + self.to + (other.to - other.from_), + slice, + self.structure, ) elif ( other.to == self.from_ @@ -93,7 +102,8 @@ def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "ReplaceSt json_data = cast(JSONDict, json.loads(json_data)) if not isinstance(json_data["from"], int) or not isinstance( - json_data["to"], int + json_data["to"], + int, ): msg = "Invlid input for ReplaceStep.from_json" raise ValueError(msg) @@ -160,7 +170,8 @@ def invert(self, doc: Node) -> "ReplaceAroundStep": self.from_ + self.insert, self.from_ + self.insert + gap, doc.slice(self.from_, self.to).remove_between( - self.gap_from - self.from_, self.gap_to - self.from_ + self.gap_from - self.from_, + self.gap_to - self.from_, ), self.gap_from - self.from_, self.structure, @@ -174,7 +185,13 @@ def map(self, mapping: Mappable) -> Optional["ReplaceAroundStep"]: if (from_.deleted and to.deleted) or gap_from < from_.pos or gap_to > to.pos: return None return ReplaceAroundStep( - from_.pos, to.pos, gap_from, gap_to, self.slice, self.insert, self.structure + from_.pos, + to.pos, + gap_from, + gap_to, + self.slice, + self.insert, + self.structure, ) def to_json(self) -> JSONDict: @@ -200,7 +217,8 @@ def to_json(self) -> JSONDict: @staticmethod def from_json( - schema: Schema[Any, Any], json_data: JSONDict | str + schema: Schema[Any, Any], + json_data: JSONDict | str, ) -> "ReplaceAroundStep": if isinstance(json_data, str): import json diff --git a/prosemirror/transform/structure.py b/prosemirror/transform/structure.py index 7061aba..634f918 100644 --- a/prosemirror/transform/structure.py +++ b/prosemirror/transform/structure.py @@ -121,7 +121,8 @@ def can_split( pos_ = doc.resolve(pos) base = pos_.depth - depth inner_type: NodeTypeWithAttrs = cast( - NodeTypeWithAttrs, (types_after and types_after[-1]) or pos_.parent + NodeTypeWithAttrs, + (types_after and types_after[-1]) or pos_.parent, ) if ( @@ -129,7 +130,7 @@ def can_split( or pos_.parent.type.spec.get("isolating") or not pos_.parent.can_replace(pos_.index(), pos_.parent.child_count) or not inner_type.type.valid_content( - pos_.parent.content.cut_by_index(pos_.index(), pos_.parent.child_count) + pos_.parent.content.cut_by_index(pos_.index(), pos_.parent.child_count), ) ): return False @@ -147,14 +148,16 @@ def can_split( if types_after and len(types_after) > i + 1: override_child = types_after[i + 1] rest = rest.replace_child( - 0, override_child.type.create(override_child.attrs) + 0, + override_child.type.create(override_child.attrs), ) after: NodeTypeWithAttrs = cast( NodeTypeWithAttrs, (types_after and len(types_after) > i and types_after[i]) or node, ) if not node.can_replace( - index + 1, node.child_count + index + 1, + node.child_count, ) or not after.type.valid_content(rest): return False d -= 1 @@ -162,7 +165,9 @@ def can_split( index = pos_.index_after(base) base_type = types_after[0] if types_after else None return pos_.node(base).can_replace_with( - index, index, base_type.type if base_type else pos_.node(base + 1).type + index, + index, + base_type.type if base_type else pos_.node(base + 1).type, ) @@ -259,10 +264,12 @@ def drop_point(doc: Node, pos: int, slice: Slice) -> int | None: else: assert content.first_child is not None wrapping = parent.content_match_at(insert_pos).find_wrapping( - content.first_child.type + content.first_child.type, ) fits = wrapping is not None and parent.can_replace_with( - insert_pos, insert_pos, wrapping[0] + insert_pos, + insert_pos, + wrapping[0], ) if fits: if bias == 0: diff --git a/prosemirror/transform/transform.py b/prosemirror/transform/transform.py index 474fbfe..11fa977 100644 --- a/prosemirror/transform/transform.py +++ b/prosemirror/transform/transform.py @@ -227,14 +227,15 @@ def clear_incompatible( slice = Slice( Fragment.from_( parent_type.schema.text( - " ", parent_type.allowed_marks(child.marks) - ) + " ", + parent_type.allowed_marks(child.marks), + ), ), 0, 0, ) repl_steps.append( - ReplaceStep(cur + m.start(), cur + m.end(), slice) + ReplaceStep(cur + m.start(), cur + m.end(), slice), ) m = newline.search(child.text, m.end()) cur = end @@ -329,7 +330,7 @@ def replace_range(self, from_: int, to: int, slice: Slice) -> "Transform": assert left_node is not None def_ = defines_content(left_node.type) if def_ and not left_node.same_markup( - from__.node(abs(preferred_target) - 1) + from__.node(abs(preferred_target) - 1), ): preferred_depth = d elif def_ or not left_node.type.is_textblock: @@ -357,7 +358,11 @@ def replace_range(self, from_: int, to: int, slice: Slice) -> "Transform": to_.after(target_depth) if expand else to, Slice( close_fragment( - slice.content, 0, slice.open_start, open_depth, None + slice.content, + 0, + slice.open_start, + open_depth, + None, ), open_depth, slice.open_end, @@ -403,7 +408,8 @@ def delete_range(self, from_: int, to: int) -> "Transform": if depth > 0 and ( last or from__.node(depth - 1).can_replace( - from__.index(depth - 1), to_.index_after(depth - 1) + from__.index(depth - 1), + to_.index_after(depth - 1), ) ): return self.delete(from__.before(depth), to_.after(depth)) @@ -465,11 +471,13 @@ def lift(self, range_: NodeRange, target: int) -> "Transform": Slice(before.append(after), open_start, open_end), before.size - open_start, True, - ) + ), ) def wrap( - self, range_: NodeRange, wrappers: list[structure.NodeTypeWithAttrs] + self, + range_: NodeRange, + wrappers: list[structure.NodeTypeWithAttrs], ) -> "Transform": content = Fragment.empty i = len(wrappers) - 1 @@ -483,15 +491,21 @@ def wrap( ) raise TransformError(msg) content = Fragment.from_( - wrappers[i].type.create(wrappers[i].attrs, content) + wrappers[i].type.create(wrappers[i].attrs, content), ) i -= 1 start = range_.start end = range_.end return self.step( ReplaceAroundStep( - start, end, start, end, Slice(content, 0, 0), len(wrappers), True - ) + start, + end, + start, + end, + Slice(content, 0, 0), + len(wrappers), + True, + ), ) def set_block_type( @@ -509,13 +523,18 @@ def set_block_type( map_from = len(self.steps) def iteratee( - node: "Node", pos: int, parent: Optional["Node"], i: int + node: "Node", + pos: int, + parent: Optional["Node"], + i: int, ) -> bool | None: if ( node.is_textblock and not node.has_markup(type, attrs) and structure.can_change_type( - self.doc, self.mapping.slice(map_from).map(pos), type + self.doc, + self.mapping.slice(map_from).map(pos), + type, ) ): self.clear_incompatible(self.mapping.slice(map_from).map(pos, 1), type) @@ -529,11 +548,13 @@ def iteratee( start_m + 1, end_m - 1, Slice( - Fragment.from_(type.create(attrs, None, node.marks)), 0, 0 + Fragment.from_(type.create(attrs, None, node.marks)), + 0, + 0, ), 1, True, - ) + ), ) return False return None @@ -569,7 +590,7 @@ def set_node_markup( Slice(Fragment.from_(new_node), 0, 0), 1, True, - ) + ), ) def set_node_attribute(self, pos: int, attr: str, value: JSON) -> "Transform": @@ -619,12 +640,12 @@ def split( after = Fragment.from_( type_after.type.create(type_after.attrs, after) if type_after - else pos_.node(d).copy(after) + else pos_.node(d).copy(after), ) d -= 1 i -= 1 return self.step( - ReplaceStep(pos, pos, Slice(before.append(after), depth, depth), True) + ReplaceStep(pos, pos, Slice(before.append(after), depth, depth), True), ) def join(self, pos: int, depth: int = 1) -> "Transform": diff --git a/pyproject.toml b/pyproject.toml index e5c7bb3..b9c1338 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,21 @@ dev = [ ] [tool.ruff.lint] -select = ["B", "E", "EM", "F", "I", "I", "PT", "RSE", "RUF", "SIM", "UP", "W"] +select = [ + "B", + "COM", + "E", + "EM", + "F", + "I", + "I", + "PT", + "RSE", + "RUF", + "SIM", + "UP", + "W", +] preview = true [tool.ruff.format] diff --git a/tests/prosemirror_model/tests/test_content.py b/tests/prosemirror_model/tests/test_content.py index 97a313f..a7b5b09 100644 --- a/tests/prosemirror_model/tests/test_content.py +++ b/tests/prosemirror_model/tests/test_content.py @@ -287,7 +287,7 @@ def test_fill3_before(expr, before, mid, after, left, right): b = False if a: b = content.match_fragment( - before.content.append(a).append(mid.content) + before.content.append(a).append(mid.content), ).fill_before(after.content, True) if left: left = Node.from_json(schema, left) diff --git a/tests/prosemirror_model/tests/test_dom.py b/tests/prosemirror_model/tests/test_dom.py index ccd690d..c06b529 100644 --- a/tests/prosemirror_model/tests/test_dom.py +++ b/tests/prosemirror_model/tests/test_dom.py @@ -57,7 +57,7 @@ p( "a ", a({"href": "foo"}, "big ", a({"href": "bar"}, "nested"), " link"), - ) + ), ), '

a big nested' ' link

', @@ -65,7 +65,8 @@ ( "it can represent an unordered list", doc( - ul(li(p("one")), li(p("two")), li(p("three", strong("!")))), p("after") + ul(li(p("one")), li(p("two")), li(p("three", strong("!")))), + p("after"), ), "
  • one

  • two

  • three" "!

after

", @@ -73,7 +74,8 @@ ( "it can represent an ordered list", doc( - ol(li(p("one")), li(p("two")), li(p("three", strong("!")))), p("after") + ol(li(p("one")), li(p("two")), li(p("three", strong("!")))), + p("after"), ), "
  1. one

  2. two

  3. three" "!

after

", @@ -169,7 +171,10 @@ def test_html_is_escaped(): { "type": "doc", "content": [ - {"type": "paragraph", "content": [{"type": "text", "text": "test"}]} + { + "type": "paragraph", + "content": [{"type": "text", "text": "test"}], + }, ], }, ), @@ -189,7 +194,7 @@ def test_html_is_escaped(): "text": "some bolded text", }, ], - } + }, ], }, ), @@ -247,7 +252,7 @@ def test_html_is_escaped(): "href": "www.google.ca", "title": None, }, - } + }, ], "text": "google", }, @@ -264,7 +269,7 @@ def test_html_is_escaped(): "alt": None, "title": None, }, - } + }, ], }, { @@ -274,7 +279,7 @@ def test_html_is_escaped(): "type": "text", "marks": [{"type": "strong", "attrs": {}}], "text": "Hello", - } + }, ], }, { @@ -314,9 +319,9 @@ def test_html_is_escaped(): { "type": "paragraph", "content": [ - {"type": "text", "text": "Testing the result of this"} + {"type": "text", "text": "Testing the result of this"}, ], - } + }, ], }, ), diff --git a/tests/prosemirror_model/tests/test_mark.py b/tests/prosemirror_model/tests/test_mark.py index 3d7995e..760a928 100644 --- a/tests/prosemirror_model/tests/test_mark.py +++ b/tests/prosemirror_model/tests/test_mark.py @@ -116,7 +116,7 @@ def test_remove_form_set(ist): Mark.same_set( link("http://foo", "title").remove_from_set([link("http://foo")]), [link("http://foo")], - ) + ), ) diff --git a/tests/prosemirror_model/tests/test_node.py b/tests/prosemirror_model/tests/test_node.py index 32bc1ee..583a2f9 100644 --- a/tests/prosemirror_model/tests/test_node.py +++ b/tests/prosemirror_model/tests/test_node.py @@ -18,7 +18,8 @@ img = out["img"] custom_schema: Schema[ - Literal["doc", "paragraph", "text", "contact", "hard_break"], str + Literal["doc", "paragraph", "text", "contact", "hard_break"], + str, ] = Schema({ "nodes": { "doc": {"content": "paragraph+"}, @@ -78,7 +79,8 @@ def test_should_respect_custom_leafText_spec(self): "email": "bob@example.com", }) paragraph = custom_schema.nodes["paragraph"].create_checked( - {}, [custom_schema.text("Hello "), contact] + {}, + [custom_schema.text("Hello "), contact], ) assert contact.text_content, "Bob " @@ -100,8 +102,9 @@ def test_cuts_deeply(self): self.cut( doc( blockquote( - ul(li(p("a"), p("bc")), li(p("d")), "", li(p("e"))), p("3") - ) + ul(li(p("a"), p("bc")), li(p("d")), "", li(p("e"))), + p("3"), + ), ), doc(blockquote(ul(li(p("c")), li(p("d"))))), ) @@ -165,7 +168,7 @@ def test_iterates_over_inline_nodes(self): em("bar", img, strong("baz"), br), "quux", code("xyz"), - ) + ), ), "paragraph", "foo", @@ -204,7 +207,7 @@ def test_works_with_leafText(self): "email": "alice@example.com", }), ], - ) + ), ], ) assert d.text_between(0, d.content.size) == "Hello Alice " @@ -222,7 +225,7 @@ def test_should_ignore_leafText_spec_when_passing_a_custom_leaf_text(self): "email": "alice@example.com", }), ], - ) + ), ], ) assert ( @@ -282,5 +285,5 @@ def test_serialize_block_leaf_nodes(self): def test_serialize_nested_nodes(self): self.round_trip( - doc(blockquote(ul(li(p("a"), p("b")), li(p(img))), p("c")), p("d")) + doc(blockquote(ul(li(p("a"), p("b")), li(p(img))), p("c")), p("d")), ) diff --git a/tests/prosemirror_model/tests/test_resolve.py b/tests/prosemirror_model/tests/test_resolve.py index 13c1447..4d4caf4 100644 --- a/tests/prosemirror_model/tests/test_resolve.py +++ b/tests/prosemirror_model/tests/test_resolve.py @@ -31,7 +31,7 @@ [_doc, _blk, _p2, 4, "ef", None], [_doc, _blk, 6, _p2["node"], None], [_doc, 12, _blk["node"], None], - ]) + ]), ), ) def test_node_resolve(pos, exp): diff --git a/tests/prosemirror_model/tests/test_slice.py b/tests/prosemirror_model/tests/test_slice.py index 637ccbe..780093a 100644 --- a/tests/prosemirror_model/tests/test_slice.py +++ b/tests/prosemirror_model/tests/test_slice.py @@ -65,8 +65,10 @@ ( doc( blockquote( - p("foobar"), ul(li(p("a")), li(p("b"), "", p("c"))), p("d") - ) + p("foobar"), + ul(li(p("a")), li(p("b"), "", p("c"))), + p("d"), + ), ), blockquote(p("bar"), ul(li(p("a")), li(p("b")))), 1, diff --git a/tests/prosemirror_transform/tests/test_step.py b/tests/prosemirror_transform/tests/test_step.py index 0b342e6..f5b7b99 100644 --- a/tests/prosemirror_transform/tests/test_step.py +++ b/tests/prosemirror_transform/tests/test_step.py @@ -10,7 +10,7 @@ def inner(): merged = step1.merge(step2) assert merged assert merged.apply(_test_doc).doc.eq( - step2.apply(step1.apply(_test_doc).doc).doc + step2.apply(step1.apply(_test_doc).doc).doc, ) return inner diff --git a/tests/prosemirror_transform/tests/test_structure.py b/tests/prosemirror_transform/tests/test_structure.py index 245af98..5fb55bd 100644 --- a/tests/prosemirror_transform/tests/test_structure.py +++ b/tests/prosemirror_transform/tests/test_structure.py @@ -42,7 +42,9 @@ def t(str, em=None): n("head", t("Subsection head")), # 46 n("para", t("Subtext")), # 55 n( - "figure", n("caption", t("Figure caption")), n("figureimage") + "figure", + n("caption", t("Figure caption")), + n("figureimage"), ), # 56 # 72 # 74 n("quote", n("para", t("!"))), ), @@ -124,7 +126,7 @@ def test_doesnt_return_true_when_split_content_doesnt_fit_in_given_node_type( "title": {"content": "text*"}, "chapter": {"content": "title scene+"}, "scene": {"content": "para+"}, - } + }, }) assert not can_split( s.node( diff --git a/tests/prosemirror_transform/tests/test_trans.py b/tests/prosemirror_transform/tests/test_trans.py index d5f1137..56afae1 100644 --- a/tests/prosemirror_transform/tests/test_trans.py +++ b/tests/prosemirror_transform/tests/test_trans.py @@ -51,7 +51,9 @@ ), ( doc( - p("before"), blockquote(p("the variable is called i")), p("after") + p("before"), + blockquote(p("the variable is called i")), + p("after"), ), schema.mark("code"), doc( @@ -83,8 +85,10 @@ def test_does_not_remove_non_excluded_marks_of_the_same_type(): }) tr = Transform( schema.node( - "doc", None, schema.text("hi", [schema.mark("comment", {"id": 10})]) - ) + "doc", + None, + schema.text("hi", [schema.mark("comment", {"id": 10})]), + ), ) tr.add_mark(0, 2, schema.mark("comment", {"id": 20})) assert len(tr.doc.first_child.marks) == 2 @@ -100,7 +104,7 @@ def test_can_remote_multiple_excluded_marks(): "doc", None, schema.text("hi", [schema.mark("small1"), schema.mark("small2")]), - ) + ), ) assert len(tr.doc.first_child.marks) == 2 tr.add_mark(0, 2, schema.mark("big")) @@ -180,7 +184,7 @@ def test_remove_more_than_one_mark_of_same_type_from_block(): "hi", [schema.mark("comment", {"id": 1}), schema.mark("comment", {"id": 2})], ), - ) + ), ) assert len(tr.doc.first_child.marks) == 2 tr.remove_mark(0, 2, schema.marks["comment"]) @@ -255,7 +259,10 @@ def test_delete(doc, expect, test_transform): [ ( doc( - blockquote(p("a")), "", blockquote(p("b")), p("after") + blockquote(p("a")), + "", + blockquote(p("b")), + p("after"), ), doc(blockquote(p("a"), "", p("b")), p("after")), ), @@ -266,12 +273,12 @@ def test_delete(doc, expect, test_transform): blockquote(p("a"), p("b")), "", blockquote(p("c"), p("d")), - ) + ), ), doc( blockquote( - blockquote(p("a"), p("b"), "", p("c"), p("d")) - ) + blockquote(p("a"), p("b"), "", p("c"), p("d")), + ), ), ), ( @@ -301,7 +308,8 @@ def test_join(doc, expect, test_transform): ( doc(blockquote(blockquote(p("foobar"))), p("after<1>")), doc( - blockquote(blockquote(p("foo")), blockquote(p("bar"))), p("after<1>") + blockquote(blockquote(p("foo")), blockquote(p("bar"))), + p("after<1>"), ), [2], ), @@ -350,7 +358,9 @@ def test_split(doc, expect, args, test_transform): ( doc(blockquote(p("one"), p("two"), p("three"))), doc( - blockquote(p("one")), p("two"), blockquote(p("three")) + blockquote(p("one")), + p("two"), + blockquote(p("three")), ), ), ( @@ -379,8 +389,8 @@ def test_split(doc, expect, args, test_transform): p("<3>three"), p("four"), p("<5>five"), - ) - ) + ), + ), ), doc( blockquote( @@ -389,7 +399,7 @@ def test_split(doc, expect, args, test_transform): p("<3>three"), p("four"), blockquote(p("<5>five")), - ) + ), ), ), ( @@ -404,7 +414,7 @@ def test_split(doc, expect, args, test_transform): ) def test_lift(doc, expect, test_transform): range = doc.resolve(doc.tag.get("a")).block_range( - doc.resolve(doc.tag.get("b") or doc.tag.get("a")) + doc.resolve(doc.tag.get("b") or doc.tag.get("a")), ) tr = Transform(doc).lift(range, lift_target(range)) test_transform(tr, expect) @@ -437,14 +447,14 @@ def test_lift(doc, expect, test_transform): li(p("<1>one")), li(p("..."), p("two"), p("three")), li(p("<4>four")), - ) + ), ), doc( ol( li(p("<1>one")), li(p("..."), ol(li(p("two"), p("three")))), li(p("<4>four")), - ) + ), ), "ordered_list", None, @@ -459,7 +469,7 @@ def test_lift(doc, expect, test_transform): ) def test_wrap(doc, expect, type, attrs, test_transform): range = doc.resolve(doc.tag.get("a")).block_range( - doc.resolve(doc.tag.get("b") or doc.tag.get("a")) + doc.resolve(doc.tag.get("b") or doc.tag.get("a")), ) tr = Transform(doc).wrap(range, find_wrapping(range, schema.nodes[type], attrs)) test_transform(tr, expect) @@ -611,8 +621,14 @@ def test_set_doc_attribute(doc, expect, attr, value, test_transform): ( doc( blockquote( - ul(li(p("a")), li(p("b")), li(p("c")), li(p("d")), li(p("e"))) - ) + ul( + li(p("a")), + li(p("b")), + li(p("c")), + li(p("d")), + li(p("e")), + ), + ), ), None, doc(blockquote(ul(li(p("a")), li(p("bd")), li(p("e"))))), @@ -676,8 +692,8 @@ def test_set_doc_attribute(doc, expect, attr, value, test_transform): ( doc( blockquote( - blockquote(p("one"), p("two"), p("three<3>"), p("four<4>")) - ) + blockquote(p("one"), p("two"), p("three<3>"), p("four<4>")), + ), ), doc(ol(li(p("helloworld")), li(p("bye"))), p("next")), doc( @@ -688,8 +704,8 @@ def test_set_doc_attribute(doc, expect, attr, value, test_transform): ol(li(p("bye"))), p("nehree<3>"), p("four<4>"), - ) - ) + ), + ), ), ), ( @@ -756,7 +772,9 @@ def test_replace(doc, source, expect, test_transform): else: slice = source.slice(source.tag.get("a"), source.tag.get("b")) tr = Transform(doc).replace( - doc.tag.get("a"), doc.tag.get("b") or doc.tag.get("a"), slice + doc.tag.get("a"), + doc.tag.get("b") or doc.tag.get("a"), + slice, ) test_transform(tr, expect) @@ -841,7 +859,7 @@ def test_replacing_in_nodes_with_fixed_content(): "b": {"content": "inline*"}, "block": {"content": "a b"}, "text": {"group": "inline"}, - } + }, }) doc = s.node( @@ -879,7 +897,7 @@ def test_preserves_mark_on_block_nodes(self): ms.node("paragraph", None, [ms.text("hey")], [ms.mark("em")]), ms.node("paragraph", None, [ms.text("ok")], [ms.mark("strong")]), ], - ) + ), ) tr.replace(2, 7, tr.doc.slice(2, 7)) assert tr.doc.eq(tr.before) @@ -887,7 +905,7 @@ def test_preserves_mark_on_block_nodes(self): def test_preserves_marks_on_open_slice_block_nodes(self): ms = self.ms tr = Transform( - ms.node("doc", None, [ms.node("paragraph", None, [ms.text("a")])]) + ms.node("doc", None, [ms.node("paragraph", None, [ms.text("a")])]), ) tr.replace( 3, @@ -978,7 +996,7 @@ def test_keeps_isolating_nodes_together(): }) doc = s.node("doc", None, [s.node("paragraph", None, [s.text("one")])]) iso = Fragment.from_( - s.node("iso", None, [s.node("paragraph", None, [s.text("two")])]) + s.node("iso", None, [s.node("paragraph", None, [s.text("two")])]), ) assert ( Transform(doc) @@ -992,7 +1010,7 @@ def test_keeps_isolating_nodes_together(): s.node("iso", None, [s.node("paragraph", None, [s.text("two")])]), s.node("paragraph", None, [s.text("e")]), ], - ) + ), ) ) assert ( @@ -1045,7 +1063,9 @@ def test_replace_range(doc, source, expect, test_transform): else: slice = source.slice(source.tag.get("a"), source.tag.get("b"), True) tr = Transform(doc).replace_range( - doc.tag.get("a"), doc.tag.get("b") or doc.tag.get("a"), slice + doc.tag.get("a"), + doc.tag.get("b") or doc.tag.get("a"), + slice, ) test_transform(tr, expect) @@ -1069,7 +1089,9 @@ def test_replace_range(doc, source, expect, test_transform): ) def test_replace_range_with(doc, node, expect, test_transform): tr = Transform(doc).replace_range_with( - doc.tag.get("a"), doc.tag.get("b") or doc.tag.get("a"), node + doc.tag.get("a"), + doc.tag.get("b") or doc.tag.get("a"), + node, ) test_transform(tr, expect) @@ -1101,7 +1123,8 @@ def test_replace_range_with(doc, node, expect, test_transform): ) def test_delete_range(doc, expect, test_transform): tr = Transform(doc).delete_range( - doc.tag.get("a"), doc.tag.get("b") or doc.tag.get("a") + doc.tag.get("a"), + doc.tag.get("b") or doc.tag.get("a"), ) test_transform(tr, expect) From 4b586c7a6e42c9cb82070b5d4142c77fc4de0f63 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sun, 21 Apr 2024 11:06:08 -0400 Subject: [PATCH 12/13] Enable N ruff rule --- prosemirror/model/fragment.py | 10 +++++----- pyproject.toml | 1 + tests/prosemirror_model/tests/test_node.py | 6 +++--- tests/prosemirror_transform/tests/test_trans.py | 10 +++++----- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/prosemirror/model/fragment.py b/prosemirror/model/fragment.py index 4e14c67..3abf586 100644 --- a/prosemirror/model/fragment.py +++ b/prosemirror/model/fragment.py @@ -17,7 +17,7 @@ from .node import Node, TextNode -def retIndex(index: int, offset: int) -> dict[str, int]: +def ret_index(index: int, offset: int) -> dict[str, int]: return {"index": index, "offset": offset} @@ -230,9 +230,9 @@ def find_diff_end( def find_index(self, pos: int, round: int = -1) -> dict[str, int]: if pos == 0: - return retIndex(0, pos) + return ret_index(0, pos) if pos == self.size: - return retIndex(len(self.content), pos) + return ret_index(len(self.content), pos) if pos > self.size or pos < 0: msg = f"Position {pos} outside of fragment ({self})" raise ValueError(msg) @@ -243,8 +243,8 @@ def find_index(self, pos: int, round: int = -1) -> dict[str, int]: end = cur_pos + cur.node_size if end >= pos: if end == pos or round > 0: - return retIndex(i + 1, end) - return retIndex(i, cur_pos) + return ret_index(i + 1, end) + return ret_index(i, cur_pos) i += 1 cur_pos = end diff --git a/pyproject.toml b/pyproject.toml index b9c1338..e5d9db9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ select = [ "F", "I", "I", + "N", "PT", "RSE", "RUF", diff --git a/tests/prosemirror_model/tests/test_node.py b/tests/prosemirror_model/tests/test_node.py index 583a2f9..b0cc8b4 100644 --- a/tests/prosemirror_model/tests/test_node.py +++ b/tests/prosemirror_model/tests/test_node.py @@ -73,7 +73,7 @@ def test_respected_by_fragment(self): ) assert str(f) == "" - def test_should_respect_custom_leafText_spec(self): + def test_should_respect_custom_leaf_text_spec(self): contact = custom_schema.nodes["contact"].create_checked({ "name": "Bob", "email": "bob@example.com", @@ -194,7 +194,7 @@ def leaf_text(node): text = d.text_between(0, d.content.size, "", leaf_text) assert text == "foo" - def test_works_with_leafText(self): + def test_works_with_leaf_text(self): d = custom_schema.nodes["doc"].create_checked( {}, [ @@ -212,7 +212,7 @@ def test_works_with_leafText(self): ) assert d.text_between(0, d.content.size) == "Hello Alice " - def test_should_ignore_leafText_spec_when_passing_a_custom_leaf_text(self): + def test_should_ignore_leaf_text_spec_when_passing_a_custom_leaf_text(self): d = custom_schema.nodes["doc"].create_checked( {}, [ diff --git a/tests/prosemirror_transform/tests/test_trans.py b/tests/prosemirror_transform/tests/test_trans.py index 56afae1..e8cc816 100644 --- a/tests/prosemirror_transform/tests/test_trans.py +++ b/tests/prosemirror_transform/tests/test_trans.py @@ -7,8 +7,8 @@ from prosemirror.transform.structure import NodeTypeWithAttrs doc = out["doc"] -docMetaOne = out["docMetaOne"] -docMetaTwo = out["docMetaTwo"] +doc_meta_one = out["docMetaOne"] +doc_meta_two = out["docMetaTwo"] blockquote = out["blockquote"] pre = out["pre"] h1 = out["h1"] @@ -573,9 +573,9 @@ def test_set_node_attribute(doc, expect, attr, value, test_transform): @pytest.mark.parametrize( ("doc", "expect", "attr", "value"), [ - (doc(h1("foo")), docMetaOne(h1("foo")), "meta", 1), - (docMetaOne(h1("foo")), docMetaTwo(h1("foo")), "meta", 2), - (docMetaTwo(h1("foo")), doc(h1("foo")), "meta", None), + (doc(h1("foo")), doc_meta_one(h1("foo")), "meta", 1), + (doc_meta_one(h1("foo")), doc_meta_two(h1("foo")), "meta", 2), + (doc_meta_two(h1("foo")), doc(h1("foo")), "meta", None), ], ) def test_set_doc_attribute(doc, expect, attr, value, test_transform): From 5dbecf4b6c4bafaa86122740a85498f1b78cbe55 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Sun, 21 Apr 2024 21:33:35 -0400 Subject: [PATCH 13/13] Add ANN ruff rule --- prosemirror/model/fragment.py | 4 ++-- prosemirror/test_builder/build.py | 16 ++++++---------- prosemirror/transform/doc_attr_step.py | 2 +- pyproject.toml | 6 ++++++ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/prosemirror/model/fragment.py b/prosemirror/model/fragment.py index 3abf586..ffe49a1 100644 --- a/prosemirror/model/fragment.py +++ b/prosemirror/model/fragment.py @@ -8,7 +8,7 @@ cast, ) -from prosemirror.utils import JSONList, text_length +from prosemirror.utils import JSON, JSONList, text_length if TYPE_CHECKING: from prosemirror.model.schema import Schema @@ -254,7 +254,7 @@ def to_json(self) -> JSONList | None: return None @classmethod - def from_json(cls, schema: "Schema[Any, Any]", value: Any) -> "Fragment": + def from_json(cls, schema: "Schema[Any, Any]", value: JSON) -> "Fragment": if not value: return cls.empty diff --git a/prosemirror/test_builder/build.py b/prosemirror/test_builder/build.py index 7829cc6..269ab81 100644 --- a/prosemirror/test_builder/build.py +++ b/prosemirror/test_builder/build.py @@ -5,8 +5,8 @@ from collections.abc import Callable from typing import Any -from prosemirror.model import Node, Schema -from prosemirror.utils import JSONDict +from prosemirror.model import Node, NodeType, Schema +from prosemirror.utils import Attrs, JSONDict NO_TAG = Node.tag = {} @@ -60,11 +60,7 @@ def flatten( return result, tag -def id(x): - return x - - -def block(type, attrs): +def block(type: NodeType, attrs: Attrs | None = None): def result(*args): my_attrs = attrs if ( @@ -76,7 +72,7 @@ def result(*args): ): my_attrs.update(args[0]) args = args[1:] - nodes, tag = flatten(type.schema, args, id) + nodes, tag = flatten(type.schema, args, lambda x: x) node = type.create(my_attrs, nodes) if tag != NO_TAG: node.tag = tag @@ -89,7 +85,7 @@ def result(*args): return result -def mark(type, attrs): +def mark(type: NodeType, attrs: Attrs): def result(*args): my_attrs = attrs.copy() if ( @@ -114,7 +110,7 @@ def f(n): return result -def builders(schema, names): +def builders(schema: Schema[Any, Any], names): result = {"schema": schema} for name in schema.nodes: result[name] = block(schema.nodes[name], {}) diff --git a/prosemirror/transform/doc_attr_step.py b/prosemirror/transform/doc_attr_step.py index 2a33d19..dc9ae58 100644 --- a/prosemirror/transform/doc_attr_step.py +++ b/prosemirror/transform/doc_attr_step.py @@ -7,7 +7,7 @@ class DocAttrStep(Step): - def __init__(self, attr: str, value: JSON): + def __init__(self, attr: str, value: JSON) -> None: super().__init__() self.attr = attr self.value = value diff --git a/pyproject.toml b/pyproject.toml index e5d9db9..544a631 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ dev = [ [tool.ruff.lint] select = [ + "ANN", "B", "COM", "E", @@ -44,8 +45,13 @@ select = [ "UP", "W", ] +ignore = ["COM812"] preview = true +[tool.ruff.lint.per-file-ignores] +"prosemirror/test_builder/**" = ["ANN"] +"tests/**" = ["ANN"] + [tool.ruff.format] preview = true