Skip to main content

mdstruct

Split and recombine markdown files by ATX headers.

Similar Projects

  • Inspired by gron - make markdown greppable by splitting into hierarchical files, then recombine them.
  • mdsplit - Python CLI for splitting markdown files (focuses on flat output structure), but has no easy reversible join.

Installation

uv pip install mdstruct
# or
pip install mdstruct

Tip: For convenience, consider adding an alias to your shell:

# Add to ~/.bashrc, ~/.zshrc, or equivalent
alias md='mdstruct'

# Then use:
md split foo
md join foo

Usage

Auto-detection (recommended)

Use mdstruct directly to automatically detect whether to split or join based on what exists:

# Auto-detects: splits if foo.md exists, joins if foo/ exists
mdstruct foo

# Works with explicit extensions too
mdstruct foo.md    # splits foo.md → foo/
mdstruct foo/      # joins foo/ → foo.md

# With options
mdstruct foo --level 3    # split with custom level
mdstruct foo --force      # skip overwrite confirmation

Tip: Create an alias for even shorter commands:

alias md='mdstruct'
# Then use: md foo

Split markdown file

# Auto-detect and split (uses smart default level)
mdstruct foo

# Explicit split command
mdstruct split foo

# Split up to H3 level
mdstruct split foo --level 3

Example:

example.md:

# First Section

Content before subsections.

## Subsection A

Content A.

## Subsection B

Content B.

After mdstruct example (or mdstruct split example):

example/
├── first-section/
│   ├── README.md         # "# First Section\n\nContent before subsections."
│   ├── subsection-a.md   # "# Subsection A\n\nContent A."
│   └── subsection-b.md   # "# Subsection B\n\nContent B."

Alphanumeric Prefixes with Fractional Indexing:

Prefixes are only added when sections are not in alphabetical order or when there are duplicates.

Automatic Indexing (during split):

  • Uses rescaling based on total item count
  • ≤36 items: 1-char indexes (0, 1, ..., 9, a, ..., z)
  • 37-702 items: 2-char indexes (aa, ab, ..., zz)
  • 703+ items: 3-char indexes (aaa, aab, ..., zzz)

Manual Insertion (between splits):

  • Add fractional characters after the index to insert between items
  • Between 0 and 1: create 0m.new-section/ (index=0, fraction=m)
  • Between z and aa: create z5.new-section/ (index=z, fraction=5)
  • Between aa and ab: create aam.new-section/ (index=aa, fraction=m)
  • Can nest deeper: 0mm., 0mmm., etc. for very tight spaces

How it works:

  • Format: <index><fraction>.slug where fraction is optional
  • During join, detects most common prefix length to determine index width
  • Everything beyond that length is treated as manual fractional insertion
  • Preserves lexicographic sorting automatically

Examples:

# Alphabetical headers → no prefixes needed
apple/
banana/
cherry/

# Non-alphabetical (3 items) → single-char indexes
0.zebra/
1.apple/
2.banana/

# Manual insertion between 0 and 1
0.zebra/
03.manually-inserted/    # index=0, fraction=3
1.apple/
2.banana/

# Many items (50) → two-char indexes for all
00.first/
...
03.second/    # index=3, fraction=0
...
bx.fiftieth/

# Rebalancing: just run join + split
mdstruct join foo
mdstruct split foo
# Removes all fractional insertions, resets to clean sequential indexes

Join split files

# Auto-detect and join (checks if foo/ exists)
mdstruct foo

# Explicit join command
mdstruct join foo

# Join with explicit output
mdstruct join foo --output combined.md

# Force overwrite without confirmation
mdstruct foo --force

Manual insertion workflow

After splitting, you can manually insert new sections between existing ones:

# Split into directories
mdstruct split doc.md

# Filesystem shows:
# doc/
# ├── 0.intro/
# ├── 1.main-content/
# └── 2.conclusion/

# Manually create new section between intro and main-content
mkdir doc/0m.background
echo "# Background" > doc/0m.background/README.md
echo "Historical context..." >> doc/0m.background/README.md

# Join back - manual insertion is preserved in order
mdstruct join doc -o doc.md

# Result preserves order:
# - Intro
# - Background (inserted)
# - Main Content
# - Conclusion

Development

Setup

# Clone repository
git clone https://github.com/DJRHails/mdstruct
cd mdstruct

# Install dependencies with uv
uv pip install -e ".[dev]"

Testing

# Run tests
uv run pytest -v

# Run tests with coverage
uv run pytest --cov=mdstruct --cov-report=term-missing

# Run linting
uv run ruff check .

# Format code
uv run ruff format .

Version Management

This project uses bumpver for version management:

# Show current version
uv run bumpver show

# Bump patch version (1.0.0 → 1.0.1)
uv run bumpver update --patch

# Bump minor version (1.0.0 → 1.1.0)
uv run bumpver update --minor

# Bump major version (1.0.0 → 2.0.0)
uv run bumpver update --major

# Dry run to preview changes
uv run bumpver update --patch --dry

Publishing

# Build package
uv build

# Publish to PyPI
uv publish

License

MIT

Metadata

Release files for mdstruct 1.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mdstruct 1.3.0
File Size Uploaded Provenance Actions
mdstruct-1.3.0.tar.gz 52.2 kB Without provenance Details for mdstruct-1.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mdstruct 1.3.0
File Interpreter ABI Platform Actions
mdstruct-1.3.0-py3-none-any.whl Python 3 none any Details for mdstruct-1.3.0-py3-none-any.whl

Total release size: 64.1 kB

Release files / mdstruct-1.3.0.tar.gz

Download URL mdstruct-1.3.0.tar.gz
Size 52.2 kB
Tags Source
SHA-256 checksum
How to use checksums
9fa643cfa164b40b0d6ce35b953f96ebb6f7f66750e6e32c2c1df2aac93b4dda
BLAKE2b-256 checksum
How to use checksums
24cb5ec0a252267ab95cb4899455018a0e8f91637f118d117fafaea4aaedc009
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / mdstruct-1.3.0-py3-none-any.whl

Download URL mdstruct-1.3.0-py3-none-any.whl
Size 12.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
06afeb2e718b550c9ee0bc1cf19978aec064d7b43096819039fc7cb9e3838096
BLAKE2b-256 checksum
How to use checksums
4f3f5052e3c983e5560a1a20a314875a0a8a3bea93a05634b929c8865c9995c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for mdstruct 1.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.3.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page