summaryrefslogtreecommitdiff
path: root/src/cli/args.zig
diff options
context:
space:
mode:
authorMitchell Hashimoto <mitchell.hashimoto@gmail.com>2023-11-07 15:59:56 -0800
committerMitchell Hashimoto <mitchell.hashimoto@gmail.com>2023-11-07 15:59:56 -0800
commit8cd3b65d0adad0a343842709997772afaa860a7f (patch)
treeca07f7de1194985bc7f68bd67760f50c98d94aca /src/cli/args.zig
parent4fac6740169372d6bb8a71a6d3154e3a187134f0 (diff)
config: packed struct of bools supported as config field
Diffstat (limited to 'src/cli/args.zig')
-rw-r--r--src/cli/args.zig123
1 files changed, 109 insertions, 14 deletions
diff --git a/src/cli/args.zig b/src/cli/args.zig
index eb91c43a8..678c44b1d 100644
--- a/src/cli/args.zig
+++ b/src/cli/args.zig
@@ -10,6 +10,9 @@ const ErrorList = @import("../config/ErrorList.zig");
// - Only `--long=value` format is accepted. Do we want to allow
// `--long value`? Not currently allowed.
+// For trimming
+const whitespace = " \t";
+
/// The base errors for arg parsing. Additional errors can be returned due
/// to type-specific parsing but these are always possible.
pub const Error = error{
@@ -191,18 +194,6 @@ fn parseIntoField(
}
}
- switch (fieldInfo) {
- .Enum => {
- @field(dst, field.name) = std.meta.stringToEnum(
- Field,
- value orelse return error.ValueRequired,
- ) orelse return error.InvalidValue;
- return;
- },
-
- else => {},
- }
-
// No parseCLI, magic the value based on the type
@field(dst, field.name) = switch (Field) {
[]const u8 => value: {
@@ -239,7 +230,19 @@ fn parseIntoField(
value orelse return error.ValueRequired,
) catch return error.InvalidValue,
- else => unreachable,
+ else => switch (fieldInfo) {
+ .Enum => std.meta.stringToEnum(
+ Field,
+ value orelse return error.ValueRequired,
+ ) orelse return error.InvalidValue,
+
+ .Struct => try parsePackedStruct(
+ Field,
+ value orelse return error.ValueRequired,
+ ),
+
+ else => unreachable,
+ },
};
return;
@@ -249,6 +252,42 @@ fn parseIntoField(
return error.InvalidField;
}
+fn parsePackedStruct(comptime T: type, v: []const u8) !T {
+ const info = @typeInfo(T).Struct;
+ assert(info.layout == .Packed);
+
+ var result: T = .{};
+
+ // We split each value by ","
+ var iter = std.mem.splitSequence(u8, v, ",");
+ loop: while (iter.next()) |part_raw| {
+ // Determine the field we're looking for and the value. If the
+ // field is prefixed with "no-" then we set the value to false.
+ const part, const value = part: {
+ const negation_prefix = "no-";
+ const trimmed = std.mem.trim(u8, part_raw, whitespace);
+ if (std.mem.startsWith(u8, trimmed, negation_prefix)) {
+ break :part .{ trimmed[negation_prefix.len..], false };
+ } else {
+ break :part .{ trimmed, true };
+ }
+ };
+
+ inline for (info.fields) |field| {
+ assert(field.type == bool);
+ if (std.mem.eql(u8, field.name, part)) {
+ @field(result, field.name) = value;
+ continue :loop;
+ }
+ }
+
+ // No field matched
+ return error.InvalidValue;
+ }
+
+ return result;
+}
+
fn parseBool(v: []const u8) !bool {
const t = &[_][]const u8{ "1", "t", "T", "true" };
const f = &[_][]const u8{ "0", "f", "F", "false" };
@@ -462,6 +501,63 @@ test "parseIntoField: enums" {
try testing.expectEqual(Enum.two, data.v);
}
+test "parseIntoField: packed struct" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ const Field = packed struct {
+ a: bool = false,
+ b: bool = true,
+ };
+ var data: struct {
+ v: Field,
+ } = undefined;
+
+ try parseIntoField(@TypeOf(data), alloc, &data, "v", "b");
+ try testing.expect(!data.v.a);
+ try testing.expect(data.v.b);
+}
+
+test "parseIntoField: packed struct negation" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ const Field = packed struct {
+ a: bool = false,
+ b: bool = true,
+ };
+ var data: struct {
+ v: Field,
+ } = undefined;
+
+ try parseIntoField(@TypeOf(data), alloc, &data, "v", "a,no-b");
+ try testing.expect(data.v.a);
+ try testing.expect(!data.v.b);
+}
+
+test "parseIntoField: packed struct whitespace" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ const Field = packed struct {
+ a: bool = false,
+ b: bool = true,
+ };
+ var data: struct {
+ v: Field,
+ } = undefined;
+
+ try parseIntoField(@TypeOf(data), alloc, &data, "v", " a, no-b ");
+ try testing.expect(data.v.a);
+ try testing.expect(!data.v.b);
+}
+
test "parseIntoField: optional field" {
const testing = std.testing;
var arena = ArenaAllocator.init(testing.allocator);
@@ -582,7 +678,6 @@ pub fn LineIterator(comptime ReaderType: type) type {
} orelse return null;
// Trim any whitespace around it
- const whitespace = " \t";
const trim = std.mem.trim(u8, entry, whitespace);
if (trim.len != entry.len) {
std.mem.copy(u8, entry, trim);