1use crate::{Cli, ClientType, detect_client, detect_installed_clients};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
8pub enum ScanMode {
9 Paths(Vec<PathBuf>),
11 AllClients,
13 SingleClient(ClientType),
15}
16
17impl ScanMode {
18 pub fn from_cli(cli: &Cli) -> Self {
20 if cli.all_clients {
21 ScanMode::AllClients
22 } else if let Some(client) = cli.client {
23 ScanMode::SingleClient(client)
24 } else {
25 ScanMode::Paths(cli.paths.clone())
26 }
27 }
28}
29
30pub fn resolve_scan_paths(cli: &Cli) -> Vec<PathBuf> {
32 let mode = ScanMode::from_cli(cli);
33
34 match mode {
35 ScanMode::Paths(paths) => {
36 if paths.is_empty() {
37 vec![PathBuf::from(".")]
39 } else {
40 paths
41 }
42 }
43 ScanMode::AllClients => {
44 let clients = detect_installed_clients();
45 if clients.is_empty() {
46 eprintln!("No AI coding clients detected on this system.");
47 return Vec::new();
48 }
49
50 let mut paths = Vec::new();
51 for client in &clients {
52 eprintln!(
53 "Detected {}: {}",
54 client.client_type.display_name(),
55 client.home_dir.display()
56 );
57 paths.extend(client.all_configs());
58 }
59 paths
60 }
61 ScanMode::SingleClient(client_type) => match detect_client(client_type) {
62 Some(client) => {
63 eprintln!(
64 "Scanning {}: {}",
65 client.client_type.display_name(),
66 client.home_dir.display()
67 );
68 client.all_configs()
69 }
70 None => {
71 eprintln!(
72 "{} is not installed or has no configuration files.",
73 client_type.display_name()
74 );
75 Vec::new()
76 }
77 },
78 }
79}
80
81pub fn detect_client_for_path(path: &str) -> Option<String> {
83 for client_type in ClientType::all() {
84 if let Some(home) = client_type.home_dir() {
85 let home_str = home.display().to_string();
86 if path.starts_with(&home_str) {
87 return Some(client_type.display_name().to_string());
88 }
89 }
90 }
91 None
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_scan_mode_from_cli_paths() {
100 let cli = crate::Cli {
101 paths: vec![PathBuf::from("/test/path")],
102 all_clients: false,
103 client: None,
104 ..Default::default()
105 };
106 match ScanMode::from_cli(&cli) {
107 ScanMode::Paths(paths) => assert_eq!(paths, vec![PathBuf::from("/test/path")]),
108 _ => panic!("Expected ScanMode::Paths"),
109 }
110 }
111
112 #[test]
113 fn test_scan_mode_from_cli_all_clients() {
114 let cli = crate::Cli {
115 paths: vec![],
116 all_clients: true,
117 client: None,
118 ..Default::default()
119 };
120 assert!(matches!(ScanMode::from_cli(&cli), ScanMode::AllClients));
121 }
122
123 #[test]
124 fn test_scan_mode_from_cli_single_client() {
125 let cli = crate::Cli {
126 paths: vec![],
127 all_clients: false,
128 client: Some(ClientType::Claude),
129 ..Default::default()
130 };
131 match ScanMode::from_cli(&cli) {
132 ScanMode::SingleClient(client) => assert_eq!(client, ClientType::Claude),
133 _ => panic!("Expected ScanMode::SingleClient"),
134 }
135 }
136
137 #[test]
138 fn test_scan_mode_debug() {
139 let mode = ScanMode::Paths(vec![PathBuf::from("./test")]);
140 let debug_str = format!("{:?}", mode);
141 assert!(debug_str.contains("Paths"));
142
143 let mode2 = ScanMode::AllClients;
144 let debug_str2 = format!("{:?}", mode2);
145 assert!(debug_str2.contains("AllClients"));
146
147 let mode3 = ScanMode::SingleClient(ClientType::Claude);
148 let debug_str3 = format!("{:?}", mode3);
149 assert!(debug_str3.contains("SingleClient"));
150 }
151
152 #[test]
153 fn test_resolve_scan_paths_empty() {
154 let cli = crate::Cli {
155 paths: vec![],
156 all_clients: false,
157 client: None,
158 ..Default::default()
159 };
160 let paths = resolve_scan_paths(&cli);
161 assert_eq!(paths, vec![PathBuf::from(".")]);
162 }
163
164 #[test]
165 fn test_resolve_scan_paths_with_paths() {
166 let cli = crate::Cli {
167 paths: vec![PathBuf::from("/test/path1"), PathBuf::from("/test/path2")],
168 all_clients: false,
169 client: None,
170 ..Default::default()
171 };
172 let paths = resolve_scan_paths(&cli);
173 assert_eq!(
174 paths,
175 vec![PathBuf::from("/test/path1"), PathBuf::from("/test/path2")]
176 );
177 }
178
179 #[test]
180 fn test_detect_client_for_path_unknown() {
181 let result = detect_client_for_path("/some/random/path");
182 let _ = result;
185 }
186
187 #[test]
188 fn test_scan_mode_clone() {
189 let mode = ScanMode::AllClients;
190 let cloned = mode.clone();
191 assert!(matches!(cloned, ScanMode::AllClients));
192 }
193
194 #[test]
195 fn test_resolve_scan_paths_all_clients() {
196 let cli = crate::Cli {
197 paths: vec![],
198 all_clients: true,
199 client: None,
200 ..Default::default()
201 };
202 let _paths = resolve_scan_paths(&cli);
205 }
206
207 #[test]
208 fn test_resolve_scan_paths_single_client_claude() {
209 let cli = crate::Cli {
210 paths: vec![],
211 all_clients: false,
212 client: Some(ClientType::Claude),
213 ..Default::default()
214 };
215 let _paths = resolve_scan_paths(&cli);
218 }
219
220 #[test]
221 fn test_resolve_scan_paths_single_client_cursor() {
222 let cli = crate::Cli {
223 paths: vec![],
224 all_clients: false,
225 client: Some(ClientType::Cursor),
226 ..Default::default()
227 };
228 let _paths = resolve_scan_paths(&cli);
229 }
230
231 #[test]
232 fn test_resolve_scan_paths_single_client_windsurf() {
233 let cli = crate::Cli {
234 paths: vec![],
235 all_clients: false,
236 client: Some(ClientType::Windsurf),
237 ..Default::default()
238 };
239 let _paths = resolve_scan_paths(&cli);
240 }
241
242 #[test]
243 fn test_resolve_scan_paths_single_client_vscode() {
244 let cli = crate::Cli {
245 paths: vec![],
246 all_clients: false,
247 client: Some(ClientType::Vscode),
248 ..Default::default()
249 };
250 let _paths = resolve_scan_paths(&cli);
251 }
252
253 #[test]
254 fn test_detect_client_for_path_all_clients() {
255 for client_type in ClientType::all() {
257 if let Some(home) = client_type.home_dir() {
258 let test_path = format!("{}/test/file.json", home.display());
259 let result = detect_client_for_path(&test_path);
260 assert!(result.is_some() || result.is_none());
262 }
263 }
264 }
265}