summaryrefslogtreecommitdiff
path: root/src/session_paths.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/session_paths.zig')
-rw-r--r--src/session_paths.zig141
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);
+}