~clarity/sitegen

78a770d5ba9aba5fdf30c342589f96cd5682e70c — Clarity Flowers a month ago 527d038
Move template code into namespace
1 files changed, 227 insertions(+), 227 deletions(-)

M src/main.zig
M src/main.zig => src/main.zig +227 -227
@@ 142,8 142,8 @@ fn make(
        try env_map.put("PATH", new_path);
    }

    const html_template = try getTemplate(&cwd, &arena.allocator, args.html, .html);
    const gmi_template = try getTemplate(&cwd, &arena.allocator, args.gmi, .gmi);
    const html_template = try Template.get(&cwd, &arena.allocator, args.html, .html);
    const gmi_template = try Template.get(&cwd, &arena.allocator, args.gmi, .gmi);

    try cwd.makePath(args.out_dir);
    var out_dir = try cwd.openDir(args.out_dir, .{});


@@ 288,12 288,12 @@ fn renderFile(
        );
        defer out_file.close();
        const writer = out_file.writer();
        try formatTemplate(template.header, doc.info, file_info, writer);
        try Template.format(template.header, doc.info, file_info, writer);
        for (doc.blocks) |block| switch (ext) {
            .html => try formatBlockHtml(block, writer),
            .gmi => try formatBlockGmi(block, writer),
        };
        try formatTemplate(template.footer, doc.info, file_info, writer);
        try Template.format(template.footer, doc.info, file_info, writer);
    }
}



