Thanks for your interest in md2any! This document covers how to report issues, propose changes, and get a development environment running locally.
Before opening an issue, please check the existing issues to see whether the bug or feature is already tracked.
A good bug report includes:
- The exact
md2any --versionoutput. - The platform and architecture (
uname -aon Linux/macOS, OS version on Windows). - A minimal markdown sample that reproduces the issue — five lines is better than five hundred.
- The exact command line you ran.
- The expected output vs what actually happened.
For renderer bugs (PPTX/ODP/DOCX/ODT showing wrong layout), please attach a screenshot or the failing output file if you can — visual bugs are hard to describe in text.
You need a stable Rust toolchain. Rustup is the easiest way to get one:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shThen:
git clone https://github.com/javaperformance/md2any
cd md2any
cargo build --release
cargo testThe repository works the same on Linux, macOS, and Windows — there are no platform-specific dependencies.
cargo build --release # build the binary
cargo test --release # run all tests
cargo test --release -- --nocapture # tests with stdout
cargo test --release -- --ignored # run opt-in integration tests too
cargo clippy --all-targets -- -D warnings # lint
cargo fmt # format
cargo doc --no-deps --open # build + open API docs
./target/release/md2any examples/demo.md
./target/release/md2any examples/demo.md -o demo.pdf
./target/release/md2any examples/demo.md --serve # localhost previewThe --ignored set currently contains the remote-image cache stress
harness (tests/integration/cache_stress.sh). It needs Python 3 and curl
on $PATH and a free local port; the test reports exit 77 (skip) if
prerequisites are missing, so it's safe to run anywhere. See
tests/integration/README.md for details and how to add new scenarios.
scripts/build-all.sh builds release binaries for every supported target.
Set up the targets once:
rustup target add x86_64-unknown-linux-musl
rustup target add aarch64-unknown-linux-gnu
rustup target add x86_64-pc-windows-msvc
rustup target add x86_64-apple-darwin aarch64-apple-darwinThen:
./scripts/build-all.sh # all targets
./scripts/build-all.sh --linux # just Linux variantsOn GitHub, the release.yml workflow
publishes binaries for every push of a v* tag.
cargo fmtis enforced — CI fails on diffs.cargo clippyis treated as warnings → errors. Don't silence lints without an explanatory comment.- Public items get rustdoc comments (
///) and types/modules get a top-level//!description. Internal helpers don't need docs but should have names that tell you why they exist. - Comments explain why, not what. The code already says what it does.
- Snapshot tests live in
tests/snapshots/*.mdwith golden filestests/snapshots/*.snap. They cover the parser → paginator pipeline. - To accept an intentional change after touching the parser or paginator:
UPDATE_SNAPSHOTS=1 cargo test. - Renderer tests are not yet exhaustive — verifying a real PPTX / DOCX in PowerPoint or LibreOffice is currently a manual step. Patches that add golden output bytes are welcome.
The IR (src/ir.rs) is renderer-agnostic by design. To add a sixth output
format:
- Add a new variant to
enum Formatinsrc/main.rs. - Wire
from_extension/from_name/ext/name. - Write
pub fn write(slides, theme, ...) -> Result<Vec<u8>>in a newsrc/<format>.rs. - Re-export the module in
src/lib.rs. - Hook the writer into the match in
main.rs.
The existing five writers (pptx.rs, odp.rs, pdf.rs, docx.rs, odt.rs)
are independent of each other — copying the closest one as a template and
modifying is the recommended approach.
- Keep PRs focused. One bug fix or one feature per PR.
- Update
CHANGELOG.mdunder[Unreleased]with what changed and why. - New features need at least one snapshot test or an example markdown file.
- The CI workflow runs
cargo build,cargo test, and a smoke test on every push.
This project follows the Contributor Covenant Code of Conduct. By participating, you agree to abide by its terms.
By contributing, you agree that your contributions will be dual-licensed under the MIT and Apache-2.0 licenses, as described in the README.