diff --git a/Justfile b/Justfile index 6e7ff9984..8a2e6e5ab 100644 --- a/Justfile +++ b/Justfile @@ -269,7 +269,7 @@ gen_test_dir: nix build -L ./#trydump - find result/dump -type f \( -name "*.stdout" -o -name "*.stderr" \) -exec sh -c 'base=$(basename {}); if [ -e "tests/gen/${base%.*}.toml" ]; then cp {} tests/gen/; elif [ -e "tests/cmd/${base%.*}.toml" ]; then cp {} tests/cmd/; elif [ -e "tests/ptests/${base%.*}.toml" ]; then cp {} tests/ptests/; fi' \; + find result/dump -type f \( -name "*.stdout" -o -name "*.stderr" \) -exec sh -c 'base=$(basename {}); if [ -e "tests/gen/${base%.*}.toml" ]; then cp {} tests/gen/; elif [ -e "tests/ptests/${base%.*}.toml" ]; then cp {} tests/ptests/; fi' \; @itest-gen: diff --git a/README.md b/README.md index 976450005..472371f08 100644 --- a/README.md +++ b/README.md @@ -108,8 +108,10 @@ eza’s options are almost, but not quite, entirely unlike `ls`’s. Quick overv - **-r**, **--reverse**: reverse the sort order - **-s**, **--sort=(field)**: which field to sort by - **--group-directories-first**: list directories before other files -- **-D**, **--only-dirs**: list only directories -- **-f**, **--only-files**: list only files +- **-D**, **--only-dirs**: list only directories and links to directories +- **-f**, **--only-files**: list only files and links to files +- **--only-links**: list only symbolic links +- **--no-links**: don't list symbolic links in **--only-files** or **--only-dirs** cases - **--git-ignore**: ignore files mentioned in `.gitignore` - **-I**, **--ignore-glob=(globs)**: glob patterns (pipe-separated) of files to ignore diff --git a/completions/fish/eza.fish b/completions/fish/eza.fish index 97a579c6f..d89025497 100644 --- a/completions/fish/eza.fish +++ b/completions/fish/eza.fish @@ -79,8 +79,10 @@ complete -c eza -s s -l sort -d "Which field to sort by" -x -a " " complete -c eza -s I -l ignore-glob -d "Ignore files that match these glob patterns" -r -complete -c eza -s D -l only-dirs -d "List only directories" -complete -c eza -s f -l only-files -d "List only files" +complete -c eza -s D -l only-dirs -d "List only directories and links to directories" +complete -c eza -s f -l only-files -d "List only files and links to files" +complete -c eza -l only-links -d "List only links" +complete -c eza -l no-links -d "Doesn't list links in --only-files or --only-dirs context" # Long view options complete -c eza -s b -l binary -d "List file sizes with binary prefixes" diff --git a/completions/nush/eza.nu b/completions/nush/eza.nu index 1859c0a1e..d4959701f 100644 --- a/completions/nush/eza.nu +++ b/completions/nush/eza.nu @@ -28,8 +28,10 @@ export extern "eza" [ --width(-w) # Limits column output of grid, 0 implies auto-width --reverse(-r) # Reverse the sort order --sort(-s) # Which field to sort by - --only-dirs(-D) # List only directories - --only-files(-f) # List only files + --only-dirs(-D) # List only directories and links to dirs + --only-files(-f) # List only files and links to files + --only-links # List only links + --no-links # Don't list links in --only-links and --only-files context --binary(-b) # List file sizes with binary prefixes --bytes(-B) # List file sizes in bytes, without any prefixes --group(-g) # List each file's group diff --git a/completions/zsh/_eza b/completions/zsh/_eza index ed1f0c299..f71707f1b 100644 --- a/completions/zsh/_eza +++ b/completions/zsh/_eza @@ -32,8 +32,10 @@ __eza() { {-a,--all}"[Show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories]" \ {-A,--almost-all}"[Equivalent to --all; included for compatibility with \'ls -A\']" \ {-d,--list-dirs}"[List directories like regular files]" \ - {-D,--only-dirs}"[List only directories]" \ - {-f,--only-files}"[List only files]" \ + {-D,--only-dirs}"[List only directories and links to directories]" \ + {-f,--only-files}"[List only files and links to files]" \ + --only-links"[List only links]" \ + --no-links"[Doesn't list links in --only-files or --only-dirs context]" \ {-L,--level}"+[Limit the depth of recursion]" \ {-w,--width}"+[Limits column output of grid, 0 implies auto-width]" \ {-r,--reverse}"[Reverse the sort order]" \ diff --git a/man/eza.1.md b/man/eza.1.md index a6d43fd86..3d52a5837 100644 --- a/man/eza.1.md +++ b/man/eza.1.md @@ -161,11 +161,16 @@ Sort fields starting with a capital letter will sort uppercase before lowercase: : List directories before other files. `-D`, `--only-dirs` -: List only directories, not files. +: List only directories and links pointing to directories, not files. `-f`, `--only-files` -: List only files, not directories. +: List only files and links pointing to files, not directories. +`--only-links` +: List only links + +`--only-links` +: Don't show links in `--only-files` or `--only-dirs` cases LONG VIEW OPTIONS ================= diff --git a/powertest.yaml b/powertest.yaml index 8889db07d..d027c9f13 100644 --- a/powertest.yaml +++ b/powertest.yaml @@ -135,6 +135,10 @@ commands: - --only-files ? - -f - --only-files + ? - null + - --only-links + ? - null + - --no-links ? # TODO: add more globs - -I - --ignore-glob diff --git a/src/fs/file.rs b/src/fs/file.rs index ba2703d91..55092ab2c 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -287,6 +287,28 @@ impl<'dir> File<'dir> { false } + pub fn symlink_target_is_directory(&self) -> bool { + if self.is_link() { + let target = self.link_target(); + if let FileTarget::Ok(target) = target { + return target.is_directory(); + } + } + + false + } + + pub fn symlink_target_is_file(&self) -> bool { + if self.is_link() { + let target = self.link_target(); + if let FileTarget::Ok(target) = target { + return target.is_file(); + } + } + + false + } + /// If this file is a directory on the filesystem, then clone its /// `PathBuf` for use in one of our own `Dir` values, and read a list of /// its contents. @@ -304,6 +326,17 @@ impl<'dir> File<'dir> { self.metadata.is_file() } + pub fn points_to_file(&self) -> bool { + if self.is_link() { + let target = self.link_target(); + if let FileTarget::Ok(target) = target { + return target.points_to_file(); + } + } + + false + } + /// Whether this file is both a regular file *and* executable for the /// current user. An executable file has a different purpose from an /// executable directory, so they should be highlighted differently. diff --git a/src/fs/filter.rs b/src/fs/filter.rs index 162cf0ac0..26cd4d728 100644 --- a/src/fs/filter.rs +++ b/src/fs/filter.rs @@ -1,5 +1,6 @@ //! Filtering and sorting the list of files before displaying them. +use log::debug; use std::cmp::Ordering; use std::iter::FromIterator; #[cfg(unix)] @@ -21,6 +22,12 @@ pub enum FileFilterFlags { /// Whether to only show files. OnlyFiles, + + /// Whether to only show links. + OnlyLinks, + + /// Whether to ignore links. + NoLinks, } /// The **file filter** processes a list of files before displaying them to @@ -74,21 +81,57 @@ 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>) { - use FileFilterFlags::{OnlyDirs, OnlyFiles}; + use FileFilterFlags::{NoLinks, OnlyDirs, OnlyFiles, OnlyLinks}; files.retain(|f| !self.ignore_patterns.is_ignored(&f.name)); + debug!("Filtering files: {:?}", self.flags); + match ( self.flags.contains(&OnlyDirs), self.flags.contains(&OnlyFiles), + self.flags.contains(&OnlyLinks), ) { - (true, false) => { + (true, false, false) => { // On pass -'-only-dirs' flag only - files.retain(File::is_directory); + if self.flags.contains(&NoLinks) { + files.retain(File::is_directory); + } else { + files.retain(File::points_to_directory); + } + } + (true, false, true) => { + // On pass -'-only-dirs' and '-only-links' flags + if !self.flags.contains(&NoLinks) { + files.retain(File::symlink_target_is_directory); + } } - (false, true) => { + (false, true, false) => { // On pass -'-only-files' flag only - files.retain(File::is_file); + if self.flags.contains(&NoLinks) { + files.retain(File::is_file); + } else { + files.retain(File::points_to_file); + } + } + (false, true, true) => { + debug!("Filtering files: links and files"); + // On pass -'-only-files' and '-only-links' flags + if !self.flags.contains(&NoLinks) { + files.retain(File::symlink_target_is_file); + } + } + (false, false, true) => { + // On pass -'-only-links' flag only + if !self.flags.contains(&NoLinks) { + files.retain(File::is_link); + } + } + (false, false, false) => { + // On pass no flags + if self.flags.contains(&NoLinks) { + files.retain(|f| f.is_file() || f.is_directory()); + } } _ => {} } diff --git a/src/options/filter.rs b/src/options/filter.rs index 745fda689..c5e3ae6fa 100644 --- a/src/options/filter.rs +++ b/src/options/filter.rs @@ -18,6 +18,8 @@ impl FileFilter { (matches.has(&flags::REVERSE)?, FFF::Reverse), (matches.has(&flags::ONLY_DIRS)?, FFF::OnlyDirs), (matches.has(&flags::ONLY_FILES)?, FFF::OnlyFiles), + (matches.has(&flags::ONLY_LINKS)?, FFF::OnlyLinks), + (matches.has(&flags::NO_LINKS)?, FFF::NoLinks), ] { if *has { filter_flags.push(flag.clone()); diff --git a/src/options/flags.rs b/src/options/flags.rs index 3d6d8359b..120f9ec4d 100644 --- a/src/options/flags.rs +++ b/src/options/flags.rs @@ -1,3 +1,4 @@ +#![cfg_attr(rustfmt, rustfmt_skip)] use crate::options::parser::{Arg, Args, TakesValue, Values}; // exa options @@ -41,6 +42,8 @@ pub static GIT_IGNORE: Arg = Arg { short: None, long: "git-ignore", t pub static DIRS_FIRST: Arg = Arg { short: None, long: "group-directories-first", takes_value: TakesValue::Forbidden }; pub static ONLY_DIRS: Arg = Arg { short: Some(b'D'), long: "only-dirs", takes_value: TakesValue::Forbidden }; pub static ONLY_FILES: Arg = Arg { short: Some(b'f'), long: "only-files", takes_value: TakesValue::Forbidden }; +pub static ONLY_LINKS: Arg = Arg { short: None, long: "only-links", takes_value: TakesValue::Forbidden }; +pub static NO_LINKS: Arg = Arg { short: None, long: "no-links", takes_value: TakesValue::Forbidden }; const SORTS: Values = &[ "name", "Name", "size", "extension", "Extension", "modified", "changed", "accessed", "created", "inode", "type", "none" ]; @@ -93,7 +96,7 @@ pub static ALL_ARGS: Args = Args(&[ &WIDTH, &NO_QUOTES, &ABSOLUTE, &ALL, &ALMOST_ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST, - &IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS, &ONLY_FILES, + &IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS, &ONLY_FILES, &ONLY_LINKS, &NO_LINKS, &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED, &BLOCKSIZE, &TOTAL_SIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS, diff --git a/src/options/help.rs b/src/options/help.rs index 0e456d070..6d9416079 100644 --- a/src/options/help.rs +++ b/src/options/help.rs @@ -39,8 +39,10 @@ FILTERING AND SORTING OPTIONS -r, --reverse reverse the sort order -s, --sort SORT_FIELD which field to sort by --group-directories-first list directories before other files - -D, --only-dirs list only directories - -f, --only-files list only files + -D, --only-dirs list only directories and links to directories + -f, --only-files list only files and links to files + --only-links list only symbolic links + --no-links don't list symbolic links in --only-files or --only-dirs cases -I, --ignore-glob GLOBS glob patterns (pipe-separated) of files to ignore"; static GIT_FILTER_HELP: &str = " \ diff --git a/tests/gen/absolute_recurse_unix.stderr b/tests/cmd/only-dirs_no_links_unix.stderr similarity index 100% rename from tests/gen/absolute_recurse_unix.stderr rename to tests/cmd/only-dirs_no_links_unix.stderr diff --git a/tests/cmd/only-dirs_no_links_unix.stdout b/tests/cmd/only-dirs_no_links_unix.stdout new file mode 100644 index 000000000..2489a9247 --- /dev/null +++ b/tests/cmd/only-dirs_no_links_unix.stdout @@ -0,0 +1,2 @@ +exa +vagrant diff --git a/tests/cmd/only-dirs_no_links_unix.toml b/tests/cmd/only-dirs_no_links_unix.toml new file mode 100644 index 000000000..10cc81cc2 --- /dev/null +++ b/tests/cmd/only-dirs_no_links_unix.toml @@ -0,0 +1,2 @@ +bin.name = "eza" +args = "tests/itest --only-dirs --no-links" diff --git a/tests/gen/absolute_unix.stderr b/tests/cmd/only-dirs_unix.stderr similarity index 100% rename from tests/gen/absolute_unix.stderr rename to tests/cmd/only-dirs_unix.stderr diff --git a/tests/cmd/only-dirs_unix.stdout b/tests/cmd/only-dirs_unix.stdout new file mode 100644 index 000000000..d4286486c --- /dev/null +++ b/tests/cmd/only-dirs_unix.stdout @@ -0,0 +1,3 @@ +dir-symlink -> vagrant/debug +exa +vagrant diff --git a/tests/cmd/only-dirs_unix.toml b/tests/cmd/only-dirs_unix.toml new file mode 100644 index 000000000..acbf5740a --- /dev/null +++ b/tests/cmd/only-dirs_unix.toml @@ -0,0 +1,2 @@ +bin.name = "eza" +args = "tests/itest --only-dirs" diff --git a/tests/cmd/only-files_no_links_unix.stderr b/tests/cmd/only-files_no_links_unix.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/tests/cmd/only-files_no_links_unix.stdout b/tests/cmd/only-files_no_links_unix.stdout new file mode 100644 index 000000000..789819226 --- /dev/null +++ b/tests/cmd/only-files_no_links_unix.stdout @@ -0,0 +1 @@ +a diff --git a/tests/cmd/only-files_no_links_unix.toml b/tests/cmd/only-files_no_links_unix.toml new file mode 100644 index 000000000..ef198155d --- /dev/null +++ b/tests/cmd/only-files_no_links_unix.toml @@ -0,0 +1,2 @@ +bin.name = "eza" +args = "tests/itest/vagrant/debug --only-files --no-links" diff --git a/tests/cmd/only-files_unix.stderr b/tests/cmd/only-files_unix.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/tests/cmd/only-files_unix.stdout b/tests/cmd/only-files_unix.stdout new file mode 100644 index 000000000..e69de29bb diff --git a/tests/cmd/only-files_unix.toml b/tests/cmd/only-files_unix.toml new file mode 100644 index 000000000..2c492f233 --- /dev/null +++ b/tests/cmd/only-files_unix.toml @@ -0,0 +1,2 @@ +bin.name = "eza" +args = "tests/itest --only-files" diff --git a/tests/cmd/only-links_and_files_unix.stderr b/tests/cmd/only-links_and_files_unix.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/tests/cmd/only-links_and_files_unix.stdout b/tests/cmd/only-links_and_files_unix.stdout new file mode 100644 index 000000000..e69de29bb diff --git a/tests/cmd/only-links_and_files_unix.toml b/tests/cmd/only-links_and_files_unix.toml new file mode 100644 index 000000000..8fdf2d14e --- /dev/null +++ b/tests/cmd/only-links_and_files_unix.toml @@ -0,0 +1,2 @@ +bin.name = "eza" +args = "tests/itest/vagrant/debug --only-files" diff --git a/tests/cmd/only-links_unix.stderr b/tests/cmd/only-links_unix.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/tests/cmd/only-links_unix.stdout b/tests/cmd/only-links_unix.stdout new file mode 100644 index 000000000..f5eb3de4d --- /dev/null +++ b/tests/cmd/only-links_unix.stdout @@ -0,0 +1,2 @@ +symlink -> a +symlink-broken -> ./b diff --git a/tests/cmd/only-links_unix.toml b/tests/cmd/only-links_unix.toml new file mode 100644 index 000000000..249216f1f --- /dev/null +++ b/tests/cmd/only-links_unix.toml @@ -0,0 +1,2 @@ +bin.name = "eza" +args = "tests/itest/vagrant/debug --only-links" diff --git a/tests/gen/absolute_recurse_unix.stdout b/tests/gen/absolute_recurse_unix.stdout deleted file mode 100644 index 256ece1fc..000000000 --- a/tests/gen/absolute_recurse_unix.stdout +++ /dev/null @@ -1,50 +0,0 @@ -/build/source/tests/itest/a -/build/source/tests/itest/b -/build/source/tests/itest/c -/build/source/tests/itest/d -/build/source/tests/itest/dir-symlink -> vagrant/debug -/build/source/tests/itest/e -/build/source/tests/itest/exa -/build/source/tests/itest/f -/build/source/tests/itest/g -/build/source/tests/itest/h -/build/source/tests/itest/i -/build/source/tests/itest/image.jpg.img.c.rs.log.png -/build/source/tests/itest/index.svg -/build/source/tests/itest/j -/build/source/tests/itest/k -/build/source/tests/itest/l -/build/source/tests/itest/m -/build/source/tests/itest/n -/build/source/tests/itest/o -/build/source/tests/itest/p -/build/source/tests/itest/q -/build/source/tests/itest/vagrant - -tests/itest/exa: -/build/source/tests/itest/exa/file.c -> djihisudjuhfius -/build/source/tests/itest/exa/sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss - -tests/itest/exa/sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss: -/build/source/tests/itest/exa/sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss/Makefile - -tests/itest/vagrant: -/build/source/tests/itest/vagrant/debug -/build/source/tests/itest/vagrant/dev -/build/source/tests/itest/vagrant/log - -tests/itest/vagrant/debug: -/build/source/tests/itest/vagrant/debug/a -/build/source/tests/itest/vagrant/debug/symlink -> a -/build/source/tests/itest/vagrant/debug/symlink-broken -> ./b - -tests/itest/vagrant/dev: -/build/source/tests/itest/vagrant/dev/main.bf - -tests/itest/vagrant/log: -/build/source/tests/itest/vagrant/log/file.png -/build/source/tests/itest/vagrant/log/run - -tests/itest/vagrant/log/run: -/build/source/tests/itest/vagrant/log/run/run.log.text -/build/source/tests/itest/vagrant/log/run/sps.log.text diff --git a/tests/gen/absolute_unix.stdout b/tests/gen/absolute_unix.stdout deleted file mode 100644 index d32c0bea8..000000000 --- a/tests/gen/absolute_unix.stdout +++ /dev/null @@ -1,22 +0,0 @@ -/build/source/tests/itest/a -/build/source/tests/itest/b -/build/source/tests/itest/c -/build/source/tests/itest/d -/build/source/tests/itest/dir-symlink -> vagrant/debug -/build/source/tests/itest/e -/build/source/tests/itest/exa -/build/source/tests/itest/f -/build/source/tests/itest/g -/build/source/tests/itest/h -/build/source/tests/itest/i -/build/source/tests/itest/image.jpg.img.c.rs.log.png -/build/source/tests/itest/index.svg -/build/source/tests/itest/j -/build/source/tests/itest/k -/build/source/tests/itest/l -/build/source/tests/itest/m -/build/source/tests/itest/n -/build/source/tests/itest/o -/build/source/tests/itest/p -/build/source/tests/itest/q -/build/source/tests/itest/vagrant diff --git a/tests/gen/only_dir_recursive_long_unix.stdout b/tests/gen/only_dir_recursive_long_unix.stdout index 209f87b30..19ab86be6 100644 --- a/tests/gen/only_dir_recursive_long_unix.stdout +++ b/tests/gen/only_dir_recursive_long_unix.stdout @@ -3054,6 +3054,7 @@ tests/test_dir/specials: tests/test_dir/symlinks: dir +symlink3 -> dir tests/test_dir/symlinks/dir: diff --git a/tests/gen/only_dir_recursive_unix.stdout b/tests/gen/only_dir_recursive_unix.stdout index 209f87b30..19ab86be6 100644 --- a/tests/gen/only_dir_recursive_unix.stdout +++ b/tests/gen/only_dir_recursive_unix.stdout @@ -3054,6 +3054,7 @@ tests/test_dir/specials: tests/test_dir/symlinks: dir +symlink3 -> dir tests/test_dir/symlinks/dir: diff --git a/tests/ptests/ptest_2439b7d68089135b.stdout b/tests/ptests/ptest_2439b7d68089135b.stdout index 67c8b96f6..ef34007cb 100644 --- a/tests/ptests/ptest_2439b7d68089135b.stdout +++ b/tests/ptests/ptest_2439b7d68089135b.stdout @@ -33,8 +33,10 @@ FILTERING AND SORTING OPTIONS -r, --reverse reverse the sort order -s, --sort SORT_FIELD which field to sort by --group-directories-first list directories before other files - -D, --only-dirs list only directories - -f, --only-files list only files + -D, --only-dirs list only directories and links to directories + -f, --only-files list only files and links to files + --only-links list only symbolic links + --no-links don't list symbolic links in --only-files or --only-dirs cases -I, --ignore-glob GLOBS glob patterns (pipe-separated) of files to ignore --git-ignore ignore files mentioned in '.gitignore' Valid sort fields: name, Name, extension, Extension, size, type,