Skip to main content

lean_ctx/core/
home.rs

1use std::path::PathBuf;
2
3/// Resolve the user's home directory in a way that is:
4/// - Override-friendly for CI/tests (HOME/USERPROFILE)
5/// - Still correct in normal interactive installs (fallback to `dirs::home_dir()`)
6pub fn resolve_home_dir() -> Option<PathBuf> {
7    if let Ok(home) = std::env::var("HOME") {
8        let trimmed = home.trim();
9        if !trimmed.is_empty() {
10            return Some(PathBuf::from(trimmed));
11        }
12    }
13
14    #[cfg(windows)]
15    {
16        if let Ok(profile) = std::env::var("USERPROFILE") {
17            let trimmed = profile.trim();
18            if !trimmed.is_empty() {
19                return Some(PathBuf::from(trimmed));
20            }
21        }
22
23        if let (Ok(drive), Ok(path)) = (std::env::var("HOMEDRIVE"), std::env::var("HOMEPATH")) {
24            if !drive.trim().is_empty() && !path.trim().is_empty() {
25                return Some(PathBuf::from(format!("{}{}", drive.trim(), path.trim())));
26            }
27        }
28    }
29
30    dirs::home_dir()
31}
32
33/// Resolve the Codex config directory.
34/// Respects `CODEX_HOME` env var (official Codex CLI feature).
35/// Falls back to `~/.codex` when unset or empty.
36pub fn resolve_codex_dir() -> Option<PathBuf> {
37    if let Ok(val) = std::env::var("CODEX_HOME") {
38        let trimmed = val.trim();
39        if !trimmed.is_empty() {
40            return Some(PathBuf::from(trimmed));
41        }
42    }
43    resolve_home_dir().map(|h| h.join(".codex"))
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn resolve_codex_dir_respects_env_var() {
52        let _guard = env_lock();
53        std::env::set_var("CODEX_HOME", "/tmp/custom-codex");
54        let result = resolve_codex_dir();
55        assert_eq!(result, Some(PathBuf::from("/tmp/custom-codex")));
56        std::env::remove_var("CODEX_HOME");
57    }
58
59    #[test]
60    fn resolve_codex_dir_ignores_empty_env() {
61        let _guard = env_lock();
62        std::env::set_var("CODEX_HOME", "  ");
63        let result = resolve_codex_dir();
64        assert!(result.is_some());
65        assert!(result.unwrap().ends_with(".codex"));
66        std::env::remove_var("CODEX_HOME");
67    }
68
69    #[test]
70    fn resolve_codex_dir_falls_back_to_home() {
71        let _guard = env_lock();
72        std::env::remove_var("CODEX_HOME");
73        let result = resolve_codex_dir();
74        assert!(result.is_some());
75        assert!(result.unwrap().ends_with(".codex"));
76    }
77
78    fn env_lock() -> std::sync::MutexGuard<'static, ()> {
79        static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
80        LOCK.lock()
81            .unwrap_or_else(std::sync::PoisonError::into_inner)
82    }
83}