mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
cli: parse auto structs
This commit is contained in:
108
src/cli/args.zig
108
src/cli/args.zig
@ -310,8 +310,9 @@ fn parseIntoField(
|
|||||||
value orelse return error.ValueRequired,
|
value orelse return error.ValueRequired,
|
||||||
) orelse return error.InvalidValue,
|
) orelse return error.InvalidValue,
|
||||||
|
|
||||||
.Struct => try parsePackedStruct(
|
.Struct => try parseStruct(
|
||||||
Field,
|
Field,
|
||||||
|
alloc,
|
||||||
value orelse return error.ValueRequired,
|
value orelse return error.ValueRequired,
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -378,9 +379,79 @@ fn parseTaggedUnion(comptime T: type, alloc: Allocator, v: []const u8) !T {
|
|||||||
return error.InvalidValue;
|
return error.InvalidValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parseStruct(comptime T: type, alloc: Allocator, v: []const u8) !T {
|
||||||
|
return switch (@typeInfo(T).Struct.layout) {
|
||||||
|
.auto => parseAutoStruct(T, alloc, v),
|
||||||
|
.@"packed" => parsePackedStruct(T, v),
|
||||||
|
else => @compileError("unsupported struct layout"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseAutoStruct(comptime T: type, alloc: Allocator, v: []const u8) !T {
|
||||||
|
const info = @typeInfo(T).Struct;
|
||||||
|
comptime assert(info.layout == .auto);
|
||||||
|
|
||||||
|
// We start our result as undefined so we don't get an error for required
|
||||||
|
// fields. We track required fields below and we validate that we set them
|
||||||
|
// all at the bottom of this function (in addition to setting defaults for
|
||||||
|
// optionals).
|
||||||
|
var result: T = undefined;
|
||||||
|
|
||||||
|
// Keep track of which fields were set so we can error if a required
|
||||||
|
// field was not set.
|
||||||
|
const FieldSet = std.StaticBitSet(info.fields.len);
|
||||||
|
var fields_set: FieldSet = FieldSet.initEmpty();
|
||||||
|
|
||||||
|
// We split each value by ","
|
||||||
|
var iter = std.mem.splitSequence(u8, v, ",");
|
||||||
|
loop: while (iter.next()) |entry| {
|
||||||
|
// Find the key/value, trimming whitespace. The value may be quoted
|
||||||
|
// which we strip the quotes from.
|
||||||
|
const idx = mem.indexOf(u8, entry, ":") orelse return error.InvalidValue;
|
||||||
|
const key = std.mem.trim(u8, entry[0..idx], whitespace);
|
||||||
|
const value = value: {
|
||||||
|
var value = std.mem.trim(u8, entry[idx + 1 ..], whitespace);
|
||||||
|
|
||||||
|
// Detect a quoted string.
|
||||||
|
if (value.len >= 2 and
|
||||||
|
value[0] == '"' and
|
||||||
|
value[value.len - 1] == '"')
|
||||||
|
{
|
||||||
|
// Trim quotes since our CLI args processor expects
|
||||||
|
// quotes to already be gone.
|
||||||
|
value = value[1 .. value.len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
break :value value;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline for (info.fields, 0..) |field, i| {
|
||||||
|
if (std.mem.eql(u8, field.name, key)) {
|
||||||
|
try parseIntoField(T, alloc, &result, key, value);
|
||||||
|
fields_set.set(i);
|
||||||
|
continue :loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No field matched
|
||||||
|
return error.InvalidValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all required fields are set
|
||||||
|
inline for (info.fields, 0..) |field, i| {
|
||||||
|
if (!fields_set.isSet(i)) {
|
||||||
|
const default_ptr = field.default_value orelse return error.InvalidValue;
|
||||||
|
const typed_ptr: *const field.type = @alignCast(@ptrCast(default_ptr));
|
||||||
|
@field(result, field.name) = typed_ptr.*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
fn parsePackedStruct(comptime T: type, v: []const u8) !T {
|
fn parsePackedStruct(comptime T: type, v: []const u8) !T {
|
||||||
const info = @typeInfo(T).Struct;
|
const info = @typeInfo(T).Struct;
|
||||||
assert(info.layout == .@"packed");
|
comptime assert(info.layout == .@"packed");
|
||||||
|
|
||||||
var result: T = .{};
|
var result: T = .{};
|
||||||
|
|
||||||
@ -847,6 +918,39 @@ test "parseIntoField: struct with parse func" {
|
|||||||
try testing.expectEqual(@as([]const u8, "HELLO!"), data.a.v);
|
try testing.expectEqual(@as([]const u8, "HELLO!"), data.a.v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "parseIntoField: struct with basic fields" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var data: struct {
|
||||||
|
value: struct {
|
||||||
|
a: []const u8,
|
||||||
|
b: u32,
|
||||||
|
c: u8 = 12,
|
||||||
|
} = undefined,
|
||||||
|
} = .{};
|
||||||
|
|
||||||
|
// Set required fields
|
||||||
|
try parseIntoField(@TypeOf(data), alloc, &data, "value", "a:hello,b:42");
|
||||||
|
try testing.expectEqualStrings("hello", data.value.a);
|
||||||
|
try testing.expectEqual(42, data.value.b);
|
||||||
|
try testing.expectEqual(12, data.value.c);
|
||||||
|
|
||||||
|
// Set all fields
|
||||||
|
try parseIntoField(@TypeOf(data), alloc, &data, "value", "a:world,b:84,c:24");
|
||||||
|
try testing.expectEqualStrings("world", data.value.a);
|
||||||
|
try testing.expectEqual(84, data.value.b);
|
||||||
|
try testing.expectEqual(24, data.value.c);
|
||||||
|
|
||||||
|
// Missing require dfield
|
||||||
|
try testing.expectError(
|
||||||
|
error.InvalidValue,
|
||||||
|
parseIntoField(@TypeOf(data), alloc, &data, "value", "a:hello"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
test "parseIntoField: tagged union" {
|
test "parseIntoField: tagged union" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
Reference in New Issue
Block a user