From 2520bb3d0712c14e2f01916d44520307a329b88b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 16 Nov 2023 13:58:53 -0800 Subject: [PATCH] config: introduce RepeatablePath to automatically expand --- src/config/Config.zig | 112 ++++++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 32 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 11890d5b5..4f19f0bb6 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -528,7 +528,7 @@ keybind: Keybinds = .{}, /// /// Cycles are not allowed. If a cycle is detected, an error will be logged /// and the configuration file will be ignored. -@"config-file": RepeatableString = .{}, +@"config-file": RepeatablePath = .{}, /// Confirms that a surface should be closed before closing it. This defaults /// to true. If set to false, surfaces will close without any confirmation. @@ -1139,7 +1139,7 @@ pub fn loadDefaultFiles(self: *Config, alloc: Allocator) !void { var buf_reader = std.io.bufferedReader(file.reader()); var iter = cli.args.lineIterator(buf_reader.reader()); try cli.args.parse(Config, alloc, self, &iter); - try self.expandConfigFiles(std.fs.path.dirname(config_path).?); + try self.expandPaths(std.fs.path.dirname(config_path).?); } else |err| switch (err) { error.FileNotFound => std.log.info( "homedir config not found, not loading path={s}", @@ -1168,15 +1168,15 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void { try cli.args.parse(Config, alloc_gpa, self, &iter); // Config files loaded from the CLI args are relative to pwd - if (self.@"config-file".list.items.len > 0) { + if (self.@"config-file".value.list.items.len > 0) { var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - try self.expandConfigFiles(try std.fs.cwd().realpath(".", &buf)); + try self.expandPaths(try std.fs.cwd().realpath(".", &buf)); } } /// Load and parse the config files that were added in the "config-file" key. pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void { - if (self.@"config-file".list.items.len == 0) return; + if (self.@"config-file".value.list.items.len == 0) return; const arena_alloc = self._arena.?.allocator(); // Keeps track of loaded files to prevent cycles. @@ -1185,8 +1185,8 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void { const cwd = std.fs.cwd(); var i: usize = 0; - while (i < self.@"config-file".list.items.len) : (i += 1) { - const path = self.@"config-file".list.items[i]; + while (i < self.@"config-file".value.list.items.len) : (i += 1) { + const path = self.@"config-file".value.list.items[i]; // Error paths if (path.len == 0) continue; @@ -1223,37 +1223,22 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void { var buf_reader = std.io.bufferedReader(file.reader()); var iter = cli.args.lineIterator(buf_reader.reader()); try cli.args.parse(Config, alloc_gpa, self, &iter); - try self.expandConfigFiles(std.fs.path.dirname(path).?); + try self.expandPaths(std.fs.path.dirname(path).?); } } /// Expand the relative paths in config-files to be absolute paths /// relative to the base directory. -fn expandConfigFiles(self: *Config, base: []const u8) !void { - assert(std.fs.path.isAbsolute(base)); - var dir = try std.fs.cwd().openDir(base, .{}); - defer dir.close(); - +fn expandPaths(self: *Config, base: []const u8) !void { const arena_alloc = self._arena.?.allocator(); - for (self.@"config-file".list.items, 0..) |path, i| { - // If it is already absolute we can ignore it. - if (path.len == 0 or std.fs.path.isAbsolute(path)) continue; - - // If it isn't absolute, we need to make it absolute relative to the base. - const abs = dir.realpathAlloc(arena_alloc, path) catch |err| { - try self._errors.add(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( - arena_alloc, - "error resolving config-file {s}: {}", - .{ path, err }, - ), - }); - self.@"config-file".list.items[i] = ""; - continue; - }; - - log.debug("expanding config-file path relative={s} abs={s}", .{ path, abs }); - self.@"config-file".list.items[i] = abs; + inline for (@typeInfo(Config).Struct.fields) |field| { + if (field.type == RepeatablePath) { + try @field(self, field.name).expand( + arena_alloc, + base, + &self._errors, + ); + } } } @@ -1885,6 +1870,69 @@ pub const RepeatableString = struct { } }; +/// RepeatablePath is like repeatable string but represents a path value. +/// The difference is that when loading the configuration any values for +/// this will be automatically expanded relative to the path of the config +/// file. +pub const RepeatablePath = struct { + const Self = @This(); + + value: RepeatableString = .{}, + + pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void { + return self.value.parseCLI(alloc, input); + } + + /// Deep copy of the struct. Required by Config. + pub fn clone(self: *const Self, alloc: Allocator) !Self { + return .{ + .value = try self.value.clone(alloc), + }; + } + + /// Compare if two of our value are requal. Required by Config. + pub fn equal(self: Self, other: Self) bool { + return self.value.equal(other.value); + } + + /// Expand all the paths relative to the base directory. + pub fn expand( + self: *Self, + alloc: Allocator, + base: []const u8, + errors: *ErrorList, + ) !void { + assert(std.fs.path.isAbsolute(base)); + var dir = try std.fs.cwd().openDir(base, .{}); + defer dir.close(); + + for (self.value.list.items, 0..) |path, i| { + // If it is already absolute we can ignore it. + if (path.len == 0 or std.fs.path.isAbsolute(path)) continue; + + // If it isn't absolute, we need to make it absolute relative + // to the base. + const abs = dir.realpathAlloc(alloc, path) catch |err| { + try errors.add(alloc, .{ + .message = try std.fmt.allocPrintZ( + alloc, + "error resolving config-file {s}: {}", + .{ path, err }, + ), + }); + self.value.list.items[i] = ""; + continue; + }; + + log.debug( + "expanding config-file path relative={s} abs={s}", + .{ path, abs }, + ); + self.value.list.items[i] = abs; + } + } +}; + /// FontVariation is a repeatable configuration value that sets a single /// font variation value. Font variations are configurations for what /// are often called "variable fonts." The font files usually end in