intent_engine/setup/
common.rs1use crate::error::{IntentError, Result};
4use serde_json::Value;
5use std::env;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9pub fn resolve_absolute_path(path: &Path) -> Result<PathBuf> {
11 path.canonicalize().or_else(|_| {
12 if path.is_absolute() {
15 Ok(path.to_path_buf())
16 } else {
17 let current_dir = env::current_dir().map_err(IntentError::IoError)?;
18 Ok(current_dir.join(path))
19 }
20 })
21}
22
23pub fn get_home_dir() -> Result<PathBuf> {
25 env::var("HOME")
26 .or_else(|_| env::var("USERPROFILE"))
27 .map(PathBuf::from)
28 .map_err(|_| IntentError::InvalidInput("Cannot determine home directory".to_string()))
29}
30
31pub fn create_backup(file_path: &Path) -> Result<Option<PathBuf>> {
33 if !file_path.exists() {
34 return Ok(None);
35 }
36
37 let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
38 let backup_path = file_path.with_extension(format!(
39 "{}.backup.{}",
40 file_path.extension().and_then(|s| s.to_str()).unwrap_or(""),
41 timestamp
42 ));
43
44 fs::copy(file_path, &backup_path).map_err(IntentError::IoError)?;
45 Ok(Some(backup_path))
46}
47
48pub fn restore_from_backup(backup_path: &Path, original_path: &Path) -> Result<()> {
50 if backup_path.exists() {
51 fs::copy(backup_path, original_path).map_err(IntentError::IoError)?;
52 }
53 Ok(())
54}
55
56pub fn remove_backup(backup_path: &Path) -> Result<()> {
58 if backup_path.exists() {
59 fs::remove_file(backup_path).map_err(IntentError::IoError)?;
60 }
61 Ok(())
62}
63
64pub fn read_json_config(path: &Path) -> Result<Value> {
66 if path.exists() {
67 let content = fs::read_to_string(path).map_err(IntentError::IoError)?;
68 serde_json::from_str(&content)
69 .map_err(|e| IntentError::InvalidInput(format!("Failed to parse JSON config: {}", e)))
70 } else {
71 Ok(serde_json::json!({}))
72 }
73}
74
75pub fn write_json_config(path: &Path, config: &Value) -> Result<()> {
77 if let Some(parent) = path.parent() {
79 fs::create_dir_all(parent).map_err(IntentError::IoError)?;
80 }
81
82 let content = serde_json::to_string_pretty(config)?;
83 fs::write(path, content).map_err(IntentError::IoError)?;
84 Ok(())
85}
86
87pub fn find_ie_binary() -> Result<PathBuf> {
89 if let Ok(current_exe) = env::current_exe() {
92 if let Some(file_name) = current_exe.file_name() {
94 let name = file_name.to_string_lossy();
95 if name == "ie"
96 || name.starts_with("ie.")
97 || name == "intent-engine"
98 || name.starts_with("intent-engine.")
99 {
100 return Ok(current_exe);
101 }
102 }
103 }
104
105 if let Ok(path) = env::var("CARGO_BIN_EXE_ie") {
107 let binary = PathBuf::from(path);
108 if binary.exists() {
109 return Ok(binary);
110 }
111 }
112
113 which::which("ie")
115 .or_else(|_| {
116 let home = get_home_dir()?;
118 let cargo_bin = home.join(".cargo").join("bin").join("ie");
119 if cargo_bin.exists() {
120 Ok(cargo_bin)
121 } else {
122 Err(IntentError::InvalidInput(
123 "ie binary not found in PATH or ~/.cargo/bin".to_string(),
124 ))
125 }
126 })
127 .or_else(|_| {
128 let candidate_paths = vec![
130 PathBuf::from("./target/debug/ie"),
131 PathBuf::from("./target/release/ie"),
132 PathBuf::from("../target/debug/ie"),
133 PathBuf::from("../target/release/ie"),
134 ];
135
136 for path in candidate_paths {
137 if path.exists() {
138 return Ok(path);
139 }
140 }
141
142 Err(IntentError::InvalidInput(
143 "ie binary not found in relative paths".to_string(),
144 ))
145 })
146 .or_else(|_| {
147 which::which("intent-engine").map_err(|_| {
149 IntentError::InvalidInput(
150 "intent-engine binary not found. Please install with: cargo install intent-engine".to_string()
151 )
152 })
153 })
154}
155
156#[cfg(unix)]
158pub fn set_executable(path: &Path) -> Result<()> {
159 use std::os::unix::fs::PermissionsExt;
160 let mut perms = fs::metadata(path)
161 .map_err(IntentError::IoError)?
162 .permissions();
163 perms.set_mode(0o755);
164 fs::set_permissions(path, perms).map_err(IntentError::IoError)?;
165 Ok(())
166}
167
168#[cfg(not(unix))]
170pub fn set_executable(_path: &Path) -> Result<()> {
171 Ok(())
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use std::fs;
178 use tempfile::TempDir;
179
180 #[test]
181 fn test_resolve_absolute_path() {
182 let _temp = TempDir::new().unwrap();
183 let rel_path = PathBuf::from("test.txt");
184 let abs_path = resolve_absolute_path(&rel_path).unwrap();
185 assert!(abs_path.is_absolute());
186 }
187
188 #[test]
189 fn test_backup_and_restore() {
190 let temp = TempDir::new().unwrap();
191 let file_path = temp.path().join("test.json");
192 fs::write(&file_path, "original content").unwrap();
193
194 let backup = create_backup(&file_path).unwrap();
196 assert!(backup.is_some());
197 let backup_path = backup.unwrap();
198 assert!(backup_path.exists());
199
200 fs::write(&file_path, "modified content").unwrap();
202
203 restore_from_backup(&backup_path, &file_path).unwrap();
205 let content = fs::read_to_string(&file_path).unwrap();
206 assert_eq!(content, "original content");
207
208 remove_backup(&backup_path).unwrap();
210 assert!(!backup_path.exists());
211 }
212
213 #[test]
214 fn test_json_config_ops() {
215 let temp = TempDir::new().unwrap();
216 let config_path = temp.path().join("config.json");
217
218 let config = read_json_config(&config_path).unwrap();
220 assert_eq!(config, serde_json::json!({}));
221
222 let test_config = serde_json::json!({
224 "key": "value",
225 "number": 42
226 });
227 write_json_config(&config_path, &test_config).unwrap();
228 assert!(config_path.exists());
229
230 let read_config = read_json_config(&config_path).unwrap();
232 assert_eq!(read_config, test_config);
233 }
234}