From 32a1c6ec0653da66b96382857228e5101564d3cb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 20 Jan 2024 14:41:49 -0800 Subject: [PATCH] config: ability to format all field types except tagged unions --- src/config/Config.zig | 139 +++++++++++++++++++++++++++++++++++++++ src/config/formatter.zig | 92 ++++++++++++++++++++++---- 2 files changed, 219 insertions(+), 12 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index e323d5ce9..b888c42fd 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -2178,6 +2178,19 @@ pub const Color = packed struct(u24) { 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 "#" /// is optional. pub fn fromHex(input: []const u8) !Color { @@ -2251,6 +2264,21 @@ pub const Palette = struct { 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" { const testing = std.testing; @@ -2314,6 +2342,19 @@ pub const RepeatableString = struct { } 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" { const testing = std.testing; var arena = ArenaAllocator.init(testing.allocator); @@ -2355,6 +2396,11 @@ pub const RepeatablePath = struct { 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. pub fn expand( self: *Self, @@ -2442,6 +2488,26 @@ pub const RepeatableFontVariation = struct { } 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" { const testing = std.testing; var arena = ArenaAllocator.init(testing.allocator); @@ -2561,6 +2627,29 @@ pub const Keybinds = struct { 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" { const testing = std.testing; var arena = ArenaAllocator.init(testing.allocator); @@ -2618,6 +2707,49 @@ pub const RepeatableCodepointMap = struct { } 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: /// /// "" (empty returns null) @@ -2836,6 +2968,13 @@ pub const RepeatableLink = struct { _ = other; 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. diff --git a/src/config/formatter.zig b/src/config/formatter.zig index ab5a8667f..26be9a816 100644 --- a/src/config/formatter.zig +++ b/src/config/formatter.zig @@ -19,7 +19,7 @@ pub const FileFormatter = struct { inline for (@typeInfo(Config).Struct.fields) |field| { if (field.name[0] == '_') continue; - try self.formatField( + try self.formatEntry( field.type, field.name, @field(self.config, field.name), @@ -28,13 +28,32 @@ pub const FileFormatter = struct { } } - fn formatField( + pub fn formatEntry( self: FileFormatter, comptime T: type, name: []const u8, value: T, writer: anytype, ) !void { + const EntryFormatter = struct { + parent: *const FileFormatter, + name: []const u8, + writer: @TypeOf(writer), + + pub fn formatEntry( + self_entry: @This(), + comptime EntryT: type, + value_entry: EntryT, + ) !void { + return self_entry.parent.formatEntry( + EntryT, + self_entry.name, + value_entry, + self_entry.writer, + ); + } + }; + switch (@typeInfo(T)) { .Bool, .Int => { try writer.print("{s} = {}\n", .{ name, value }); @@ -46,15 +65,29 @@ pub const FileFormatter = struct { return; }, - .Optional => |info| if (value) |inner| { - try self.formatField( - info.child, - name, - inner, - writer, - ); - } else { + .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 self.formatEntry( + info.child, + name, + inner, + writer, + ); + } else { + try writer.print("{s} = \n", .{name}); + } + + return; }, .Pointer => switch (T) { @@ -62,16 +95,51 @@ pub const FileFormatter = struct { [: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{ + .parent = &self, + .name = name, + .writer = 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 => {}, + }, + + // TODO + .Union => return, + else => {}, } - // TODO: make a compiler error so we can detect when - // we don't support a type. + // Compile error so that we can catch missing cases. + @compileLog(T); + @compileError("missing case for type"); } };