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);
+}
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 {
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);
+}