about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Content.zig210
-rw-r--r--src/Context.zig242
-rw-r--r--src/FileSwapper.zig101
-rw-r--r--src/gen.zig255
-rw-r--r--src/inc/2025.htm12
-rw-r--r--src/inc/2025.md12
-rw-r--r--src/inc/2026.htm2
-rw-r--r--src/inc/2026.md2
-rw-r--r--src/inc/japan.htm16
-rw-r--r--src/inc/meta.baseof.htm36
-rw-r--r--src/inc/meta.nav.htm12
-rw-r--r--src/inc/nikko.htm2
-rw-r--r--src/inc/nikko.md2
-rw-r--r--src/inc/notes.htm12
-rw-r--r--src/inc/notes.md12
-rw-r--r--src/main.zig782
-rw-r--r--src/os.zig84
17 files changed, 767 insertions, 1027 deletions
diff --git a/src/Content.zig b/src/Content.zig
deleted file mode 100644
index 120d8b5..0000000
--- a/src/Content.zig
+++ /dev/null
@@ -1,210 +0,0 @@
-const std = @import("std");
-const os = @import("os.zig");
-
-const Content = @This();
-const log = std.log.scoped(.Content);
-
-pub const Digest = [64]u8;
-
-io: std.Io,
-arena: *std.heap.ArenaAllocator,
-root: std.Io.Dir,
-
-pub fn init(io: std.Io, allocator: std.mem.Allocator, root_dir: std.Io.Dir) !Content {
-    const arena = try allocator.create(std.heap.ArenaAllocator);
-    errdefer allocator.destroy(arena);
-    arena.* = std.heap.ArenaAllocator.init(allocator);
-
-    return .{
-        .io = io,
-        .arena = arena,
-        .root = root_dir,
-    };
-}
-
-pub fn deinit(self: *Content) void {
-    self.root.close(self.io);
-    const allocator = self.arena.child_allocator;
-    self.arena.deinit();
-    allocator.destroy(self.arena);
-}
-
-pub fn digest(self: *Content, dir: std.Io.Dir, sub_path: []const u8) !Digest {
-    // generate sha256 digest
-    // TODO: use zig-based hashing?
-    const command = blk: {
-        var buf: [std.os.linux.PATH_MAX + 10]u8 = undefined;
-        @memcpy(buf[0..10], "sha256sum ");
-        @memcpy(buf[10 .. 10 + sub_path.len], sub_path);
-        break :blk buf[0 .. 10 + sub_path.len];
-    };
-    var buf: [std.os.linux.PATH_MAX + 64]u8 = undefined;
-    var w = std.Io.Writer.fixed(&buf);
-    try os.captureStdout(self.io, command, .{ .cwd = .{ .dir = dir } }, &w);
-
-    // take only the digest
-    return buf[0..64].*;
-}
-
-pub fn get(self: *Content, dgst: Digest) !?Digest {
-    const found = blk: {
-        self.root.access(self.io, &dgst, .{}) catch |err| switch (err) {
-            error.FileNotFound => break :blk false,
-            else => return err,
-        };
-        break :blk true;
-    };
-    if (found) {
-        return dgst;
-    } else {
-        return null;
-    }
-}
-
-pub fn add(self: *Content, dir: std.Io.Dir, sub_path: []const u8) !Digest {
-    const dgst = try self.digest(dir, sub_path);
-
-    return (try self.get(dgst)) orelse {
-        // create digest directory
-        try self.root.createDirPath(self.io, &dgst);
-
-        log.debug("Added {s}", .{dgst});
-        return dgst;
-    };
-}
-
-pub fn del(self: *Content, dgst: Digest) !void {
-    try self.root.deleteTree(self.io, &dgst);
-}
-
-const Enumerator = struct {
-    content: *Content,
-    walker: std.Io.Dir.SelectiveWalker,
-
-    pub fn deinit(self: *Enumerator) void {
-        self.walker.deinit();
-    }
-
-    pub fn next(self: *Enumerator) !?Digest {
-        if (try self.walker.next(self.content.io)) |e| {
-            switch (e.kind) {
-                // TODO: we should probably check that this is actually a digest
-                .directory => return e.basename[0..64].*,
-                else => return null,
-            }
-        } else {
-            return null;
-        }
-    }
-};
-
-pub fn enumerate(self: *Content, allocator: std.mem.Allocator) !Enumerator {
-    return .{
-        .walker = try self.root.walkSelectively(allocator),
-        .content = self,
-    };
-}
-
-pub fn propSet(self: *Content, dgst: Digest, key: []const u8, val: ?[]const u8) !void {
-    const digest_dir = try self.root.openDir(self.io, &dgst, .{});
-    defer digest_dir.close(self.io);
-
-    if (val == null) {
-        return try digest_dir.deleteFile(self.io, key);
-    }
-
-    return try digest_dir.writeFile(self.io, .{
-        .sub_path = key,
-        .data = val.?,
-    });
-}
-
-pub fn propGet(self: *Content, dgst: Digest, key: []const u8, allocator: std.mem.Allocator) !?[]const u8 {
-    const digest_dir = try self.root.openDir(self.io, &dgst, .{});
-    defer digest_dir.close(self.io);
-
-    if (digest_dir.readFileAlloc(self.io, key, allocator, .unlimited)) |val| {
-        return val;
-    } else |err| switch (err) {
-        error.FileNotFound => return null,
-        else => return err,
-    }
-}
-
-// fn checkLink(self: *Content, allocator: std.mem.Allocator, dgst: Digest) ![]const u8 {
-//     const link_target = try self.propGet(dgst, "link", allocator);
-//     // check target exists
-//     try std.Io.Dir.cwd().access(self.io, link_target, .{ .read = true });
-//     return link_target;
-// }
-
-test "e2e" {
-    std.testing.log_level = .debug;
-
-    const DEMO_DIR: []const u8 = "/home/robert/tmp/rbc-demo/";
-    const CONTENT_DIR: []const u8 = DEMO_DIR ++ ".content";
-
-    try std.Io.Dir.cwd().deleteTree(std.testing.io, CONTENT_DIR);
-
-    const demo_dir = try std.Io.Dir.cwd().openDir(std.testing.io, DEMO_DIR, .{ .iterate = true });
-    const content_dir = try std.Io.Dir.cwd().createDirPathOpen(std.testing.io, CONTENT_DIR, .{ .open_options = .{ .iterate = true } });
-
-    var cnt = try Content.init(std.testing.io, std.testing.allocator, content_dir);
-    defer cnt.deinit();
-
-    {
-        var walker = try demo_dir.walkSelectively(std.testing.allocator);
-        defer walker.deinit();
-
-        while (try walker.next(std.testing.io)) |e| {
-            switch (e.kind) {
-                .file => {
-                    log.info("+ {s}", .{e.path});
-                    const dgst = try cnt.add(demo_dir, e.path);
-                    try content_dir.access(std.testing.io, &dgst, .{});
-                    const rel = try std.fs.path.relativePosix(std.testing.allocator, DEMO_DIR, CONTENT_DIR, e.path);
-                    defer std.testing.allocator.free(rel);
-                    try cnt.propSet(dgst, "link", rel);
-                },
-                else => {},
-            }
-        }
-    }
-
-    var etor = try cnt.enumerate(std.testing.allocator);
-    defer etor.deinit();
-
-    {
-        const dgst = try etor.next();
-        try std.testing.expect(dgst != null);
-
-        try cnt.propSet(dgst.?, "foo", "bar");
-        const val1 = try cnt.propGet(dgst.?, "foo", std.testing.allocator);
-        try std.testing.expect(val1 != null);
-        defer std.testing.allocator.free(val1);
-        try std.testing.expect(std.mem.eql(u8, "bar", val1.?));
-
-        try cnt.propSet(dgst.?, "foo", null);
-        const val2 = try cnt.propGet(dgst.?, "foo", std.testing.allocator);
-        try std.testing.expect(val2 == null);
-    }
-
-    {
-        const dgst = try etor.next();
-        try std.testing.expect(dgst != null);
-
-        try cnt.del(dgst.?);
-        const err = content_dir.access(std.testing.io, &dgst.?, .{});
-        try std.testing.expectError(error.FileNotFound, err);
-    }
-
-    while (try etor.next()) |dgst| {
-        log.info("? {s}", .{dgst});
-        try content_dir.access(std.testing.io, &dgst, .{});
-        const link = try cnt.propGet(dgst, "link", std.testing.allocator);
-        try std.testing.expect(link != null);
-        defer std.testing.allocator.free(link.?);
-        try content_dir.access(std.testing.io, link.?, .{});
-        log.info("l {s}", .{link.?});
-    }
-}
diff --git a/src/Context.zig b/src/Context.zig
deleted file mode 100644
index f5a638e..0000000
--- a/src/Context.zig
+++ /dev/null
@@ -1,242 +0,0 @@
-const std = @import("std");
-
-const Content = @import("Content.zig");
-const FileSwapper = @import("FileSwapper.zig");
-const gen = @import("gen.zig");
-
-const Context = @This();
-const log = std.log.scoped(.Context);
-
-pub const Defaults = struct {
-    // TODO: should this be next to the root_path of Context.index?
-    pub const CONTENT_PATH = ".rbc/content";
-    pub const CONTENT_REF = "../..";
-
-    pub const SITE_NAME = "gzr.im";
-    pub const LICENSE_HOLDER = "robertgzr";
-    pub const LICENSE_YEAR = "2025";
-    pub const LICENSE_NAME = "BY-NC-SA 4.0";
-    pub const LICENSE_URL = "https://git.sr.ht/~robertgzr/site/blob/master/LICENSE.content";
-    pub const SOURCE_URL = "https://git.sr.ht/~robertgzr/site/tree/master";
-    pub const PUBLIC_INBOX = "~robertgzr/public-inbox@lists.sr.ht";
-};
-
-io: std.Io,
-arena: std.heap.ArenaAllocator,
-content: ?Content = null,
-root: ?std.Io.Dir = null,
-
-pub fn init(io: std.Io, gpa: std.mem.Allocator) !Context {
-    return .{
-        .io = io,
-        .arena = std.heap.ArenaAllocator.init(gpa),
-    };
-}
-
-pub fn deinit(self: *Context) void {
-    if (self.content != null) {
-        self.content.?.deinit();
-    }
-    self.arena.deinit();
-}
-
-pub fn index(self: *Context, root_dir: std.Io.Dir) !void {
-    if (self.content == null) {
-        // setup content store
-        const content_dir = try root_dir.createDirPathOpen(
-            self.io,
-            Defaults.CONTENT_PATH,
-            .{ .open_options = .{ .iterate = true } },
-        );
-        self.content = try Content.init(self.io, self.arena.allocator(), content_dir);
-        self.root = root_dir;
-    } else {
-        try self.gc();
-    }
-
-    var walk = try root_dir.walkSelectively(self.arena.allocator());
-    defer walk.deinit();
-
-    var digests: std.ArrayList(Content.Digest) = .empty;
-    defer digests.deinit(self.arena.allocator());
-
-    while (try walk.next(self.io)) |e| {
-        switch (e.kind) {
-            .file => {
-                if (std.mem.endsWith(u8, e.basename, ".htm")) {
-                    const dgst = try self.content.?.add(root_dir, e.path);
-                    try self.content.?.propSet(dgst, "link", e.path);
-
-                    try digests.append(self.arena.allocator(), dgst);
-                    log.debug("Indexing page {s} (digest={s})", .{ e.path, dgst });
-                }
-            },
-            else => {},
-        }
-    }
-
-    log.info("Indexed {d} pages", .{digests.items.len});
-}
-
-// garbage collect
-fn gc(self: *Context) !void {
-    if (self.content == null) {
-        return error.NoIndex;
-    }
-
-    var etor = try self.content.?.enumerate(self.arena.allocator());
-    defer etor.deinit();
-
-    var gc_n: usize = 0;
-    while (try etor.next()) |dgst| {
-        if (try self.content.?.propGet(dgst, "link", self.arena.allocator())) |link| {
-            defer self.arena.allocator().free(link);
-
-            // cleanup entries with missing link
-            self.root.?.access(self.io, link, .{}) catch |err| switch (err) {
-                error.FileNotFound => {
-                    try self.content.?.del(dgst);
-                    gc_n += 1;
-                },
-                else => return err,
-            };
-        }
-    }
-
-    log.info("Garbage collected {d} pages", .{gc_n});
-}
-
-pub fn render(self: *Context, site_dir: std.Io.Dir, force: bool) !void {
-    if (self.content == null) {
-        return error.NoIndex;
-    }
-
-    log.info("Rendering site", .{});
-
-    var page_n: u32 = 0;
-
-    var etor = try self.content.?.enumerate(self.arena.allocator());
-    defer etor.deinit();
-
-    // render pages
-    while (try etor.next()) |dgst| {
-        if (!force and try self.hasRenderPage(dgst, site_dir)) {
-            continue; // no need to re-render
-        }
-
-        try self.renderPage(site_dir, dgst);
-        page_n += 1;
-    }
-
-    log.info("Rendered {d} pages", .{page_n});
-}
-
-fn hasRenderPage(self: *Context, dgst: Content.Digest, site_dir: std.Io.Dir) !bool {
-    const render_path = try self.content.?.propGet(dgst, "render", self.arena.allocator());
-    if (render_path == null) {
-        return false;
-    }
-    defer self.arena.allocator().free(render_path.?);
-
-    site_dir.access(self.io, render_path.?, .{}) catch |err| switch (err) {
-        error.FileNotFound => return false,
-        else => return err,
-    };
-
-    log.debug("Skipping {s}", .{render_path.?});
-    return true;
-}
-
-fn renderPage(self: *Context, site_dir: std.Io.Dir, dgst: Content.Digest) !void {
-    var arena = std.heap.ArenaAllocator.init(self.arena.allocator());
-    defer arena.deinit();
-    const gpa = arena.allocator();
-
-    const page_path = try self.content.?.propGet(dgst, "link", self.arena.allocator());
-    if (page_path == null) {
-        return error.NoLink;
-    }
-
-    const page_name = std.mem.cutSuffix(u8, std.fs.path.basename(page_path.?), ".htm").?;
-
-    if (std.mem.startsWith(u8, page_name, "meta.")) {
-        log.warn("skipping meta page => {s}", .{page_name});
-        return;
-    } else {
-        log.debug("Rendering page {s} (name={s})", .{ page_path.?, page_name });
-
-        const render_name = try std.mem.concat(gpa, u8, &.{ page_name, ".html" });
-        const pretty_name = try std.mem.replaceOwned(u8, gpa, page_name, "_", " ");
-
-        const sw = try FileSwapper.createAt(self.io, gpa, site_dir, render_name);
-        defer sw.close();
-        var w = sw.writer();
-
-        try gen.build_htm(self.io, gpa, &w.interface, .{
-            .root_dir = self.root.?,
-            .site_dir = site_dir,
-            .page_path = page_path.?,
-            .page_name = pretty_name,
-            .meta = .{
-                .site_name = Defaults.SITE_NAME,
-                .license_url = Defaults.LICENSE_URL,
-                .license_holder = Defaults.LICENSE_HOLDER,
-                .license_year = Defaults.LICENSE_YEAR,
-                .license_name = Defaults.LICENSE_NAME,
-                .public_inbox = Defaults.PUBLIC_INBOX,
-                .source_url = Defaults.SOURCE_URL,
-            },
-        });
-
-        try self.content.?.propSet(dgst, "render", render_name);
-        try self.content.?.propSet(dgst, "name", pretty_name);
-    }
-}
-
-// test "e2e" {
-//     std.testing.log_level = .debug;
-//
-//     const DEMO_DIR: []const u8 = "/home/robert/tmp/rbc-demo/";
-//     const SITE_DIR: []const u8 = DEMO_DIR ++ "site/";
-//
-//     const demo_dir = try std.Io.Dir.cwd().openDir(std.testing.io, DEMO_DIR, .{ .iterate = true });
-//     defer demo_dir.close(std.testing.io);
-//     const site_dir = try std.Io.Dir.cwd().createDirPathOpen(std.testing.io, SITE_DIR, .{});
-//     defer site_dir.close(std.testing.io);
-//
-//     var ctx = try Context.init(std.testing.io, std.testing.allocator);
-//     defer ctx.deinit();
-//
-//     try ctx.index(demo_dir);
-//     try ctx.render(site_dir);
-//
-//     // TODO:
-//     // index; build a list of input files
-//     // parse; detect backlinks, dependencies, etc.
-//     // render; write output
-// }
-
-test "gc" {
-    std.testing.log_level = .debug;
-
-    var tmp = std.testing.tmpDir(.{ .iterate = true });
-    defer tmp.cleanup();
-
-    const tmp_src = try tmp.dir.createDirPathOpen(std.testing.io, "src", .{ .open_options = .{ .iterate = true } });
-    // const tmp_dst = try tmp.dir.createDirPathOpen(std.testing.io, "dst", .{});
-
-    try tmp_src.writeFile(std.testing.io, .{ .sub_path = "foo.htm", .data = "hello world" });
-
-    var ctx = try Context.init(std.testing.io, std.testing.allocator);
-    defer ctx.deinit();
-
-    try ctx.index(tmp_src);
-    try tmp_src.deleteFile(std.testing.io, "foo.htm");
-
-    try ctx.index(tmp_src);
-    var etor = try ctx.content.?.enumerate(std.testing.allocator);
-    defer etor.deinit();
-    var seen: u32 = 0;
-    while (try etor.next()) |_| seen += 1;
-    try std.testing.expect(seen == 0);
-}
diff --git a/src/FileSwapper.zig b/src/FileSwapper.zig
deleted file mode 100644
index 13bafe4..0000000
--- a/src/FileSwapper.zig
+++ /dev/null
@@ -1,101 +0,0 @@
-const std = @import("std");
-
-const FileSwapper = @This();
-const log = std.log.scoped(.FileSwapper);
-
-io: std.Io,
-arena: *std.heap.ArenaAllocator,
-dir: std.Io.Dir,
-target_path: []const u8,
-tmp_path: []const u8,
-tmp: std.Io.File,
-
-pub fn createAt(io: std.Io, allocator: std.mem.Allocator, dir: std.Io.Dir, sub_path: []const u8) !FileSwapper {
-    const arena = try allocator.create(std.heap.ArenaAllocator);
-    errdefer allocator.destroy(arena);
-    arena.* = std.heap.ArenaAllocator.init(allocator);
-
-    const tmp_path = try std.fmt.allocPrint(
-        arena.allocator(),
-        ".{s}",
-        .{sub_path},
-    );
-
-    log.debug("Creating {s}", .{tmp_path});
-
-    const f = try dir.createFile(io, tmp_path, .{});
-
-    return .{
-        .io = io,
-        .arena = arena,
-        .dir = dir,
-        .target_path = try arena.allocator().dupe(u8, sub_path),
-        .tmp_path = tmp_path,
-        .tmp = f,
-    };
-}
-
-fn deinit(self: FileSwapper) void {
-    self.tmp.close(self.io);
-    const allocator = self.arena.child_allocator;
-    self.arena.deinit();
-    allocator.destroy(self.arena);
-}
-
-pub fn file(self: FileSwapper) std.fs.File {
-    return self.tmp;
-}
-
-pub fn writer(self: FileSwapper) std.Io.File.Writer {
-    const buf = self.arena.allocator().alloc(u8, 1024) catch
-        @panic("oom");
-    return self.tmp.writer(self.io, buf);
-}
-
-pub fn swap(self: FileSwapper) !void {
-    log.debug("Swapping {s} -> {s}", .{ self.tmp_path, self.target_path });
-
-    // flush writes
-    try self.tmp.sync(self.io);
-
-    // swap file into final location
-    // NOTE: the tmp fd is still valid after this
-    try std.Io.Dir.rename(
-        self.dir,
-        self.tmp_path,
-        self.dir,
-        self.target_path,
-        self.io,
-    );
-}
-
-pub fn close(self: FileSwapper) void {
-    self.swap() catch
-        @panic("failed to swap target file");
-
-    self.deinit();
-}
-
-test "smoke" {
-    std.testing.log_level = .debug;
-
-    const alloc = std.testing.allocator;
-
-    var tmp = std.testing.tmpDir(.{});
-    defer tmp.cleanup();
-
-    const fileswapper = try FileSwapper.createAt(std.testing.io, alloc, tmp.dir, "test.txt");
-    errdefer fileswapper.deinit();
-
-    var w = fileswapper.writer();
-    try w.interface.writeAll("hi");
-
-    try tmp.dir.access(std.testing.io, fileswapper.tmp_path, .{ .read = true, .write = true });
-
-    try fileswapper.swap();
-
-    const err = tmp.dir.access(std.testing.io, fileswapper.tmp_path, .{ .read = true, .write = true });
-    try std.testing.expectError(error.FileNotFound, err);
-
-    fileswapper.deinit();
-}
diff --git a/src/gen.zig b/src/gen.zig
deleted file mode 100644
index 8def5cf..0000000
--- a/src/gen.zig
+++ /dev/null
@@ -1,255 +0,0 @@
-const std = @import("std");
-
-const os = @import("os.zig");
-
-const log = std.log.scoped(.gen);
-
-const DELIM_START: []const u8 = "{[";
-const DELIM_END: []const u8 = "]}";
-
-pub const BuildOpts = struct {
-    root_dir: std.Io.Dir,
-    site_dir: std.Io.Dir,
-
-    page_path: []const u8,
-    page_name: []const u8,
-
-    meta: struct {
-        source_url: []const u8,
-        site_name: []const u8,
-        license_url: []const u8,
-        license_holder: []const u8,
-        license_name: []const u8,
-        license_year: []const u8,
-        public_inbox: []const u8,
-    },
-};
-
-// build a single html file
-pub fn build_htm(io: std.Io, gpa: std.mem.Allocator, w: *std.Io.Writer, opts: BuildOpts) !void {
-    const stat = try opts.root_dir.statFile(io, opts.page_path, .{});
-    const mtime_str = try os.formatTimestamp(gpa, stat.mtime, "%F");
-    defer gpa.free(mtime_str);
-
-    // HEAD
-    try w.print(
-        \\<!-- Generated file, do not edit, you may edit and discard {[SOURCE]s} :) -->
-        \\<!DOCTYPE html>
-        \\<html lang="en">
-        \\<head>
-        \\<meta charset="utf-8">
-        \\<meta name="thumbnail" content="/media/services/thumbnail.jpg">
-        \\<meta name="viewport" content="width=device-width,initial-scale=1">
-        \\<link rel="preload" href="/media/interface/logo.svg" as="image">
-        \\<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/links/rss.xml">
-        \\<link rel="stylesheet" type="text/css" href="/links/main.css">
-        \\<link rel="shortcut icon" type="image/png" href="/media/services/icon.png">
-        \\<link rel="license" href="{[LICENSE_URL]s}">
-        \\<title>{[SITE_NAME]s} &mdash; {[PAGE_NAME]s}</title>
-        \\</head>
-        \\<body>
-        \\<header>
-        \\<a href="/"><img src="/media/interface/logo.svg" alt="{[SITE_NAME]s}"></a>
-        \\
-    , .{
-        .SOURCE = opts.page_path,
-        .PAGE_NAME = opts.page_name,
-        .LICENSE_URL = opts.meta.license_url,
-        .SITE_NAME = opts.meta.site_name,
-    });
-
-    // NAV
-    try w.writeAll(
-        \\<nav>
-    );
-    template_portal(io, gpa, w, "meta.nav", opts) catch |err| {
-        if (err != error.FileNotFound) return err;
-    };
-    try w.writeAll(
-        \\</nav>
-        \\</header>
-        \\
-    );
-
-    // MAIN
-    try w.writeAll(
-        \\<main>
-        \\
-    );
-    try inject(io, gpa, w, opts);
-    try w.writeAll(
-        \\</main>
-        \\
-    );
-
-    // FOOTER
-    try w.print(
-        \\<footer>
-        \\Edited on {[MTIME]s} <a href="{[SOURCE_URL]s}/{[PATH]s}" target="_blank">[src]</a>
-        \\<a href="mailto:{[PUBLIC_INBOX]s}">[comments]</a>
-        \\<br>
-        \\<b>{[LICENSE_HOLDER]s}</b>
-        \\&copy; {[LICENSE_YEAR]s}
-        \\&mdash;
-        \\<a href="{[LICENSE_URL]s}" target="_blank">{[LICENSE_NAME]s}</a>
-        \\</footer>
-        \\
-    , .{
-        .MTIME = mtime_str,
-        .PATH = opts.page_path,
-        .LICENSE_HOLDER = opts.meta.license_holder,
-        .LICENSE_NAME = opts.meta.license_name,
-        .LICENSE_URL = opts.meta.license_url,
-        .LICENSE_YEAR = opts.meta.license_year,
-        .PUBLIC_INBOX = opts.meta.public_inbox,
-        .SOURCE_URL = opts.meta.source_url,
-    });
-
-    try w.writeAll(
-        \\<a href="#top">[&#9206;]</a>
-        \\
-    );
-
-    try w.writeAll(
-        \\</body>
-        \\</html>
-    );
-
-    try w.flush();
-}
-
-// parses srcpath, resolves refs and portals and writes everything to w
-fn inject(io: std.Io, gpa: std.mem.Allocator, w: *std.Io.Writer, opts: BuildOpts) !void {
-    var arena = std.heap.ArenaAllocator.init(gpa);
-    defer arena.deinit();
-
-    const inc = opts.root_dir.openFile(io, opts.page_path, .{ .mode = .read_only }) catch |err| {
-        if (err == error.FileNotFound)
-            log.warn("Missing {s} include.", .{opts.page_path});
-
-        return err;
-    };
-    defer inc.close(io);
-
-    const buf = try arena.allocator().alloc(u8, 1024);
-    var r = inc.readerStreaming(io, buf);
-
-    var chunkw = std.Io.Writer.Allocating.init(arena.allocator());
-    defer chunkw.deinit();
-
-    while (true) {
-        defer chunkw.clearRetainingCapacity(); // reset chunk
-
-        _ = r.interface.streamDelimiter(&chunkw.writer, '\n') catch |err|
-            if (err == error.EndOfStream) break else return err;
-
-        _ = r.interface.toss(1); // skip newline delimiter
-
-        var chunk = chunkw.written();
-        templating: while (true) {
-            if (std.mem.indexOfPosLinear(u8, chunk, 0, DELIM_START)) |start| {
-                if (std.mem.indexOfPosLinear(u8, chunk, start + 1, DELIM_END)) |end| {
-                    try w.writeAll(chunk[0..start]);
-                    template(io, gpa, w, chunk[(start + DELIM_START.len)..end], opts) catch |err| switch (err) {
-                        error.FileNotFound => {
-                            // log.warn("ref {s} not found", .{chunk[start + 1 .. end]});
-                            // trim already written chunk and exit templating
-                            chunk = chunk[start..];
-                            break :templating;
-                        },
-                        else => @panic("Failed to resolve template"),
-                    };
-                    chunk = chunk[end + DELIM_END.len ..];
-                } else break :templating;
-            } else break :templating;
-        }
-
-        try w.writeAll(chunk);
-        try w.writeByte('\n');
-        try w.flush();
-    }
-
-    try w.flush();
-}
-
-// implements various reference types that can be used in .htm templates
-fn template(io: std.Io, gpa: std.mem.Allocator, w: *std.Io.Writer, ref_name: []const u8, opts: BuildOpts) anyerror!void {
-    switch (ref_name[0]) {
-        '/' => return try template_portal(io, gpa, w, ref_name[1..], opts),
-        //     '$' => return try ref_link(allocator, w, refname[1..]),
-        //     '+' => return try ref_name(allocator, w, refname[1..]),
-        else => return try template_ref(io, gpa, w, ref_name, opts),
-    }
-}
-
-// insert link
-fn template_ref(io: std.Io, gpa: std.mem.Allocator, w: *std.Io.Writer, ref_name: []const u8, opts: BuildOpts) anyerror!void {
-    log.debug("template:ref (ref={s})", .{ref_name});
-
-    const ref_path = try std.fmt.allocPrint(gpa, "{s}.html", .{ref_name});
-    defer gpa.free(ref_path);
-
-    _ = io;
-    _ = opts;
-
-    try w.print(
-        \\<a href="{s}">{s}</a>
-    , .{ ref_path, ref_name });
-}
-
-// insert contents
-fn template_portal(io: std.Io, gpa: std.mem.Allocator, w: *std.Io.Writer, ref_name: []const u8, opts: BuildOpts) anyerror!void {
-    log.debug("template:portal (ref={s})", .{ref_name});
-
-    const ref_path = try std.fmt.allocPrint(gpa, "{s}.htm", .{ref_name});
-    defer gpa.free(ref_path);
-
-    try opts.root_dir.access(io, ref_path, .{});
-
-    var iopts = opts;
-    iopts.page_path = ref_path;
-    iopts.page_name = ref_name;
-    try inject(io, gpa, w, iopts);
-}
-
-// build a meta file
-pub fn build_meta(io: std.Io, gpa: std.mem.Allocator, w: *std.Io.Writer, opts: BuildOpts) !void {
-    if (std.mem.startsWith(u8, opts.page_name, "meta.nav")) return;
-
-    if (std.mem.startsWith(u8, opts.page_name, "meta.tilde")) {
-        const name = std.mem.cutPrefix(u8, opts.page_name, "meta.tilde.").?;
-        const path = try std.fmt.allocPrint(gpa, "~{s}", .{name});
-        defer gpa.free(path);
-
-        const link = blk: {
-            const content = try opts.root_dir.readFileAlloc(io, opts.page_path, gpa, .unlimited);
-            break :blk std.mem.trim(u8, content, " \n\t\r");
-        };
-        defer gpa.free(link);
-
-        try w.interface.print(
-            \\<!DOCTYPE html>
-            \\<html lang="en">
-            \\<head>
-            \\<meta charset="utf-8">
-            \\<meta http-equiv="refresh" content="0; url={[LINK]s}">
-            \\<meta name="fediverse:creator" content="@robertgzr@toot.berlin">
-            \\<link rel="stylesheet" type="text/css" href="links/main.css">
-            \\</head>
-            \\<body>
-            \\<noscript>Continue to <a href="{[LINK]s}">{[LINK]s}</a>.</noscript>
-            \\<script>
-            \\'use strict'
-            \\window.location.replace("{[LINK]s}")
-            \\</script>
-            \\</body>
-            \\</html>
-            \\
-        , .{
-            .LINK = link,
-        });
-        try w.interface.flush();
-    }
-
-    return error.UnsupportedMeta;
-}
diff --git a/src/inc/2025.htm b/src/inc/2025.htm
index e1f3933..f867484 100644
--- a/src/inc/2025.htm
+++ b/src/inc/2025.htm
@@ -86,7 +86,7 @@ target="_blank">all-systems-go</a></li>
 <li>saw parcels live</li>
 <li>visited amsterdam</li>
 <li>took the sleeper train</li>
