config: move file formatter to dedicated file to prevent import bloat

This commit is contained in:
Mitchell Hashimoto
2026-03-23 09:11:18 -07:00
parent 04b5dc7332
commit 7253668ec2
4 changed files with 113 additions and 104 deletions

View File

@@ -2,6 +2,7 @@ const builtin = @import("builtin");
const file_load = @import("config/file_load.zig");
const formatter = @import("config/formatter.zig");
const formatter_file = @import("config/formatter_file.zig");
pub const Config = @import("config/Config.zig");
pub const conditional = @import("config/conditional.zig");
pub const io = @import("config/io.zig");
@@ -10,7 +11,7 @@ pub const edit = @import("config/edit.zig");
pub const url = @import("config/url.zig");
pub const ConditionalState = conditional.State;
pub const FileFormatter = formatter.FileFormatter;
pub const FileFormatter = formatter_file.FileFormatter;
pub const entryFormatter = formatter.entryFormatter;
pub const formatEntry = formatter.formatEntry;
pub const preferredDefaultFilePath = file_load.preferredDefaultFilePath;

View File

@@ -1,8 +1,7 @@
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.
@@ -125,106 +124,6 @@ pub fn formatEntry(
@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,
writer: *std.Io.Writer,
) std.Io.Writer.Error!void {
@setEvalBranchQuota(10_000);
// If we're change-tracking then we need the default config to
// compare against.
var default: ?Config = if (self.changed)
Config.default(self.alloc) catch return error.WriteFailed
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});
}
}
formatEntry(
field.type,
field.name,
value,
writer,
) catch return error.WriteFailed;
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.Io.Writer.Allocating = .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 fmt.format(&buf.writer);
//std.log.warn("{s}", .{buf.written()});
}
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.Io.Writer.Allocating = .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 fmt.format(&buf.writer);
//std.log.warn("{s}", .{buf.written()});
}
test "formatEntry bool" {
const testing = std.testing;

View File

@@ -0,0 +1,110 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Config = @import("Config.zig");
const Key = @import("key.zig").Key;
const help_strings = @import("help_strings");
const formatter = @import("formatter.zig");
// IMPORTANT: This is in a seperate file from formatter.zig because it
// puts a build-time dependency on Config.zig which brings in too much
// into libghostty-vt tests which reference some formattable types.
/// 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,
writer: *std.Io.Writer,
) std.Io.Writer.Error!void {
@setEvalBranchQuota(10_000);
// If we're change-tracking then we need the default config to
// compare against.
var default: ?Config = if (self.changed)
Config.default(self.alloc) catch return error.WriteFailed
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});
}
}
formatter.formatEntry(
field.type,
field.name,
value,
writer,
) catch return error.WriteFailed;
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.Io.Writer.Allocating = .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 fmt.format(&buf.writer);
//std.log.warn("{s}", .{buf.written()});
}
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.Io.Writer.Allocating = .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 fmt.format(&buf.writer);
//std.log.warn("{s}", .{buf.written()});
}

View File

@@ -2,7 +2,6 @@ const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const cimgui = @import("dcimgui");
const OptionAsAlt = @import("config.zig").OptionAsAlt;
pub const Mods = @import("key_mods.zig").Mods;