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.
Files changed (140) hide show
  1. {mistune-3.3.2 → mistune-3.3.4}/PKG-INFO +1 -1
  2. mistune-3.3.4/benchmark/bench_edges.py +269 -0
  3. {mistune-3.3.2 → mistune-3.3.4}/docs/changes.rst +27 -0
  4. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/__init__.py +1 -1
  5. mistune-3.3.4/src/mistune/_inline/__init__.py +1 -0
  6. mistune-3.3.4/src/mistune/_inline/emphasis.py +395 -0
  7. mistune-3.3.4/src/mistune/_inline/links.py +250 -0
  8. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/core.py +8 -0
  9. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/helpers.py +35 -11
  10. mistune-3.3.4/src/mistune/inline_parser.py +416 -0
  11. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/list_parser.py +5 -2
  12. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/def_list.py +4 -1
  13. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/footnotes.py +6 -1
  14. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/formatting.py +6 -0
  15. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/ruby.py +16 -12
  16. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/spoiler.py +21 -5
  17. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/table.py +6 -1
  18. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/_list.py +3 -0
  19. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/html.py +33 -21
  20. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/markdown.py +45 -7
  21. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/rst.py +1 -0
  22. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/util.py +19 -6
  23. {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/PKG-INFO +1 -1
  24. {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/SOURCES.txt +5 -0
  25. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/def_list.txt +13 -0
  26. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fix-commonmark.txt +15 -0
  27. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/renderer_rst.txt +16 -0
  28. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/table.txt +23 -0
  29. {mistune-3.3.2 → mistune-3.3.4}/tests/test_commonmark.py +4 -4
  30. {mistune-3.3.2 → mistune-3.3.4}/tests/test_directives.py +23 -0
  31. {mistune-3.3.2 → mistune-3.3.4}/tests/test_misc.py +25 -0
  32. {mistune-3.3.2 → mistune-3.3.4}/tests/test_renderers.py +78 -0
  33. mistune-3.3.4/tests/test_security_edge_cases.py +102 -0
  34. {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_formatting.py +23 -6
  35. mistune-3.3.4/tests/test_security_inline.py +154 -0
  36. {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_math.py +15 -1
  37. {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_ref_links.py +2 -6
  38. mistune-3.3.4/tests/test_security_urls.py +76 -0
  39. mistune-3.3.2/src/mistune/inline_parser.py +0 -854
  40. mistune-3.3.2/tests/test_security_inline.py +0 -37
  41. mistune-3.3.2/tests/test_security_urls.py +0 -20
  42. {mistune-3.3.2 → mistune-3.3.4}/LICENSE +0 -0
  43. {mistune-3.3.2 → mistune-3.3.4}/MANIFEST.in +0 -0
  44. {mistune-3.3.2 → mistune-3.3.4}/README.rst +0 -0
  45. {mistune-3.3.2 → mistune-3.3.4}/benchmark/bench.py +0 -0
  46. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/atx.txt +0 -0
  47. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/auto_links.txt +0 -0
  48. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/blockcode.txt +0 -0
  49. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/blockhtml.txt +0 -0
  50. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/blockquote.txt +0 -0
  51. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/emphasis.txt +0 -0
  52. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/fenced.txt +0 -0
  53. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/insane_ol.txt +0 -0
  54. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/insane_ul.txt +0 -0
  55. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/normal_ol.txt +0 -0
  56. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/normal_ul.txt +0 -0
  57. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/paragraph.txt +0 -0
  58. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/ref_links.txt +0 -0
  59. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/setext.txt +0 -0
  60. {mistune-3.3.2 → mistune-3.3.4}/benchmark/cases/std_links.txt +0 -0
  61. {mistune-3.3.2 → mistune-3.3.4}/docs/Makefile +0 -0
  62. {mistune-3.3.2 → mistune-3.3.4}/docs/_static/dark-icon.svg +0 -0
  63. {mistune-3.3.2 → mistune-3.3.4}/docs/_static/light-icon.svg +0 -0
  64. {mistune-3.3.2 → mistune-3.3.4}/docs/_static/logo-black.svg +0 -0
  65. {mistune-3.3.2 → mistune-3.3.4}/docs/_static/logo-white.svg +0 -0
  66. {mistune-3.3.2 → mistune-3.3.4}/docs/_templates/partials/extra-foot.html +0 -0
  67. {mistune-3.3.2 → mistune-3.3.4}/docs/_templates/partials/globaltoc-above.html +0 -0
  68. {mistune-3.3.2 → mistune-3.3.4}/docs/advanced.rst +0 -0
  69. {mistune-3.3.2 → mistune-3.3.4}/docs/api.rst +0 -0
  70. {mistune-3.3.2 → mistune-3.3.4}/docs/cli.rst +0 -0
  71. {mistune-3.3.2 → mistune-3.3.4}/docs/community.rst +0 -0
  72. {mistune-3.3.2 → mistune-3.3.4}/docs/conf.py +0 -0
  73. {mistune-3.3.2 → mistune-3.3.4}/docs/directives.rst +0 -0
  74. {mistune-3.3.2 → mistune-3.3.4}/docs/guide.rst +0 -0
  75. {mistune-3.3.2 → mistune-3.3.4}/docs/index.rst +0 -0
  76. {mistune-3.3.2 → mistune-3.3.4}/docs/plugins.rst +0 -0
  77. {mistune-3.3.2 → mistune-3.3.4}/docs/renderers.rst +0 -0
  78. {mistune-3.3.2 → mistune-3.3.4}/docs/upgrade.rst +0 -0
  79. {mistune-3.3.2 → mistune-3.3.4}/pyproject.toml +0 -0
  80. {mistune-3.3.2 → mistune-3.3.4}/setup.cfg +0 -0
  81. {mistune-3.3.2 → mistune-3.3.4}/setup.py +0 -0
  82. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/__main__.py +0 -0
  83. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/block_parser.py +0 -0
  84. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/__init__.py +0 -0
  85. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/_base.py +0 -0
  86. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/_fenced.py +0 -0
  87. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/_rst.py +0 -0
  88. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/admonition.py +0 -0
  89. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/image.py +0 -0
  90. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/include.py +0 -0
  91. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/directives/toc.py +0 -0
  92. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/markdown.py +0 -0
  93. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/__init__.py +0 -0
  94. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/abbr.py +0 -0
  95. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/math.py +0 -0
  96. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/speedup.py +0 -0
  97. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/task_lists.py +0 -0
  98. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/plugins/url.py +0 -0
  99. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/py.typed +0 -0
  100. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/renderers/__init__.py +0 -0
  101. {mistune-3.3.2 → mistune-3.3.4}/src/mistune/toc.py +0 -0
  102. {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/dependency_links.txt +0 -0
  103. {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/entry_points.txt +0 -0
  104. {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/requires.txt +0 -0
  105. {mistune-3.3.2 → mistune-3.3.4}/src/mistune.egg-info/top_level.txt +0 -0
  106. {mistune-3.3.2 → mistune-3.3.4}/tests/__init__.py +0 -0
  107. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/__init__.py +0 -0
  108. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/abbr.txt +0 -0
  109. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/cli.txt +0 -0
  110. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/commonmark.json +0 -0
  111. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fenced_admonition.txt +0 -0
  112. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fenced_figure.txt +0 -0
  113. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fenced_image.txt +0 -0
  114. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/fenced_toc.txt +0 -0
  115. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/footnotes.txt +0 -0
  116. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/hook_toc.txt +0 -0
  117. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/include/hello.md +0 -0
  118. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/include/hello.txt +0 -0
  119. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/include/text.html +0 -0
  120. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/include/text.md +0 -0
  121. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/insert.txt +0 -0
  122. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/mark.txt +0 -0
  123. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/math.txt +0 -0
  124. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/renderer_markdown.txt +0 -0
  125. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/rst_admonition.txt +0 -0
  126. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/rst_toc.txt +0 -0
  127. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/ruby.txt +0 -0
  128. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/spoiler.txt +0 -0
  129. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/strikethrough.txt +0 -0
  130. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/subscript.txt +0 -0
  131. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/superscript.txt +0 -0
  132. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/task_lists.txt +0 -0
  133. {mistune-3.3.2 → mistune-3.3.4}/tests/fixtures/url.txt +0 -0
  134. {mistune-3.3.2 → mistune-3.3.4}/tests/test_cli.py +0 -0
  135. {mistune-3.3.2 → mistune-3.3.4}/tests/test_hooks.py +0 -0
  136. {mistune-3.3.2 → mistune-3.3.4}/tests/test_plugins.py +0 -0
  137. {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_image.py +0 -0
  138. {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_include.py +0 -0
  139. {mistune-3.3.2 → mistune-3.3.4}/tests/test_security_toc.py +0 -0
  140. {mistune-3.3.2 → mistune-3.3.4}/tests/test_syntax.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mistune
3
- Version: 3.3.2
3
+ Version: 3.3.4
4
4
  Summary: A sane and fast Markdown parser with useful plugins and renderers
5
5
  Author-email: Hsiaoming Yang <me@lepture.com>
6
6
  License: BSD-3-Clause
@@ -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 + "text" + "](/url)" * 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
 
@@ -97,5 +97,5 @@ __all__ = [
97
97
  "markdown",
98
98
  ]
99
99
 
100
- __version__ = "3.3.2"
100
+ __version__ = "3.3.4"
101
101
  __homepage__ = "https://mistune.lepture.com/"
@@ -0,0 +1 @@
1
+ """Internal helpers for inline Markdown parsing."""