mistune 3.3.2__tar.gz → 3.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mistune-3.3.2 → mistune-3.3.4}/PKG-INFO +1 -1
- mistune-3.3.4/benchmark/bench_edges.py +269 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/changes.rst +27 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/__init__.py +1 -1
- mistune-3.3.4/src/mistune/_inline/__init__.py +1 -0
- mistune-3.3.4/src/mistune/_inline/emphasis.py +395 -0
- mistune-3.3.4/src/mistune/_inline/links.py +250 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/core.py +8 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/helpers.py +35 -11
- mistune-3.3.4/src/mistune/inline_parser.py +416 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/list_parser.py +5 -2
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/def_list.py +4 -1
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/footnotes.py +6 -1
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/formatting.py +6 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/ruby.py +16 -12
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/spoiler.py +21 -5
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/table.py +6 -1
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/_list.py +3 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/html.py +33 -21
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/markdown.py +45 -7
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/rst.py +1 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/util.py +19 -6
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/PKG-INFO +1 -1
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/SOURCES.txt +5 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/def_list.txt +13 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fix-commonmark.txt +15 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/renderer_rst.txt +16 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/table.txt +23 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_commonmark.py +4 -4
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_directives.py +23 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_misc.py +25 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_renderers.py +78 -0
- mistune-3.3.4/tests/test_security_edge_cases.py +102 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_formatting.py +23 -6
- mistune-3.3.4/tests/test_security_inline.py +154 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_math.py +15 -1
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_ref_links.py +2 -6
- mistune-3.3.4/tests/test_security_urls.py +76 -0
- mistune-3.3.2/src/mistune/inline_parser.py +0 -854
- mistune-3.3.2/tests/test_security_inline.py +0 -37
- mistune-3.3.2/tests/test_security_urls.py +0 -20
- {mistune-3.3.2 → mistune-3.3.4}/LICENSE +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/MANIFEST.in +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/README.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/bench.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/atx.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/auto_links.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/blockcode.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/blockhtml.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/blockquote.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/emphasis.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/fenced.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/insane_ol.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/insane_ul.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/normal_ol.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/normal_ul.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/paragraph.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/ref_links.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/setext.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/std_links.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/Makefile +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/_static/dark-icon.svg +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/_static/light-icon.svg +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/_static/logo-black.svg +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/_static/logo-white.svg +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/_templates/partials/extra-foot.html +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/_templates/partials/globaltoc-above.html +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/advanced.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/api.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/cli.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/community.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/conf.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/directives.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/guide.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/index.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/plugins.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/renderers.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/docs/upgrade.rst +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/pyproject.toml +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/setup.cfg +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/setup.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/__main__.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/block_parser.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/__init__.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/_base.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/_fenced.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/_rst.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/admonition.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/image.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/include.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/toc.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/markdown.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/__init__.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/abbr.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/math.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/speedup.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/task_lists.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/url.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/py.typed +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/__init__.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune/toc.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/dependency_links.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/entry_points.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/requires.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/top_level.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/__init__.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/__init__.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/abbr.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/cli.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/commonmark.json +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fenced_admonition.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fenced_figure.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fenced_image.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fenced_toc.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/footnotes.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/hook_toc.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/include/hello.md +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/include/hello.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/include/text.html +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/include/text.md +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/insert.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/mark.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/math.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/renderer_markdown.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/rst_admonition.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/rst_toc.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/ruby.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/spoiler.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/strikethrough.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/subscript.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/superscript.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/task_lists.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/url.txt +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_cli.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_hooks.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_plugins.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_image.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_include.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_toc.py +0 -0
- {mistune-3.3.2 → mistune-3.3.4}/tests/test_syntax.py +0 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Benchmark adversarial Markdown inputs.
|
|
2
|
+
|
|
3
|
+
Run one case with:
|
|
4
|
+
|
|
5
|
+
python benchmark/bench_edges.py --case blank-list-continuations
|
|
6
|
+
|
|
7
|
+
The ``normalized`` column is the time growth divided by the input-size
|
|
8
|
+
growth. Values near 1 indicate linear scaling; values near 2 indicate that
|
|
9
|
+
doubling the input takes roughly four times as long.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import statistics
|
|
16
|
+
import time
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class EdgeCase:
|
|
24
|
+
name: str
|
|
25
|
+
category: str
|
|
26
|
+
generate: Callable[[int], str]
|
|
27
|
+
make_markdown: Callable[[str], object]
|
|
28
|
+
sizes: tuple[int, ...]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class Result:
|
|
33
|
+
case: str
|
|
34
|
+
renderer: str
|
|
35
|
+
size: int
|
|
36
|
+
bytes: int
|
|
37
|
+
best: float
|
|
38
|
+
mean: float
|
|
39
|
+
growth: Optional[float]
|
|
40
|
+
normalized_growth: Optional[float]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def markdown_factory(renderer: str = "html", plugins: Optional[list[str]] = None) -> Callable[[str], object]:
|
|
44
|
+
def make_markdown(_renderer: str = renderer, _plugins: Optional[list[str]] = plugins):
|
|
45
|
+
import mistune
|
|
46
|
+
|
|
47
|
+
return mistune.create_markdown(renderer=_renderer, escape=False, plugins=_plugins)
|
|
48
|
+
|
|
49
|
+
return make_markdown
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
CORE = markdown_factory()
|
|
53
|
+
SPOILER = markdown_factory(plugins=["spoiler"])
|
|
54
|
+
MATH = markdown_factory(plugins=["math"])
|
|
55
|
+
FOOTNOTES = markdown_factory(plugins=["footnotes"])
|
|
56
|
+
GITHUB = markdown_factory(plugins=["table", "task_lists"])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
EDGE_CASES = {
|
|
60
|
+
"deep-blockquote": EdgeCase("deep-blockquote", "containers", lambda n: "> " * n + "text\n", CORE, (100, 1000, 10000)),
|
|
61
|
+
"deep-list": EdgeCase("deep-list", "containers", lambda n: "- " * n + "text\n", CORE, (100, 1000, 10000)),
|
|
62
|
+
"blockquote-depth-boundary": EdgeCase(
|
|
63
|
+
"blockquote-depth-boundary", "containers", lambda n: "> " * n + "text\n", CORE, (99, 100, 101, 1000)
|
|
64
|
+
),
|
|
65
|
+
"alternating-containers": EdgeCase(
|
|
66
|
+
"alternating-containers", "containers", lambda n: "> - " * n + "text\n", CORE, (64, 128, 256)
|
|
67
|
+
),
|
|
68
|
+
"deep-block-spoiler": EdgeCase(
|
|
69
|
+
"deep-block-spoiler", "containers", lambda n: ">! " * n + "text\n", SPOILER, (100, 1000, 10000)
|
|
70
|
+
),
|
|
71
|
+
"unmatched-links": EdgeCase("unmatched-links", "inline", lambda n: "[" * n + "\n", CORE, (1000, 10000, 100000)),
|
|
72
|
+
"nested-link-labels": EdgeCase(
|
|
73
|
+
"nested-link-labels", "inline", lambda n: "[" * n + "text" + "]" * n + "(/url)\n", CORE, (200, 1000, 5000)
|
|
74
|
+
),
|
|
75
|
+
"nested-image-labels": EdgeCase(
|
|
76
|
+
"nested-image-labels", "inline", lambda n: "" * n + "\n", CORE, (200, 1000, 5000)
|
|
77
|
+
),
|
|
78
|
+
"dense-emphasis": EdgeCase("dense-emphasis", "inline", lambda n: "*a" * n + "\n", CORE, (1000, 10000, 100000)),
|
|
79
|
+
"long-code-span-runs": EdgeCase(
|
|
80
|
+
"long-code-span-runs", "inline", lambda n: "`" * n + "text" + "`" * (n - 1) + "\n", CORE, (1000, 10000, 100000)
|
|
81
|
+
),
|
|
82
|
+
"successful-long-code-span": EdgeCase(
|
|
83
|
+
"successful-long-code-span", "inline", lambda n: "`" * n + "text" + "`" * n + "\n", CORE, (1000, 10000, 100000)
|
|
84
|
+
),
|
|
85
|
+
"many-wrong-code-runs": EdgeCase(
|
|
86
|
+
"many-wrong-code-runs", "inline", lambda n: "``start " + "`x" * n + " ``\n", CORE, (1000, 10000, 100000)
|
|
87
|
+
),
|
|
88
|
+
"link-label-long-code-runs": EdgeCase(
|
|
89
|
+
"link-label-long-code-runs",
|
|
90
|
+
"inline",
|
|
91
|
+
lambda n: "[" + "`" * n + "text" + "`" * (n - 1) + "](/url)\n",
|
|
92
|
+
CORE,
|
|
93
|
+
(1000, 10000, 100000),
|
|
94
|
+
),
|
|
95
|
+
"nested-text-directives": EdgeCase(
|
|
96
|
+
"nested-text-directives", "inline", lambda n: ":x[" * n + "text" + "]" * n + "\n", CORE, (100, 500, 1000)
|
|
97
|
+
),
|
|
98
|
+
"invalid-inline-math-closers": EdgeCase(
|
|
99
|
+
"invalid-inline-math-closers", "inline", lambda n: "$x" + "$5" * n + "\n", MATH, (1000, 5000, 10000)
|
|
100
|
+
),
|
|
101
|
+
"long-list-spacing": EdgeCase(
|
|
102
|
+
"long-list-spacing", "blocks", lambda n: "- " + " " * n + "text\n", CORE, (1000, 10000, 100000)
|
|
103
|
+
),
|
|
104
|
+
"blank-list-continuations": EdgeCase(
|
|
105
|
+
"blank-list-continuations", "blocks", lambda n: "- first\n" + "\n" * n + " last\n", CORE, (1000, 5000, 10000)
|
|
106
|
+
),
|
|
107
|
+
"list-marker-interrupt": EdgeCase(
|
|
108
|
+
"list-marker-interrupt", "blocks", lambda n: "paragraph\n1. " + " " * n + "\n", CORE, (1000, 10000, 100000)
|
|
109
|
+
),
|
|
110
|
+
"unclosed-fence": EdgeCase(
|
|
111
|
+
"unclosed-fence", "blocks", lambda n: "```\n" + "line\n" * n, CORE, (1000, 5000, 10000)
|
|
112
|
+
),
|
|
113
|
+
"lazy-blockquote-lines": EdgeCase(
|
|
114
|
+
"lazy-blockquote-lines", "blocks", lambda n: "> first\n" + "lazy continuation\n" * n, CORE, (1000, 5000, 10000)
|
|
115
|
+
),
|
|
116
|
+
"many-empty-blockquotes": EdgeCase(
|
|
117
|
+
"many-empty-blockquotes", "blocks", lambda n: ">\n" * n, CORE, (1000, 5000, 10000)
|
|
118
|
+
),
|
|
119
|
+
"task-list-lines": EdgeCase(
|
|
120
|
+
"task-list-lines", "blocks", lambda n: "- [x] item\n" * n, GITHUB, (1000, 5000, 10000)
|
|
121
|
+
),
|
|
122
|
+
"ordered-list-markers": EdgeCase(
|
|
123
|
+
"ordered-list-markers", "blocks", lambda n: "123456789. item\n" * n, CORE, (1000, 5000, 10000)
|
|
124
|
+
),
|
|
125
|
+
"nested-directives": EdgeCase(
|
|
126
|
+
"nested-directives", "containers", lambda n: ":::note\n" * n + "text\n" + ":::\n" * n, CORE, (64, 128, 256)
|
|
127
|
+
),
|
|
128
|
+
"fenced-directive-attributes": EdgeCase(
|
|
129
|
+
"fenced-directive-attributes", "blocks", lambda n: "```{note}\n" + ":key: value\n" * n + "```\n", CORE, (1000, 5000, 10000)
|
|
130
|
+
),
|
|
131
|
+
"unclosed-reference-title": EdgeCase(
|
|
132
|
+
"unclosed-reference-title", "references", lambda n: '[x]: /url "\n' + "line\n" * n + "\n[x]\n", CORE, (1000, 5000, 10000)
|
|
133
|
+
),
|
|
134
|
+
"footnote-blank-continuations": EdgeCase(
|
|
135
|
+
"footnote-blank-continuations",
|
|
136
|
+
"references",
|
|
137
|
+
lambda n: "[^x]: first\n" + "\n" * n + " second\n\n[^x]\n",
|
|
138
|
+
FOOTNOTES,
|
|
139
|
+
(1000, 5000, 10000),
|
|
140
|
+
),
|
|
141
|
+
"multiline-reference-label": EdgeCase(
|
|
142
|
+
"multiline-reference-label", "references", lambda n: "[label\n" + "part\n" * n + "]: /url\n", CORE, (1000, 5000, 10000)
|
|
143
|
+
),
|
|
144
|
+
"many-reference-definitions": EdgeCase(
|
|
145
|
+
"many-reference-definitions", "references", lambda n: "".join("[x%d]: /url\n" % i for i in range(n)), CORE, (1000, 5000, 10000)
|
|
146
|
+
),
|
|
147
|
+
"nested-html-containers": EdgeCase(
|
|
148
|
+
"nested-html-containers", "html", lambda n: "<div>\n" * n + "</div>\n" * n, CORE, (64, 128, 256)
|
|
149
|
+
),
|
|
150
|
+
"long-html-tag-name": EdgeCase(
|
|
151
|
+
"long-html-tag-name", "html", lambda n: "<" + "x" * n + ">\ntext\n</" + "x" * n + ">\n", CORE, (1000, 10000, 100000)
|
|
152
|
+
),
|
|
153
|
+
"html-many-attributes": EdgeCase(
|
|
154
|
+
"html-many-attributes",
|
|
155
|
+
"html",
|
|
156
|
+
lambda n: '<div ' + " ".join('data-x%d="v"' % i for i in range(n)) + ">\ntext\n</div>\n",
|
|
157
|
+
CORE,
|
|
158
|
+
(100, 1000, 5000),
|
|
159
|
+
),
|
|
160
|
+
"wide-table": EdgeCase(
|
|
161
|
+
"wide-table",
|
|
162
|
+
"tables",
|
|
163
|
+
lambda n: "| " + " | ".join(["cell"] * n) + " |\n| " + " | ".join(["---"] * n) + " |\n",
|
|
164
|
+
markdown_factory(plugins=["table"]),
|
|
165
|
+
(100, 1000, 5000),
|
|
166
|
+
),
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def benchmark_case(edge_case: EdgeCase, sizes: list[int], iterations: int, warmup: int, renderer: str) -> list[Result]:
|
|
171
|
+
markdown = edge_case.make_markdown(renderer)
|
|
172
|
+
results = []
|
|
173
|
+
previous_size = None
|
|
174
|
+
previous_mean = None
|
|
175
|
+
for size in sizes:
|
|
176
|
+
text = edge_case.generate(size)
|
|
177
|
+
for _ in range(warmup):
|
|
178
|
+
markdown(text)
|
|
179
|
+
|
|
180
|
+
timings = []
|
|
181
|
+
for _ in range(iterations):
|
|
182
|
+
started = time.perf_counter()
|
|
183
|
+
markdown(text)
|
|
184
|
+
timings.append(time.perf_counter() - started)
|
|
185
|
+
|
|
186
|
+
mean = statistics.fmean(timings)
|
|
187
|
+
growth = None if previous_mean is None else mean / previous_mean
|
|
188
|
+
normalized = None if growth is None else growth / (size / previous_size)
|
|
189
|
+
results.append(Result(edge_case.name, renderer, size, len(text.encode()), min(timings), mean, growth, normalized))
|
|
190
|
+
previous_size = size
|
|
191
|
+
previous_mean = mean
|
|
192
|
+
return results
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def parse_sizes(value: str) -> list[int]:
|
|
196
|
+
try:
|
|
197
|
+
sizes = sorted({int(item) for item in value.split(",")})
|
|
198
|
+
except ValueError as exc:
|
|
199
|
+
raise argparse.ArgumentTypeError("sizes must be comma-separated integers") from exc
|
|
200
|
+
if not sizes or sizes[0] < 1:
|
|
201
|
+
raise argparse.ArgumentTypeError("sizes must contain positive integers")
|
|
202
|
+
return sizes
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def format_duration(seconds: float) -> str:
|
|
206
|
+
if seconds < 0.001:
|
|
207
|
+
return "%.1fus" % (seconds * 1_000_000)
|
|
208
|
+
if seconds < 1:
|
|
209
|
+
return "%.2fms" % (seconds * 1_000)
|
|
210
|
+
return "%.3fs" % seconds
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def print_results(results: list[Result]) -> None:
|
|
214
|
+
headers = ["case", "renderer", "size", "bytes", "best", "mean", "ns/unit", "growth", "normalized"]
|
|
215
|
+
rows = []
|
|
216
|
+
for result in results:
|
|
217
|
+
rows.append(
|
|
218
|
+
[
|
|
219
|
+
result.case,
|
|
220
|
+
result.renderer,
|
|
221
|
+
str(result.size),
|
|
222
|
+
str(result.bytes),
|
|
223
|
+
format_duration(result.best),
|
|
224
|
+
format_duration(result.mean),
|
|
225
|
+
"%.1f" % (result.mean / result.size * 1_000_000_000),
|
|
226
|
+
"-" if result.growth is None else "%.2fx" % result.growth,
|
|
227
|
+
"-" if result.normalized_growth is None else "%.2fx" % result.normalized_growth,
|
|
228
|
+
]
|
|
229
|
+
)
|
|
230
|
+
widths = [len(header) for header in headers]
|
|
231
|
+
for row in rows:
|
|
232
|
+
widths = [max(width, len(value)) for width, value in zip(widths, row)]
|
|
233
|
+
print(" ".join(header.ljust(width) for header, width in zip(headers, widths)))
|
|
234
|
+
print(" ".join("-" * width for width in widths))
|
|
235
|
+
for row in rows:
|
|
236
|
+
print(" ".join(value.ljust(width) for value, width in zip(row, widths)))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def main() -> None:
|
|
240
|
+
parser = argparse.ArgumentParser(description="Benchmark adversarial Markdown parser inputs.")
|
|
241
|
+
parser.add_argument("--case", default="all", choices=["all", *EDGE_CASES])
|
|
242
|
+
parser.add_argument("--category", default="all", choices=["all", *sorted({case.category for case in EDGE_CASES.values()})])
|
|
243
|
+
parser.add_argument("--sizes", type=parse_sizes, help="override comma-separated case sizes")
|
|
244
|
+
parser.add_argument("--iterations", type=int, default=5)
|
|
245
|
+
parser.add_argument("--warmup", type=int, default=1)
|
|
246
|
+
parser.add_argument("--renderer", choices=["html", "ast"], default="html")
|
|
247
|
+
args = parser.parse_args()
|
|
248
|
+
if args.iterations < 1 or args.warmup < 0:
|
|
249
|
+
raise SystemExit("iterations must be at least 1 and warmup must not be negative")
|
|
250
|
+
if args.case != "all" and args.category != "all":
|
|
251
|
+
raise SystemExit("--case and --category cannot be combined")
|
|
252
|
+
|
|
253
|
+
if args.case != "all":
|
|
254
|
+
cases = [EDGE_CASES[args.case]]
|
|
255
|
+
elif args.category != "all":
|
|
256
|
+
cases = [case for case in EDGE_CASES.values() if case.category == args.category]
|
|
257
|
+
else:
|
|
258
|
+
cases = list(EDGE_CASES.values())
|
|
259
|
+
|
|
260
|
+
results = [
|
|
261
|
+
result
|
|
262
|
+
for case in cases
|
|
263
|
+
for result in benchmark_case(case, args.sizes or list(case.sizes), args.iterations, args.warmup, args.renderer)
|
|
264
|
+
]
|
|
265
|
+
print_results(results)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
main()
|
|
@@ -3,6 +3,33 @@ Changelog
|
|
|
3
3
|
|
|
4
4
|
Here is the full history of mistune v3.
|
|
5
5
|
|
|
6
|
+
Unreleased
|
|
7
|
+
----------
|
|
8
|
+
|
|
9
|
+
Version 3.3.4
|
|
10
|
+
-------------
|
|
11
|
+
|
|
12
|
+
**Released on Jul 22, 2026**
|
|
13
|
+
|
|
14
|
+
* Harden inline parsing against deeply nested and adversarial link, image,
|
|
15
|
+
emphasis, formatting, math, and reference inputs.
|
|
16
|
+
* Improve performance for repeated link suffixes, dense emphasis, unclosed
|
|
17
|
+
formatting markers, inline spoilers, and adjacent ruby tokens.
|
|
18
|
+
* Refactor the inline parser into dedicated emphasis and link modules.
|
|
19
|
+
* Escape literal ``*``/``_`` emphasis markers in the Markdown renderer so
|
|
20
|
+
round-tripping escaped text does not re-introduce emphasis.
|
|
21
|
+
|
|
22
|
+
Version 3.3.3
|
|
23
|
+
-------------
|
|
24
|
+
|
|
25
|
+
**Released on Jul 9, 2026**
|
|
26
|
+
|
|
27
|
+
* Limit deeply nested emphasis and image parsing to avoid ``RecursionError``.
|
|
28
|
+
* Fix repeated link suffix and unclosed formatting marker performance issues.
|
|
29
|
+
* Fix unclosed inline spoiler performance issues.
|
|
30
|
+
* Avoid recursive parsing for adjacent ruby tokens.
|
|
31
|
+
* Speed up footnote reference indexing.
|
|
32
|
+
|
|
6
33
|
Version 3.3.2
|
|
7
34
|
-------------
|
|
8
35
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Internal helpers for inline Markdown parsing."""
|