about summary refs log tree commit diff
path: root/src/gen.zig
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/gen.zig404
1 files changed, 117 insertions, 287 deletions
diff --git a/src/gen.zig b/src/gen.zig
index fd1dff7..8def5cf 100644
--- a/src/gen.zig
+++ b/src/gen.zig
@@ -1,131 +1,35 @@
 const std = @import("std");
 
-const fs = @import("fs.zig");
-const exe = @import("exe.zig");
-const Context = @import("main.zig").Context;
+const os = @import("os.zig");
 
 const log = std.log.scoped(.gen);
 
-pub fn generate(ctx: *Context) !void {
-    ctx.generated.clearRetainingCapacity();
-
-    ctx.progress_start("generate", true);
-    defer ctx.progress_end();
-
-    try index(ctx);
-
-    log.info("Generated index.", .{});
-
-    try pages(ctx);
-
-    log.info("Generated {d} files.", .{ctx.generated.items.len});
-    log.info("Generated {d} tildes.", .{ctx.tilde});
-}
-
-fn index(ctx: *Context) !void {
-    ctx.progress_start("index.htm", false);
-    defer ctx.progress_end();
-
-    const srcpathIndex = try fs.joinPathAndFilename(ctx.allocator(), &.{Context.params.PAGE_PATH}, &.{"index.htm"});
-    defer ctx.allocator().free(srcpathIndex);
-
-    const f = try fs.FileSwapper.createAt(ctx.allocator(), srcpathIndex);
-    defer f.close();
-
-    var buffer: [1024]u8 = undefined;
-    var w = f.writer(&buffer);
-
-    try w.interface.writeAll(
-        \\<ul class="col2 captial">
-        \\
-    );
-
-    for (ctx.sites.items) |s| {
-        for (s.pages.items) |p| {
-            if (p.kind == .Htm) {
-                const dstpath = try fs.joinPathAndFilename(ctx.allocator(), &.{Context.params.SITE_PATH}, &.{ p.name, ".html" });
-                defer ctx.allocator().free(dstpath);
-
-                try w.interface.print(
-                    \\<li><a href="/{[LINK]s}">{[NAME]s}</a></li>
-                    \\
-                , .{
-                    .LINK = dstpath,
-                    .NAME = p.name,
-                });
-            }
-        }
-    }
-
-    try w.interface.writeAll(
-        \\</ul>
-        \\
-    );
-
-    try w.interface.flush();
-}
-
-fn pages(ctx: *Context) !void {
-    for (ctx.sites.items) |s| {
-        for (s.pages.items) |p| {
-            if (p.kind == .Meta) {
-                try meta(ctx, p);
-            } else if (p.kind == .Htm) {
-                try page(ctx, p);
-            }
-        }
-    }
-}
-
-fn page(ctx: *Context, p: Context.Site.Page) !void {
-    ctx.progress_start(p.name, false);
-    defer ctx.progress_end();
-
-    const srcpath = try fs.joinPathAndFilename(ctx.allocator(), &.{Context.params.PAGE_PATH}, &.{ p.name, ".htm" });
-    defer ctx.allocator().free(srcpath);
-    const dstpath = try fs.joinPathAndFilename(ctx.allocator(), &.{Context.params.SITE_PATH}, &.{ p.name, ".html" });
-    defer ctx.allocator().free(dstpath);
-    const rel_dstpath = try std.fs.path.relative(ctx.allocator(), Context.params.SITE_PATH, dstpath);
-    defer ctx.allocator().free(rel_dstpath);
-
-    if (std.fs.path.dirname(rel_dstpath)) |d| {
-        const dstdir = try fs.joinPathAndFilename(ctx.allocator(), &.{Context.params.SITE_PATH}, &.{d});
-        defer ctx.allocator().free(dstdir);
-
-        try std.fs.cwd().makePath(dstdir);
-    }
-
-    const srcmtime = try fs.getLastEdit(srcpath);
-    const dstmtime = try fs.getLastEdit(dstpath);
-    if (!ctx.force and srcmtime.sec < dstmtime.sec) return;
-
-    log.debug("generating {s} at {s}", .{ p.path, rel_dstpath });
-
-    const f = try fs.FileSwapper.createAt(ctx.allocator(), dstpath);
-
-    var buffer: [1024]u8 = undefined;
-    var w = f.writer(&buffer);
-
-    const page_name = try std.mem.replaceOwned(u8, ctx.allocator(), p.name, "_", " ");
-    defer ctx.allocator().free(page_name);
-
-    try build(ctx.allocator(), &w.interface, page_name, srcpath);
-
-    f.close();
-
-    if (f.swapped.*) {
-        try ctx.generated.append(
-            ctx.allocator(),
-            try ctx.allocator().dupe(u8, dstpath),
-        );
-    }
-}
+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
-fn build(allocator: std.mem.Allocator, w: *std.Io.Writer, page_name: []const u8, srcpath: []const u8) !void {
-    const mtime = try fs.getLastEdit(srcpath);
-    const mtime_str = try fs.formatTimespec(allocator, mtime, "%F");
-    defer allocator.free(mtime_str);
+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(
@@ -148,18 +52,19 @@ fn build(allocator: std.mem.Allocator, w: *std.Io.Writer, page_name: []const u8,
         \\<a href="/"><img src="/media/interface/logo.svg" alt="{[SITE_NAME]s}"></a>
         \\
     , .{
-        .SOURCE = srcpath,
-        .LICENSE_URL = Context.params.LICENSE_URL,
-        .PAGE_NAME = page_name,
-        .SITE_NAME = Context.params.SITE_NAME,
+        .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>
-        \\
     );
-    portal(allocator, w, "meta.nav", false) catch {}; // ignore error
+    template_portal(io, gpa, w, "meta.nav", opts) catch |err| {
+        if (err != error.FileNotFound) return err;
+    };
     try w.writeAll(
         \\</nav>
         \\</header>
@@ -167,11 +72,11 @@ fn build(allocator: std.mem.Allocator, w: *std.Io.Writer, page_name: []const u8,
     );
 
     // MAIN
-    try w.print(
+    try w.writeAll(
         \\<main>
         \\
-    , .{});
-    try inject(allocator, w, srcpath);
+    );
+    try inject(io, gpa, w, opts);
     try w.writeAll(
         \\</main>
         \\
@@ -190,14 +95,14 @@ fn build(allocator: std.mem.Allocator, w: *std.Io.Writer, page_name: []const u8,
         \\</footer>
         \\
     , .{
-        .LICENSE_HOLDER = Context.params.LICENSE_HOLDER,
-        .LICENSE_NAME = Context.params.LICENSE_NAME,
-        .LICENSE_URL = Context.params.LICENSE_URL,
-        .LICENSE_YEAR = Context.params.LICENSE_YEAR,
         .MTIME = mtime_str,
-        .PATH = srcpath,
-        .PUBLIC_INBOX = Context.params.PUBLIC_INBOX,
-        .SOURCE_URL = Context.params.SOURCE_URL,
+        .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(
@@ -208,92 +113,26 @@ fn build(allocator: std.mem.Allocator, w: *std.Io.Writer, page_name: []const u8,
     try w.writeAll(
         \\</body>
         \\</html>
-        \\
     );
 
     try w.flush();
 }
 
-// build a virtual html file, currently either:
-// - meta.nav: used for populating the global navigation menu
-// - meta.tilde: used to create ~foobar files
-fn meta(ctx: *Context, p: Context.Site.Page) !void {
-    ctx.progress_start(p.name, false);
-    defer ctx.progress_end();
-
-    ctx.meta += 1;
-
-    if (std.mem.startsWith(u8, p.name, "meta.nav")) return;
-
-    if (std.mem.startsWith(u8, p.name, "meta.tilde")) {
-        const srcpath = try fs.joinPathAndFilename(ctx.allocator(), &.{Context.params.PAGE_PATH}, &.{p.path});
-        defer ctx.allocator().free(srcpath);
-        const dstname = p.name[11..]; // trim "meta.tilde." from the name
-        const dstpath = try fs.joinPathAndFilename(ctx.allocator(), &.{Context.params.LINK_PATH}, &.{ "~", dstname });
-        defer ctx.allocator().free(dstpath);
-
-        const link = blk: {
-            const f = try std.fs.cwd().openFile(srcpath, .{ .mode = .read_only });
-            defer f.close();
-            const content = try f.readToEndAlloc(ctx.allocator(), std.os.linux.PATH_MAX);
-            defer ctx.allocator().free(content);
-            const trimmed = std.mem.trim(u8, content, " \n\t\r");
-            break :blk try ctx.allocator().dupe(u8, trimmed);
-        };
-        defer ctx.allocator().free(link);
-
-        const f = try fs.FileSwapper.createAt(ctx.allocator(), dstpath);
-        defer f.close();
-
-        var buffer: [1024]u8 = undefined;
-        var w = f.writer(&buffer);
-
-        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();
-
-        ctx.tilde += 1;
-        return;
-    }
-
-    log.err("unknown meta page type: {s}", .{p.name});
-    return error.unknownMeta;
-}
-
 // parses srcpath, resolves refs and portals and writes everything to w
-fn inject(allocator: std.mem.Allocator, w: *std.Io.Writer, srcpath: []const u8) !void {
-    var arena = std.heap.ArenaAllocator.init(allocator);
+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 = std.fs.cwd().openFile(srcpath, .{ .mode = .read_only }) catch |err| {
+    const inc = opts.root_dir.openFile(io, opts.page_path, .{ .mode = .read_only }) catch |err| {
         if (err == error.FileNotFound)
-            log.warn("Missing {s} include.", .{srcpath});
+            log.warn("Missing {s} include.", .{opts.page_path});
 
         return err;
     };
-    defer inc.close();
+    defer inc.close(io);
 
-    var buffer: [1024]u8 = undefined;
-    var r = inc.readerStreaming(&buffer);
+    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();
@@ -308,10 +147,10 @@ fn inject(allocator: std.mem.Allocator, w: *std.Io.Writer, srcpath: []const u8)
 
         var chunk = chunkw.written();
         templating: while (true) {
-            if (std.mem.indexOfPosLinear(u8, chunk, 0, "{")) |start| {
-                if (std.mem.indexOfPosLinear(u8, chunk, start + 1, "}")) |end| {
+            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(allocator, w, chunk[start + 1 .. end]) catch |err| switch (err) {
+                    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
@@ -320,7 +159,7 @@ fn inject(allocator: std.mem.Allocator, w: *std.Io.Writer, srcpath: []const u8)
                         },
                         else => @panic("Failed to resolve template"),
                     };
-                    chunk = chunk[end + 1 ..];
+                    chunk = chunk[end + DELIM_END.len ..];
                 } else break :templating;
             } else break :templating;
         }
@@ -334,92 +173,83 @@ fn inject(allocator: std.mem.Allocator, w: *std.Io.Writer, srcpath: []const u8)
 }
 
 // implements various reference types that can be used in .htm templates
-fn template(allocator: std.mem.Allocator, w: *std.Io.Writer, refname: []const u8) anyerror!void {
-    switch (refname[0]) {
-        '/' => return try portal(allocator, w, refname[1..], false),
-        '$' => return try ref_link(allocator, w, refname[1..]),
-        '+' => return try ref_name(allocator, w, refname[1..]),
-        else => return try ref(allocator, w, refname),
+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),
     }
 }
 
-const Page = struct {
-    arena: std.heap.ArenaAllocator,
-    ref: []const u8,
-    name: []const u8,
-    html: []const u8,
-    htm: []const u8,
-    anchor: []const u8,
-
-    fn from_ref(allocator: std.mem.Allocator, refname: []const u8) !Page {
-        var arena = std.heap.ArenaAllocator.init(allocator);
-
-        const endofref = std.mem.lastIndexOf(u8, refname, "#") orelse refname.len;
-        const basename = try std.mem.replaceOwned(u8, arena.allocator(), refname[0..endofref], " ", "_");
-        const html = try fs.joinPathAndFilename(arena.allocator(), &.{Context.params.SITE_PATH}, &.{ basename, ".html" });
-        const htm = try fs.joinPathAndFilename(arena.allocator(), &.{Context.params.PAGE_PATH}, &.{ basename, ".htm" });
-
-        try std.fs.cwd().access(htm, .{ .mode = .read_only });
-
-        return .{
-            .arena = arena,
-            .ref = refname[0..endofref],
-            .name = basename,
-            .html = html,
-            .htm = htm,
-            .anchor = if (endofref < refname.len) refname[endofref..] else "",
-        };
-    }
+// 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});
 
-    fn deinit(self: Page) void {
-        self.arena.deinit();
-    }
-};
+    const ref_path = try std.fmt.allocPrint(gpa, "{s}.html", .{ref_name});
+    defer gpa.free(ref_path);
 
-fn portal(allocator: std.mem.Allocator, w: *std.Io.Writer, refname: []const u8, insert_heading: bool) anyerror!void {
-    const p = try Page.from_ref(allocator, refname);
-    defer p.deinit();
+    _ = io;
+    _ = opts;
 
-    if (insert_heading)
-        try w.print(
-            \\<h2 id="{[ID]s}"><a href="/{[PATH]s}">{[NAME]s}</a></2>
-            \\
-        , .{
-            .ID = p.ref,
-            .NAME = p.name,
-            .PATH = p.html,
-        });
-
-    try inject(allocator, w, p.htm);
-
-    // XXX: track ref count?
+    try w.print(
+        \\<a href="{s}">{s}</a>
+    , .{ ref_path, ref_name });
 }
 
-fn ref(allocator: std.mem.Allocator, w: *std.Io.Writer, refname: []const u8) anyerror!void {
-    const p = try Page.from_ref(allocator, refname);
-    defer p.deinit();
+// 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});
 
-    try w.print(
-        \\<a href="/{[PATH]s}{[ANCHOR]s}">{[NAME]s}</a>
-    , .{
-        .NAME = p.name,
-        .PATH = p.html,
-        .ANCHOR = p.anchor,
-    });
+    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, .{});
 
-    // XXX: track ref count?
+    var iopts = opts;
+    iopts.page_path = ref_path;
+    iopts.page_name = ref_name;
+    try inject(io, gpa, w, iopts);
 }
 
-fn ref_link(allocator: std.mem.Allocator, w: *std.Io.Writer, refname: []const u8) anyerror!void {
-    const p = try Page.from_ref(allocator, refname);
-    defer p.deinit();
+// 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;
 
-    try w.print("/{s}{s}", .{ p.html, p.anchor });
-}
+    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);
 
-fn ref_name(allocator: std.mem.Allocator, w: *std.Io.Writer, refname: []const u8) anyerror!void {
-    const p = try Page.from_ref(allocator, refname);
-    defer p.deinit();
+        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();
+    }
 
-    try w.print("{s}", .{p.ref});
+    return error.UnsupportedMeta;
 }