ghostty/src/config.zig
Mitchell Hashimoto 782ddfe722 --config-file to load a config file
The config file is just CLI args one per line.
2022-08-01 18:04:39 -07:00

117 lines
3.6 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
/// Config is the main config struct. These fields map directly to the
/// CLI flag names hence we use a lot of `@""` syntax to support hyphens.
pub const Config = struct {
/// Font size
@"font-size": u8 = 14,
/// Background color for the window.
background: Color = .{ .r = 0, .g = 0, .b = 0 },
/// Foreground color for the window.
foreground: Color = .{ .r = 0xFF, .g = 0xA5, .b = 0 },
/// The command to run, usually a shell. If this is not an absolute path,
/// it'll be looked up in the PATH.
command: ?[]const u8 = null,
/// Additional configuration files to read.
@"config-file": RepeatableString = .{},
/// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null,
pub fn deinit(self: *Config) void {
if (self._arena) |arena| arena.deinit();
self.* = undefined;
}
};
/// Color represents a color using RGB.
pub const Color = struct {
r: u8,
g: u8,
b: u8,
pub const Error = error{
InvalidFormat,
};
pub fn parseCLI(input: ?[]const u8) !Color {
return fromHex(input orelse return error.ValueRequired);
}
/// fromHex parses a color from a hex value such as #RRGGBB. The "#"
/// is optional.
pub fn fromHex(input: []const u8) !Color {
// Trim the beginning '#' if it exists
const trimmed = if (input.len != 0 and input[0] == '#') input[1..] else input;
// We expect exactly 6 for RRGGBB
if (trimmed.len != 6) return Error.InvalidFormat;
// Parse the colors two at a time.
var result: Color = undefined;
comptime var i: usize = 0;
inline while (i < 6) : (i += 2) {
const v: u8 =
((try std.fmt.charToDigit(trimmed[i], 16)) * 16) +
try std.fmt.charToDigit(trimmed[i + 1], 16);
@field(result, switch (i) {
0 => "r",
2 => "g",
4 => "b",
else => unreachable,
}) = v;
}
return result;
}
test "fromHex" {
const testing = std.testing;
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 = 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());
}