mdformat-space-control 0.3.2__tar.gz → 0.4.1__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.
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/CLAUDE.md +1 -1
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/PKG-INFO +25 -3
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/README.md +22 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/mdformat_space_control/__init__.py +2 -2
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/mdformat_space_control/plugin.py +137 -4
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/pyproject.toml +2 -2
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/.editorconfig +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/.gitignore +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/LICENSE +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/dev/CLAUDE.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/dev/contributing.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/dev/default-formatting-style.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/dev/installation-and-usage.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/dev/overview.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/dev/plugins.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/mdformat_space_control/config.py +0 -0
|
@@ -32,7 +32,7 @@ uv run python -m pytest tests/test_editorconfig.py # Run specific test file
|
|
|
32
32
|
mdformat_space_control/
|
|
33
33
|
├── __init__.py # Public API exports, version
|
|
34
34
|
├── config.py # EditorConfig lookup, file context tracking
|
|
35
|
-
└── plugin.py #
|
|
35
|
+
└── plugin.py # Renderers, postprocessors, parser extensions
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
**Key components:**
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdformat_space_control
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: An mdformat plugin for space control: EditorConfig indentation, tight lists, frontmatter spacing, and wikilinks.
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: An mdformat plugin for space control: EditorConfig indentation, tight lists, frontmatter spacing, smart dash conversion, explicit line breaks, and wikilinks.
|
|
5
5
|
Project-URL: Homepage, https://github.com/jdmonaco/mdformat-space-control
|
|
6
6
|
Author-email: Joseph Monaco <joe@selfmotion.net>
|
|
7
7
|
License-File: LICENSE
|
|
8
|
-
Keywords: editorconfig,formatter,frontmatter,indentation,lists,markdown,mdformat,obsidian,wikilink
|
|
8
|
+
Keywords: editorconfig,formatter,frontmatter,indentation,lists,markdown,mdformat,obsidian,typography,wikilink
|
|
9
9
|
Classifier: Development Status :: 3 - Alpha
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -39,6 +39,7 @@ An [mdformat](https://github.com/executablebooks/mdformat) plugin that provides
|
|
|
39
39
|
- **Consecutive blank line normalization**: Limits runs of 3+ empty lines to a maximum of 2
|
|
40
40
|
- **Trailing whitespace removal**: Strips trailing whitespace outside code blocks
|
|
41
41
|
- **Escaped link repair**: Fixes malformed multi-line links from web-clipped content
|
|
42
|
+
- **Smart dash conversion**: Converts `--` to en-dash (–) and `---` to em-dash (—), preserving code blocks and inline code
|
|
42
43
|
- **Wikilink preservation**: Handles Obsidian-style `[[links]]`, `[[links|aliases]]`, `[[page#heading]]`, `[[page#^blockid]]`, and `![[embeds]]`
|
|
43
44
|
- **Explicit line breaks**: Converts soft breaks to hard breaks (`\` + newline) so source line breaks render visually in all Markdown renderers
|
|
44
45
|
|
|
@@ -173,6 +174,27 @@ finally:
|
|
|
173
174
|
set_current_file(None)
|
|
174
175
|
```
|
|
175
176
|
|
|
177
|
+
### Smart Dash Conversion
|
|
178
|
+
|
|
179
|
+
Markdown dash sequences are automatically converted to their Unicode equivalents:
|
|
180
|
+
|
|
181
|
+
- `--` → en-dash (–, U+2013)
|
|
182
|
+
- `---` → em-dash (—, U+2014)
|
|
183
|
+
|
|
184
|
+
**Before:**
|
|
185
|
+
```markdown
|
|
186
|
+
The result---unexpected as it was---changed everything.
|
|
187
|
+
Pages 10--20 of the report.
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**After:**
|
|
191
|
+
```markdown
|
|
192
|
+
The result—unexpected as it was—changed everything.
|
|
193
|
+
Pages 10–20 of the report.
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Dashes are preserved inside fenced code blocks and inline code spans. Thematic breaks (`---`) and frontmatter delimiters are not affected. Sequences of 4+ dashes are left unchanged.
|
|
197
|
+
|
|
176
198
|
### Wikilink Preservation
|
|
177
199
|
|
|
178
200
|
Obsidian-style wikilinks are preserved during formatting:
|
|
@@ -11,6 +11,7 @@ An [mdformat](https://github.com/executablebooks/mdformat) plugin that provides
|
|
|
11
11
|
- **Consecutive blank line normalization**: Limits runs of 3+ empty lines to a maximum of 2
|
|
12
12
|
- **Trailing whitespace removal**: Strips trailing whitespace outside code blocks
|
|
13
13
|
- **Escaped link repair**: Fixes malformed multi-line links from web-clipped content
|
|
14
|
+
- **Smart dash conversion**: Converts `--` to en-dash (–) and `---` to em-dash (—), preserving code blocks and inline code
|
|
14
15
|
- **Wikilink preservation**: Handles Obsidian-style `[[links]]`, `[[links|aliases]]`, `[[page#heading]]`, `[[page#^blockid]]`, and `![[embeds]]`
|
|
15
16
|
- **Explicit line breaks**: Converts soft breaks to hard breaks (`\` + newline) so source line breaks render visually in all Markdown renderers
|
|
16
17
|
|
|
@@ -145,6 +146,27 @@ finally:
|
|
|
145
146
|
set_current_file(None)
|
|
146
147
|
```
|
|
147
148
|
|
|
149
|
+
### Smart Dash Conversion
|
|
150
|
+
|
|
151
|
+
Markdown dash sequences are automatically converted to their Unicode equivalents:
|
|
152
|
+
|
|
153
|
+
- `--` → en-dash (–, U+2013)
|
|
154
|
+
- `---` → em-dash (—, U+2014)
|
|
155
|
+
|
|
156
|
+
**Before:**
|
|
157
|
+
```markdown
|
|
158
|
+
The result---unexpected as it was---changed everything.
|
|
159
|
+
Pages 10--20 of the report.
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**After:**
|
|
163
|
+
```markdown
|
|
164
|
+
The result—unexpected as it was—changed everything.
|
|
165
|
+
Pages 10–20 of the report.
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Dashes are preserved inside fenced code blocks and inline code spans. Thematic breaks (`---`) and frontmatter delimiters are not affected. Sequences of 4+ dashes are left unchanged.
|
|
169
|
+
|
|
148
170
|
### Wikilink Preservation
|
|
149
171
|
|
|
150
172
|
Obsidian-style wikilinks are preserved during formatting:
|
{mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/mdformat_space_control/__init__.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""An mdformat plugin for space control: EditorConfig indentation, tight lists, frontmatter spacing, explicit line breaks, and wikilinks."""
|
|
1
|
+
"""An mdformat plugin for space control: EditorConfig indentation, tight lists, frontmatter spacing, smart dash conversion, explicit line breaks, and wikilinks."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.
|
|
3
|
+
__version__ = "0.4.1"
|
|
4
4
|
|
|
5
5
|
from .config import (
|
|
6
6
|
get_current_file,
|
{mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/mdformat_space_control/plugin.py
RENAMED
|
@@ -4,6 +4,7 @@ Provides custom renderers that combine:
|
|
|
4
4
|
- EditorConfig-based indentation settings
|
|
5
5
|
- Tight list formatting with multi-paragraph awareness
|
|
6
6
|
- Frontmatter spacing normalization
|
|
7
|
+
- Smart dash conversion (-- → en-dash, --- → em-dash)
|
|
7
8
|
- Trailing whitespace removal (outside code blocks)
|
|
8
9
|
- Wikilink preservation ([[link]] and ![[embed]] syntax)
|
|
9
10
|
"""
|
|
@@ -112,12 +113,55 @@ def _get_indent(default_width: int) -> tuple[str, int]:
|
|
|
112
113
|
return (" " * size, size)
|
|
113
114
|
|
|
114
115
|
|
|
116
|
+
def _is_task_list_item(node: RenderTreeNode) -> bool:
|
|
117
|
+
"""Check if a list item is a GFM task list item.
|
|
118
|
+
|
|
119
|
+
When mdformat-gfm is installed, the tasklists markdown-it plugin annotates
|
|
120
|
+
task list items with a 'task-list-item' CSS class.
|
|
121
|
+
"""
|
|
122
|
+
classes = node.attrs.get("class", "")
|
|
123
|
+
return isinstance(classes, str) and "task-list-item" in classes
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _extract_task_checkbox(node: RenderTreeNode) -> str | None:
|
|
127
|
+
"""Extract and remove the task checkbox HTML from a task list item node.
|
|
128
|
+
|
|
129
|
+
The tasklists markdown-it plugin injects an HTML <input> element as the
|
|
130
|
+
first child of the inline token. This function extracts the checked state
|
|
131
|
+
and removes the HTML node so it doesn't appear in the rendered output.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
"x" for checked, " " for unchecked, or None if not a task list item.
|
|
135
|
+
"""
|
|
136
|
+
if not _is_task_list_item(node):
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
paragraph_node = node.children[0]
|
|
140
|
+
inline_node = paragraph_node.children[0]
|
|
141
|
+
if inline_node.type != "inline" or not inline_node.children:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
html_inline_node = inline_node.children[0]
|
|
145
|
+
if 'class="task-list-item-checkbox"' not in getattr(html_inline_node, "content", ""):
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
# Remove the HTML checkbox node before rendering
|
|
149
|
+
inline_node.children.remove(html_inline_node)
|
|
150
|
+
return "x" if 'checked="checked"' in html_inline_node.content else " "
|
|
151
|
+
|
|
152
|
+
|
|
115
153
|
def _render_list_item(node: RenderTreeNode, context: RenderContext) -> str:
|
|
116
154
|
"""Render a list item with appropriate tight/loose formatting.
|
|
117
155
|
|
|
118
156
|
For single-paragraph items in tight lists, use tight formatting.
|
|
119
157
|
For multi-paragraph items, preserve loose formatting.
|
|
158
|
+
|
|
159
|
+
Handles GFM task list items when mdformat-gfm is installed: extracts the
|
|
160
|
+
checkbox state from the HTML token and prepends [x] or [ ] to the output.
|
|
120
161
|
"""
|
|
162
|
+
# Extract task checkbox before rendering (mutates the node tree)
|
|
163
|
+
checkmark = _extract_task_checkbox(node)
|
|
164
|
+
|
|
121
165
|
# Check if this item has multiple paragraphs
|
|
122
166
|
if has_multiple_paragraphs(node):
|
|
123
167
|
# Use loose list formatting for multi-paragraph items
|
|
@@ -136,6 +180,13 @@ def _render_list_item(node: RenderTreeNode, context: RenderContext) -> str:
|
|
|
136
180
|
|
|
137
181
|
if not text.strip():
|
|
138
182
|
return ""
|
|
183
|
+
|
|
184
|
+
if checkmark is not None:
|
|
185
|
+
# Strip leading space entities left after removing the HTML checkbox node
|
|
186
|
+
text = re.sub(r"^( )+", "", text)
|
|
187
|
+
text = text.lstrip()
|
|
188
|
+
return f"[{checkmark}] {text}"
|
|
189
|
+
|
|
139
190
|
return text
|
|
140
191
|
|
|
141
192
|
|
|
@@ -336,6 +387,84 @@ def _strip_trailing_whitespace(text: str) -> str:
|
|
|
336
387
|
return "\n".join(result)
|
|
337
388
|
|
|
338
389
|
|
|
390
|
+
def _convert_dash_sequences(text: str) -> str:
|
|
391
|
+
"""Convert markdown dash sequences to Unicode em-dash and en-dash.
|
|
392
|
+
|
|
393
|
+
Converts ``---`` to em-dash (U+2014) and ``--`` to en-dash (U+2013).
|
|
394
|
+
Preserves dashes inside fenced code blocks, inline code spans,
|
|
395
|
+
HTML comments, and HTML tags.
|
|
396
|
+
Skips lines that are only dashes (thematic breaks, frontmatter delimiters).
|
|
397
|
+
|
|
398
|
+
Em-dash is matched first (longer sequence) to prevent ``---`` from being
|
|
399
|
+
partially converted as en-dash + hyphen.
|
|
400
|
+
"""
|
|
401
|
+
# Patterns for inline code span placeholders
|
|
402
|
+
inline_code_re = re.compile(r"(`+)(.+?)\1")
|
|
403
|
+
|
|
404
|
+
# HTML protection patterns
|
|
405
|
+
html_comment_re = re.compile(r"<!--.*?-->")
|
|
406
|
+
html_tag_re = re.compile(r"<[^>]+>")
|
|
407
|
+
|
|
408
|
+
# Dash conversion patterns: negative lookahead/behind prevent matching
|
|
409
|
+
# 4+ dash sequences (e.g., ---- horizontal rules in some contexts)
|
|
410
|
+
em_dash_re = re.compile(r"(?<!-)---(?!-)")
|
|
411
|
+
en_dash_re = re.compile(r"(?<!-)--(?!-)")
|
|
412
|
+
|
|
413
|
+
# Lines that are only dashes (thematic breaks, frontmatter delimiters)
|
|
414
|
+
only_dashes_re = re.compile(r"^-{2,}\s*$")
|
|
415
|
+
|
|
416
|
+
lines = text.split("\n")
|
|
417
|
+
result = []
|
|
418
|
+
in_code_block = False
|
|
419
|
+
in_html_comment = False
|
|
420
|
+
|
|
421
|
+
for line in lines:
|
|
422
|
+
stripped = line.lstrip()
|
|
423
|
+
if stripped.startswith("```") or stripped.startswith("~~~"):
|
|
424
|
+
in_code_block = not in_code_block
|
|
425
|
+
result.append(line)
|
|
426
|
+
elif in_code_block:
|
|
427
|
+
result.append(line)
|
|
428
|
+
elif in_html_comment:
|
|
429
|
+
# Inside a multi-line HTML comment, skip dash conversion
|
|
430
|
+
if "-->" in line:
|
|
431
|
+
in_html_comment = False
|
|
432
|
+
result.append(line)
|
|
433
|
+
elif only_dashes_re.match(line):
|
|
434
|
+
result.append(line)
|
|
435
|
+
else:
|
|
436
|
+
# Check for multi-line HTML comment opening (no closing on same line)
|
|
437
|
+
if "<!--" in line and "-->" not in line:
|
|
438
|
+
in_html_comment = True
|
|
439
|
+
result.append(line)
|
|
440
|
+
continue
|
|
441
|
+
|
|
442
|
+
# Protect inline code spans and HTML elements with placeholders
|
|
443
|
+
placeholders = []
|
|
444
|
+
|
|
445
|
+
def _save_placeholder(m: re.Match) -> str:
|
|
446
|
+
placeholders.append(m.group(0))
|
|
447
|
+
return f"\x00CODE{len(placeholders) - 1}\x00"
|
|
448
|
+
|
|
449
|
+
protected = inline_code_re.sub(_save_placeholder, line)
|
|
450
|
+
|
|
451
|
+
# Protect HTML comments first (longer matches), then tags
|
|
452
|
+
protected = html_comment_re.sub(_save_placeholder, protected)
|
|
453
|
+
protected = html_tag_re.sub(_save_placeholder, protected)
|
|
454
|
+
|
|
455
|
+
# Convert em-dash first (longer match), then en-dash
|
|
456
|
+
protected = em_dash_re.sub("\u2014", protected)
|
|
457
|
+
protected = en_dash_re.sub("\u2013", protected)
|
|
458
|
+
|
|
459
|
+
# Restore placeholders
|
|
460
|
+
for i, original in enumerate(placeholders):
|
|
461
|
+
protected = protected.replace(f"\x00CODE{i}\x00", original)
|
|
462
|
+
|
|
463
|
+
result.append(protected)
|
|
464
|
+
|
|
465
|
+
return "\n".join(result)
|
|
466
|
+
|
|
467
|
+
|
|
339
468
|
def _normalize_consecutive_blank_lines(text: str) -> str:
|
|
340
469
|
"""Limit consecutive blank lines to a maximum of 2.
|
|
341
470
|
|
|
@@ -375,8 +504,9 @@ def _postprocess_root(text: str, node: RenderTreeNode, context: RenderContext) -
|
|
|
375
504
|
Applies the following transformations in order:
|
|
376
505
|
1. Frontmatter spacing normalization
|
|
377
506
|
2. Escaped link repair
|
|
378
|
-
3.
|
|
379
|
-
4.
|
|
507
|
+
3. Smart dash conversion (-- → en-dash, --- → em-dash)
|
|
508
|
+
4. Consecutive blank line normalization
|
|
509
|
+
5. Trailing whitespace removal
|
|
380
510
|
"""
|
|
381
511
|
# 1. Frontmatter spacing
|
|
382
512
|
text = _normalize_frontmatter_spacing(text)
|
|
@@ -384,10 +514,13 @@ def _postprocess_root(text: str, node: RenderTreeNode, context: RenderContext) -
|
|
|
384
514
|
# 2. Repair escaped links (before trailing whitespace removal)
|
|
385
515
|
text = _repair_escaped_links(text)
|
|
386
516
|
|
|
387
|
-
# 3.
|
|
517
|
+
# 3. Smart dash conversion
|
|
518
|
+
text = _convert_dash_sequences(text)
|
|
519
|
+
|
|
520
|
+
# 4. Limit consecutive blank lines to 2
|
|
388
521
|
text = _normalize_consecutive_blank_lines(text)
|
|
389
522
|
|
|
390
|
-
#
|
|
523
|
+
# 5. Trailing whitespace removal
|
|
391
524
|
text = _strip_trailing_whitespace(text)
|
|
392
525
|
|
|
393
526
|
return text
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mdformat_space_control"
|
|
7
7
|
dynamic = ["version"]
|
|
8
|
-
description = "An mdformat plugin for space control: EditorConfig indentation, tight lists, frontmatter spacing, and wikilinks."
|
|
8
|
+
description = "An mdformat plugin for space control: EditorConfig indentation, tight lists, frontmatter spacing, smart dash conversion, explicit line breaks, and wikilinks."
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Joseph Monaco", email = "joe@selfmotion.net" },
|
|
11
11
|
]
|
|
@@ -22,7 +22,7 @@ classifiers = [
|
|
|
22
22
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
23
|
"Topic :: Text Processing :: Markup :: Markdown",
|
|
24
24
|
]
|
|
25
|
-
keywords = ["mdformat", "markdown", "formatter", "editorconfig", "indentation", "lists", "frontmatter", "wikilink", "obsidian"]
|
|
25
|
+
keywords = ["mdformat", "markdown", "formatter", "editorconfig", "indentation", "lists", "frontmatter", "wikilink", "obsidian", "typography"]
|
|
26
26
|
requires-python = ">=3.10"
|
|
27
27
|
dependencies = [
|
|
28
28
|
"mdformat>=0.7.0",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/dev/default-formatting-style.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mdformat_space_control-0.3.2 → mdformat_space_control-0.4.1}/mdformat_space_control/config.py
RENAMED
|
File without changes
|