Makko, the people-oriented static site generator made for blogging. forge.starlightnet.work/Team/Makko
ssg static-site-generator makko starlight-network
at main 4.5 kB view raw
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}