Skip to content

JimmyOei/mdx_better_lists

Repository files navigation

mdx_better_lists

PyPI Python Versions License CI codecov Code style: black

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

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages