mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46: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 Allocator = mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
|
const ErrorList = @import("config/ErrorList.zig");
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Only `--long=value` format is accepted. Do we want to allow
|
// - Only `--long=value` format is accepted. Do we want to allow
|
||||||
// `--long value`? Not currently allowed.
|
// `--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.
|
/// Parse the command line arguments from iter into dst.
|
||||||
///
|
///
|
||||||
/// dst must be a struct. The fields and their types will be used to determine
|
/// 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
|
/// an arena allocator will be created (or reused if set already) for any
|
||||||
/// allocations. Allocations are necessary for certain types, like `[]const u8`.
|
/// 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
|
/// 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.
|
/// 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 {
|
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;
|
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.
|
/// Parse a single key/value pair into the destination type T.
|
||||||
///
|
///
|
||||||
/// This may result in allocations. The allocations can only be freed by freeing
|
/// 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);
|
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" {
|
test "parseIntoField: ignore underscore-prefixed fields" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
@ -14,6 +14,7 @@ const cli_args = @import("../cli_args.zig");
|
|||||||
|
|
||||||
const Key = @import("key.zig").Key;
|
const Key = @import("key.zig").Key;
|
||||||
const KeyValue = @import("key.zig").Value;
|
const KeyValue = @import("key.zig").Value;
|
||||||
|
const ErrorList = @import("ErrorList.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.config);
|
const log = std.log.scoped(.config);
|
||||||
|
|
||||||
@ -341,6 +342,11 @@ keybind: Keybinds = .{},
|
|||||||
/// This is set by the CLI parser for deinit.
|
/// This is set by the CLI parser for deinit.
|
||||||
_arena: ?ArenaAllocator = null,
|
_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 {
|
pub fn deinit(self: *Config) void {
|
||||||
if (self._arena) |arena| arena.deinit();
|
if (self._arena) |arena| arena.deinit();
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
|
@ -393,7 +393,7 @@ pub const Set = struct {
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
t: Trigger,
|
t: Trigger,
|
||||||
action: Action,
|
action: Action,
|
||||||
) !void {
|
) Allocator.Error!void {
|
||||||
// unbind should never go into the set, it should be handled prior
|
// unbind should never go into the set, it should be handled prior
|
||||||
assert(action != .unbind);
|
assert(action != .unbind);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user