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( \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\{[SITE_NAME]s} — {[PAGE_NAME]s} \\ \\ \\
\\{[SITE_NAME]s} \\ , .{ .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( \\ \\
\\ ); // MAIN try w.writeAll( \\
\\ ); try inject(io, gpa, w, opts); try w.writeAll( \\
\\ ); // FOOTER try w.print( \\ \\ , .{ .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( \\[⏶] \\ ); try w.writeAll( \\ \\ ); 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( \\{s} , .{ 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( \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ , .{ .LINK = link, }); try w.interface.flush(); } return error.UnsupportedMeta; }