diff options
author | Benjamin Sago <ogham@bsago.me> | 2017-09-28 09:09:57 +0100 |
---|---|---|
committer | Benjamin Sago <ogham@bsago.me> | 2017-09-30 09:17:29 +0200 |
commit | b95446d834f82fc57480f16d65db09e0fd469519 (patch) | |
tree | a9e3458603787b3061e40a3c7d98a723097dfcdc | |
parent | 07443e87ba3fa8b656fffb453f16bb610ff1b3dd (diff) |
Thread an ignore cache through the program
!
-rw-r--r-- | src/exa.rs | 23 | ||||
-rw-r--r-- | src/fs/dir.rs | 12 | ||||
-rw-r--r-- | src/fs/feature/ignore.rs | 69 | ||||
-rw-r--r-- | src/fs/feature/mod.rs | 4 | ||||
-rw-r--r-- | src/fs/filter.rs | 15 | ||||
-rw-r--r-- | src/options/filter.rs | 1 | ||||
-rw-r--r-- | src/output/details.rs | 13 | ||||
-rw-r--r-- | src/output/grid_details.rs | 9 |
8 files changed, 125 insertions, 21 deletions
@@ -30,6 +30,7 @@ use std::path::{Component, PathBuf}; use ansi_term::{ANSIStrings, Style}; use fs::{Dir, File}; +use fs::feature::ignore::IgnoreCache; use fs::feature::git::GitCache; use options::{Options, Vars}; pub use options::Misfire; @@ -61,6 +62,10 @@ pub struct Exa<'args, 'w, W: Write + 'w> { /// This has to last the lifetime of the program, because the user might /// want to list several directories in the same repository. pub git: Option<GitCache>, + + /// A cache of git-ignored files. + /// This lasts the lifetime of the program too, for the same reason. + pub ignore: Option<IgnoreCache>, } /// The “real” environment variables type. @@ -84,6 +89,15 @@ fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> { } } +fn ignore_cache(options: &Options) -> Option<IgnoreCache> { + use fs::filter::GitIgnore; + + match options.filter.git_ignore { + GitIgnore::CheckAndIgnore => Some(IgnoreCache::new()), + GitIgnore::Off => None, + } +} + impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> { pub fn new<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire> where I: Iterator<Item=&'args OsString> { @@ -99,7 +113,8 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> { } let git = git_options(&options, &args); - Exa { options, writer, args, git } + let ignore = ignore_cache(&options); + Exa { options, writer, args, git, ignore } }) } @@ -160,7 +175,7 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> { } let mut children = Vec::new(); - for file in dir.files(self.options.filter.dot_filter) { + for file in dir.files(self.options.filter.dot_filter, self.ignore.as_ref()) { match file { Ok(file) => children.push(file), Err((path, e)) => writeln!(stderr(), "[{}: {}]", path.display(), e)?, @@ -212,10 +227,10 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> { grid::Render { files, colours, style, opts }.render(self.writer) } Mode::Details(ref opts) => { - details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.git.as_ref(), self.writer) + details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.git.as_ref(), self.ignore.as_ref(), self.writer) } Mode::GridDetails(ref opts) => { - grid_details::Render { dir, files, colours, style, grid: &opts.grid, details: &opts.details, filter: &self.options.filter, row_threshold: opts.row_threshold }.render(self.git.as_ref(), self.writer) + grid_details::Render { dir, files, colours, style, grid: &opts.grid, details: &opts.details, filter: &self.options.filter, row_threshold: opts.row_threshold }.render(self.git.as_ref(), self.ignore.as_ref(), self.writer) } } } diff --git a/src/fs/dir.rs b/src/fs/dir.rs index c0ba0ea..72cd2ae 100644 --- a/src/fs/dir.rs +++ b/src/fs/dir.rs @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf}; use std::slice::Iter as SliceIter; use fs::File; +use fs::feature::ignore::IgnoreCache; /// A **Dir** provides a cached list of the file paths in a directory that's @@ -43,12 +44,13 @@ impl Dir { /// Produce an iterator of IO results of trying to read all the files in /// this directory. - pub fn files(&self, dots: DotFilter) -> Files { + pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, ignore: Option<&'ig IgnoreCache>) -> Files<'dir, 'ig> { Files { inner: self.contents.iter(), dir: self, dotfiles: dots.shows_dotfiles(), dots: dots.dots(), + ignore: ignore, } } @@ -65,7 +67,7 @@ impl Dir { /// Iterator over reading the contents of a directory as `File` objects. -pub struct Files<'dir> { +pub struct Files<'dir, 'ig> { /// The internal iterator over the paths that have been read already. inner: SliceIter<'dir, PathBuf>, @@ -79,9 +81,11 @@ pub struct Files<'dir> { /// Whether the `.` or `..` directories should be produced first, before /// any files have been listed. dots: Dots, + + ignore: Option<&'ig IgnoreCache>, } -impl<'dir> Files<'dir> { +impl<'dir, 'ig> Files<'dir, 'ig> { fn parent(&self) -> PathBuf { // We can’t use `Path#parent` here because all it does is remove the // last path component, which is no good for us if the path is @@ -124,7 +128,7 @@ enum Dots { } -impl<'dir> Iterator for Files<'dir> { +impl<'dir, 'ig> Iterator for Files<'dir, 'ig> { type Item = Result<File<'dir>, (PathBuf, io::Error)>; fn next(&mut self) -> Option<Self::Item> { diff --git a/src/fs/feature/ignore.rs b/src/fs/feature/ignore.rs new file mode 100644 index 0000000..66a8caf --- /dev/null +++ b/src/fs/feature/ignore.rs @@ -0,0 +1,69 @@ +//! Ignoring globs in `.gitignore` files. +//! +//! This uses a cache because the file with the globs in might not be the same +//! directory that we’re listing! + +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; + +use fs::filter::IgnorePatterns; + + +/// An **ignore cache** holds sets of glob patterns paired with the +/// directories that they should be ignored underneath. +#[derive(Default, Debug)] +pub struct IgnoreCache { + entries: Vec<(PathBuf, IgnorePatterns)> +} + +impl IgnoreCache { + pub fn new() -> IgnoreCache { + IgnoreCache::default() + } + + #[allow(unused_results)] // don’t do this + pub fn discover_underneath(&mut self, path: &Path) { + let mut path = Some(path); + + while let Some(p) = path { + let ignore_file = p.join(".gitignore"); + if ignore_file.is_file() { + if let Ok(mut file) = File::open(ignore_file) { + let mut contents = String::new(); + file.read_to_string(&mut contents).expect("Reading gitignore failed"); + + let (patterns, mut _errors) = IgnorePatterns::parse_from_iter(contents.lines()); + self.entries.push((p.into(), patterns)); + } + } + + path = p.parent(); + } + } + + pub fn is_ignored(&self, suspect: &Path) -> bool { + self.entries.iter().any(|&(ref base_path, ref patterns)| { + if let Ok(suffix) = suspect.strip_prefix(&base_path) { + patterns.is_ignored_path(suffix) + } + else { + false + } + }) + } +} + + + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn empty() { + let ignores = IgnoreCache::default(); + assert_eq!(false, ignores.is_ignored(Path::new("/usr/bin/drinking"))); + assert_eq!(false, ignores.is_ignored(Path::new("target/debug/exa"))); + } +} diff --git a/src/fs/feature/mod.rs b/src/fs/feature/mod.rs index 988ec1f..e0ee7d4 100644 --- a/src/fs/feature/mod.rs +++ b/src/fs/feature/mod.rs @@ -1,7 +1,5 @@ -// Extended attribute support pub mod xattr; - -// Git support +pub mod ignore; #[cfg(feature="git")] pub mod git; diff --git a/src/fs/filter.rs b/src/fs/filter.rs index e4092c5..961b23b 100644 --- a/src/fs/filter.rs +++ b/src/fs/filter.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use std::iter::FromIterator; use std::os::unix::fs::MetadataExt; +use std::path::Path; use glob; use natord; @@ -79,6 +80,12 @@ pub struct FileFilter { /// Glob patterns to ignore. Any file name that matches *any* of these /// patterns won’t be displayed in the list. pub ignore_patterns: IgnorePatterns, + + /// Whether to ignore Git-ignored patterns. + /// This is implemented completely separately from the actual Git + /// repository scanning — a `.gitignore` file will still be scanned even + /// if there’s no `.git` folder present. + pub git_ignore: GitIgnore, } @@ -302,6 +309,14 @@ impl IgnorePatterns { fn is_ignored(&self, file: &str) -> bool { self.patterns.iter().any(|p| p.matches(file)) } + + /// Test whether the given file should be hidden from the results. + pub fn is_ignored_path(&self, file: &Path) -> bool { + self.patterns.iter().any(|p| p.matches_path(file)) + } + + // TODO(ogham): The fact that `is_ignored_path` is pub while `is_ignored` + // isn’t probably means it’s in the wrong place } diff --git a/src/options/filter.rs b/src/options/filter.rs index c3e961f..c22df93 100644 --- a/src/options/filter.rs +++ b/src/options/filter.rs @@ -17,6 +17,7 @@ impl FileFilter { sort_field: SortField::deduce(matches)?, dot_filter: DotFilter::deduce(matches)?, ignore_patterns: IgnorePatterns::deduce(matches)?, + git_ignore: GitIgnore::deduce(matches)?, }) } } diff --git a/src/output/details.rs b/src/output/details.rs index 10a9f07..9b1a14b 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -69,6 +69,7 @@ use ansi_term::Style; use fs::{Dir, File}; use fs::dir_action::RecurseOptions; use fs::filter::FileFilter; +use fs::feature::ignore::IgnoreCache; use fs::feature::git::GitCache; use fs::feature::xattr::{Attribute, FileAttributes}; use style::Colours; @@ -140,7 +141,7 @@ impl<'a> AsRef<File<'a>> for Egg<'a> { impl<'a> Render<'a> { - pub fn render<W: Write>(self, mut git: Option<&'a GitCache>, w: &mut W) -> IOResult<()> { + pub fn render<W: Write>(self, mut git: Option<&'a GitCache>, ignore: Option<&'a IgnoreCache>, w: &mut W) -> IOResult<()> { let mut rows = Vec::new(); if let Some(ref table) = self.opts.table { @@ -161,14 +162,14 @@ impl<'a> Render<'a> { // This is weird, but I can’t find a way around it: // https://internals.rust-lang.org/t/should-option-mut-t-implement-copy/3715/6 let mut table = Some(table); - self.add_files_to_table(&mut table, &mut rows, &self.files, TreeDepth::root()); + self.add_files_to_table(&mut table, &mut rows, &self.files, ignore, TreeDepth::root()); for row in self.iterate_with_table(table.unwrap(), rows) { writeln!(w, "{}", row.strings())? } } else { - self.add_files_to_table(&mut None, &mut rows, &self.files, TreeDepth::root()); + self.add_files_to_table(&mut None, &mut rows, &self.files, ignore, TreeDepth::root()); for row in self.iterate(rows) { writeln!(w, "{}", row.strings())? @@ -180,7 +181,7 @@ impl<'a> Render<'a> { /// Adds files to the table, possibly recursively. This is easily /// parallelisable, and uses a pool of threads. - fn add_files_to_table<'dir>(&self, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &Vec<File<'dir>>, depth: TreeDepth) { + fn add_files_to_table<'dir, 'ig>(&self, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &Vec<File<'dir>>, ignore: Option<&'ig IgnoreCache>, depth: TreeDepth) { use num_cpus; use scoped_threadpool::Pool; use std::sync::{Arc, Mutex}; @@ -282,7 +283,7 @@ impl<'a> Render<'a> { rows.push(row); if let Some(ref dir) = egg.dir { - for file_to_add in dir.files(self.filter.dot_filter) { + for file_to_add in dir.files(self.filter.dot_filter, ignore) { match file_to_add { Ok(f) => files.push(f), Err((path, e)) => errors.push((e, Some(path))) @@ -300,7 +301,7 @@ impl<'a> Render<'a> { rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), false), path)); } - self.add_files_to_table(table, rows, &files, depth.deeper()); + self.add_files_to_table(table, rows, &files, ignore, depth.deeper()); continue; } } diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs index 152ecf4..f4a7748 100644 --- a/src/output/grid_details.rs +++ b/src/output/grid_details.rs @@ -6,6 +6,7 @@ use ansi_term::ANSIStrings; use term_grid as grid; use fs::{Dir, File}; +use fs::feature::ignore::IgnoreCache; use fs::feature::git::GitCache; use fs::feature::xattr::FileAttributes; use fs::filter::FileFilter; @@ -111,16 +112,16 @@ impl<'a> Render<'a> { } } - pub fn render<W: Write>(self, git: Option<&GitCache>, w: &mut W) -> IOResult<()> { - if let Some((grid, width)) = self.find_fitting_grid(git) { + pub fn render<W: Write>(self, git: Option<&GitCache>, ignore: Option<&'a IgnoreCache>, w: &mut W) -> IOResult<()> { + if let Some((grid, width)) = self.find_fitting_grid(git, ignore) { write!(w, "{}", grid.fit_into_columns(width)) } else { - self.give_up().render(git, w) + self.give_up().render(git, ignore, w) } } - pub fn find_fitting_grid(&self, git: Option<&GitCache>) -> Option<(grid::Grid, grid::Width)> { + pub fn find_fitting_grid(&self, git: Option<&GitCache>, ignore: Option<&'a IgnoreCache>) -> Option<(grid::Grid, grid::Width)> { let options = self.details.table.as_ref().expect("Details table options not given!"); let drender = self.details(); |