Processors
RSConstruct uses processors to discover and build products. Each processor scans for source files matching its conventions and produces output files.
Processor Types
There are four processor types: checker, generator, explicit, and mass generator. They differ in how inputs are discovered, how outputs are declared, and how results are cached.
See Processor Types for full descriptions, examples, and a comparison table.
Configuration
Declare processors by adding [processor.TYPE.NAME] sections to rsconstruct.toml:
[processor.checker.ruff]
[processor.checker.pylint]
args = ["--disable=C0114"]
[processor.generator.cc_single_file]
Only declared processors run — no processors are enabled by default. Use rsconstruct smart auto to auto-detect and add relevant processors.
Use rsconstruct processor list to see declared processors and descriptions.
Use rsconstruct processor list --all to show all built-in processors, not just those enabled in the project.
Use rsconstruct processor files to see which files each processor discovers.
Available Processors
- Tera — renders Tera templates into output files
- Ruff — lints Python files with ruff
- Pylint — lints Python files with pylint
- Mypy — type-checks Python files with mypy
- Pyrefly — type-checks Python files with pyrefly
- CC — builds full C/C++ projects from cc.yaml manifests
- CC Single File — compiles C/C++ source files into executables (single-file)
- Linux Module — builds Linux kernel modules from linux-module.yaml manifests
- Cppcheck — runs static analysis on C/C++ source files
- Clang-Tidy — runs clang-tidy static analysis on C/C++ source files
- Shellcheck — lints shell scripts using shellcheck
- Zspell — checks documentation files for spelling errors
- Rumdl — lints Markdown files with rumdl
- Oxlint — lints JavaScript/TypeScript files with oxlint
- Selene — lints Lua files with selene
- Iluacheck — lints Lua files with luacheck’s analysis (native)
- Biome — lints CSS/JavaScript/TypeScript/JSON files with biome
- Make — runs make in directories containing Makefiles
- Cargo — builds Rust projects using Cargo
- Yamllint — lints YAML files with yamllint
- Jq — validates JSON files with jq
- Jsonlint — lints JSON files with jsonlint
- Taplo — checks TOML files with taplo
- Terms — checks that technical terms are backtick-quoted in Markdown files
- Json Schema — validates JSON schema propertyOrdering
- Iyamlschema — validates YAML files against JSON schemas (native)
- Yaml2json — converts YAML files to JSON (native)
- Markdown2html — converts Markdown to HTML using markdown CLI
- Imarkdown2html — converts Markdown to HTML (native)
- Mass Generator — runs a tool that enumerates its outputs in advance, one cached product per predicted file
- Zola — builds a zola site, one cached product per file zola writes, planned natively
Clean behavior
rsconstruct clean outputs calls each product’s processor-defined clean(). What gets removed depends on the processor type:
| Processor type | What clean() removes | Recursive? |
|---|---|---|
| Checker | Nothing — checkers produce no outputs | — |
| Generator | Each declared output file (product.outputs) | No |
| Explicit | Declared output_files + declared output_dirs | Yes (for output_dirs only) |
| Mass generator | Each predicted output file (product.outputs) | No |
Recursive directory removal is reserved for the user-declared output_dirs of Explicit. All other processor types remove individually-declared files and nothing else. A mass generator’s output_dirs only bound where its predicted files may live; the directory itself is never removed directly, though the empty-directory sweep removes directories that become empty.
After every product’s clean() completes, the orchestrator runs an empty-directory sweep: every parent of every removed output (and every parent of every removed output_dir) is tried with fs::remove_dir (non-recursive — succeeds only on already-empty directories), walking upward until a non-empty directory or the project root. The sweep does not special-case out/ — any ancestor of a cleaned output is eligible.
See rsconstruct clean for the full command reference, the --no-empty-dirs flag, the -p filter, and the other clean variants (all, git, unknown).
Worked example
Consider this project:
# rsconstruct.toml
[processor.checker.ruff] # Checker — no outputs
src_dirs = ["src"]
[processor.generator.tera] # Generator — declared file outputs
src_dirs = ["templates"]
src_extensions = [".tera"]
[processor.mass_generator.mdbook] # Mass generator — planned file outputs
src_dirs = ["docs"]
Layout:
project/
├── rsconstruct.toml
├── src/main.py # ruff lints this
├── templates/index.html.tera # tera renders to → out/processor.generator.tera/index.html
├── docs/
│ ├── book.toml
│ └── src/SUMMARY.md
└── book/ # mdbook output_dir; each file is a planned product
├── index.html
├── css/general.css
└── ...
After rsconstruct build, both out/processor.generator.tera/index.html and the planned files under book/ exist.
Now run rsconstruct clean outputs:
- ruff — Checker.
clean()is a no-op. Nothing removed. - tera — Generator.
clean()callsfs::remove_file("out/processor.generator.tera/index.html"). The file goes;out/processor.generator.tera/andout/are not yet touched. - mdbook — Mass generator.
clean()removes each planned output file underbook/. - Empty-directory sweep. The orchestrator collects parents of every removed path (
out/processor.generator.tera,out,book/css,book) and removes those that are now empty, deepest-first.
Final state: src/main.py, templates/index.html.tera, docs/..., and rsconstruct.toml remain untouched. The cache (.rsconstruct/) is untouched. out/ and book/ are gone.
If you instead ran rsconstruct clean outputs --no-empty-dirs, the only difference is step 4 is skipped — out/processor.generator.tera/ and out/ would remain as empty directories.
If you ran rsconstruct clean outputs -p processor.generator.tera, only step 2 + the sweep of its parents runs. book/ is left intact because mdbook is filtered out.
Custom Processors
A command of your own runs through a config-driven processor: [processor.checker.script] for a check, [processor.generator.generic] for one output per input, [processor.explicit.generic] for declared inputs and outputs, and [processor.mass_generator.generic] for a tool that can predict its outputs. A new tool with its own discovery or output rules is a new processor file under src/processor/.