From 20793ce7f4b0a891bf2a7c35a817f7d3818295a4 Mon Sep 17 00:00:00 2001 From: Benjamin Sago Date: Tue, 27 Jun 2017 01:13:50 +0100 Subject: [PATCH] Implement . and .. by inserting them maually MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I originally thought that the entries . and .. were in *every* directory entry, and exa was already doing something to filter it out. And then... I could find no such code! Turns out, if we want those entries present, we have to insert them ourselves. This was harder than expected. Because the file filter doesn’t have access to the parent directory path, it can’t “filter” the files vector by inserting the files at the beginning. Instead, we do it at the iterator level. A directory can be scanned in three different ways depending on what sort of dotfiles, if any, are wanted. At this point, we already have access to the parent directory’s path, so we can just insert them manually. The enum got moved to the dir module because it’s used most there. --- src/exa.rs | 4 ++-- src/fs/dir.rs | 52 ++++++++++++++++++++++++++++++++++++------- src/fs/file.rs | 47 +++++++++++++++++--------------------- src/fs/mod.rs | 2 +- src/options/filter.rs | 39 +++++--------------------------- src/options/mod.rs | 6 ++--- src/output/details.rs | 2 +- 7 files changed, 77 insertions(+), 75 deletions(-) diff --git a/src/exa.rs b/src/exa.rs index c1ebe8c..aecf717 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -82,7 +82,7 @@ impl<'w, W: Write + 'w> Exa<'w, W> { }, Ok(f) => { if f.is_directory() && !self.options.dir_action.treat_dirs_as_files() { - match f.to_dir(self.options.should_scan_for_git()) { + match f.to_dir(self.options.filter.dot_filter, self.options.should_scan_for_git()) { Ok(d) => dirs.push(d), Err(e) => writeln!(stderr(), "{}: {}", file_name, e)?, } @@ -142,7 +142,7 @@ impl<'w, W: Write + 'w> Exa<'w, W> { let mut child_dirs = Vec::new(); for child_dir in children.iter().filter(|f| f.is_directory()) { - match child_dir.to_dir(false) { + match child_dir.to_dir(self.options.filter.dot_filter, false) { Ok(d) => child_dirs.push(d), Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?, } diff --git a/src/fs/dir.rs b/src/fs/dir.rs index 16adb60..802e583 100644 --- a/src/fs/dir.rs +++ b/src/fs/dir.rs @@ -29,15 +29,28 @@ pub struct Dir { impl Dir { /// Create a new Dir object filled with all the files in the directory - /// pointed to by the given path. Fails if the directory can't be read, or - /// isn't actually a directory, or if there's an IO error that occurs - /// while scanning. - pub fn read_dir(path: &Path, git: bool) -> IOResult { - let reader = fs::read_dir(path)?; - let contents = try!(reader.map(|e| e.map(|e| e.path())).collect()); + /// pointed to by the given path. Fails if the directory can’t be read, or + /// isn’t actually a directory, or if there’s an IO error that occurs at + /// any point. + /// + /// The `read_dir` iterator doesn’t actually yield the `.` and `..` + /// entries, so if the user wants to see them, we’ll have to add them + /// ourselves after the files have been read. + pub fn read_dir(path: &Path, dots: DotFilter, git: bool) -> IOResult { + let mut paths: Vec = try!(fs::read_dir(path)? + .map(|result| result.map(|entry| entry.path())) + .collect()); + match dots { + DotFilter::JustFiles => paths.retain(|p| p.file_name().and_then(|name| name.to_str()).map(|s| !s.starts_with('.')).unwrap_or(true)), + DotFilter::Dotfiles => {/* Don’t add or remove anything */}, + DotFilter::DotfilesAndDots => { + paths.insert(0, path.join("..")); + paths.insert(0, path.join(".")); + } + } Ok(Dir { - contents: contents, + contents: paths, path: path.to_path_buf(), git: if git { Git::scan(path).ok() } else { None }, }) @@ -90,4 +103,27 @@ impl<'dir> Iterator for Files<'dir> { fn next(&mut self) -> Option { self.inner.next().map(|path| File::from_path(path, Some(self.dir)).map_err(|t| (path.clone(), t))) } -} \ No newline at end of file +} + + +/// Usually files in Unix use a leading dot to be hidden or visible, but two +/// entries in particular are "extra-hidden": `.` and `..`, which only become +/// visible after an extra `-a` option. +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum DotFilter { + + /// Shows files, dotfiles, and `.` and `..`. + DotfilesAndDots, + + /// Show files and dotfiles, but hide `.` and `..`. + Dotfiles, + + /// Just show files, hiding anything beginning with a dot. + JustFiles, +} + +impl Default for DotFilter { + fn default() -> DotFilter { + DotFilter::JustFiles + } +} diff --git a/src/fs/file.rs b/src/fs/file.rs index c0a0653..1b635b0 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -6,7 +6,7 @@ use std::io::Result as IOResult; use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt}; use std::path::{Path, PathBuf}; -use fs::dir::Dir; +use fs::dir::{Dir, DotFilter}; use fs::fields as f; @@ -94,8 +94,8 @@ impl<'dir> File<'dir> { /// /// Returns an IO error upon failure, but this shouldn't be used to check /// if a `File` is a directory or not! For that, just use `is_directory()`. - pub fn to_dir(&self, scan_for_git: bool) -> IOResult { - Dir::read_dir(&*self.path, scan_for_git) + pub fn to_dir(&self, dots: DotFilter, scan_for_git: bool) -> IOResult { + Dir::read_dir(&*self.path, dots, scan_for_git) } /// Whether this file is a regular file on the filesystem - that is, not a @@ -119,32 +119,25 @@ impl<'dir> File<'dir> { /// Whether this file is a named pipe on the filesystem. pub fn is_pipe(&self) -> bool { - self.metadata.file_type().is_fifo() - } - - /// Whether this file is a char device on the filesystem. - pub fn is_char_device(&self) -> bool { - self.metadata.file_type().is_char_device() - } - - /// Whether this file is a block device on the filesystem. - pub fn is_block_device(&self) -> bool { - self.metadata.file_type().is_block_device() - } - - /// Whether this file is a socket on the filesystem. - pub fn is_socket(&self) -> bool { - self.metadata.file_type().is_socket() - } - - - /// Whether this file is a dotfile, based on its name. In Unix, file names - /// beginning with a dot represent system or configuration files, and - /// should be hidden by default. - pub fn is_dotfile(&self) -> bool { - self.name.starts_with('.') + self.metadata.file_type().is_fifo() } + /// Whether this file is a char device on the filesystem. + pub fn is_char_device(&self) -> bool { + self.metadata.file_type().is_char_device() + } + + /// Whether this file is a block device on the filesystem. + pub fn is_block_device(&self) -> bool { + self.metadata.file_type().is_block_device() + } + + /// Whether this file is a socket on the filesystem. + pub fn is_socket(&self) -> bool { + self.metadata.file_type().is_socket() + } + + /// Re-prefixes the path pointed to by this file, if it's a symlink, to /// make it an absolute path that can be accessed from whichever /// directory exa is being run from. diff --git a/src/fs/mod.rs b/src/fs/mod.rs index c50eb1a..53ce5b0 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -1,5 +1,5 @@ mod dir; -pub use self::dir::Dir; +pub use self::dir::{Dir, DotFilter}; mod file; pub use self::file::{File, FileTarget}; diff --git a/src/options/filter.rs b/src/options/filter.rs index 02e1d43..b1942f5 100644 --- a/src/options/filter.rs +++ b/src/options/filter.rs @@ -6,6 +6,7 @@ use glob; use natord; use fs::File; +use fs::DotFilter; use options::misfire::Misfire; @@ -27,11 +28,12 @@ pub struct FileFilter { /// ones, depending on the sort field. pub reverse: bool, - /// Whether to include invisible “dot” files when listing a directory. + /// Which invisible “dot” files to include when listing a directory. /// /// Files starting with a single “.” are used to determine “system” or /// “configuration” files that should not be displayed in a regular - /// directory listing. + /// directory listing, and the directory entries “.” and “..” are + /// considered extra-special. /// /// This came about more or less by a complete historical accident, /// when the original `ls` tried to hide `.` and `..`: @@ -84,12 +86,6 @@ impl FileFilter { /// Remove every file in the given vector that does *not* pass the /// filter predicate for files found inside a directory. pub fn filter_child_files(&self, files: &mut Vec) { - match self.dot_filter { - DotFilter::JustFiles => files.retain(|f| !f.is_dotfile()), - DotFilter::ShowDotfiles => {/* keep all elements */}, - DotFilter::ShowDotfilesAndDots => unimplemented!(), - } - files.retain(|f| !self.ignore_patterns.is_ignored(f)); } @@ -254,35 +250,12 @@ impl SortField { } -/// Usually files in Unix use a leading dot to be hidden or visible, but two -/// entries in particular are "extra-hidden": `.` and `..`, which only become -/// visible after an extra `-a` option. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum DotFilter { - - /// Shows files, dotfiles, and `.` and `..`. - ShowDotfilesAndDots, - - /// Show files and dotfiles, but hide `.` and `..`. - ShowDotfiles, - - /// Just show files, hiding anything beginning with a dot. - JustFiles, -} - -impl Default for DotFilter { - fn default() -> DotFilter { - DotFilter::JustFiles - } -} - - impl DotFilter { pub fn deduce(matches: &getopts::Matches) -> DotFilter { match matches.opt_count("all") { 0 => DotFilter::JustFiles, - 1 => DotFilter::ShowDotfiles, - _ => DotFilter::ShowDotfilesAndDots, + 1 => DotFilter::Dotfiles, + _ => DotFilter::DotfilesAndDots, } } } diff --git a/src/options/mod.rs b/src/options/mod.rs index 7625982..dab8673 100644 --- a/src/options/mod.rs +++ b/src/options/mod.rs @@ -145,7 +145,7 @@ impl Options { #[cfg(test)] mod test { use super::{Options, Misfire, SortField, SortCase}; - use super::filter::DotFilter; + use fs::DotFilter; use fs::feature::xattr; fn is_helpful(misfire: Result) -> bool { @@ -290,12 +290,12 @@ mod test { #[test] fn all() { let dots = Options::getopts(&[ "--all".to_string() ]).unwrap().0.filter.dot_filter; - assert_eq!(dots, DotFilter::ShowDotfiles); + assert_eq!(dots, DotFilter::Dotfiles); } #[test] fn allall() { let dots = Options::getopts(&[ "-a".to_string(), "-a".to_string() ]).unwrap().0.filter.dot_filter; - assert_eq!(dots, DotFilter::ShowDotfilesAndDots); + assert_eq!(dots, DotFilter::DotfilesAndDots); } } diff --git a/src/output/details.rs b/src/output/details.rs index 899bc89..405fe83 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -315,7 +315,7 @@ impl<'a> Render<'a> { if let Some(r) = self.recurse { if file.is_directory() && r.tree && !r.is_too_deep(depth) { - if let Ok(d) = file.to_dir(false) { + if let Ok(d) = file.to_dir(self.filter.dot_filter, false) { dir = Some(d); } }