use crate::Settings;
use crate::parsing::get_registry;
use ignore::WalkBuilder;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug)]
pub struct FileWalker {
settings: Arc<Settings>,
}
impl FileWalker {
pub fn new(settings: Arc<Settings>) -> Self {
Self { settings }
}
pub fn walk(&self, root: &Path) -> impl Iterator<Item = PathBuf> {
let mut builder = WalkBuilder::new(root);
builder
.hidden(false) .git_ignore(true) .git_global(true) .git_exclude(true) .follow_links(false) .max_depth(None) .require_git(false);
builder.add_custom_ignore_filename(".codannaignore");
let enabled_extensions = self.get_enabled_extensions();
builder
.build()
.filter_map(Result::ok) .filter(|entry| entry.file_type().is_some_and(|ft| ft.is_file()))
.filter_map(move |entry| {
let path = entry.path();
if let Some(file_name) = path.file_name() {
if let Some(name_str) = file_name.to_str() {
if name_str.starts_with('.') {
return None;
}
}
}
if let Some(extension) = path.extension() {
if let Some(ext_str) = extension.to_str() {
if enabled_extensions.iter().any(|ext| ext == ext_str) {
return Some(path.to_path_buf());
}
}
}
None
})
}
fn get_enabled_extensions(&self) -> Vec<String> {
let registry = get_registry();
if let Ok(registry) = registry.lock() {
registry
.enabled_extensions(&self.settings)
.map(|ext| ext.to_string())
.collect()
} else {
Vec::new()
}
}
pub fn count_files(&self, root: &Path) -> usize {
self.walk(root).count()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn create_test_settings() -> Arc<Settings> {
let mut settings = Settings::default();
settings.languages.get_mut("python").unwrap().enabled = false;
settings.languages.get_mut("php").unwrap().enabled = false;
Arc::new(settings)
}
#[test]
fn test_walk_directory() {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
fs::write(root.join("main.rs"), "fn main() {}").unwrap();
fs::write(root.join("lib.rs"), "pub fn lib() {}").unwrap();
fs::write(root.join("test.py"), "def test(): pass").unwrap();
fs::write(root.join("README.md"), "# Test").unwrap();
let settings = create_test_settings();
let walker = FileWalker::new(settings);
let files: Vec<_> = walker.walk(root).collect();
assert_eq!(files.len(), 2);
assert!(files.iter().any(|p| p.ends_with("main.rs")));
assert!(files.iter().any(|p| p.ends_with("lib.rs")));
}
#[test]
fn test_ignore_hidden_files() {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
fs::write(root.join(".hidden.rs"), "fn hidden() {}").unwrap();
fs::write(root.join("visible.rs"), "fn visible() {}").unwrap();
let settings = create_test_settings();
let walker = FileWalker::new(settings);
let files: Vec<_> = walker.walk(root).collect();
assert_eq!(files.len(), 1);
assert!(files[0].ends_with("visible.rs"));
}
#[test]
fn test_gitignore_respected() {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
fs::write(root.join(".gitignore"), "ignored.rs\n").unwrap();
fs::write(root.join("ignored.rs"), "fn ignored() {}").unwrap();
fs::write(root.join("included.rs"), "fn included() {}").unwrap();
let settings = create_test_settings();
let walker = FileWalker::new(settings);
let files: Vec<_> = walker.walk(root).collect();
assert_eq!(files.len(), 1);
assert!(files[0].ends_with("included.rs"));
}
}