Skip to content

Commit 13ad279

Browse files
committed
fix(#45): do not wrap attribute lists
1 parent 9be26c6 commit 13ad279

File tree

8 files changed

+144
-4
lines changed

8 files changed

+144
-4
lines changed

mdformat_mkdocs/mdit_plugins/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
from ._pymd_abbreviations import PYMD_ABBREVIATIONS_PREFIX, pymd_abbreviations_plugin
1818
from ._pymd_admon import pymd_admon_plugin
1919
from ._pymd_snippet import PYMD_SNIPPET_PREFIX, pymd_snippet_plugin
20+
from ._python_markdown_attr_list import (
21+
PYTHON_MARKDOWN_ATTR_LIST_PREFIX,
22+
python_markdown_attr_list_plugin,
23+
)
2024

2125
__all__ = (
2226
"MATERIAL_ADMON_MARKERS",
@@ -26,11 +30,13 @@
2630
"MKDOCSTRINGS_HEADING_AUTOREFS_PREFIX",
2731
"PYMD_ABBREVIATIONS_PREFIX",
2832
"PYMD_SNIPPET_PREFIX",
33+
"PYTHON_MARKDOWN_ATTR_LIST_PREFIX",
2934
"material_admon_plugin",
3035
"material_content_tabs_plugin",
3136
"mkdocstrings_autorefs_plugin",
3237
"mkdocstrings_crossreference_plugin",
3338
"pymd_abbreviations_plugin",
3439
"pymd_admon_plugin",
3540
"pymd_snippet_plugin",
41+
"python_markdown_attr_list_plugin",
3642
)

mdformat_mkdocs/mdit_plugins/_pymd_snippet.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
"""Python-Markdown Snippets.
1+
"""Python-Markdown Extensions: Snippets.
22
33
WARNING: matches only the "scissors" portion, leaving the rest unparsed
44
55
```md
66
--8<-- ...
77
```
88
9-
Docs: <https://facelessuser.github.io/pymd-extensions/extensions/snippets>
9+
Docs: <https://facelessuser.github.io/pymdown-extensions/extensions/snippets>
1010
1111
"""
1212

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Python-Markdown: Attribute List.
2+
3+
WARNING: does not properly render HTML with the attributes and does not respect escaping '\{ '
4+
5+
Docs: <https://python-markdown.github.io/extensions/attr_list>
6+
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import re
12+
from typing import TYPE_CHECKING
13+
14+
from markdown_it import MarkdownIt
15+
16+
from mdformat_mkdocs._synced.admon_factories import new_token
17+
18+
if TYPE_CHECKING:
19+
from markdown_it import MarkdownIt
20+
from markdown_it.rules_inline import StateInline
21+
22+
_ATTR_LIST_PATTERN = re.compile(r"{:? (?P<attrs>[^}]+) }")
23+
24+
PYTHON_MARKDOWN_ATTR_LIST_PREFIX = "python_markdown_attr_list"
25+
26+
27+
def _python_markdown_attr_list(state: StateInline, silent: bool) -> bool:
28+
match = _ATTR_LIST_PATTERN.match(state.src[state.pos : state.posMax])
29+
if not match:
30+
return False
31+
32+
if state.pos > 0 and state.src[state.pos - 1] == "\\":
33+
return False
34+
35+
if silent:
36+
return True
37+
38+
original_pos = state.pos
39+
original_pos_max = state.posMax
40+
state.pos += 1
41+
state.posMax = state.pos + (match.end() - len(" }"))
42+
with new_token(state, PYTHON_MARKDOWN_ATTR_LIST_PREFIX, "span") as token:
43+
token.attrs = {"attributes": match["attrs"].split(" ")}
44+
token.meta = {"content": match.group()}
45+
46+
state.md.inline.tokenize(state)
47+
48+
state.pos = original_pos
49+
state.posMax = original_pos_max
50+
state.pos += match.end()
51+
52+
return True
53+
54+
55+
def python_markdown_attr_list_plugin(md: MarkdownIt) -> None:
56+
md.inline.ruler.push(
57+
PYTHON_MARKDOWN_ATTR_LIST_PREFIX,
58+
_python_markdown_attr_list,
59+
)

mdformat_mkdocs/plugin.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
MKDOCSTRINGS_HEADING_AUTOREFS_PREFIX,
1818
PYMD_ABBREVIATIONS_PREFIX,
1919
PYMD_SNIPPET_PREFIX,
20+
PYTHON_MARKDOWN_ATTR_LIST_PREFIX,
2021
material_admon_plugin,
2122
material_content_tabs_plugin,
2223
mkdocstrings_autorefs_plugin,
2324
mkdocstrings_crossreference_plugin,
2425
pymd_abbreviations_plugin,
2526
pymd_admon_plugin,
2627
pymd_snippet_plugin,
28+
python_markdown_attr_list_plugin,
2729
)
2830

2931
if TYPE_CHECKING:
@@ -79,8 +81,9 @@ def update_mdit(mdit: MarkdownIt) -> None:
7981
mdit.use(material_content_tabs_plugin)
8082
mdit.use(mkdocstrings_autorefs_plugin)
8183
mdit.use(pymd_abbreviations_plugin)
82-
mdit.use(pymd_snippet_plugin)
8384
mdit.use(pymd_admon_plugin)
85+
mdit.use(pymd_snippet_plugin)
86+
mdit.use(python_markdown_attr_list_plugin)
8487

8588
if cli_is_ignore_missing_references(mdit.options):
8689
mdit.use(mkdocstrings_crossreference_plugin)
@@ -187,10 +190,11 @@ def add_extra_admon_newline(node: RenderTreeNode, context: RenderContext) -> str
187190
"content_tab_mkdocs": add_extra_admon_newline,
188191
"content_tab_mkdocs_title": render_admon_title,
189192
MKDOCSTRINGS_AUTOREFS_PREFIX: _render_meta_content,
190-
MKDOCSTRINGS_HEADING_AUTOREFS_PREFIX: _render_heading_autoref,
191193
MKDOCSTRINGS_CROSSREFERENCE_PREFIX: _render_cross_reference,
194+
MKDOCSTRINGS_HEADING_AUTOREFS_PREFIX: _render_heading_autoref,
192195
PYMD_ABBREVIATIONS_PREFIX: _render_inline_content,
193196
PYMD_SNIPPET_PREFIX: _render_inline_content,
197+
PYTHON_MARKDOWN_ATTR_LIST_PREFIX: _render_meta_content,
194198
}
195199

196200

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ known-first-party = ['mdformat_mkdocs', 'tests']
140140
'PT004', # flake8-pytest-style: fixture does not return
141141
'S101', # Use of `assert` detected
142142
]
143+
'tests/format/test_wrap.py' = [
144+
'E501', # Line too long (122 > 88)
145+
]
143146

144147
[tool.ruff.lint.pydocstyle]
145148
convention = "google"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Examples from https://python-markdown.github.io/extensions/attr_list
2+
.
3+
{: #someid .someclass somekey='some value' #id1 .class1 id=id2 class="class2 class3" .class4 }
4+
5+
\{ not an attribute list, but not escaped because '\' is dropped during read_fixture_file }
6+
7+
{ #someid .someclass somekey='some value' }
8+
9+
This is a paragraph.
10+
{: #an_id .a_class }
11+
12+
A setext style header {: #setext}
13+
=================================
14+
15+
### A hash style header ### {: #hash }
16+
17+
[link](http://example.com){: class="foo bar" title="Some title!" }
18+
.
19+
{: #someid .someclass somekey='some value' #id1 .class1 id=id2 class="class2 class3" .class4 }
20+
21+
{ not an attribute list, but not escaped because '' is dropped during read_fixture_file }
22+
23+
{ #someid .someclass somekey='some value' }
24+
25+
This is a paragraph.
26+
{: #an_id .a_class }
27+
28+
# A setext style header {: #setext}
29+
30+
### A hash style header ### {: #hash }
31+
32+
[link](http://example.com){: class="foo bar" title="Some title!" }
33+
.

tests/format/test_format.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def flatten(nested_list: list[list[T]]) -> list[T]:
2525
"mkdocstrings_autorefs.md",
2626
"pymd_abbreviations.md",
2727
"pymd_snippet.md",
28+
"python_markdown_attr_list.md",
2829
"text.md",
2930
)
3031
],

tests/format/test_wrap.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,38 @@
147147
148148
"""
149149

150+
WITH_ATTR_LIST = r"""
151+
{: #someid .someclass somekey='some value' #id1 .class1 id=id2 class="class2 class3" .class4 }
152+
153+
\\{ not an attribute list and should be wrapped at 80 characters and not kept inline }
154+
155+
This is a long paragraph that is more than 80 characters long and should be wrapped.
156+
{: #an_id .a_class #an_id .a_class #an_id .a_class #an_id .a_class #an_id .a_class #an_id .a_class #an_id .a_class }
157+
158+
A setext style header {: #setext}
159+
=================================
160+
161+
### A hash style header ### {: #hash }
162+
163+
[link](http://example.com){: class="foo bar" title="Some title!" .a_class1 .a_class2 .a_class1 .a_class2 .a_class1 .a_class2 }
164+
"""
165+
WITH_ATTR_LIST_TRUE_80 = r"""
166+
{: #someid .someclass somekey='some value' #id1 .class1 id=id2 class="class2 class3" .class4 }
167+
168+
\\{ not an attribute list and should be wrapped at 80 characters and not kept
169+
inline }
170+
171+
This is a long paragraph that is more than 80 characters long and should be
172+
wrapped.
173+
{: #an_id .a_class #an_id .a_class #an_id .a_class #an_id .a_class #an_id .a_class #an_id .a_class #an_id .a_class }
174+
175+
# A setext style header {: #setext}
176+
177+
### A hash style header ### {: #hash }
178+
179+
[link](http://example.com){: class="foo bar" title="Some title!" .a_class1 .a_class2 .a_class1 .a_class2 .a_class1 .a_class2 }
180+
"""
181+
150182

151183
@pytest.mark.parametrize(
152184
("text", "expected", "align_lists", "wrap"),
@@ -157,6 +189,7 @@
157189
(CASE_1, CASE_1_TRUE_80, True, 80),
158190
(TICKET_020, TICKET_020_TRUE_79, True, 79),
159191
(WITH_CODE, WITH_CODE_TRUE_80, True, 80),
192+
(WITH_ATTR_LIST, WITH_ATTR_LIST_TRUE_80, True, 80),
160193
],
161194
ids=[
162195
"CASE_1_FALSE_40",
@@ -165,6 +198,7 @@
165198
"CASE_1_TRUE_80",
166199
"TICKET_020_TRUE_79",
167200
"WITH_CODE_TRUE_80",
201+
"WITH_ATTR_LIST_TRUE_80",
168202
],
169203
)
170204
def test_wrap(text: str, expected: str, align_lists: bool, wrap: int):

0 commit comments

Comments
 (0)