diff --git a/src/cli_args.zig b/src/cli_args.zig index 9a69ebabc..080cdbfb7 100644 --- a/src/cli_args.zig +++ b/src/cli_args.zig @@ -276,3 +276,54 @@ test "parseIntoField: struct with parse func" { try parseIntoField(@TypeOf(data), alloc, &data, "a", "42"); try testing.expectEqual(@as([]const u8, "HELLO!"), data.a.v); } + +/// Returns an interator (implements "next") that reads CLI args by line. +/// Each CLI arg is expected to be a single line. This is used to implement +/// configuration files. +pub fn LineIterator(comptime ReaderType: type) type { + return struct { + const Self = @This(); + + /// The maximum size a single line can be. We don't expect any + /// CLI arg to exceed this size. Can't wait to git blame this in + /// like 4 years and be wrong about this. + pub const MAX_LINE_SIZE = 4096; + + r: ReaderType, + entry: [MAX_LINE_SIZE]u8 = [_]u8{ '-', '-' } ++ ([_]u8{0} ** (MAX_LINE_SIZE - 2)), + + pub fn next(self: *Self) ?[]const u8 { + // TODO: detect "--" prefixed lines and give a friendlier error + const buf = self.r.readUntilDelimiterOrEof(self.entry[2..], '\n') catch { + // TODO: handle errors + unreachable; + } orelse return null; + + // We need to reslice so that we include our '--' at the beginning + // of our buffer so that we can trick the CLI parser to treat it + // as CLI args. + return self.entry[0 .. buf.len + 2]; + } + }; +} + +// Constructs a LineIterator (see docs for that). +pub fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) { + return .{ .r = reader }; +} + +test "LineIterator" { + const testing = std.testing; + var fbs = std.io.fixedBufferStream( + \\A + \\B + \\C + ); + + var iter = lineIterator(fbs.reader()); + try testing.expectEqualStrings("--A", iter.next().?); + try testing.expectEqualStrings("--B", iter.next().?); + try testing.expectEqualStrings("--C", iter.next().?); + try testing.expectEqual(@as(?[]const u8, null), iter.next()); + try testing.expectEqual(@as(?[]const u8, null), iter.next()); +} diff --git a/src/config.zig b/src/config.zig index efd89caab..63043eb4b 100644 --- a/src/config.zig +++ b/src/config.zig @@ -2,6 +2,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; +/// Config is the main config struct. These fields map directly to the +/// CLI flag names hence we use a lot of `@""` syntax to support hyphens. pub const Config = struct { /// Font size @"font-size": u8 = 14, diff --git a/src/main.zig b/src/main.zig index f958ebc03..d179f0f9c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -43,6 +43,22 @@ pub fn main() !void { break :config result; }; defer config.deinit(); + + // Parse the config files + // TODO(mitchellh): support nesting (config-file in a config file) + // TODO(mitchellh): detect cycles when nesting + if (config.@"config-file".list.items.len > 0) { + const cwd = std.fs.cwd(); + for (config.@"config-file".list.items) |path| { + var file = try cwd.openFile(path, .{}); + defer file.close(); + + var buf_reader = std.io.bufferedReader(file.reader()); + var iter = cli_args.lineIterator(buf_reader.reader()); + + try cli_args.parse(Config, alloc, &config, &iter); + } + } log.info("config={}", .{config}); // We want to log all our errors