From: Alexander Goussas Date: Sat, 18 Apr 2026 04:12:28 +0000 (-0500) Subject: start implementing templating for generated HTML X-Git-Tag: v0.0.1~8 X-Git-Url: http://git.frustrated-labs.net/?a=commitdiff_plain;h=14ddaabcc25916f0730b6f4b2e5edff809afb3aa;p=frustrated-functor.dev.git start implementing templating for generated HTML --- diff --git a/bin/blog-processor/src/html_formatter.zig b/bin/blog-processor/src/html_formatter.zig index e3e801e..71b0384 100644 --- a/bin/blog-processor/src/html_formatter.zig +++ b/bin/blog-processor/src/html_formatter.zig @@ -1,26 +1,183 @@ const std = @import("std"); const md = @import("./markdown_parser.zig"); +const stdC = @cImport({ + @cInclude("stdio.h"); +}); + +const Options = struct { + template: ?[]const u8, +}; + const HtmlFormatter = struct { buffer: std.ArrayList(u8), + opts: Options, - pub fn init() @This() { + pub fn initDefault() @This() { + return init(.{ .template = null }); + } + + pub fn init(opts: Options) @This() { const ary: std.ArrayList(u8) = .empty; return .{ - .buffer = ary + .buffer = ary, + .opts = opts, }; } - pub fn format(self: *@This(), doc: md.MarkdownDoc) []const u8 { - _ = doc; + pub fn deinit(self: *@This(), alloc: std.mem.Allocator) void { + self.buffer.deinit(alloc); + } + + pub fn format(self: *@This(), alloc: std.mem.Allocator, doc: md.MarkdownDoc) ![]const u8 { + for (doc.content.items, 0..) |node, ix| { + switch (node) { + .h1 => |h1| try self.tag(alloc, "h1", h1), + .h2 => |h2| try self.tag(alloc, "h2", h2), + .p => |p| try self.tag(alloc, "p", p), + } + if (ix < doc.content.items.len - 1) { + try self.buffer.append(alloc, '\n'); + } + } + + if (self.opts.template) |t| { + } + return self.buffer.items; } + + fn tag(self: *@This(), alloc: std.mem.Allocator, tagName: []const u8, innerText: []const u8) !void { + const openingTag = try std.mem.concat(alloc, u8, &[_][]const u8{"<", tagName, ">"}); + defer alloc.free(openingTag); + + const closingTag = try std.mem.concat(alloc, u8, &[_][]const u8{""}); + defer alloc.free(closingTag); + + try self.buffer.appendSlice(alloc, openingTag); + try self.buffer.appendSlice(alloc, innerText); + try self.buffer.appendSlice(alloc, closingTag); + } }; test "can format empty document" { - const formatter = HtmlFormatter.init(); - const doc = md.MarkdownDoc.parse(""); + const alloc = std.testing.allocator; + var formatter = HtmlFormatter.initDefault(); + const doc = try md.MarkdownDoc.parse("", alloc); + + const result = try formatter.format(alloc, doc); + + try std.testing.expectEqualStrings("", result); +} + +test "can format document with single h1 element" { + const alloc = std.testing.allocator; + + var formatter = HtmlFormatter.initDefault(); + defer formatter.deinit(alloc); + + var doc = try md.MarkdownDoc.parse( + \\# Hello, World! + , + alloc); + defer doc.deinit(alloc); + + const result = try formatter.format(alloc, doc); + + try std.testing.expectEqualStrings("

Hello, World!

", result); +} + +test "can format document with single h2 element" { + const alloc = std.testing.allocator; + + var formatter = HtmlFormatter.initDefault(); + defer formatter.deinit(alloc); - _ = formatter.format(doc); + var doc = try md.MarkdownDoc.parse( + \\## Hello, World! + , + alloc); + defer doc.deinit(alloc); + + const result = try formatter.format(alloc, doc); + + try std.testing.expectEqualStrings("

Hello, World!

