From 5593fa22336e939850bf68784c5450e031e4382b Mon Sep 17 00:00:00 2001 From: Yorhel Date: Sat, 16 Nov 2024 11:38:09 +0100 Subject: [PATCH] Expand ~ and ~user in config file Fixes #243 --- src/c.zig | 3 +++ src/main.zig | 16 ++++++++++------ src/util.zig | 28 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/c.zig b/src/c.zig index 9f5e1e5..88f8e12 100644 --- a/src/c.zig +++ b/src/c.zig @@ -9,6 +9,9 @@ pub const c = @cImport({ @cInclude("wchar.h"); // wcwidth() @cInclude("locale.h"); // setlocale() and localeconv() @cInclude("fnmatch.h"); // fnmatch() + @cInclude("unistd.h"); // getuid() + @cInclude("sys/types.h"); // struct passwd + @cInclude("pwd.h"); // getpwnam(), getpwuid() if (@import("builtin").os.tag == .linux) { @cInclude("sys/vfs.h"); // statfs() } diff --git a/src/main.zig b/src/main.zig index 2cf0f8e..703952c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -194,7 +194,7 @@ const Args = struct { } }; -fn argConfig(args: *Args, opt: Args.Option) bool { +fn argConfig(args: *Args, opt: Args.Option, infile: bool) bool { if (opt.is("-q") or opt.is("--slow-ui-updates")) config.update_delay = 2*std.time.ns_per_s else if (opt.is("--fast-ui-updates")) config.update_delay = 100*std.time.ns_per_ms else if (opt.is("-x") or opt.is("--one-file-system")) config.same_fs = true @@ -270,9 +270,13 @@ fn argConfig(args: *Args, opt: Args.Option) bool { else if (opt.is("--no-si")) config.si = false else if (opt.is("-L") or opt.is("--follow-symlinks")) config.follow_symlinks = true else if (opt.is("--no-follow-symlinks")) config.follow_symlinks = false - else if (opt.is("--exclude")) exclude.addPattern(args.arg()) - else if (opt.is("-X") or opt.is("--exclude-from")) { - const arg = args.arg(); + else if (opt.is("--exclude")) { + const arg = if (infile) (util.expanduser(args.arg(), allocator) catch unreachable) else args.arg(); + defer if (infile) allocator.free(arg); + exclude.addPattern(arg); + } else if (opt.is("-X") or opt.is("--exclude-from")) { + const arg = if (infile) (util.expanduser(args.arg(), allocator) catch unreachable) else args.arg(); + defer if (infile) allocator.free(arg); readExcludeFile(arg) catch |e| ui.die("Error reading excludes from {s}: {s}.\n", .{ arg, ui.errorString(e) }); } else if (opt.is("--exclude-caches")) config.exclude_caches = true else if (opt.is("--include-caches")) config.exclude_caches = false @@ -341,7 +345,7 @@ fn tryReadArgsFile(path: [:0]const u8) void { var args = Args.init(arglist.items); while (args.next()) |opt| { - if (!argConfig(&args, opt)) + if (!argConfig(&args, opt, true)) ui.die("Unrecognized option in config file '{s}': {s}.\nRun with --ignore-config to skip reading config files.\n", .{path, opt.val}); } for (arglist.items) |i| allocator.free(i); @@ -540,7 +544,7 @@ pub fn main() void { else if (opt.is("-f")) import_file = allocator.dupeZ(u8, args.arg()) catch unreachable else if (opt.is("--ignore-config")) {} else if (opt.is("--quit-after-scan")) quit_after_scan = true // undocumented feature to help with benchmarking scan/import - else if (argConfig(&args, opt)) {} + else if (argConfig(&args, opt, false)) {} else ui.die("Unrecognized option '{s}'.\n", .{opt.val}); } } diff --git a/src/util.zig b/src/util.zig index d571136..443983b 100644 --- a/src/util.zig +++ b/src/util.zig @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT const std = @import("std"); +const c = @import("c.zig").c; // Cast any integer type to the target type, clamping the value to the supported maximum if necessary. pub fn castClamp(comptime T: type, x: anytype) T { @@ -170,3 +171,30 @@ test "strnatcmp" { for (i+1..w.len) |j| try eq(strnatcmp(w[i], w[j]), .lt); } } + + +pub fn expanduser(path: []const u8, alloc: std.mem.Allocator) ![:0]u8 { + if (path.len == 0 or path[0] != '~') return alloc.dupeZ(u8, path); + + const len = std.mem.indexOfScalar(u8, path, '/') orelse path.len; + const home_raw = blk: { + const pwd = pwd: { + if (len == 1) { + if (std.posix.getenvZ("HOME")) |p| break :blk p; + break :pwd c.getpwuid(c.getuid()); + } else { + const name = try alloc.dupeZ(u8, path[1..len]); + defer alloc.free(name); + break :pwd c.getpwnam(name.ptr); + } + }; + if (pwd != null) + if (@as(*c.struct_passwd, pwd).pw_dir) |p| + break :blk std.mem.span(p); + return alloc.dupeZ(u8, path); + }; + const home = std.mem.trimRight(u8, home_raw, "/"); + + if (home.len == 0 and path.len == len) return alloc.dupeZ(u8, "/"); + return try std.fmt.allocPrintZ(alloc, "{s}{s}", .{ home, path[len..] }); +}