Python-Markdown extension for better list handling
Project description
mdx_better_lists
A Python-Markdown extension for better list handling, providing more intuitive list behavior and formatting with fine-grained control over list rendering. Created with Test-Driven Development (TDD) principles to ensure reliability and maintainability.
Features
- Configurable nested indentation - Control how many spaces are required for nested lists (default: 2)
- Marker-based list separation - Automatically separate lists when marker types change (-, *, +)
- Blank line list separation - Separate unordered lists when blank lines appear between items
- Loose list control - Control paragraph wrapping in ordered lists with blank lines
- Number preservation - Optionally preserve exact list numbers from markdown source
- Always start at one - Force ordered lists to always start at 1
- Paragraph-list splitting - Optionally split paragraphs and lists without requiring blank lines between them
Installation
pip install mdx_better_lists
Usage
Basic Usage
from markdown import markdown
text = """
- Item 1
- Item 2
- Item 3
"""
html = markdown(text, extensions=['mdx_better_lists'])
With Configuration
from markdown import markdown
text = """
1. First
2. Second
2. Another second
"""
html = markdown(text, extensions=['mdx_better_lists'],
extension_configs={'mdx_better_lists': {
'preserve_numbers': True
}})
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
nested_indent |
int | 2 | Number of spaces required for nested list indentation |
marker_separation |
bool | True | Separate lists when marker types (-, *, +) differ |
unordered_list_separation |
bool | True | Separate unordered lists when blank lines appear between items |
ordered_list_loose |
bool | True | Wrap ordered list items in <p> tags when blank lines separate them |
preserve_numbers |
bool | False | Preserve exact list numbers from markdown (use value attribute) |
always_start_at_one |
bool | False | Force all ordered lists to start at 1 |
split_paragraph_lists |
bool | False | Split paragraphs and lists when they appear without blank lines between them |
Configuration Details
nested_indent (default: 2)
Controls how many spaces are required for a list item to be considered nested.
# With nested_indent=2 (default)
- Parent
- Nested # 2 spaces = nested
# With nested_indent=4
- Parent
- Nested # 4 spaces = nested
- Not nested # 2 spaces = not nested
marker_separation (default: True)
When enabled, lists with different markers (-, *, +) are separated into different <ul> elements.
# With marker_separation=True (default)
- Item with dash
+ Item with plus # Creates a new <ul>
# Output: Two separate <ul> elements
# With marker_separation=False
- Item with dash
+ Item with plus # Same <ul>
# Output: Single <ul> element
unordered_list_separation (default: True)
When enabled, unordered lists are separated into different <ul> elements when blank lines appear.
# With unordered_list_separation=True (default)
- First
- Second # Creates a new <ul>
# Output: Two separate <ul> elements
# With unordered_list_separation=False
- First
- Second # Same <ul>
# Output: Single <ul> element
ordered_list_loose (default: True)
When enabled, ordered list items are wrapped in <p> tags when blank lines separate them.
# With ordered_list_loose=True (default)
1. First
2. Second
# Output:
<ol>
<li><p>First</p></li>
<li><p>Second</p></li>
</ol>
# With ordered_list_loose=False
1. First
2. Second
# Output:
<ol>
<li>First</li>
<li>Second</li>
</ol>
preserve_numbers (default: False)
When enabled, preserves exact list numbers from the markdown source using the value attribute.
# With preserve_numbers=True
1. First
2. Second
2. Another second
3. Third
# Output:
<ol>
<li value="1">First</li>
<li value="2">Second</li>
<li value="2">Another second</li>
<li value="3">Third</li>
</ol>
always_start_at_one (default: False)
When enabled, forces all ordered lists to start at 1, ignoring the starting number in markdown.
# With always_start_at_one=True
5. Fifth
6. Sixth
# Output:
<ol>
<li>Fifth</li>
<li>Sixth</li>
</ol>
# (renders as 1, 2 instead of 5, 6)
# With always_start_at_one=False (default)
5. Fifth
6. Sixth
# Output:
<ol start="5">
<li>Fifth</li>
<li>Sixth</li>
</ol>
split_paragraph_lists (default: False)
When enabled, automatically splits paragraphs and lists that appear without blank lines between them into separate blocks. This allows lists to be recognized immediately after paragraphs without requiring a blank line separator.
# With split_paragraph_lists=False (default)
This is a paragraph before the list.
- First item
- Second item
# Output:
<p>This is a paragraph before the list.
- First item
- Second item</p>
# (list markers are treated as plain text)
# With split_paragraph_lists=True
This is a paragraph before the list.
- First item
- Second item
# Output:
<p>This is a paragraph before the list.</p>
<ul>
<li>First item</li>
<li>Second item</li>
</ul>
# (paragraph and list are separated)
This also works with ordered lists:
# With split_paragraph_lists=True
Introduction paragraph.
1. First point
2. Second point
# Output:
<p>Introduction paragraph.</p>
<ol>
<li>First point</li>
<li>Second point</li>
</ol>
Note: This feature only operates at the top level. List markers inside list items that are not properly indented will remain as text (standard Markdown behavior).
Examples
Example 1: Marker Separation
from markdown import markdown
text = """
- Item with dash
- Another dash
+ Item with plus
+ Another plus
"""
html = markdown(text, extensions=['mdx_better_lists'])
# Output: Three separate <ul> elements (blank line + marker change)
Example 2: Nested Lists with Custom Indentation
from markdown import markdown
text = """
- Parent
- Nested (4 spaces)
- Deeply nested (8 spaces)
"""
html = markdown(text, extensions=['mdx_better_lists'],
extension_configs={'mdx_better_lists': {
'nested_indent': 4
}})
Example 3: Preserving List Numbers
from markdown import markdown
text = """
1. Introduction
1. Background
1. Methods
"""
html = markdown(text, extensions=['mdx_better_lists'],
extension_configs={'mdx_better_lists': {
'preserve_numbers': True
}})
# Each item gets value="1"
Migration from mdx_truly_sane_lists
# mdx_truly_sane_lists
markdown(text, extensions=['mdx_truly_sane_lists'],
extension_configs={'mdx_truly_sane_lists': {
'truly_sane': True # Default
'nested_indent': 2 # Default
}})
# Equivalent in mdx_better_lists (this is the default)
markdown(text, extensions=['mdx_better_lists'],
extension_configs={'mdx_better_lists': {
'marker_separation': True, # Default
'unordered_list_separation': True, # Default
'ordered_list_loose': True # Default
'nested_indent': 2 # Default
}})
Note: mdx_better_lists does not support loose list behavior (paragraph wrapping) for unordered lists. Unordered lists always remain tight, even when both marker_separation and unordered_list_separation are set to False.
Development
This project follows Test-Driven Development (TDD) principles.
Running Tests
pytest tests/
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Inspired by mdx_truly_sane_lists
- Built on Python-Markdown
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mdx_better_lists-1.1.0.tar.gz.
File metadata
- Download URL: mdx_better_lists-1.1.0.tar.gz
- Upload date:
- Size: 23.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02ca2999a5f558bbd56c392b86381cf993497870cd7f11320c990907d1cc3730
|
|
| MD5 |
2a1d4588c01808d3b14eef07138bf417
|
|
| BLAKE2b-256 |
0af50b8880572f39e5b2e4fc2a7c1a7adcf70e81fbaabdb35943ed488bc09f71
|
Provenance
The following attestation bundles were made for mdx_better_lists-1.1.0.tar.gz:
Publisher:
release.yml on JimmyOei/mdx_better_lists
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mdx_better_lists-1.1.0.tar.gz -
Subject digest:
02ca2999a5f558bbd56c392b86381cf993497870cd7f11320c990907d1cc3730 - Sigstore transparency entry: 962632396
- Sigstore integration time:
-
Permalink:
JimmyOei/mdx_better_lists@37df15dd3706f4e86dafdf27b74685747d4ef7bc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/JimmyOei
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@37df15dd3706f4e86dafdf27b74685747d4ef7bc -
Trigger Event:
push
-
Statement type:
File details
Details for the file mdx_better_lists-1.1.0-py3-none-any.whl.
File metadata
- Download URL: mdx_better_lists-1.1.0-py3-none-any.whl
- Upload date:
- Size: 8.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
606f9dab04a1b2fb1ccb525753a423c30b17024e17f49e5314e81504f891d9f6
|
|
| MD5 |
11533fb463775ab4cd8640e8395cdfd8
|
|
| BLAKE2b-256 |
c70dfcd2020b3d518d28c011677aae7b8065070a385b6f908507a2bf599b68d8
|
Provenance
The following attestation bundles were made for mdx_better_lists-1.1.0-py3-none-any.whl:
Publisher:
release.yml on JimmyOei/mdx_better_lists
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mdx_better_lists-1.1.0-py3-none-any.whl -
Subject digest:
606f9dab04a1b2fb1ccb525753a423c30b17024e17f49e5314e81504f891d9f6 - Sigstore transparency entry: 962632400
- Sigstore integration time:
-
Permalink:
JimmyOei/mdx_better_lists@37df15dd3706f4e86dafdf27b74685747d4ef7bc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/JimmyOei
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@37df15dd3706f4e86dafdf27b74685747d4ef7bc -
Trigger Event:
push
-
Statement type: