mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-12-28 02:35:03 +00:00
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:
commit
5cd7609034
@ -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`):
|
||||
|
30
Vagrantfile
vendored
30
Vagrantfile
vendored
@ -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')
|
||||
|
@ -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"
|
||||
|
@ -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]" \
|
||||
|
@ -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
|
||||
|
@ -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)?,
|
||||
|
146
src/fs/dir.rs
146
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<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 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<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 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<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 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<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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
185
src/fs/file.rs
185
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<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 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<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("/")))
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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 {
|
||||
|
@ -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>,
|
||||
|
@ -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:
|
||||
|
@ -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(¬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);
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
1
xtests/hiddens
Normal file
@ -0,0 +1 @@
|
||||
visible
|
1
xtests/hiddens_a
Normal file
1
xtests/hiddens_a
Normal file
@ -0,0 +1 @@
|
||||
..extra-hidden .hidden visible
|
1
xtests/hiddens_aa
Normal file
1
xtests/hiddens_aa
Normal file
@ -0,0 +1 @@
|
||||
[1;34m.[0m [1;34m..[0m ..extra-hidden .hidden visible
|
1
xtests/hiddens_l
Normal file
1
xtests/hiddens_l
Normal file
@ -0,0 +1 @@
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m visible
|
3
xtests/hiddens_la
Normal file
3
xtests/hiddens_la
Normal file
@ -0,0 +1,3 @@
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m ..extra-hidden
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m .hidden
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m visible
|
5
xtests/hiddens_laa
Normal file
5
xtests/hiddens_laa
Normal file
@ -0,0 +1,5 @@
|
||||
[1;34md[33mr[31mw[32mx[0m[33mr[38;5;244m-[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [1;34m.[0m
|
||||
[1;34md[33mr[31mw[32mx[0m[33mr[38;5;244m-[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [1;34m..[0m
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m ..extra-hidden
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m .hidden
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m visible
|
@ -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!"
|
||||
|
Loading…
Reference in New Issue
Block a user