use std::path::PathBuf;
use crate::error::Result;
pub fn config_dir() -> Result<PathBuf> {
xdg_or_home("XDG_CONFIG_HOME", &[".config"])
}
pub fn data_dir() -> Result<PathBuf> {
xdg_or_home("XDG_DATA_HOME", &[".local", "share"])
}
pub fn state_dir() -> Result<PathBuf> {
xdg_or_home("XDG_STATE_HOME", &[".local", "state"])
}
fn xdg_or_home(xdg_var: &str, home_base: &[&str]) -> Result<PathBuf> {
use crate::error::CruiseError;
if let Some(val) = std::env::var_os(xdg_var) {
return Ok(PathBuf::from(val).join("cruise"));
}
let home = home::home_dir()
.ok_or_else(|| CruiseError::Other("cannot determine home directory".to_string()))?;
let mut path = home;
for component in home_base {
path = path.join(component);
}
Ok(path.join("cruise"))
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
use crate::test_support::{EnvGuard, lock_process, set_fake_home};
#[test]
fn test_config_dir_uses_xdg_config_home_when_set() {
let _lock = lock_process();
let tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _home_guards = set_fake_home(tmp.path());
let _xdg = EnvGuard::set("XDG_CONFIG_HOME", tmp.path().join("xdg-config").as_os_str());
let dir = config_dir().unwrap_or_else(|e| panic!("expected Ok, got: {e}"));
assert_eq!(dir, tmp.path().join("xdg-config").join("cruise"));
}
#[test]
fn test_config_dir_falls_back_to_home_dot_config_when_xdg_unset() {
let _lock = lock_process();
let tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _home_guards = set_fake_home(tmp.path());
let dir = config_dir().unwrap_or_else(|e| panic!("expected Ok, got: {e}"));
assert_eq!(dir, tmp.path().join(".config").join("cruise"));
}
#[test]
fn test_data_dir_uses_xdg_data_home_when_set() {
let _lock = lock_process();
let tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _home_guards = set_fake_home(tmp.path());
let _xdg = EnvGuard::set("XDG_DATA_HOME", tmp.path().join("xdg-data").as_os_str());
let dir = data_dir().unwrap_or_else(|e| panic!("expected Ok, got: {e}"));
assert_eq!(dir, tmp.path().join("xdg-data").join("cruise"));
}
#[test]
fn test_data_dir_falls_back_to_home_dot_local_share_when_xdg_unset() {
let _lock = lock_process();
let tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _home_guards = set_fake_home(tmp.path());
let dir = data_dir().unwrap_or_else(|e| panic!("expected Ok, got: {e}"));
assert_eq!(dir, tmp.path().join(".local").join("share").join("cruise"));
}
#[test]
fn test_state_dir_uses_xdg_state_home_when_set() {
let _lock = lock_process();
let tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _home_guards = set_fake_home(tmp.path());
let _xdg = EnvGuard::set("XDG_STATE_HOME", tmp.path().join("xdg-state").as_os_str());
let dir = state_dir().unwrap_or_else(|e| panic!("expected Ok, got: {e}"));
assert_eq!(dir, tmp.path().join("xdg-state").join("cruise"));
}
#[test]
fn test_state_dir_falls_back_to_home_dot_local_state_when_xdg_unset() {
let _lock = lock_process();
let tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _home_guards = set_fake_home(tmp.path());
let dir = state_dir().unwrap_or_else(|e| panic!("expected Ok, got: {e}"));
assert_eq!(dir, tmp.path().join(".local").join("state").join("cruise"));
}
#[test]
fn test_config_dir_xdg_takes_priority_over_home_fallback() {
let _lock = lock_process();
let home_tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let xdg_tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _home_guards = set_fake_home(home_tmp.path());
let _xdg = EnvGuard::set("XDG_CONFIG_HOME", xdg_tmp.path().as_os_str());
let dir = config_dir().unwrap_or_else(|e| panic!("expected Ok, got: {e}"));
assert!(
dir.starts_with(xdg_tmp.path()),
"XDG_CONFIG_HOME should take priority, got: {}",
dir.display()
);
}
#[test]
fn test_data_dir_xdg_takes_priority_over_home_fallback() {
let _lock = lock_process();
let home_tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let xdg_tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _home_guards = set_fake_home(home_tmp.path());
let _xdg = EnvGuard::set("XDG_DATA_HOME", xdg_tmp.path().as_os_str());
let dir = data_dir().unwrap_or_else(|e| panic!("expected Ok, got: {e}"));
assert!(
dir.starts_with(xdg_tmp.path()),
"XDG_DATA_HOME should take priority, got: {}",
dir.display()
);
}
#[test]
fn test_state_dir_xdg_takes_priority_over_home_fallback() {
let _lock = lock_process();
let home_tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let xdg_tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _home_guards = set_fake_home(home_tmp.path());
let _xdg = EnvGuard::set("XDG_STATE_HOME", xdg_tmp.path().as_os_str());
let dir = state_dir().unwrap_or_else(|e| panic!("expected Ok, got: {e}"));
assert!(
dir.starts_with(xdg_tmp.path()),
"XDG_STATE_HOME should take priority, got: {}",
dir.display()
);
}
#[test]
fn test_all_dirs_end_with_cruise_component() {
let _lock = lock_process();
let tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _guards = set_fake_home(tmp.path());
let config = config_dir().unwrap_or_else(|e| panic!("{e}"));
let data = data_dir().unwrap_or_else(|e| panic!("{e}"));
let state = state_dir().unwrap_or_else(|e| panic!("{e}"));
assert_eq!(
config
.file_name()
.unwrap_or_else(|| panic!("config_dir must have a final component")),
"cruise",
"config_dir should end with 'cruise'"
);
assert_eq!(
data.file_name()
.unwrap_or_else(|| panic!("data_dir must have a final component")),
"cruise",
"data_dir should end with 'cruise'"
);
assert_eq!(
state
.file_name()
.unwrap_or_else(|| panic!("state_dir must have a final component")),
"cruise",
"state_dir should end with 'cruise'"
);
}
#[test]
fn test_set_fake_home_isolates_all_three_dirs_from_host_xdg_vars() {
let _lock = lock_process();
let tmp = TempDir::new().unwrap_or_else(|e| panic!("{e:?}"));
let _guards = set_fake_home(tmp.path());
let config = config_dir().unwrap_or_else(|e| panic!("{e}"));
let data = data_dir().unwrap_or_else(|e| panic!("{e}"));
let state = state_dir().unwrap_or_else(|e| panic!("{e}"));
assert!(
config.starts_with(tmp.path()),
"config_dir should be under fake HOME: {}",
config.display()
);
assert!(
data.starts_with(tmp.path()),
"data_dir should be under fake HOME: {}",
data.display()
);
assert!(
state.starts_with(tmp.path()),
"state_dir should be under fake HOME: {}",
state.display()
);
}
}