1use std::{
2 env,
3 fs::File,
4 io::{self, Read, Write},
5 path::Path,
6};
7
8use std::sync::OnceLock;
9use tempfile::NamedTempFile;
10
11pub static COMAN_FILE: &str = "coman.json";
12
13pub fn home_dir() -> &'static str {
14 static CACHE: OnceLock<String> = OnceLock::new();
15
16 CACHE.get_or_init(|| {
17 env::var("HOME")
18 .or_else(|_| env::var("USERPROFILE"))
19 .unwrap_or("/".to_string())
20 })
21}
22
23pub fn coman_json() -> &'static str {
24 static CACHE: OnceLock<String> = OnceLock::new();
25
26 CACHE.get_or_init(|| env::var("COMAN_JSON").unwrap_or_else(|_| COMAN_FILE.to_string()))
27}
28
29pub fn get_file_path() -> &'static str {
30 static CACHE: OnceLock<&'static str> = OnceLock::new();
31
32 CACHE.get_or_init(|| {
33 let json_path = coman_json();
34 if json_path != COMAN_FILE {
36 json_path
37 } else {
38 Box::leak(format!("{}/{}", home_dir(), json_path).into_boxed_str())
40 }
41 })
42}
43
44pub fn write_json_to_file<T: serde::Serialize>(data: &T) -> Result<(), Box<dyn std::error::Error>> {
52 let file_path = get_file_path();
53 let path = Path::new(&file_path);
54
55 let parent_dir = path.parent().unwrap_or(Path::new("."));
57
58 let json = serde_json::to_string_pretty(data)?;
61
62 let mut temp_file = NamedTempFile::new_in(parent_dir)?;
64
65 temp_file.write_all(json.as_bytes())?;
67 temp_file.flush()?;
68
69 temp_file.as_file().sync_all()?;
71
72 temp_file.persist(file_path)?;
75
76 Ok(())
77}
78
79pub fn read_json_from_file<T: serde::de::DeserializeOwned>() -> Result<T, Box<dyn std::error::Error>>
86{
87 let file_path = get_file_path();
88
89 let mut file = match File::open(file_path) {
91 Ok(f) => f,
92 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
93 return Err(Box::new(std::io::Error::new(
94 std::io::ErrorKind::NotFound,
95 format!("File not found: {}", file_path),
96 )));
97 }
98 Err(e) => return Err(Box::new(e)),
99 };
100
101 let mut json = String::new();
102 file.read_to_string(&mut json)?;
103
104 let data = serde_json::from_str(&json)?;
105 Ok(data)
106}
107
108pub fn confirm(prompt: &str) -> bool {
109 eprint!("{} (y/n): ", prompt);
110 io::stdout().flush().ok();
111 let mut response = String::new();
112 std::io::stdin().read_line(&mut response).ok();
113 response.to_lowercase().starts_with('y')
114}
115
116#[cfg(test)]
117pub mod tests {
118
119 use serial_test::serial;
120
121 #[test]
122 #[serial]
123 fn test_serial_01_read_write_json_from_file() {
124 let home = super::home_dir();
125
126 assert!(!home.is_empty());
127
128 std::env::set_var("COMAN_JSON", "test.json");
129
130 let path = "test.json".to_string();
131
132 assert_eq!(super::get_file_path(), path);
133
134 let result: Result<Vec<crate::models::collection::Collection>, Box<dyn std::error::Error>> =
135 super::read_json_from_file();
136
137 if let Err(e) = &result {
138 println!("Error: {}", e);
139 }
140
141 assert!(result.is_ok());
142
143 let result = super::write_json_to_file(&result.unwrap());
144
145 assert!(result.is_ok());
146 }
147}