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