Skip to content

Commit c7c6955

Browse files
authored
Merge branch 'master' into dependabot/github_actions/actions/upload-artifact-7
2 parents 9e322e9 + 4bc9d49 commit c7c6955

35 files changed

Lines changed: 1577 additions & 176 deletions

.github/workflows/tests.yml

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ jobs:
5252
run: |
5353
pytest tests/ --cov=markdown_it --cov-report=xml --cov-report=term-missing
5454
- name: Upload to Codecov
55-
if: matrix.python-version == '3.10' && github.repository == 'executablebooks/markdown-it-py'
55+
# note currently not working for external contributions
56+
if: false && matrix.python-version == '3.10' && github.repository == 'executablebooks/markdown-it-py'
5657
uses: codecov/codecov-action@v5
5758
with:
5859
name: markdown-it-py-pytests
@@ -112,26 +113,23 @@ jobs:
112113

113114
publish:
114115

115-
name: Publish to PyPi
116+
name: Publish to PyPI
116117
needs: [pre-commit, tests]
117118
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
118119
runs-on: ubuntu-latest
120+
permissions:
121+
id-token: write
122+
environment:
123+
name: pypi
124+
url: https://pypi.org/p/markdown-it-py
119125
steps:
120-
- name: Checkout source
121-
uses: actions/checkout@v4
122-
- name: Set up Python
123-
uses: actions/setup-python@v5
126+
- uses: actions/checkout@v4
127+
- uses: actions/setup-python@v5
124128
with:
125129
python-version: '3.10'
126-
- name: install flit
127-
run: |
128-
pip install flit~=3.4
129-
- name: Build and publish
130-
run: |
131-
flit publish
132-
env:
133-
FLIT_USERNAME: __token__
134-
FLIT_PASSWORD: ${{ secrets.PYPI_KEY }}
130+
- run: pip install flit~=3.4
131+
- run: flit build
132+
- uses: pypa/gh-action-pypi-publish@release/v1
135133

136134
allgood:
137135
runs-on: ubuntu-latest

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,22 @@ exclude: >
1616
repos:
1717

1818
- repo: https://github.com/pre-commit/pre-commit-hooks
19-
rev: v5.0.0
19+
rev: v6.0.0
2020
hooks:
2121
- id: check-json
2222
- id: check-yaml
2323
- id: end-of-file-fixer
2424
- id: trailing-whitespace
2525

2626
- repo: https://github.com/astral-sh/ruff-pre-commit
27-
rev: v0.12.8
27+
rev: v0.15.12
2828
hooks:
2929
- id: ruff
3030
args: [--fix]
3131
- id: ruff-format
3232

3333
- repo: https://github.com/pre-commit/mirrors-mypy
34-
rev: v1.17.1
34+
rev: v1.20.2
3535
hooks:
3636
- id: mypy
3737
additional_dependencies: [mdurl, typing-extensions]

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Change Log
22

