envelope_cli/config/
paths.rs1use std::path::PathBuf;
12
13use crate::error::EnvelopeError;
14
15#[derive(Debug, Clone)]
17pub struct EnvelopePaths {
18 base_dir: PathBuf,
20}
21
22impl EnvelopePaths {
23 pub fn new() -> Result<Self, EnvelopeError> {
34 let base_dir = if let Ok(custom) = std::env::var("ENVELOPE_CLI_DATA_DIR") {
35 PathBuf::from(custom)
36 } else {
37 resolve_default_path()?
38 };
39
40 Ok(Self { base_dir })
41 }
42
43 pub fn with_base_dir(base_dir: PathBuf) -> Self {
45 Self { base_dir }
46 }
47
48 pub fn base_dir(&self) -> &PathBuf {
50 &self.base_dir
51 }
52
53 pub fn config_dir(&self) -> PathBuf {
55 self.base_dir.clone()
56 }
57
58 pub fn data_dir(&self) -> PathBuf {
60 self.base_dir.join("data")
61 }
62
63 pub fn backup_dir(&self) -> PathBuf {
65 self.base_dir.join("backups")
66 }
67
68 pub fn settings_file(&self) -> PathBuf {
70 self.base_dir.join("config.json")
71 }
72
73 pub fn audit_log(&self) -> PathBuf {
75 self.base_dir.join("audit.log")
76 }
77
78 pub fn accounts_file(&self) -> PathBuf {
80 self.data_dir().join("accounts.json")
81 }
82
83 pub fn transactions_file(&self) -> PathBuf {
85 self.data_dir().join("transactions.json")
86 }
87
88 pub fn budget_file(&self) -> PathBuf {
90 self.data_dir().join("budget.json")
91 }
92
93 pub fn allocations_file(&self) -> PathBuf {
95 self.data_dir().join("allocations.json")
96 }
97
98 pub fn payees_file(&self) -> PathBuf {
100 self.data_dir().join("payees.json")
101 }
102
103 pub fn targets_file(&self) -> PathBuf {
105 self.data_dir().join("targets.json")
106 }
107
108 pub fn income_file(&self) -> PathBuf {
110 self.data_dir().join("income.json")
111 }
112
113 pub fn ensure_directories(&self) -> Result<(), EnvelopeError> {
120 std::fs::create_dir_all(&self.base_dir)
121 .map_err(|e| EnvelopeError::Io(format!("Failed to create base directory: {}", e)))?;
122
123 std::fs::create_dir_all(self.data_dir())
124 .map_err(|e| EnvelopeError::Io(format!("Failed to create data directory: {}", e)))?;
125
126 std::fs::create_dir_all(self.backup_dir())
127 .map_err(|e| EnvelopeError::Io(format!("Failed to create backup directory: {}", e)))?;
128
129 Ok(())
130 }
131
132 pub fn is_initialized(&self) -> bool {
134 self.settings_file().exists()
135 }
136}
137
138#[cfg(not(windows))]
140fn resolve_default_path() -> Result<PathBuf, EnvelopeError> {
141 let config_base = std::env::var("XDG_CONFIG_HOME")
143 .map(PathBuf::from)
144 .unwrap_or_else(|_| {
145 let home = std::env::var("HOME").expect("HOME environment variable not set");
146 PathBuf::from(home).join(".config")
147 });
148 Ok(config_base.join("envelope-cli"))
149}
150
151#[cfg(windows)]
153fn resolve_default_path() -> Result<PathBuf, EnvelopeError> {
154 let appdata = std::env::var("APPDATA")
156 .map_err(|_| EnvelopeError::Config("Could not determine APPDATA directory".into()))?;
157 Ok(PathBuf::from(appdata).join("envelope-cli"))
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use std::env;
164 use tempfile::TempDir;
165
166 #[test]
167 fn test_custom_base_dir() {
168 let temp_dir = TempDir::new().unwrap();
169 let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
170
171 assert_eq!(paths.base_dir(), temp_dir.path());
172 assert_eq!(paths.data_dir(), temp_dir.path().join("data"));
173 assert_eq!(paths.backup_dir(), temp_dir.path().join("backups"));
174 }
175
176 #[test]
177 fn test_env_var_override() {
178 let temp_dir = TempDir::new().unwrap();
179 let custom_path = temp_dir.path().to_str().unwrap();
180
181 env::set_var("ENVELOPE_CLI_DATA_DIR", custom_path);
183
184 let paths = EnvelopePaths::new().unwrap();
185 assert_eq!(paths.base_dir(), temp_dir.path());
186
187 env::remove_var("ENVELOPE_CLI_DATA_DIR");
189 }
190
191 #[test]
192 fn test_ensure_directories() {
193 let temp_dir = TempDir::new().unwrap();
194 let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
195
196 paths.ensure_directories().unwrap();
197
198 assert!(paths.data_dir().exists());
199 assert!(paths.backup_dir().exists());
200 }
201
202 #[test]
203 fn test_file_paths() {
204 let temp_dir = TempDir::new().unwrap();
205 let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
206
207 assert_eq!(paths.settings_file(), temp_dir.path().join("config.json"));
208 assert_eq!(
209 paths.accounts_file(),
210 temp_dir.path().join("data").join("accounts.json")
211 );
212 }
213}