mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #1341 from mitchellh/format-config
config: support encoding back to string
This commit is contained in:
@ -170,6 +170,22 @@ fn parseIntoField(
|
|||||||
|
|
||||||
inline for (info.Struct.fields) |field| {
|
inline for (info.Struct.fields) |field| {
|
||||||
if (field.name[0] != '_' and mem.eql(u8, field.name, key)) {
|
if (field.name[0] != '_' and mem.eql(u8, field.name, key)) {
|
||||||
|
// If the field is optional then consider scenarios we reset
|
||||||
|
// the value to being unset. We allow unsetting optionals
|
||||||
|
// whenever the value is "".
|
||||||
|
//
|
||||||
|
// At the time of writing this, empty string isn't a desirable
|
||||||
|
// value for any optional field under any realistic scenario.
|
||||||
|
//
|
||||||
|
// We don't allow unset values to set optional fields to
|
||||||
|
// null because unset value for booleans always means true.
|
||||||
|
if (@typeInfo(field.type) == .Optional) optional: {
|
||||||
|
if (std.mem.eql(u8, "", value orelse break :optional)) {
|
||||||
|
@field(dst, field.name) = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For optional fields, we just treat it as the child type.
|
// For optional fields, we just treat it as the child type.
|
||||||
// This lets optional fields default to null but get set by
|
// This lets optional fields default to null but get set by
|
||||||
// the CLI.
|
// the CLI.
|
||||||
@ -617,6 +633,10 @@ test "parseIntoField: optional field" {
|
|||||||
// True
|
// True
|
||||||
try parseIntoField(@TypeOf(data), alloc, &data, "a", "1");
|
try parseIntoField(@TypeOf(data), alloc, &data, "a", "1");
|
||||||
try testing.expectEqual(true, data.a.?);
|
try testing.expectEqual(true, data.a.?);
|
||||||
|
|
||||||
|
// Unset
|
||||||
|
try parseIntoField(@TypeOf(data), alloc, &data, "a", "");
|
||||||
|
try testing.expect(data.a == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parseIntoField: struct with parse func" {
|
test "parseIntoField: struct with parse func" {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
pub usingnamespace @import("config/key.zig");
|
pub usingnamespace @import("config/key.zig");
|
||||||
|
pub usingnamespace @import("config/formatter.zig");
|
||||||
pub const Config = @import("config/Config.zig");
|
pub const Config = @import("config/Config.zig");
|
||||||
pub const string = @import("config/string.zig");
|
pub const string = @import("config/string.zig");
|
||||||
pub const edit = @import("config/edit.zig");
|
pub const edit = @import("config/edit.zig");
|
||||||
|
@ -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;
|
||||||
@ -2178,6 +2179,19 @@ pub const Color = packed struct(u24) {
|
|||||||
return std.meta.eql(self, other);
|
return std.meta.eql(self, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(self: Color, formatter: anytype) !void {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
try formatter.formatEntry(
|
||||||
|
[]const u8,
|
||||||
|
std.fmt.bufPrint(
|
||||||
|
&buf,
|
||||||
|
"#{x:0>2}{x:0>2}{x:0>2}",
|
||||||
|
.{ self.r, self.g, self.b },
|
||||||
|
) catch return error.OutOfMemory,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// fromHex parses a color from a hex value such as #RRGGBB. The "#"
|
/// fromHex parses a color from a hex value such as #RRGGBB. The "#"
|
||||||
/// is optional.
|
/// is optional.
|
||||||
pub fn fromHex(input: []const u8) !Color {
|
pub fn fromHex(input: []const u8) !Color {
|
||||||
@ -2218,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
|
||||||
@ -2251,6 +2275,21 @@ pub const Palette = struct {
|
|||||||
return std.meta.eql(self, other);
|
return std.meta.eql(self, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(self: Self, formatter: anytype) !void {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
for (0.., self.value) |k, v| {
|
||||||
|
try formatter.formatEntry(
|
||||||
|
[]const u8,
|
||||||
|
std.fmt.bufPrint(
|
||||||
|
&buf,
|
||||||
|
"{d}=#{x:0>2}{x:0>2}{x:0>2}",
|
||||||
|
.{ k, v.r, v.g, v.b },
|
||||||
|
) catch return error.OutOfMemory,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "parseCLI" {
|
test "parseCLI" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
@ -2267,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
|
||||||
@ -2314,6 +2363,19 @@ pub const RepeatableString = struct {
|
|||||||
} else return true;
|
} else return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(self: Self, formatter: anytype) !void {
|
||||||
|
// If no items, we want to render an empty field.
|
||||||
|
if (self.list.items.len == 0) {
|
||||||
|
try formatter.formatEntry(void, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.list.items) |value| {
|
||||||
|
try formatter.formatEntry([]const u8, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "parseCLI" {
|
test "parseCLI" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
@ -2328,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.
|
||||||
@ -2355,6 +2458,11 @@ pub const RepeatablePath = struct {
|
|||||||
return self.value.equal(other.value);
|
return self.value.equal(other.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(self: Self, formatter: anytype) !void {
|
||||||
|
try self.value.formatEntry(formatter);
|
||||||
|
}
|
||||||
|
|
||||||
/// Expand all the paths relative to the base directory.
|
/// Expand all the paths relative to the base directory.
|
||||||
pub fn expand(
|
pub fn expand(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
@ -2442,6 +2550,26 @@ pub const RepeatableFontVariation = struct {
|
|||||||
} else return true;
|
} else return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(
|
||||||
|
self: Self,
|
||||||
|
formatter: anytype,
|
||||||
|
) !void {
|
||||||
|
if (self.list.items.len == 0) {
|
||||||
|
try formatter.formatEntry(void, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
for (self.list.items) |value| {
|
||||||
|
const str = std.fmt.bufPrint(&buf, "{s}={d}", .{
|
||||||
|
value.id.str(),
|
||||||
|
value.value,
|
||||||
|
}) catch return error.OutOfMemory;
|
||||||
|
try formatter.formatEntry([]const u8, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "parseCLI" {
|
test "parseCLI" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
@ -2483,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.
|
||||||
@ -2561,6 +2704,29 @@ pub const Keybinds = struct {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(self: Keybinds, formatter: anytype) !void {
|
||||||
|
if (self.set.bindings.size == 0) {
|
||||||
|
try formatter.formatEntry(void, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var iter = self.set.bindings.iterator();
|
||||||
|
while (iter.next()) |next| {
|
||||||
|
const k = next.key_ptr.*;
|
||||||
|
const v = next.value_ptr.*;
|
||||||
|
try formatter.formatEntry(
|
||||||
|
[]const u8,
|
||||||
|
std.fmt.bufPrint(
|
||||||
|
&buf,
|
||||||
|
"{}={}",
|
||||||
|
.{ k, v },
|
||||||
|
) catch return error.OutOfMemory,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "parseCLI" {
|
test "parseCLI" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
@ -2571,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.
|
||||||
@ -2618,6 +2799,49 @@ pub const RepeatableCodepointMap = struct {
|
|||||||
} else return true;
|
} else return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(
|
||||||
|
self: Self,
|
||||||
|
formatter: anytype,
|
||||||
|
) !void {
|
||||||
|
if (self.map.list.len == 0) {
|
||||||
|
try formatter.formatEntry(void, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
const ranges = self.map.list.items(.range);
|
||||||
|
const descriptors = self.map.list.items(.descriptor);
|
||||||
|
for (ranges, descriptors) |range, descriptor| {
|
||||||
|
if (range[0] == range[1]) {
|
||||||
|
try formatter.formatEntry(
|
||||||
|
[]const u8,
|
||||||
|
std.fmt.bufPrint(
|
||||||
|
&buf,
|
||||||
|
"U+{X:0>4}={s}",
|
||||||
|
.{
|
||||||
|
range[0],
|
||||||
|
descriptor.family orelse "",
|
||||||
|
},
|
||||||
|
) catch return error.OutOfMemory,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try formatter.formatEntry(
|
||||||
|
[]const u8,
|
||||||
|
std.fmt.bufPrint(
|
||||||
|
&buf,
|
||||||
|
"U+{X:0>4}-U+{X:0>4}={s}",
|
||||||
|
.{
|
||||||
|
range[0],
|
||||||
|
range[1],
|
||||||
|
descriptor.family orelse "",
|
||||||
|
},
|
||||||
|
) catch return error.OutOfMemory,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses the list of Unicode codepoint ranges. Valid syntax:
|
/// Parses the list of Unicode codepoint ranges. Valid syntax:
|
||||||
///
|
///
|
||||||
/// "" (empty returns null)
|
/// "" (empty returns null)
|
||||||
@ -2751,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) {
|
||||||
@ -2792,6 +3065,20 @@ pub const FontStyle = union(enum) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(self: Self, formatter: anytype) !void {
|
||||||
|
switch (self) {
|
||||||
|
.default, .false => try formatter.formatEntry(
|
||||||
|
[]const u8,
|
||||||
|
@tagName(self),
|
||||||
|
),
|
||||||
|
|
||||||
|
.name => |name| {
|
||||||
|
try formatter.formatEntry([:0]const u8, name);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "parseCLI" {
|
test "parseCLI" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
@ -2808,6 +3095,51 @@ pub const FontStyle = union(enum) {
|
|||||||
try p.parseCLI(alloc, "bold");
|
try p.parseCLI(alloc, "bold");
|
||||||
try testing.expectEqualStrings("bold", p.name);
|
try testing.expectEqualStrings("bold", p.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatConfig default" {
|
||||||
|
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 p: Self = .{ .default = {} };
|
||||||
|
try p.parseCLI(alloc, "default");
|
||||||
|
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = default\n", buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "formatConfig false" {
|
||||||
|
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 p: Self = .{ .default = {} };
|
||||||
|
try p.parseCLI(alloc, "false");
|
||||||
|
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = false\n", buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "formatConfig named" {
|
||||||
|
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 p: Self = .{ .default = {} };
|
||||||
|
try p.parseCLI(alloc, "bold");
|
||||||
|
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = bold\n", buf.items);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// See "link" for documentation.
|
/// See "link" for documentation.
|
||||||
@ -2836,6 +3168,13 @@ pub const RepeatableLink = struct {
|
|||||||
_ = other;
|
_ = other;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(self: Self, formatter: anytype) !void {
|
||||||
|
// This currently can't be set so we don't format anything.
|
||||||
|
_ = self;
|
||||||
|
_ = formatter;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Options for copy on select behavior.
|
/// Options for copy on select behavior.
|
||||||
|
338
src/config/formatter.zig
Normal file
338
src/config/formatter.zig
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -119,6 +119,34 @@ pub const Modifier = union(enum) {
|
|||||||
return try parse(input orelse return error.ValueRequired);
|
return try parse(input orelse return error.ValueRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by config formatter
|
||||||
|
pub fn formatEntry(self: Modifier, formatter: anytype) !void {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
switch (self) {
|
||||||
|
.percent => |v| {
|
||||||
|
try formatter.formatEntry(
|
||||||
|
[]const u8,
|
||||||
|
std.fmt.bufPrint(
|
||||||
|
&buf,
|
||||||
|
"{d}%",
|
||||||
|
.{(v - 1) * 100},
|
||||||
|
) catch return error.OutOfMemory,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
.absolute => |v| {
|
||||||
|
try formatter.formatEntry(
|
||||||
|
[]const u8,
|
||||||
|
std.fmt.bufPrint(
|
||||||
|
&buf,
|
||||||
|
"{d}",
|
||||||
|
.{v},
|
||||||
|
) catch return error.OutOfMemory,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply a modifier to a numeric value.
|
/// Apply a modifier to a numeric value.
|
||||||
pub fn apply(self: Modifier, v: u32) u32 {
|
pub fn apply(self: Modifier, v: u32) u32 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
@ -140,6 +168,28 @@ pub const Modifier = union(enum) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "formatConfig percent" {
|
||||||
|
const configpkg = @import("../../config.zig");
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
const p = try parseCLI("24%");
|
||||||
|
try p.formatEntry(configpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = 24%\n", buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "formatConfig absolute" {
|
||||||
|
const configpkg = @import("../../config.zig");
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
const p = try parseCLI("-30");
|
||||||
|
try p.formatEntry(configpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = -30\n", buf.items);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Key is an enum of all the available metrics keys.
|
/// Key is an enum of all the available metrics keys.
|
||||||
|
Reference in New Issue
Block a user