Merge branch 'almost-all'

This merge adds support for `--all --all`, which displays the `.` and `..` directories like how ls does it.

Doing this was harder than it seemed — exa wasn’t filtering these directories out already, they just weren’t being included! So instead of just including them again, there had to be quite a lot of internal restructuring (yes, again) in order for there to be Files like `..` that don’t have the same name in the list as they do on the filesystem.

Fixes #155.
This commit is contained in:
Benjamin Sago 2017-06-29 13:31:09 +01:00
commit 5cd7609034
23 changed files with 358 additions and 118 deletions

View File

@ -24,7 +24,7 @@ exas 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 @@ exas 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`):

30
Vagrantfile vendored
View File

@ -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 doesnt run coverage over the xtests so its less useful for now
if ENV.key?('INSTALL_KCOV')

View File

@ -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"

View File

@ -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]" \

View File

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

View File

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

View File

@ -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<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: PathBuf, git: bool) -> IOResult<Dir> {
let contents: Vec<PathBuf> = 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 cant 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<Result<File<'dir>, (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 arent 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<File<'dir>, (PathBuf, io::Error)>;
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)))
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()
}
}
}
}
/// 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,
}
}
}

View File

@ -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<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN) -> IOResult<File<'dir>>
where PD: Into<Option<&'dir Dir>>,
FN: Into<Option<String>>
{
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<File<'dir>> {
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 files 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<String> {
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> {
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<File<'a>> 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<String> {
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("/")))
}
}

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

@ -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 {

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 `..`:
@ -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<File>) {
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<DotFilter, Misfire> {
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<glob::Pattern>,

View File

@ -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:

View File

@ -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<T>(misfire: Result<T, Misfire>) -> 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<String> = Vec::new();
let dots = Options::getopts(&nothing).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);
}
}

View File

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

View File

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

View File

@ -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:

1
xtests/hiddens Normal file
View File

@ -0,0 +1 @@
visible

1
xtests/hiddens_a Normal file
View File

@ -0,0 +1 @@
..extra-hidden .hidden visible

1
xtests/hiddens_aa Normal file
View File

@ -0,0 +1 @@
. .. ..extra-hidden .hidden visible

1
xtests/hiddens_l Normal file
View File

@ -0,0 +1 @@
.rw-r--r-- 0 cassowary  1 Jan 12:34 visible

3
xtests/hiddens_la Normal file
View File

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

5
xtests/hiddens_laa Normal file
View File

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

View File

@ -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!"