diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 04230b02..f17466ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,14 +24,14 @@ repos: - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.12 + rev: v0.16.5 hooks: - id: ruff args: [--fix] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.20.2 + rev: v2.3.1 hooks: - id: mypy additional_dependencies: [mdurl, typing-extensions] diff --git a/AGENTS.md b/AGENTS.md index 7e49651b..5aadebec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -158,11 +158,9 @@ from __future__ import annotations from typing import Sequence + def parse_blocks( - state: StateBlock, - start_line: int, - end_line: int, - silent: bool = False + state: StateBlock, start_line: int, end_line: int, silent: bool = False ) -> bool: """Parse block-level content. @@ -293,18 +291,20 @@ HTML Output import pytest from markdown_it import MarkdownIt + def test_basic_parsing(): md = MarkdownIt() result = md.render("# Heading\n\nParagraph") assert "
Paragraph
" in result + @pytest.mark.parametrize( "input_text,expected", [ ("**bold**", "bold"), ("*italic*", "italic"), - ] + ], ) def test_emphasis(input_text, expected): md = MarkdownIt() @@ -389,7 +389,7 @@ for token in tokens: print(md.get_all_rules()) # Enable/disable specific rules -md.disable(['emphasis']) +md.disable(["emphasis"]) result = md.render("*text*") # Won't be emphasized ``` @@ -401,13 +401,13 @@ result = md.render("*text*") # Won't be emphasized 2. Create rule function in appropriate `rules_*/` directory 3. Rule signature for block rules: ```python - def rule_name(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: - ... + def rule_name( + state: StateBlock, startLine: int, endLine: int, silent: bool + ) -> bool: ... ``` 4. Rule signature for inline rules: ```python - def rule_name(state: StateInline, silent: bool) -> bool: - ... + def rule_name(state: StateInline, silent: bool) -> bool: ... ``` 5. Register the rule in the appropriate parser's `__init__` method 6. Add tests for the new rule @@ -434,11 +434,13 @@ result = md.render("*text*") # Won't be emphasized ```python from markdown_it import MarkdownIt + def render_custom_link(self, tokens, idx, options, env): tokens[idx].attrSet("target", "_blank") tokens[idx].attrSet("rel", "noopener noreferrer") return self.renderToken(tokens, idx, options, env) + md = MarkdownIt() md.add_render_rule("link_open", render_custom_link) ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d50cb3e..dfaa08d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -263,6 +263,7 @@ It can then be activated by: ```python from markdown_it import MarkdownIt + md = MarkdownIt().enable("linkify") md.options["linkify"] = True ``` @@ -280,6 +281,7 @@ It can be activated by: ```python from markdown_it import MarkdownIt + md = MarkdownIt().enable("smartquotes") md.options["typographer"] = True ``` @@ -300,6 +302,7 @@ This plugin can be activated by: ```python from markdown_it import MarkdownIt from markdown_it.extensions.tasklists import tasklists_plugin + md = MarkdownIt().use(tasklists_plugin) ``` diff --git a/README.md b/README.md index 82d218b3..ce27254f 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,12 @@ from mdit_py_plugins.front_matter import front_matter_plugin from mdit_py_plugins.footnote import footnote_plugin md = ( - MarkdownIt('commonmark', {'breaks':True,'html':True}) + MarkdownIt("commonmark", {"breaks": True, "html": True}) .use(front_matter_plugin) .use(footnote_plugin) - .enable('table') + .enable("table") ) -text = (""" +text = """ --- a: 1 --- @@ -86,7 +86,7 @@ a | b A footnote [^1] [^1]: some details -""") +""" tokens = md.parse(text) html_text = md.render(text) diff --git a/docs/architecture.md b/docs/architecture.md index 3100af7f..2ed00fac 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -105,7 +105,7 @@ with the same signature: ```python def function(renderer, tokens, idx, options, env): - return htmlResult + return htmlResult ``` In many cases that allows easy output change even without parser intrusion. @@ -113,23 +113,28 @@ For example, let's replace images with vimeo links to player's iframe: ```python import re + md = MarkdownIt("commonmark") -vimeoRE = re.compile(r'^https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)') +vimeoRE = re.compile(r"^https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)") + def render_vimeo(self, tokens, idx, options, env): token = tokens[idx] if vimeoRE.match(token.attrs["src"]): - ident = vimeoRE.match(token.attrs["src"])[2] - return ('\n') + return ( + '\n" + ) return self.image(tokens, idx, options, env) + md = MarkdownIt("commonmark") md.add_render_rule("image", render_vimeo) print(md.render("")) @@ -140,12 +145,14 @@ Here is another example, how to add `target="_blank"` to all links: ```python from markdown_it import MarkdownIt + def render_blank_link(self, tokens, idx, options, env): tokens[idx].attrSet("target", "_blank") # pass token to default renderer. return self.renderToken(tokens, idx, options, env) + md = MarkdownIt("commonmark") md.add_render_rule("link_open", render_blank_link) print(md.render("[a]\n\n[a]: b")) diff --git a/docs/plugins.md b/docs/plugins.md index 98d9600c..bfe66155 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -13,14 +13,16 @@ These can be enabled individually: ```python from markdown_it import MarkdownIt -md = MarkdownIt("commonmark").enable('table') + +md = MarkdownIt("commonmark").enable("table") ``` or as part of a configuration: ```python from markdown_it import MarkdownIt -md = MarkdownIt("gfm-like") # tables, strikethrough, linkify + +md = MarkdownIt("gfm-like") # tables, strikethrough, linkify md = MarkdownIt("gfm-like2") # + task lists, alerts, single-tilde strikethrough ``` @@ -48,6 +50,7 @@ They can be chained and loaded *via*: ```python from markdown_it import MarkdownIt from mdit_py_plugins import plugin1, plugin2 + md = MarkdownIt().use(plugin1, keyword=value).use(plugin2, keyword=value) html_string = md.render("some *Markdown*") ``` diff --git a/docs/using.md b/docs/using.md index 507f49c1..f1c66f56 100644 --- a/docs/using.md +++ b/docs/using.md @@ -298,7 +298,7 @@ with the same signature: ```python def function(renderer, tokens, idx, options, env): - return htmlResult + return htmlResult ``` +++ diff --git a/markdown_it/common/utils.py b/markdown_it/common/utils.py index 11bda644..500a590d 100644 --- a/markdown_it/common/utils.py +++ b/markdown_it/common/utils.py @@ -102,7 +102,7 @@ def replaceEntityPattern(match: str, name: str) -> str: if name in entities: return entities[name] - code: None | int = None + code: int | None = None if pat := DIGITAL_ENTITY_BASE10_RE.fullmatch(name): code = int(pat.group(1), 10) elif pat := DIGITAL_ENTITY_BASE16_RE.fullmatch(name): diff --git a/markdown_it/rules_block/fence.py b/markdown_it/rules_block/fence.py index 0d7e651e..621924b5 100644 --- a/markdown_it/rules_block/fence.py +++ b/markdown_it/rules_block/fence.py @@ -37,10 +37,10 @@ def make_fence_rule( closing_matcher: Callable[[int, int], bool] if exact_match: # closing code fence must have exactly the same number of markers as the opening one - closing_matcher = lambda opening_len, closing_len: closing_len == opening_len # noqa: E731 + closing_matcher = lambda opening_len, closing_len: closing_len == opening_len else: # closing code fence must be at least as long as the opening one - closing_matcher = lambda opening_len, closing_len: closing_len >= opening_len # noqa: E731 + closing_matcher = lambda opening_len, closing_len: closing_len >= opening_len def _fence_rule( state: StateBlock, startLine: int, endLine: int, silent: bool diff --git a/markdown_it/rules_core/smartquotes.py b/markdown_it/rules_core/smartquotes.py index f9b8b457..3392dcb5 100644 --- a/markdown_it/rules_core/smartquotes.py +++ b/markdown_it/rules_core/smartquotes.py @@ -58,7 +58,7 @@ def process_inlines(tokens: list[Token], state: StateCore) -> None: # Find previous character, # default to space if it's the beginning of the line - lastChar: None | int = 0x20 + lastChar: int | None = 0x20 if t.start(0) + lastIndex - 1 >= 0: lastChar = charCodeAt(text, t.start(0) + lastIndex - 1) @@ -75,7 +75,7 @@ def process_inlines(tokens: list[Token], state: StateCore) -> None: # Find next character, # default to space if it's the end of the line - nextChar: None | int = 0x20 + nextChar: int | None = 0x20 if pos < maximum: nextChar = charCodeAt(text, pos) diff --git a/markdown_it/token.py b/markdown_it/token.py index d6d0b453..309bb96f 100644 --- a/markdown_it/token.py +++ b/markdown_it/token.py @@ -95,11 +95,11 @@ def attrPush(self, attrData: tuple[str, str | int | float]) -> None: name, value = attrData self.attrSet(name, value) - def attrSet(self, name: str, value: str | int | float) -> None: + def attrSet(self, name: str, value: str | float) -> None: """Set `name` attribute to `value`. Override old value if exists.""" self.attrs[name] = value - def attrGet(self, name: str) -> None | str | int | float: + def attrGet(self, name: str) -> str | int | float | None: """Get the value of attribute `name`, or null if it does not exist.""" return self.attrs.get(name, None) diff --git a/markdown_it/tree.py b/markdown_it/tree.py index 24bc2466..b78fd5d5 100644 --- a/markdown_it/tree.py +++ b/markdown_it/tree.py @@ -9,6 +9,8 @@ import textwrap from typing import Any, NamedTuple, TypeVar, overload +from typing_extensions import Self + from .token import Token @@ -84,13 +86,13 @@ def __getitem__(self: _NodeType, item: int) -> _NodeType: ... @overload def __getitem__(self: _NodeType, item: slice) -> list[_NodeType]: ... - def __getitem__(self: _NodeType, item: int | slice) -> _NodeType | list[_NodeType]: + def __getitem__(self, item: int | slice) -> Self | list[Self]: return self.children[item] - def to_tokens(self: _NodeType) -> list[Token]: + def to_tokens(self) -> list[Token]: """Recover the linear token stream.""" - def recursive_collect_tokens(node: _NodeType, token_list: list[Token]) -> None: + def recursive_collect_tokens(node: Self, token_list: list[Token]) -> None: if node.type == "root": for child in node.children: recursive_collect_tokens(child, token_list) @@ -108,19 +110,19 @@ def recursive_collect_tokens(node: _NodeType, token_list: list[Token]) -> None: return tokens @property - def children(self: _NodeType) -> list[_NodeType]: + def children(self) -> list[Self]: return self._children @children.setter - def children(self: _NodeType, value: list[_NodeType]) -> None: + def children(self, value: list[Self]) -> None: self._children = value @property - def parent(self: _NodeType) -> _NodeType | None: + def parent(self) -> Self | None: return self._parent # type: ignore @parent.setter - def parent(self: _NodeType, value: _NodeType | None) -> None: + def parent(self, value: Self | None) -> None: self._parent = value @property @@ -139,7 +141,7 @@ def is_nested(self) -> bool: return bool(self.nester_tokens) @property - def siblings(self: _NodeType) -> Sequence[_NodeType]: + def siblings(self) -> Sequence[Self]: """Get siblings of the node. Gets the whole group of siblings, including self. @@ -165,7 +167,7 @@ def type(self) -> str: return self.nester_tokens.opening.type.removesuffix("_open") @property - def next_sibling(self: _NodeType) -> _NodeType | None: + def next_sibling(self) -> Self | None: """Get the next node in the sequence of siblings. Returns `None` if this is the last sibling. @@ -176,7 +178,7 @@ def next_sibling(self: _NodeType) -> _NodeType | None: return None @property - def previous_sibling(self: _NodeType) -> _NodeType | None: + def previous_sibling(self) -> Self | None: """Get the previous node in the sequence of siblings. Returns `None` if this is the first sibling. @@ -241,9 +243,7 @@ def pretty( ) return text - def walk( - self: _NodeType, *, include_self: bool = True - ) -> Generator[_NodeType, None, None]: + def walk(self, *, include_self: bool = True) -> Generator[Self, None, None]: """Recursively yield all descendant nodes in the tree starting at self. The order mimics the order of the underlying linear token @@ -282,7 +282,7 @@ def attrs(self) -> dict[str, str | int | float]: """Html attributes.""" return self._attribute_token().attrs - def attrGet(self, name: str) -> None | str | int | float: + def attrGet(self, name: str) -> str | int | float | None: """Get the value of attribute `name`, or null if it does not exist.""" return self._attribute_token().attrGet(name)