Skip to content

Commit

Permalink
cli: allow renaming config fields to maintain backwards compatibility
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mitchellh committed Jan 23, 2025
1 parent 4a3b4ea commit a3bae55
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 2 deletions.
52 changes: 52 additions & 0 deletions src/cli/args.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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.
Expand Down
13 changes: 11 additions & 2 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ const c = @cImport({
@cInclude("unistd.h");
});

/// Renamed fields, used by cli.parse
pub const renamed = std.StaticStringMap([]const u8).initComptime(&.{
// Ghostty 1.1 introduced background-blur support for Linux which
// doesn't support a specific radius value. The renaming is to let
// one field be used for both platforms (macOS retained the ability
// to set a radius).
.{ "background-blur-radius", "background-blur" },
});

/// The font families to use.
///
/// You can generate the list of valid values using the CLI:
Expand Down Expand Up @@ -649,7 +658,7 @@ palette: Palette = .{},
/// need to set environment-specific settings and/or install third-party plugins
/// in order to support background blur, as there isn't a unified interface for
/// doing so.
@"background-blur-radius": BackgroundBlur = .false,
@"background-blur": BackgroundBlur = .false,

/// The opacity level (opposite of transparency) of an unfocused split.
/// Unfocused splits by default are slightly faded out to make it easier to see
Expand Down Expand Up @@ -5854,7 +5863,7 @@ pub const AutoUpdate = enum {
download,
};

/// See background-blur-radius
/// See background-blur
pub const BackgroundBlur = union(enum) {
false,
true,
Expand Down

0 comments on commit a3bae55

Please sign in to comment.