", result); +} + +test "can format document with single p element" { + const alloc = std.testing.allocator; + + var formatter = HtmlFormatter.initDefault(); + defer formatter.deinit(alloc); + + var doc = try md.MarkdownDoc.parse( + \\Hello, World! + , + alloc); + defer doc.deinit(alloc); + + const result = try formatter.format(alloc, doc); + + try std.testing.expectEqualStrings("

Hello, World!

", result); +} + +test "can format document with more than one element" { + const alloc = std.testing.allocator; + + var formatter = HtmlFormatter.initDefault(); + defer formatter.deinit(alloc); + + var doc = try md.MarkdownDoc.parse( + \\# Post title + \\ + \\## Post subtitle + \\ + \\Some body text + \\ + \\Some more body text + , + alloc); + defer doc.deinit(alloc); + + const expectedHtml = + \\

Post title

+ \\

Post subtitle

+ \\

Some body text

+ \\

Some more body text

+ ; + + const result = try formatter.format(alloc, doc); + + try std.testing.expectEqualStrings(expectedHtml, result); +} + +test "can format empty document with template" { + const alloc = std.testing.allocator; + const template = + \\%s + ; + var formatter = HtmlFormatter.init(.{ .template = template }); + defer formatter.deinit(alloc); + + const doc = try md.MarkdownDoc.parse("", alloc); + + const result = try formatter.format(alloc, doc); + + try std.testing.expectEqualStrings("", result); } +test "can format empty non-document with template" { + const alloc = std.testing.allocator; + const template = + \\%s + ; + var formatter = HtmlFormatter.init(.{ .template = template }); + defer formatter.deinit(alloc); + + var doc = try md.MarkdownDoc.parse("# hello world", alloc); + defer doc.deinit(alloc); + + const result = try formatter.format(alloc, doc); + + try std.testing.expectEqualStrings("

hello world

", result); +} diff --git a/bin/blog-processor/src/markdown_parser.zig b/bin/blog-processor/src/markdown_parser.zig index f4722ed..6e5a030 100644 --- a/bin/blog-processor/src/markdown_parser.zig +++ b/bin/blog-processor/src/markdown_parser.zig @@ -95,9 +95,7 @@ pub const MarkdownDoc = struct { advanceWhileNot(doc, current, '\n'); try nodes.append(alloc, headerFn.call(doc[start..current.*])); - if (current.* < doc.len and doc[current.*] == '\n') { - current.* += 1; - } + advanceWhile(doc, current, '\n'); } fn parse_paragraph(alloc: std.mem.Allocator, doc: []const u8, current: *usize, nodes: *std.ArrayList(MarkdownNode)) !void { @@ -325,3 +323,49 @@ test "can parse single-line paragraph and multi-line parapgraphs at once at the try std.testing.expectEqualStrings("Hello world,", result.content.items[0].p); try std.testing.expectEqualStrings("this is my first post!\nBye.", result.content.items[1].p); } + +test "can parse a header and paragraph" { + const doc = + \\---- + \\date: 12/04/2026 + \\summary: This is the shit! + \\---- + \\# Post title + \\ + \\## Post subtitle + \\ + \\Hello world, + \\this is my first post! + ; + + const alloc = std.testing.allocator; + + var result = MarkdownDoc.parse(doc, alloc) catch unreachable; + defer result.deinit(alloc); + + try std.testing.expectEqual(3, result.content.items.len); + try std.testing.expectEqualStrings("Post title", result.content.items[0].h1); + try std.testing.expectEqualStrings("Post subtitle", result.content.items[1].h2); + try std.testing.expectEqualStrings("Hello world,\nthis is my first post!", result.content.items[2].p); +} + +test "can parse two consecutive paragraphs" { + const doc = + \\---- + \\date: 12/04/2026 + \\summary: This is the shit! + \\---- + \\Hello world, + \\ + \\this is my first post! + ; + + const alloc = std.testing.allocator; + + var result = MarkdownDoc.parse(doc, alloc) catch unreachable; + defer result.deinit(alloc); + + try std.testing.expectEqual(2, result.content.items.len); + try std.testing.expectEqualStrings("Hello world,", result.content.items[0].p); + try std.testing.expectEqualStrings("this is my first post!", result.content.items[1].p); +}