3+
## 4.2.0 - 2026-05-07
4+
5+
* ✨ Add `make_fence_rule()` factory for configurable fence markers in [#394](https://github.com/executablebooks/markdown-it-py/pull/394)
6+
7+
## 4.1.0 - 2025-05-06
8+
9+
* ✨ Add `gfm-like2` preset with task lists, alerts, and single-tilde strikethrough core plugins in [#388](https://github.com/executablebooks/markdown-it-py/pull/388)
10+
* ✨ Allow plugins to register inline terminator characters in [#391](https://github.com/executablebooks/markdown-it-py/pull/391)
11+
* 👌 Fix quadratic complexity in `fragments_join` / `text_join` in [#389](https://github.com/executablebooks/markdown-it-py/pull/389), thanks to [@petricevich](https://github.com/petricevich)
12+
* 👌 Add `--stdin` option to CLI for reading Markdown from standard input in [#379](https://github.com/executablebooks/markdown-it-py/pull/379), thanks to [@mcepl](https://github.com/mcepl)
13+
* 🔧 Add typing to Scanner in [#382](https://github.com/executablebooks/markdown-it-py/pull/382), thanks to [@Alunderin](https://github.com/Alunderin)
14+
15+
**Full Changelog**: <https://github.com/executablebooks/markdown-it-py/compare/v4.0.0...v4.1.0>
16+
317
## 4.0.0 - 2024-08-10
418

519
This primarily drops support for Python 3.9, adds support for Python 3.13,

docs/contributing.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,12 @@ __Note:__ Don't try to replace text with HTML markup! That's not secure.
118118

119119
### Why is my inline rule not executed?
120120

121-
The inline parser skips pieces of texts to optimize speed. It stops only on [a small set of chars](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_inline/text.mjs), which can be tokens. We did not made this list extensible for performance reasons too.
121+
The inline parser skips pieces of texts to optimize speed. It stops only on [a small set of chars](https://github.com/executablebooks/markdown-it-py/blob/master/markdown_it/parser_inline.py), which can be tokens.
122122

123-
If you are absolutely sure that something important is missing there - create a
124-
ticket and we will consider adding it as a new charcode.
123+
If your inline rule needs to trigger on a character that is not in the default terminator set, you can register it via `md.inline.add_terminator_char`:
124+
125+
```python
126+
def my_plugin(md: MarkdownIt) -> None:
127+
md.inline.add_terminator_char("w") # stop text rule on 'w'
128+
md.inline.ruler.push("my_rule", my_inline_rule)
129+
```

docs/plugins.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The following plugins are embedded within the core package:
66

77
- [tables](https://help.github.com/articles/organizing-information-with-tables/) (GFM)
88
- [strikethrough](https://help.github.com/articles/basic-writing-and-formatting-syntax/#styling-text) (GFM)
9+
- [task lists](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/about-task-lists) (GFM) — `- [x] done`
10+
- [alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) (GFM) — `> [!NOTE]`
911

1012
These can be enabled individually:
1113

@@ -18,7 +20,8 @@ or as part of a configuration:
1820

1921
```python
2022
from markdown_it import MarkdownIt
21-
md = MarkdownIt("gfm-like")
23+
md = MarkdownIt("gfm-like") # tables, strikethrough, linkify
24+
md = MarkdownIt("gfm-like2") # + task lists, alerts, single-tilde strikethrough
2225
```
2326

2427
```{seealso}

docs/using.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ You can define this configuration *via* directly supplying a dictionary or a pre
5858
- `gfm-like`: This configures the parser to approximately comply with the [GitHub Flavored Markdown specification](https://github.github.com/gfm/).
5959
Compared to `commonmark`, it enables the table, strikethrough and linkify components.
6060
**Important**, to use this configuration you must have `linkify-it-py` installed.
61+
- `gfm-like2`: Builds on `gfm-like` and additionally enables task lists (`- [x] done`),
62+
GitHub-style alerts (`> [!NOTE]`), and single-tilde strikethrough (`~text~`).
63+
**Important**, to use this configuration you must have `linkify-it-py` installed.
6164

6265
```{jupyter-execute}
6366
from markdown_it.presets import zero

markdown_it/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""A Python port of Markdown-It"""
22

33
__all__ = ("MarkdownIt",)
4-
__version__ = "4.0.0"
4+
__version__ = "4.2.0"
55

66
from .main import MarkdownIt

markdown_it/common/html_re.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
2222

2323
HTML_TAG_RE = re.compile(
24-
"^(?:"
24+
# No leading `^`: applied via `.match(src, pos)`, which anchors at `pos`
25+
# without slicing `src[pos:]` (an O(len) copy per call, quadratic over a run).
26+
"(?:"
2527
+ open_tag
2628
+ "|"
2729
+ close_tag

markdown_it/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"zero": presets.zero.make(),
2727
"commonmark": presets.commonmark.make(),
2828
"gfm-like": presets.gfm_like.make(),
29+
"gfm-like2": presets.gfm_like2.make(),
2930
}
3031

3132

@@ -125,7 +126,7 @@ def configure(
125126
if options_update:
126127
options = {**options, **options_update} # type: ignore
127128

128-
self.set(options) # type: ignore
129+
self.set(options)
129130

130131
if "components" in config:
131132
for name, component in config["components"].items():

markdown_it/parser_inline.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from __future__ import annotations
44

55
from collections.abc import Callable
6+
import functools
7+
import re
68
from typing import TYPE_CHECKING
79

810
from . import rules_inline
@@ -15,6 +17,47 @@
1517
from markdown_it import MarkdownIt
1618

1719

20+
# Default set of characters that terminate a text token and allow inline rules to fire.
21+
# '{}$%@~+=:' reserved for extensions.
22+
# Note: Don't confuse with "Markdown ASCII Punctuation" chars.
23+
# http://spec.commonmark.org/0.15/#ascii-punctuation-character
24+
_DEFAULT_TERMINATORS: frozenset[str] = frozenset(
25+
{
26+
"\n",
27+
"!",
28+
"#",
29+
"$",
30+
"%",
31+
"&",
32+
"*",
33+
"+",
34+
"-",
35+
":",
36+
"<",
37+
"=",
38+
">",
39+
"@",
40+
"[",
41+
"\\",
42+
"]",
43+
"^",
44+
"_",
45+
"`",
46+
"{",
47+
"}",
48+
"~",
49+
}
50+
)
51+
52+
53+
# Lazily compiled regex for the default terminator set. The @cache ensures it is
54+
# compiled at most once (on first ParserInline instantiation) and shared across all
55+
# instances that have not added extra chars, keeping __init__ cost near zero.
56+
@functools.cache
57+
def _default_terminator_re() -> re.Pattern[str]:
58+
return re.compile("[" + re.escape("".join(_DEFAULT_TERMINATORS)) + "]")
59+
60+
1861
# Parser rules
1962
RuleFuncInlineType = Callable[[StateInline, bool], bool]
2063
"""(state: StateInline, silent: bool) -> matched: bool)
@@ -61,6 +104,30 @@ def __init__(self) -> None:
61104
self.ruler2 = Ruler[RuleFuncInline2Type]()
62105
for name, rule2 in _rules2:
63106
self.ruler2.push(name, rule2)
107+
# Characters that stop the text rule, allowing other inline rules to fire.
108+
# _extra_terminator_chars is only allocated when add_terminator_char() is called
109+
# with a char outside the defaults, keeping __init__ allocation-free.
110+
self._extra_terminator_chars: set[str] = set()
111+
# Pre-compiled regex shared with all default instances (no copy in the common path).
112+
self.terminator_re: re.Pattern[str] = _default_terminator_re()
113+
114+
def add_terminator_char(self, ch: str) -> None:
115+
"""Register a character that stops the ``text`` rule, allowing inline rules to fire.
116+
117+
This lets plugins declare which characters their inline rules react to,
118+
mirroring the ``MARKER`` mechanism in the Rust markdown-it implementation.
119+
120+
:param ch: A single character to add to the terminator set.
121+
"""
122+
if ch not in _DEFAULT_TERMINATORS and ch not in self._extra_terminator_chars:
123+
self._extra_terminator_chars.add(ch)
124+
self.terminator_re = re.compile(
125+
"["
126+
+ re.escape(
127+
"".join(_DEFAULT_TERMINATORS | self._extra_terminator_chars)
128+
)
129+
+ "]"
130+
)
64131

65132
def skipToken(self, state: StateInline) -> None:
66133
"""Skip single token by running all rules in validation mode;
@@ -130,7 +197,7 @@ def tokenize(self, state: StateInline) -> None:
130197
break
131198
continue
132199

133-
state.pending += state.src[state.pos]
200+
state.append_pending(state.src[state.pos])
134201
state.pos += 1
135202

136203
if state.pending:

0 commit comments

Comments
 (0)