wattle
Wattle walks a set of input directories, hands every file to a Lua script you wrote, and puts whatever comes back into an output directory. That is the whole model.
It is not a static site generator. There is no configuration file, no theme directory, and no opinion about what a post is. What you get instead is a pipeline with three stages and a Lua standard library aimed at the work a site build turns out to be: Markdown, templates, SCSS, images, feeds, and the long tail of formats you end up parsing.
If you want a static site generator, use one — they are good, and most sites are the shape they assume. This is for when you have wanted to change how one works and found that the answer was a plugin API.
Install
Wattle is a flake, which is the shortest path to a working binary because it brings its own C libraries with it:
nix build github:collectedly/wattle # binary at ./result/bin/wattle
nix run github:collectedly/wattle -- build --input content
nix develop gives you a shell with cargo, clippy, rust-analyzer and everything the build links against. nix flake check runs the test suite, clippy with warnings denied, a formatting check, and a build of this site.
Without Nix
Cargo cannot install the C libraries wattle links against, so they have to come from somewhere first: LuaJIT for the Lua runtime, OpenSSL for the HTTP client, and nasm for the SIMD kernels mozjpeg assembles.
doas apk add luajit-dev openssl-dev nasm # Alpine
brew install luajit openssl nasm # macOS
sudo apt install libluajit-5.1-dev libssl-dev nasm # Debian, Ubuntu
Then:
git clone https://github.com/collectedly/wattle.git
cd wattle
cargo build --release
cp target/release/wattle ~/.local/bin/
A First Build
Put a wattle.lua beside a content directory:
function wattle.gather(path)
if wattle.path.extname(path) == ".md" then
return { type = "page", file = path }
end
end
function wattle.process(item)
item.content = wattle.markdown.render(item.file)
return item
end
function wattle.transform(item)
local out = o(wattle.path.basename(item.file, ".md") .. ".html")
wattle.file.write(out, wattle.hbs.render("layout.hbs", item))
return out
end
Then:
wattle build --input content --output dist
The entry script is a positional argument that defaults to wattle.lua, so the common case needs no flag for it. --input repeats.
One Thing to Know First
Every stage function goes on the wattle table. A plain function gather(path) is not an error and is not found either: the runner looks the name up on wattle, misses, skips the stage, and reports success. A build that matched nothing and wrote nothing exits zero.
It is the first mistake nearly everyone makes, including us, and the reason every derivation that builds a wattle site should assert that the output contains something.
Where to Go Next
How a build works is the model: three stages, six hooks, and what each command does. The other pages are reference, grouped by what you are reaching for — prose and templates, files and images, data formats, and everything touching the machine.