const std = @import("std");
+const assert = std.debug.assert;
+
+const logger = std.log.scoped(.markdown_parser);
+
+const MarkdownParserError = error{
+ UnexpectedToken,
+};
pub const MarkdownDoc = struct {
date: []const u8,
summary: []const u8,
content: []const u8,
- pub fn parse(doc: []const u8, alloc: std.mem.Allocator) @This() {
+ pub fn parse(doc: []const u8, alloc: std.mem.Allocator) MarkdownParserError!@This() {
_ = alloc;
- var start = 0;
- var current = 0;
+ var start: usize = 0;
+ var current: usize = 0;
+
+ var date: []const u8 = undefined;
+ var summary: []const u8 = undefined;
while (current < doc.len) {
start = current;
const c = doc[current];
switch (c) {
- '-' => parse_frontmatter(&start, ¤t),
- _ => current += 1,
+ '-' => try parse_frontmatter(¤t, doc, &date, &summary),
+ else => current += 1,
}
}
- return MarkdownDoc{};
+ return .{
+ .date = date,
+ .summary = summary,
+ .content = undefined
+ };
}
fn parse_frontmatter(
- start: *u32,
- current: *u32
- ) void {
+ current: *usize,
+ doc: []const u8,
+ date: *[]const u8,
+ summary: *[]const u8,
+ ) MarkdownParserError!void {
+ assert(doc[current.*] == '-');
+
+ advanceWhile(doc, current, '-');
+ try expectToken(doc, '\n', current);
+
+ try parse_frontmatter_value(current, doc, date);
+ try parse_frontmatter_value(current, doc, summary);
+
+ try expectToken(doc, '-', current);
+ advanceWhile(doc, current, '-');
+ }
+
+ fn parse_frontmatter_value(current: *usize, doc: []const u8, value: *[]const u8) MarkdownParserError!void {
+ var start: usize = undefined;
+
+ advanceWhileFn(doc, current, std.ascii.isAlphabetic);
+
+ try expectToken(doc, ':', current);
+
+ advanceWhile(doc, current, ' ');
+ start = current.*;
+
+ advanceWhileNot(doc, current, '\n');
+ try expectToken(doc, '\n', current);
+
+ value.* = doc[start..current.* - 1];
+ }
+
+ fn expectToken(doc: []const u8, c: u8, current: *usize) MarkdownParserError!void {
+ if (doc[current.*] != c) {
+ const errMsg = "Expected '" ++ [1]u8{c} ++ "', but got '" ++ [1]u8{doc[current.*]} ++ "'";
+ if (!@inComptime()) {
+ logger.err(errMsg, .{});
+ return error.UnexpectedToken;
+ } else {
+ @compileError(errMsg);
+ }
+ }
+ assert(doc[current.*] == c);
+ current.* += 1;
+ }
+
+ fn advanceWhileFn(doc: []const u8, current: *usize, f: fn(u8) bool) void {
+ while (current.* < doc.len and f(doc[current.*])) {
+ current.* += 1;
+ }
+ }
+
+ fn advanceWhile(doc: []const u8, current: *usize, c: u8) void {
+ while (current.* < doc.len and doc[current.*] == c) {
+ current.* += 1;
+ }
+ }
+
+ fn advanceWhileNot(doc: []const u8, current: *usize, c: u8) void {
+ while (current.* < doc.len and doc[current.*] != c) {
+ current.* += 1;
+ }
}
};
+
+test "can parse date in frontmatter" {
+ const doc =
+ \\----
+ \\date: 12/04/2026
+ \\summary: This is the shit!
+ \\----
+ ;
+
+ const alloc = std.testing.allocator;
+
+ const result = comptime MarkdownDoc.parse(doc, alloc) catch unreachable;
+
+ try std.testing.expectEqualStrings("12/04/2026", result.date);
+}
+
+test "can parse summary in frontmatter" {
+ const doc =
+ \\----
+ \\date: 12/04/2026
+ \\summary: This is the shit!
+ \\----
+ ;
+
+ const alloc = std.testing.allocator;
+
+ const result = comptime MarkdownDoc.parse(doc, alloc) catch unreachable;
+
+ try std.testing.expectEqualStrings("This is the shit!", result.summary);
+}