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 + ?Sized>(
52 data: &T,
53) -> Result<(), Box<dyn std::error::Error>> {
54 let file_path = get_file_path();
55 let path = Path::new(&file_path);
56
57 let parent_dir = path.parent().unwrap_or(Path::new("."));
59
60 let json = serde_json::to_string_pretty(data)?;
63
64 let mut temp_file = NamedTempFile::new_in(parent_dir)?;
66
67 temp_file.write_all(json.as_bytes())?;
69 temp_file.flush()?;
70
71 temp_file.as_file().sync_all()?;
73
74 temp_file.persist(file_path)?;
77
78 Ok(())
79}
80
81pub fn read_json_from_file<T: serde::de::DeserializeOwned>() -> Result<T, Box<dyn std::error::Error>>
88{
89 let file_path = get_file_path();
90
91 let mut file = match File::open(file_path) {
93 Ok(f) => f,
94 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
95 return Err(Box::new(std::io::Error::new(
96 std::io::ErrorKind::NotFound,
97 format!("File not found: {}", file_path),
98 )));
99 }
100 Err(e) => return Err(Box::new(e)),
101 };
102
103 let mut json = String::new();
104 file.read_to_string(&mut json)?;
105
106 let data = serde_json::from_str(&json)?;
107 Ok(data)
108}
109
110pub fn confirm(prompt: &str) -> bool {
111 eprint!("{} (y/n): ", prompt);
112 io::stdout().flush().ok();
113 let mut response = String::new();
114 std::io::stdin().read_line(&mut response).ok();
115 response.to_lowercase().starts_with('y')
116}
117
118#[cfg(test)]
119pub mod tests {
120
121 use serial_test::serial;
122
123 #[test]
124 #[serial]
125 fn test_serial_01_read_write_json_from_file() {
126 let home = super::home_dir();
127
128 assert!(!home.is_empty());
129
130 std::env::set_var("COMAN_JSON", "test.json");
131
132 let path = "test.json".to_string();
133
134 assert_eq!(super::get_file_path(), path);
135
136 let result: Result<Vec<crate::models::collection::Collection>, Box<dyn std::error::Error>> =
137 super::read_json_from_file();
138
139 if let Err(e) = &result {
140 println!("Error: {}", e);
141 }
142
143 assert!(result.is_ok());
144
145 let result = super::write_json_to_file(&result.unwrap());
146
147 assert!(result.is_ok());
148 }
149}