1use std::path::{Path, PathBuf};
18
19use anyhow::{Context, Result};
20use ignore::WalkBuilder;
21
22pub fn discover_rust_files(
25 src: &Path,
26 exclude: &[String],
27 respect_gitignore: bool,
28) -> Result<Vec<PathBuf>> {
29 let mut builder = WalkBuilder::new(src);
30 builder.git_ignore(respect_gitignore);
31
32 if !exclude.is_empty() {
34 let mut overrides = ignore::overrides::OverrideBuilder::new(src);
35 for pattern in exclude {
36 overrides
37 .add(&format!("!{pattern}"))
38 .with_context(|| format!("invalid exclude pattern: {pattern}"))?;
39 }
40 builder.overrides(overrides.build()?);
41 }
42
43 let mut files = Vec::new();
44 for entry in builder.build() {
45 let entry = entry?;
46 if entry.file_type().is_some_and(|ft| ft.is_file())
47 && entry.path().extension().is_some_and(|ext| ext == "rs")
48 {
49 files.push(entry.into_path());
50 }
51 }
52
53 files.sort();
55 Ok(files)
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use std::fs;
62
63 #[test]
64 fn discover_rust_files_finds_nested() {
65 let dir = tempfile::tempdir().unwrap();
66 let src = dir.path().join("src");
67 fs::create_dir_all(src.join("sub")).unwrap();
68 fs::write(src.join("lib.rs"), "").unwrap();
69 fs::write(src.join("sub").join("mod.rs"), "").unwrap();
70 fs::write(src.join("readme.txt"), "").unwrap();
71
72 let files = discover_rust_files(&src, &[], false).unwrap();
73 assert_eq!(files.len(), 2);
74 assert!(files.iter().all(|f| f.extension().unwrap() == "rs"));
75 }
76
77 #[test]
78 fn discover_rust_files_sorted_deterministically() {
79 let dir = tempfile::tempdir().unwrap();
80 let src = dir.path().join("src");
81 fs::create_dir_all(&src).unwrap();
82 fs::write(src.join("z.rs"), "").unwrap();
83 fs::write(src.join("a.rs"), "").unwrap();
84 fs::write(src.join("m.rs"), "").unwrap();
85
86 let files = discover_rust_files(&src, &[], false).unwrap();
87 let names: Vec<_> = files.iter().map(|f| f.file_name().unwrap()).collect();
88 assert_eq!(names, vec!["a.rs", "m.rs", "z.rs"]);
89 }
90}