empty cli or config args reset the value to the default

Fixes #1367

We previously special-cased optionals but we should do better and have
this reset ANY type to the defined default value on the struct.
This commit is contained in:
Mitchell Hashimoto
2024-01-23 18:57:33 -08:00
parent 78a5f64e84
commit c9371500c9
2 changed files with 41 additions and 14 deletions

View File

@ -71,6 +71,10 @@ foreground= ffffff
keybind = ctrl+z=close_surface keybind = ctrl+z=close_surface
keybind = ctrl+d=new_split:right keybind = ctrl+d=new_split:right
# Empty values reset the configuration to the default value
font-family =
# Colors can be changed by setting the 16 colors of `palette`, which each color # Colors can be changed by setting the 16 colors of `palette`, which each color
# being defined as regular and bold. # being defined as regular and bold.
# #

View File

@ -170,20 +170,14 @@ fn parseIntoField(
inline for (info.Struct.fields) |field| { inline for (info.Struct.fields) |field| {
if (field.name[0] != '_' and mem.eql(u8, field.name, key)) { if (field.name[0] != '_' and mem.eql(u8, field.name, key)) {
// If the field is optional then consider scenarios we reset // If the value is empty string (set but empty string),
// the value to being unset. We allow unsetting optionals // then we reset the value to the default.
// whenever the value is "". if (value) |v| default: {
// if (v.len != 0) break :default;
// At the time of writing this, empty string isn't a desirable const raw = field.default_value orelse break :default;
// value for any optional field under any realistic scenario. const ptr: *const field.type = @alignCast(@ptrCast(raw));
// @field(dst, field.name) = ptr.*;
// We don't allow unset values to set optional fields to return;
// null because unset value for booleans always means true.
if (@typeInfo(field.type) == .Optional) optional: {
if (std.mem.eql(u8, "", value orelse break :optional)) {
@field(dst, field.name) = null;
return;
}
} }
// For optional fields, we just treat it as the child type. // For optional fields, we just treat it as the child type.
@ -396,6 +390,26 @@ test "parse: quoted value" {
try testing.expectEqualStrings("hello!", data.b); try testing.expectEqualStrings("hello!", data.b);
} }
test "parse: empty value resets to default" {
const testing = std.testing;
var data: struct {
a: u8 = 42,
b: bool = false,
_arena: ?ArenaAllocator = null,
} = .{};
defer if (data._arena) |arena| arena.deinit();
var iter = try std.process.ArgIteratorGeneral(.{}).init(
testing.allocator,
"--a= --b=",
);
defer iter.deinit();
try parse(@TypeOf(data), testing.allocator, &data, &iter);
try testing.expectEqual(@as(u8, 42), data.a);
try testing.expect(!data.b);
}
test "parse: error tracking" { test "parse: error tracking" {
const testing = std.testing; const testing = std.testing;
@ -865,6 +879,15 @@ test "LineIterator spaces around '='" {
try testing.expectEqual(@as(?[]const u8, null), iter.next()); try testing.expectEqual(@as(?[]const u8, null), iter.next());
} }
test "LineIterator no value" {
const testing = std.testing;
var fbs = std.io.fixedBufferStream("A = \n\n");
var iter = lineIterator(fbs.reader());
try testing.expectEqualStrings("--A=", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next());
}
test "LineIterator with CRLF line endings" { test "LineIterator with CRLF line endings" {
const testing = std.testing; const testing = std.testing;
var fbs = std.io.fixedBufferStream("A\r\nB = C\r\n"); var fbs = std.io.fixedBufferStream("A\r\nB = C\r\n");