use crate::config::Config;
use ignore::WalkBuilder;
use log::debug;
use std::path::Path;
pub fn configured_walk_builder(root: &Path, config: &Config) -> WalkBuilder {
let mut builder = WalkBuilder::new(root);
let _ = builder
.hidden(!config.include_dotfiles())
.git_ignore(config.respect_gitignore())
.git_global(config.respect_gitignore())
.git_exclude(config.respect_gitignore())
.require_git(false);
if config.max_depth() > 0 {
let _ = builder.max_depth(Some(config.max_depth().saturating_add(1)));
}
let patterns = config.patterns().clone();
let filter_root = root.to_path_buf();
let _ = builder.filter_entry(move |entry| {
let file_name = entry.file_name().to_str().unwrap_or("");
let path = entry.path();
let relative_path = path.strip_prefix(&filter_root).unwrap_or(path);
if patterns.should_ignore_glob(relative_path) {
debug!("Skipping path (glob match): {}", path.display());
return false;
}
let file_type = entry.file_type();
let is_dir = file_type.as_ref().is_some_and(std::fs::FileType::is_dir);
let is_file = file_type.as_ref().is_some_and(std::fs::FileType::is_file);
if is_dir && patterns.should_ignore_directory(file_name) {
debug!("Skipping directory: {}", path.display());
return false;
}
if is_file {
if patterns.should_ignore_file(file_name) {
debug!("Skipping file: {}", path.display());
return false;
}
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if patterns.should_ignore_extension(ext) {
debug!("Skipping file (extension): {}", path.display());
return false;
}
}
}
true
});
builder
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use serial_test::serial;
use std::fs;
use tempfile::TempDir;
#[test]
#[serial]
fn test_configured_builder_respects_hidden() {
let temp = TempDir::new().unwrap();
let root = temp.path().canonicalize().unwrap();
fs::write(root.join(".hidden"), "h").unwrap();
fs::write(root.join("visible.txt"), "v").unwrap();
let config = Config::new_for_test(root.clone());
let walk = configured_walk_builder(&root, &config);
let names: Vec<String> = walk
.build()
.filter_map(Result::ok)
.filter(|e| e.file_type().is_some_and(|ft| ft.is_file()))
.map(|e| e.file_name().to_str().unwrap_or_default().to_string())
.collect();
assert!(names.contains(&"visible.txt".to_string()));
assert!(!names.contains(&".hidden".to_string()));
}
#[test]
#[serial]
fn test_configured_builder_filters_binary_extensions() {
let temp = TempDir::new().unwrap();
let root = temp.path().canonicalize().unwrap();
fs::write(root.join("image.png"), "fake png").unwrap();
fs::write(root.join("archive.zip"), "fake zip").unwrap();
fs::write(root.join("source.rs"), "fn main() {}").unwrap();
fs::write(root.join("readme.txt"), "hello").unwrap();
let config = Config::new_for_test(root.clone());
let walk = configured_walk_builder(&root, &config);
let names: Vec<String> = walk
.build()
.filter_map(Result::ok)
.filter(|e| e.file_type().is_some_and(|ft| ft.is_file()))
.map(|e| e.file_name().to_str().unwrap_or_default().to_string())
.collect();
assert!(names.contains(&"source.rs".to_string()));
assert!(names.contains(&"readme.txt".to_string()));
assert!(!names.contains(&"image.png".to_string()));
assert!(!names.contains(&"archive.zip".to_string()));
}
}