cargo_e/
e_discovery.rs

1// src/e_discovery.rs
2use std::{
3    fs,
4    path::{Path, PathBuf},
5};
6
7use crate::e_target::{CargoTarget, TargetKind};
8use anyhow::{anyhow, Context, Result};
9
10/// Discover targets in the given directory.
11/// This function scans for a Cargo.toml and then looks for example files or subproject directories.
12// pub fn discover_targets(current_dir: &Path) -> Result<Vec<CargoTarget>> {
13//     let targets = Vec::new();
14//     return Ok(targets);
15//     let parent = current_dir.parent().expect("expected cwd to have a parent");
16//     // Check if a Cargo.toml exists in the current directory.
17//     let manifest_path = current_dir.join("Cargo.toml");
18//     // if manifest_path.exists() {
19//     //     // Check for Tauri: if "src-tauri" folder and "package.json" exist in the same directory.
20//     //     let tauri_folder = current_dir.join("src-tauri");
21//     //     let tauri_config = current_dir.join("tauri.conf.json");
22//     //     let target_kind = if tauri_folder.exists() || tauri_config.exists() {
23//     //         debug!("FOUND TAURI {}",manifest_path.display());
24//     //         TargetKind::ManifestTauri
25//     //     } else {
26//     //         // default kind for a manifest target (or you could use a different variant)
27//     //         TargetKind::Manifest
28//     //     };
29
30//     //     targets.push(CargoTarget {
31//     //         name: "default".to_string(),
32//     //         display_name: "Default Manifest".to_string(),
33//     //         manifest_path: manifest_path.to_string_lossy().to_string(),
34//     //         kind: target_kind,
35//     //         extended: false,
36//     //         origin: None,
37//     //     });
38//     // }
39
40//     // Scan the "examples" directory for example targets.
41//     let examples_dir = current_dir.join("examples");
42//     if examples_dir.exists() && examples_dir.is_dir() {
43//         for entry in fs::read_dir(&examples_dir)
44//             .with_context(|| format!("Reading directory {:?}", examples_dir))?
45//         {
46//             let entry = entry?;
47//             let path = entry.path();
48//             if path.is_file() {
49//                 // Assume that any .rs file in examples/ is an example.
50//                 if let Some(ext) = path.extension() {
51//                     if ext == "rs" {
52//                         if let Some(stem) = path.file_stem() {
53//                             // Read the file's contents
54//                             let file_contents = std::fs::read_to_string(&path).unwrap_or_default();
55
56//                             // Check for dioxus-specific markers
57//                             let target_kind = if file_contents.contains("dioxus::LaunchBuilder")
58//                                 || file_contents.contains("dioxus::launch")
59//                             {
60//                                 TargetKind::ManifestDioxusExample
61//                             } else if file_contents.contains("fn main") {
62//                                 TargetKind::Example
63//                             } else {
64//                                 continue;
65//                             };
66//                             targets.push(CargoTarget {
67//                                 name: stem.to_string_lossy().to_string(),
68//                                 display_name: stem.to_string_lossy().to_string(),
69//                                 manifest_path: current_dir.join("Cargo.toml"),
70//                                 kind: target_kind,
71//                                 extended: false,
72//                                 origin: Some(TargetOrigin::SingleFile(path)),
73//                             });
74//                         }
75//                     }
76//                 }
77//             } else if path.is_dir() {
78//                 // If the directory contains a Cargo.toml, treat it as an extended subproject.
79//                 let sub_manifest = path.join("Cargo.toml");
80//                 if sub_manifest.exists() {
81//                     let tauri_folder = path.join("src-tauri");
82//                     let tauri_config = path.join("tauri.conf.json");
83//                     let dioxus_config = path.join("Dioxus.toml");
84
85//                     let target_kind = if tauri_folder.exists() || tauri_config.exists() {
86//                         debug!("FOUND TAURI {}", manifest_path.display());
87//                         TargetKind::ManifestTauri
88//                     } else if dioxus_config.exists() {
89//                         debug!("FOUND DIOXUS {}", manifest_path.display());
90//                         TargetKind::ManifestDioxus
91//                     } else {
92//                         // default example aleady represented in prior scans
93//                         continue;
94//                     };
95
96//                     if let Some(name) = path.file_name() {
97//                         debug!("FOUND {:?} {:?}", name, target_kind);
98//                         targets.push(CargoTarget {
99//                             name: name.to_string_lossy().to_string(),
100//                             display_name: format!(
101//                                 "-examples/ {}",
102//                                 // sub_manifest.file_name().unwrap_or_default().to_string_lossy(),
103//                                 name.to_string_lossy()
104//                             ),
105//                             manifest_path: sub_manifest.clone(),
106//                             kind: target_kind,
107//                             extended: true,
108//                             origin: Some(TargetOrigin::SubProject(sub_manifest)),
109//                         });
110//                     }
111//                 }
112//                 // else {
113
114//                 //     let tauri_folder = path.join("src-tauri");
115//                 //     let tauri_config = path.join("tauri.conf.json");
116//                 //     if tauri_folder.exists() || tauri_config.exists() {
117//                 //         let target_kind=TargetKind::ManifestTauri;
118//                 //         if let Some(name) = path.file_name() {
119//                 //             targets.push(CargoTarget {
120//                 //                 name: name.to_string_lossy().to_string(),
121//                 //                 display_name: format!(
122//                 //                     "- examples/ {}",
123//                 //                     // parent.display(),
124//                 //                     name.to_string_lossy()
125//                 //                 ),
126//                 //                 manifest_path: sub_manifest.to_string_lossy().to_string(),
127//                 //                 kind: target_kind,
128//                 //                 extended: true,
129//                 //                 origin: Some(TargetOrigin::SubProject(sub_manifest)),
130//                 //             });
131//                 //     };
132//                 // }
133//                 //}
134//             }
135//         }
136//     }
137
138//     // Additional discovery for binaries or tests can be added here.
139
140//     Ok(targets)
141// }
142
143pub fn scan_tests_directory(manifest_path: &Path) -> Result<Vec<String>> {
144    // Determine the project root from the manifest's parent directory.
145    let project_root = manifest_path
146        .parent()
147        .ok_or_else(|| anyhow!("Unable to determine project root from manifest"))?;
148
149    // Construct the path to the tests directory.
150    let tests_dir = project_root.join("tests");
151    let mut tests = Vec::new();
152
153    // Only scan if the tests directory exists and is a directory.
154    if tests_dir.exists() && tests_dir.is_dir() {
155        for entry in fs::read_dir(tests_dir)? {
156            let entry = entry?;
157            let path = entry.path();
158            // Only consider files with a `.rs` extension.
159            if path.is_file() {
160                if let Some(ext) = path.extension() {
161                    if ext == "rs" {
162                        if let Some(stem) = path.file_stem() {
163                            tests.push(stem.to_string_lossy().to_string());
164                        }
165                    }
166                }
167            }
168        }
169    }
170
171    Ok(tests)
172}
173
174pub fn scan_examples_directory(manifest_path: &Path) -> Result<Vec<CargoTarget>> {
175    // Determine the project root from the manifest's parent directory.
176    let project_root = manifest_path
177        .parent()
178        .ok_or_else(|| anyhow::anyhow!("Unable to determine project root"))?;
179    let examples_dir = project_root.join("examples");
180    let mut targets = Vec::new();
181
182    if examples_dir.exists() && examples_dir.is_dir() {
183        for entry in fs::read_dir(&examples_dir)
184            .with_context(|| format!("Reading directory {:?}", examples_dir))?
185        {
186            let entry = entry?;
187            let path = entry.path();
188            if path.is_file() {
189                // Assume that any .rs file in examples/ is an example.
190                if let Some(ext) = path.extension() {
191                    if ext == "rs" {
192                        if let Some(stem) = path.file_stem() {
193                            if let Some(target) = CargoTarget::from_source_file(
194                                stem,
195                                &path,
196                                manifest_path,
197                                true,
198                                false,
199                            ) {
200                                targets.push(target);
201                            }
202                        }
203                    }
204                }
205            } else if path.is_dir() {
206                if let Some(target) = CargoTarget::from_folder(&path, &manifest_path, true, true) {
207                    if target.kind == TargetKind::Unknown {
208                        continue;
209                    }
210                    targets.push(target);
211                }
212                // If the directory contains a Cargo.toml, treat it as an extended subproject.
213                // let sub_manifest = path.join("Cargo.toml");
214                // if sub_manifest.exists() {
215                //     // Look for a Tauri or Dioxus configuration.
216                //     let tauri_folder = path.join("src-tauri");
217                //     let tauri_config = path.join("tauri.conf.json");
218                //     let dioxus_config = path.join("Dioxus.toml");
219
220                //     let target_kind = if tauri_folder.exists() || tauri_config.exists() {
221                //         TargetKind::ManifestTauri
222                //     } else if dioxus_config.exists() {
223                //         TargetKind::ManifestDioxus
224                //     } else {
225                //         // Skip directories that don't match known subproject configurations.
226                //         continue;
227                //     };
228
229                //     if let Some(name) = path.file_name() {
230                //         targets.push(CargoTarget {
231                //             name: name.to_string_lossy().to_string(),
232                //             display_name: format!("-examples/ {}", name.to_string_lossy()),
233                //             manifest_path: sub_manifest.clone(),
234                //             kind: target_kind,
235                //             extended: true,
236                //             origin: Some(TargetOrigin::SubProject(sub_manifest)),
237                //         });
238                //     }
239                // }
240            }
241        }
242    }
243
244    Ok(targets)
245}
246
247/// Determines the target kind and (optionally) an updated manifest path based on:
248/// - Tauri configuration: If the parent directory of the original manifest contains a
249///   "tauri.conf.json", and also a Cargo.toml exists in that same directory, then update the manifest path
250///   and return ManifestTauri.
251/// - Dioxus markers: If the file contents contain any Dioxus markers, return either ManifestDioxusExample
252///   (if `example` is true) or ManifestDioxus.
253/// - Otherwise, if the file contains "fn main", decide based on the candidate's parent folder name.
254///   If the parent is "examples" (or "bin"), return the corresponding Example/Binary (or extended variant).
255/// - If none of these conditions match, return Example as a fallback.
256///
257/// Returns a tuple of (TargetKind, updated_manifest_path).
258pub fn determine_target_kind_and_manifest(
259    manifest_path: &Path,
260    candidate: &Path,
261    file_contents: &str,
262    example: bool,
263    extended: bool,
264    incoming_kind: Option<TargetKind>,
265) -> (TargetKind, PathBuf) {
266    // Start with the original manifest path.
267    let mut new_manifest = manifest_path.to_path_buf();
268
269    // If the incoming kind is already known (Test or Bench), return it.
270    if let Some(kind) = incoming_kind {
271        if kind == TargetKind::Test || kind == TargetKind::Bench {
272            return (kind, new_manifest);
273        }
274    }
275    // Tauri detection: check if the manifest's parent or candidate's parent contains tauri config.
276    let tauri_detected = manifest_path
277        .parent()
278        .and_then(|p| p.file_name())
279        .map(|s| s.to_string_lossy().eq_ignore_ascii_case("src-tauri"))
280        .unwrap_or(false)
281        || manifest_path
282            .parent()
283            .map(|p| p.join("tauri.conf.json"))
284            .map_or(false, |p| p.exists())
285        || manifest_path
286            .parent()
287            .map(|p| p.join("src-tauri"))
288            .map_or(false, |p| p.exists())
289        || candidate
290            .parent()
291            .map(|p| p.join("tauri.conf.json"))
292            .map_or(false, |p| p.exists());
293
294    if tauri_detected {
295        if example {
296            return (TargetKind::ManifestTauriExample, new_manifest);
297        }
298        // If the candidate's parent contains tauri.conf.json, update the manifest path if there's a Cargo.toml there.
299        if let Some(candidate_parent) = candidate.parent() {
300            let candidate_manifest = candidate_parent.join("Cargo.toml");
301            if candidate_manifest.exists() {
302                new_manifest = candidate_manifest;
303            }
304        }
305        return (TargetKind::ManifestTauri, new_manifest);
306    }
307
308    // Dioxus detection
309    if file_contents.contains("dioxus::") {
310        let kind = if example {
311            TargetKind::ManifestDioxusExample
312        } else {
313            TargetKind::ManifestDioxus
314        };
315        return (kind, new_manifest);
316    }
317
318    // Check if the file contains "fn main"
319    if file_contents.contains("fn main") {
320        if file_contents.contains("fn main") {
321            if example {
322                let kind = if extended {
323                    TargetKind::ExtendedExample
324                } else {
325                    TargetKind::Example
326                };
327                return (kind, new_manifest);
328            } else {
329                let kind = if extended {
330                    TargetKind::ExtendedBinary
331                } else {
332                    TargetKind::Binary
333                };
334                return (kind, new_manifest);
335            }
336        }
337    }
338    // Check if the file contains a #[test] attribute; if so, mark it as a test.
339    if file_contents.contains("#[test]") {
340        return (TargetKind::Test, new_manifest);
341    }
342
343    // Default fallback.
344    (TargetKind::Unknown, "errorNOfnMAIN".into())
345}
346
347/// Determines the target kind based on the manifest path and file contents.
348/// Returns Some(kind) if one of the conditions is met, or None if the file doesn’t appear runnable.
349// pub fn determine_target_kind(
350//     manifest_path: &Path,
351//     candidate: &Path,
352//     file_contents: &str,
353//     example: bool,
354//     extended: bool,
355// ) -> Option<TargetKind> {
356//     // Check if the manifest's parent is "src-tauri" or if a Tauri configuration exists.
357//  if manifest_path
358//     .parent()
359//     .and_then(|p| p.file_name())
360//     .map(|s| s.to_string_lossy().eq_ignore_ascii_case("src-tauri"))
361//     .unwrap_or(false)
362//     || manifest_path
363//         .parent()
364//         .map(|p| p.join("tauri.conf.json"))
365//         .map_or(false, |p| p.exists())
366//     || manifest_path
367//         .parent()
368//         .map(|p| p.join("src-tauri"))
369//         .map_or(false, |p| p.exists())
370//     || candidate
371//         .parent()
372//         .map(|p| p.join("tauri.conf.json"))
373//         .map_or(false, |p| p.exists())
374// {
375//     return Some(TargetKind::ManifestTauri);
376// }
377
378//     // Check for Dioxus markers.
379//     if file_contents.contains("LaunchBuilder::new")
380//         || file_contents.contains("dioxus::LaunchBuilder")
381//         || file_contents.contains("dioxus::launch")
382//     {
383//         return Some(if example {
384//             TargetKind::ManifestDioxusExample
385//         } else {
386//             TargetKind::ManifestDioxus
387//         });
388//     }
389
390//     // Check if the file is a runnable source file.
391//     if file_contents.contains("fn main") {
392//         if example {
393//             if extended {
394//                 return Some(TargetKind::ExtendedExample);
395//             } else {
396//                 return Some(TargetKind::Example);
397//             }
398//         } else {
399//             if extended {
400//                 return Some(TargetKind::ExtendedBinary);
401//             } else {
402//                 return Some(TargetKind::Binary);
403//             }
404//         }
405//     }
406
407//     None
408// }
409
410/// Returns true if the candidate file is not located directly in the project root.
411pub fn is_extended_target(manifest_path: &Path, candidate: &Path) -> bool {
412    if let Some(project_root) = manifest_path.parent() {
413        // If the candidate's parent is not the project root, it's nested (i.e. extended).
414        candidate
415            .parent()
416            .map(|p| p != project_root)
417            .unwrap_or(false)
418    } else {
419        false
420    }
421}
422
423// #[cfg(test)]
424// mod tests {
425//     use super::*;
426//     use std::fs;
427//     use tempfile::tempdir;
428
429//     #[test]
430//     fn test_discover_targets_no_manifest() {
431//         let temp = tempdir().unwrap();
432//         // With no Cargo.toml, we expect an empty list.
433//         let targets = discover_targets(temp.path()).unwrap();
434//         assert!(targets.is_empty());
435//     }
436
437//     #[test]
438//     fn test_discover_targets_with_manifest_and_example() {
439//         let temp = tempdir().unwrap();
440//         // Create a dummy Cargo.toml.
441//         let manifest_path = temp.path().join("Cargo.toml");
442//         fs::write(&manifest_path, "[package]\nname = \"dummy\"\n").unwrap();
443
444//         // Create an examples directory with a dummy example file.
445//         let examples_dir = temp.path().join("examples");
446//         fs::create_dir(&examples_dir).unwrap();
447//         let example_file = examples_dir.join("example1.rs");
448//         fs::write(&example_file, "fn main() {}").unwrap();
449
450//         let targets = discover_targets(temp.path()).unwrap();
451//         // Expect at least two targets: one for the manifest and one for the example.
452//         assert!(targets.len() >= 2);
453
454//         let example_target = targets
455//             .iter()
456//             .find(|t| t.kind == TargetKind::Example && t.name == "example1");
457//         assert!(example_target.is_some());
458//     }
459// }