1use std::path::{Path, PathBuf};
4
5use anyhow::Result;
6use walkdir::WalkDir;
7
8use crate::model::Config;
9use crate::plan::build_plan;
10
11fn is_ignored_dir(name: &str) -> bool {
13 matches!(name, "target" | ".git" | "node_modules") || name.starts_with('.')
14}
15
16fn is_candidate(path: &Path, min_groups: usize) -> bool {
18 let Ok(src) = std::fs::read_to_string(path) else {
19 return false;
20 };
21 match build_plan(path, &src) {
22 Ok(plan) => plan.files.len() >= min_groups,
23 Err(_) => false, }
25}
26
27pub fn find_candidates(root: &Path, config: &Config) -> Result<Vec<PathBuf>> {
30 let mut out = Vec::new();
31 if root.is_file() {
32 if is_candidate(root, config.min_groups) {
33 out.push(root.to_path_buf());
34 }
35 return Ok(out);
36 }
37
38 for entry in WalkDir::new(root)
39 .into_iter()
40 .filter_entry(|e| {
41 if e.file_type().is_dir() {
43 if let Some(name) = e.file_name().to_str() {
44 if e.depth() > 0 && is_ignored_dir(name) {
45 return false;
46 }
47 }
48 }
49 true
50 })
51 .flatten()
52 {
53 let path = entry.path();
54 if path.extension().and_then(|e| e.to_str()) != Some("rs") {
55 continue;
56 }
57 if is_candidate(path, config.min_groups) {
58 out.push(path.to_path_buf());
59 }
60 }
61
62 out.sort();
63 Ok(out)
64}