mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
config: store some basic errors on parse
This commit is contained in:
@ -4,10 +4,20 @@ const assert = std.debug.assert;
|
||||
const Allocator = mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
|
||||
const ErrorList = @import("config/ErrorList.zig");
|
||||
|
||||
// TODO:
|
||||
// - Only `--long=value` format is accepted. Do we want to allow
|
||||
// `--long value`? Not currently allowed.
|
||||
|
||||
/// 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{
|
||||
ValueRequired,
|
||||
InvalidField,
|
||||
InvalidValue,
|
||||
};
|
||||
|
||||
/// Parse the command line arguments from iter into dst.
|
||||
///
|
||||
/// dst must be a struct. The fields and their types will be used to determine
|
||||
@ -19,6 +29,10 @@ const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
/// an arena allocator will be created (or reused if set already) for any
|
||||
/// allocations. Allocations are necessary for certain types, like `[]const u8`.
|
||||
///
|
||||
/// If the destination type has a field "_errors" of type "ErrorList" then
|
||||
/// errors will be added to that list. In this case, the only error returned by
|
||||
/// parse are allocation errors.
|
||||
///
|
||||
/// Note: If the arena is already non-null, then it will be used. In this
|
||||
/// case, in the case of an error some memory might be leaked into the arena.
|
||||
pub fn parse(comptime T: type, alloc: Allocator, dst: *T, iter: anytype) !void {
|
||||
@ -62,11 +76,45 @@ pub fn parse(comptime T: type, alloc: Allocator, dst: *T, iter: anytype) !void {
|
||||
break :value null;
|
||||
};
|
||||
|
||||
try parseIntoField(T, arena_alloc, dst, key, value);
|
||||
parseIntoField(T, arena_alloc, dst, key, value) catch |err| {
|
||||
if (comptime !canTrackErrors(T)) return err;
|
||||
switch (err) {
|
||||
error.InvalidField => try dst._errors.add(arena_alloc, .{
|
||||
.message = try std.fmt.allocPrintZ(
|
||||
arena_alloc,
|
||||
"unknown field: {s}",
|
||||
.{key},
|
||||
),
|
||||
}),
|
||||
|
||||
error.ValueRequired => try dst._errors.add(arena_alloc, .{
|
||||
.message = try std.fmt.allocPrintZ(
|
||||
arena_alloc,
|
||||
"{s}: value required",
|
||||
.{key},
|
||||
),
|
||||
}),
|
||||
|
||||
error.InvalidValue => try dst._errors.add(arena_alloc, .{
|
||||
.message = try std.fmt.allocPrintZ(
|
||||
arena_alloc,
|
||||
"{s}: invalid value",
|
||||
.{key},
|
||||
),
|
||||
}),
|
||||
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this type can track errors.
|
||||
fn canTrackErrors(comptime T: type) bool {
|
||||
return @hasField(T, "_errors");
|
||||
}
|
||||
|
||||
/// Parse a single key/value pair into the destination type T.
|
||||
///
|
||||
/// This may result in allocations. The allocations can only be freed by freeing
|
||||
@ -240,6 +288,29 @@ test "parse: quoted value" {
|
||||
try testing.expectEqualStrings("hello!", data.b);
|
||||
}
|
||||
|
||||
test "parse: error tracking" {
|
||||
const testing = std.testing;
|
||||
|
||||
var data: struct {
|
||||
a: []const u8 = "",
|
||||
b: enum { one } = .one,
|
||||
|
||||
_arena: ?ArenaAllocator = null,
|
||||
_errors: ErrorList = .{},
|
||||
} = .{};
|
||||
defer if (data._arena) |arena| arena.deinit();
|
||||
|
||||
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
||||
testing.allocator,
|
||||
"--what --a=42",
|
||||
);
|
||||
defer iter.deinit();
|
||||
try parse(@TypeOf(data), testing.allocator, &data, &iter);
|
||||
try testing.expect(data._arena != null);
|
||||
try testing.expectEqualStrings("42", data.a);
|
||||
try testing.expect(!data._errors.empty());
|
||||
}
|
||||
|
||||
test "parseIntoField: ignore underscore-prefixed fields" {
|
||||
const testing = std.testing;
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
|
@ -14,6 +14,7 @@ const cli_args = @import("../cli_args.zig");
|
||||
|
||||
const Key = @import("key.zig").Key;
|
||||
const KeyValue = @import("key.zig").Value;
|
||||
const ErrorList = @import("ErrorList.zig");
|
||||
|
||||
const log = std.log.scoped(.config);
|
||||
|
||||
@ -341,6 +342,11 @@ keybind: Keybinds = .{},
|
||||
/// This is set by the CLI parser for deinit.
|
||||
_arena: ?ArenaAllocator = null,
|
||||
|
||||
/// List of errors that occurred while loading. This can be accessed directly
|
||||
/// by callers. It is only underscore-prefixed so it can't be set by the
|
||||
/// configuration file.
|
||||
_errors: ErrorList = .{},
|
||||
|
||||
pub fn deinit(self: *Config) void {
|
||||
if (self._arena) |arena| arena.deinit();
|
||||
self.* = undefined;
|
||||
|
@ -393,7 +393,7 @@ pub const Set = struct {
|
||||
alloc: Allocator,
|
||||
t: Trigger,
|
||||
action: Action,
|
||||
) !void {
|
||||
) Allocator.Error!void {
|
||||
// unbind should never go into the set, it should be handled prior
|
||||
assert(action != .unbind);
|
||||
|
||||
|
Reference in New Issue
Block a user