mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
cli parsing supports modification, add "RepeatableString" as example
This lets values modify themselves, which we use to make a repeatable string implementation. We will use this initially to specify config files to load.
This commit is contained in:
@ -61,7 +61,6 @@ fn parseIntoField(
|
|||||||
|
|
||||||
inline for (info.Struct.fields) |field| {
|
inline for (info.Struct.fields) |field| {
|
||||||
if (mem.eql(u8, field.name, key)) {
|
if (mem.eql(u8, field.name, key)) {
|
||||||
@field(dst, field.name) = field: {
|
|
||||||
// For optional fields, we just treat it as the child type.
|
// For optional fields, we just treat it as the child type.
|
||||||
// This lets optional fields default to null but get set by
|
// This lets optional fields default to null but get set by
|
||||||
// the CLI.
|
// the CLI.
|
||||||
@ -71,14 +70,28 @@ fn parseIntoField(
|
|||||||
};
|
};
|
||||||
const fieldInfo = @typeInfo(Field);
|
const fieldInfo = @typeInfo(Field);
|
||||||
|
|
||||||
// If the type implements a parse function, call that.
|
// If we are a struct and have parseCLI, we call that and use
|
||||||
// NOTE(mitchellh): this is a pretty nasty break statement.
|
// that to set the value.
|
||||||
// I split it into two at first and it failed with Zig as of
|
if (fieldInfo == .Struct and @hasDecl(Field, "parseCLI")) {
|
||||||
// July 21, 2022. I think stage2+ will fix this so lets clean
|
const fnInfo = @typeInfo(@TypeOf(Field.parseCLI)).Fn;
|
||||||
// this up when that comes out.
|
switch (fnInfo.args.len) {
|
||||||
break :field if (fieldInfo == .Struct and @hasDecl(Field, "parseCLI"))
|
// 1 arg = (input) => output
|
||||||
try Field.parseCLI(value)
|
1 => @field(dst, field.name) = try Field.parseCLI(value),
|
||||||
else switch (Field) {
|
|
||||||
|
// 2 arg = (self, input) => void
|
||||||
|
2 => try @field(dst, field.name).parseCLI(value),
|
||||||
|
|
||||||
|
// 3 arg = (self, alloc, input) => void
|
||||||
|
3 => try @field(dst, field.name).parseCLI(alloc, value),
|
||||||
|
|
||||||
|
else => @compileError("parseCLI invalid argument count"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No parseCLI, magic the value based on the type
|
||||||
|
@field(dst, field.name) = switch (Field) {
|
||||||
[]const u8 => if (value) |slice| value: {
|
[]const u8 => if (value) |slice| value: {
|
||||||
const buf = try alloc.alloc(u8, slice.len);
|
const buf = try alloc.alloc(u8, slice.len);
|
||||||
mem.copy(u8, buf, slice);
|
mem.copy(u8, buf, slice);
|
||||||
@ -91,7 +104,6 @@ fn parseIntoField(
|
|||||||
|
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
pub const Config = struct {
|
pub const Config = struct {
|
||||||
@ -15,6 +16,9 @@ pub const Config = struct {
|
|||||||
/// it'll be looked up in the PATH.
|
/// it'll be looked up in the PATH.
|
||||||
command: ?[]const u8 = null,
|
command: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// Additional configuration files to read.
|
||||||
|
@"config-file": RepeatableString = .{},
|
||||||
|
|
||||||
/// This is set by the CLI parser for deinit.
|
/// This is set by the CLI parser for deinit.
|
||||||
_arena: ?ArenaAllocator = null,
|
_arena: ?ArenaAllocator = null,
|
||||||
|
|
||||||
@ -65,13 +69,46 @@ pub const Color = struct {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
test "Color.fromHex" {
|
test "fromHex" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
try testing.expectEqual(Color{ .r = 0, .g = 0, .b = 0 }, try Color.fromHex("#000000"));
|
try testing.expectEqual(Color{ .r = 0, .g = 0, .b = 0 }, try Color.fromHex("#000000"));
|
||||||
try testing.expectEqual(Color{ .r = 10, .g = 11, .b = 12 }, try Color.fromHex("#0A0B0C"));
|
try testing.expectEqual(Color{ .r = 10, .g = 11, .b = 12 }, try Color.fromHex("#0A0B0C"));
|
||||||
try testing.expectEqual(Color{ .r = 10, .g = 11, .b = 12 }, try Color.fromHex("0A0B0C"));
|
try testing.expectEqual(Color{ .r = 10, .g = 11, .b = 12 }, try Color.fromHex("0A0B0C"));
|
||||||
try testing.expectEqual(Color{ .r = 255, .g = 255, .b = 255 }, try Color.fromHex("FFFFFF"));
|
try testing.expectEqual(Color{ .r = 255, .g = 255, .b = 255 }, try Color.fromHex("FFFFFF"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// RepeatableString is a string value that can be repeated to accumulate
|
||||||
|
/// a list of strings. This isn't called "StringList" because I find that
|
||||||
|
/// sometimes leads to confusion that it _accepts_ a list such as
|
||||||
|
/// comma-separated values.
|
||||||
|
pub const RepeatableString = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
// Allocator for the list is the arena for the parent config.
|
||||||
|
list: std.ArrayListUnmanaged([]const u8) = .{},
|
||||||
|
|
||||||
|
pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void {
|
||||||
|
const value = input orelse return error.ValueRequired;
|
||||||
|
try self.list.append(alloc, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseCLI" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var list: Self = .{};
|
||||||
|
try list.parseCLI(alloc, "A");
|
||||||
|
try list.parseCLI(alloc, "B");
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 2), list.list.items.len);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ pub fn main() !void {
|
|||||||
break :config result;
|
break :config result;
|
||||||
};
|
};
|
||||||
defer config.deinit();
|
defer config.deinit();
|
||||||
|
log.info("config={}", .{config});
|
||||||
|
|
||||||
// We want to log all our errors
|
// We want to log all our errors
|
||||||
glfw.setErrorCallback(glfwErrorCallback);
|
glfw.setErrorCallback(glfwErrorCallback);
|
||||||
|
Reference in New Issue
Block a user