code_moniker_workspace/
walk.rs1use std::path::{Path, PathBuf};
2
3use crate::lang::path_to_lang;
4use code_moniker_core::lang::Lang;
5
6pub struct WalkedFile {
7 pub path: PathBuf,
8 pub lang: Lang,
9}
10
11pub fn walk_lang_files(root: &Path) -> Vec<WalkedFile> {
12 ignore::WalkBuilder::new(root)
13 .build()
14 .filter_map(|entry| entry.ok())
15 .filter(|e| e.file_type().is_some_and(|t| t.is_file()))
16 .filter_map(|e| {
17 let p = e.into_path();
18 let lang = path_to_lang(&p).ok()?;
19 Some(WalkedFile { path: p, lang })
20 })
21 .collect()
22}
23
24pub fn explicit_lang_file(path: &Path) -> Option<WalkedFile> {
25 ignore::WalkBuilder::new(path)
26 .build()
27 .filter_map(|entry| entry.ok())
28 .filter(|e| e.file_type().is_some_and(|t| t.is_file()))
29 .find_map(|e| {
30 let p = e.into_path();
31 let lang = path_to_lang(&p).ok()?;
32 Some(WalkedFile { path: p, lang })
33 })
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39 use std::collections::HashSet;
40 use std::fs;
41
42 fn write(root: &Path, rel: &str, body: &str) {
43 let p = root.join(rel);
44 if let Some(parent) = p.parent() {
45 fs::create_dir_all(parent).unwrap();
46 }
47 fs::write(p, body).unwrap();
48 }
49
50 #[test]
51 fn walks_supported_extensions_only() {
52 let tmp = tempfile::tempdir().unwrap();
53 let root = tmp.path();
54 write(root, "a.ts", "");
55 write(root, "b.rs", "");
56 write(root, "c.txt", "ignored");
57 write(root, "nested/d.py", "");
58 let mut files: HashSet<(String, Lang)> = walk_lang_files(root)
59 .into_iter()
60 .map(|f| {
61 let rel = f.path.strip_prefix(root).unwrap().to_string_lossy().into();
62 (rel, f.lang)
63 })
64 .collect();
65 assert!(files.remove(&("a.ts".into(), Lang::Ts)));
66 assert!(files.remove(&("b.rs".into(), Lang::Rs)));
67 assert!(files.remove(&("nested/d.py".into(), Lang::Python)));
68 assert!(files.is_empty(), "unexpected files: {files:?}");
69 }
70
71 #[test]
72 fn respects_gitignore() {
73 let tmp = tempfile::tempdir().unwrap();
74 let root = tmp.path();
75 write(root, ".gitignore", "skip/\n");
76 write(root, "kept.ts", "");
77 write(root, "skip/dropped.ts", "");
78 fs::create_dir_all(root.join(".git")).unwrap();
79 let files: Vec<String> = walk_lang_files(root)
80 .into_iter()
81 .map(|f| f.path.strip_prefix(root).unwrap().to_string_lossy().into())
82 .collect();
83 assert_eq!(files, vec!["kept.ts".to_string()]);
84 }
85
86 #[test]
87 fn explicit_file_accepts_supported_file() {
88 let tmp = tempfile::tempdir().unwrap();
89 let root = tmp.path();
90 write(root, "kept.ts", "");
91
92 assert!(explicit_lang_file(&root.join("kept.ts")).is_some());
93 assert!(explicit_lang_file(&root.join("kept.txt")).is_none());
94 }
95}