# rumdl - A high-performance Markdown linter, written in Rust
![rumdl Logo](https://raw.githubusercontent.com/rvben/rumdl/main/assets/logo.png) [![Build Status](https://img.shields.io/github/actions/workflow/status/rvben/rumdl/ci.yml)](https://github.com/rvben/rumdl/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Crates.io](https://img.shields.io/crates/v/rumdl)](https://crates.io/crates/rumdl) [![PyPI](https://img.shields.io/pypi/v/rumdl)](https://pypi.org/project/rumdl/) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/rvben/rumdl)](https://github.com/rvben/rumdl/releases/latest) [![GitHub stars](https://img.shields.io/github/stars/rvben/rumdl)](https://github.com/rvben/rumdl/stargazers) [![Discord](https://img.shields.io/badge/Discord-Join%20us-5865F2?logo=discord&logoColor=white)](https://discord.gg/ADTJFSFUyn) [![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-pink?logo=github-sponsors)](https://github.com/sponsors/rvben) ## A modern Markdown linter and formatter, built for speed with Rust | [**Docs**](https://rumdl.dev) | [**Rules**](https://rumdl.dev/rules) | [**Configuration**](https://rumdl.dev/global-settings) | [**Markdown Flavors**](https://rumdl.dev/flavors) | [**vs markdownlint**](https://rumdl.dev/markdownlint-comparison) |
## Quick Start ```bash # Install using Cargo cargo install rumdl # Lint Markdown files in the current directory rumdl check . # Format files (exits 0 on success, even if unfixable violations remain) rumdl fmt . # Auto-fix and report unfixable violations (exits 0 if all fixed, 1 if violations remain) rumdl check --fix . # Create a default configuration file rumdl init ``` ## Overview rumdl is a high-performance Markdown linter and formatter that helps ensure consistency and best practices in your Markdown files. Inspired by [ruff](https://github.com/astral-sh/ruff) 's approach to Python linting, rumdl brings similar speed and developer experience improvements to the Markdown ecosystem. > Questions or feedback? Join us on [Discord](https://discord.gg/ADTJFSFUyn). It offers: - ⚡️ **Built for speed** with Rust - significantly faster than alternatives - 🔍 **75 lint rules** covering common Markdown issues - 🛠️ **Automatic formatting** with `--fix` for files and stdin/stdout - 📦 **Zero dependencies** - single binary with no runtime requirements - 🔧 **Highly configurable** with TOML-based config files - 🎯 **Multiple Markdown flavors** - [GFM, MkDocs, MDX, Quarto, MyST](docs/flavors.md) support with auto-detection - 🌐 **Multiple installation options** - Rust, Python, standalone binaries - 🐍 **Installable via pip** for Python users - 📏 **Modern CLI** with detailed error reporting - 🔄 **CI/CD friendly** with non-zero exit code on errors ### Performance rumdl is designed for speed. Benchmarked on the [Rust Book](https://github.com/rust-lang/book) repository (478 markdown files, October 2025): ![Cold start benchmark comparison](assets/benchmark.svg) With intelligent caching, subsequent runs are even faster - rumdl only re-lints files that have changed, making it ideal for watch mode and editor integration. ## Table of Contents - [Installation](#installation) - [Using Homebrew (macOS/Linux)](#using-homebrew-macoslinux) - [Using Cargo (Rust)](#using-cargo-rust) - [Using npm](#using-npm) - [Using pip (Python)](#using-pip-python) - [Using uv](#using-uv) - [Using mise](#using-mise) - [Using Nix (macOS/Linux)](#using-nix-macoslinux) - [Using Termux User Repository (TUR) (Android)](#using-termux-user-repository-tur-android) - [Using pacman (Arch Linux)](#using-pacman-arch-linux) - [Download binary](#download-binary) - [Editor Plugins](#editor-plugins) - [Shell Completions](#shell-completions) - [Usage](#usage) - [Stdin/Stdout Formatting](#stdinstdout-formatting) - [Editor Integration](#editor-integration) - [Pre-commit Integration](#pre-commit-integration) - [Excluding Files in Pre-commit](#excluding-files-in-pre-commit) - [CI/CD Integration](#cicd-integration) - [GitHub Actions](#github-actions) - [Inputs](#inputs) - [Examples](#examples) - [Rules](#rules) - [Flavors](#flavors) - [Supported Flavors](#supported-flavors) - [Configuring Flavors](#configuring-flavors) - [Command-line Interface](#command-line-interface) - [Commands](#commands) - [`check [PATHS...]`](#check-paths) - [`fmt [PATHS...]`](#fmt-paths) - [`init [OPTIONS]`](#init-options) - [`import [OPTIONS]`](#import-file-options) - [`rule []`](#rule-rule) - [`config [OPTIONS] [COMMAND]`](#config-options-command) - [`server [OPTIONS]`](#server-options) - [`vscode [OPTIONS]`](#vscode-options) - [`completions [SHELL]`](#completions-shell) - [`version`](#version) - [Global Options](#global-options) - [Exit Codes](#exit-codes) - [Usage Examples](#usage-examples) - [LSP](#lsp) - [Configuration](#configuration) - [Configuration Discovery](#configuration-discovery) - [Editor Support (JSON Schema)](#editor-support-json-schema) - [Global Configuration](#global-configuration) - [Markdownlint Migration](#markdownlint-migration) - [Inline Configuration](#inline-configuration) - [Configuration File Example](#configuration-file-example) - [Style Guide Presets](#style-guide-presets) - [Initializing Configuration](#initializing-configuration) - [Configuration in pyproject.toml](#configuration-in-pyprojecttoml) - [Configuration Output](#configuration-output) - [Effective Configuration (`rumdl config`)](#effective-configuration-rumdl-config) - [Example output](#example-output) - [Defaults Only (`rumdl config --defaults`)](#defaults-only-rumdl-config---defaults) - [Non-Defaults Only (`rumdl config --no-defaults`)](#non-defaults-only-rumdl-config---no-defaults) - [Output Style](#output-style) - [Output Format](#output-format) - [Text Output (Default)](#text-output-default) - [JSON Output](#json-output) - [Stability](#stability) - [Development](#development) - [Prerequisites](#prerequisites) - [Building](#building) - [Testing](#testing) - [JSON Schema Generation](#json-schema-generation) - [Used By](#used-by) - [Sponsors](#sponsors) - [License](#license) ## Installation Choose the installation method that works best for you: ### Using winget (Windows) ```powershell winget install --id rvben.rumdl --exact ``` ### Using Homebrew (macOS/Linux) ```bash brew install rumdl ``` ### Using Cargo (Rust) ```bash cargo install rumdl ``` ### Using npm ```bash npm install -g rumdl ``` Or as a dev dependency: ```bash npm install --save-dev rumdl ``` ### Using pip (Python) ```bash pip install rumdl ``` ### Using uv For faster installation and better dependency management with [uv](https://github.com/astral-sh/uv): ```bash # Install directly uv tool install rumdl # Or run without installing uvx rumdl check . ``` ### Using mise For dependency management with [mise](https://github.com/jdx/mise): ```bash # List available versions mise ls-remote rumdl # Install the latest version mise install rumdl # Use a specific version for the project mise use rumdl@0.2.19 ``` ### Using Nix (macOS/Linux) ```bash nix-channel --update nix-env --install --attr nixpkgs.rumdl ``` Alternatively, you can use flakes to run it without installation. ```bash nix run --extra-experimental-features 'flakes nix-command' nixpkgs/nixpkgs-unstable#rumdl -- --version ``` ### Using Termux User Repository (TUR) (Android) After enabling the TUR repo using ```bash pkg install tur-repo ``` ```bash pkg install rumdl ``` ### Using pacman (Arch Linux) rumdl is available in the [official Arch Linux repositories](https://archlinux.org/packages/extra/x86_64/rumdl/): ```bash pacman -S rumdl ``` ### Download binary ```bash # Linux/macOS curl -LsSf https://github.com/rvben/rumdl/releases/latest/download/rumdl-linux-x86_64.tar.gz | tar xzf - -C /usr/local/bin # Windows PowerShell Invoke-WebRequest -Uri "https://github.com/rvben/rumdl/releases/latest/download/rumdl-windows-x86_64.zip" -OutFile "rumdl.zip" Expand-Archive -Path "rumdl.zip" -DestinationPath "$env:USERPROFILE\.rumdl" ``` ### Using Docker Multi-arch images (amd64, arm64) are published to the GitHub Container Registry on every release. Mount your project at `/data` (the working directory inside the container): ```bash docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/data" ghcr.io/rvben/rumdl:latest check . # Pin a specific version docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/data" ghcr.io/rvben/rumdl:0.3.0 check . ``` The image runs as a non-root user by default, so it never writes root-owned files into your project. Passing `--user` runs rumdl as your own user, which lets the lint cache (`.rumdl_cache`) be written into the mounted project with your ownership; without it the cache is skipped gracefully. The image contains only the static rumdl binary, so there is no shell to enter; pass rumdl arguments directly. For environments that need a shell inside the image, such as GitLab CI job containers, an Alpine-based flavour is published as `ghcr.io/rvben/rumdl:alpine` (or pin `:-alpine`). It has no rumdl entrypoint; invoke the binary by name: ```bash docker run --rm -v "$PWD:/data" ghcr.io/rvben/rumdl:alpine rumdl check . ``` ```yaml # .gitlab-ci.yml lint-markdown: image: ghcr.io/rvben/rumdl:alpine script: - rumdl check . ``` ### Editor Plugins | Editor | Install | | ----------------------------------- | ------------------------------------------------------------------------------------------------ | | VS Code / Cursor / Windsurf | `rumdl vscode` or [Marketplace](https://marketplace.visualstudio.com/items?itemName=rvben.rumdl) | | JetBrains (PyCharm, IntelliJ, etc.) | [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/29943-rumdl) | All plugins provide real-time linting, formatting on save, hover documentation, and automatic configuration discovery. ### Shell Completions rumdl can generate tab-completion scripts via `rumdl completions [SHELL]`. Supported shells: `bash`, `zsh`, `fish`, `powershell`, `elvish`. When `SHELL` is omitted, rumdl auto-detects it from `$SHELL`. Run `rumdl completions --list` to see all supported shells. **Bash** — add to `~/.bashrc`: ```bash source <(rumdl completions bash) ``` **Zsh** — add to `~/.zshrc`: ```bash source <(rumdl completions zsh) ``` Or install system-wide for `zsh`: ```bash rumdl completions zsh > "${fpath[1]}/_rumdl" ``` **Fish** — write the completion file once: ```fish rumdl completions fish > ~/.config/fish/completions/rumdl.fish ``` **PowerShell** — add to your `$PROFILE`: ```powershell rumdl completions powershell | Out-String | Invoke-Expression ``` **Elvish** — add to `~/.config/elvish/rc.elv`: ```elvish eval (rumdl completions elvish | slurp) ``` ## Usage Getting started with rumdl is simple: ```bash # Lint a single file rumdl check README.md # Lint all Markdown files in current directory and subdirectories rumdl check . # Format a specific file rumdl fmt README.md # Create a default configuration file rumdl init ``` Common usage examples: ```bash # Lint with custom configuration rumdl check --config my-config.toml docs/ # Override config inline without touching any file (Ruff-compatible syntax) rumdl check --config 'MD013.line-length=120' --config 'MD013.reflow=true' docs/ # See docs/cli-config-overrides.md for the full reference. # Disable specific rules rumdl check --disable MD013,MD033 README.md # Enable only specific rules rumdl check --enable MD001,MD003 README.md # Exclude specific files/directories rumdl check --exclude "node_modules,dist" . # Include only specific files/directories rumdl check --include "docs/*.md,README.md" . # Watch mode for continuous linting rumdl check --watch docs/ # Combine include and exclude patterns rumdl check --include "docs/**/*.md" --exclude "docs/temp,docs/drafts" . # Don't respect gitignore files (note: --respect-gitignore defaults to true) rumdl check --respect-gitignore=false . # Disable all exclude patterns from config rumdl check excluded.md --no-exclude ``` ### Stdin/Stdout Formatting rumdl supports formatting via stdin/stdout, making it ideal for editor integrations and CI pipelines: ```bash # Format content from stdin and output to stdout cat README.md | rumdl fmt --silent - > README_formatted.md # Alternative: cat README.md | rumdl fmt --silent --stdin > README_formatted.md # Use in a pipeline echo "# Title " | rumdl fmt --silent - # Output: # Title # Format clipboard content (macOS example) pbpaste | rumdl fmt --silent - | pbcopy # Provide filename context for better error messages (useful for editor integrations) cat README.md | rumdl check - --stdin-filename README.md ``` Use `--silent` whenever stdout should contain only formatted Markdown. Plain `rumdl fmt -` may also emit remaining diagnostics. ### Editor Integration For editor integration, use stdin/stdout mode with the `--silent` flag when you want pure formatted output on stdout. Use `--quiet` if you still want diagnostics but want to suppress summary lines: ```bash # Format selection in editor (example for vim) :'<,'>!rumdl fmt - --silent # Format entire buffer :%!rumdl fmt - --silent ``` ## Pre-commit Integration You can use `rumdl` as a pre-commit hook to check and format your Markdown files. The recommended way is to use the official pre-commit hook repository: [rumdl-pre-commit repository](https://github.com/rvben/rumdl-pre-commit) Add the following to your `.pre-commit-config.yaml`: ```yaml repos: - repo: https://github.com/rvben/rumdl-pre-commit rev: v0.2.19 hooks: - id: rumdl # Lint only; add args [--fix] to auto-fix - id: rumdl-fmt # Pure format, always exits 0 ``` Two hooks are available: - **`rumdl`** - Lints files and exits 1 if violations are found; non-destructive by default (recommended as the primary hook) - **`rumdl-fmt`** - Formats files in place and always exits 0; relies on pre-commit's file-change detection This mirrors the `ruff` + `ruff-format` split: the linter hook reports by default and never rewrites your files unless you opt in. To auto-fix violations in place, add `args: [--fix]`: ```yaml repos: - repo: https://github.com/rvben/rumdl-pre-commit rev: v0.2.19 hooks: - id: rumdl args: [--fix] # Auto-fix violations in place ``` When you run `pre-commit install` or `pre-commit run`, pre-commit will automatically install `rumdl` in an isolated Python environment using pip. You do **not** need to install rumdl manually. ### Excluding Files in Pre-commit By default, when pre-commit explicitly passes files to rumdl, the exclude patterns defined in your `.rumdl.toml` configuration file are respected. However, for pre-commit workflows where you want to include all files, even when they're excluded in the config, you can use the `--no-exclude` flag in your pre-commit config, e.g.: ```yaml repos: - repo: https://github.com/rvben/rumdl-pre-commit rev: v0.2.19 hooks: - id: rumdl args: [--no-exclude] # Disable all exclude patterns ``` ## CI/CD Integration ### GitHub Actions We have a companion Action you can use to integrate rumdl directly in your workflow: ```yaml jobs: rumdl-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: rvben/rumdl@v0 ``` The `v0` tag always points to the latest stable release, following GitHub Actions conventions. #### Inputs | Input | Description | Default | | ------------- | -------------------------------------- | -------------- | | `version` | Version of rumdl to install | latest | | `path` | Path to lint | workspace root | | `config` | Path to config file | auto-detected | | `report-type` | Output format: `logs` or `annotations` | `logs` | #### Examples **Lint specific directory with pinned version:** ```yaml - uses: rvben/rumdl@v0 with: version: "0.1.71" path: docs/ ``` **Use custom config and show annotations in PR:** ```yaml - uses: rvben/rumdl@v0 with: config: .rumdl.toml report-type: annotations ``` The `annotations` report type displays issues directly in the PR's "Files changed" tab with error/warning severity levels and precise locations. The action ref (`rvben/rumdl@v0`) selects the GitHub Action version, while the optional `version` input pins the `rumdl` CLI version installed inside the workflow. ## Rules rumdl implements 75 lint rules for Markdown files. Here are some key rule categories: | Category | Description | Example Rules | | -------------- | ---------------------------------------- | ------------------- | | **Headings** | Proper heading structure and formatting | MD001, MD002, MD003 | | **Lists** | Consistent list formatting and structure | MD004, MD005, MD007 | | **Whitespace** | Proper spacing and line length | MD009, MD010, MD012 | | **Code** | Code block formatting and language tags | MD040, MD046, MD048 | | **Links** | Proper link and reference formatting | MD034, MD039, MD042 | | **Images** | Image alt text and references | MD045, MD052 | | **Style** | Consistent style across document | MD031, MD032, MD035 | For a complete list of rules and their descriptions, see our [documentation](https://rumdl.dev/rules/) or run: ```bash rumdl rule ``` ## Flavors rumdl supports multiple Markdown flavors to accommodate different documentation systems. Each flavor adjusts rule behavior for syntax specific to that system, reducing false positives. ### Supported Flavors | Flavor | Use Case | Key Features | | -------------------------------------------- | ---------------------------- | --------------------------------------------------- | | [standard](docs/flavors/standard.md) | Default Markdown | CommonMark + GFM extensions (tables, task lists) | | [gfm](docs/flavors/gfm.md) | GitHub Flavored Markdown | Extended autolinks, security-sensitive HTML | | [mkdocs](docs/flavors/mkdocs.md) | MkDocs / Material for MkDocs | Admonitions, content tabs, mkdocstrings | | [mdx](docs/flavors/mdx.md) | MDX (JSX in Markdown) | JSX components, ESM imports, expressions | | [quarto](docs/flavors/quarto.md) | Quarto / RMarkdown | Citations, shortcodes, executable code blocks | | [pandoc](docs/flavors/pandoc.md) | Pandoc Markdown | Fenced divs, attribute lists, citations, math | | [obsidian](docs/flavors/obsidian.md) | Obsidian | Tag syntax (#tagname treated as tags, not headings) | | [kramdown](docs/flavors/kramdown.md) | Jekyll / kramdown | IALs, ALDs, extension blocks | | [azure_devops](docs/flavors/azure_devops.md) | Azure DevOps Wiki | Colon code fences (:::lang ... :::) | | [myst](docs/flavors/myst.md) | MyST / Jupyter Book / Sphinx | Directives, roles, `%` comments | ### Configuring Flavors Set a global flavor in your configuration: ```toml [global] flavor = "mkdocs" ``` Or configure per-file patterns: ```toml [per-file-flavor] "docs/**/*.md" = "mkdocs" "**/*.mdx" = "mdx" "**/*.qmd" = "quarto" ``` When no flavor is configured, rumdl auto-detects based on file extension (`.mdx` → mdx, `.qmd`/`.Rmd` → quarto, `.md` → standard). For complete flavor documentation, see the [Flavors Guide](docs/flavors.md). ## Command-line Interface ```bash rumdl [options] [file or directory...] ``` ### Commands #### `check [PATHS...]` Lint Markdown files and print warnings/errors (main subcommand) **Arguments:** - `[PATHS...]`: Files or directories to lint. If provided, these paths take precedence over include patterns **Options:** - `-f, --fix`: Automatically fix issues where possible - `--diff`: Show diff of what would be fixed instead of fixing files - `-w, --watch`: Run in watch mode by re-running whenever files change - `-l, --list-rules`: List all available rules - `-d, --disable `: Disable specific rules (comma-separated) - `-e, --enable `: Enable only specific rules (comma-separated) - `--exclude `: Exclude specific files or directories (comma-separated glob patterns) - `--include `: Include only specific files or directories (comma-separated glob patterns) - `--respect-gitignore`: Respect .gitignore files when scanning directories (does not apply to explicitly provided paths) - `--no-exclude`: Disable all exclude patterns from config - `-v, --verbose`: Show detailed output - `--profile`: Show profiling information - `--statistics`: Show rule violation statistics summary - `-q, --quiet`: Print diagnostics, but suppress summary lines - `--output-format `: Output format for diagnostics - `--stdin`: Read from stdin instead of files #### `fmt [PATHS...]` Format Markdown files and apply fixes. Unlike `check --fix`, `fmt` keeps formatter-style exit codes and exits 0 after successful formatting, making it ideal for editor integration. **Arguments:** - `[PATHS...]`: Files or directories to format. If provided, these paths take precedence over include patterns **Options:** All the same options as `check` are available (except `--fix` which is always enabled), including: - `--stdin`: Format content from stdin and output to stdout - `-d, --disable `: Disable specific rules during formatting - `-e, --enable `: Format using only specific rules - `--exclude/--include`: Control which files to format - `-q, --quiet`: Print diagnostics, but suppress summary lines - `-s, --silent`: Suppress diagnostics and summaries for pure formatter output **Examples:** ```bash # Format all Markdown files in current directory rumdl fmt # Format specific file rumdl fmt README.md # Format from stdin (using dash syntax) cat README.md | rumdl fmt --silent - > formatted.md # Alternative: cat README.md | rumdl fmt --silent --stdin > formatted.md ``` #### `init [OPTIONS]` Create a default configuration file in the current directory **Options:** - `--pyproject`: Generate configuration for `pyproject.toml` instead of `.rumdl.toml` #### `import [OPTIONS]` Import and convert markdownlint configuration files to rumdl format **Arguments:** - ``: Path to markdownlint config file (JSON/JSONC/YAML) **Options:** - `-o, --output `: Output file path (default: `.rumdl.toml`) - `--format `: Output format: `toml` or `json` (default: `toml`) - `--dry-run`: Show converted config without writing to file #### `rule []` Show information about a rule or list all rules **Arguments:** - `[rule]`: Rule name or ID (optional). If provided, shows details for that rule. If omitted, lists all available rules **Useful options:** - `--list-categories`: List available rule categories and exit - `--category `: Filter rules by category when listing - `--output-format `: Emit structured output such as `json` or `json-lines` - `--explain`: Include full documentation in `json` and `json-lines` output #### `config [OPTIONS] [COMMAND]` Show configuration or query a specific key **Options:** - `--defaults`: Show only the default configuration values - `--no-defaults`: Show only non-default configuration values (exclude defaults) - `--output `: Output format (e.g. `toml`, `json`) **Subcommands:** - `get `: Query a specific config key (e.g. `global.exclude` or `MD013.line_length`) - `file`: Show the absolute path of the configuration file that was loaded #### `server [OPTIONS]` Start the Language Server Protocol server for editor integration **Options:** - `--port `: TCP port to listen on (for debugging) - `-v, --verbose`: Enable verbose logging #### `vscode [OPTIONS]` Install the rumdl VS Code extension **Options:** - `--force`: Force reinstall even if already installed - `--update`: Update to the latest version (only if newer version is available) - `--status`: Show installation status without installing #### `completions [SHELL]` Print a shell completion script for `rumdl` to stdout. See [Shell Completions](#shell-completions) for installation snippets. **Arguments:** - `[SHELL]`: One of `bash`, `zsh`, `fish`, `powershell`, `elvish`. Auto-detected from `$SHELL` when omitted. **Options:** - `-l, --list`: List available shells and exit #### `version` Show version information ### Global Options These options are available for all commands: - `--color `: Control colored output: `auto` (default), `always`, `never` - `--config `: Path to configuration file - `--no-config`: Ignore all configuration files and use built-in defaults - `--isolated`: Hidden compatibility alias for `--no-config` ### Exit Codes - `0`: Success (no violations found, or all violations were fixed) - `1`: Violations found (or remain after `--fix`) - `2`: Tool error **Note:** `rumdl fmt` exits 0 on successful formatting (even if unfixable violations remain), making it compatible with editor integrations. `rumdl check --fix` exits 0 if all violations are fixed, or 1 if violations remain after fixing (useful for pre-commit hooks and CI/CD). ### Usage Examples ```bash # Lint all Markdown files in the current directory rumdl check . # Format files (exits 0 on success, even if unfixable violations remain) rumdl fmt . # Auto-fix and report unfixable violations (exits 0 if all fixed, 1 if violations remain) rumdl check --fix . # Preview what would be fixed without modifying files rumdl check --diff . # Create a default configuration file rumdl init # Create or update a pyproject.toml file with rumdl configuration rumdl init --pyproject # Import a markdownlint config file rumdl import .markdownlint.json # Convert markdownlint config to JSON format rumdl import --format json .markdownlint.yaml --output rumdl-config.json # Preview conversion without writing file rumdl import --dry-run .markdownlint.json # Show information about a specific rule rumdl rule MD013 # List all available rules rumdl rule # Query a specific config key rumdl config get global.exclude # Show the path of the loaded configuration file rumdl config file # Show configuration as JSON instead of the default format rumdl config --output json # Show only non-default configuration values rumdl config --no-defaults # Lint content from stdin echo "# My Heading" | rumdl check --stdin # Get JSON output for integration with other tools rumdl check --output-format json README.md # Show statistics summary of rule violations rumdl check --statistics . # Disable colors in output rumdl check --color never README.md # Use built-in defaults, ignoring all config files rumdl check --no-config README.md # Show version information rumdl version ``` ## LSP rumdl is also available as an LSP server for editor integration. For editors that support generic LSP configuration, the minimal stdio setup is: ```toml command = ["rumdl", "server"] ``` For editor-specific information on setting up the LSP, refer to our [LSP documentation](https://rumdl.dev/lsp/) ## Configuration rumdl can be configured in several ways: 1. Using a `.rumdl.toml` or `rumdl.toml` file in your project directory or parent directories 2. Using a `/.config/rumdl.toml` file (following the [config-dir convention](https://github.com/pi0/config-dir)) 3. Using the `[tool.rumdl]` section in your project's `pyproject.toml` file (for Python projects) 4. Using command-line arguments 5. Using a **global user config** at `~/.config/rumdl/rumdl.toml` or a home-directory dotfile at `~/.rumdl.toml` (see [Global Configuration](#global-configuration) below) 6. **Automatic markdownlint compatibility**: rumdl automatically discovers and loads existing markdownlint config files (`.markdownlint.json`, `.markdownlint.yaml`, etc.) ### Configuration Discovery rumdl automatically searches for configuration files by traversing up the directory tree from the current working directory, similar to tools like `git` , `ruff` , and `eslint` . This means you can run rumdl from any subdirectory of your project and it will find the configuration file at the project root. The search follows these rules: - Searches upward for `.rumdl.toml`, `rumdl.toml`, `/.config/rumdl.toml`, or `pyproject.toml` (with `[tool.rumdl]` section) - Precedence order: `.rumdl.toml` > `rumdl.toml` > `/.config/rumdl.toml` > `pyproject.toml` - Stops at the first configuration file found - Warns when more than one of these files exists in the same directory, so you can tell which is winning (`rumdl config file` prints the loaded config's path) - Stops searching when it encounters a `.git` directory (project boundary) - Maximum traversal depth of 100 directories - Falls back to markdownlint config files (`.markdownlint.yaml`, etc.) using the same upward traversal - Falls back to user configuration if no project configuration is found (see Global Configuration below) #### Per-Directory Configuration When running `rumdl check .` from the project root, rumdl resolves configuration on a **per-directory** basis. Files in subdirectories with their own `.rumdl.toml` use that config instead of the root config. This matches the behavior of [Ruff](https://docs.astral.sh/ruff/) and [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2). Subdirectory configs are **standalone** by default. Use `extends` to inherit from a parent config: ```toml # docs/.rumdl.toml — inherits root config, overrides line-length extends = "../.rumdl.toml" [global] line-length = 120 ``` `extends` paths support `~/`, absolute, and relative paths, plus `$VAR` / `${VAR}` environment-variable expansion (e.g. `extends = "$GEM_PATH/gems/my-style/.rumdl.toml"`) for a base config delivered at a machine-dependent location. See [Config inheritance](docs/global-settings.md#extends) for details. Per-directory resolution is disabled when `--config` or `--no-config` is used (`--isolated` is still accepted as a compatibility alias). To disable all configuration discovery and use only built-in defaults, use the `--no-config` flag: ```bash # Use discovered configuration (default behavior) rumdl check . # Ignore all configuration files rumdl check --no-config . ``` ### Editor Support (JSON Schema) rumdl provides a JSON Schema for `.rumdl.toml` configuration files, enabling autocomplete, validation, and inline documentation in supported editors like VS Code, IntelliJ IDEA, and others. The schema is available at `https://raw.githubusercontent.com/rvben/rumdl/main/rumdl.schema.json`. **Automatic Setup (via SchemaStore):** The schema is registered with [SchemaStore](https://www.schemastore.org/), so editors with TOML support will automatically provide autocomplete and validation for `.rumdl.toml` and `rumdl.toml` files. **VS Code:** Install the "Even Better TOML" extension - schema association is automatic. **Manual Schema Association:** If your editor doesn't support SchemaStore, associate this schema URL with `.rumdl.toml` or `rumdl.toml` in the editor's TOML schema settings: `https://raw.githubusercontent.com/rvben/rumdl/main/rumdl.schema.json` ### Global Configuration When no project configuration is found, rumdl looks for a user-level configuration file in two locations, in this order: **1. Platform user-config directory** (preferred): - **Linux/macOS**: `~/.config/rumdl/` (respects `XDG_CONFIG_HOME` if set) - **Windows**: `%APPDATA%\rumdl\` Files checked (in order): `.rumdl.toml`, `rumdl.toml`, `pyproject.toml` (must contain `[tool.rumdl]` section). **2. Home-directory dotfile** (fallback): If nothing is found in the platform config directory, rumdl also checks for `~/.rumdl.toml`, then `~/rumdl.toml`. This honors the classic Unix dotfile convention used by tools like `git` and `npm`. This allows you to set personal preferences that apply to all projects without local configuration. **Example:** Create `~/.config/rumdl/rumdl.toml` (preferred) or `~/.rumdl.toml`: ```toml [global] line-length = 100 disable = ["MD013", "MD041"] [MD007] indent = 2 ``` **Note:** User configuration is only used when no project configuration exists. Project configurations always take precedence. When both a platform user-config file and a home-directory dotfile exist, the platform user-config file wins. ### Markdownlint Migration rumdl provides seamless compatibility with existing markdownlint configurations: **Automatic Discovery**: rumdl automatically detects and loads markdownlint config files by traversing up the directory tree (just like `.rumdl.toml`): - `.markdownlint.json` / `.markdownlint.jsonc` - `.markdownlint.yaml` / `.markdownlint.yml` - `markdownlint.json` / `markdownlint.yaml` This means you can place a `.markdownlint.yaml` at your project root and run rumdl from any subdirectory - it will find and use the config automatically. **Explicit Import**: Convert markdownlint configs to rumdl format: ```bash # Convert to .rumdl.toml rumdl import .markdownlint.json # Convert to JSON format rumdl import --format json .markdownlint.yaml --output config.json # Preview conversion rumdl import --dry-run .markdownlint.json ``` For comprehensive documentation on global settings (file selection, rule enablement, etc.), see our [Global Settings Reference](docs/global-settings.md). ### Inline Configuration rumdl supports inline HTML comments to disable or configure rules for specific sections of your Markdown files. This is useful for making exceptions without changing global configuration: ```markdown This line can be as long as needed without triggering the line length rule. ``` Note: `markdownlint-disable`/`markdownlint-enable` comments are also supported for compatibility with existing markdownlint configurations. For complete documentation on inline configuration options, see our [Inline Configuration Reference](docs/inline-configuration.md). ### Configuration File Example Here's an example `.rumdl.toml` configuration file: ```toml [global] line-length = 100 exclude = ["node_modules", "build", "dist"] respect-gitignore = true flavor = "mkdocs" # Use MkDocs flavor (see Flavors section) disable = ["MD013", "MD033"] # Per-file flavor overrides [per-file-flavor] "**/*.mdx" = "mdx" # Disable specific rules for specific files [per-file-ignores] "README.md" = ["MD033"] # Allow HTML in README "SUMMARY.md" = ["MD025"] # Allow multiple H1 in table of contents "docs/api/**/*.md" = ["MD013", "MD041"] # Relax rules for generated docs # Configure individual rules [MD007] indent = 2 [MD013] line-length = 100 code-blocks = false tables = false reflow = true # Enable automatic line wrapping (required for --fix) [MD025] level = 1 front-matter-title = "title" [MD044] names = ["rumdl", "Markdown", "GitHub"] [MD048] code-fence-style = "backtick" # Code block tools (optional) [code-block-tools] enabled = true normalize-language = "linguist" on-error = "warn" timeout = 30000 [code-block-tools.language-aliases] py = "python" bash = "shell" [code-block-tools.languages.python] lint = ["ruff:check"] format = ["ruff:format"] ``` ### Style Guide Presets Ready-to-use configurations for popular style guides are available in the [`examples/`](examples/) directory: - **[Google Style](examples/google-style.rumdl.toml)** - Google's Markdown style guide - **[Prettier-compatible](examples/prettier-compatible.rumdl.toml)** - Aligns with Prettier's markdown formatting Copy one to your project as `.rumdl.toml` to use it. ### Initializing Configuration To create a configuration file, use the `init` command: ```bash # Create a .rumdl.toml file (for any project) rumdl init # Create or update a pyproject.toml file with rumdl configuration (for Python projects) rumdl init --pyproject ``` ### Configuration in pyproject.toml For Python projects, you can include rumdl configuration in your `pyproject.toml` file, keeping all project configuration in one place. Example: ```toml [tool.rumdl] # Global options at root level line-length = 100 disable = ["MD033"] include = ["docs/*.md", "README.md"] exclude = [".git", "node_modules"] respect-gitignore = true # Rule-specific configuration [tool.rumdl.MD013] code_blocks = false tables = false [tool.rumdl.MD044] names = ["rumdl", "Markdown", "GitHub"] ``` Both kebab-case (`line-length`, `respect-gitignore`) and snake_case (`line_length`, `respect_gitignore`) formats are supported for compatibility with different Python tooling conventions. ### Configuration Output #### Effective Configuration (`rumdl config`) The `rumdl config` command prints the **full effective configuration** (defaults + all overrides), showing every key and its value, annotated with the source of each value. The output is colorized and the `[from ...]` annotation is globally aligned for easy scanning. #### Example output ```text [global] enable = [] [from default] disable = ["MD033"] [from .rumdl.toml] include = ["README.md"] [from .rumdl.toml] respect_gitignore = true [from .rumdl.toml] [MD013] line_length = 200 [from .rumdl.toml] code_blocks = true [from .rumdl.toml] ... ``` - ** Keys** are cyan, **values** are yellow, and the `[from ...]` annotation is colored by source: - Green: CLI - Blue: `.rumdl.toml` - Magenta: `pyproject.toml` - Yellow: default - The `[from ...]` column is aligned across all sections. ### Defaults Only (`rumdl config --defaults`) The `rumdl config --defaults` command shows only the default configuration values, useful for understanding what the built-in defaults are. ### Non-Defaults Only (`rumdl config --no-defaults`) The `rumdl config --no-defaults` command shows only configuration values that differ from defaults, making it easy to see what you've customized. This is particularly useful when you want to see only your project-specific or user-specific overrides without the noise of default values. **Example:** ```bash $ rumdl config --no-defaults [global] disable = ["MD013"] [from project config] line_length = 100 [from pyproject.toml] [MD004] style = "asterisk" [from project config] ``` This helps you quickly identify what customizations you've made to the default configuration. The `--defaults` flag prints only the default configuration as TOML, suitable for copy-paste or reference: ```toml [global] enable = [] disable = [] exclude = [] include = [] respect_gitignore = true force_exclude = false # Set to true to exclude files even when explicitly specified [MD013] line_length = 80 code_blocks = true ... ``` ## Output Style rumdl produces clean, colorized output similar to modern linting tools: ```text README.md:12:1: [MD022] Headings should be surrounded by blank lines [*] README.md:24:5: [MD037] Spaces inside emphasis markers: "* incorrect *" [*] README.md:31:76: [MD013] Line length exceeds 80 characters README.md:42:3: [MD010] Hard tabs found, use spaces instead [*] ``` When running with `--fix`, rumdl shows which issues were fixed: ```text README.md:12:1: [MD022] Headings should be surrounded by blank lines [fixed] README.md:24:5: [MD037] Spaces inside emphasis markers: "* incorrect *" [fixed] README.md:42:3: [MD010] Hard tabs found, use spaces instead [fixed] Fixed 3 issues in 1 file ``` For a more detailed view, use the `--verbose` option: ```text ✓ No issues found in CONTRIBUTING.md README.md:12:1: [MD022] Headings should be surrounded by blank lines [*] README.md:24:5: [MD037] Spaces inside emphasis markers: "* incorrect *" [*] README.md:42:3: [MD010] Hard tabs found, use spaces instead [*] Found 3 issues in 1 file (2 files checked) Run `rumdl fmt` to automatically fix issues ``` ### Output Format #### Text Output (Default) rumdl uses a consistent output format for all issues: ```text {file}:{line}:{column}: [{rule_id}] {message} [{fix_indicator}] ``` The output is colorized by default: - Filenames appear in blue and underlined - Line and column numbers appear in cyan - Rule IDs appear in yellow - Error messages appear in white - Fixable issues are marked with `[*]` in green - Fixed issues are marked with `[fixed]` in green #### JSON Output For integration with other tools and automation, use `--output-format json`: ```bash rumdl check --output-format json README.md ``` This produces a flat JSON array of warning objects, one per issue. Fixable issues include a `fix` object with the byte `range` to replace and the `replacement` text: ```json [ { "file": "README.md", "line": 12, "column": 1, "rule": "MD022", "message": "Headings should be surrounded by blank lines", "severity": "warning", "fixable": true, "fix": { "range": { "start": 142, "end": 142 }, "replacement": "\n" } } ] ``` ## Stability rumdl is currently **Beta** while its compatibility policy and 1.0 exit criteria are formalized. The core CLI, configuration model, and rule set are already intended for production use. See [Stability and Compatibility](docs/stability.md) for the compatibility guarantees, versioning, deprecation, and MSRV policies, and a dateless checklist of what remains before 1.0. If a release breaks something documented as stable, that is a bug. ## Development ### Prerequisites - Rust 1.94 or higher - Make (for development commands) ### Building ```bash make build ``` ### Testing ```bash make test ``` ### JSON Schema Generation If you modify the configuration structures in `src/config.rs`, regenerate the JSON schema: ```bash # Generate/update the schema make schema # Or: rumdl schema generate # Check if schema is up-to-date (useful in CI) make check-schema # Or: rumdl schema check # Print schema to stdout rumdl schema print ``` The schema is automatically generated from the Rust types using `schemars` and should be kept in sync with the configuration structures. ## Used By rumdl is used by these notable open source projects: | Project | Stars | | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | [aio-libs/aiobotocore](https://github.com/aio-libs/aiobotocore) | ![stars](https://img.shields.io/github/stars/aio-libs/aiobotocore?style=flat-square) | | [apache/lucene](https://github.com/apache/lucene) | ![stars](https://img.shields.io/github/stars/apache/lucene?style=flat-square) | | [beeware/beeware.github.io](https://github.com/beeware/beeware.github.io) | ![stars](https://img.shields.io/github/stars/beeware/beeware.github.io?style=flat-square) | | [beeware/briefcase](https://github.com/beeware/briefcase) | ![stars](https://img.shields.io/github/stars/beeware/briefcase?style=flat-square) | | [beeware/toga](https://github.com/beeware/toga) | ![stars](https://img.shields.io/github/stars/beeware/toga?style=flat-square) | | [callowayproject/bump-my-version](https://github.com/callowayproject/bump-my-version) | ![stars](https://img.shields.io/github/stars/callowayproject/bump-my-version?style=flat-square) | | [chrisgrieser/nvim-scissors](https://github.com/chrisgrieser/nvim-scissors) | ![stars](https://img.shields.io/github/stars/chrisgrieser/nvim-scissors?style=flat-square) | | [chrisgrieser/nvim-spider](https://github.com/chrisgrieser/nvim-spider) | ![stars](https://img.shields.io/github/stars/chrisgrieser/nvim-spider?style=flat-square) | | [chrisgrieser/nvim-various-textobjs](https://github.com/chrisgrieser/nvim-various-textobjs) | ![stars](https://img.shields.io/github/stars/chrisgrieser/nvim-various-textobjs?style=flat-square) | | [chrisgrieser/shimmering-focus](https://github.com/chrisgrieser/shimmering-focus) | ![stars](https://img.shields.io/github/stars/chrisgrieser/shimmering-focus?style=flat-square) | | [chrisgrieser/shimmering-obsidian](https://github.com/chrisgrieser/shimmering-obsidian) | ![stars](https://img.shields.io/github/stars/chrisgrieser/shimmering-obsidian?style=flat-square) | | [copier-org/copier](https://github.com/copier-org/copier) | ![stars](https://img.shields.io/github/stars/copier-org/copier?style=flat-square) | | [Hexlet/ru-test-assignments](https://github.com/Hexlet/ru-test-assignments) | ![stars](https://img.shields.io/github/stars/Hexlet/ru-test-assignments?style=flat-square) | | [kreuzberg-dev/html-to-markdown](https://github.com/kreuzberg-dev/html-to-markdown) | ![stars](https://img.shields.io/github/stars/kreuzberg-dev/html-to-markdown?style=flat-square) | | [matrix-org/matrix-rust-sdk](https://github.com/matrix-org/matrix-rust-sdk) | ![stars](https://img.shields.io/github/stars/matrix-org/matrix-rust-sdk?style=flat-square) | | [matrix-org/matrix.org](https://github.com/matrix-org/matrix.org) | ![stars](https://img.shields.io/github/stars/matrix-org/matrix.org?style=flat-square) | | [mikavilpas/yazi.nvim](https://github.com/mikavilpas/yazi.nvim) | ![stars](https://img.shields.io/github/stars/mikavilpas/yazi.nvim?style=flat-square) | | [modular/modular](https://github.com/modular/modular) | ![stars](https://img.shields.io/github/stars/modular/modular?style=flat-square) | | [mopidy/mopidy](https://github.com/mopidy/mopidy) | ![stars](https://img.shields.io/github/stars/mopidy/mopidy?style=flat-square) | | [mozilla-firefox/firefox](https://github.com/mozilla-firefox/firefox) | ![stars](https://img.shields.io/github/stars/mozilla-firefox/firefox?style=flat-square) | | [PyO3/pyo3](https://github.com/PyO3/pyo3) | ![stars](https://img.shields.io/github/stars/PyO3/pyo3?style=flat-square) | | [Ravencentric/awesome-arr](https://github.com/Ravencentric/awesome-arr) | ![stars](https://img.shields.io/github/stars/Ravencentric/awesome-arr?style=flat-square) | | [rust-lang/rustlings](https://github.com/rust-lang/rustlings) | ![stars](https://img.shields.io/github/stars/rust-lang/rustlings?style=flat-square) | | [scop/bash-completion](https://github.com/scop/bash-completion) | ![stars](https://img.shields.io/github/stars/scop/bash-completion?style=flat-square) | | [Ulauncher/Ulauncher](https://github.com/Ulauncher/Ulauncher) | ![stars](https://img.shields.io/github/stars/Ulauncher/Ulauncher?style=flat-square) | | [WeblateOrg/weblate](https://github.com/WeblateOrg/weblate) | ![stars](https://img.shields.io/github/stars/WeblateOrg/weblate?style=flat-square) | | [wfxr/forgit](https://github.com/wfxr/forgit) | ![stars](https://img.shields.io/github/stars/wfxr/forgit?style=flat-square) | *Using rumdl? [Let us know!](https://github.com/rvben/rumdl/issues/307)* ## Sponsors rumdl is free and open source. If it saves you time, consider [sponsoring the project](https://github.com/sponsors/rvben). - [David Hewitt](https://github.com/davidhewitt) ## License rumdl is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.