lib/zen/src/frontmatter/scan.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 const model = @import("model.zig");
 3 
 4 pub const Sections = struct {
 5     metadata_start: usize,
 6     metadata_end: usize,
 7     body_start: usize,
 8 };
 9 
10 pub const Field = union(enum) {
11     pair: struct {
12         key: []const u8,
13         value: []const u8,
14     },
15     continuation: []const u8,
16 };
17 
18 pub const Iterator = struct {
19     lines: std.mem.SplitIterator(u8, .scalar),
20 
21     pub fn init(bytes: []const u8) Iterator {
22         return .{ .lines = std.mem.splitScalar(u8, bytes, '\n') };
23     }
24 
25     pub fn next(self: *Iterator) ?Field {
26         while (self.lines.next()) |raw| {
27             const continuation = raw.len != 0 and (raw[0] == ' ' or raw[0] == '\t');
28             if (continuation) {
29                 const value = std.mem.trim(u8, raw, " \t\r");
30                 if (value.len != 0) return .{ .continuation = value };
31                 continue;
32             }
33             const line = std.mem.trim(u8, raw, " \t\r");
34             if (line.len == 0) continue;
35             const colon = std.mem.indexOfScalar(u8, line, ':') orelse continue;
36             const key = std.mem.trim(u8, line[0..colon], " \t");
37             if (key.len == 0) continue;
38             return .{ .pair = .{
39                 .key = key,
40                 .value = stripQuotes(std.mem.trim(u8, line[colon + 1 ..], " \t")),
41             } };
42         }
43         return null;
44     }
45 };
46 
47 pub fn sections(markdown: []const u8) model.ParseError!Sections {
48     const first_end = std.mem.indexOfScalar(u8, markdown, '\n') orelse return .{
49         .metadata_start = 0,
50         .metadata_end = 0,
51         .body_start = 0,
52     };
53     const first = std.mem.trim(u8, markdown[0..first_end], "\r");
54     if (!std.mem.eql(u8, first, "---")) return .{
55         .metadata_start = 0,
56         .metadata_end = 0,
57         .body_start = 0,
58     };
59 
60     var cursor = first_end + 1;
61     while (cursor <= markdown.len) {
62         const start = cursor;
63         const end = std.mem.indexOfScalarPos(u8, markdown, start, '\n') orelse markdown.len;
64         const line = std.mem.trim(u8, std.mem.trim(u8, markdown[start..end], "\r"), " \t");
65         if (std.mem.eql(u8, line, "---")) return .{
66             .metadata_start = first_end + 1,
67             .metadata_end = start,
68             .body_start = if (end < markdown.len) end + 1 else end,
69         };
70         if (end == markdown.len) break;
71         cursor = end + 1;
72     }
73     return error.UnclosedFrontmatter;
74 }
75 
76 fn stripQuotes(value: []const u8) []const u8 {
77     if (value.len >= 2 and value[0] == '"' and value[value.len - 1] == '"') {
78         return value[1 .. value.len - 1];
79     }
80     if (value.len >= 2 and value[0] == '\'' and value[value.len - 1] == '\'') {
81         return value[1 .. value.len - 1];
82     }
83     return value;
84 }