Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Processor Types

Every processor in RSConstruct has a type that determines how it discovers inputs, produces outputs, and interacts with the cache. There are four types.

Run rsconstruct processor types to list them. Its Batch column is the type’s contract on taking more than one input in one tool invocation: optional for checkers and generators, where each processor decides (processor list shows which do), and always for explicit processors and mass generators, which take every input in one invocation by construction. --verbose adds the one-line contract behind each value.

Checker

A checker validates input files without producing any output. If the check passes, the result is cached — if the inputs haven’t changed on the next build, the check is skipped entirely.

How it works

  1. Scans for files matching src_extensions in src_dirs
  2. Creates one product per input file
  3. Runs the tool on each file (or batch of files)
  4. If the tool exits successfully, records a marker in the cache
  5. On the next build, if inputs are unchanged, the check is skipped

What gets cached

A marker entry — no files, no blobs. The marker’s presence means “this check passed with these inputs.”

Examples

Lint Python files with ruff:

[processor.checker.ruff]

Scans for .py files, runs ruff check on each. No output files produced.

src/main.py → (checker)
src/utils.py → (checker)

Lint shell scripts:

[processor.checker.shellcheck]

Scans for .sh and .bash files, runs shellcheck on each.

Validate YAML files:

[processor.checker.yamllint]

Scans for .yml and .yaml files, runs yamllint on each.

Validate JSON files with jq:

[processor.checker.jq]

Scans for .json files, validates each with jq.

Spell check Markdown files:

[processor.checker.zspell]

Scans for .md files, checks spelling with the built-in zspell engine.

Built-in checkers

actionlint, ascii, aspell, biome, black, checkpatch, checkstyle, clang_tidy, clippy, cmake, cppcheck, cpplint, doctest, duplicate_files, encoding, eslint, hadolint, htmlhint, htmllint, iactionlint, icpplint, idockerfile, ijq, ijsonlint, iluacheck, isvglint, itaplo, itidy, ixmllint, iyamllint, iyamlschema, iyq, jq, jshint, jslint, json_schema, jsonlint, license_header, luacheck, make, markdownlint, marp_images, mdl, mypy, oxlint, perlcritic, php_lint, prettier, pylint, pyrefly, pytest, ruff, rumdl, script, selene, shellcheck, slidev, standard, stylelint, svglint, svgo, taplo, terms, tidy, xmllint, yamllint, yq, zspell

Generator

A generator transforms each input file into one or more output files. It creates one product per input file (or one per input x format pair for multi-format generators like pandoc).

How it works

  1. Scans for files matching src_extensions in src_dirs
  2. For each input file, computes the output path from the input path, output directory, and format
  3. Creates one product per input x format pair
  4. Runs the tool to produce the output file
  5. Stores the output as a content-addressed blob in the cache

What gets cached

One blob per output file. The blob is the raw file content, stored by its SHA-256 hash. On restore, the blob is hardlinked (or copied) to the output path.

Examples

Render Tera templates:

[processor.generator.tera]

Scans tera.templates/ for .tera files, renders each template. The output path is the template path with the .tera extension stripped:

tera.templates/config.py.tera → config.py
tera.templates/README.md.tera → README.md

Convert Marp slides to PDF:

[processor.generator.marp]

Scans marp/ for .md files, converts each to PDF (and optionally other formats):

marp/slides.md → out/processor.generator.marp/slides.pdf
marp/intro.md → out/processor.generator.marp/intro.pdf

Convert documents with pandoc (multi-format):

[processor.generator.pandoc]

Scans pandoc/ for .md files, converts each to PDF, HTML, and DOCX. Each format is a separate product with its own cache entry:

pandoc/syllabus.md → out/processor.generator.pandoc/syllabus.pdf
pandoc/syllabus.md → out/processor.generator.pandoc/syllabus.html
pandoc/syllabus.md → out/processor.generator.pandoc/syllabus.docx

Compile single-file C programs:

[processor.generator.cc_single_file]

Scans src/ for .c and .cc files, compiles each into an executable:

src/main.c → out/processor.generator.cc_single_file/src/main.elf
src/test.c → out/processor.generator.cc_single_file/src/test.elf

Convert Mermaid diagrams:

[processor.generator.mermaid]

Scans for .mmd files, converts each to PNG (configurable formats):

diagrams/flow.mmd → out/processor.generator.mermaid/diagrams/flow.png

Compile SCSS to CSS:

[processor.generator.sass]

Scans sass/ for .scss and .sass files, compiles each to CSS:

sass/styles.scss → out/processor.generator.sass/styles.css

Built-in generators

