rumdl 0.1.51

A fast Markdown linter written in Rust (Ru(st) MarkDown Linter)
Documentation
# Code Block Tools [preview]

Run external linters and formatters on fenced code blocks in your markdown files.

> **Preview Feature**: This feature is experimental and may change in future versions.

## Overview

Code block tools let you lint and format code embedded in markdown:

- **Lint mode** (`rumdl check`): Run linters on code blocks and report issues
- **Fix mode** (`rumdl check --fix`): Run formatters to auto-fix code blocks

This is similar to [mdsf](https://github.com/hougesen/mdsf) but integrated directly into rumdl.

## Quick Start

Add to your `.rumdl.toml`:

```toml
[code-block-tools]
enabled = true

[code-block-tools.languages]
python = { lint = ["ruff:check"], format = ["ruff:format"] }
shell = { lint = ["shellcheck"], format = ["shfmt"] }
```

Then run:

```bash
# Lint code blocks
rumdl check file.md

# Format code blocks
rumdl check --fix file.md
```

## Configuration

### Basic Options

```toml
[code-block-tools]
enabled = false                              # Master switch (default: false)
normalize-language = "linguist"              # Language alias resolution (see below)
on-error = "warn"                            # Error handling: "fail", "warn", or "skip"
on-missing-language-definition = "ignore"   # See "Missing Language/Tool Handling" below
on-missing-tool-binary = "ignore"            # See "Missing Language/Tool Handling" below
timeout = 30000                              # Tool timeout in milliseconds
```

### Language Configuration

Configure tools per language:

```toml
[code-block-tools.languages]
python = { lint = ["ruff:check"], format = ["ruff:format"] }
javascript = { format = ["prettier"] }
shell = { lint = ["shellcheck"], format = ["shfmt"], on-error = "skip" }
json = { lint = ["jq"], format = ["jq"] }
```

Each language can have:

- `enabled` - Whether tools are enabled for this language (default: `true`)
- `lint` - List of tool IDs to run during `rumdl check`
- `format` - List of tool IDs to run during `rumdl check --fix`
- `on-error` - Override global error handling for this language

### Disabling Tools for a Language

Set `enabled = false` to acknowledge a language without configuring tools.
This is useful in strict mode where you want to declare that a language
is intentionally without lint/format tools:

```toml
[code-block-tools]
enabled = true
on-missing-language-definition = "fail"

[code-block-tools.languages]
python = { lint = ["ruff:check"], format = ["ruff:format"] }
plaintext = { enabled = false }
text = { enabled = false }
```

With this configuration, `plaintext` and `text` code blocks are silently skipped without triggering strict mode errors, while unconfigured languages still produce errors.

### Language Aliases

Map language tags to canonical names:

```toml
[code-block-tools.language-aliases]
py = "python"
sh = "shell"
bash = "shell"
```

With `normalize-language = "linguist"` (default), common aliases are resolved automatically using GitHub's Linguist data. Set to `"exact"` to disable alias resolution.

## Built-in Tools

rumdl includes definitions for common tools:

| Tool ID           | Language   | Type   | Command                                |
| ----------------- | ---------- | ------ | -------------------------------------- |
| `ruff:check`      | Python     | Lint   | `ruff check --output-format=concise -` |
| `ruff:format`     | Python     | Format | `ruff format -`                        |
| `black`           | Python     | Format | `black --quiet -`                      |
| `isort`           | Python     | Format | `isort -`                              |
| `shellcheck`      | Shell      | Lint   | `shellcheck -`                         |
| `shfmt`           | Shell      | Format | `shfmt`                                |
| `beautysh`        | Shell      | Both   | `beautysh -`                           |
| `prettier`        | Multi      | Format | `prettier --stdin-filepath=_.EXT`      |
| `eslint`          | JS/TS      | Lint   | `eslint --stdin --format=compact`      |
| `oxfmt`           | JS/TS/JSON | Format | `oxfmt --stdin-filepath=_.EXT`         |
| `rustfmt`         | Rust       | Format | `rustfmt`                              |
| `gofmt`           | Go         | Format | `gofmt`                                |
| `goimports`       | Go         | Format | `goimports`                            |
| `clang-format`    | C/C++      | Format | `clang-format`                         |
| `stylua`          | Lua        | Format | `stylua -`                             |
| `taplo`           | TOML       | Format | `taplo format -`                       |
| `tombi`           | TOML       | Lint   | `tombi lint -`                         |
| `tombi:lint`      | TOML       | Lint   | `tombi lint -`                         |
| `tombi:format`    | TOML       | Format | `tombi format -`                       |
| `yamlfmt`         | YAML       | Format | `yamlfmt -`                            |
| `jq`              | JSON       | Both   | `jq .`                                 |
| `sql-formatter`   | SQL        | Format | `sql-formatter`                        |
| `pg_format`       | SQL        | Format | `pg_format`                            |
| `nixfmt`          | Nix        | Format | `nixfmt`                               |
| `terraform-fmt`   | Terraform  | Format | `terraform fmt -`                      |
| `mix:format`      | Elixir     | Format | `mix format -`                         |
| `dart-format`     | Dart       | Format | `dart format`                          |
| `ktfmt`           | Kotlin     | Format | `ktfmt --stdin`                        |
| `scalafmt`        | Scala      | Format | `scalafmt --stdin`                     |
| `ormolu`          | Haskell    | Format | `ormolu`                               |
| `fourmolu`        | Haskell    | Format | `fourmolu`                             |
| `latexindent`     | LaTeX      | Format | `latexindent`                          |
| `markdownlint`    | Markdown   | Lint   | `markdownlint --stdin`                 |
| `typos`           | Multi      | Lint   | `typos --format=brief -`               |
| `cljfmt`          | Clojure    | Format | `cljfmt fix -`                         |
| `rubocop`         | Ruby       | Both   | `rubocop -a` / `rubocop`               |
| `djlint`          | Jinja/HTML | Both   | `djlint -` / `djlint - --reformat`     |
| `rumdl`           | Markdown   | Lint   | (built-in, see below)                  |

**Note**: Tools must be installed separately. rumdl does not install them for you.

### Embedded Markdown Linting

The special `rumdl` tool enables linting of markdown content inside fenced code blocks:

```toml
[code-block-tools]
enabled = true

[code-block-tools.languages.markdown]
lint = ["rumdl"]
```

This runs rumdl's own lint rules on markdown code blocks, useful for documentation that includes markdown examples. Unlike external tools, `rumdl` is built-in and requires no additional installation.

**Note**: This feature is opt-in. Without this configuration, markdown code blocks are not linted, allowing you to show intentionally "broken" markdown examples in documentation.

## Custom Tools

Define custom tools in your config:

```toml
[code-block-tools.tools.my-formatter]
command = ["my-tool", "--format", "-"]
stdin = true
stdout = true
```

Then use in language config:

```toml
[code-block-tools.languages]
mylang = { format = ["my-formatter"] }
```

## Error Handling

The `on-error` option controls behavior when tools fail:

| Value    | Behavior                           |
| -------- | ---------------------------------- |
| `"fail"` | Stop processing, return error      |
| `"warn"` | Log warning, continue processing   |
| `"skip"` | Silently skip, continue processing |

Set globally or per-language:

```toml
[code-block-tools]
on-error = "warn"  # Global default

[code-block-tools.languages]
shell = { lint = ["shellcheck"], on-error = "skip" }  # Override for shell
```

## Missing Language/Tool Handling

Two additional options control behavior when configuration or tools are missing:

### `on-missing-language-definition`

Controls what happens when a code block has a language tag, but no tools are configured for that language in the current mode (`lint` for `rumdl check`, `format` for `rumdl check --fix`).

| Value         | Behavior                                                     |
| ------------- | ------------------------------------------------------------ |
| `"ignore"`    | Silently skip the block (default, backward compatible)       |
| `"fail"`      | Record an error, continue processing, exit non-zero at end   |
| `"fail-fast"` | Stop immediately, exit non-zero                              |

### `on-missing-tool-binary`

Controls what happens when a configured tool's binary cannot be found in PATH.

| Value         | Behavior                                                     |
| ------------- | ------------------------------------------------------------ |
| `"ignore"`    | Silently skip the tool (default, backward compatible)        |
| `"fail"`      | Record an error, continue processing, exit non-zero at end   |
| `"fail-fast"` | Stop immediately, exit non-zero                              |

### Example: Strict Mode

For CI environments where you want to ensure all code blocks are processed:

```toml
[code-block-tools]
enabled = true
on-missing-language-definition = "fail"
on-missing-tool-binary = "fail-fast"

[code-block-tools.languages]
python = { lint = ["ruff:check"], format = ["ruff:format"] }
shell = { lint = ["shellcheck"], format = ["shfmt"] }
plaintext = { enabled = false }
```

With this configuration:

- A Python code block without ruff installed will fail immediately
- A `plaintext` code block is silently skipped (acknowledged but no tools needed)
- A JavaScript code block (not configured at all) will record an error but continue
- The final exit code will be non-zero if any errors were recorded

## How It Works

1. **Extract**: Parse markdown to find fenced code blocks with language tags
2. **Resolve**: Map language tag to canonical name (e.g., `py``python`)
3. **Lookup**: Find configured tools for that language
4. **Execute**: Run tools via stdin/stdout
5. **Report/Apply**: Show lint diagnostics or apply formatted output

### Line Number Mapping

Tool output references lines within the code block. rumdl maps these to the actual markdown file line numbers so diagnostics point to the correct location.

### Indented Code Blocks

For code blocks inside lists or blockquotes, rumdl:

1. Strips the indentation before sending to tools
2. Re-applies indentation to formatted output

## Examples

### Python with Ruff

```toml
[code-block-tools]
enabled = true

[code-block-tools.languages]
python = { lint = ["ruff:check"], format = ["ruff:format"] }
```

### Multi-language Project

```toml
[code-block-tools]
enabled = true
on-error = "warn"

[code-block-tools.languages]
python = { lint = ["ruff:check"], format = ["ruff:format"] }
javascript = { lint = ["eslint"], format = ["prettier"] }
typescript = { lint = ["eslint"], format = ["prettier"] }
shell = { lint = ["shellcheck"], format = ["shfmt"] }
json = { lint = ["jq"], format = ["jq"] }
yaml = { format = ["yamlfmt"] }
```

### Formatting Only (No Linting)

```toml
[code-block-tools]
enabled = true

[code-block-tools.languages]
python = { format = ["black"] }
rust = { format = ["rustfmt"] }
go = { format = ["gofmt"] }
```

## Troubleshooting

### Tool not found

Ensure the tool is installed and in your PATH:

```bash
which ruff  # Should show path
ruff --version  # Should show version
```

### No output from tool

Check the tool works with stdin:

```bash
echo 'x=1' | ruff check --output-format=concise -
```

### Timeout errors

Increase the timeout for slow tools:

```toml
[code-block-tools]
timeout = 60000  # 60 seconds
```

### Wrong language detected

Use explicit aliases:

```toml
[code-block-tools.language-aliases]
py3 = "python"
zsh = "shell"
```

## Comparison with mdsf

| Feature          | rumdl          | mdsf       |
| ---------------- | -------------- | ---------- |
| Built-in tools   | 31             | 339        |
| Custom tools     | Yes            | Yes        |
| Linting          | Yes            | No         |
| Formatting       | Yes            | Yes        |
| Language aliases | Yes (Linguist) | Yes        |
| Integration      | Part of rumdl  | Standalone |

rumdl focuses on common tools with the ability to add custom ones. mdsf has broader tool coverage but only formats (no linting).