Implement . and .. by inserting them maually

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.
This commit is contained in:
Benjamin Sago 2017-06-27 01:13:50 +01:00
parent 39fd905999
commit 20793ce7f4
7 changed files with 77 additions and 75 deletions

View File

@ -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)?,
}

View File

@ -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<Dir> {
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 cant be read, or
/// isnt actually a directory, or if theres an IO error that occurs at
/// any point.
///
/// The `read_dir` iterator doesnt actually yield the `.` and `..`
/// entries, so if the user wants to see them, well have to add them
/// ourselves after the files have been read.
pub fn read_dir(path: &Path, dots: DotFilter, git: bool) -> IOResult<Dir> {
let mut paths: Vec<PathBuf> = 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 => {/* Dont 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::Item> {
self.inner.next().map(|path| File::from_path(path, Some(self.dir)).map_err(|t| (path.clone(), t)))
}
}
}
/// 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
}
}

View File

@ -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> {
Dir::read_dir(&*self.path, scan_for_git)
pub fn to_dir(&self, dots: DotFilter, scan_for_git: bool) -> IOResult<Dir> {
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.

View File

@ -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};

View File

@ -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<File>) {
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,
}
}
}

View File

@ -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<T>(misfire: Result<T, Misfire>) -> 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);
}
}

View File

@ -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);
}
}