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
10pub fn scan_tests_directory(manifest_path: &Path) -> Result<Vec<String>> {
11    // Determine the project root from the manifest's parent directory.
12    let project_root = manifest_path
13        .parent()
14        .ok_or_else(|| anyhow!("Unable to determine project root from manifest"))?;
15
16    // Construct the path to the tests directory.
17    let tests_dir = project_root.join("tests");
18    let mut tests = Vec::new();
19
20    // Only scan if the tests directory exists and is a directory.
21    if tests_dir.exists() && tests_dir.is_dir() {
22        for entry in fs::read_dir(tests_dir)? {
23            let entry = entry?;
24            let path = entry.path();
25            // Only consider files with a `.rs` extension.
26            if path.is_file() {
27                if let Some(ext) = path.extension() {
28                    if ext == "rs" {
29                        if let Some(stem) = path.file_stem() {
30                            tests.push(stem.to_string_lossy().to_string());
31                        }
32                    }
33                }
34            }
35        }
36    }
37
38    Ok(tests)
39}
40
41pub fn scan_examples_directory(
42    manifest_path: &Path,
43    examples_folder: &str,
44) -> Result<Vec<CargoTarget>> {
45    // Determine the project root from the manifest's parent directory.
46    let project_root = manifest_path
47        .parent()
48        .ok_or_else(|| anyhow::anyhow!("Unable to determine project root"))?;
49    let examples_dir = project_root.join(examples_folder);
50    let mut targets = Vec::new();
51
52    if examples_dir.exists() && examples_dir.is_dir() {
53        for entry in fs::read_dir(&examples_dir)
54            .with_context(|| format!("Reading directory {:?}", examples_dir))?
55        {
56            let entry = entry?;
57            let path = entry.path();
58            if path.is_file() {
59                // Assume that any .rs file in examples/ is an example.
60                if let Some(ext) = path.extension() {
61                    if ext == "rs" {
62                        if let Some(stem) = path.file_stem() {
63                            if let Some(target) = CargoTarget::from_source_file(
64                                stem,
65                                &path,
66                                manifest_path,
67                                true,
68                                false,
69                            ) {
70                                targets.push(target);
71                            }
72                        }
73                    }
74                }
75            } else if path.is_dir() {
76                if let Some(target) = CargoTarget::from_folder(&path, &manifest_path, true, true) {
77                    if target.kind == TargetKind::Unknown {
78                        continue;
79                    }
80                    targets.push(target);
81                }
82            }
83        }
84    }
85
86    Ok(targets)
87}
88
89/// Determines the target kind and (optionally) an updated manifest path based on:
90/// - Tauri configuration: If the parent directory of the original manifest contains a
91///   "tauri.conf.json", and also a Cargo.toml exists in that same directory, then update the manifest path
92///   and return ManifestTauri.
93/// - Dioxus markers: If the file contents contain any Dioxus markers, return either ManifestDioxusExample
94///   (if `example` is true) or ManifestDioxus.
95/// - Otherwise, if the file contains "fn main", decide based on the candidate's parent folder name.
96///   If the parent is "examples" (or "bin"), return the corresponding Example/Binary (or extended variant).
97/// - If none of these conditions match, return Example as a fallback.
98///
99/// Returns a tuple of (TargetKind, updated_manifest_path).
100pub fn determine_target_kind_and_manifest(
101    manifest_path: &Path,
102    candidate: &Path,
103    file_contents: &str,
104    example: bool,
105    extended: bool,
106    incoming_kind: Option<TargetKind>,
107) -> (TargetKind, PathBuf) {
108    // Start with the original manifest path.
109    let mut new_manifest = manifest_path.to_path_buf();
110
111    // If the incoming kind is already known (Test or Bench), return it.
112    if let Some(kind) = incoming_kind {
113        if kind == TargetKind::Test || kind == TargetKind::Bench {
114            return (kind, new_manifest);
115        }
116    }
117    // Tauri detection: check if the manifest's parent or candidate's parent contains tauri config.
118    let tauri_detected = manifest_path
119        .parent()
120        .and_then(|p| p.file_name())
121        .map(|s| s.to_string_lossy().eq_ignore_ascii_case("src-tauri"))
122        .unwrap_or(false)
123        || manifest_path
124            .parent()
125            .map(|p| p.join("tauri.conf.json"))
126            .map_or(false, |p| p.exists())
127        || manifest_path
128            .parent()
129            .map(|p| p.join("src-tauri"))
130            .map_or(false, |p| p.exists())
131        || candidate
132            .parent()
133            .map(|p| p.join("tauri.conf.json"))
134            .map_or(false, |p| p.exists());
135
136    if tauri_detected {
137        if example {
138            return (TargetKind::ManifestTauriExample, new_manifest);
139        }
140        // If the candidate's parent contains tauri.conf.json, update the manifest path if there's a Cargo.toml there.
141        if let Some(candidate_parent) = candidate.parent() {
142            let candidate_manifest = candidate_parent.join("Cargo.toml");
143            if candidate_manifest.exists() {
144                new_manifest = candidate_manifest;
145            }
146        }
147        return (TargetKind::ManifestTauri, new_manifest);
148    }
149
150    // Dioxus detection
151    if file_contents.contains("dioxus::") {
152        let kind = if example {
153            TargetKind::ManifestDioxusExample
154        } else {
155            TargetKind::ManifestDioxus
156        };
157        return (kind, new_manifest);
158    }
159
160    // leptos detection
161    if file_contents.contains("leptos::") {
162        return (TargetKind::ManifestLeptos, new_manifest);
163    }
164
165    // Check if the file contains "fn main"
166    if file_contents.contains("fn main") {
167        let kind = if example {
168            if extended {
169                TargetKind::ExtendedExample
170            } else {
171                TargetKind::Example
172            }
173        } else if extended {
174            TargetKind::ExtendedBinary
175        } else {
176            TargetKind::Binary
177        };
178        return (kind, new_manifest);
179    }
180    // Check if the file contains a #[test] attribute; if so, mark it as a test.
181    if file_contents.contains("#[test]") {
182        return (TargetKind::Test, new_manifest);
183    }
184
185    // Default fallback.
186    (TargetKind::Unknown, "errorNOfnMAIN".into())
187}
188
189/// Returns true if the candidate file is not located directly in the project root.
190pub fn is_extended_target(manifest_path: &Path, candidate: &Path) -> bool {
191    if let Some(project_root) = manifest_path.parent() {
192        // If the candidate's parent is not the project root, it's nested (i.e. extended).
193        candidate
194            .parent()
195            .map(|p| p != project_root)
196            .unwrap_or(false)
197    } else {
198        false
199    }
200}
201
202// #[cfg(test)]
203// mod tests {
204//     use super::*;
205//     use std::fs;
206//     use tempfile::tempdir;
207
208//     #[test]
209//     fn test_discover_targets_no_manifest() {
210//         let temp = tempdir().unwrap();
211//         // With no Cargo.toml, we expect an empty list.
212//         let targets = discover_targets(temp.path()).unwrap();
213//         assert!(targets.is_empty());
214//     }
215
216//     #[test]
217//     fn test_discover_targets_with_manifest_and_example() {
218//         let temp = tempdir().unwrap();
219//         // Create a dummy Cargo.toml.
220//         let manifest_path = temp.path().join("Cargo.toml");
221//         fs::write(&manifest_path, "[package]\nname = \"dummy\"\n").unwrap();
222
223//         // Create an examples directory with a dummy example file.
224//         let examples_dir = temp.path().join("examples");
225//         fs::create_dir(&examples_dir).unwrap();
226//         let example_file = examples_dir.join("example1.rs");
227//         fs::write(&example_file, "fn main() {}").unwrap();
228
229//         let targets = discover_targets(temp.path()).unwrap();
230//         // Expect at least two targets: one for the manifest and one for the example.
231//         assert!(targets.len() >= 2);
232
233//         let example_target = targets
234//             .iter()
235//             .find(|t| t.kind == TargetKind::Example && t.name == "example1");
236//         assert!(example_target.is_some());
237//     }
238// }