diff options
| author | T <t@tjp.lol> | 2026-05-27 12:45:20 -0600 |
|---|---|---|
| committer | T <t@tjp.lol> | 2026-05-27 16:21:52 -0600 |
| commit | 6545cdfd8f2bc865aa06a2b5515056daf58ba111 (patch) | |
| tree | 5393cefda42dad12eb90612c6b7ff3c50d8eb800 /src/session_paths.zig | |
| parent | b1a155273662d7dc2ea1fe0cfc43c1741ed30b6d (diff) | |
session files
Diffstat (limited to 'src/session_paths.zig')
| -rw-r--r-- | src/session_paths.zig | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/src/session_paths.zig b/src/session_paths.zig new file mode 100644 index 0000000..162085e --- /dev/null +++ b/src/session_paths.zig @@ -0,0 +1,141 @@ +//! Resolves session file locations from the environment. +//! +//! Layout (defaults; override base via `PANTO_SESSION_DIR`): +//! +//! $XDG_DATA_HOME/panto/sessions/<encoded-cwd>/ +//! ↳ falls back to $HOME/.local/share/panto/sessions/<encoded-cwd>/ +//! if $XDG_DATA_HOME is unset. +//! +//! `<encoded-cwd>` is the working directory with leading `/` stripped and +//! every `/` or `:` replaced by `-`, with `--` glued to both ends. This +//! gives a flat directory name per project, easy to spot in `ls`. + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +/// Resolve the absolute sessions directory for the given cwd. Caller owns +/// the returned slice. +/// +/// Precedence: +/// 1. `PANTO_SESSION_DIR` (full path to the base dir, used as-is — no +/// "panto/sessions" suffix is added) +/// 2. `XDG_DATA_HOME/panto/sessions` +/// 3. `HOME/.local/share/panto/sessions` +/// +/// In all three cases, `<encoded-cwd>/` is appended. +pub fn sessionDirForCwd( + allocator: Allocator, + environ_map: *const std.process.Environ.Map, + cwd: []const u8, +) ![]u8 { + const base = try resolveSessionsBase(allocator, environ_map); + defer allocator.free(base); + + const encoded = try encodeCwd(allocator, cwd); + defer allocator.free(encoded); + + return try std.fs.path.join(allocator, &.{ base, encoded }); +} + +/// Resolve the absolute "sessions" base directory, before per-cwd grouping. +/// Caller owns the returned slice. +pub fn resolveSessionsBase( + allocator: Allocator, + environ_map: *const std.process.Environ.Map, +) ![]u8 { + if (environ_map.get("PANTO_SESSION_DIR")) |explicit| { + return try allocator.dupe(u8, explicit); + } + if (environ_map.get("XDG_DATA_HOME")) |xdg| { + return try std.fs.path.join(allocator, &.{ xdg, "panto", "sessions" }); + } + if (environ_map.get("HOME")) |home| { + return try std.fs.path.join(allocator, &.{ home, ".local", "share", "panto", "sessions" }); + } + return error.NoHomeDirectory; +} + +/// Encode a working directory into a flat directory name. Caller owns. +/// +/// Example: `/Users/travis/Code/pantograph` → `--Users-travis-Code-pantograph--` +pub fn encodeCwd(allocator: Allocator, cwd: []const u8) ![]u8 { + // Strip leading slash(es), then replace `/` and `:` with `-`. + var start: usize = 0; + while (start < cwd.len and (cwd[start] == '/' or cwd[start] == '\\')) : (start += 1) {} + const body = cwd[start..]; + const out = try allocator.alloc(u8, body.len + 4); // `--` + body + `--` + out[0] = '-'; + out[1] = '-'; + for (body, 0..) |c, i| { + out[2 + i] = if (c == '/' or c == '\\' or c == ':') '-' else c; + } + out[out.len - 2] = '-'; + out[out.len - 1] = '-'; + return out; +} + +// ============================================================================= +// Tests +// ============================================================================= + +const testing = std.testing; + +test "encodeCwd: replaces slashes and colons" { + const a = testing.allocator; + const got = try encodeCwd(a, "/Users/travis/Code/pantograph"); + defer a.free(got); + try testing.expectEqualStrings("--Users-travis-Code-pantograph--", got); +} + +test "encodeCwd: handles already-relative paths" { + const a = testing.allocator; + const got = try encodeCwd(a, "Users/travis"); + defer a.free(got); + try testing.expectEqualStrings("--Users-travis--", got); +} + +test "resolveSessionsBase: PANTO_SESSION_DIR wins" { + const a = testing.allocator; + var env: std.process.Environ.Map = .init(a); + defer env.deinit(); + try env.put("PANTO_SESSION_DIR", "/custom/sessions"); + try env.put("XDG_DATA_HOME", "/ignored"); + + const got = try resolveSessionsBase(a, &env); + defer a.free(got); + try testing.expectEqualStrings("/custom/sessions", got); +} + +test "resolveSessionsBase: XDG_DATA_HOME before HOME" { + const a = testing.allocator; + var env: std.process.Environ.Map = .init(a); + defer env.deinit(); + try env.put("XDG_DATA_HOME", "/x/data"); + try env.put("HOME", "/h"); + + const got = try resolveSessionsBase(a, &env); + defer a.free(got); + try testing.expectEqualStrings("/x/data/panto/sessions", got); +} + +test "resolveSessionsBase: falls back to HOME/.local/share" { + const a = testing.allocator; + var env: std.process.Environ.Map = .init(a); + defer env.deinit(); + try env.put("HOME", "/home/user"); + + const got = try resolveSessionsBase(a, &env); + defer a.free(got); + try testing.expectEqualStrings("/home/user/.local/share/panto/sessions", got); +} + +test "sessionDirForCwd: joins base and encoded cwd" { + const a = testing.allocator; + var env: std.process.Environ.Map = .init(a); + defer env.deinit(); + try env.put("PANTO_SESSION_DIR", "/sess"); + + const got = try sessionDirForCwd(a, &env, "/Users/travis/Code/pantograph"); + defer a.free(got); + try testing.expectEqualStrings("/sess/--Users-travis-Code-pantograph--", got); +} |
