use anyhow::Result;
use dirs;
use std::path::Path;
use std::path::PathBuf;
pub fn expand_path(path: &Path) -> Result<PathBuf> {
let path_str = path.to_string_lossy();
if let Some(stripped) = path_str.strip_prefix("~/") {
let home = dirs::home_dir()
.ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
Ok(home.join(stripped))
} else if path_str == "~" {
dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))
} else {
Ok(path.to_path_buf())
}
}
pub fn ensure_dir(path: &Path) -> Result<()> {
if !path.exists() {
std::fs::create_dir_all(path)?;
}
Ok(())
}
pub fn sanitize_dir_name(name: &str) -> String {
name.chars()
.map(|c| match c {
'/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
_ => c,
})
.collect()
}
pub fn get_repo_config_path(repo_root: &Path) -> PathBuf {
repo_root.join(".thoughts").join("config.json")
}
#[cfg(target_os = "macos")]
pub fn get_external_metadata_dir() -> Result<PathBuf> {
let home =
dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
Ok(home.join(".thoughts").join("data").join("external"))
}
pub fn get_local_metadata_path(repo_root: &Path) -> PathBuf {
repo_root.join(".thoughts").join("data").join("local.json")
}
pub fn get_repo_rules_path(repo_root: &Path) -> PathBuf {
repo_root.join(".thoughts").join("rules.json")
}
fn xdg_config_home() -> Result<PathBuf> {
if let Some(dir) = std::env::var_os("XDG_CONFIG_HOME") {
return Ok(PathBuf::from(dir));
}
let home =
dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
Ok(home.join(".config"))
}
pub fn get_repo_mapping_path() -> Result<PathBuf> {
Ok(xdg_config_home()?.join("agentic").join("repos.json"))
}
pub fn get_legacy_repo_mapping_path() -> Result<PathBuf> {
let home =
dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
Ok(home.join(".thoughts").join("repos.json"))
}
pub fn get_personal_config_path() -> Result<PathBuf> {
let home =
dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
Ok(home.join(".thoughts").join("config.json"))
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
#[test]
#[serial]
fn test_expand_path() {
let home = dirs::home_dir().unwrap();
assert_eq!(expand_path(Path::new("~/test")).unwrap(), home.join("test"));
assert_eq!(expand_path(Path::new("~")).unwrap(), home);
assert_eq!(
expand_path(Path::new("/tmp/test")).unwrap(),
PathBuf::from("/tmp/test")
);
assert_eq!(
expand_path(Path::new("test")).unwrap(),
PathBuf::from("test")
);
}
#[test]
fn test_sanitize_dir_name() {
assert_eq!(sanitize_dir_name("normal-name_123"), "normal-name_123");
assert_eq!(
sanitize_dir_name("bad/name:with*chars?"),
"bad_name_with_chars_"
);
}
}