-<li>got really into the passage for two weeks ↪ {read#sep25}</li>
+<li>got really into the passage for two weeks ↪ {[read#sep25]}</li>
 <li>had delicious coffee in linden</li>
 <li>played chunithm @ hi-score (again)</li>
 <li>went to hannover (again)</li>
@@ -143,7 +143,7 @@ class="i size-m" alt="coffee" /></p>
 <p><img src="/media/content/2025/apr-train.jpg" class="i"
 alt="train" /></p>
 <ul>
-<li>started {like}</li>
+<li>started {[like]}</li>
 <li>visited the northern part of niedersachsen</li>
 <li>held our first “plancake”</li>
 <li>learned about unikernels 🐒</li>
@@ -166,7 +166,7 @@ class="i size-m" alt="basilica-cistern-medusa" /></p>
 <li>went to <a href="https://en.wikipedia.org/wiki/Basilica_Cistern"
 target="_blank">basilica cistern</a> in istanbul</li>
 <li>had kokoreç again</li>
-<li>published {unicode-range}</li>
+<li>published {[unicode-range]}</li>
 <li>made custom anki cards for turkish</li>
 <li>went to marji’s place in kadiköy</li>
 <li>had lebanese-turkish breakfast</li>
@@ -193,15 +193,15 @@ target="_blank">tuzla</a></li>
 <p><img src="/media/content/2025/jan-sky.jpg" class="i"
 alt="january sky" /></p>
 <ul>
-<li>published {plan9-cursors}</li>
+<li>published {[plan9-cursors]}</li>
 <li>ate at kaijin ramen and got inspired to make mapo tofu</li>
 <li><a href="https://github.com/containers/podman/pull/25029"
 target="_blank">implemented</a> gpu support for podman-kube</li>
 <li>reorganised anime folder on shimakaze</li>
 <li>watched one pace “whole cake island” arc</li>
-<li>published {sony-wf1000xm3-battery}</li>
+<li>published {[sony-wf1000xm3-battery]}</li>
 <li>played siedler von catan</li>
-<li>published {pyonji}</li>
+<li>published {[pyonji]}</li>
 <li>replaced my hugo page with this one</li>
 <li>watched season 1 of mr robot</li>
 <li>started a daily workout and anki (for japanese) routine</li>
diff --git a/src/inc/2025.md b/src/inc/2025.md
index 8d67310..978c4be 100644
--- a/src/inc/2025.md
+++ b/src/inc/2025.md
@@ -43,7 +43,7 @@ toc: true
 - saw parcels live
 - visited amsterdam
 - took the sleeper train
-- got really into the passage for two weeks ↪  {read#sep25}
+- got really into the passage for two weeks ↪  {[read#sep25]}
 - had delicious coffee in linden
 - played chunithm @ hi-score (again)
 - went to hannover (again)
@@ -100,7 +100,7 @@ toc: true
 
 ![](/media/content/2025/apr-train.jpg){alt=train class=i}
 
-- started {like}
+- started {[like]}
 - visited the northern part of niedersachsen
 - held our first "plancake"
 - learned about unikernels 🐒
@@ -120,7 +120,7 @@ toc: true
 - finished my japanese deck on anki
 - went to [basilica cistern](https://en.wikipedia.org/wiki/Basilica_Cistern) in istanbul
 - had kokoreç again
-- published {unicode-range}
+- published {[unicode-range]}
 - made custom anki cards for turkish
 - went to marji's place in kadiköy
 - had lebanese-turkish breakfast
@@ -145,14 +145,14 @@ toc: true
 
 ![](/media/content/2025/jan-sky.jpg){alt="january sky" class=i}
 
-- published {plan9-cursors}
+- published {[plan9-cursors]}
 - ate at kaijin ramen and got inspired to make mapo tofu
 - [implemented](https://github.com/containers/podman/pull/25029) gpu support for podman-kube
 - reorganised anime folder on shimakaze
 - watched one pace "whole cake island" arc
-- published {sony-wf1000xm3-battery}
+- published {[sony-wf1000xm3-battery]}
 - played siedler von catan
-- published {pyonji}
+- published {[pyonji]}
 - replaced my hugo page with this one
 - watched season 1 of mr robot
 - started a daily workout and anki (for japanese) routine
diff --git a/src/inc/2026.htm b/src/inc/2026.htm
index 795ad41..a331b8a 100644
--- a/src/inc/2026.htm
+++ b/src/inc/2026.htm
@@ -48,4 +48,4 @@
 target="_blank">raus.life</a></li>
 </ul>
 <hr />
-<p>↪ {2025}</p>
+<p>↪ {[2025]}</p>
diff --git a/src/inc/2026.md b/src/inc/2026.md
index e26d0e7..35c0985 100644
--- a/src/inc/2026.md
+++ b/src/inc/2026.md
@@ -35,4 +35,4 @@ toc: true
 
 ---
 
-↪ {2025}
+↪ {[2025]}
diff --git a/src/inc/japan.htm b/src/inc/japan.htm
index a2ee56a..305d8b4 100644
--- a/src/inc/japan.htm
+++ b/src/inc/japan.htm
@@ -12,12 +12,12 @@
     The linked pages document those trips:
 </p>
 <ul class="capital">
-    <li>{kamakura}</li>
-    <li>{nikko}</li>
-    <li>{yamanashi}</li>
-    <li>{niigata}</li>
-    <li>{ibaraki}</li>
-    <li>{toyama}</li>
-    <li>{shikoku}</li>
-    <li>{hokkaido}</li>
+    <li>{[kamakura]}</li>
+    <li>{[nikko]}</li>
+    <li>{[yamanashi]}</li>
+    <li>{[niigata]}</li>
+    <li>{[ibaraki]}</li>
+    <li>{[toyama]}</li>
+    <li>{[shikoku]}</li>
+    <li>{[hokkaido]}</li>
 </ul>
diff --git a/src/inc/meta.baseof.htm b/src/inc/meta.baseof.htm
new file mode 100644
index 0000000..462d279
--- /dev/null
+++ b/src/inc/meta.baseof.htm
@@ -0,0 +1,36 @@
+<!-- Generated file, do not edit, you may edit and discard {[builtin.path]} :) -->
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<meta name="thumbnail" content="/media/services/thumbnail.jpg">
+		<meta name="viewport" content="width=device-width,initial-scale=1">
+		<link rel="preload" href="/media/interface/logo.svg" as="image">
+		<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/links/rss.xml">
+		<link rel="stylesheet" type="text/css" href="/links/main.css">
+		<link rel="shortcut icon" type="image/png" href="/media/services/icon.png">
+		<link rel="license" href="https://git.sr.ht/~robertgzr/site/blob/master/LICENSE.content">
+		<title>gzr.im &mdash; {[builtin.title]}</title>
+	</head>
+	<body>
+		<header>
+			<a href="/"><img src="/media/interface/logo.svg" alt="gzr.im"></a>
+			<nav>
+				{[/meta.nav]}
+			</nav>
+		</header>
+		<main>
+			{[builtin.fragment]}
+		</main>
+		<footer>
+			Edited on 2026-04-08 <a href="https://git.sr.ht/~robertgzr/site/tree/master/src/inc/{[builtin.path]}" target="_blank">[src]</a>
+			<a href="mailto:~robertgzr/public-inbox@lists.sr.ht">[comments]</a>
+			<br>
+			<b>robertgzr</b>
+&copy; 2025
+			&mdash;
+			<a href="https://git.sr.ht/~robertgzr/site/blob/master/LICENSE.content" target="_blank">BY-NC-SA 4.0</a>
+		</footer>
+		<a href="#top">[&#9206;]</a>
+	</body>
+</html>
diff --git a/src/inc/meta.nav.htm b/src/inc/meta.nav.htm
index 8e7d70b..afb87c6 100644
--- a/src/inc/meta.nav.htm
+++ b/src/inc/meta.nav.htm
@@ -1,19 +1,19 @@
 <section>
 	<ul class="nobull capital">
-		<li>{about}</li>
-		<li>{2026}</li>
+		<li>{[about]}</li>
+		<li>{[2026]}</li>
 	</ul>
 </section>
 <section>
 	<ul class="nobull capital">
-		<li>{notes}</li>
-		<li>{like}</li>
-		<li>{read}</li>
+		<li>{[notes]}</li>
+		<li>{[like]}</li>
+		<li>{[read]}</li>
 	</ul>
 </section>
 <section>
 	<ul class="nobull capital">
-		<li>{photos}</li>
+		<li>{[photos]}</li>
 		<li><a href="/map/">map</a></li>
 	</ul>
 </section>
diff --git a/src/inc/nikko.htm b/src/inc/nikko.htm
index e9ba14f..aa93bb5 100644
--- a/src/inc/nikko.htm
+++ b/src/inc/nikko.htm
@@ -194,7 +194,7 @@ href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
 class="footnote-back" role="doc-backlink">↩︎</a></p></li>
 <li id="fn5"><p>prior to leaving for Japan, I started experimenting with
 binaural audio recording as a way to capture memories. I have a page
-with some notes around the setup and some recordings here: {audio}<a
+with some notes around the setup and some recordings here: {[audio]}<a
 href="#fnref5" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
 </ol>
 </section>
diff --git a/src/inc/nikko.md b/src/inc/nikko.md
index 69c6f38..70a1325 100644
--- a/src/inc/nikko.md
+++ b/src/inc/nikko.md
@@ -185,7 +185,7 @@ a good sense of how serious I need to take them.
 
 [^rec]: prior to leaving for Japan, I started experimenting with binaural
     audio recording as a way to capture memories. I have a page with some notes
-    around the setup and some recordings here: {audio}
+    around the setup and some recordings here: {[audio]}
 
 <aside class="notice">
 I learn over time that the "bear problem" is taken maybe a little too serious.
diff --git a/src/inc/notes.htm b/src/inc/notes.htm
index fbf96f3..a86f6e4 100644
--- a/src/inc/notes.htm
+++ b/src/inc/notes.htm
@@ -1,9 +1,9 @@
 <!-- Generated file, you may edit and discard notes.md :) -->
 <ul>
-<li><code>2025-11-12</code> — {varlink}</li>
-<li><code>2025-05-14</code> — {porta}</li>
-<li><code>2025-03-07</code> — {unicode-range}</li>
-<li><code>2025-01-24</code> — {plan9-cursors}</li>
-<li><code>2025-01-10</code> — {sony-wf1000xm3-battery}</li>
-<li><code>2025-01-08</code> — {pyonji}</li>
+<li><code>2025-11-12</code> — {[varlink]}</li>
+<li><code>2025-05-14</code> — {[porta]}</li>
+<li><code>2025-03-07</code> — {[unicode-range]}</li>
+<li><code>2025-01-24</code> — {[plan9-cursors]}</li>
+<li><code>2025-01-10</code> — {[sony-wf1000xm3-battery]}</li>
+<li><code>2025-01-08</code> — {[pyonji]}</li>
 </ul>
diff --git a/src/inc/notes.md b/src/inc/notes.md
index 0d6d298..0ca5e7f 100644
--- a/src/inc/notes.md
+++ b/src/inc/notes.md
@@ -1,6 +1,6 @@
-* `2025-11-12` —  {varlink}
-* `2025-05-14` —  {porta}
-* `2025-03-07` —  {unicode-range}
-* `2025-01-24` —   {plan9-cursors}
-* `2025-01-10` —   {sony-wf1000xm3-battery}
-* `2025-01-08` —   {pyonji}
+* `2025-11-12` —  {[varlink]}
+* `2025-05-14` —  {[porta]}
+* `2025-03-07` —  {[unicode-range]}
+* `2025-01-24` —   {[plan9-cursors]}
+* `2025-01-10` —   {[sony-wf1000xm3-battery]}
+* `2025-01-08` —   {[pyonji]}
diff --git a/src/main.zig b/src/main.zig
index 354eed9..2134d45 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,127 +1,723 @@
-// TODO: just let compiler infer error set through recursive calls
-// depends on: https://github.com/ziglang/zig/issues/2971
-
-// This code is heavily insprired by Devine's implementation in C:
-// https://github.com/hundredrabbits/100r.co/blob/main/src/main.c and his
-// personal site https://github.com/XXIIVV/Oscean
-
-const Context = @import("Context.zig");
-const os = @import("os.zig");
-
 const std = @import("std");
+const Io = std.Io;
+const Allocator = std.mem.Allocator;
+const linux = std.os.linux;
+
+const Digester = std.crypto.hash.Md5;
+pub const Digest = [16]u8;
 
 const log = std.log.scoped(.rbc);
 
-// tune std.log options
+const BUFFER_SIZE = 1024;
+
+// tune log options
 pub const std_options = std.Options{
     .log_level = if (@import("builtin").mode == .Debug) .debug else .info,
 };
 
+pub fn main(init: std.process.Init) !void {
+    try Cli.run(init);
+}
+
 const Cli = struct {
-    const Defaults = struct {
-        const ROOT: []const u8 = "src/inc";
-        const SITE: []const u8 = "site";
+    fn usage() void {
+        std.debug.print("usage: rbc SOURCE TARGET-DIR\n\n", .{});
+        std.debug.print("Writes SOURCE into TARGET-DIR, resolving references to other files in ROOT (= parent dir of SOURCE).\n", .{});
+        std.debug.print("\n", .{});
+    }
+
+    fn run(init: std.process.Init) !void {
+        const io = init.io;
+        const gpa = init.gpa;
+
+        const args = try init.minimal.args.toSlice(gpa);
+        defer gpa.free(args);
+
+        // parse options
+        for (args) |arg| {
+            if (std.mem.eql(u8, arg, "help")) return Cli.usage();
+            if (std.mem.eql(u8, arg, "--help")) return Cli.usage();
+            if (std.mem.eql(u8, arg, "-h")) return Cli.usage();
+        }
+
+        if (args.len < 3) return Cli.usage();
+
+        // TODO: flag handling
+
+        const source_path = args[1];
+        const target_path = args[2];
+
+        log.info("target={s} source={s} ", .{ target_path, source_path });
+
+        // TODO: allow setting custom root path via flags
+        const root_path = std.fs.path.dirname(source_path) orelse return error.NoDirname;
+        const root_dir = try Io.Dir.cwd().openDir(io, root_path, .{});
+        defer root_dir.close(io);
+
+        var source = try File.init(io, root_dir, std.fs.path.basename(source_path), null);
+        defer source.close(io);
+
+        // open/create destination dir
+        const target_dir = try Io.Dir.cwd().createDirPathOpen(io, target_path, .{});
+        defer target_dir.close(io);
+
+        const b = try Builder.init(gpa, root_dir, source, target_dir);
+        defer b.deinit(gpa);
+        try b.build(io, gpa);
+    }
+};
+
+const File = struct {
+    path: []const u8,
+    fd: Io.File,
+    mmap: Io.File.MemoryMap,
+    mtime: Io.Timestamp,
+
+    fn init(io: Io, dir: Io.Dir, path: []const u8, options: ?Io.Dir.OpenFileOptions) !File {
+        const fd = try dir.openFile(io, path, options orelse .{
+            .lock = .exclusive,
+            .mode = .read_write,
+        });
+        var mmap = try fd.createMemoryMap(io, .{
+            .len = try fd.length(io),
+            // .protection = .{ .read = true, .write = false },
+        });
+        try mmap.read(io);
+        const stat = try fd.stat(io);
+        return .{
+            .path = path,
+            .fd = fd,
+            .mmap = mmap,
+            .mtime = stat.mtime,
+        };
+    }
+
+    fn digest(self: File) !Digest {
+        var buffer: [Digester.digest_length]u8 = undefined;
+        var d = Digester.init(.{});
+        d.update(self.mmap.memory);
+        var buf2: [BUFFER_SIZE]u8 = undefined;
+        d.update(try std.fmt.bufPrint(&buf2, "{d}", .{self.mtime}));
+        d.final(&buffer);
+        return buffer;
+    }
+
+    fn close(self: *File, io: Io) void {
+        self.mmap.destroy(io);
+        self.fd.close(io);
+    }
+};
+
+const Builder = struct {
+    root_dir: Io.Dir,
+    source: File,
+    target_dir: Io.Dir,
+    options: Options,
+
+    // track files which affect build output (paths relative to root_dir)
+    parts: std.ArrayList([]const u8),
+    // track component digests which should affect digest check
+    dgsts: std.ArrayList(Digest),
+
+    pub const Options = struct {
+        // TODO: this is a bit of a hack no?
+        offset_from_siteroot: ?[]const u8,
     };
 
-    fn run(init: std.process.Init, ctx: *Context) !void {
-        var argv = init.minimal.args.iterate();
+    const DELIM_START: []const u8 = "{[";
+    const DELIM_END: []const u8 = "]}";
+
+    fn init(gpa: Allocator, root_dir: Io.Dir, source: File, target_dir: Io.Dir) !*Builder {
+        const b = try gpa.create(Builder);
+        b.* = .{
+            .root_dir = root_dir,
+            .source = source,
+            .target_dir = target_dir,
+            .options = .{
+                .offset_from_siteroot = "/site",
+            },
+            .parts = .empty,
+            .dgsts = .empty,
+        };
+        return b;
+    }
+
+    fn deinit(self: *Builder, gpa: Allocator) void {
+        for (self.parts.items) |p| gpa.free(p);
+        self.parts.deinit(gpa);
+        self.dgsts.deinit(gpa);
+        gpa.destroy(self);
+    }
+
+    fn build(self: *Builder, io: Io, gpa: Allocator) !void {
+        log.debug("{s}: building", .{self.source.path});
+
+        // track source
+        try self.parts.append(gpa, try gpa.dupe(u8, self.source.path));
+        try self.dgsts.append(gpa, try self.source.digest());
+
+        const source_stem = std.fs.path.stem(self.source.path);
 
-        _ = argv.skip(); // skip command name
+        // handle meta pages before even creating target_path
+        if (std.mem.startsWith(u8, source_stem, "meta.tilde")) {
+            return try self.metaTilde(io, gpa);
+        }
+
+        // target file
+        const target_path = try std.fmt.allocPrint(gpa, "{s}.html", .{source_stem});
+        defer gpa.free(target_path);
+
+        // atomic?
+        const target_fd = try self.target_dir.createFile(io, target_path, .{
+            .lock = .exclusive,
+        });
+        defer target_fd.close(io);
 
-        if (argv.inner.remaining.len == 0) {
-            return Cli.helpCmd();
+        // check if we need to do any work
+        if (try targetExists(io, gpa, target_fd, self.root_dir)) {
+            defer log.debug("{s}: done", .{self.source.path});
+            return; // skip any further processing
         }
 
-        while (argv.next()) |arg| {
-            if (std.mem.eql(u8, arg, "help")) {
-                return Cli.helpCmd();
-            } else if (std.mem.eql(u8, arg, "gen")) {
-                try Cli.genCmd(init, &argv, ctx);
-            } else if (std.mem.eql(u8, arg, "md")) {
-                try Cli.mdCmd(ctx, &argv);
-            } else if (std.mem.eql(u8, arg, "watch")) {
-                try Cli.watchCmd(init, &argv);
-            } else if (std.mem.eql(u8, arg, "serve")) {
-                try Cli.serveCmd(init, &argv);
-            } else {
-                return Cli.helpCmd();
+        log.debug("{s}: writing", .{self.source.path});
+
+        var writer_buf: [BUFFER_SIZE]u8 = undefined;
+        var writer = target_fd.writer(io, &writer_buf);
+
+        // determine if this is a fragment and exit if not
+        if (try isFullPage(self.source)) {
+            // only copy source to target
+            try writer.interface.writeAll(self.source.mmap.memory);
+        } else {
+            // load baseof fragment
+            var baseof = try File.init(io, self.root_dir, "meta.baseof.htm", null);
+            defer baseof.close(io);
+
+            try self.inject(io, gpa, baseof, &writer.interface);
+        }
+
+        try self.finalize(gpa, target_fd);
+        defer log.debug("{s}: done", .{self.source.path});
+    }
+
+    // finalize computes the digest and attaches it to the target file
+    fn finalize(self: *Builder, gpa: Allocator, target_fd: Io.File) !void {
+        log.debug("{s}: finalizing", .{self.source.path});
+
+        const composite_dgst = compositeDigest(self.dgsts.items);
+        try setXattrSourceDigest(target_fd, composite_dgst);
+        try setXattrSourceComponents(gpa, target_fd, self.parts.items);
+    }
+
+    // inject interprets source and writes to w
+    fn inject(self: *Builder, io: Io, gpa: Allocator, source: File, w: *Io.Writer) !void {
+        var iter = std.mem.splitScalar(u8, source.mmap.memory, '\n');
+        while (iter.next()) |line| {
+            var offset: usize = 0;
+            parseLn: while (true) {
+                if (std.mem.findPos(u8, line, offset, DELIM_START)) |start| {
+                    if (std.mem.findPos(u8, line, offset + start + 1, DELIM_END)) |end| {
+                        // write out beginning of chunk
+                        try w.writeAll(line[offset..start]);
+
+                        // do templating
+                        const ref_name = line[(offset + start + DELIM_START.len)..end];
+                        try self.template(io, gpa, ref_name, w);
+
+                        // advance offset
+                        offset += end + DELIM_END.len;
+                    } else break :parseLn;
+                } else break :parseLn;
             }
+
+            // write out remaining chunk
+            try w.writeAll(line[offset..]);
+            try w.writeByte('\n');
         }
+        try w.flush();
+
+        // track injected file
+        try self.parts.append(gpa, try gpa.dupe(u8, source.path));
+        try self.dgsts.append(gpa, try source.digest());
     }
 
-    fn helpCmd() void {
-        std.debug.print("usage: rbc COMMAND\n\n", .{});
-        std.debug.print(" rbc gen ROOT SITE [force=true]\n", .{});
-        std.debug.print(" rbc watch\n", .{});
-        std.debug.print(" rbc serve\n", .{});
-        std.debug.print("\n", .{});
+    fn template(self: *Builder, io: Io, gpa: Allocator, ref_name: []const u8, w: *Io.Writer) anyerror!void {
+        log.debug("templating ref={s}", .{ref_name});
+
+        // special case for when we're injecting the source into the baseof
+        if (std.mem.eql(u8, ref_name, "builtin.fragment")) {
+            try w.writeByte('\n');
+            try self.inject(io, gpa, self.source, w);
+            try w.writeByte('\n');
+            return;
+        }
+        // builtin variables
+        if (std.mem.eql(u8, ref_name, "builtin.title")) {
+            const title = try formatName(gpa, std.fs.path.stem(self.source.path));
+            defer gpa.free(title);
+            try w.writeAll(title);
+            return;
+        }
+        if (std.mem.eql(u8, ref_name, "builtin.path")) {
+            try w.writeAll(self.source.path);
+            return;
+        }
+        // TODO: other builtins?
+
+        switch (ref_name[0]) {
+            '/' => try self.templatePortal(io, gpa, ref_name[1..], w),
+            '$' => return try self.templateLink(gpa, ref_name[1..], w),
+            '+' => return try self.templateName(gpa, ref_name[1..], w),
+            else => try self.templateRef(io, gpa, ref_name, w),
+        }
     }
 
-    fn genCmd(init: std.process.Init, argv: *std.process.Args.Iterator, ctx: *Context) !void {
-        const root = argv.next() orelse Cli.Defaults.ROOT;
-        const site = argv.next() orelse Cli.Defaults.SITE;
-        const force = blk: {
-            const forcearg = argv.next();
-            if (forcearg != null and std.mem.eql(u8, forcearg.?, "force=true")) {
-                break :blk true;
-            } else {
-                break :blk false;
+    fn templatePortal(self: *Builder, io: Io, gpa: Allocator, ref_name: []const u8, w: *Io.Writer) !void {
+        log.debug("portaling ref={s}", .{ref_name});
+
+        const ref_path = try std.fmt.allocPrint(gpa, "{s}.htm", .{ref_name});
+        defer gpa.free(ref_path);
+
+        var ref = try File.init(io, self.root_dir, ref_path, null);
+        defer ref.close(io);
+
+        try w.writeByte('\n');
+        try self.inject(io, gpa, ref, w);
+        try w.writeByte('\n');
+    }
+
+    fn templateRef(self: *Builder, io: Io, gpa: Allocator, ref_str: []const u8, w: *Io.Writer) !void {
+        const fragment = std.mem.indexOf(u8, ref_str, "#");
+        const ref = if (fragment) |i| ref_str[0..i] else ref_str;
+
+        log.debug("referencing ref={s}", .{ref});
+
+        const ref_path = try std.fmt.allocPrint(gpa, "{s}.html", .{ref});
+        defer gpa.free(ref_path);
+
+        // check ref'ed page can exist
+        {
+            const ref_htm = try std.fmt.allocPrint(gpa, "{s}.htm", .{ref});
+            defer gpa.free(ref_htm);
+
+            const possible_paths = [_][]const u8{ ref_path, ref_htm };
+            var found = false;
+            for (possible_paths) |p|
+                found = found or (blk: {
+                    self.root_dir.access(io, p, .{}) catch break :blk false;
+                    break :blk true;
+                });
+            if (!found) {
+                log.err("{s} not found", .{ref});
+                return error.FileNotFound;
             }
-        };
-        log.info("root = {s}", .{root});
-        log.info("site = {s}", .{site});
-        try ctx.index(try std.Io.Dir.cwd().openDir(init.io, root, .{ .iterate = true }));
-        try ctx.render(try std.Io.Dir.cwd().createDirPathOpen(init.io, site, .{}), force);
+        }
+
+        const ref_name = try formatName(gpa, ref);
+        defer gpa.free(ref_name);
+        const ref_link = try formatLink(gpa, ref, self.options.offset_from_siteroot);
+        defer gpa.free(ref_link);
+
+        try w.print(
+            \\<a href="{s}{s}">{s}</a>
+        , .{
+            ref_link,
+            if (fragment) |i| ref_str[i..] else "",
+            ref_name,
+        });
     }
 
-    fn mdCmd(init: std.process.Init, argv: *std.process.Args.Iterator, ctx: *Context) !void {
-        const root = argv.next() orelse Cli.Defaults.ROOT;
-        log.info("root = {s}", .{root});
+    fn templateLink(self: *Builder, gpa: Allocator, ref_name: []const u8, w: *std.Io.Writer) !void {
+        const link = try formatLink(gpa, ref_name, self.options.offset_from_siteroot);
+        defer gpa.free(link);
+        try w.writeAll(link);
+    }
 
-        try os.shell(init.io,
-            \\pandoc --from markdown+wikilinks_title_after_pipe+autolink_bare_uris --to html5 --lua-filter preproc.lua --strip-comments --output=-
-        , .{ .environ_map = &env_map, .cwd = .{ .path = root } });
+    fn templateName(_: *Builder, gpa: Allocator, ref_name: []const u8, w: *std.Io.Writer) !void {
+        const name = try formatName(gpa, ref_name);
+        defer gpa.free(name);
+        try w.writeAll(name);
     }
 
-    fn watchCmd(init: std.process.Init, argv: *std.process.Args.Iterator) !void {
-        const root = argv.next() orelse Cli.Defaults.ROOT;
-        const site = argv.next() orelse Cli.Defaults.SITE;
+    // meta_tilde implements
+    fn metaTilde(self: *Builder, io: Io, gpa: Allocator) !void {
+        const source_stem = std.fs.path.stem(self.source.path)[11..]; // trim "meta.tilde."
+        log.debug("meta.tilde ~{s}", .{source_stem});
+
+        const content = std.mem.trim(u8, self.source.mmap.memory, "\n\t\r");
+        const target_name = try std.fmt.allocPrint(gpa, "~{s}", .{source_stem});
+        defer gpa.free(target_name);
 
-        const exePath = blk: {
-            var buf: [std.os.linux.PATH_MAX]u8 = undefined;
-            const n = try std.process.executablePath(init.io, &buf);
-            break :blk buf[0..n];
+        const target_fd = try self.target_dir.createFile(io, target_name, .{ .exclusive = true });
+        if (try targetExists(io, gpa, target_fd, self.root_dir)) return;
+
+        var buffer: [BUFFER_SIZE]u8 = undefined;
+        var writer = target_fd.writer(io, &buffer);
+
+        try writer.interface.print(
+            \\<!DOCTYPE html>
+            \\<html lang="en">
+            \\<head>
+            \\<meta charset="utf-8">
+            \\<meta http-equiv="refresh" content="0; url={[LINK]s}">
+            \\<meta name="fediverse:creator" content="@robertgzr@toot.berlin">
+            \\<link rel="stylesheet" type="text/css" href="links/main.css">
+            \\</head>
+            \\<body>
+            \\<noscript>Continue to <a href="{[LINK]s}">{[LINK]s}</a>.</noscript>
+            \\<script>
+            \\'use strict'
+            \\window.location.replace("{[LINK]s}")
+            \\</script>
+            \\</body>
+            \\</html>
+            \\
+        , .{
+            .LINK = content,
+        });
+        try writer.interface.flush();
+
+        try self.finalize(gpa, target_fd);
+        log.debug("{s}: done", .{self.source.path});
+    }
+};
+
+// helpers
+
+fn formatName(gpa: Allocator, name: []const u8) ![]u8 {
+    return try std.mem.replaceOwned(u8, gpa, name, "_", " ");
+}
+
+fn formatLink(gpa: Allocator, name: []const u8, offset: ?[]const u8) ![]u8 {
+    var link = try std.fmt.allocPrint(gpa, "{s}.html", .{name});
+    if (offset) |_| {
+        const link2 = try std.fs.path.join(gpa, &.{ offset.?, link });
+        gpa.free(link);
+        link = link2;
+    }
+    return link;
+}
+
+fn targetExists(io: Io, gpa: Allocator, target_fd: Io.File, root_dir: Io.Dir) !bool {
+    const target_parts = getXattrSourceComponents(gpa, target_fd) catch |err|
+        switch (err) {
+            error.XattrNotFound => return false,
+            else => return err,
+        };
+    defer gpa.free(target_parts);
+    defer for (target_parts) |p| gpa.free(p);
+
+    const target_dgst = getXattrSourceDigest(gpa, target_fd) catch |err|
+        switch (err) {
+            error.XattrNotFound => return false,
+            else => return err,
         };
-        const command = try std.fmt.allocPrint(init.gpa,
-            \\find "{s}" -path '*.htm' -type f -print | entr -nc -dd -- "{s}" gen "{s}" "{s}"
-        , .{ root, exePath, root, site });
-        try os.shell(init.io, command, .{});
-    }
-
-    fn serveCmd(init: std.process.Init, argv: *std.process.Args.Iterator) !void {
-        const root = argv.next() orelse ".";
-        log.info("root = {s}", .{root});
-
-        var env_map = try init.minimal.environ.createMap(init.gpa);
-        defer env_map.deinit();
-        try env_map.put("CADDY_HTTP_PORT", "1414");
-        try env_map.put("CADDY_HOST", "localhost");
-
-        if ((try std.Io.Dir.cwd().openDir(init.io, root, .{})).access(init.io, "Caddyfile", .{})) {
-            try os.shell(init.io,
-                \\caddy fmt --overwrite && caddy run --watch
-            , .{ .environ_map = &env_map, .cwd = .{ .path = root } });
-        } else |_| {
-            try os.shell(init.io,
-                \\caddy file-server --listen=$CADDY_HOST:$CADDY_HTTP_PORT
-            , .{ .environ_map = &env_map, .cwd = .{ .path = root } });
+
+    // create digests for parts
+    var actual_dgsts: std.ArrayList(Digest) = .empty;
+    defer actual_dgsts.deinit(gpa);
+    for (target_parts) |p| {
+        var f = try File.init(io, root_dir, p, .{ .mode = .read_write });
+        defer f.close(io);
+        try actual_dgsts.append(gpa, try f.digest());
+    }
+    const actual_dgst = compositeDigest(actual_dgsts.items);
+    return std.mem.eql(u8, &actual_dgst, &target_dgst);
+}
+
+fn isFullPage(source: File) !bool {
+    // exit early if extension is .html vs .htm
+    const ext = std.fs.path.extension(source.path);
+    if (std.mem.eql(u8, ext, ".html")) return true;
+
+    // peek if file starts with <html>
+    const buf = source.mmap.memory[0..6];
+    return std.mem.eql(u8, buf, "<html>");
+}
+
+fn compositeDigest(parts: []Digest) Digest {
+    std.sort.block(Digest, parts, {}, comptime struct {
+        pub fn inner(_: void, a: Digest, b: Digest) bool {
+            return std.mem.lessThan(u8, &a, &b);
         }
+    }.inner);
+
+    var buffer: [Digester.digest_length]u8 = undefined;
+    var d = Digester.init(.{});
+    for (parts) |dgst| d.update(&dgst);
+    d.final(&buffer);
+    return buffer;
+}
+
+// xattr code
+// TODO: fallback to cache dir when xattr isnt supported
+
+fn setXattr(fd: Io.File, key: [:0]const u8, val: []const u8) !void {
+    const rc = linux.fsetxattr(fd.handle, key.ptr, val.ptr, val.len, 0);
+    switch (linux.errno(rc)) {
+        .SUCCESS => {},
+        .OPNOTSUPP => return error.XattrNotSupported,
+        .NODATA => return error.XattrNotFound,
+        else => return error.XattrError,
     }
-};
+}
 
-pub fn main(init: std.process.Init) !void {
-    var ctx = try Context.init(init.io, init.gpa);
-    defer ctx.deinit();
+fn getXattr(gpa: Allocator, fd: Io.File, key: [:0]const u8) ![]u8 {
+    var buffer: [BUFFER_SIZE]u8 = undefined;
+    const rc = linux.fgetxattr(fd.handle, key.ptr, &buffer, buffer.len);
+    switch (linux.errno(rc)) {
+        .SUCCESS => {},
+        .OPNOTSUPP => return error.XattrNotSupported,
+        .NODATA => return error.XattrNotFound,
+        else => {
+            log.err("errno: {d}", .{linux.errno(rc)});
+            return error.XattrError;
+        },
+    }
+    return try gpa.dupe(u8, buffer[0..@intCast(rc)]);
+}
+
+const XATTR_SOURCE_DIGEST = "user.rbc-source-digest";
+const XATTR_SOURCE_COMPONENTS = "user.rbc-source-components";
+
+fn setXattrSourceDigest(fd: Io.File, dgst: Digest) !void {
+    return try setXattr(fd, XATTR_SOURCE_DIGEST, &dgst);
+}
+
+fn getXattrSourceDigest(gpa: Allocator, fd: Io.File) !Digest {
+    const val = try getXattr(gpa, fd, XATTR_SOURCE_DIGEST);
+    defer gpa.free(val);
+    return val[0..16].*;
+}
+
+fn setXattrSourceComponents(gpa: Allocator, fd: Io.File, parts: []const []const u8) !void {
+    const val = try std.mem.join(gpa, ";", parts);
+    defer gpa.free(val);
+    return try setXattr(fd, XATTR_SOURCE_COMPONENTS, val);
+}
+
+fn getXattrSourceComponents(gpa: Allocator, fd: Io.File) ![][]const u8 {
+    const val = try getXattr(gpa, fd, XATTR_SOURCE_COMPONENTS);
+    defer gpa.free(val);
+
+    var parts: std.ArrayList([]const u8) = .empty;
+    errdefer parts.deinit(gpa);
+
+    var iter = std.mem.splitScalar(u8, val, ';');
+    while (iter.next()) |p| try parts.append(gpa, try gpa.dupe(u8, p));
+    return try parts.toOwnedSlice(gpa);
+}
+
+// tests
+
+pub const Testing = if (@import("builtin").is_test)
+    struct {
+        pub fn shell(
+            io: std.Io,
+            command: []const u8,
+            opts: struct {
+                cwd: std.process.Child.Cwd = .inherit,
+                environ_map: ?*const std.process.Environ.Map = null,
+                progress_node: std.Progress.Node = std.Progress.Node.none,
+            },
+        ) !void {
+            log.debug("Running Shell <<< {s}", .{command});
+
+            var p = try std.process.spawn(io, .{
+                .argv = &.{ "sh", "-c", command },
+                .cwd = opts.cwd,
+                .stdin = .ignore,
+                .stderr = .inherit,
+                .stdout = .inherit,
+                .environ_map = opts.environ_map,
+                .progress_node = opts.progress_node,
+            });
+
+            const result = try p.wait(io);
+            if (result.exited != 0) return error.NonZeroExitCode;
+        }
+
+        pub fn parse_hash(comptime str: []const u8) !Digest {
+            var buffer: [BUFFER_SIZE]u8 = undefined;
+            var out = try std.fmt.hexToBytes(&buffer, str);
+            return out[0..Digester.digest_length].*;
+        }
+    };
 
-    try Cli.run(init, &ctx);
+test "xattr" {
+    std.testing.log_level = .debug;
+
+    const io = std.testing.io;
+    const gpa = std.testing.allocator;
+
+    var tmp = std.testing.tmpDir(.{});
+    defer tmp.cleanup();
+
+    const fd = try tmp.dir.createFile(io, &tmp.sub_path, .{});
+
+    // test generic funcs
+    {
+        try setXattr(fd, "user.foo", "bar");
+        const val = try getXattr(gpa, fd, "user.foo");
+        defer gpa.free(val);
+        try std.testing.expectEqualStrings("bar", val);
+    }
+
+    // test digest func
+    {
+        const dgst = try Testing.parse_hash("accd9547a71463f530ae12a8dd0d7696");
+        try setXattrSourceDigest(fd, dgst);
+        const val = try getXattrSourceDigest(gpa, fd);
+        try std.testing.expectEqualStrings(&dgst, &val);
+    }
+
+    // test paths func
+    {
+        const parts = &[_][]const u8{ "foo.htm", "bar.htm" };
+        try setXattrSourceComponents(gpa, fd, parts);
+        const val = try getXattrSourceComponents(gpa, fd);
+        defer gpa.free(val);
+        defer for (val) |v| gpa.free(v);
+        try std.testing.expectEqual(parts.len, val.len);
+        for (parts, 0..) |p, i| try std.testing.expectEqualStrings(p, val[i]);
+    }
+}
+
+test "targetExists" {
+    std.testing.log_level = .debug;
+
+    const io = std.testing.io;
+    const gpa = std.testing.allocator;
+
+    var tmp = std.testing.tmpDir(.{});
+    defer tmp.cleanup();
+
+    var dgsts: std.ArrayList(Digest) = .empty;
+    defer dgsts.deinit(gpa);
+
+    const source_fd = try tmp.dir.createFile(io, "source.htm", .{});
+    try source_fd.writePositionalAll(io, "hello world", 0);
+    const source = try File.init(io, tmp.dir, "source.htm", null);
+    try dgsts.append(gpa, try source.digest());
+
+    const ref_fd = try tmp.dir.createFile(io, "ref.htm", .{});
+    try ref_fd.writePositionalAll(io, "foobar", 0);
+    const ref = try File.init(io, tmp.dir, "ref.htm", null);
+    try dgsts.append(gpa, try ref.digest());
+
+    const dgst = compositeDigest(dgsts.items);
+
+    const target_fd = try tmp.dir.createFile(io, "target.htm", .{});
+    try setXattrSourceDigest(target_fd, dgst);
+    try setXattrSourceComponents(gpa, target_fd, &.{
+        source.path,
+        ref.path,
+    });
+
+    const exists = try targetExists(io, gpa, target_fd, tmp.dir);
+    try std.testing.expectEqual(true, exists);
+}
+
+test "e2e" {
+    std.testing.log_level = .debug;
+
+    const io = std.testing.io;
+    const gpa = std.testing.allocator;
+
+    const Tc = struct {
+        const Self = @This();
+
+        source: File,
+        root_dir: Io.Dir,
+
+        expect_path: []const u8,
+        expect_dgst: Digest,
+
+        fn init(path: []const u8, expect_path: []const u8, expect_dgst: Digest) !Self {
+            const root_dir = try std.Io.Dir.cwd().openDir(io, std.fs.path.dirname(path).?, .{});
+            return .{
+                .root_dir = root_dir,
+                .source = try File.init(io, root_dir, std.fs.path.basename(path), null),
+                .expect_path = expect_path,
+                .expect_dgst = expect_dgst,
+            };
+        }
+
+        fn run(tc: *Self, dest: Io.Dir) !void {
+            std.debug.print("=== {s} =============================\n", .{tc.source.path});
+
+            defer tc.root_dir.close(io);
+            defer tc.source.close(io);
+
+            const b = try Builder.init(gpa, tc.root_dir, tc.source, dest);
+            defer b.deinit(gpa);
+            try b.build(
+                io,
+                gpa,
+            );
+
+            const fd = try dest.openFile(io, tc.expect_path, .{ .mode = .read_write, .lock = .exclusive });
+            defer fd.close(io);
+            const xattr_dgst = try getXattrSourceDigest(gpa, fd);
+
+            log.debug("{x} == {x}", .{ tc.expect_dgst, xattr_dgst });
+            if (!std.mem.eql(u8, &tc.expect_dgst, &xattr_dgst)) {
+                std.debug.print("====== expected this digest: =========\n", .{});
+                std.debug.print("{x}\n\n", .{tc.expect_dgst});
+                std.debug.print("======== instead found this: =========\n", .{});
+                std.debug.print("{x}\n\n", .{xattr_dgst});
+                std.debug.print("======================================\n", .{});
+                return error.TestExpectedEqual;
+            }
+
+            try Testing.shell(io, "tree", .{ .cwd = .{ .dir = dest } });
+            const cmd = try std.fmt.allocPrint(gpa, "cat {s}", .{tc.expect_path});
+            defer gpa.free(cmd);
+            try Testing.shell(io, cmd, .{ .cwd = .{ .dir = dest } });
+        }
+    };
+
+    var tmp = std.testing.tmpDir(.{ .iterate = true });
+    defer tmp.cleanup();
+
+    {
+        var tc = try Tc.init(
+            "./testdata/only-copy.html",
+            "only-copy.html",
+            try Testing.parse_hash("eae5ff7df7c71f8bbeda89302f9fe721"),
+        );
+        try tc.run(tmp.dir);
+    }
+    {
+        var tc = try Tc.init(
+            "./testdata/only-copy.html",
+            "only-copy.html",
+            try Testing.parse_hash("eae5ff7df7c71f8bbeda89302f9fe721"),
+        );
+        try tc.run(tmp.dir);
+    }
+    {
+        var tc = try Tc.init(
+            "./testdata/fragment.htm",
+            "fragment.html",
+            try Testing.parse_hash("a5c099fac15ea9ea9928712fa6229702"),
+        );
+        try tc.run(tmp.dir);
+    }
+    {
+        var tc = try Tc.init(
+            "./testdata/page.htm",
+            "page.html",
+            try Testing.parse_hash("5d810c74eb9ba4d03fa49ab920b85e45"),
+        );
+        try tc.run(tmp.dir);
+    }
+    {
+        var tc = try Tc.init(
+            "./testdata/meta.tilde.foo.htm",
+            "~foo",
+            try Testing.parse_hash("f5bdd49b1a3d691113033ee606e0aa1d"),
+        );
+        try tc.run(tmp.dir);
+    }
 }
diff --git a/src/os.zig b/src/os.zig
deleted file mode 100644
index ad9fe23..0000000
--- a/src/os.zig
+++ /dev/null
@@ -1,84 +0,0 @@
-const std = @import("std");
-const c = @cImport({
-    @cInclude("stdlib.h");
-    @cInclude("time.h");
-});
-
-const log = std.log.scoped(.os);
-
-pub fn captureStdout(
-    io: std.Io,
-    command: []const u8,
-    opts: struct {
-        cwd: std.process.Child.Cwd = .inherit,
-        environ_map: ?*const std.process.Environ.Map = null,
-        progress_node: std.Progress.Node = std.Progress.Node.none,
-    },
-    w: *std.Io.Writer,
-) !void {
-    log.debug("Running <<< {s}", .{command});
-
-    var p = try std.process.spawn(io, .{
-        .argv = &.{ "sh", "-c", command },
-        .cwd = opts.cwd,
-        .stdin = .ignore,
-        .stderr = .ignore,
-        .stdout = .pipe,
-        .environ_map = opts.environ_map,
-        .progress_node = opts.progress_node,
-    });
-
-    var buf: [1024]u8 = undefined;
-    var r = p.stdout.?.readerStreaming(io, &buf);
-
-    _ = try w.sendFileAll(&r, .unlimited);
-    try w.flush();
-
-    const result = try p.wait(io);
-    if (result.exited != 0) return error.NonZeroExitCode;
-}
-
-pub fn shell(
-    io: std.Io,
-    command: []const u8,
-    opts: struct {
-        cwd: std.process.Child.Cwd = .inherit,
-        environ_map: ?*const std.process.Environ.Map = null,
-        progress_node: std.Progress.Node = std.Progress.Node.none,
-    },
-) !void {
-    log.debug("Running Shell <<< {s}", .{command});
-
-    var p = try std.process.spawn(io, .{
-        .argv = &.{ "sh", "-c", command },
-        .cwd = opts.cwd,
-        .stdin = .ignore,
-        .stderr = .inherit,
-        .stdout = .inherit,
-        .environ_map = opts.environ_map,
-        .progress_node = opts.progress_node,
-    });
-
-    const result = try p.wait(io);
-    if (result.exited != 0) return error.NonZeroExitCode;
-}
-
-// use libc strftime to format time string
-pub fn formatTimestamp(alloc: std.mem.Allocator, ts: std.Io.Timestamp, fmt: [*c]const u8) ![]const u8 {
-    const mtime_str = try alloc.alloc(u8, std.os.linux.PATH_MAX);
-    var tm: c.struct_tm = undefined;
-
-    const sec = ts.toSeconds();
-    _ = c.localtime_r(&sec, &tm);
-    const rc = c.strftime(
-        mtime_str.ptr,
-        mtime_str.len,
-        fmt,
-        &tm,
-    );
-    switch (std.posix.errno(rc)) {
-        .SUCCESS => {},
-        else => |err| return std.posix.unexpectedErrno(err),
-    }
-    return try alloc.realloc(mtime_str, rc);
-}