a2x, cc_single_file, chromium, drawio, generic, ijinja2, imarkdown2html, ipdfunite, iprotobuf, isass, jinja2, libreoffice, mako, markdown2html, marp, mermaid, objdump, pandoc, pdflatex, pdfunite, protobuf, requirements, rust_single_file, sass, tags, tera, yaml2json

Explicit

An explicit processor aggregates many inputs into (possibly) many output files and/or directories. Unlike other types which create one product per discovered file, explicit creates a single product with all declared inputs and outputs.

How it works

  1. Inputs are listed explicitly via inputs and input_globs in the config
  2. Creates a single product with all inputs and all outputs
  3. Runs the command, passing --inputs and --outputs on the command line
  4. Stores each output file as a content-addressed blob

What gets cached

One blob per output file (like generator).

Examples

Build a static site from generated HTML:

[processor.explicit.generic.site]
command = "python3"
args = ["build_site.py"]
input_globs = ["out/processor.generator.pandoc/*.html", "templates/*.html"]
inputs = ["site.yaml"]
outputs = ["out/site/index.html", "out/site/style.css"]

Waits for pandoc to produce HTML files, then combines them with templates into a site. All inputs are aggregated into one product:

out/processor.generator.pandoc/page1.html, out/processor.generator.pandoc/page2.html, templates/base.html, site.yaml → out/site/index.html, out/site/style.css

Merge PDFs into a course bundle:

[processor.explicit.generic.course]
command = "pdfunite"
input_globs = ["out/processor.generator.pdflatex/*.pdf"]
outputs = ["out/course/full-course.pdf"]

Aggregates all PDF outputs from pdflatex into a single merged PDF.

Built-in explicit processors

generic

Mass Generator

A mass generator runs a tool that produces many files in one invocation and enumerates them in advance. Every planned file becomes a product of its own with its own inputs and blob cache entry, while the underlying build tool runs at most once per manifest per build: once for the whole instance when the tool has a single plan (generic), once per manifest file when the processor scans for them (one make per linux-module.yaml, one cargo build per Cargo.toml).

How it works

  1. At graph-build time, computes the output plan (natively or via a planner command)
  2. Creates one product per planned file: inputs = the entry’s sources, outputs = its path
  3. Classifies each product individually (skip, restore, or build)
  4. The first product that needs building runs command; the rest find the tool already run
  5. Compares the files the tool wrote against the plan — a missing predicted file or an off-plan write fails the build. Files another product declares, as output_files or under its output_dirs, are that product’s and are not reported even when it writes them during the tool’s run
  6. Stores each predicted file as a content-addressed blob

What gets cached

One blob per predicted file (like generator). When every product is clean the tool does not run at all; when some are, the tool runs once and every dirty product caches its own file.

Examples

A static site generator with a plan mode:

[processor.mass_generator.generic.site]
command         = "rssite"
args            = ["build"]
predict_command = "rssite"
predict_args    = ["plan"]
output_dirs     = ["_site"]

rssite plan prints which _site/*.html files rssite build will write and which sources each depends on. Changing docs/about.md dirties only _site/about/index.html; a linter scanning _site/ depends on each page individually.

docs/index.md, templates/default.html → _site/index.html
docs/about.md, templates/default.html → _site/about/index.html

Build documentation with Sphinx:

[processor.mass_generator.sphinx]
dep_inputs = ["src/mypkg/**/*.py", "pyproject.toml", "uv.lock"]

Asks Sphinx itself which files sphinx-build -b html will write (a planner runs Sphinx’s read phase, nothing more), builds sphinx/ into docs/ through a staging directory, and caches every page, static file and index as a product of its own.

sphinx/*, src/mypkg/**/*.py → docs/index.html, docs/mypkg.html, docs/_static/basic.css, …

Built-in mass generators

cargo, cc, gem, generic, jekyll, linux_module, mdbook, npm, pip, sphinx, zola

Any tool that honors the manifest contract plugs into the config-driven generic mass generator via [processor.mass_generator.generic.NAME]; see Mass Generator.

Comparison

CheckerGeneratorExplicitMass Generator
PurposeValidateTransformAggregateTransform, many at once
InputsScannedScannedDeclared in configDeclared by the tool’s plan
ProductsOne per inputOne per input (x format)One totalOne per predicted file
OutputsNoneDerived from input pathDeclared filesPredicted files
Cache typeMarkerBlobBlobBlob
Runs inProject rootProject rootProject rootProject root / anchor dir
Command argsInput filesInput + output--inputs + --outputsUser-defined args
BatchingOptional: a processor that supports it runs once over all its filesOptional: a processor that supports it runs once over the ready inputsAlways: one invocation, every inputAlways: the tool runs once per manifest and writes every product of it