1use std::{io::Write, path::Path};
2
3use crate::PathAction;
4use color_eyre::Result;
5use color_eyre::eyre::eyre;
6use envx_core::{EnvVarManager, PathManager};
7
8#[allow(clippy::too_many_lines)]
28pub fn handle_path_command(action: Option<PathAction>, check: bool, var: &str, permanent: bool) -> Result<()> {
29 let mut manager = EnvVarManager::new();
30 manager.load_all()?;
31
32 let path_var = manager.get(var).ok_or_else(|| eyre!("Variable '{}' not found", var))?;
34
35 let mut path_mgr = PathManager::new(&path_var.value);
36
37 if action.is_none() {
39 if check {
40 handle_path_check(&path_mgr, true);
41 }
42 handle_path_list(&path_mgr, false, false);
43 }
44
45 let command = action.expect("Action should be Some if we reach here");
46 match command {
47 PathAction::Add {
48 directory,
49 first,
50 create,
51 } => {
52 let path = Path::new(&directory);
53
54 if !path.exists() {
56 if create {
57 std::fs::create_dir_all(path)?;
58 println!("Created directory: {directory}");
59 } else if !path.exists() {
60 eprintln!("Warning: Directory does not exist: {directory}");
61 print!("Add anyway? [y/N]: ");
62 std::io::stdout().flush()?;
63
64 let mut input = String::new();
65 std::io::stdin().read_line(&mut input)?;
66
67 if !input.trim().eq_ignore_ascii_case("y") {
68 return Ok(());
69 }
70 }
71 }
72
73 if path_mgr.contains(&directory) {
75 println!("Directory already in {var}: {directory}");
76 return Ok(());
77 }
78
79 if first {
81 path_mgr.add_first(directory.clone());
82 println!("Added to beginning of {var}: {directory}");
83 } else {
84 path_mgr.add_last(directory.clone());
85 println!("Added to end of {var}: {directory}");
86 }
87
88 let new_value = path_mgr.to_string();
90 manager.set(var, &new_value, permanent)?;
91 }
92
93 PathAction::Remove { directory, all } => {
94 let removed = if all {
95 path_mgr.remove_all(&directory)
96 } else {
97 path_mgr.remove_first(&directory)
98 };
99
100 if removed > 0 {
101 println!("Removed {removed} occurrence(s) of: {directory}");
102 let new_value = path_mgr.to_string();
103 manager.set(var, &new_value, permanent)?;
104 } else {
105 println!("Directory not found in {var}: {directory}");
106 }
107 }
108
109 PathAction::Clean { dedupe, dry_run } => {
110 let invalid = path_mgr.get_invalid();
111 let duplicates = if dedupe { path_mgr.get_duplicates() } else { vec![] };
112
113 if invalid.is_empty() && duplicates.is_empty() {
114 println!("No invalid or duplicate entries found in {var}");
115 return Ok(());
116 }
117
118 if !invalid.is_empty() {
119 println!("Invalid/non-existent paths to remove:");
120 for path in &invalid {
121 println!(" - {path}");
122 }
123 }
124
125 if !duplicates.is_empty() {
126 println!("Duplicate paths to remove:");
127 for path in &duplicates {
128 println!(" - {path}");
129 }
130 }
131
132 if dry_run {
133 println!("\n(Dry run - no changes made)");
134 } else {
135 let removed_invalid = path_mgr.remove_invalid();
136 let removed_dupes = if dedupe {
137 path_mgr.deduplicate(false) } else {
139 0
140 };
141
142 println!("Removed {removed_invalid} invalid and {removed_dupes} duplicate entries");
143 let new_value = path_mgr.to_string();
144 manager.set(var, &new_value, permanent)?;
145 }
146 }
147
148 PathAction::Dedupe { keep_first, dry_run } => {
149 let duplicates = path_mgr.get_duplicates();
150
151 if duplicates.is_empty() {
152 println!("No duplicate entries found in {var}");
153 return Ok(());
154 }
155
156 println!("Duplicate paths to remove:");
157 for path in &duplicates {
158 println!(" - {path}");
159 }
160 println!(
161 "Strategy: keep {} occurrence",
162 if keep_first { "first" } else { "last" }
163 );
164
165 if dry_run {
166 println!("\n(Dry run - no changes made)");
167 } else {
168 let removed = path_mgr.deduplicate(keep_first);
169 println!("Removed {removed} duplicate entries");
170 let new_value = path_mgr.to_string();
171 manager.set(var, &new_value, permanent)?;
172 }
173 }
174
175 PathAction::Check { verbose } => {
176 handle_path_check(&path_mgr, verbose);
177 }
178
179 PathAction::List { numbered, check } => {
180 handle_path_list(&path_mgr, numbered, check);
181 }
182
183 PathAction::Move { from, to } => {
184 let from_idx = if let Ok(idx) = from.parse::<usize>() {
186 idx
187 } else {
188 path_mgr
189 .find_index(&from)
190 .ok_or_else(|| eyre!("Path not found: {}", from))?
191 };
192
193 let to_idx = match to.as_str() {
195 "first" => 0,
196 "last" => path_mgr.len() - 1,
197 _ => to.parse::<usize>().map_err(|_| eyre!("Invalid position: {}", to))?,
198 };
199
200 path_mgr.move_entry(from_idx, to_idx)?;
201 println!("Moved entry from position {from_idx} to {to_idx}");
202
203 let new_value = path_mgr.to_string();
204 manager.set(var, &new_value, permanent)?;
205 }
206 }
207
208 Ok(())
209}
210
211fn handle_path_check(path_mgr: &PathManager, verbose: bool) {
212 let entries = path_mgr.entries();
213 let mut issues = Vec::new();
214 let mut valid_count = 0;
215
216 for (idx, entry) in entries.iter().enumerate() {
217 let path = Path::new(entry);
218 let exists = path.exists();
219 let is_dir = path.is_dir();
220
221 if verbose || !exists {
222 let status = if !exists {
223 issues.push(format!("Not found: {entry}"));
224 "❌ NOT FOUND"
225 } else if !is_dir {
226 issues.push(format!("Not a directory: {entry}"));
227 "⚠️ NOT DIR"
228 } else {
229 valid_count += 1;
230 "✓ OK"
231 };
232
233 if verbose {
234 println!("[{idx:3}] {status} - {entry}");
235 }
236 } else if exists && is_dir {
237 valid_count += 1;
238 }
239 }
240
241 println!("\nPATH Analysis:");
243 println!(" Total entries: {}", entries.len());
244 println!(" Valid entries: {valid_count}");
245
246 let duplicates = path_mgr.get_duplicates();
247 if !duplicates.is_empty() {
248 println!(" Duplicates: {} entries", duplicates.len());
249 if verbose {
250 for dup in &duplicates {
251 println!(" - {dup}");
252 }
253 }
254 }
255
256 let invalid = path_mgr.get_invalid();
257 if !invalid.is_empty() {
258 println!(" Invalid entries: {}", invalid.len());
259 if verbose {
260 for inv in &invalid {
261 println!(" - {inv}");
262 }
263 }
264 }
265
266 if issues.is_empty() {
267 println!("\n✅ No issues found!");
268 } else {
269 println!("\n⚠️ {} issue(s) found", issues.len());
270 if !verbose {
271 println!("Run with --verbose for details");
272 }
273 }
274}
275
276fn handle_path_list(path_mgr: &PathManager, numbered: bool, check: bool) {
277 let entries = path_mgr.entries();
278
279 if entries.is_empty() {
280 println!("PATH is empty");
281 }
282
283 for (idx, entry) in entries.iter().enumerate() {
284 let prefix = if numbered { format!("[{idx:3}] ") } else { String::new() };
285
286 let suffix = if check {
287 let path = Path::new(entry);
288 if !path.exists() {
289 " [NOT FOUND]"
290 } else if !path.is_dir() {
291 " [NOT A DIRECTORY]"
292 } else {
293 ""
294 }
295 } else {
296 ""
297 };
298
299 println!("{prefix}{entry}{suffix}");
300 }
301}