use std::path::{Path, PathBuf};
use crate::brand::{BRAND, Brand, env_name};
use crate::env;
#[derive(Clone, Debug, Default)]
pub struct LocatorOverrides {
pub home: Option<PathBuf>,
pub cwd: Option<PathBuf>,
}
#[derive(Clone, Debug)]
pub struct Locator {
home: PathBuf,
cwd: PathBuf,
brand: Brand,
}
impl Locator {
pub fn new(overrides: LocatorOverrides) -> Self {
Self::with_brand(overrides, BRAND)
}
pub fn with_brand(overrides: LocatorOverrides, brand: Brand) -> Self {
let home = Self::resolve_home(overrides.home, &brand);
let cwd = overrides
.cwd
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
Self { home, cwd, brand }
}
fn resolve_home(override_home: Option<PathBuf>, brand: &Brand) -> PathBuf {
if let Some(h) = override_home
&& !h.as_os_str().is_empty()
{
return h;
}
let _ = brand; if let Some(from_env) = env::read_env("HOME") {
return PathBuf::from(from_env);
}
env::home_dir().unwrap_or_else(|| PathBuf::from("."))
}
pub fn home(&self) -> &Path {
&self.home
}
pub fn cwd(&self) -> &Path {
&self.cwd
}
pub fn profile_dir(&self) -> PathBuf {
self.home.join(self.brand.profile_dir_name)
}
pub fn settings_path(&self) -> PathBuf {
self.profile_dir().join(self.brand.settings_file_name)
}
pub fn project_settings_path(&self, cwd: Option<&Path>) -> PathBuf {
let root = match cwd {
Some(c) if c.is_absolute() => c.to_path_buf(),
Some(c) => self.cwd.join(c),
None => self.cwd.clone(),
};
root.join(self.brand.project_dir_name)
.join(self.brand.project_settings_file_name)
}
pub fn sessions_dir(&self) -> PathBuf {
self.profile_dir().join(self.brand.sessions_dir_name)
}
pub fn auth_store_path(&self) -> PathBuf {
self.profile_dir().join(self.brand.auth_store_file_name)
}
pub fn logs_dir(&self) -> PathBuf {
self.profile_dir().join(self.brand.logs_dir_name)
}
pub async fn ensure_profile_dir(&self) -> std::io::Result<PathBuf> {
self.ensure_dir(self.profile_dir()).await
}
pub async fn ensure_sessions_dir(&self) -> std::io::Result<PathBuf> {
self.ensure_dir(self.sessions_dir()).await
}
pub async fn ensure_logs_dir(&self) -> std::io::Result<PathBuf> {
self.ensure_dir(self.logs_dir()).await
}
pub async fn ensure_parent_of(&self, file_path: PathBuf) -> std::io::Result<PathBuf> {
if let Some(parent) = file_path.parent() {
self.ensure_dir(parent.to_path_buf()).await?;
}
Ok(file_path)
}
pub async fn ensure_dir(&self, dir: PathBuf) -> std::io::Result<PathBuf> {
tokio::fs::create_dir_all(&dir).await?;
Ok(dir)
}
}
pub fn home_env_var() -> String {
env_name("HOME")
}
#[cfg(test)]
mod tests {
use super::*;
fn sandbox(home: &str, cwd: &str) -> Locator {
Locator::new(LocatorOverrides {
home: Some(PathBuf::from(home)),
cwd: Some(PathBuf::from(cwd)),
})
}
#[test]
fn override_home_drives_every_state_path() {
let loc = sandbox("/tmp/box", "/work/project");
assert_eq!(loc.profile_dir(), PathBuf::from("/tmp/box/.indusagi"));
assert_eq!(
loc.settings_path(),
PathBuf::from("/tmp/box/.indusagi/settings.json")
);
assert_eq!(
loc.auth_store_path(),
PathBuf::from("/tmp/box/.indusagi/auth.json")
);
assert_eq!(
loc.sessions_dir(),
PathBuf::from("/tmp/box/.indusagi/sessions")
);
assert_eq!(loc.logs_dir(), PathBuf::from("/tmp/box/.indusagi/logs"));
}
#[test]
fn project_settings_resolve_relative_against_cwd() {
let loc = sandbox("/tmp/box", "/work/project");
assert_eq!(
loc.project_settings_path(Some(Path::new("/abs/repo"))),
PathBuf::from("/abs/repo/.indusagi/settings.json")
);
assert_eq!(
loc.project_settings_path(Some(Path::new("sub"))),
PathBuf::from("/work/project/sub/.indusagi/settings.json")
);
assert_eq!(
loc.project_settings_path(None),
PathBuf::from("/work/project/.indusagi/settings.json")
);
}
#[test]
fn home_env_var_is_branded() {
assert_eq!(home_env_var(), "INDUSAGI_HOME");
}
#[tokio::test]
async fn ensure_dir_creates_nested_paths() {
let tmp = std::env::temp_dir().join(format!("indusagi-loc-test-{}", crate::ids::new_id()));
let loc = Locator::new(LocatorOverrides {
home: Some(tmp.clone()),
cwd: None,
});
let sessions = loc.ensure_sessions_dir().await.unwrap();
assert!(sessions.is_dir());
assert_eq!(sessions, tmp.join(".indusagi/sessions"));
let _ = tokio::fs::remove_dir_all(&tmp).await;
}
}