@@ 1207,259 1207,259 @@ fn formatIndexMarkup(writer: anytype, pages: []const IndexEntry) !void {
// ---- TEMPLATES ----

const Template = struct {
    header: []const TemplateNode,
    footer: []const TemplateNode,
};
    header: []const Node,
    footer: []const Node,

const TemplateNode = union(enum) {
    text: []const u8,
    variable: TemplateVariable,
    conditional: TemplateConditional,
};
    const Node = union(enum) {
        text: []const u8,
        variable: Variable,
        conditional: Conditional,
    };

const TemplateVariableName = enum {
    title,
    file,
    dir,
    written,
    updated,
    back_text,
    back,
    parent_name,
    parent,
};
    const VariableName = enum {
        title,
        file,
        dir,
        written,
        updated,
        back_text,
        back,
        parent_name,
        parent,
    };

const TemplateVariable = struct {
    name: TemplateVariableName,
    format: ?[]const u8 = null,
};
    const Variable = struct {
        name: VariableName,
        format: ?[]const u8 = null,
    };

const TemplateConditional = struct {
    name: TemplateVariableName,
    output: []const TemplateNode,
};
    const Conditional = struct {
        name: VariableName,
        output: []const Node,
    };

fn getTemplate(cwd: *std.fs.Dir, allocator: *std.mem.Allocator, template_file: ?[]const u8, comptime ext: Ext) !Template {
    return try parseTemplate(
        if (template_file) |file|
            try cwd.readFileAlloc(
                allocator,
                file,
                1024 * 1024 * 1024,
            )
        else
            @embedFile("default_template." ++ @tagName(ext)),
        allocator,
    );
}
    fn get(cwd: *std.fs.Dir, allocator: *std.mem.Allocator, template_file: ?[]const u8, comptime ext: Ext) !Template {
        return try parse(
            if (template_file) |file|
                try cwd.readFileAlloc(
                    allocator,
                    file,
                    1024 * 1024 * 1024,
                )
            else
                @embedFile("default_template." ++ @tagName(ext)),
            allocator,
        );
    }

fn parseTemplate(text: []const u8, allocator: *std.mem.Allocator) !Template {
    var arena = std.heap.ArenaAllocator.init(allocator);
    errdefer arena.deinit();
    var index: usize = 0;
    var text_start = index;
    var header = std.ArrayList(TemplateNode).init(&arena.allocator);
    var footer = std.ArrayList(TemplateNode).init(&arena.allocator);
    while (index < text.len) {
        if (parseLiteral(text, index, "{{content}}")) |new_pos| {
            if (index > text_start) {
                try header.append(.{ .text = text[text_start..index] });
    fn parse(text: []const u8, allocator: *std.mem.Allocator) !Template {
        var arena = std.heap.ArenaAllocator.init(allocator);
        errdefer arena.deinit();
        var index: usize = 0;
        var text_start = index;
        var header = std.ArrayList(Node).init(&arena.allocator);
        var footer = std.ArrayList(Node).init(&arena.allocator);
        while (index < text.len) {
            if (parseLiteral(text, index, "{{content}}")) |new_pos| {
                if (index > text_start) {
                    try header.append(.{ .text = text[text_start..index] });
                }
                index = new_pos;
                text_start = index;
                break;
            } else if (try parseVariable(
                text,
                index,
                &arena.allocator,
            )) |res| {
                if (index > text_start) {
                    try header.append(.{ .text = text[text_start..index] });
                }
                index = res.new_pos;
                text_start = index;
                try header.append(res.data);
            } else {
                index += 1;
            }
            index = new_pos;
            text_start = index;
            break;
        } else if (try parseTemplateVariable(
            text,
            index,
            &arena.allocator,
        )) |res| {
            if (index > text_start) {
                try header.append(.{ .text = text[text_start..index] });
        }
        if (index > text_start) {
            try header.append(.{ .text = text[text_start..index] });
        }
        while (index < text.len) {
            if (try parseVariable(
                text,
                index,
                &arena.allocator,
            )) |res| {
                if (index > text_start) {
                    try footer.append(.{ .text = text[text_start..index] });
                }
                index = res.new_pos;
                text_start = index;
                try footer.append(res.data);
            } else {
                index += 1;
            }
            index = res.new_pos;
            text_start = index;
            try header.append(res.data);
        } else {
            index += 1;
        }
        if (index > text_start) {
            try footer.append(.{ .text = text[text_start..index] });
        }
        return Template{
            .header = header.toOwnedSlice(),
            .footer = footer.toOwnedSlice(),
        };
    }
    if (index > text_start) {
        try header.append(.{ .text = text[text_start..index] });
    }
    while (index < text.len) {
        if (try parseTemplateVariable(
            text,
            index,
            &arena.allocator,
        )) |res| {
            if (index > text_start) {
                try footer.append(.{ .text = text[text_start..index] });

    fn parseVariableName(
        text: []const u8,
        start: usize,
    ) ?ParseResult(VariableName) {
        if (start >= text.len) return null;
        inline for (@typeInfo(VariableName).Enum.fields) |fld| {
            if (parseLiteral(text, start, fld.name)) |index| {
                return ok(@field(VariableName, fld.name), index);
            }
            index = res.new_pos;
            text_start = index;
            try footer.append(res.data);
        } else {
            index += 1;
        }
        return null;
    }
    if (index > text_start) {
        try footer.append(.{ .text = text[text_start..index] });
    }
    return Template{
        .header = header.toOwnedSlice(),
        .footer = footer.toOwnedSlice(),
    };
}

fn parseTemplateVariableName(
    text: []const u8,
    start: usize,
) ?ParseResult(TemplateVariableName) {
    if (start >= text.len) return null;
    inline for (@typeInfo(TemplateVariableName).Enum.fields) |fld| {
        if (parseLiteral(text, start, fld.name)) |index| {
            return ok(@field(TemplateVariableName, fld.name), index);
        }
    fn parseVariableFormat(
        text: []const u8,
        start: usize,
    ) ?ParseResult([]const u8) {
        if (start >= text.len) return null;
        const text_start = parseLiteral(text, start, "|") orelse return null;
        const text_end = std.mem.indexOfPos(u8, text, text_start, "}}") orelse
            return null;
        return ok(text[text_start..text_end], text_end + 2);
    }
    return null;
}

fn parseTemplateVariableFormat(
    text: []const u8,
    start: usize,
) ?ParseResult([]const u8) {
    if (start >= text.len) return null;
    const text_start = parseLiteral(text, start, "|") orelse return null;
    const text_end = std.mem.indexOfPos(u8, text, text_start, "}}") orelse
    fn parseConditional(
        text: []const u8,
        start: usize,
        allocator: *std.mem.Allocator,
    ) std.mem.Allocator.Error!?ParseResult([]const Node) {
        var index = parseLiteral(text, start, "?") orelse return null;
        var arena = std.heap.ArenaAllocator.init(allocator);
        errdefer arena.deinit();
        var text_start = index;
        var result = std.ArrayList(Node).init(&arena.allocator);
        while (index < text.len) {
            if (parseLiteral(text, index, "}}")) |new_index| {
                if (index > text_start) {
                    try result.append(.{ .text = text[text_start..index] });
                }
                return ok(
                    @as([]const Node, result.toOwnedSlice()),
                    new_index,
                );
            } else if (try parseVariable(
                text,
                index,
                &arena.allocator,
            )) |res| {
                if (index > text_start) {
                    try result.append(.{ .text = text[text_start..index] });
                }
                index = res.new_pos;
                text_start = index;
                try result.append(res.data);
            } else {
                index += 1;
            }
        }
        arena.deinit();
        return null;
    return ok(text[text_start..text_end], text_end + 2);
}
    }

fn parseTemplateConditional(
    text: []const u8,
    start: usize,
    allocator: *std.mem.Allocator,
) std.mem.Allocator.Error!?ParseResult([]const TemplateNode) {
    var index = parseLiteral(text, start, "?") orelse return null;
    var arena = std.heap.ArenaAllocator.init(allocator);
    errdefer arena.deinit();
    var text_start = index;
    var result = std.ArrayList(TemplateNode).init(&arena.allocator);
    while (index < text.len) {
        if (parseLiteral(text, index, "}}")) |new_index| {
            if (index > text_start) {
                try result.append(.{ .text = text[text_start..index] });
            }
            return ok(
                @as([]const TemplateNode, result.toOwnedSlice()),
                new_index,
            );
        } else if (try parseTemplateVariable(
    fn parseVariable(
        text: []const u8,
        start: usize,
        allocator: *std.mem.Allocator,
    ) std.mem.Allocator.Error!?ParseResult(Node) {
        var index = start;
        index = parseLiteral(text, index, "{{") orelse return null;
        const name_res = parseVariableName(text, index) orelse return null;

        index = name_res.new_pos;
        if (parseVariableFormat(text, index)) |format_res| {
            return ok(Node{
                .variable = .{
                    .name = name_res.data,
                    .format = format_res.data,
                },
            }, format_res.new_pos);
        } else if (try parseConditional(
            text,
            index,
            &arena.allocator,
        )) |res| {
            if (index > text_start) {
                try result.append(.{ .text = text[text_start..index] });
            }
            index = res.new_pos;
            text_start = index;
            try result.append(res.data);
        } else {
            index += 1;
            allocator,
        )) |cond_res| {
            return ok(Node{
                .conditional = .{
                    .name = name_res.data,
                    .output = cond_res.data,
                },
            }, cond_res.new_pos);
        }
        const end_index = parseLiteral(text, index, "}}") orelse return null;
        return ok(Node{ .variable = .{ .name = name_res.data } }, end_index);
    }
    arena.deinit();
    return null;
}

fn parseTemplateVariable(
    text: []const u8,
    start: usize,
    allocator: *std.mem.Allocator,
) std.mem.Allocator.Error!?ParseResult(TemplateNode) {
    var index = start;
    index = parseLiteral(text, index, "{{") orelse return null;
    const name_res = parseTemplateVariableName(text, index) orelse return null;

    index = name_res.new_pos;
    if (parseTemplateVariableFormat(text, index)) |format_res| {
        return ok(TemplateNode{
            .variable = .{
                .name = name_res.data,
                .format = format_res.data,
            },
        }, format_res.new_pos);
    } else if (try parseTemplateConditional(
        text,
        index,
        allocator,
    )) |cond_res| {
        return ok(TemplateNode{
            .conditional = .{
                .name = name_res.data,
                .output = cond_res.data,
            },
        }, cond_res.new_pos);
    }
    const end_index = parseLiteral(text, index, "}}") orelse return null;
    return ok(TemplateNode{ .variable = .{ .name = name_res.data } }, end_index);
}

fn formatTemplate(
    template: []const TemplateNode,
    info: Info,
    file: FileInfo,
    writer: anytype,
) @TypeOf(writer).Error!void {
    for (template) |node| switch (node) {
        .text => |text| try writer.writeAll(text),
        .variable => |variable| switch (variable.name) {
            .written => {
                try info.created.formatRuntime(
                    variable.format orelse "",
                    writer,
                );
            },
            .updated => {
                if (info.changes.len > 0) {
                    try info.changes[info.changes.len - 1].date.formatRuntime(
    fn format(
        template: []const Node,
        info: Info,
        file: FileInfo,
        writer: anytype,
    ) @TypeOf(writer).Error!void {
        for (template) |node| switch (node) {
            .text => |text| try writer.writeAll(text),
            .variable => |variable| switch (variable.name) {
                .written => {
                    try info.created.formatRuntime(
                        variable.format orelse "",
                        writer,
                    );
                }
            },
            .title => try writer.writeAll(info.title),
            .file => try writer.writeAll(file.name),
            .dir => if (file.dir) |dir| try writer.writeAll(dir),
            .back, .parent => {
                try writer.writeByte('.');
                if (file.dir != null) {
                    if (std.mem.eql(u8, file.name, "index")) {
                        try writer.writeByte('.');
                },
                .updated => {
                    if (info.changes.len > 0) {
                        try info.changes[info.changes.len - 1].date.formatRuntime(
                            variable.format orelse "",
                            writer,
                        );
                    }
                }
                },
                .title => try writer.writeAll(info.title),
                .file => try writer.writeAll(file.name),
                .dir => if (file.dir) |dir| try writer.writeAll(dir),
                .back, .parent => {
                    try writer.writeByte('.');
                    if (file.dir != null) {
                        if (std.mem.eql(u8, file.name, "index")) {
                            try writer.writeByte('.');
                        }
                    }
                },
                .back_text, .parent_name => {
                    if (file.parent_title) |name| {
                        try writer.writeAll(name);
                    }
                },
            },
            .back_text, .parent_name => {
                if (file.parent_title) |name| {
                    try writer.writeAll(name);
            .conditional => |conditional| {
                if (switch (conditional.name) {
                    .written, .title, .file, .back_text => true,
                    .dir => file.dir != null,
                    .back, .parent => file.dir != null or
                        !std.mem.eql(u8, file.name, "index"),
                    .updated => info.changes.len > 0,
                    .parent_name => file.parent_title != null,
                }) {
                    try format(conditional.output, info, file, writer);
                }
            },
        },
        .conditional => |conditional| {
            if (switch (conditional.name) {
                .written, .title, .file, .back_text => true,
                .dir => file.dir != null,
                .back, .parent => file.dir != null or
                    !std.mem.eql(u8, file.name, "index"),
                .updated => info.changes.len > 0,
                .parent_name => file.parent_title != null,
            }) {
                try formatTemplate(conditional.output, info, file, writer);
            }
        },
    };
}
        };
    }
};

// ---- UTILS ----