Static site generation in dune
2024-11-09
category: uncategorized
It’s been a while. People have suggested that I blog again, so here I am. Mostly it’s been a lack of time, but getting my static site generator working again was too painful for me to muster the energy to write anything. Let’s fix that first.
Dune, the build system often used for OCaml projects, is pretty general, and you can use it as a Makefile-on-steroids.
Here’s a simple dune rule for building a markdown file into an html file:
(rule
(deps build.sh 2024-11-09-static-site-generation-in-dune.md)
(targets 2024-11-09-static-site-generation-in-dune.html)
(action
(run build.sh 2024-11-09-static-site-generation-in-dune.md 2024-11-09-static-site-generation-in-dune.html)))
Where build.sh
can do whatever you want to
turn the markdown into html. I use pandoc for this site.
There’s an immediate problem with this approach. We need to
write one rule for every markdown file! What we want is
something like recursive
Makefiles to generate rules for each markdown file, but
for dune.
Fortunately, the dune docs have a tutorial on “rule generation” that walks us through doing just this.
The main dune file in our directory would look like this:
(include dune.inc)
(rule
(deps
(source_tree .))
(action
(with-stdout-to
dune.inc.gen
(run ../gen/gen.exe))))
(rule
(alias default)
(action
(diff dune.inc dune.inc.gen)))
The idea here is that we’ll have a program-generated
dune.inc
file which this dune
file
will include. Whenever there is a change in the source
directory containing markdown files,
../gen/gen.exe
will be invoked to re-generate
the dune.inc
file, which will then result in a
diff between the old and new dune.inc
files due
to the (diff ...)
rule.
dune promote
will update the generated
dune.inc
file to the new version, and
dune build
will rebuild all the markdown files.
Using dune build -w @default --auto-promote
will automate the promotion and watch for changes.
The source for gen.exe
looks like this:
open! Core
let generate_pragma slug =
print_endline
string
[%
{|
(rule
(deps build.sh %{slug}.md)
(targets %{slug}.html)
(action
(run build.sh %{slug}.md %{slug}.html)))|}]
;;
let () =
let source_files =
"."
Sys_unix.ls_dir List.sort ~compare:String.compare
|> List.filter_map ~f:(fun file -> String.chop_suffix file ~suffix:".md")
|> in
List.iter source_files ~f:generate_pragma;
let output_files = List.map source_files ~f:(fun file -> [%string "%{file}.html"]) in
print_endline
string
[%
{|
(alias
(name default)String.concat ~sep:" " output_files}))
(deps %{
|}] ;;
We also want to generate an index page, so we’ll add an rule for that:
(rule
(deps ./index.sh (source_tree .))
(action
(with-stdout-to
index.html
(run ./index.sh))))
You can see the full source for this site on GitHub.
index | atom/rss | generated off rev d866987