Skip to content
Oeiuwq Faith Blog OpenSource Porfolio

drupol/markdown-code-runner

Execute and optionally rewrite code blocks in Markdown files based on external commands

drupol/markdown-code-runner.json
{
"createdAt": "2025-04-08T15:08:46Z",
"defaultBranch": "main",
"description": "Execute and optionally rewrite code blocks in Markdown files based on external commands",
"fullName": "drupol/markdown-code-runner",
"homepage": null,
"language": "Rust",
"name": "markdown-code-runner",
"pushedAt": "2026-05-15T13:25:05Z",
"stargazersCount": 25,
"topics": [],
"updatedAt": "2026-05-13T17:45:43Z",
"url": "https://github.com/drupol/markdown-code-runner"
}

![GitHub stars][GitHub stars] [![Crates.io Version][Crates.io Version]][mdcr crates] [![Crates.io License][Crates.io License]][mdcr crates] [![Donate!][Donate!]][sponsor link]

A configurable command-line tool written in Rust that parses Markdown files, extracts fenced code blocks, executes them via external arbitrary commands, and optionally replaces the content of the blocks with the command output.

Useful for:

  • Validating Markdown tutorials with executable code
  • Auto-updating examples and documentation
  • Code formatting code blocks (e.g. black, nixfmt, shfmt, php-cs-fixer, etc)
  • Linting code blocks (e.g. ruff, php -l, prettier, etc)
  • Clean configuration via a TOML file
  • Fast and dependency-free thanks to Rust
  • Scans fenced Markdown code blocks by language
  • Configurable per-language command execution
  • Optional block replacement based on command output
  • --check mode for CI/linting use cases
  • Markdown code blocks can opt-out using the mdcr-skip flag
  • Placeholder support ({file}, {lang}, etc.)

You can install the binary with Cargo:

Terminal window
cargo install mdcr

Available via the [markdown-code-runner package], the binary is called mdcr.

Clone the repository and run in the sourcecode folder:

Terminal window
cargo build --release

The binary will be in target/release/mdcr.

You can use the package from this repository with Nix. If you have Nix installed, you can run the tool directly:

Terminal window
nix run github:drupol/markdown-code-runner
Terminal window
mdcr --config config.toml path/to/file.md
Terminal window
mdcr --config config.toml --check path/to/file.md

This will:

  • Execute configured commands for each code block
  • Fail with exit code 1 if output differs from original (like a linter)
  • Do not modify files

The configuration file defines which commands to run for which Markdown block languages.

Save this file as config.toml:

[presets.ruff-format]
languages = ["python", "py"]
command = ["ruff", "format", "-"]
[presets.nixfmt]
language = "nix"
command = ["nixfmt"]
[presets.php]
language = "php"
# php-cs-fixer does not support STDIN, so we use a temporary file
command = [
"sh",
"-c",
"php-cs-fixer fix -q --rules=@PSR12 {file}; cat {file}"
]
input_mode = "file"
[presets.rust]
language = "rust"
command = ["rustfmt"]
[presets.typstyle]
language = "typst"
command = ["typstyle"]
[presets.latex]
language = "latex"
command = ["tex-fmt", "--stdin"]

Each preset supports an optional input_mode, which defines how the code block is passed to the command:

  • stdin (default): The code is passed via standard input (STDIN)
  • file: The code is written to a temporary file and its path is passed, the temporary file is deleted immediately after execution

Each preset also supports an optional output_mode, which defines how the command output is used:

  • replace (default): Replace the code block content with the command’s output
  • check: Check the command’s exit code, if it is different from 0, the command failed, and the tool will return a non-zero exit code

If not specified, both input_mode and output_mode default to stdin and replace, respectively.

The tool scans for fenced code blocks like:

```python
print( "hello" )
```

It will execute all matching commands whose language is python.

To exclude a block from processing, add mdcr-skip after the language:

```python mdcr-skip
print("don't touch this")
```

You can use placeholders in the command field:

PlaceholderDescription
{file}Path to the temporary code file
{lang}Language of the block (python)
{suffix}File suffix (e.g. .py)
{tmpdir}Temporary directory path
  • Blocks with unsupported languages are skipped with a warning.
  • {file} placeholder is only available in input_mode: "file" mode.

Recommended usage in continuous integration:

Terminal window
mdcr --config config.toml --check docs/

This runs all configured checks and returns a non-zero exit code if:

  • Output differs from the original
  • A command fails

The --check mode will not modify any files.

The CLI option --log allows you to control the verbosity and destination of log messages emitted during execution.

Terminal window
mdcr --config config.toml --log debug path/to/file.md

The logging system uses standard log levels, from most verbose to least:

LevelDescription
traceHighly detailed, useful for debugging internal issues
debugGeneral debugging information
infoInformational messages about execution progress
warnNon-critical issues that deserve attention
errorCritical problems encountered during execution

By default, if no --log option is provided, the logging level defaults to warn.

[GitHub stars] !: https://img.shields.io/github/stars/drupol/markdown-code-runner.svg?style=flat-square [Donate!] !: https://img.shields.io/badge/Sponsor-Github-brightgreen.svg?style=flat-square [sponsor link] !: https://github.com/sponsors/drupol [Crates.io License] !: https://img.shields.io/crates/l/mdcr?style=flat-square [Crates.io Version] !: https://img.shields.io/crates/v/mdcr?style=flat-square [mdcr crates] !: https://crates.io/crates/mdcr [markdown-code-runner package] !: https://search.nixos.org/packages?channel=unstable&from=0&size=50&sort=relevance&type=packages&query=markdown-code-runner