Makko, the people-oriented static site generator made for blogging.
forge.starlightnet.work/Team/Makko
ssg
static-site-generator
makko
starlight-network
1// Faux implements a pretty terrible subset of YAML
2// because apparently no YAML parser for Zig is good yet
3// and I am honestly already growing quite angered about it.
4//
5// It's insanely simple, it's meant to be simple, and it
6// will stay that way.
7//
8// I like to see it as it's own format, atp.
9//
10// Also, important note, the day that a proper YAML parser
11// comes out for Zig, this library must be pulled off
12// immediately and destroyed. yes.
13//
14
15const std = @import("std");
16
17fn parseSimpleKV(
18 allocator: std.mem.Allocator,
19 input: []const u8,
20) !Map {
21 var map = Map.init(allocator);
22 var lines = std.mem.tokenizeScalar(u8, input, '\n');
23
24 while (lines.next()) |line| {
25 const hash_index = std.mem.indexOfScalar(u8, line, '#') orelse line.len;
26 const trimmed = std.mem.trim(u8, line[0..hash_index], " \t\r");
27
28 if (trimmed.len == 0) continue;
29
30 const colon_index = std.mem.indexOfScalar(u8, trimmed, ':') orelse continue;
31
32 const key = std.mem.trim(u8, trimmed[0..colon_index], " \t");
33 var value = std.mem.trim(u8, trimmed[colon_index + 1 ..], " \t");
34
35 if (value.len >= 2 and ((value[0] == '"' and value[value.len - 1] == '"') or
36 (value[0] == '\'' and value[value.len - 1] == '\'')))
37 {
38 const inner = value[1 .. value.len - 1];
39 value = try unescapeString(allocator, inner, value[0]);
40 } else {
41 value = try unescapeString(allocator, value, 0);
42 }
43
44 try map.put(key, value);
45 }
46
47 return map;
48}
49
50fn unescapeString(
51 allocator: std.mem.Allocator,
52 s: []const u8,
53 quote: u8,
54) ![]const u8 {
55 var buffer = std.ArrayList(u8).init(allocator);
56 var i: usize = 0;
57
58 while (i < s.len) : (i += 1) {
59 if (s[i] == '\\' and i + 1 < s.len) {
60 i += 1;
61 switch (s[i]) {
62 'n' => try buffer.append('\n'),
63 'r' => try buffer.append('\r'),
64 't' => try buffer.append('\t'),
65 '\\' => try buffer.append('\\'),
66 '"' => if (quote == '"' or quote == 0) try buffer.append('"') else {
67 try buffer.append('\\');
68 try buffer.append('"');
69 },
70 '\'' => if (quote == '\'' or quote == 0) try buffer.append('\'') else {
71 try buffer.append('\\');
72 try buffer.append('\'');
73 },
74 else => {
75 try buffer.append('\\');
76 try buffer.append(s[i]);
77 },
78 }
79 } else {
80 try buffer.append(s[i]);
81 }
82 }
83
84 return buffer.toOwnedSlice();
85}
86
87fn populateStructFromMap(comptime T: type, dest: *T, map: Map, allocator: std.mem.Allocator) !void {
88 const fields = std.meta.fields(T);
89 inline for (fields) |field| {
90 const key = field.name;
91 const field_type = field.type;
92
93 @field(dest, key) = if (map.get(key)) |raw|
94 try parseValue(field_type, raw, allocator)
95 else
96 field.defaultValue() orelse return error.MissingField;
97 }
98}
99
100fn parseValue(comptime T: type, raw: []const u8, allocator: std.mem.Allocator) !T {
101 const info = @typeInfo(T);
102 return switch (info) {
103 .bool => std.mem.eql(u8, raw, "true"),
104
105 .pointer => if (T == []const u8 or T == []u8)
106 try allocator.dupe(u8, raw)
107 else
108 error.PointersUnsupported,
109
110 .@"enum" => std.meta.stringToEnum(T, raw) orelse error.InvalidEnumValue,
111
112 .optional => blk: {
113 if (std.mem.eql(u8, raw, "null"))
114 break :blk null;
115
116 const child = info.optional.child;
117 const parsed = try parseValue(child, raw, allocator);
118 break :blk parsed;
119 },
120
121 else => error.NotImplemented,
122 };
123}
124
125const Map = std.StringHashMap([]const u8);
126map: Map,
127arena: std.heap.ArenaAllocator,
128
129const Faux = @This();
130
131pub fn parse(source: []const u8, allocator: std.mem.Allocator) !Faux {
132 var f = Faux{
133 .arena = std.heap.ArenaAllocator.init(allocator),
134 .map = undefined,
135 };
136
137 f.map = try parseSimpleKV(f.arena.allocator(), source);
138 return f;
139}
140
141pub fn toStructure(self: *Faux, comptime T: type) !T {
142 var t: T = undefined;
143 try populateStructFromMap(T, &t, self.map, self.arena.allocator());
144 return t;
145}
146
147pub fn deinit(self: *Faux) void {
148 self.arena.deinit();
149}