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
0and1: create0m.new-section/(index=0, fraction=m) - Between
zandaa: createz5.new-section/(index=z, fraction=5) - Between
aaandab: createaam.new-section/(index=aa, fraction=m) - Can nest deeper:
0mm.,0mmm., etc. for very tight spaces
How it works:
- Format:
<index><fraction>.slugwhere 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.