FlexiLib/src/config.zig
Emil Lerch 5bd4d227db
Some checks failed
Build / build (push) Successful in 46s
Build / sign (push) Failing after 47s
Build / deploy (push) Successful in 58s
split -> splitScalar
2024-10-15 14:52:54 -07:00

108 lines
4.2 KiB
Zig

const Self = @This();
const std = @import("std");
allocator: std.mem.Allocator,
pub const ParsedConfig = struct {
key_value_map: *std.StringArrayHashMap([:0]u8),
value_key_map: *std.StringArrayHashMap(*std.ArrayList([:0]u8)),
config_allocator: std.mem.Allocator,
const SelfConfig = @This();
var by_keys: std.StringArrayHashMap([:0]u8) = undefined;
var by_values: std.StringArrayHashMap(*std.ArrayList([:0]u8)) = undefined;
pub fn init(config_allocator: std.mem.Allocator) SelfConfig {
by_keys = std.StringArrayHashMap([:0]u8).init(config_allocator);
by_values = std.StringArrayHashMap(*std.ArrayList([:0]u8)).init(config_allocator);
return SelfConfig{
.config_allocator = config_allocator,
.key_value_map = &by_keys,
.value_key_map = &by_values,
};
}
pub fn deinit(self: *SelfConfig) void {
for (self.key_value_map.keys(), self.key_value_map.values()) |k, v| {
// StringArrayHashMap assumes []const u8, but what we've allocated is null terminated
self.config_allocator.free(@as([:0]const u8, @ptrCast(k))); // this is also the key in value_key_map
self.config_allocator.free(v);
}
self.key_value_map.deinit();
for (self.value_key_map.values()) |v| {
// The string in the value array is the same as the one in the key
// no need to free
// for (v.items) |item| {
// self.config_allocator.free(item);
// }
v.deinit();
self.config_allocator.destroy(v);
}
// These were already freed above
// for (self.value_key_map.keys()) |*k| {
// self.config_allocator.free(k.*);
// }
self.value_key_map.deinit();
}
};
pub fn init(allocator: std.mem.Allocator) Self {
return Self{
.allocator = allocator,
};
}
/// Based on a reader, the return will be an ordered list of entries. The
/// key is a path prefix, while the value is the library to use
/// caller owns the allocated memory
pub fn parse(self: Self, reader: anytype) !ParsedConfig {
const ws = " \t";
var rc = ParsedConfig.init(self.allocator);
errdefer rc.deinit();
while (try reader.readUntilDelimiterOrEofAlloc(self.allocator, '\n', std.math.maxInt(usize))) |line| {
defer self.allocator.free(line);
const nocomments = std.mem.trim(u8, @constCast(&std.mem.splitScalar(u8, line, '#')).first(), ws);
var data_iterator = std.mem.splitScalar(u8, nocomments, '=');
const key = std.mem.trim(u8, data_iterator.first(), ws); // first never fails
if (key.len == 0) continue;
const value = std.mem.trim(u8, data_iterator.next() orelse return error.NoValueForKey, ws);
// keys should be putNoClobber, but values can be put.
// Because we have to dup the memory here though, we want to
// manage duplicate values seperately
const dup_key = try self.allocator.dupeZ(u8, key);
const dup_value = try self.allocator.dupeZ(u8, value);
try rc.key_value_map.putNoClobber(dup_key, dup_value);
if (!rc.value_key_map.contains(value)) {
const keys = try self.allocator.create(std.ArrayList([:0]u8));
keys.* = std.ArrayList([:0]u8).init(self.allocator);
try rc.value_key_map.put(dup_value, keys);
}
try rc.value_key_map.get(value).?.append(dup_key);
}
return rc;
}
test "gets config from a stream" {
const allocator = std.testing.allocator;
var stream = std.io.fixedBufferStream(
\\# This is a simple "path prefix" = dynamic library path mapping
\\ # no reordering will be done, so you must do things most -> least specific
\\
\\
\\foo = bar
\\
\\baz= qux# what *is* this?
\\
\\
\\bar =foo
\\qaz=foo
);
var config = try Self.init(allocator).parse(stream.reader());
defer config.deinit();
try std.testing.expectEqual(@as(usize, 4), config.key_value_map.keys().len);
try std.testing.expectEqual(@as(usize, 3), config.value_key_map.keys().len);
try std.testing.expectEqual(@as(usize, 2), config.value_key_map.get("foo").?.items.len);
}