use std::path::PathBuf;
use crate::error::EnvelopeError;
#[derive(Debug, Clone)]
pub struct EnvelopePaths {
base_dir: PathBuf,
}
impl EnvelopePaths {
pub fn new() -> Result<Self, EnvelopeError> {
let base_dir = if let Ok(custom) = std::env::var("ENVELOPE_CLI_DATA_DIR") {
PathBuf::from(custom)
} else {
resolve_default_path()?
};
Ok(Self { base_dir })
}
pub fn with_base_dir(base_dir: PathBuf) -> Self {
Self { base_dir }
}
pub fn base_dir(&self) -> &PathBuf {
&self.base_dir
}
pub fn config_dir(&self) -> PathBuf {
self.base_dir.clone()
}
pub fn data_dir(&self) -> PathBuf {
self.base_dir.join("data")
}
pub fn backup_dir(&self) -> PathBuf {
self.base_dir.join("backups")
}
pub fn settings_file(&self) -> PathBuf {
self.base_dir.join("config.json")
}
pub fn audit_log(&self) -> PathBuf {
self.base_dir.join("audit.log")
}
pub fn accounts_file(&self) -> PathBuf {
self.data_dir().join("accounts.json")
}
pub fn transactions_file(&self) -> PathBuf {
self.data_dir().join("transactions.json")
}
pub fn budget_file(&self) -> PathBuf {
self.data_dir().join("budget.json")
}
pub fn allocations_file(&self) -> PathBuf {
self.data_dir().join("allocations.json")
}
pub fn payees_file(&self) -> PathBuf {
self.data_dir().join("payees.json")
}
pub fn targets_file(&self) -> PathBuf {
self.data_dir().join("targets.json")
}
pub fn income_file(&self) -> PathBuf {
self.data_dir().join("income.json")
}
pub fn ensure_directories(&self) -> Result<(), EnvelopeError> {
std::fs::create_dir_all(&self.base_dir)
.map_err(|e| EnvelopeError::Io(format!("Failed to create base directory: {}", e)))?;
std::fs::create_dir_all(self.data_dir())
.map_err(|e| EnvelopeError::Io(format!("Failed to create data directory: {}", e)))?;
std::fs::create_dir_all(self.backup_dir())
.map_err(|e| EnvelopeError::Io(format!("Failed to create backup directory: {}", e)))?;
Ok(())
}
pub fn is_initialized(&self) -> bool {
self.settings_file().exists()
}
}
#[cfg(not(windows))]
fn resolve_default_path() -> Result<PathBuf, EnvelopeError> {
let config_base = std::env::var("XDG_CONFIG_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let home = std::env::var("HOME").expect("HOME environment variable not set");
PathBuf::from(home).join(".config")
});
Ok(config_base.join("envelope-cli"))
}
#[cfg(windows)]
fn resolve_default_path() -> Result<PathBuf, EnvelopeError> {
let appdata = std::env::var("APPDATA")
.map_err(|_| EnvelopeError::Config("Could not determine APPDATA directory".into()))?;
Ok(PathBuf::from(appdata).join("envelope-cli"))
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use tempfile::TempDir;
#[test]
fn test_custom_base_dir() {
let temp_dir = TempDir::new().unwrap();
let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
assert_eq!(paths.base_dir(), temp_dir.path());
assert_eq!(paths.data_dir(), temp_dir.path().join("data"));
assert_eq!(paths.backup_dir(), temp_dir.path().join("backups"));
}
#[test]
fn test_env_var_override() {
let temp_dir = TempDir::new().unwrap();
let custom_path = temp_dir.path().to_str().unwrap();
env::set_var("ENVELOPE_CLI_DATA_DIR", custom_path);
let paths = EnvelopePaths::new().unwrap();
assert_eq!(paths.base_dir(), temp_dir.path());
env::remove_var("ENVELOPE_CLI_DATA_DIR");
}
#[test]
fn test_ensure_directories() {
let temp_dir = TempDir::new().unwrap();
let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
paths.ensure_directories().unwrap();
assert!(paths.data_dir().exists());
assert!(paths.backup_dir().exists());
}
#[test]
fn test_file_paths() {
let temp_dir = TempDir::new().unwrap();
let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
assert_eq!(paths.settings_file(), temp_dir.path().join("config.json"));
assert_eq!(
paths.accounts_file(),
temp_dir.path().join("data").join("accounts.json")
);
}
}