mdformat-space-control 0.3.2__tar.gz → 0.4.0__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.0}/CLAUDE.md +1 -1
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/PKG-INFO +25 -3
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/README.md +22 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/mdformat_space_control/__init__.py +2 -2
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/mdformat_space_control/plugin.py +116 -4
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/pyproject.toml +2 -2
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/.editorconfig +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/.gitignore +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/LICENSE +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/dev/CLAUDE.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/dev/contributing.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/dev/default-formatting-style.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/dev/installation-and-usage.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/dev/overview.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/dev/plugins.md +0 -0
- {mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/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.0
|
|
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.0}/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.0"
|
|
4
4
|
|
|
5
5
|
from .config import (
|
|
6
6
|
get_current_file,
|
{mdformat_space_control-0.3.2 → mdformat_space_control-0.4.0}/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,63 @@ 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 and inline code spans.
|
|
395
|
+
Skips lines that are only dashes (thematic breaks, frontmatter delimiters).
|
|
396
|
+
|
|
397
|
+
Em-dash is matched first (longer sequence) to prevent ``---`` from being
|
|
398
|
+
partially converted as en-dash + hyphen.
|
|
399
|
+
"""
|
|
400
|
+
# Patterns for inline code span placeholders
|
|
401
|
+
inline_code_re = re.compile(r"(`+)(.+?)\1")
|
|
402
|
+
|
|
403
|
+
# Dash conversion patterns: negative lookahead/behind prevent matching
|
|
404
|
+
# 4+ dash sequences (e.g., ---- horizontal rules in some contexts)
|
|
405
|
+
em_dash_re = re.compile(r"(?<!-)---(?!-)")
|
|
406
|
+
en_dash_re = re.compile(r"(?<!-)--(?!-)")
|
|
407
|
+
|
|
408
|
+
# Lines that are only dashes (thematic breaks, frontmatter delimiters)
|
|
409
|
+
only_dashes_re = re.compile(r"^-{2,}\s*$")
|
|
410
|
+
|
|
411
|
+
lines = text.split("\n")
|
|
412
|
+
result = []
|
|
413
|
+
in_code_block = False
|
|
414
|
+
|
|
415
|
+
for line in lines:
|
|
416
|
+
stripped = line.lstrip()
|
|
417
|
+
if stripped.startswith("```") or stripped.startswith("~~~"):
|
|
418
|
+
in_code_block = not in_code_block
|
|
419
|
+
result.append(line)
|
|
420
|
+
elif in_code_block:
|
|
421
|
+
result.append(line)
|
|
422
|
+
elif only_dashes_re.match(line):
|
|
423
|
+
result.append(line)
|
|
424
|
+
else:
|
|
425
|
+
# Protect inline code spans with placeholders
|
|
426
|
+
placeholders = []
|
|
427
|
+
|
|
428
|
+
def _save_code_span(m: re.Match) -> str:
|
|
429
|
+
placeholders.append(m.group(0))
|
|
430
|
+
return f"\x00CODE{len(placeholders) - 1}\x00"
|
|
431
|
+
|
|
432
|
+
protected = inline_code_re.sub(_save_code_span, line)
|
|
433
|
+
|
|
434
|
+
# Convert em-dash first (longer match), then en-dash
|
|
435
|
+
protected = em_dash_re.sub("\u2014", protected)
|
|
436
|
+
protected = en_dash_re.sub("\u2013", protected)
|
|
437
|
+
|
|
438
|
+
# Restore inline code spans
|
|
439
|
+
for i, original in enumerate(placeholders):
|
|
440
|
+
protected = protected.replace(f"\x00CODE{i}\x00", original)
|
|
441
|
+
|
|
442
|
+
result.append(protected)
|
|
443
|
+
|
|
444
|
+
return "\n".join(result)
|
|
445
|
+
|
|
446
|
+
|
|
339
447
|
def _normalize_consecutive_blank_lines(text: str) -> str:
|
|
340
448
|
"""Limit consecutive blank lines to a maximum of 2.
|
|
341
449
|
|
|
@@ -375,8 +483,9 @@ def _postprocess_root(text: str, node: RenderTreeNode, context: RenderContext) -
|
|
|
375
483
|
Applies the following transformations in order:
|
|
376
484
|
1. Frontmatter spacing normalization
|
|
377
485
|
2. Escaped link repair
|
|
378
|
-
3.
|
|
379
|
-
4.
|
|
486
|
+
3. Smart dash conversion (-- → en-dash, --- → em-dash)
|
|
487
|
+
4. Consecutive blank line normalization
|
|
488
|
+
5. Trailing whitespace removal
|
|
380
489
|
"""
|
|
381
490
|
# 1. Frontmatter spacing
|
|
382
491
|
text = _normalize_frontmatter_spacing(text)
|
|
@@ -384,10 +493,13 @@ def _postprocess_root(text: str, node: RenderTreeNode, context: RenderContext) -
|
|
|
384
493
|
# 2. Repair escaped links (before trailing whitespace removal)
|
|
385
494
|
text = _repair_escaped_links(text)
|
|
386
495
|
|
|
387
|
-
# 3.
|
|
496
|
+
# 3. Smart dash conversion
|
|
497
|
+
text = _convert_dash_sequences(text)
|
|
498
|
+
|
|
499
|
+
# 4. Limit consecutive blank lines to 2
|
|
388
500
|
text = _normalize_consecutive_blank_lines(text)
|
|
389
501
|
|
|
390
|
-
#
|
|
502
|
+
# 5. Trailing whitespace removal
|
|
391
503
|
text = _strip_trailing_whitespace(text)
|
|
392
504
|
|
|
393
505
|
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.0}/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.0}/mdformat_space_control/config.py
RENAMED
|
File without changes
|