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// }