use anyhow::{Context, Result};
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct PathConfig {
pub config_dir: Option<PathBuf>,
}
impl PathConfig {
pub fn from_env_and_cli(cli_dir: Option<PathBuf>) -> Self {
let config_dir = cli_dir.or_else(|| {
std::env::var("PLAYA_CONFIG_DIR")
.ok()
.map(PathBuf::from)
});
Self { config_dir }
}
}
pub fn config_file(name: &str, config: &PathConfig) -> PathBuf {
get_config_dir(config).join(name)
}
pub fn data_file(name: &str, config: &PathConfig) -> PathBuf {
get_data_dir(config).join(name)
}
pub fn ensure_dirs(config: &PathConfig) -> Result<()> {
let config_dir = get_config_dir(config);
let data_dir = get_data_dir(config);
if !config_dir.exists() {
std::fs::create_dir_all(&config_dir)
.with_context(|| format!("Failed to create config directory: {}", config_dir.display()))?;
}
if data_dir != config_dir && !data_dir.exists() {
std::fs::create_dir_all(&data_dir)
.with_context(|| format!("Failed to create data directory: {}", data_dir.display()))?;
}
Ok(())
}
fn has_local_config_files(dir: &PathBuf) -> bool {
let files = ["playa.json", "playa_cache.json", "playa.log"];
files.iter().any(|f| dir.join(f).exists())
}
fn get_config_dir(config: &PathConfig) -> PathBuf {
if let Some(dir) = &config.config_dir {
return dir.clone();
}
if let Ok(current_dir) = std::env::current_dir() {
if has_local_config_files(¤t_dir) {
return current_dir;
}
}
if let Some(dir) = dirs_next::config_dir() {
return dir.join("playa");
}
PathBuf::from(".")
}
fn get_data_dir(config: &PathConfig) -> PathBuf {
if let Some(dir) = &config.config_dir {
return dir.clone();
}
if let Ok(current_dir) = std::env::current_dir() {
if has_local_config_files(¤t_dir) {
return current_dir;
}
}
if let Some(dir) = dirs_next::data_dir() {
return dir.join("playa");
}
PathBuf::from(".")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_file_with_custom_dir() {
let config = PathConfig {
config_dir: Some(PathBuf::from("/custom")),
};
let path = config_file("test.json", &config);
assert_eq!(path, PathBuf::from("/custom/test.json"));
}
#[test]
fn test_data_file_with_custom_dir() {
let config = PathConfig {
config_dir: Some(PathBuf::from("/custom")),
};
let path = data_file("cache.json", &config);
assert_eq!(path, PathBuf::from("/custom/cache.json"));
}
#[test]
fn test_config_file_uses_platform_defaults() {
let config = PathConfig { config_dir: None };
let path = config_file("test.json", &config);
assert!(path.to_string_lossy().contains("playa"));
assert!(path.to_string_lossy().contains("test.json"));
}
#[test]
fn test_show_actual_paths() {
println!("\n=== Platform-specific paths (no local files) ===");
let config = PathConfig { config_dir: None };
let cfg_path = config_file("playa.json", &config);
let cache_path = data_file("playa_cache.json", &config);
let log_path = data_file("playa.log", &config);
println!("Config file: {}", cfg_path.display());
println!("Cache file: {}", cache_path.display());
println!("Log file: {}", log_path.display());
println!("\n=== With custom directory ===");
let custom_config = PathConfig {
config_dir: Some(PathBuf::from("/tmp/playa-test")),
};
println!("Config file: {}", config_file("playa.json", &custom_config).display());
println!("Cache file: {}", data_file("playa_cache.json", &custom_config).display());
println!("Log file: {}", data_file("playa.log", &custom_config).display());
}
#[test]
fn test_local_files_priority() {
use std::fs;
let temp_dir = std::env::temp_dir().join("playa_test_local");
let _ = fs::create_dir_all(&temp_dir);
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&temp_dir).unwrap();
let config = PathConfig { config_dir: None };
let path_without_local = config_file("playa.json", &config);
println!("\nWithout local files: {}", path_without_local.display());
assert!(path_without_local.to_string_lossy().contains("playa"));
assert!(!path_without_local.starts_with(&temp_dir));
fs::write(temp_dir.join("playa.json"), "{}").unwrap();
let path_with_local = config_file("playa.json", &config);
println!("With local playa.json: {}", path_with_local.display());
let canonical_result = path_with_local.canonicalize().unwrap();
let canonical_expected = temp_dir.join("playa.json").canonicalize().unwrap();
assert_eq!(canonical_result, canonical_expected);
std::env::set_current_dir(&original_dir).unwrap();
let _ = fs::remove_dir_all(&temp_dir);
}
}