From c393792cd10a889fb7071944ed9cb9ef0ce3fde9 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Thu, 25 May 2023 14:18:49 -0700 Subject: [PATCH] add config --- src/config.zig | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 5 ++- 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/config.zig diff --git a/src/config.zig b/src/config.zig new file mode 100644 index 0000000..eab3bea --- /dev/null +++ b/src/config.zig @@ -0,0 +1,107 @@ +const Self = @This(); +const std = @import("std"); + +allocator: std.mem.Allocator, + +pub const ParsedConfig = struct { + key_value_map: *std.StringArrayHashMap([]u8), + value_key_map: *std.StringArrayHashMap(*std.ArrayList([]u8)), + + config_allocator: std.mem.Allocator, + const SelfConfig = @This(); + + var by_keys: std.StringArrayHashMap([]u8) = undefined; + var by_values: std.StringArrayHashMap(*std.ArrayList([]u8)) = undefined; + + pub fn init(config_allocator: std.mem.Allocator) SelfConfig { + by_keys = std.StringArrayHashMap([]u8).init(config_allocator); + by_values = std.StringArrayHashMap(*std.ArrayList([]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| { + self.config_allocator.free(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.split(u8, line, "#")).first(), ws); + var data_iterator = std.mem.split(u8, nocomments, "="); + var key = std.mem.trim(u8, data_iterator.first(), ws); // first never fails + if (key.len == 0) continue; + var 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 + var dup_key = try self.allocator.dupe(u8, key); + var dup_value = try self.allocator.dupe(u8, value); + try rc.key_value_map.putNoClobber(dup_key, dup_value); + if (!rc.value_key_map.contains(value)) { + var keys = try self.allocator.create(std.ArrayList([]u8)); + keys.* = std.ArrayList([]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" { + var 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); +} diff --git a/src/main.zig b/src/main.zig index 7b55bc8..6f3632e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,10 +2,11 @@ const std = @import("std"); const builtin = @import("builtin"); const interface = @import("interface.zig"); const Watch = @import("Watch.zig"); +const config = @import("config.zig"); + const serveFn = *const fn (*interface.Request) ?*interface.Response; const zigInitFn = *const fn (*anyopaque) void; const requestDeinitFn = *const fn () void; - const timeout = 250; const FullReturn = struct { @@ -414,6 +415,8 @@ test { // `@This()` is a builtin function that returns the innermost container it is called from. // In this example, the innermost container is this file (implicitly a struct). std.testing.refAllDecls(@This()); + std.testing.refAllDecls(config); + std.testing.refAllDecls(interface); } var test_resp_buf: [1024]u8 = undefined; var test_resp_buf_len: usize = undefined;