diff --git a/README.md b/README.md index c02781f..2c3c3f2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ exa’s options are similar, but not exactly the same, as `ls`. ### Filtering Options -- **-a**, **--all**: don't hide hidden and 'dot' files +- **-a**, **--all**: show hidden and 'dot' files - **-d**, **--list-dirs**: list directories like regular files - **-L**, **--level=(depth)**: limit the depth of recursion - **-r**, **--reverse**: reverse the sort order @@ -32,6 +32,8 @@ exa’s options are similar, but not exactly the same, as `ls`. - **--group-directories-first**: list directories before other files - **-I**, **--ignore-glob=(globs)**: glob patterns (pipe-separated) of files to ignore +Pass the `--all` option twice to also show the `.` and `..` directories. + ### Long View Options These options are available when running with --long (`-l`): diff --git a/Vagrantfile b/Vagrantfile index 91d4295..a10aaf5 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -369,6 +369,36 @@ Vagrant.configure(2) do |config| EOF + # Hidden and dot file testcases. + # We need to set the permissions of `.` and `..` because they actually + # get displayed in the output here, so this has to come last. + config.vm.provision :shell, privileged: false, inline: <<-EOF + set -xe + shopt -u dotglob + GLOBIGNORE=".:.." + + mkdir "#{test_dir}/hiddens" + touch "#{test_dir}/hiddens/visible" + touch "#{test_dir}/hiddens/.hidden" + touch "#{test_dir}/hiddens/..extra-hidden" + + # ./hiddens/ + touch -t #{some_date} "#{test_dir}/hiddens/"* + chmod 644 "#{test_dir}/hiddens/"* + sudo chown #{user}:#{user} "#{test_dir}/hiddens/"* + + # . + touch -t #{some_date} "#{test_dir}/hiddens" + chmod 755 "#{test_dir}/hiddens" + sudo chown #{user}:#{user} "#{test_dir}/hiddens" + + # .. + sudo touch -t #{some_date} "#{test_dir}" + sudo chmod 755 "#{test_dir}" + sudo chown #{user}:#{user} "#{test_dir}" + EOF + + # Install kcov for test coverage # This doesn’t run coverage over the xtests so it’s less useful for now if ENV.key?('INSTALL_KCOV') diff --git a/contrib/completions.fish b/contrib/completions.fish index 7c13d7c..460cbb9 100644 --- a/contrib/completions.fish +++ b/contrib/completions.fish @@ -17,7 +17,7 @@ complete -c exa -l 'colour-scale' -d "Highlight levels of file sizes dist # Filtering and sorting options complete -c exa -l 'group-directories-first' -d "Sort directories before other files" -complete -c exa -s 'a' -l 'all' -d "Don't hide hidden and 'dot' files" +complete -c exa -s 'a' -l 'all' -d "Show and 'dot' files" complete -c exa -s 'd' -l 'list-dirs' -d "List directories like regular files" complete -c exa -s 'L' -l 'level' -d "Limit the depth of recursion" -a "1 2 3 4 5 6 7 8 9" complete -c exa -s 'r' -l 'reverse' -d "Reverse the sort order" diff --git a/contrib/completions.zsh b/contrib/completions.zsh index e67453c..8f9be11 100644 --- a/contrib/completions.zsh +++ b/contrib/completions.zsh @@ -14,7 +14,7 @@ __exa() { {--color,--colour}"[When to use terminal colours]" \ {--color,--colour}-scale"[Highlight levels of file sizes distinctly]" \ --group-directories-first"[Sort directories before other files]" \ - {-a,--all}"[Don't hide hidden and 'dot' files]" \ + {-a,--all}"[Show hidden and 'dot' files]" \ {-d,--list-dirs}"[List directories like regular files]" \ {-L,--level}"+[Limit the depth of recursion]" \ {-r,--reverse}"[Reverse the sort order]" \ diff --git a/contrib/man/exa.1 b/contrib/man/exa.1 index 4aecccd..315862c 100644 --- a/contrib/man/exa.1 +++ b/contrib/man/exa.1 @@ -59,7 +59,8 @@ highlight levels of file sizes distinctly .SH FILTERING AND SORTING OPTIONS .TP .B \-a, \-\-all -don\[aq]t hide hidden and \[aq]dot\[aq] files +show hidden and \[aq]dot\[aq] files. +Use this twice to also show the \f[C].\f[] and \f[C]..\f[] directories. .RS .RE .TP diff --git a/src/exa.rs b/src/exa.rs index c1ebe8c..f262bc5 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -24,7 +24,7 @@ extern crate lazy_static; use std::ffi::OsStr; use std::io::{stderr, Write, Result as IOResult}; -use std::path::{Component, Path}; +use std::path::{Component, PathBuf}; use ansi_term::{ANSIStrings, Style}; @@ -75,7 +75,7 @@ impl<'w, W: Write + 'w> Exa<'w, W> { } for file_name in &self.args { - match File::from_path(Path::new(&file_name), None) { + match File::new(PathBuf::from(file_name), None, None) { Err(e) => { exit_status = 2; writeln!(stderr(), "{}: {}", file_name, e)?; @@ -126,7 +126,7 @@ impl<'w, W: Write + 'w> Exa<'w, W> { } let mut children = Vec::new(); - for file in dir.files() { + for file in dir.files(self.options.filter.dot_filter) { match file { Ok(file) => children.push(file), Err((path, e)) => writeln!(stderr(), "[{}: {}]", path.display(), e)?, diff --git a/src/fs/dir.rs b/src/fs/dir.rs index 16adb60..a9e774c 100644 --- a/src/fs/dir.rs +++ b/src/fs/dir.rs @@ -29,26 +29,30 @@ 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: PathBuf, git: bool) -> IOResult { + let contents: Vec = try!(fs::read_dir(&path)? + .map(|result| result.map(|entry| entry.path())) + .collect()); - Ok(Dir { - contents: contents, - path: path.to_path_buf(), - git: if git { Git::scan(path).ok() } else { None }, - }) + let git = if git { Git::scan(&path).ok() } else { None }; + Ok(Dir { contents, path, git }) } /// Produce an iterator of IO results of trying to read all the files in /// this directory. - pub fn files(&self) -> Files { + pub fn files(&self, dots: DotFilter) -> Files { Files { - inner: self.contents.iter(), - dir: self, + inner: self.contents.iter(), + dir: self, + dotfiles: dots.shows_dotfiles(), + dots: dots.dots(), } } @@ -80,14 +84,124 @@ impl Dir { /// Iterator over reading the contents of a directory as `File` objects. pub struct Files<'dir> { + + /// The internal iterator over the paths that have been read already. inner: SliceIter<'dir, PathBuf>, + + /// The directory that begat those paths. dir: &'dir Dir, + + /// Whether to include dotfiles in the list. + dotfiles: bool, + + /// Whether the `.` or `..` directories should be produced first, before + /// any files have been listed. + dots: Dots, } +impl<'dir> Files<'dir> { + 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 + // relative. For example, while the parent of `/testcases/files` is + // `/testcases`, the parent of `.` is an empty path. Adding `..` on + // the end is the only way to get to the *actual* parent directory. + self.dir.path.join("..") + } + + /// Go through the directory until we encounter a file we can list (which + /// varies depending on the dotfile visibility flag) + fn next_visible_file(&mut self) -> Option, (PathBuf, io::Error)>> { + loop { + if let Some(path) = self.inner.next() { + let filename = File::filename(path); + if !self.dotfiles && filename.starts_with(".") { continue } + + return Some(File::new(path.clone(), self.dir, filename) + .map_err(|e| (path.clone(), e))) + } + else { + return None + } + } + } +} + +/// The dot directories that need to be listed before actual files, if any. +/// If these aren’t being printed, then `FilesNext` is used to skip them. +enum Dots { + + /// List the `.` directory next. + DotNext, + + /// List the `..` directory next. + DotDotNext, + + /// Forget about the dot directories and just list files. + FilesNext, +} + + impl<'dir> Iterator for Files<'dir> { type Item = Result, (PathBuf, io::Error)>; fn next(&mut self) -> Option { - self.inner.next().map(|path| File::from_path(path, Some(self.dir)).map_err(|t| (path.clone(), t))) + if let Dots::DotNext = self.dots { + self.dots = Dots::DotDotNext; + Some(File::new(self.dir.path.to_path_buf(), self.dir, String::from(".")) + .map_err(|e| (Path::new(".").to_path_buf(), e))) + } + else if let Dots::DotDotNext = self.dots { + self.dots = Dots::FilesNext; + Some(File::new(self.parent(), self.dir, String::from("..")) + .map_err(|e| (self.parent(), e))) + } + else { + self.next_visible_file() + } } -} \ 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 + } +} + +impl DotFilter { + + /// Whether this filter should show dotfiles in a listing. + fn shows_dotfiles(&self) -> bool { + match *self { + DotFilter::JustFiles => false, + DotFilter::Dotfiles => true, + DotFilter::DotfilesAndDots => true, + } + } + + /// Whether this filter should add dot directories to a listing. + fn dots(&self) -> Dots { + match *self { + DotFilter::JustFiles => Dots::FilesNext, + DotFilter::Dotfiles => Dots::FilesNext, + DotFilter::DotfilesAndDots => Dots::DotNext, + } + } +} diff --git a/src/fs/file.rs b/src/fs/file.rs index c0a0653..3defdd7 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -53,34 +53,49 @@ pub struct File<'dir> { /// However, *directories* that get passed in will produce files that /// contain a reference to it, which is used in certain operations (such /// as looking up a file's Git status). - pub dir: Option<&'dir Dir>, + pub parent_dir: Option<&'dir Dir>, } impl<'dir> File<'dir> { + pub fn new(path: PathBuf, parent_dir: PD, filename: FN) -> IOResult> + where PD: Into>, + FN: Into> + { + let parent_dir = parent_dir.into(); + let metadata = fs::symlink_metadata(&path)?; + let name = filename.into().unwrap_or_else(|| File::filename(&path)); + let ext = File::ext(&path); - /// Create a new `File` object from the given `Path`, inside the given - /// `Dir`, if appropriate. - /// - /// This uses `symlink_metadata` instead of `metadata`, which doesn't - /// follow symbolic links. - pub fn from_path(path: &Path, parent: Option<&'dir Dir>) -> IOResult> { - fs::symlink_metadata(path).map(|metadata| File::with_metadata(metadata, path, parent)) + Ok(File { path, parent_dir, metadata, ext, name }) } - /// Create a new File object from the given metadata result, and other data. - pub fn with_metadata(metadata: fs::Metadata, path: &Path, parent: Option<&'dir Dir>) -> File<'dir> { - let filename = match path.components().next_back() { - Some(comp) => comp.as_os_str().to_string_lossy().to_string(), - None => String::new(), + /// A file’s name is derived from its string. This needs to handle directories + /// such as `/` or `..`, which have no `file_name` component. So instead, just + /// use the last component as the name. + pub fn filename(path: &Path) -> String { + match path.components().next_back() { + Some(back) => back.as_os_str().to_string_lossy().to_string(), + None => path.display().to_string(), // use the path as fallback + } + } + + /// Extract an extension from a file path, if one is present, in lowercase. + /// + /// The extension is the series of characters after the last dot. This + /// deliberately counts dotfiles, so the ".git" folder has the extension "git". + /// + /// ASCII lowercasing is used because these extensions are only compared + /// against a pre-compiled list of extensions which are known to only exist + /// within ASCII, so it's alright. + fn ext(path: &Path) -> Option { + use std::ascii::AsciiExt; + + let name = match path.file_name() { + Some(f) => f.to_string_lossy().to_string(), + None => return None, }; - File { - path: path.to_path_buf(), - dir: parent, - metadata: metadata, - ext: ext(path), - name: filename, - } + name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase()) } /// Whether this file is a directory on the filesystem. @@ -95,7 +110,7 @@ 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) + Dir::read_dir(self.path.clone(), scan_for_git) } /// Whether this file is a regular file on the filesystem - that is, not a @@ -119,32 +134,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. @@ -152,7 +160,7 @@ impl<'dir> File<'dir> { if path.is_absolute() { path.to_path_buf() } - else if let Some(dir) = self.dir { + else if let Some(dir) = self.parent_dir { dir.join(&*path) } else if let Some(parent) = self.path.parent() { @@ -179,20 +187,22 @@ impl<'dir> File<'dir> { // this file -- which could be absolute or relative -- to the path // we actually look up and turn into a `File` -- which needs to be // absolute to be accessible from any directory. - let display_path = match fs::read_link(&self.path) { - Ok(path) => path, - Err(e) => return FileTarget::Err(e), + let path = match fs::read_link(&self.path) { + Ok(p) => p, + Err(e) => return FileTarget::Err(e), }; - let target_path = self.reorient_target_path(&*display_path); + let absolute_path = self.reorient_target_path(&path); // Use plain `metadata` instead of `symlink_metadata` - we *want* to // follow links. - if let Ok(metadata) = fs::metadata(&target_path) { - FileTarget::Ok(File::with_metadata(metadata, &*display_path, None)) + if let Ok(metadata) = fs::metadata(&absolute_path) { + let ext = File::ext(&path); + let name = File::filename(&path); + FileTarget::Ok(File { parent_dir: None, path, ext, metadata, name }) } else { - FileTarget::Broken(display_path) + FileTarget::Broken(path) } } @@ -356,7 +366,7 @@ impl<'dir> File<'dir> { pub fn git_status(&self) -> f::Git { use std::env::current_dir; - match self.dir { + match self.parent_dir { None => f::Git { staged: f::GitStatus::NotModified, unstaged: f::GitStatus::NotModified }, Some(d) => { let cwd = match current_dir() { @@ -378,26 +388,6 @@ impl<'a> AsRef> for File<'a> { } -/// Extract an extension from a file path, if one is present, in lowercase. -/// -/// The extension is the series of characters after the last dot. This -/// deliberately counts dotfiles, so the ".git" folder has the extension "git". -/// -/// ASCII lowercasing is used because these extensions are only compared -/// against a pre-compiled list of extensions which are known to only exist -/// within ASCII, so it's alright. -fn ext(path: &Path) -> Option { - use std::ascii::AsciiExt; - - let name = match path.file_name() { - Some(f) => f.to_string_lossy().to_string(), - None => return None, - }; - - name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase()) -} - - /// The result of following a symlink. pub enum FileTarget<'dir> { @@ -459,22 +449,59 @@ mod modes { #[cfg(test)] -mod test { - use super::ext; +mod ext_test { + use super::File; use std::path::Path; #[test] fn extension() { - assert_eq!(Some("dat".to_string()), ext(Path::new("fester.dat"))) + assert_eq!(Some("dat".to_string()), File::ext(Path::new("fester.dat"))) } #[test] fn dotfile() { - assert_eq!(Some("vimrc".to_string()), ext(Path::new(".vimrc"))) + assert_eq!(Some("vimrc".to_string()), File::ext(Path::new(".vimrc"))) } #[test] fn no_extension() { - assert_eq!(None, ext(Path::new("jarlsberg"))) + assert_eq!(None, File::ext(Path::new("jarlsberg"))) + } +} + + +#[cfg(test)] +mod filename_test { + use super::File; + use std::path::Path; + + #[test] + fn file() { + assert_eq!("fester.dat", File::filename(Path::new("fester.dat"))) + } + + #[test] + fn no_path() { + assert_eq!("foo.wha", File::filename(Path::new("/var/cache/foo.wha"))) + } + + #[test] + fn here() { + assert_eq!(".", File::filename(Path::new("."))) + } + + #[test] + fn there() { + assert_eq!("..", File::filename(Path::new(".."))) + } + + #[test] + fn everywhere() { + assert_eq!("..", File::filename(Path::new("./.."))) + } + + #[test] + fn topmost() { + assert_eq!("/", File::filename(Path::new("/"))) } } 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/info/filetype.rs b/src/info/filetype.rs index 74491b7..47af438 100644 --- a/src/info/filetype.rs +++ b/src/info/filetype.rs @@ -80,7 +80,7 @@ impl<'a> File<'a> { if self.extension_is_one_of( &[ "class", "elc", "hi", "o", "pyc" ]) { true } - else if let Some(dir) = self.dir { + else if let Some(dir) = self.parent_dir { self.get_source_files().iter().any(|path| dir.contains(path)) } else { diff --git a/src/options/filter.rs b/src/options/filter.rs index f3acbfb..c608cfc 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 `..`: @@ -60,7 +62,7 @@ pub struct FileFilter { /// most of them are or whether they're still needed. Every file name /// evaluation that goes through my home directory is slowed down by /// this accumulated sludge. - show_invisibles: bool, + pub dot_filter: DotFilter, /// Glob patterns to ignore. Any file name that matches *any* of these /// patterns won't be displayed in the list. @@ -76,7 +78,7 @@ impl FileFilter { list_dirs_first: matches.opt_present("group-directories-first"), reverse: matches.opt_present("reverse"), sort_field: SortField::deduce(matches)?, - show_invisibles: matches.opt_present("all"), + dot_filter: DotFilter::deduce(matches)?, ignore_patterns: IgnorePatterns::deduce(matches)?, }) } @@ -84,10 +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) { - if !self.show_invisibles { - files.retain(|f| !f.is_dotfile()); - } - files.retain(|f| !self.ignore_patterns.is_ignored(f)); } @@ -252,6 +250,24 @@ impl SortField { } +impl DotFilter { + pub fn deduce(matches: &getopts::Matches) -> Result { + let dots = match matches.opt_count("all") { + 0 => return Ok(DotFilter::JustFiles), + 1 => DotFilter::Dotfiles, + _ => DotFilter::DotfilesAndDots, + }; + + if matches.opt_present("tree") { + Err(Misfire::Useless("all --all", true, "tree")) + } + else { + Ok(dots) + } + } +} + + #[derive(PartialEq, Default, Debug, Clone)] struct IgnorePatterns { patterns: Vec, diff --git a/src/options/help.rs b/src/options/help.rs index 733a4cd..e06d275 100644 --- a/src/options/help.rs +++ b/src/options/help.rs @@ -17,7 +17,7 @@ DISPLAY OPTIONS --colo[u]r-scale highlight levels of file sizes distinctly FILTERING AND SORTING OPTIONS - -a, --all don't hide hidden and 'dot' files + -a, --all show hidden and 'dot' files -d, --list-dirs list directories like regular files -r, --reverse reverse the sort order -s, --sort SORT_FIELD which field to sort by: diff --git a/src/options/mod.rs b/src/options/mod.rs index c37ff42..fccc08c 100644 --- a/src/options/mod.rs +++ b/src/options/mod.rs @@ -69,7 +69,7 @@ impl Options { // Filtering and sorting options opts.optflag("", "group-directories-first", "sort directories before other files"); - opts.optflag("a", "all", "don't hide hidden and 'dot' files"); + opts.optflagmulti("a", "all", "show hidden and 'dot' files"); opts.optflag("d", "list-dirs", "list directories like regular files"); opts.optopt ("L", "level", "limit the depth of recursion", "DEPTH"); opts.optflag("r", "reverse", "reverse the sert order"); @@ -145,6 +145,7 @@ impl Options { #[cfg(test)] mod test { use super::{Options, Misfire, SortField, SortCase}; + use fs::DotFilter; use fs::feature::xattr; fn is_helpful(misfire: Result) -> bool { @@ -277,4 +278,29 @@ mod test { let opts = Options::getopts(&[ "--level", "69105" ]); assert_eq!(opts.unwrap_err(), Misfire::Useless2("level", "recurse", "tree")) } + + #[test] + fn all_all_with_tree() { + let opts = Options::getopts(&[ "--all", "--all", "--tree" ]); + assert_eq!(opts.unwrap_err(), Misfire::Useless("all --all", true, "tree")) + } + + #[test] + fn nowt() { + let nothing: Vec = Vec::new(); + let dots = Options::getopts(¬hing).unwrap().0.filter.dot_filter; + assert_eq!(dots, DotFilter::JustFiles); + } + + #[test] + fn all() { + let dots = Options::getopts(&[ "--all".to_string() ]).unwrap().0.filter.dot_filter; + 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::DotfilesAndDots); + } } diff --git a/src/output/details.rs b/src/output/details.rs index 899bc89..f514a46 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -344,7 +344,7 @@ impl<'a> Render<'a> { table.rows.push(row); if let Some(ref dir) = egg.dir { - for file_to_add in dir.files() { + for file_to_add in dir.files(self.filter.dot_filter) { match file_to_add { Ok(f) => files.push(f), Err((path, e)) => errors.push((e, Some(path))) diff --git a/src/output/file_name.rs b/src/output/file_name.rs index 1ba0fed..a2b138a 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -49,7 +49,7 @@ impl<'a, 'dir> FileName<'a, 'dir> { pub fn paint(&self) -> TextCellContents { let mut bits = Vec::new(); - if self.file.dir.is_none() { + if self.file.parent_dir.is_none() { if let Some(parent) = self.file.path.parent() { self.add_parent_bits(&mut bits, parent); } diff --git a/xtests/help b/xtests/help index 8f9b79d..d6211c3 100644 --- a/xtests/help +++ b/xtests/help @@ -16,7 +16,7 @@ DISPLAY OPTIONS --colo[u]r-scale highlight levels of file sizes distinctly FILTERING AND SORTING OPTIONS - -a, --all don't hide hidden and 'dot' files + -a, --all show hidden and 'dot' files -d, --list-dirs list directories like regular files -r, --reverse reverse the sort order -s, --sort SORT_FIELD which field to sort by: diff --git a/xtests/hiddens b/xtests/hiddens new file mode 100644 index 0000000..9afbc9c --- /dev/null +++ b/xtests/hiddens @@ -0,0 +1 @@ +visible diff --git a/xtests/hiddens_a b/xtests/hiddens_a new file mode 100644 index 0000000..5300dfd --- /dev/null +++ b/xtests/hiddens_a @@ -0,0 +1 @@ +..extra-hidden .hidden visible diff --git a/xtests/hiddens_aa b/xtests/hiddens_aa new file mode 100644 index 0000000..f2b5c68 --- /dev/null +++ b/xtests/hiddens_aa @@ -0,0 +1 @@ +. .. ..extra-hidden .hidden visible diff --git a/xtests/hiddens_l b/xtests/hiddens_l new file mode 100644 index 0000000..8e6e048 --- /dev/null +++ b/xtests/hiddens_l @@ -0,0 +1 @@ +.rw-r--r-- 0 cassowary  1 Jan 12:34 visible diff --git a/xtests/hiddens_la b/xtests/hiddens_la new file mode 100644 index 0000000..c8d8f9d --- /dev/null +++ b/xtests/hiddens_la @@ -0,0 +1,3 @@ +.rw-r--r-- 0 cassowary  1 Jan 12:34 ..extra-hidden +.rw-r--r-- 0 cassowary  1 Jan 12:34 .hidden +.rw-r--r-- 0 cassowary  1 Jan 12:34 visible diff --git a/xtests/hiddens_laa b/xtests/hiddens_laa new file mode 100644 index 0000000..de014cb --- /dev/null +++ b/xtests/hiddens_laa @@ -0,0 +1,5 @@ +drwxr-xr-x - cassowary  1 Jan 12:34 . +drwxr-xr-x - cassowary  1 Jan 12:34 .. +.rw-r--r-- 0 cassowary  1 Jan 12:34 ..extra-hidden +.rw-r--r-- 0 cassowary  1 Jan 12:34 .hidden +.rw-r--r-- 0 cassowary  1 Jan 12:34 visible diff --git a/xtests/run.sh b/xtests/run.sh index 4e36285..8583909 100755 --- a/xtests/run.sh +++ b/xtests/run.sh @@ -38,7 +38,7 @@ COLUMNS=120 $exa $testcases/files | diff -q - $results/files_120 || exit 1 COLUMNS=160 $exa $testcases/files | diff -q - $results/files_160 || exit 1 COLUMNS=200 $exa $testcases/files | diff -q - $results/files_200 || exit 1 -COLUMNS=100 $exa $testcases/files/* | diff -q - $results/files_star_100 || exit 1 +COLUMNS=100 $exa $testcases/files/* | diff -q - $results/files_star_100 || exit 1 COLUMNS=150 $exa $testcases/files/* | diff -q - $results/files_star_150 || exit 1 COLUMNS=200 $exa $testcases/files/* | diff -q - $results/files_star_200 || exit 1 @@ -120,8 +120,20 @@ COLUMNS=80 $exa_binary --colour=automatic $testcases/files -l | diff -q - $resul $exa $testcases/git/additions -l --git 2>&1 | diff -q - $results/git_additions || exit 1 $exa $testcases/git/edits -l --git 2>&1 | diff -q - $results/git_edits || exit 1 + +# Hidden files +COLUMNS=80 $exa $testcases/hiddens 2>&1 | diff -q - $results/hiddens || exit 1 +COLUMNS=80 $exa $testcases/hiddens -a 2>&1 | diff -q - $results/hiddens_a || exit 1 +COLUMNS=80 $exa $testcases/hiddens -aa 2>&1 | diff -q - $results/hiddens_aa || exit 1 + +$exa $testcases/hiddens -l 2>&1 | diff -q - $results/hiddens_l || exit 1 +$exa $testcases/hiddens -l -a 2>&1 | diff -q - $results/hiddens_la || exit 1 +$exa $testcases/hiddens -l -aa 2>&1 | diff -q - $results/hiddens_laa || exit 1 + + # And finally... $exa --help | diff -q - $results/help || exit 1 $exa --help --long | diff -q - $results/help_long || exit 1 + echo "All the tests passed!"