context_builder/
file_utils.rs1use ignore::{DirEntry, WalkBuilder};
2use std::io::{self, Write};
3use std::path::Path;
4
5pub fn collect_files(
7 base_path: &Path,
8 filters: &[String],
9 ignores: &[String],
10) -> io::Result<Vec<DirEntry>> {
11 let mut walker = WalkBuilder::new(base_path);
12 if !filters.is_empty() {
17 let mut type_builder = ignore::types::TypesBuilder::new();
18 type_builder.add_defaults();
19 for filter in filters {
20 let _ = type_builder.add(filter, &format!("*.{}", filter));
21 type_builder.select(filter);
22 }
23 let types = type_builder.build().unwrap();
24 walker.types(types);
25 }
26
27 Ok(walker
28 .build()
29 .filter_map(Result::ok)
30 .filter(|e| e.file_type().is_some_and(|ft| ft.is_file()))
31 .filter(|e| {
32 let path = e.path();
33 !path.components().any(|c| {
35 let comp = c.as_os_str();
36 ignores
37 .iter()
38 .any(|name| comp == std::ffi::OsStr::new(name))
39 })
40 })
41 .collect())
42}
43
44pub fn confirm_processing(file_count: usize) -> io::Result<bool> {
46 if file_count > 100 {
47 print!(
48 "Warning: You're about to process {} files. This might take a while. Continue? [y/N] ",
49 file_count
50 );
51 io::stdout().flush()?;
52 let mut input = String::new();
53 io::stdin().read_line(&mut input)?;
54 if !input.trim().eq_ignore_ascii_case("y") {
55 return Ok(false);
56 }
57 }
58 Ok(true)
59}
60
61pub fn confirm_overwrite(file_path: &str) -> io::Result<bool> {
63 print!("The file '{}' already exists. Overwrite? [y/N] ", file_path);
64 io::stdout().flush()?;
65 let mut input = String::new();
66 io::stdin().read_line(&mut input)?;
67
68 if input.trim().eq_ignore_ascii_case("y") {
69 Ok(true)
70 } else {
71 Ok(false)
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use std::fs;
79 use std::path::Path;
80 use tempfile::tempdir;
81
82 fn to_rel_paths(mut entries: Vec<DirEntry>, base: &Path) -> Vec<String> {
83 entries.sort_by_key(|e| e.path().to_path_buf());
84 entries
85 .iter()
86 .map(|e| {
87 e.path()
88 .strip_prefix(base)
89 .unwrap()
90 .to_string_lossy()
91 .replace('\\', "/")
92 })
93 .collect()
94 }
95
96 #[test]
97 fn collect_files_respects_filters() {
98 let dir = tempdir().unwrap();
99 let base = dir.path();
100
101 fs::create_dir_all(base.join("src")).unwrap();
103 fs::create_dir_all(base.join("scripts")).unwrap();
104 fs::write(base.join("src").join("main.rs"), "fn main() {}").unwrap();
105 fs::write(base.join("Cargo.toml"), "[package]\nname=\"x\"").unwrap();
106 fs::write(base.join("README.md"), "# readme").unwrap();
107 fs::write(base.join("scripts").join("build.sh"), "#!/bin/sh\n").unwrap();
108
109 let filters = vec!["rs".to_string(), "toml".to_string()];
110 let ignores: Vec<String> = vec![];
111
112 let files = collect_files(base, &filters, &ignores).unwrap();
113 let relative_paths = to_rel_paths(files, base);
114
115 assert!(relative_paths.contains(&"src/main.rs".to_string()));
116 assert!(relative_paths.contains(&"Cargo.toml".to_string()));
117 assert!(!relative_paths.contains(&"README.md".to_string()));
118 assert!(!relative_paths.contains(&"scripts/build.sh".to_string()));
119 }
120
121 #[test]
122 fn collect_files_respects_ignores_for_dirs_and_files() {
123 let dir = tempdir().unwrap();
124 let base = dir.path();
125
126 fs::create_dir_all(base.join("src")).unwrap();
127 fs::create_dir_all(base.join("target")).unwrap();
128 fs::create_dir_all(base.join("node_modules")).unwrap();
129
130 fs::write(base.join("src").join("main.rs"), "fn main() {}").unwrap();
131 fs::write(base.join("target").join("artifact.txt"), "bin").unwrap();
132 fs::write(base.join("node_modules").join("pkg.js"), "console.log();").unwrap();
133 fs::write(base.join("README.md"), "# readme").unwrap();
134
135 let filters: Vec<String> = vec![];
136 let ignores: Vec<String> = vec!["target".into(), "node_modules".into(), "README.md".into()];
137
138 let files = collect_files(base, &filters, &ignores).unwrap();
139 let relative_paths = to_rel_paths(files, base);
140
141 assert!(relative_paths.contains(&"src/main.rs".to_string()));
142 assert!(!relative_paths.contains(&"target/artifact.txt".to_string()));
143 assert!(!relative_paths.contains(&"node_modules/pkg.js".to_string()));
144 assert!(!relative_paths.contains(&"README.md".to_string()));
145 }
146}