Repository navigation
We embedded Lua 5.4 into a static file server. On purpose.
🌙 Lua URL Rewriting Engine
CaseDropper now ships with an embedded Lua 5.4.7 scripting engine for URL rewriting. Set LUA_SCRIPT to a Lua file that defines a rewrite(path) function, and CaseDropper will generate URL aliases at index build time. Both the original path and the rewritten alias serve the same file.
The key architectural decision: Lua runs at index time, not per-request. The path map is built once (and rebuilt on file changes), so request handling is still just a hash map lookup. We added a scripting engine and it costs exactly zero nanoseconds at runtime.
LUA_SCRIPT=scripts/smallcaps.lua WWWROOT=wwwroot ./zig-out/bin/casedropper
# /index.html → 200 ✅
# /ɪɴᴅᴇx.html → 200 ✅📜 Write Your Own
function rewrite(path)
-- return string for alias, nil to skip
return path:gsub("hello", "привет")
endDrop it in scripts/, set LUA_SCRIPT, restart. The path map rebuilds with your aliases. File watching picks up changes automatically.
Example scripts ship in scripts/ — discoverable by the curious.
🔗 URL Percent-Decoding
PathMap.normalizePath now percent-decodes URLs before lookup. Unicode characters in URLs work correctly when the browser percent-encodes them. This also fixes edge cases with filenames containing spaces or special characters.
Breaking Changes
- musl libc is now statically linked — Lua needs libc. The binary is still fully static and the scratch container still works, but the binary is slightly larger.
PathMap.init()now takes an optionalLuaEnginepointer as its third argument.
Technical Details
- Lua 5.4.7 source vendored in
deps/lua/src/(32 C files, compiled by Zig's build system) - Zero external dependencies at build time — Zig compiles the Lua C sources alongside the Zig code
- Cross-compilation to all 9 architectures still works (Lua is pure C99)
- Lua engine lifecycle: init once at startup, called during path map build/rebuild, deinit at shutdown
- Error handling: script load failures are fatal; runtime errors in
rewrite()are logged and skipped - File extensions are preserved by bundled scripts (only stems are transformed)
- First-write-wins collision handling — real paths always beat Lua aliases