Skip to main content

arborist_cli/
traversal.rs

1use std::path::{Path, PathBuf};
2
3use ignore::WalkBuilder;
4
5use crate::error::ArboristError;
6
7/// Known file extensions mapped to language names for filtering.
8fn extension_to_language(ext: &str) -> Option<&'static str> {
9    match ext {
10        "rs" => Some("rust"),
11        "py" => Some("python"),
12        "js" => Some("javascript"),
13        "ts" => Some("typescript"),
14        "tsx" => Some("tsx"),
15        "jsx" => Some("jsx"),
16        "java" => Some("java"),
17        "go" => Some("go"),
18        "c" | "h" => Some("c"),
19        "cpp" | "cc" | "cxx" | "hpp" => Some("cpp"),
20        "cs" => Some("c_sharp"),
21        "rb" => Some("ruby"),
22        "swift" => Some("swift"),
23        "kt" | "kts" => Some("kotlin"),
24        "php" => Some("php"),
25        _ => None,
26    }
27}
28
29pub fn collect_files(
30    paths: &[PathBuf],
31    languages_filter: Option<&[String]>,
32    gitignore: bool,
33) -> Result<Vec<PathBuf>, ArboristError> {
34    let mut files = Vec::new();
35
36    for path in paths {
37        if path.is_file() {
38            files.push(path.clone());
39        } else if path.is_dir() {
40            collect_directory(path, languages_filter, gitignore, &mut files)?;
41        } else {
42            return Err(ArboristError::Io(std::io::Error::new(
43                std::io::ErrorKind::NotFound,
44                format!("file not found: {}", path.display()),
45            )));
46        }
47    }
48
49    Ok(files)
50}
51
52fn collect_directory(
53    dir: &Path,
54    languages_filter: Option<&[String]>,
55    gitignore: bool,
56    files: &mut Vec<PathBuf>,
57) -> Result<(), ArboristError> {
58    let walker = WalkBuilder::new(dir)
59        .git_ignore(gitignore)
60        .git_global(false)
61        .git_exclude(false)
62        .hidden(false)
63        .build();
64
65    let languages_lower: Option<Vec<String>> =
66        languages_filter.map(|langs| langs.iter().map(|l| l.to_lowercase()).collect());
67
68    for entry in walker {
69        let entry = entry.map_err(|e| ArboristError::Io(std::io::Error::other(e.to_string())))?;
70
71        let path = entry.path();
72        if !path.is_file() {
73            continue;
74        }
75
76        let ext = match path.extension().and_then(|e| e.to_str()) {
77            Some(ext) => ext,
78            None => continue,
79        };
80
81        let lang = match extension_to_language(ext) {
82            Some(lang) => lang,
83            None => continue,
84        };
85
86        if let Some(ref filter) = languages_lower
87            && !filter.iter().any(|f| f == lang)
88        {
89            continue;
90        }
91
92        files.push(path.to_path_buf());
93    }
94
95    files.sort();
96    Ok(())
97}