Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
22 changes: 12 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 "<h1>Heading</h1>" in result
assert "<p>Paragraph</p>" in result


@pytest.mark.parametrize(
"input_text,expected",
[
("**bold**", "<strong>bold</strong>"),
("*italic*", "<em>italic</em>"),
]
],
)
def test_emphasis(input_text, expected):
md = MarkdownIt()
Expand Down Expand Up @@ -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
```

Expand All @@ -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
Expand All @@ -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)
```
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ It can then be activated by:

```python
from markdown_it import MarkdownIt

md = MarkdownIt().enable("linkify")
md.options["linkify"] = True
```
Expand All @@ -280,6 +281,7 @@ It can be activated by:

```python
from markdown_it import MarkdownIt

md = MarkdownIt().enable("smartquotes")
md.options["typographer"] = True
```
Expand All @@ -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)
```

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand All @@ -86,7 +86,7 @@ a | b
A footnote [^1]

[^1]: some details
""")
"""
tokens = md.parse(text)
html_text = md.render(text)

Expand Down
21 changes: 14 additions & 7 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,31 +105,36 @@ 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.
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 ('<div class="embed-responsive embed-responsive-16by9">\n' +
' <iframe class="embed-responsive-item" src="//player.vimeo.com/video/' +
ident + '"></iframe>\n' +
'</div>\n')
return (
'<div class="embed-responsive embed-responsive-16by9">\n'
+ ' <iframe class="embed-responsive-item" src="//player.vimeo.com/video/'
+ ident
+ '"></iframe>\n'
+ "</div>\n"
)
return self.image(tokens, idx, options, env)


md = MarkdownIt("commonmark")
md.add_render_rule("image", render_vimeo)
print(md.render("![](https://www.vimeo.com/123)"))
Expand All @@ -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"))
Expand Down
7 changes: 5 additions & 2 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down Expand Up @@ -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*")
```
2 changes: 1 addition & 1 deletion docs/using.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ with the same signature:

```python
def function(renderer, tokens, idx, options, env):
return htmlResult
return htmlResult
```

+++
Expand Down
2 changes: 1 addition & 1 deletion markdown_it/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions markdown_it/rules_block/fence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions markdown_it/rules_core/smartquotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions markdown_it/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
28 changes: 14 additions & 14 deletions markdown_it/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import textwrap
from typing import Any, NamedTuple, TypeVar, overload

from typing_extensions import Self

from .token import Token


Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
Loading