1use std::collections::HashMap;
7use std::path::Path;
8use walkdir::WalkDir;
9
10#[derive(Debug, Default)]
12pub struct ProjectScan {
13 pub languages: Vec<DetectedLanguage>,
14 pub has_package_json: bool,
15 pub has_cargo_toml: bool,
16 pub has_pyproject_toml: bool,
17 pub has_go_mod: bool,
18 }
20
21#[derive(Debug, Clone)]
22pub struct DetectedLanguage {
23 pub name: &'static str,
24 pub patterns: Vec<&'static str>,
25 pub file_count: usize,
26}
27
28pub fn scan_project<P: AsRef<Path>>(root: P) -> ProjectScan {
30 let root = root.as_ref();
31 let mut ext_counts: HashMap<String, usize> = HashMap::new();
32 let mut scan = ProjectScan::default();
33
34 for entry in WalkDir::new(root)
35 .max_depth(10)
36 .into_iter()
37 .filter_map(|e| e.ok())
38 {
39 let path = entry.path();
40
41 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
43 if matches!(
44 name,
45 "node_modules" | "target" | "dist" | "build" | ".git" | "vendor" | "__pycache__"
46 ) {
47 continue;
48 }
49 }
50
51 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
53 match name {
54 "package.json" => scan.has_package_json = true,
55 "Cargo.toml" => scan.has_cargo_toml = true,
56 "pyproject.toml" | "setup.py" => scan.has_pyproject_toml = true,
57 "go.mod" => scan.has_go_mod = true,
58 _ => {}
59 }
60 }
61
62 if path.is_file() {
64 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
65 let ext = ext.to_lowercase();
66 *ext_counts.entry(ext).or_insert(0) += 1;
67 }
68 }
69 }
70
71 let lang_mappings: [(&str, &[&str], &[&str]); 6] = [
73 ("TypeScript", &["ts", "tsx"], &["**/*.ts", "**/*.tsx"]),
74 (
75 "JavaScript",
76 &["js", "jsx", "mjs"],
77 &["**/*.js", "**/*.jsx", "**/*.mjs"],
78 ),
79 ("Rust", &["rs"], &["**/*.rs"]),
80 ("Python", &["py"], &["**/*.py"]),
81 ("Go", &["go"], &["**/*.go"]),
82 ("Java", &["java"], &["**/*.java"]),
83 ];
84
85 for (name, exts, patterns) in lang_mappings {
86 let count: usize = exts.iter().filter_map(|e| ext_counts.get(*e)).sum();
87
88 if count > 0 {
89 scan.languages.push(DetectedLanguage {
90 name,
91 patterns: patterns.to_vec(),
92 file_count: count,
93 });
94 }
95 }
96
97 scan.languages
99 .sort_by(|a, b| b.file_count.cmp(&a.file_count));
100
101 scan
105}
106
107