diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 14:53:46 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 12c257e624d3f1f80ca3df041b54026ff772d638 (patch) | |
| tree | 6bba52da1148e167ed695e219bb3a9c742337d95 /src/fs.zig | |
| parent | e56a8ba396566eaab818b6c742850b5842e880bb (diff) | |
| download | pardes-12c257e624d3f1f80ca3df041b54026ff772d638.tar.gz pardes-12c257e624d3f1f80ca3df041b54026ff772d638.zip | |
A path typed with a leading ~ is the home directory, at every entry point that takes one, through one helper
Only DumpDir (dump.zig) and ShaderBuild expanded `~/`, each its own way.
So a look at `~/notes.txt`, `name ~/x`, `Save ~/x`, ThemeFile, Restore and
a quoted `pardes '~/x'` all took `~` as a directory of that name under the
pane's. There is now one rule, filesystem.expandHome. A leading `~`,
alone or before `/` or `:`, is $HOME, else the passwd entry's home;
`~user` is that user's (getpwnam). As in a shell, it applies even beside a
file named `~`, and `./~` names that file. Every entry point runs its
typed path through it:
- look, B3 and 9P, before any parsing, so `~/x:12`, `~/x:12:3` and
`~/x:/re/` all work;
- name, and a tag's rename;
- Save <path>;
- ThemeFile, DumpDir, Restore and ShaderBuild's shader files;
- the forwarding launch.
Recent rows are not shown with `~` and need nothing. How names are shown
is unchanged.
Tests: expandHome's cases, and HOME unset (passwd), with HOME passed in
rather than set, since setenv moves the environ a spawned child reads.
fs.py covers Save into missing directories under `~`, `name ~/x`, a look
at `~/x:3`, and a forwarded `pardes '~/x'`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/fs.zig')
| -rw-r--r-- | src/fs.zig | 57 |
1 files changed, 56 insertions, 1 deletions
@@ -732,6 +732,59 @@ pub fn dotRelative(word: []const u8) bool { /// Where a path really is on the host, and whether it is a directory. The /// path may lie in a mount this editor serves, so the turn is given up for /// the syscalls: another thread answers them. +/// A path's leading `~` -- alone, or before `/` or `:` (`~/x:12`) -- is the +/// home directory: $HOME, else the passwd entry's. `~user/...` is that +/// user's. As in a shell, `~` is home even beside a file named `~` in the +/// current directory. Every entry point that takes a path the user typed +/// runs it through here: look (B3 and 9P), name, Save, ThemeFile, DumpDir, +/// Restore, a forwarded `pardes ~/x`. The path itself when there is no +/// `~` to expand, or no home for it. +pub fn expandHome(path: []const u8, out: *[4096]u8) []const u8 { + if (comptime !pardes.hosted) return path; + return expandHomeFrom(path, out, std.c.getenv("HOME")); +} + +/// expandHome with HOME given (null: unset), so a test need not change the +/// process's environment, which a child spawned later reads. +fn expandHomeFrom(path: []const u8, out: *[4096]u8, env_home: ?[*:0]const u8) []const u8 { + if (path.len == 0 or path[0] != '~') return path; + const end = std.mem.indexOfAny(u8, path, "/:") orelse path.len; + const home = (if (end == 1) homeDir(env_home) else userHome(path[1..end])) orelse return path; + const trimmed = std.mem.trimEnd(u8, home, "/"); + const rest = path[end..]; + if (trimmed.len == 0 and (rest.len == 0 or rest[0] != '/')) return std.fmt.bufPrint(out, "/{s}", .{rest}) catch path; + return std.fmt.bufPrint(out, "{s}{s}", .{ trimmed, rest }) catch path; +} + +fn homeDir(env_home: ?[*:0]const u8) ?[]const u8 { + if (env_home) |h| if (h[0] != 0) return std.mem.span(h); + const pw = std.c.getpwuid(std.c.getuid()) orelse return null; + return std.mem.span(pw.dir orelse return null); +} + +fn userHome(name: []const u8) ?[]const u8 { + var z: [256]u8 = undefined; + const name_z = std.fmt.bufPrintSentinel(&z, "{s}", .{name}, 0) catch return null; + const pw = std.c.getpwnam(name_z) orelse return null; + return std.mem.span(pw.dir orelse return null); +} + +test "a leading ~ is home, from HOME or else the passwd entry" { + var out: [4096]u8 = undefined; + const home = homeDir(std.c.getenv("HOME")) orelse return error.SkipZigTest; + var want: [4096]u8 = undefined; + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{s}/x:12", .{std.mem.trimEnd(u8, home, "/")}), expandHome("~/x:12", &out)); + try std.testing.expectEqualStrings(std.mem.trimEnd(u8, home, "/"), expandHome("~", &out)); + try std.testing.expectEqualStrings("a/~/b", expandHome("a/~/b", &out)); + try std.testing.expectEqualStrings("~no-such-user-pardes/x", expandHome("~no-such-user-pardes/x", &out)); + try std.testing.expectEqualStrings("/", expandHome("~root", &out)[0..1]); + + // HOME unset: the passwd entry's home. + const pw = std.c.getpwuid(std.c.getuid()) orelse return error.SkipZigTest; + var from_passwd: [4096]u8 = undefined; + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&from_passwd, "{s}/y", .{std.mem.trimEnd(u8, std.mem.span(pw.dir.?), "/")}), expandHomeFrom("~/y", &out, null)); +} + pub fn resolveOs(path: []const u8, out: *[4096]u8) ?Resolved { if (comptime !platform_has_fs) return null; var z: [4096]u8 = undefined; @@ -1171,7 +1224,9 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 { /// Restore first tries a relative argument under the default dump directory, /// then preserves the ordinary file lookup. Absolute paths remain verbatim. -pub fn readRestore(gpa: std.mem.Allocator, path: []const u8, dir_setting: []const u8) ![]u8 { +pub fn readRestore(gpa: std.mem.Allocator, typed: []const u8, dir_setting: []const u8) ![]u8 { + var home_buf: [4096]u8 = undefined; + const path = expandHome(typed, &home_buf); var dir_buf: [4096]u8 = undefined; const dir = @import("dump.zig").directory(&dir_buf, dir_setting); return readRestoreIn(gpa, path, dir); |
