diff --git a/bin/src/config.rs b/bin/src/config.rs index 678e216a..cb67e12d 100644 --- a/bin/src/config.rs +++ b/bin/src/config.rs @@ -2,6 +2,7 @@ use std::{ default::Default, fmt, fs, path::{Path, PathBuf}, + slice, str::FromStr, }; @@ -77,8 +78,7 @@ impl Check { Ok(ReadOnlyVfs::singleton("", src.as_bytes())) } else { let all_ignores = [self.ignore.as_slice(), extra_ignores].concat(); - let ignore = dirs::build_ignore_set(&all_ignores, &self.target, self.unrestricted)?; - let files = dirs::walk_nix_files(ignore, &self.target)?; + let files = dirs::walk_nix_files(&all_ignores, slice::from_ref(&self.target), self.unrestricted)?; Ok(vfs(&files.collect::>())) } } @@ -130,8 +130,7 @@ impl Fix { Ok(ReadOnlyVfs::singleton("", src.as_bytes())) } else { let all_ignores = [self.ignore.as_slice(), extra_ignores].concat(); - let ignore = dirs::build_ignore_set(&all_ignores, &self.target, self.unrestricted)?; - let files = dirs::walk_nix_files(ignore, &self.target)?; + let files = dirs::walk_nix_files(&all_ignores, slice::from_ref(&self.target), self.unrestricted)?; Ok(vfs(&files.collect::>())) } } diff --git a/bin/src/dirs.rs b/bin/src/dirs.rs index 13b66d7a..9aaa4760 100644 --- a/bin/src/dirs.rs +++ b/bin/src/dirs.rs @@ -1,112 +1,69 @@ use std::{ - fs, io::{self, Error, ErrorKind}, path::{Path, PathBuf}, }; -use crate::dirs; +use ignore::{overrides::OverrideBuilder, WalkBuilder}; -use ignore::{ - Error as IgnoreError, Match, - gitignore::{Gitignore, GitignoreBuilder}, -}; +/// Walks through target paths and returns an iterator of .nix files, respecting gitignore rules. +/// +/// # Arguments +/// * `ignore_patterns` - Globs of file patterns to skip (e.g., "*.tmp", "build/*") +/// * `targets` - File or directory paths to walk through +/// * `unrestricted` - If true, don't respect .gitignore files; if false, respect them +pub fn walk_nix_files>( + ignore_patterns: &[String], + targets: &[P], + unrestricted: bool, +) -> Result, io::Error> { + let mut targets_iter = targets.iter(); -#[derive(Debug)] -pub struct Walker { - dirs: Vec, - files: Vec, - ignore: Gitignore, -} + let first_target = targets_iter + .next() + .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "No targets provided"))?; -impl Walker { - pub fn new>(target: P, ignore: Gitignore) -> io::Result { - let target = target.as_ref().to_path_buf(); - if !target.exists() { - Err(Error::new( - ErrorKind::NotFound, - format!("file not found: {}", target.display()), - )) - } else if target.is_dir() { - Ok(Self { - dirs: vec![target], - files: vec![], - ignore, - }) - } else { - Ok(Self { - dirs: vec![], - files: vec![target], - ignore, - }) - } - } -} + let mut builder = WalkBuilder::new(first_target.as_ref()); -impl Iterator for Walker { - type Item = PathBuf; - fn next(&mut self) -> Option { - self.files.pop().or_else(|| { - while let Some(dir) = self.dirs.pop() { - if dir.is_dir() - && let Match::None | Match::Whitelist(_) = self.ignore.matched(&dir, true) - { - let mut found = false; - for entry in fs::read_dir(&dir).ok()? { - let entry = entry.ok()?; - let path = entry.path(); - if path.is_dir() { - self.dirs.push(path); - } else if path.is_file() - && let Match::None | Match::Whitelist(_) = - self.ignore.matched(&path, false) - { - found = true; - self.files.push(path); - } - } - if found { - break; - } - } - } - self.files.pop() - }) + for target in targets_iter { + builder.add(target.as_ref()); } -} -pub fn build_ignore_set>( - ignore: &[String], - target: P, - unrestricted: bool, -) -> Result { - let gitignore_path = target.as_ref().join(".gitignore"); + builder.git_ignore(!unrestricted); - // Looks like GitignoreBuilder::new does not source globs - // within gitignore_path by default, we have to enforce that - // using GitignoreBuilder::add. Probably a bug in the ignore - // crate? - let mut gitignore = GitignoreBuilder::new(&gitignore_path); + // Add files/directories to ignore set passed in --ignore + if !ignore_patterns.is_empty() { + let mut override_builder = OverrideBuilder::new(""); - // if we are to "restrict" aka "respect" .gitignore, then - // add globs from gitignore path as well - if !unrestricted { - gitignore.add(&gitignore_path); + for pattern in ignore_patterns { + // Note: The `!` prefix has inverted semantics in OverrideBuilder compared to gitignore. + // In OverrideBuilder: `!pattern` means "ignore files matching pattern" + // In gitignore: `!pattern` means "don't ignore files matching pattern" (whitelist) + // So we add `!` to make ignore_patterns actually ignore files. + override_builder + .add(&format!("!{pattern}")) + .map_err(|e| Error::new(ErrorKind::InvalidInput, e))?; + } - // ignore .git by default, nobody cares about .git, i'm sure - gitignore.add_line(None, ".git")?; - } + let overrides = override_builder + .build() + .map_err(|e| Error::new(ErrorKind::InvalidInput, e))?; - for i in ignore { - gitignore.add_line(None, i.as_str())?; + builder.overrides(overrides); } - gitignore.build() -} + let walker = builder.build(); -pub fn walk_nix_files>( - ignore: Gitignore, - target: P, -) -> Result, io::Error> { - let walker = dirs::Walker::new(target, ignore)?; - Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix"))) + Ok(walker + .filter_map(|result| match result { + Ok(entry) => Some(entry), + Err(err) => { + eprintln!("Warning: Error reading directory entry: {err}"); + None + } + }) + .filter_map(|entry| { + let path = entry.path(); + (path.is_file() && matches!(path.extension(), Some(ext) if ext == "nix")) + .then(|| path.to_path_buf()) + })) }