envelope_cli/config/
paths.rs1use std::path::PathBuf;
7
8use directories::ProjectDirs;
9
10use crate::error::EnvelopeError;
11
12#[derive(Debug, Clone)]
14pub struct EnvelopePaths {
15 base_dir: PathBuf,
17}
18
19impl EnvelopePaths {
20 pub fn new() -> Result<Self, EnvelopeError> {
26 let base_dir = if let Some(proj_dirs) = ProjectDirs::from("", "", "envelope") {
27 proj_dirs.data_dir().to_path_buf()
28 } else {
29 dirs_fallback()?
31 };
32
33 Ok(Self { base_dir })
34 }
35
36 pub fn with_base_dir(base_dir: PathBuf) -> Self {
38 Self { base_dir }
39 }
40
41 pub fn base_dir(&self) -> &PathBuf {
43 &self.base_dir
44 }
45
46 pub fn config_dir(&self) -> PathBuf {
48 self.base_dir.clone()
49 }
50
51 pub fn data_dir(&self) -> PathBuf {
53 self.base_dir.join("data")
54 }
55
56 pub fn backup_dir(&self) -> PathBuf {
58 self.base_dir.join("backups")
59 }
60
61 pub fn settings_file(&self) -> PathBuf {
63 self.base_dir.join("config.json")
64 }
65
66 pub fn audit_log(&self) -> PathBuf {
68 self.base_dir.join("audit.log")
69 }
70
71 pub fn accounts_file(&self) -> PathBuf {
73 self.data_dir().join("accounts.json")
74 }
75
76 pub fn transactions_file(&self) -> PathBuf {
78 self.data_dir().join("transactions.json")
79 }
80
81 pub fn budget_file(&self) -> PathBuf {
83 self.data_dir().join("budget.json")
84 }
85
86 pub fn allocations_file(&self) -> PathBuf {
88 self.data_dir().join("allocations.json")
89 }
90
91 pub fn payees_file(&self) -> PathBuf {
93 self.data_dir().join("payees.json")
94 }
95
96 pub fn targets_file(&self) -> PathBuf {
98 self.data_dir().join("targets.json")
99 }
100
101 pub fn ensure_directories(&self) -> Result<(), EnvelopeError> {
108 std::fs::create_dir_all(&self.base_dir)
109 .map_err(|e| EnvelopeError::Io(format!("Failed to create base directory: {}", e)))?;
110
111 std::fs::create_dir_all(self.data_dir())
112 .map_err(|e| EnvelopeError::Io(format!("Failed to create data directory: {}", e)))?;
113
114 std::fs::create_dir_all(self.backup_dir())
115 .map_err(|e| EnvelopeError::Io(format!("Failed to create backup directory: {}", e)))?;
116
117 Ok(())
118 }
119
120 pub fn is_initialized(&self) -> bool {
122 self.settings_file().exists()
123 }
124}
125
126fn dirs_fallback() -> Result<PathBuf, EnvelopeError> {
128 let home = std::env::var("HOME")
129 .map_err(|_| EnvelopeError::Config("Could not determine home directory".into()))?;
130 Ok(PathBuf::from(home).join(".envelope"))
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use tempfile::TempDir;
137
138 #[test]
139 fn test_custom_base_dir() {
140 let temp_dir = TempDir::new().unwrap();
141 let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
142
143 assert_eq!(paths.base_dir(), temp_dir.path());
144 assert_eq!(paths.data_dir(), temp_dir.path().join("data"));
145 assert_eq!(paths.backup_dir(), temp_dir.path().join("backups"));
146 }
147
148 #[test]
149 fn test_ensure_directories() {
150 let temp_dir = TempDir::new().unwrap();
151 let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
152
153 paths.ensure_directories().unwrap();
154
155 assert!(paths.data_dir().exists());
156 assert!(paths.backup_dir().exists());
157 }
158
159 #[test]
160 fn test_file_paths() {
161 let temp_dir = TempDir::new().unwrap();
162 let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
163
164 assert_eq!(paths.settings_file(), temp_dir.path().join("config.json"));
165 assert_eq!(
166 paths.accounts_file(),
167 temp_dir.path().join("data").join("accounts.json")
168 );
169 }
170}