mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-19 22:10:29 +00:00
cli: allow renaming config fields to maintain backwards compatibility
Fixes #4631 This introduces a mechanism by which parsed config fields can be renamed to maintain backwards compatibility. This already has a use case -- implemented in this commit -- for `background-blur-radius` to be renamed to `background-blur`. The remapping is comptime-known which lets us do some comptime validation. The remap check isn't done unless no fields match which means for well-formed config files, there's no overhead. For future improvements: - We should update our config help generator to note renamed fields. - We could offer automatic migration of config files be rewriting them. - We can enrich the value type with more metadata to help with config gen or other tooling.
This commit is contained in:
@@ -38,6 +38,12 @@ pub const Error = error{
|
||||
/// "DiagnosticList" and any diagnostic messages will be added to that list.
|
||||
/// When diagnostics are present, only allocation errors will be returned.
|
||||
///
|
||||
/// If the destination type has a decl "renamed", it must be of type
|
||||
/// std.StaticStringMap([]const u8) and contains a mapping from the old
|
||||
/// field name to the new field name. This is used to allow renaming fields
|
||||
/// while still supporting the old name. If a renamed field is set, parsing
|
||||
/// will automatically set the new field name.
|
||||
///
|
||||
/// 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.
|
||||
pub fn parse(
|
||||
@@ -49,6 +55,24 @@ pub fn parse(
|
||||
const info = @typeInfo(T);
|
||||
assert(info == .Struct);
|
||||
|
||||
comptime {
|
||||
// Verify all renamed fields are valid (source does not exist,
|
||||
// destination does exist).
|
||||
if (@hasDecl(T, "renamed")) {
|
||||
for (T.renamed.keys(), T.renamed.values()) |key, value| {
|
||||
if (@hasField(T, key)) {
|
||||
@compileLog(key);
|
||||
@compileError("renamed field source exists");
|
||||
}
|
||||
|
||||
if (!@hasField(T, value)) {
|
||||
@compileLog(value);
|
||||
@compileError("renamed field destination does not exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make an arena for all our allocations if we support it. Otherwise,
|
||||
// use an allocator that always fails. If the arena is already set on
|
||||
// the config, then we reuse that. See memory note in parse docs.
|
||||
@@ -367,6 +391,16 @@ pub fn parseIntoField(
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown field, is the field renamed?
|
||||
if (@hasDecl(T, "renamed")) {
|
||||
for (T.renamed.keys(), T.renamed.values()) |old, new| {
|
||||
if (mem.eql(u8, old, key)) {
|
||||
try parseIntoField(T, alloc, dst, new, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error.InvalidField;
|
||||
}
|
||||
|
||||
@@ -1104,6 +1138,24 @@ test "parseIntoField: tagged union missing tag" {
|
||||
);
|
||||
}
|
||||
|
||||
test "parseIntoField: renamed field" {
|
||||
const testing = std.testing;
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var data: struct {
|
||||
a: []const u8,
|
||||
|
||||
const renamed = std.StaticStringMap([]const u8).initComptime(&.{
|
||||
.{ "old", "a" },
|
||||
});
|
||||
} = undefined;
|
||||
|
||||
try parseIntoField(@TypeOf(data), alloc, &data, "old", "42");
|
||||
try testing.expectEqualStrings("42", data.a);
|
||||
}
|
||||
|
||||
/// An iterator that considers its location to be CLI args. It
|
||||
/// iterates through an underlying iterator and increments a counter
|
||||
/// to track the current CLI arg index.
|
||||
|
||||
Reference in New Issue
Block a user