// 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 std = @import("std"); const gen = @import("gen.zig"); const exe = @import("exe.zig"); const log = std.log.scoped(.root); // tune std.log options pub const std_options = std.Options{ .log_level = .info, }; pub const Context = struct { arena: std.heap.ArenaAllocator, arena_mx: std.Thread.Mutex, alloc: std.heap.ThreadSafeAllocator, progress_nodes: std.ArrayListUnmanaged(std.Progress.Node), progress_nodes_mx: std.Thread.Mutex, pages: std.ArrayListUnmanaged([]const u8), markdown: std.ArrayListUnmanaged([]const u8), generated: usize = 0, meta: usize = 0, tilde: usize = 0, pub const params = struct { pub const PAGE_PATH = "src/inc"; pub const SITE_PATH = "site"; pub const LINK_PATH = "links"; 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"; pub const PANDOC = "pandoc"; pub const PANDOC_PREPROC = "preproc.lua"; }; fn create(parent_alloc: std.mem.Allocator) !*Context { const self = try parent_alloc.create(Context); self.* = .{ .arena = std.heap.ArenaAllocator.init(parent_alloc), .arena_mx = .{}, .alloc = undefined, .progress_nodes = .{}, .progress_nodes_mx = .{}, .pages = .{}, .markdown = .{}, }; // create thread safe allocator self.alloc = std.heap.ThreadSafeAllocator{ .child_allocator = self.arena.allocator(), .mutex = self.arena_mx, }; // create root progress node try self.progress_nodes.append( self.allocator(), std.Progress.start(.{ .root_name = "" }), ); return self; } fn destroy(self: *Context) void { self.progress_end(); self.progress_nodes.deinit(self.allocator()); self.arena.deinit(); self.arena.child_allocator.destroy(self); } pub fn allocator(self: *Context) std.mem.Allocator { return self.alloc.allocator(); } // progress handling pub fn progress_start(self: *Context, name: []const u8) void { self.progress_nodes_mx.lock(); const new = self.progress_nodes.getLast().start(name, 0); self.progress_nodes.append(self.allocator(), new) catch @panic("insufficient memory"); self.progress_nodes_mx.unlock(); } pub fn progress_end(self: *Context) void { self.progress_nodes_mx.lock(); const current = self.progress_nodes.pop() orelse return; self.progress_nodes_mx.unlock(); current.end(); } }; fn index(ctx: *Context) !void { ctx.progress_start("index"); defer ctx.progress_end(); var root = std.fs.cwd().openDir(Context.params.PAGE_PATH, .{ .iterate = true }) catch |err| switch (err) { error.FileNotFound => { std.log.err("Missing {s} folder.", .{Context.params.PAGE_PATH}); std.os.linux.exit(1); }, else => |e| return e, }; defer root.close(); var walk = try root.walk(ctx.allocator()); defer walk.deinit(); while (try walk.next()) |e| { switch (e.kind) { .file => { ctx.progress_start(e.basename); defer ctx.progress_end(); if (std.mem.endsWith(u8, e.basename, ".htm")) { std.log.debug("Indexing {s}", .{e.path}); try ctx.pages.append(ctx.allocator(), try ctx.allocator().dupe(u8, e.path)); } if (std.mem.endsWith(u8, e.basename, ".md")) { std.log.debug("Indexing {s}", .{e.path}); try ctx.markdown.append(ctx.allocator(), try ctx.allocator().dupe(u8, e.path)); } }, else => {}, } } log.info("Indexed {d} pages.", .{ctx.pages.items.len}); log.info("Indexed {d} markdown files.", .{ctx.markdown.items.len}); return; } const RunFn = fn (*Context) void; const Cli = struct { fn parse() *const RunFn { var argv = std.process.args(); const more = argv.skip(); // skip arg0 if (!more) return &Cli.helpCmd; const next_arg = argv.next() orelse return &Cli.helpCmd; if (std.mem.eql(u8, next_arg, "gen")) { return &Cli.genCmd; } else if (std.mem.eql(u8, next_arg, "md")) { return &Cli.genMdCmd; } else { return &Cli.helpCmd; } } fn helpCmd(_: *Context) void { std.debug.print("usage: rbl [gen|md]\n\n", .{}); } fn genCmd(ctx: *Context) void { index(ctx) catch { log.err("Failed to index pages.", .{}); std.os.linux.exit(1); }; gen.generate(ctx) catch { log.err("Failed to generate site.", .{}); std.os.linux.exit(1); }; } fn genMdCmd(ctx: *Context) void { index(ctx) catch { log.err("Failed to index pages.", .{}); std.os.linux.exit(1); }; exe.runPandoc(ctx) catch { log.err("Failed to process markdown files.", .{}); std.os.linux.exit(1); }; } }; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}).init; const allocator = gpa.allocator(); defer _ = gpa.deinit(); var ctx = try Context.create(allocator); defer ctx.destroy(); Cli.parse()(ctx); }