use std::path::PathBuf;
pub const CODECORA_HOME_ENV: &str = "CODECORA_HOME";
pub fn codecora_home() -> PathBuf {
let override_dir = std::env::var_os(CODECORA_HOME_ENV);
#[cfg(test)]
if override_dir.is_none() {
return test_home().to_path_buf();
}
resolve_home(override_dir, dirs::home_dir())
}
fn resolve_home(override_dir: Option<std::ffi::OsString>, home: Option<PathBuf>) -> PathBuf {
match override_dir {
Some(dir) => PathBuf::from(dir),
None => home
.expect("Cannot determine home directory. Set CODECORA_HOME or HOME.")
.join(".codecora"),
}
}
#[cfg(test)]
fn test_home() -> &'static std::path::Path {
static HOME: std::sync::LazyLock<PathBuf> = std::sync::LazyLock::new(|| {
tempfile::Builder::new()
.prefix("cora-test-home-")
.tempdir()
.expect("create test data dir")
.keep()
});
&HOME
}
pub fn product_data_dir(product: &str) -> PathBuf {
codecora_home().join(product)
}
pub fn cora_data_dir() -> PathBuf {
product_data_dir("cora-code")
}
pub fn graph_db_path() -> PathBuf {
let dir = cora_data_dir();
let new_db = dir.join("cora.db");
if !new_db.exists() {
let old_db = dir.join("graph.db");
if old_db.exists() {
if let Err(e) = std::fs::rename(&old_db, &new_db) {
eprintln!("warning: failed to migrate graph.db → cora.db: {e}");
return old_db;
}
}
}
new_db
}
pub fn update_cache_path() -> PathBuf {
cora_data_dir().join("update-cache.json")
}
pub fn ensure_data_dir() -> anyhow::Result<PathBuf> {
let dir = cora_data_dir();
std::fs::create_dir_all(&dir)?;
Ok(dir)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_home_uses_override_then_home() {
assert_eq!(
resolve_home(Some("/custom".into()), Some(PathBuf::from("/h"))),
PathBuf::from("/custom")
);
assert_eq!(
resolve_home(None, Some(PathBuf::from("/h"))),
PathBuf::from("/h/.codecora")
);
}
#[test]
fn unit_tests_never_resolve_into_the_real_home() {
if std::env::var_os(CODECORA_HOME_ENV).is_some() {
return; }
let real = dirs::home_dir().unwrap().join(".codecora");
for p in [
codecora_home(),
cora_data_dir(),
product_data_dir("x"),
graph_db_path(),
] {
assert!(!p.starts_with(&real), "{p:?} is under the real home");
assert!(p.starts_with(test_home()), "{p:?} is not under test home");
}
}
#[test]
fn product_dirs_are_nested_under_home() {
assert_eq!(
product_data_dir("cora-code"),
codecora_home().join("cora-code")
);
assert_eq!(cora_data_dir(), product_data_dir("cora-code"));
let db = graph_db_path();
assert!(
db.ends_with("cora.db") || db.ends_with("graph.db"),
"{db:?}"
);
}
}