Skip to main content

dejavu/paths/
mod.rs

1//! Cache/config path resolution and the per-repo cache layout.
2//!
3//! Note the deliberate macOS asymmetry (spec ยง7): the *cache* follows the
4//! platform convention (`~/Library/Caches` on macOS, `$XDG_CACHE_HOME` on
5//! Linux/WSL), but the *config* is XDG-style (`~/.config`) on **all**
6//! platforms โ€” never `~/Library/Application Support`.
7
8mod layout;
9pub use layout::CacheLayout;
10
11use crate::error::PathError;
12use crate::util::sha256_hex;
13use std::path::{Path, PathBuf};
14
15fn home() -> Result<PathBuf, PathError> {
16    dirs::home_dir().ok_or(PathError::NoHome)
17}
18
19/// `<cache>/dejavu` โ€” the root under which every repo gets a `<repo_hash>` dir.
20/// macOS: `~/Library/Caches/dejavu`. Linux/WSL: `$XDG_CACHE_HOME/dejavu` then
21/// `~/.cache/dejavu`. `dirs::cache_dir()` already encodes this platform matrix.
22pub fn cache_root() -> Result<PathBuf, PathError> {
23    let base = dirs::cache_dir().ok_or(PathError::NoCacheDir)?;
24    Ok(base.join("dejavu"))
25}
26
27/// The repo-independent shim dir used by global activation (`dejavu
28/// shellenv`): `<cache_root>/shims/bin`. Never collides with per-repo caches
29/// (`<cache_root>/<16-hex-hash>/`).
30pub fn global_shims_bin() -> Result<PathBuf, PathError> {
31    Ok(cache_root()?.join("shims").join("bin"))
32}
33
34/// Global config file: `$XDG_CONFIG_HOME/dejavu/config.toml`, else
35/// `~/.config/dejavu/config.toml` โ€” XDG on every platform.
36pub fn config_file_path() -> Result<PathBuf, PathError> {
37    let base = match std::env::var_os("XDG_CONFIG_HOME") {
38        Some(v) if !v.is_empty() && Path::new(&v).is_absolute() => PathBuf::from(v),
39        _ => home()?.join(".config"),
40    };
41    Ok(base.join("dejavu").join("config.toml"))
42}
43
44/// Stable 16-hex-char id for a repo, derived from its canonicalized absolute
45/// path (symlinks resolved so two paths to the same repo collide correctly).
46pub fn repo_hash(repo_root: &Path) -> String {
47    let canonical = std::fs::canonicalize(repo_root).unwrap_or_else(|_| repo_root.to_path_buf());
48    let full = sha256_hex(canonical.to_string_lossy().as_bytes());
49    full[..16].to_string()
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn repo_hash_is_stable_and_16_hex() {
58        let a = repo_hash(Path::new("/nonexistent/path/one"));
59        let b = repo_hash(Path::new("/nonexistent/path/one"));
60        let c = repo_hash(Path::new("/nonexistent/path/two"));
61        assert_eq!(a, b);
62        assert_ne!(a, c);
63        assert_eq!(a.len(), 16);
64        assert!(a.chars().all(|ch| ch.is_ascii_hexdigit()));
65    }
66}