mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
339 lines
9.2 KiB
Zig
339 lines
9.2 KiB
Zig
const formatter = @This();
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const help_strings = @import("help_strings");
|
|
const Config = @import("Config.zig");
|
|
const Key = @import("key.zig").Key;
|
|
|
|
/// 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 => {},
|
|
},
|
|
|
|
.Union => if (@hasDecl(T, "formatEntry")) {
|
|
try value.formatEntry(entryFormatter(name, writer));
|
|
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
|
|
/// config in a file-like format. This uses more generous whitespace,
|
|
/// can include comments, etc.
|
|
pub const FileFormatter = struct {
|
|
alloc: Allocator,
|
|
config: *const Config,
|
|
|
|
/// Include comments for documentation of each key
|
|
docs: bool = false,
|
|
|
|
/// Only include changed values from the default.
|
|
changed: bool = false,
|
|
|
|
/// Implements std.fmt so it can be used directly with std.fmt.
|
|
pub fn format(
|
|
self: FileFormatter,
|
|
comptime layout: []const u8,
|
|
opts: std.fmt.FormatOptions,
|
|
writer: anytype,
|
|
) !void {
|
|
_ = layout;
|
|
_ = opts;
|
|
|
|
// If we're change-tracking then we need the default config to
|
|
// compare against.
|
|
var default: ?Config = if (self.changed)
|
|
try Config.default(self.alloc)
|
|
else
|
|
null;
|
|
defer if (default) |*v| v.deinit();
|
|
|
|
inline for (@typeInfo(Config).Struct.fields) |field| {
|
|
if (field.name[0] == '_') continue;
|
|
|
|
const value = @field(self.config, field.name);
|
|
const do_format = if (default) |d| format: {
|
|
const key = @field(Key, field.name);
|
|
break :format d.changed(self.config, key);
|
|
} else true;
|
|
|
|
if (do_format) {
|
|
const do_docs = self.docs and @hasDecl(help_strings.Config, field.name);
|
|
if (do_docs) {
|
|
const help = @field(help_strings.Config, field.name);
|
|
var lines = std.mem.splitScalar(u8, help, '\n');
|
|
while (lines.next()) |line| {
|
|
try writer.print("# {s}\n", .{line});
|
|
}
|
|
}
|
|
|
|
try formatEntry(
|
|
field.type,
|
|
field.name,
|
|
value,
|
|
writer,
|
|
);
|
|
|
|
if (do_docs) try writer.print("\n", .{});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
test "format default config" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
var cfg = try Config.default(alloc);
|
|
defer cfg.deinit();
|
|
|
|
var buf = std.ArrayList(u8).init(alloc);
|
|
defer buf.deinit();
|
|
|
|
// We just make sure this works without errors. We aren't asserting output.
|
|
const fmt: FileFormatter = .{
|
|
.alloc = alloc,
|
|
.config = &cfg,
|
|
};
|
|
try std.fmt.format(buf.writer(), "{}", .{fmt});
|
|
|
|
//std.log.warn("{s}", .{buf.items});
|
|
}
|
|
|
|
test "format default config changed" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
var cfg = try Config.default(alloc);
|
|
defer cfg.deinit();
|
|
cfg.@"font-size" = 42;
|
|
|
|
var buf = std.ArrayList(u8).init(alloc);
|
|
defer buf.deinit();
|
|
|
|
// We just make sure this works without errors. We aren't asserting output.
|
|
const fmt: FileFormatter = .{
|
|
.alloc = alloc,
|
|
.config = &cfg,
|
|
.changed = true,
|
|
};
|
|
try std.fmt.format(buf.writer(), "{}", .{fmt});
|
|
|
|
//std.log.warn("{s}", .{buf.items});
|
|
}
|
|
|
|
test "formatEntry bool" {
|
|
const testing = std.testing;
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry(bool, "a", true, buf.writer());
|
|
try testing.expectEqualStrings("a = true\n", buf.items);
|
|
}
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry(bool, "a", false, buf.writer());
|
|
try testing.expectEqualStrings("a = false\n", buf.items);
|
|
}
|
|
}
|
|
|
|
test "formatEntry int" {
|
|
const testing = std.testing;
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry(u8, "a", 123, buf.writer());
|
|
try testing.expectEqualStrings("a = 123\n", buf.items);
|
|
}
|
|
}
|
|
|
|
test "formatEntry float" {
|
|
const testing = std.testing;
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry(f64, "a", 0.7, buf.writer());
|
|
try testing.expectEqualStrings("a = 0.7\n", buf.items);
|
|
}
|
|
}
|
|
|
|
test "formatEntry enum" {
|
|
const testing = std.testing;
|
|
const Enum = enum { one, two, three };
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry(Enum, "a", .two, buf.writer());
|
|
try testing.expectEqualStrings("a = two\n", buf.items);
|
|
}
|
|
}
|
|
|
|
test "formatEntry void" {
|
|
const testing = std.testing;
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry(void, "a", {}, buf.writer());
|
|
try testing.expectEqualStrings("a = \n", buf.items);
|
|
}
|
|
}
|
|
|
|
test "formatEntry optional" {
|
|
const testing = std.testing;
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry(?bool, "a", null, buf.writer());
|
|
try testing.expectEqualStrings("a = \n", buf.items);
|
|
}
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry(?bool, "a", false, buf.writer());
|
|
try testing.expectEqualStrings("a = false\n", buf.items);
|
|
}
|
|
}
|
|
|
|
test "formatEntry string" {
|
|
const testing = std.testing;
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry([]const u8, "a", "hello", buf.writer());
|
|
try testing.expectEqualStrings("a = hello\n", buf.items);
|
|
}
|
|
}
|
|
|
|
test "formatEntry packed struct" {
|
|
const testing = std.testing;
|
|
const Value = packed struct {
|
|
one: bool = true,
|
|
two: bool = false,
|
|
};
|
|
|
|
{
|
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
defer buf.deinit();
|
|
try formatEntry(Value, "a", .{}, buf.writer());
|
|
try testing.expectEqualStrings("a = one,no-two\n", buf.items);
|
|
}
|
|
}
|