]> git.frustrated-labs.net Git - frustrated-functor.dev.git/commitdiff
start implementing templating for generated HTML
authorAlexander Goussas <[email protected]>
Sat, 18 Apr 2026 04:12:28 +0000 (23:12 -0500)
committerAlexander Goussas <[email protected]>
Sat, 18 Apr 2026 04:12:28 +0000 (23:12 -0500)
bin/blog-processor/src/html_formatter.zig
bin/blog-processor/src/markdown_parser.zig

index e3e801e650ae411b7be86bdc990e8e7dd3b9e330..71b03843b89e1c106e59f72544fe2b96e5c964c7 100644 (file)
 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{"</", tagName, ">"});
+        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("<h1>Hello, World!</h1>", 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("<h2>Hello, World!</h2>", 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("<p>Hello, World!</p>", 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 =
+        \\<h1>Post title</h1>
+        \\<h2>Post subtitle</h2>
+        \\<p>Some body text</p>
+        \\<p>Some more body text</p>
+        ;
+
+    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 = 
+        \\<body>%s</body>
+        ;
+    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("<body></body>", result);
 }
 
+test "can format empty non-document with template" {
+    const alloc = std.testing.allocator;
+    const template = 
+        \\<body>%s</body>
+        ;
+    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("<body><h1>hello world</h1></body>", result);
+}
index f4722eda26c4f5817bdec583b9da7be1aa91dd24..6e5a030730c32d5d4e0acd437a64480011a9117d 100644 (file)
@@ -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);
+}