mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 00:36:07 +03:00
config: tests for all custom formatEntry calls
This commit is contained in:
@ -15,6 +15,7 @@ const internal_os = @import("../os/main.zig");
|
|||||||
const cli = @import("../cli.zig");
|
const cli = @import("../cli.zig");
|
||||||
const Command = @import("../Command.zig");
|
const Command = @import("../Command.zig");
|
||||||
|
|
||||||
|
const formatterpkg = @import("formatter.zig");
|
||||||
const url = @import("url.zig");
|
const url = @import("url.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;
|
||||||
@ -2231,6 +2232,16 @@ pub const Color = packed struct(u24) {
|
|||||||
test "parseCLI from name" {
|
test "parseCLI from name" {
|
||||||
try std.testing.expectEqual(Color{ .r = 0, .g = 0, .b = 0 }, try Color.parseCLI("black"));
|
try std.testing.expectEqual(Color{ .r = 0, .g = 0, .b = 0 }, try Color.parseCLI("black"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatConfig" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var color: Color = .{ .r = 10, .g = 11, .b = 12 };
|
||||||
|
try color.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = #0a0b0c\n", buf.items);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Palette is the 256 color palette for 256-color mode. This is still
|
/// Palette is the 256 color palette for 256-color mode. This is still
|
||||||
@ -2295,6 +2306,16 @@ pub const Palette = struct {
|
|||||||
var p: Self = .{};
|
var p: Self = .{};
|
||||||
try testing.expectError(error.Overflow, p.parseCLI("256=#AABBCC"));
|
try testing.expectError(error.Overflow, p.parseCLI("256=#AABBCC"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatConfig" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var list: Self = .{};
|
||||||
|
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = 0=#1d1f21\n", buf.items[0..14]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// RepeatableString is a string value that can be repeated to accumulate
|
/// RepeatableString is a string value that can be repeated to accumulate
|
||||||
@ -2369,6 +2390,47 @@ pub const RepeatableString = struct {
|
|||||||
try list.parseCLI(alloc, "");
|
try list.parseCLI(alloc, "");
|
||||||
try testing.expectEqual(@as(usize, 0), list.list.items.len);
|
try testing.expectEqual(@as(usize, 0), list.list.items.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatConfig empty" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var list: Self = .{};
|
||||||
|
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = \n", buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "formatConfig single item" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var list: Self = .{};
|
||||||
|
try list.parseCLI(alloc, "A");
|
||||||
|
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = A\n", buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "formatConfig multiple items" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
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 list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = A\na = B\n", buf.items);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// RepeatablePath is like repeatable string but represents a path value.
|
/// RepeatablePath is like repeatable string but represents a path value.
|
||||||
@ -2549,6 +2611,21 @@ pub const RepeatableFontVariation = struct {
|
|||||||
.value = -15,
|
.value = -15,
|
||||||
}, list.list.items[1]);
|
}, list.list.items[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatConfig single" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var list: Self = .{};
|
||||||
|
try list.parseCLI(alloc, "wght = 200");
|
||||||
|
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = wght=200\n", buf.items);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Stores a set of keybinds.
|
/// Stores a set of keybinds.
|
||||||
@ -2660,6 +2737,21 @@ pub const Keybinds = struct {
|
|||||||
try set.parseCLI(alloc, "shift+a=copy_to_clipboard");
|
try set.parseCLI(alloc, "shift+a=copy_to_clipboard");
|
||||||
try set.parseCLI(alloc, "shift+a=csi:hello");
|
try set.parseCLI(alloc, "shift+a=csi:hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatConfig single" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var list: Keybinds = .{};
|
||||||
|
try list.parseCLI(alloc, "shift+a=csi:hello");
|
||||||
|
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = shift+a=csi:hello\n", buf.items);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// See "font-codepoint-map" for documentation.
|
/// See "font-codepoint-map" for documentation.
|
||||||
@ -2738,7 +2830,7 @@ pub const RepeatableCodepointMap = struct {
|
|||||||
[]const u8,
|
[]const u8,
|
||||||
std.fmt.bufPrint(
|
std.fmt.bufPrint(
|
||||||
&buf,
|
&buf,
|
||||||
"U+{X:0>4}-U{X:0>4}={s}",
|
"U+{X:0>4}-U+{X:0>4}={s}",
|
||||||
.{
|
.{
|
||||||
range[0],
|
range[0],
|
||||||
range[1],
|
range[1],
|
||||||
@ -2883,6 +2975,55 @@ pub const RepeatableCodepointMap = struct {
|
|||||||
try testing.expectEqualStrings("Courier", entry.descriptor.family.?);
|
try testing.expectEqualStrings("Courier", entry.descriptor.family.?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatConfig single" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var list: Self = .{};
|
||||||
|
try list.parseCLI(alloc, "U+ABCD=Comic Sans");
|
||||||
|
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = U+ABCD=Comic Sans\n", buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "formatConfig range" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var list: Self = .{};
|
||||||
|
try list.parseCLI(alloc, "U+0001 - U+0005=Verdana");
|
||||||
|
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = U+0001-U+0005=Verdana\n", buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "formatConfig multiple" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var list: Self = .{};
|
||||||
|
try list.parseCLI(alloc, "U+0006-U+0009, U+ABCD=Courier");
|
||||||
|
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8,
|
||||||
|
\\a = U+0006-U+0009=Courier
|
||||||
|
\\a = U+ABCD=Courier
|
||||||
|
\\
|
||||||
|
, buf.items);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FontStyle = union(enum) {
|
pub const FontStyle = union(enum) {
|
||||||
|
@ -1,6 +1,127 @@
|
|||||||
|
const formatter = @This();
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Config = @import("Config.zig");
|
const Config = @import("Config.zig");
|
||||||
|
|
||||||
|
/// Returns a single entry formatter for the given field name and writer.
|
||||||
|
pub fn entryFormatter(
|
||||||
|
name: []const u8,
|
||||||
|
writer: anytype,
|
||||||
|
) EntryFormatter(@TypeOf(writer)) {
|
||||||
|
return .{ .name = name, .writer = writer };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The entry formatter type for a given writer.
|
||||||
|
pub fn EntryFormatter(comptime WriterType: type) type {
|
||||||
|
return struct {
|
||||||
|
name: []const u8,
|
||||||
|
writer: WriterType,
|
||||||
|
|
||||||
|
pub fn formatEntry(
|
||||||
|
self: @This(),
|
||||||
|
comptime T: type,
|
||||||
|
value: T,
|
||||||
|
) !void {
|
||||||
|
return formatter.formatEntry(
|
||||||
|
T,
|
||||||
|
self.name,
|
||||||
|
value,
|
||||||
|
self.writer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a single type with the given name and value.
|
||||||
|
pub fn formatEntry(
|
||||||
|
comptime T: type,
|
||||||
|
name: []const u8,
|
||||||
|
value: T,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Bool, .Int => {
|
||||||
|
try writer.print("{s} = {}\n", .{ name, value });
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Float => {
|
||||||
|
try writer.print("{s} = {d}\n", .{ name, value });
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Enum => {
|
||||||
|
try writer.print("{s} = {s}\n", .{ name, @tagName(value) });
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Void => {
|
||||||
|
try writer.print("{s} = \n", .{name});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Optional => |info| {
|
||||||
|
if (value) |inner| {
|
||||||
|
try formatEntry(
|
||||||
|
info.child,
|
||||||
|
name,
|
||||||
|
inner,
|
||||||
|
writer,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try writer.print("{s} = \n", .{name});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Pointer => switch (T) {
|
||||||
|
[]const u8,
|
||||||
|
[:0]const u8,
|
||||||
|
=> {
|
||||||
|
try writer.print("{s} = {s}\n", .{ name, value });
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Structs of all types require a "formatEntry" function
|
||||||
|
// to be defined which will be called to format the value.
|
||||||
|
// This is given the formatter in use so that they can
|
||||||
|
// call BACK to our formatEntry to write each primitive
|
||||||
|
// value.
|
||||||
|
.Struct => |info| if (@hasDecl(T, "formatEntry")) {
|
||||||
|
try value.formatEntry(entryFormatter(name, writer));
|
||||||
|
return;
|
||||||
|
} else switch (info.layout) {
|
||||||
|
// Packed structs we special case.
|
||||||
|
.Packed => {
|
||||||
|
try writer.print("{s} = ", .{name});
|
||||||
|
inline for (info.fields, 0..) |field, i| {
|
||||||
|
if (i > 0) try writer.print(",", .{});
|
||||||
|
try writer.print("{s}{s}", .{
|
||||||
|
if (!@field(value, field.name)) "no-" else "",
|
||||||
|
field.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try writer.print("\n", .{});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
.Union => return,
|
||||||
|
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile error so that we can catch missing cases.
|
||||||
|
@compileLog(T);
|
||||||
|
@compileError("missing case for type");
|
||||||
|
}
|
||||||
|
|
||||||
/// FileFormatter is a formatter implementation that outputs the
|
/// FileFormatter is a formatter implementation that outputs the
|
||||||
/// config in a file-like format. This uses more generous whitespace,
|
/// config in a file-like format. This uses more generous whitespace,
|
||||||
/// can include comments, etc.
|
/// can include comments, etc.
|
||||||
@ -19,7 +140,7 @@ pub const FileFormatter = struct {
|
|||||||
|
|
||||||
inline for (@typeInfo(Config).Struct.fields) |field| {
|
inline for (@typeInfo(Config).Struct.fields) |field| {
|
||||||
if (field.name[0] == '_') continue;
|
if (field.name[0] == '_') continue;
|
||||||
try self.formatEntry(
|
try formatEntry(
|
||||||
field.type,
|
field.type,
|
||||||
field.name,
|
field.name,
|
||||||
@field(self.config, field.name),
|
@field(self.config, field.name),
|
||||||
@ -27,120 +148,6 @@ pub const FileFormatter = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn formatEntry(
|
|
||||||
self: FileFormatter,
|
|
||||||
comptime T: type,
|
|
||||||
name: []const u8,
|
|
||||||
value: T,
|
|
||||||
writer: anytype,
|
|
||||||
) !void {
|
|
||||||
const EntryFormatter = struct {
|
|
||||||
parent: *const FileFormatter,
|
|
||||||
name: []const u8,
|
|
||||||
writer: @TypeOf(writer),
|
|
||||||
|
|
||||||
pub fn formatEntry(
|
|
||||||
self_entry: @This(),
|
|
||||||
comptime EntryT: type,
|
|
||||||
value_entry: EntryT,
|
|
||||||
) !void {
|
|
||||||
return self_entry.parent.formatEntry(
|
|
||||||
EntryT,
|
|
||||||
self_entry.name,
|
|
||||||
value_entry,
|
|
||||||
self_entry.writer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (@typeInfo(T)) {
|
|
||||||
.Bool, .Int => {
|
|
||||||
try writer.print("{s} = {}\n", .{ name, value });
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
.Float => {
|
|
||||||
try writer.print("{s} = {d}\n", .{ name, value });
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
.Enum => {
|
|
||||||
try writer.print("{s} = {s}\n", .{ name, @tagName(value) });
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
.Void => {
|
|
||||||
try writer.print("{s} = \n", .{name});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
.Optional => |info| {
|
|
||||||
if (value) |inner| {
|
|
||||||
try self.formatEntry(
|
|
||||||
info.child,
|
|
||||||
name,
|
|
||||||
inner,
|
|
||||||
writer,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
try writer.print("{s} = \n", .{name});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
.Pointer => switch (T) {
|
|
||||||
[]const u8,
|
|
||||||
[:0]const u8,
|
|
||||||
=> {
|
|
||||||
try writer.print("{s} = {s}\n", .{ name, value });
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Structs of all types require a "formatEntry" function
|
|
||||||
// to be defined which will be called to format the value.
|
|
||||||
// This is given the formatter in use so that they can
|
|
||||||
// call BACK to our formatEntry to write each primitive
|
|
||||||
// value.
|
|
||||||
.Struct => |info| if (@hasDecl(T, "formatEntry")) {
|
|
||||||
try value.formatEntry(EntryFormatter{
|
|
||||||
.parent = &self,
|
|
||||||
.name = name,
|
|
||||||
.writer = writer,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} else switch (info.layout) {
|
|
||||||
// Packed structs we special case.
|
|
||||||
.Packed => {
|
|
||||||
try writer.print("{s} = ", .{name});
|
|
||||||
inline for (info.fields, 0..) |field, i| {
|
|
||||||
if (i > 0) try writer.print(",", .{});
|
|
||||||
try writer.print("{s}{s}", .{
|
|
||||||
if (!@field(value, field.name)) "no-" else "",
|
|
||||||
field.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
try writer.print("\n", .{});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {},
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
.Union => return,
|
|
||||||
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile error so that we can catch missing cases.
|
|
||||||
@compileLog(T);
|
|
||||||
@compileError("missing case for type");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
test "format default config" {
|
test "format default config" {
|
||||||
@ -155,5 +162,5 @@ test "format default config" {
|
|||||||
const fmt: FileFormatter = .{ .config = &cfg };
|
const fmt: FileFormatter = .{ .config = &cfg };
|
||||||
try std.fmt.format(buf.writer(), "{}", .{fmt});
|
try std.fmt.format(buf.writer(), "{}", .{fmt});
|
||||||
|
|
||||||
std.log.warn("{s}", .{buf.items});
|
//std.log.warn("{s}", .{buf.items});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user