Batch source formatting

I read through every file and applied a couple of rustfmt suggestions. The brace placement and alignment of items on similar lines has been made consistent, even if neither are rustfmt's default style (a file has been put in place to enforce this). Other changes are:

• Alphabetical imports and modules
• Comma placement at the end of match blocks
• Use newlines and indentation judiciously
• Spaces around associated types
• Spaces after negations (it makes it more clear imho)
• Comment formatting
• Use early-returns and Optional `?` where appropriate
This commit is contained in:
Benjamin Sago 2020-10-10 19:49:46 +01:00
parent c3c39fee0a
commit f8df02dae7
49 changed files with 861 additions and 607 deletions

1
.rustfmt.toml Normal file
View File

@ -0,0 +1 @@
disable_all_formatting = true

View File

@ -11,8 +11,9 @@
/// - https://crates.io/crates/vergen /// - https://crates.io/crates/vergen
extern crate datetime; extern crate datetime;
use std::io::Result as IOResult;
use std::env; use std::env;
use std::io::Result as IOResult;
fn git_hash() -> String { fn git_hash() -> String {
use std::process::Command; use std::process::Command;
@ -52,7 +53,8 @@ fn write_statics() -> IOResult<()> {
let ver = if is_development_version() { let ver = if is_development_version() {
format!("exa v{} ({} built on {})", cargo_version(), git_hash(), build_date()) format!("exa v{} ({} built on {})", cargo_version(), git_hash(), build_date())
} else { }
else {
format!("exa v{}", cargo_version()) format!("exa v{}", cargo_version())
}; };

View File

@ -10,7 +10,7 @@ use log::*;
use crate::fs::File; use crate::fs::File;
/// A **Dir** provides a cached list of the file paths in a directory that's /// A **Dir** provides a cached list of the file paths in a directory thats
/// being listed. /// being listed.
/// ///
/// This object gets passed to the Files themselves, in order for them to /// This object gets passed to the Files themselves, in order for them to
@ -39,8 +39,8 @@ impl Dir {
info!("Reading directory {:?}", &path); info!("Reading directory {:?}", &path);
let contents = fs::read_dir(&path)? let contents = fs::read_dir(&path)?
.map(|result| result.map(|entry| entry.path())) .map(|result| result.map(|entry| entry.path()))
.collect::<Result<_,_>>()?; .collect::<Result<_, _>>()?;
Ok(Self { contents, path }) Ok(Self { contents, path })
} }
@ -53,7 +53,8 @@ impl Dir {
dir: self, dir: self,
dotfiles: dots.shows_dotfiles(), dotfiles: dots.shows_dotfiles(),
dots: dots.dots(), dots: dots.dots(),
git, git_ignoring, git,
git_ignoring,
} }
} }
@ -106,7 +107,9 @@ impl<'dir, 'ig> Files<'dir, 'ig> {
loop { loop {
if let Some(path) = self.inner.next() { if let Some(path) = self.inner.next() {
let filename = File::filename(path); let filename = File::filename(path);
if !self.dotfiles && filename.starts_with('.') { continue } if ! self.dotfiles && filename.starts_with('.') {
continue;
}
if self.git_ignoring { if self.git_ignoring {
let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default(); let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
@ -139,7 +142,6 @@ enum DotsNext {
Files, Files,
} }
impl<'dir, 'ig> Iterator for Files<'dir, 'ig> { impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
type Item = Result<File<'dir>, (PathBuf, io::Error)>; type Item = Result<File<'dir>, (PathBuf, io::Error)>;
@ -149,22 +151,24 @@ impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
self.dots = DotsNext::DotDot; self.dots = DotsNext::DotDot;
Some(File::new_aa_current(self.dir) Some(File::new_aa_current(self.dir)
.map_err(|e| (Path::new(".").to_path_buf(), e))) .map_err(|e| (Path::new(".").to_path_buf(), e)))
}, }
DotsNext::DotDot => { DotsNext::DotDot => {
self.dots = DotsNext::Files; self.dots = DotsNext::Files;
Some(File::new_aa_parent(self.parent(), self.dir) Some(File::new_aa_parent(self.parent(), self.dir)
.map_err(|e| (self.parent(), e))) .map_err(|e| (self.parent(), e)))
}, }
DotsNext::Files => { DotsNext::Files => {
self.next_visible_file() self.next_visible_file()
}, }
} }
} }
} }
/// Usually files in Unix use a leading dot to be hidden or visible, but two /// 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 /// entries in particular are “extra-hidden”: `.` and `..`, which only become
/// visible after an extra `-a` option. /// visible after an extra `-a` option.
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
pub enum DotFilter { pub enum DotFilter {
@ -199,9 +203,9 @@ impl DotFilter {
/// Whether this filter should add dot directories to a listing. /// Whether this filter should add dot directories to a listing.
fn dots(self) -> DotsNext { fn dots(self) -> DotsNext {
match self { match self {
Self::JustFiles => DotsNext::Files, Self::JustFiles => DotsNext::Files,
Self::Dotfiles => DotsNext::Files, Self::Dotfiles => DotsNext::Files,
Self::DotfilesAndDots => DotsNext::Dot, Self::DotfilesAndDots => DotsNext::Dot,
} }
} }
} }

View File

@ -36,7 +36,9 @@ impl GitCache {
use std::iter::FromIterator; use std::iter::FromIterator;
impl FromIterator<PathBuf> for GitCache { impl FromIterator<PathBuf> for GitCache {
fn from_iter<I: IntoIterator<Item=PathBuf>>(iter: I) -> Self { fn from_iter<I>(iter: I) -> Self
where I: IntoIterator<Item=PathBuf>
{
let iter = iter.into_iter(); let iter = iter.into_iter();
let mut git = Self { let mut git = Self {
repos: Vec::with_capacity(iter.size_hint().0), repos: Vec::with_capacity(iter.size_hint().0),
@ -61,8 +63,10 @@ impl FromIterator<PathBuf> for GitCache {
debug!("Discovered new Git repo"); debug!("Discovered new Git repo");
git.repos.push(r); git.repos.push(r);
}, }
Err(miss) => git.misses.push(miss), Err(miss) => {
git.misses.push(miss)
}
} }
} }
} }
@ -72,8 +76,6 @@ impl FromIterator<PathBuf> for GitCache {
} }
/// A **Git repository** is one weve discovered somewhere on the filesystem. /// A **Git repository** is one weve discovered somewhere on the filesystem.
pub struct GitRepo { pub struct GitRepo {
@ -99,7 +101,9 @@ pub struct GitRepo {
enum GitContents { enum GitContents {
/// All the interesting Git stuff goes through this. /// All the interesting Git stuff goes through this.
Before { repo: git2::Repository }, Before {
repo: git2::Repository,
},
/// Temporary value used in `repo_to_statuses` so we can move the /// Temporary value used in `repo_to_statuses` so we can move the
/// repository out of the `Before` variant. /// repository out of the `Before` variant.
@ -107,7 +111,9 @@ enum GitContents {
/// The data weve extracted from the repository, but only after weve /// The data weve extracted from the repository, but only after weve
/// actually done so. /// actually done so.
After { statuses: Git } After {
statuses: Git,
},
} }
impl GitRepo { impl GitRepo {
@ -116,7 +122,7 @@ impl GitRepo {
/// depending on the prefix-lookup flag) and returns its Git status. /// depending on the prefix-lookup flag) and returns its Git status.
/// ///
/// Actually querying the `git2` repository for the mapping of paths to /// Actually querying the `git2` repository for the mapping of paths to
/// Git statuses is only done once, and gets cached so we don't need to /// Git statuses is only done once, and gets cached so we dont need to
/// re-query the entire repository the times after that. /// re-query the entire repository the times after that.
/// ///
/// The temporary `Processing` enum variant is used after the `git2` /// The temporary `Processing` enum variant is used after the `git2`
@ -166,7 +172,7 @@ impl GitRepo {
let workdir = workdir.to_path_buf(); let workdir = workdir.to_path_buf();
let contents = Mutex::new(GitContents::Before { repo }); let contents = Mutex::new(GitContents::Before { repo });
Ok(Self { contents, workdir, original_path: path, extra_paths: Vec::new() }) Ok(Self { contents, workdir, original_path: path, extra_paths: Vec::new() })
}, }
None => { None => {
warn!("Repository has no workdir?"); warn!("Repository has no workdir?");
Err(path) Err(path)
@ -205,8 +211,10 @@ fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {
let elem = (path, e.status()); let elem = (path, e.status());
statuses.push(elem); statuses.push(elem);
} }
}, }
Err(e) => error!("Error looking up Git statuses: {:?}", e), Err(e) => {
error!("Error looking up Git statuses: {:?}", e)
}
} }
Git { statuses } Git { statuses }
@ -217,7 +225,7 @@ fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {
// 20.311276 INFO:exa::fs::feature::git: Getting Git statuses for repo with workdir "/vagrant/" // 20.311276 INFO:exa::fs::feature::git: Getting Git statuses for repo with workdir "/vagrant/"
// 20.799610 DEBUG:exa::output::table: Getting Git status for file "./Cargo.toml" // 20.799610 DEBUG:exa::output::table: Getting Git status for file "./Cargo.toml"
// //
// Even inserting another logging line immediately afterwards doesn't make it // Even inserting another logging line immediately afterwards doesnt make it
// look any faster. // look any faster.
@ -239,6 +247,7 @@ impl Git {
/// Get the status for the file at the given path. /// Get the status for the file at the given path.
fn file_status(&self, file: &Path) -> f::Git { fn file_status(&self, file: &Path) -> f::Git {
let path = reorient(file); let path = reorient(file);
self.statuses.iter() self.statuses.iter()
.find(|p| p.0.as_path() == path) .find(|p| p.0.as_path() == path)
.map(|&(_, s)| f::Git { staged: index_status(s), unstaged: working_tree_status(s) }) .map(|&(_, s)| f::Git { staged: index_status(s), unstaged: working_tree_status(s) })
@ -250,25 +259,31 @@ impl Git {
/// directories, which dont really have an official status. /// directories, which dont really have an official status.
fn dir_status(&self, dir: &Path) -> f::Git { fn dir_status(&self, dir: &Path) -> f::Git {
let path = reorient(dir); let path = reorient(dir);
let s = self.statuses.iter()
.filter(|p| p.0.starts_with(&path))
.fold(git2::Status::empty(), |a, b| a | b.1);
f::Git { staged: index_status(s), unstaged: working_tree_status(s) } let s = self.statuses.iter()
.filter(|p| p.0.starts_with(&path))
.fold(git2::Status::empty(), |a, b| a | b.1);
let staged = index_status(s);
let unstaged = working_tree_status(s);
f::Git { staged, unstaged }
} }
} }
/// Converts a path to an absolute path based on the current directory. /// Converts a path to an absolute path based on the current directory.
/// Paths need to be absolute for them to be compared properly, otherwise /// Paths need to be absolute for them to be compared properly, otherwise
/// youd ask a repo about “./README.md” but it only knows about /// youd ask a repo about “./README.md” but it only knows about
/// “/vagrant/README.md”, prefixed by the workdir. /// “/vagrant/README.md”, prefixed by the workdir.
fn reorient(path: &Path) -> PathBuf { fn reorient(path: &Path) -> PathBuf {
use std::env::current_dir; use std::env::current_dir;
// Im not 100% on this func tbh
// TODO: Im not 100% on this func tbh
let path = match current_dir() { let path = match current_dir() {
Err(_) => Path::new(".").join(&path), Err(_) => Path::new(".").join(&path),
Ok(dir) => dir.join(&path), Ok(dir) => dir.join(&path),
}; };
path.canonicalize().unwrap_or(path) path.canonicalize().unwrap_or(path)
} }

View File

@ -1,8 +1,9 @@
pub mod xattr; pub mod xattr;
#[cfg(feature="git")] pub mod git; #[cfg(feature = "git")]
pub mod git;
#[cfg(not(feature="git"))] #[cfg(not(feature = "git"))]
pub mod git { pub mod git {
use std::iter::FromIterator; use std::iter::FromIterator;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -13,8 +14,10 @@ pub mod git {
pub struct GitCache; pub struct GitCache;
impl FromIterator<PathBuf> for GitCache { impl FromIterator<PathBuf> for GitCache {
fn from_iter<I: IntoIterator<Item=PathBuf>>(_iter: I) -> Self { fn from_iter<I>(_iter: I) -> Self
GitCache where I: IntoIterator<Item=PathBuf>
{
Self
} }
} }

View File

@ -1,4 +1,5 @@
//! Extended attribute support for Darwin and Linux systems. //! Extended attribute support for Darwin and Linux systems.
#![allow(trivial_casts)] // for ARM #![allow(trivial_casts)] // for ARM
extern crate libc; extern crate libc;
@ -6,7 +7,11 @@ use std::cmp::Ordering;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
pub const ENABLED: bool = cfg!(feature="git") && cfg!(any(target_os="macos", target_os="linux"));
pub const ENABLED: bool =
cfg!(feature="git") &&
cfg!(any(target_os = "macos", target_os = "linux"));
pub trait FileAttributes { pub trait FileAttributes {
fn attributes(&self) -> io::Result<Vec<Attribute>>; fn attributes(&self) -> io::Result<Vec<Attribute>>;
@ -27,20 +32,21 @@ impl FileAttributes for Path {
#[cfg(not(any(target_os = "macos", target_os = "linux")))] #[cfg(not(any(target_os = "macos", target_os = "linux")))]
impl FileAttributes for Path { impl FileAttributes for Path {
fn attributes(&self) -> io::Result<Vec<Attribute>> { fn attributes(&self) -> io::Result<Vec<Attribute>> {
Ok(vec![]) Ok(Vec::new())
} }
fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> { fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
Ok(vec![]) Ok(Vec::new())
} }
} }
/// Attributes which can be passed to `Attribute::list_with_flags` /// Attributes which can be passed to `Attribute::list_with_flags`
#[cfg(any(target_os = "macos", target_os = "linux"))] #[cfg(any(target_os = "macos", target_os = "linux"))]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum FollowSymlinks { pub enum FollowSymlinks {
Yes, Yes,
No No,
} }
/// Extended attribute /// Extended attribute
@ -50,29 +56,32 @@ pub struct Attribute {
pub size: usize, pub size: usize,
} }
#[cfg(any(target_os = "macos", target_os = "linux"))] #[cfg(any(target_os = "macos", target_os = "linux"))]
pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> { pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
use std::ffi::CString; use std::ffi::CString;
let c_path = match path.to_str().and_then(|s| { CString::new(s).ok() }) { let c_path = match path.to_str().and_then(|s| CString::new(s).ok()) {
Some(cstring) => cstring, Some(cstring) => cstring,
None => return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?")), None => {
return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?"));
}
}; };
let bufsize = lister.listxattr_first(&c_path); let bufsize = lister.listxattr_first(&c_path);
match bufsize.cmp(&0) { match bufsize.cmp(&0) {
Ordering::Less => return Err(io::Error::last_os_error()), Ordering::Less => return Err(io::Error::last_os_error()),
Ordering::Equal => return Ok(Vec::new()), Ordering::Equal => return Ok(Vec::new()),
Ordering::Greater => {}, Ordering::Greater => {},
} }
let mut buf = vec![0_u8; bufsize as usize]; let mut buf = vec![0_u8; bufsize as usize];
let err = lister.listxattr_second(&c_path, &mut buf, bufsize); let err = lister.listxattr_second(&c_path, &mut buf, bufsize);
match err.cmp(&0) { match err.cmp(&0) {
Ordering::Less => return Err(io::Error::last_os_error()), Ordering::Less => return Err(io::Error::last_os_error()),
Ordering::Equal => return Ok(Vec::new()), Ordering::Equal => return Ok(Vec::new()),
Ordering::Greater => {}, Ordering::Greater => {},
} }
let mut names = Vec::new(); let mut names = Vec::new();
@ -91,33 +100,40 @@ pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attrib
if size > 0 { if size > 0 {
names.push(Attribute { names.push(Attribute {
name: lister.translate_attribute_name(&buf[start..end]), name: lister.translate_attribute_name(&buf[start..end]),
size: size as usize size: size as usize,
}); });
} }
start = c_end; start = c_end;
} }
} }
Ok(names) Ok(names)
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod lister { mod lister {
use std::ffi::CString;
use libc::{c_int, size_t, ssize_t, c_char, c_void};
use super::FollowSymlinks; use super::FollowSymlinks;
use libc::{c_int, size_t, ssize_t, c_char, c_void};
use std::ffi::CString;
use std::ptr; use std::ptr;
extern "C" { extern "C" {
fn listxattr( fn listxattr(
path: *const c_char, namebuf: *mut c_char, path: *const c_char,
size: size_t, options: c_int namebuf: *mut c_char,
size: size_t,
options: c_int,
) -> ssize_t; ) -> ssize_t;
fn getxattr( fn getxattr(
path: *const c_char, name: *const c_char, path: *const c_char,
value: *mut c_void, size: size_t, position: u32, name: *const c_char,
options: c_int value: *mut c_void,
size: size_t,
position: u32,
options: c_int,
) -> ssize_t; ) -> ssize_t;
} }
@ -128,24 +144,25 @@ mod lister {
impl Lister { impl Lister {
pub fn new(do_follow: FollowSymlinks) -> Self { pub fn new(do_follow: FollowSymlinks) -> Self {
let c_flags: c_int = match do_follow { let c_flags: c_int = match do_follow {
FollowSymlinks::Yes => 0x0001, FollowSymlinks::Yes => 0x0001,
FollowSymlinks::No => 0x0000, FollowSymlinks::No => 0x0000,
}; };
Self { c_flags } Self { c_flags }
} }
pub fn translate_attribute_name(&self, input: &[u8]) -> String { pub fn translate_attribute_name(&self, input: &[u8]) -> String {
use std::str::from_utf8_unchecked; unsafe { std::str::from_utf8_unchecked(input).into() }
unsafe {
from_utf8_unchecked(input).into()
}
} }
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t { pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
unsafe { unsafe {
listxattr(c_path.as_ptr(), ptr::null_mut(), 0, self.c_flags) listxattr(
c_path.as_ptr(),
ptr::null_mut(),
0,
self.c_flags,
)
} }
} }
@ -154,7 +171,8 @@ mod lister {
listxattr( listxattr(
c_path.as_ptr(), c_path.as_ptr(),
buf.as_mut_ptr() as *mut c_char, buf.as_mut_ptr() as *mut c_char,
bufsize as size_t, self.c_flags bufsize as size_t,
self.c_flags,
) )
} }
} }
@ -164,13 +182,17 @@ mod lister {
getxattr( getxattr(
c_path.as_ptr(), c_path.as_ptr(),
buf.as_ptr() as *const c_char, buf.as_ptr() as *const c_char,
ptr::null_mut(), 0, 0, self.c_flags ptr::null_mut(),
0,
0,
self.c_flags,
) )
} }
} }
} }
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
mod lister { mod lister {
use std::ffi::CString; use std::ffi::CString;
@ -180,21 +202,29 @@ mod lister {
extern "C" { extern "C" {
fn listxattr( fn listxattr(
path: *const c_char, list: *mut c_char, size: size_t path: *const c_char,
list: *mut c_char,
size: size_t,
) -> ssize_t; ) -> ssize_t;
fn llistxattr( fn llistxattr(
path: *const c_char, list: *mut c_char, size: size_t path: *const c_char,
list: *mut c_char,
size: size_t,
) -> ssize_t; ) -> ssize_t;
fn getxattr( fn getxattr(
path: *const c_char, name: *const c_char, path: *const c_char,
value: *mut c_void, size: size_t name: *const c_char,
value: *mut c_void,
size: size_t,
) -> ssize_t; ) -> ssize_t;
fn lgetxattr( fn lgetxattr(
path: *const c_char, name: *const c_char, path: *const c_char,
value: *mut c_void, size: size_t name: *const c_char,
value: *mut c_void,
size: size_t,
) -> ssize_t; ) -> ssize_t;
} }
@ -213,41 +243,46 @@ mod lister {
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t { pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
let listxattr = match self.follow_symlinks { let listxattr = match self.follow_symlinks {
FollowSymlinks::Yes => listxattr, FollowSymlinks::Yes => listxattr,
FollowSymlinks::No => llistxattr, FollowSymlinks::No => llistxattr,
}; };
unsafe { unsafe {
listxattr(c_path.as_ptr() as *const _, ptr::null_mut(), 0) listxattr(
c_path.as_ptr() as *const _,
ptr::null_mut(),
0,
)
} }
} }
pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t { pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
let listxattr = match self.follow_symlinks { let listxattr = match self.follow_symlinks {
FollowSymlinks::Yes => listxattr, FollowSymlinks::Yes => listxattr,
FollowSymlinks::No => llistxattr, FollowSymlinks::No => llistxattr,
}; };
unsafe { unsafe {
listxattr( listxattr(
c_path.as_ptr() as *const _, c_path.as_ptr() as *const _,
buf.as_mut_ptr() as *mut c_char, buf.as_mut_ptr() as *mut c_char,
bufsize as size_t bufsize as size_t,
) )
} }
} }
pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t { pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
let getxattr = match self.follow_symlinks { let getxattr = match self.follow_symlinks {
FollowSymlinks::Yes => getxattr, FollowSymlinks::Yes => getxattr,
FollowSymlinks::No => lgetxattr, FollowSymlinks::No => lgetxattr,
}; };
unsafe { unsafe {
getxattr( getxattr(
c_path.as_ptr() as *const _, c_path.as_ptr() as *const _,
buf.as_ptr() as *const c_char, buf.as_ptr() as *const c_char,
ptr::null_mut(), 0 ptr::null_mut(),
0,
) )
} }
} }

View File

@ -1,4 +1,3 @@
//! Wrapper types for the values returned from `File`s. //! Wrapper types for the values returned from `File`s.
//! //!
//! The methods of `File` that return information about the entry on the //! The methods of `File` that return information about the entry on the
@ -45,7 +44,14 @@ pub type uid_t = u32;
/// Its ordering is used when sorting by type. /// Its ordering is used when sorting by type.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum Type { pub enum Type {
Directory, File, Link, Pipe, Socket, CharDevice, BlockDevice, Special, Directory,
File,
Link,
Pipe,
Socket,
CharDevice,
BlockDevice,
Special,
} }
impl Type { impl Type {
@ -152,7 +158,7 @@ pub enum Size {
/// have a file size. For example, a directory will just contain a list of /// have a file size. For example, a directory will just contain a list of
/// its files as its “contents” and will be specially flagged as being a /// its files as its “contents” and will be specially flagged as being a
/// directory, rather than a file. However, seeing the “file size” of this /// directory, rather than a file. However, seeing the “file size” of this
/// data is rarely useful -- I cant think of a time when Ive seen it and /// data is rarely useful I cant think of a time when Ive seen it and
/// learnt something. So we discard it and just output “-” instead. /// learnt something. So we discard it and just output “-” instead.
/// ///
/// See this answer for more: http://unix.stackexchange.com/a/68266 /// See this answer for more: http://unix.stackexchange.com/a/68266
@ -214,10 +220,11 @@ pub enum GitStatus {
/// A file thats ignored (that matches a line in .gitignore) /// A file thats ignored (that matches a line in .gitignore)
Ignored, Ignored,
/// A file that's updated but unmerged. /// A file thats updated but unmerged.
Conflicted, Conflicted,
} }
/// A files complete Git status. Its possible to make changes to a file, add /// A files complete Git status. Its possible to make changes to a file, add
/// it to the staging area, then make *more* changes, so we need to list each /// it to the staging area, then make *more* changes, so we need to list each
/// files status for both of these. /// files status for both of these.
@ -227,11 +234,13 @@ pub struct Git {
pub unstaged: GitStatus, pub unstaged: GitStatus,
} }
use std::default::Default;
impl Default for Git { impl Default for Git {
/// Create a Git status for a file with nothing done to it. /// Create a Git status for a file with nothing done to it.
fn default() -> Self { fn default() -> Self {
Self { staged: GitStatus::NotModified, unstaged: GitStatus::NotModified } Self {
staged: GitStatus::NotModified,
unstaged: GitStatus::NotModified,
}
} }
} }

View File

@ -2,7 +2,7 @@
use std::io::Error as IOError; use std::io::Error as IOError;
use std::io::Result as IOResult; use std::io::Result as IOResult;
use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt}; use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
@ -11,7 +11,8 @@ use log::*;
use crate::fs::dir::Dir; use crate::fs::dir::Dir;
use crate::fs::fields as f; use crate::fs::fields as f;
/// A **File** is a wrapper around one of Rust's Path objects, along with
/// A **File** is a wrapper around one of Rusts `PathBuf` values, along with
/// associated data about the file. /// associated data about the file.
/// ///
/// Each file is definitely going to have its filename displayed at least /// Each file is definitely going to have its filename displayed at least
@ -44,7 +45,7 @@ pub struct File<'dir> {
/// ///
/// This too is queried multiple times, and is *not* cached by the OS, as /// This too is queried multiple times, and is *not* cached by the OS, as
/// it could easily change between invocations — but exa is so short-lived /// it could easily change between invocations — but exa is so short-lived
/// it's better to just cache it. /// its better to just cache it.
pub metadata: std::fs::Metadata, pub metadata: std::fs::Metadata,
/// A reference to the directory that contains this file, if any. /// A reference to the directory that contains this file, if any.
@ -60,7 +61,7 @@ pub struct File<'dir> {
/// Whether this is one of the two `--all all` directories, `.` and `..`. /// Whether this is one of the two `--all all` directories, `.` and `..`.
/// ///
/// Unlike all other entries, these are not returned as part of the /// Unlike all other entries, these are not returned as part of the
/// directory's children, and are in fact added specifically by exa; this /// directorys children, and are in fact added specifically by exa; this
/// means that they should be skipped when recursing. /// means that they should be skipped when recursing.
pub is_all_all: bool, pub is_all_all: bool,
} }
@ -88,8 +89,9 @@ impl<'dir> File<'dir> {
debug!("Statting file {:?}", &path); debug!("Statting file {:?}", &path);
let metadata = std::fs::symlink_metadata(&path)?; let metadata = std::fs::symlink_metadata(&path)?;
let is_all_all = true; let is_all_all = true;
let parent_dir = Some(parent_dir);
Ok(File { path, parent_dir: Some(parent_dir), metadata, ext, name: ".".to_string(), is_all_all }) Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all })
} }
pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> IOResult<File<'dir>> { pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> IOResult<File<'dir>> {
@ -98,8 +100,9 @@ impl<'dir> File<'dir> {
debug!("Statting file {:?}", &path); debug!("Statting file {:?}", &path);
let metadata = std::fs::symlink_metadata(&path)?; let metadata = std::fs::symlink_metadata(&path)?;
let is_all_all = true; let is_all_all = true;
let parent_dir = Some(parent_dir);
Ok(File { path, parent_dir: Some(parent_dir), metadata, ext, name: "..".to_string(), is_all_all }) Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all })
} }
/// A files name is derived from its string. This needs to handle directories /// A files name is derived from its string. This needs to handle directories
@ -127,7 +130,9 @@ impl<'dir> File<'dir> {
fn ext(path: &Path) -> Option<String> { fn ext(path: &Path) -> Option<String> {
let name = path.file_name().map(|f| f.to_string_lossy().to_string())?; let name = path.file_name().map(|f| f.to_string_lossy().to_string())?;
name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase()) name.rfind('.')
.map(|p| name[p + 1 ..]
.to_ascii_lowercase())
} }
/// Whether this file is a directory on the filesystem. /// Whether this file is a directory on the filesystem.
@ -249,7 +254,8 @@ impl<'dir> File<'dir> {
Ok(metadata) => { Ok(metadata) => {
let ext = File::ext(&path); let ext = File::ext(&path);
let name = File::filename(&path); let name = File::filename(&path);
FileTarget::Ok(Box::new(File { parent_dir: None, path, ext, metadata, name, is_all_all: false })) let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false };
FileTarget::Ok(Box::new(file))
} }
Err(e) => { Err(e) => {
error!("Error following link {:?}: {:#?}", &path, e); error!("Error following link {:?}: {:#?}", &path, e);
@ -274,14 +280,14 @@ impl<'dir> File<'dir> {
} }
} }
/// This file's inode. /// This files inode.
pub fn inode(&self) -> f::Inode { pub fn inode(&self) -> f::Inode {
f::Inode(self.metadata.ino()) f::Inode(self.metadata.ino())
} }
/// This file's number of filesystem blocks. /// This files number of filesystem blocks.
/// ///
/// (Not the size of each block, which we don't actually report on) /// (Not the size of each block, which we dont actually report on)
pub fn blocks(&self) -> f::Blocks { pub fn blocks(&self) -> f::Blocks {
if self.is_file() || self.is_link() { if self.is_file() || self.is_link() {
f::Blocks::Some(self.metadata.blocks()) f::Blocks::Some(self.metadata.blocks())
@ -334,17 +340,19 @@ impl<'dir> File<'dir> {
pub fn changed_time(&self) -> Option<SystemTime> { pub fn changed_time(&self) -> Option<SystemTime> {
let (mut sec, mut nsec) = (self.metadata.ctime(), self.metadata.ctime_nsec()); let (mut sec, mut nsec) = (self.metadata.ctime(), self.metadata.ctime_nsec());
Some( if sec < 0 {
if sec < 0 { if nsec > 0 {
if nsec > 0 { sec += 1;
sec += 1; nsec -= 1_000_000_000;
nsec -= 1_000_000_000; }
}
UNIX_EPOCH - Duration::new(sec.abs() as u64, nsec.abs() as u32) let duration = Duration::new(sec.abs() as u64, nsec.abs() as u32);
} else { Some(UNIX_EPOCH - duration)
UNIX_EPOCH + Duration::new(sec as u64, nsec as u32) }
} else {
) let duration = Duration::new(sec as u64, nsec as u32);
Some(UNIX_EPOCH + duration)
}
} }
/// This files last accessed timestamp, if available on this platform. /// This files last accessed timestamp, if available on this platform.
@ -392,7 +400,7 @@ impl<'dir> File<'dir> {
/// This files permissions, with flags for each bit. /// This files permissions, with flags for each bit.
pub fn permissions(&self) -> f::Permissions { pub fn permissions(&self) -> f::Permissions {
let bits = self.metadata.mode(); let bits = self.metadata.mode();
let has_bit = |bit| { bits & bit == bit }; let has_bit = |bit| bits & bit == bit;
f::Permissions { f::Permissions {
user_read: has_bit(modes::USER_READ), user_read: has_bit(modes::USER_READ),
@ -423,7 +431,7 @@ impl<'dir> File<'dir> {
} }
} }
/// Whether this file's name, including extension, is any of the strings /// Whether this files name, including extension, is any of the strings
/// that get passed in. /// that get passed in.
pub fn name_is_one_of(&self, choices: &[&str]) -> bool { pub fn name_is_one_of(&self, choices: &[&str]) -> bool {
choices.contains(&&self.name[..]) choices.contains(&&self.name[..])
@ -455,7 +463,7 @@ pub enum FileTarget<'dir> {
// Err is its own variant, instead of having the whole thing be inside an // Err is its own variant, instead of having the whole thing be inside an
// `IOResult`, because being unable to follow a symlink is not a serious // `IOResult`, because being unable to follow a symlink is not a serious
// error -- we just display the error message and move on. // error we just display the error message and move on.
} }
impl<'dir> FileTarget<'dir> { impl<'dir> FileTarget<'dir> {
@ -471,9 +479,10 @@ impl<'dir> FileTarget<'dir> {
/// More readable aliases for the permission bits exposed by libc. /// More readable aliases for the permission bits exposed by libc.
#[allow(trivial_numeric_casts)] #[allow(trivial_numeric_casts)]
mod modes { mod modes {
pub type Mode = u32;
// The `libc::mode_t` types actual type varies, but the value returned // The `libc::mode_t` types actual type varies, but the value returned
// from `metadata.permissions().mode()` is always `u32`. // from `metadata.permissions().mode()` is always `u32`.
pub type Mode = u32;
pub const USER_READ: Mode = libc::S_IRUSR as Mode; pub const USER_READ: Mode = libc::S_IRUSR as Mode;
pub const USER_WRITE: Mode = libc::S_IWUSR as Mode; pub const USER_WRITE: Mode = libc::S_IWUSR as Mode;

View File

@ -5,8 +5,8 @@ use std::iter::FromIterator;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::Path; use std::path::Path;
use crate::fs::File;
use crate::fs::DotFilter; use crate::fs::DotFilter;
use crate::fs::File;
/// The **file filter** processes a list of files before displaying them to /// The **file filter** processes a list of files before displaying them to
@ -88,12 +88,11 @@ pub struct FileFilter {
pub git_ignore: GitIgnore, pub git_ignore: GitIgnore,
} }
impl FileFilter { impl FileFilter {
/// Remove every file in the given vector that does *not* pass the /// Remove every file in the given vector that does *not* pass the
/// filter predicate for files found inside a directory. /// filter predicate for files found inside a directory.
pub fn filter_child_files(&self, files: &mut Vec<File>) { pub fn filter_child_files(&self, files: &mut Vec<File>) {
files.retain(|f| !self.ignore_patterns.is_ignored(&f.name)); files.retain(|f| ! self.ignore_patterns.is_ignored(&f.name));
if self.only_dirs { if self.only_dirs {
files.retain(File::is_directory); files.retain(File::is_directory);
@ -110,14 +109,18 @@ impl FileFilter {
/// `exa -I='*.ogg' music/*` should filter out the ogg files obtained /// `exa -I='*.ogg' music/*` should filter out the ogg files obtained
/// from the glob, even though the globbing is done by the shell! /// from the glob, even though the globbing is done by the shell!
pub fn filter_argument_files(&self, files: &mut Vec<File>) { pub fn filter_argument_files(&self, files: &mut Vec<File>) {
files.retain(|f| !self.ignore_patterns.is_ignored(&f.name)); files.retain(|f| {
! self.ignore_patterns.is_ignored(&f.name)
});
} }
/// Sort the files in the given vector based on the sort field option. /// Sort the files in the given vector based on the sort field option.
pub fn sort_files<'a, F>(&self, files: &mut Vec<F>) pub fn sort_files<'a, F>(&self, files: &mut Vec<F>)
where F: AsRef<File<'a>> { where F: AsRef<File<'a>>
{
files.sort_by(|a, b| self.sort_field.compare_files(a.as_ref(), b.as_ref())); files.sort_by(|a, b| {
self.sort_field.compare_files(a.as_ref(), b.as_ref())
});
if self.reverse { if self.reverse {
files.reverse(); files.reverse();
@ -126,8 +129,9 @@ impl FileFilter {
if self.list_dirs_first { if self.list_dirs_first {
// This relies on the fact that `sort_by` is *stable*: it will keep // This relies on the fact that `sort_by` is *stable*: it will keep
// adjacent elements next to each other. // adjacent elements next to each other.
files.sort_by(|a, b| {b.as_ref().points_to_directory() files.sort_by(|a, b| {
.cmp(&a.as_ref().points_to_directory()) b.as_ref().points_to_directory()
.cmp(&a.as_ref().points_to_directory())
}); });
} }
} }
@ -175,13 +179,13 @@ pub enum SortField {
/// The time the file was changed (the “ctime”). /// The time the file was changed (the “ctime”).
/// ///
/// This field is used to mark the time when a files metadata /// This field is used to mark the time when a files metadata
/// changed -- its permissions, owners, or link count. /// changed its permissions, owners, or link count.
/// ///
/// In original Unix, this was, however, meant as creation time. /// In original Unix, this was, however, meant as creation time.
/// https://www.bell-labs.com/usr/dmr/www/cacm.html /// https://www.bell-labs.com/usr/dmr/www/cacm.html
ChangedDate, ChangedDate,
/// The time the file was created (the "btime" or "birthtime"). /// The time the file was created (the “btime” or “birthtime”).
CreatedDate, CreatedDate,
/// The type of the file: directories, links, pipes, regular, files, etc. /// The type of the file: directories, links, pipes, regular, files, etc.
@ -280,11 +284,8 @@ impl SortField {
} }
fn strip_dot(n: &str) -> &str { fn strip_dot(n: &str) -> &str {
if n.starts_with('.') { if n.starts_with('.') { &n[1..] }
&n[1..] else { n }
} else {
n
}
} }
} }
@ -298,8 +299,12 @@ pub struct IgnorePatterns {
} }
impl FromIterator<glob::Pattern> for IgnorePatterns { impl FromIterator<glob::Pattern> for IgnorePatterns {
fn from_iter<I: IntoIterator<Item = glob::Pattern>>(iter: I) -> Self {
Self { patterns: iter.into_iter().collect() } fn from_iter<I>(iter: I) -> Self
where I: IntoIterator<Item = glob::Pattern>
{
let patterns = iter.into_iter().collect();
Self { patterns }
} }
} }
@ -371,7 +376,6 @@ pub enum GitIgnore {
// > usually found in $XDG_CONFIG_HOME/git/ignore. // > usually found in $XDG_CONFIG_HOME/git/ignore.
#[cfg(test)] #[cfg(test)]
mod test_ignores { mod test_ignores {
use super::*; use super::*;

View File

@ -4,7 +4,7 @@ pub use self::dir::{Dir, DotFilter};
mod file; mod file;
pub use self::file::{File, FileTarget}; pub use self::file::{File, FileTarget};
pub mod dir_action;
pub mod feature; pub mod feature;
pub mod fields; pub mod fields;
pub mod filter; pub mod filter;
pub mod dir_action;

View File

@ -124,11 +124,17 @@ impl FileIcon for FileExtensions {
fn icon_file(&self, file: &File) -> Option<char> { fn icon_file(&self, file: &File) -> Option<char> {
use crate::output::icons::Icons; use crate::output::icons::Icons;
Some(match file { if self.is_music(file) || self.is_lossless(file) {
f if self.is_music(f) || self.is_lossless(f) => Icons::Audio.value(), Some(Icons::Audio.value())
f if self.is_image(f) => Icons::Image.value(), }
f if self.is_video(f) => Icons::Video.value(), else if self.is_image(file) {
_ => return None, Some(Icons::Image.value())
}) }
else if self.is_video(file) {
Some(Icons::Video.value())
}
else {
None
}
} }
} }

View File

@ -11,7 +11,7 @@ impl<'a> File<'a> {
/// The point of this is to highlight compiled files such as `foo.js` when /// The point of this is to highlight compiled files such as `foo.js` when
/// their source file `foo.coffee` exists in the same directory. /// their source file `foo.coffee` exists in the same directory.
/// For example, `foo.js` is perfectly valid without `foo.coffee`, so we /// For example, `foo.js` is perfectly valid without `foo.coffee`, so we
/// don't want to always blindly highlight `*.js` as compiled. /// dont want to always blindly highlight `*.js` as compiled.
/// (See also `FileExtensions#is_compiled`) /// (See also `FileExtensions#is_compiled`)
pub fn get_source_files(&self) -> Vec<PathBuf> { pub fn get_source_files(&self) -> Vec<PathBuf> {
if let Some(ext) = &self.ext { if let Some(ext) = &self.ext {
@ -34,7 +34,7 @@ impl<'a> File<'a> {
} }
} }
else { else {
vec![] // No source files if there's no extension, either! vec![] // No source files if theres no extension, either!
} }
} }
} }

View File

@ -57,10 +57,10 @@ impl log::Log for Logger {
fn level(level: log::Level) -> ANSIString<'static> { fn level(level: log::Level) -> ANSIString<'static> {
match level { match level {
log::Level::Error => Colour::Red.paint("ERROR"), log::Level::Error => Colour::Red.paint("ERROR"),
log::Level::Warn => Colour::Yellow.paint("WARN"), log::Level::Warn => Colour::Yellow.paint("WARN"),
log::Level::Info => Colour::Cyan.paint("INFO"), log::Level::Info => Colour::Cyan.paint("INFO"),
log::Level::Debug => Colour::Blue.paint("DEBUG"), log::Level::Debug => Colour::Blue.paint("DEBUG"),
log::Level::Trace => Colour::Fixed(245).paint("TRACE"), log::Level::Trace => Colour::Fixed(245).paint("TRACE"),
} }
} }

View File

@ -11,11 +11,10 @@ use ansi_term::{ANSIStrings, Style};
use log::*; use log::*;
use crate::fs::{Dir, File}; use crate::fs::{Dir, File};
use crate::fs::filter::GitIgnore;
use crate::fs::feature::git::GitCache; use crate::fs::feature::git::GitCache;
use crate::fs::filter::GitIgnore;
use crate::options::{Options, Vars}; use crate::options::{Options, Vars};
pub use crate::options::vars; pub use crate::options::{Misfire, vars};
pub use crate::options::Misfire;
use crate::output::{escape, lines, grid, grid_details, details, View, Mode}; use crate::output::{escape, lines, grid, grid_details, details, View, Mode};
mod fs; mod fs;
@ -35,18 +34,24 @@ fn main() {
match Exa::from_args(args.iter(), stdout()) { match Exa::from_args(args.iter(), stdout()) {
Ok(mut exa) => { Ok(mut exa) => {
match exa.run() { match exa.run() {
Ok(exit_status) => exit(exit_status), Ok(exit_status) => {
exit(exit_status)
}
Err(e) => { Err(e) => {
match e.kind() { match e.kind() {
ErrorKind::BrokenPipe => exit(exits::SUCCESS), ErrorKind::BrokenPipe => {
exit(exits::SUCCESS);
}
_ => { _ => {
eprintln!("{}", e); eprintln!("{}", e);
exit(exits::RUNTIME_ERROR); exit(exits::RUNTIME_ERROR);
}, }
}; };
} }
}; };
}, }
Err(ref e) if e.is_error() => { Err(ref e) if e.is_error() => {
let mut stderr = stderr(); let mut stderr = stderr();
@ -57,13 +62,13 @@ fn main() {
} }
exit(exits::OPTIONS_ERROR); exit(exits::OPTIONS_ERROR);
}, }
Err(ref e) => { Err(ref e) => {
println!("{}", e); println!("{}", e);
exit(exits::SUCCESS); exit(exits::SUCCESS);
}, }
}; }
} }
@ -109,7 +114,7 @@ fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
impl<'args> Exa<'args> { impl<'args> Exa<'args> {
pub fn from_args<I>(args: I, writer: Stdout) -> Result<Exa<'args>, Misfire> pub fn from_args<I>(args: I, writer: Stdout) -> Result<Exa<'args>, Misfire>
where I: Iterator<Item=&'args OsString> where I: Iterator<Item = &'args OsString>
{ {
let (options, mut args) = Options::parse(args, &LiveVars)?; let (options, mut args) = Options::parse(args, &LiveVars)?;
debug!("Dir action from arguments: {:#?}", options.dir_action); debug!("Dir action from arguments: {:#?}", options.dir_action);
@ -136,18 +141,19 @@ impl<'args> Exa<'args> {
Err(e) => { Err(e) => {
exit_status = 2; exit_status = 2;
writeln!(stderr(), "{:?}: {}", file_path, e)?; writeln!(stderr(), "{:?}: {}", file_path, e)?;
}, }
Ok(f) => { Ok(f) => {
if f.points_to_directory() && !self.options.dir_action.treat_dirs_as_files() { if f.points_to_directory() && ! self.options.dir_action.treat_dirs_as_files() {
match f.to_dir() { match f.to_dir() {
Ok(d) => dirs.push(d), Ok(d) => dirs.push(d),
Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?, Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
} }
} }
else { else {
files.push(f); files.push(f);
} }
}, }
} }
} }
@ -176,7 +182,7 @@ impl<'args> Exa<'args> {
writeln!(&mut self.writer)?; writeln!(&mut self.writer)?;
} }
if !is_only_dir { if ! is_only_dir {
let mut bits = Vec::new(); let mut bits = Vec::new();
escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default()); escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?; writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?;
@ -196,10 +202,10 @@ impl<'args> Exa<'args> {
if let Some(recurse_opts) = self.options.dir_action.recurse_options() { if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1; let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;
if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) { if ! recurse_opts.tree && ! recurse_opts.is_too_deep(depth) {
let mut child_dirs = Vec::new(); let mut child_dirs = Vec::new();
for child_dir in children.iter().filter(|f| f.is_directory() && !f.is_all_all) { for child_dir in children.iter().filter(|f| f.is_directory() && ! f.is_all_all) {
match child_dir.to_dir() { match child_dir.to_dir() {
Ok(d) => child_dirs.push(d), Ok(d) => child_dirs.push(d),
Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?, Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
@ -225,43 +231,42 @@ impl<'args> Exa<'args> {
/// For various annoying logistical reasons, each one handles /// For various annoying logistical reasons, each one handles
/// printing differently... /// printing differently...
fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> { fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> {
if !files.is_empty() { if files.is_empty() {
let View { ref mode, ref colours, ref style } = self.options.view; return Ok(());
match mode {
Mode::Lines(ref opts) => {
let r = lines::Render { files, colours, style, opts };
r.render(&mut self.writer)
}
Mode::Grid(ref opts) => {
let r = grid::Render { files, colours, style, opts };
r.render(&mut self.writer)
}
Mode::Details(ref opts) => {
let filter = &self.options.filter;
let recurse = self.options.dir_action.recurse_options();
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
let r = details::Render { dir, files, colours, style, opts, filter, recurse, git_ignoring };
r.render(self.git.as_ref(), &mut self.writer)
}
Mode::GridDetails(ref opts) => {
let grid = &opts.grid;
let filter = &self.options.filter;
let details = &opts.details;
let row_threshold = opts.row_threshold;
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
let r = grid_details::Render { dir, files, colours, style, grid, details, filter, row_threshold, git_ignoring };
r.render(self.git.as_ref(), &mut self.writer)
}
}
} }
else {
Ok(()) let View { ref mode, ref colours, ref style } = self.options.view;
match mode {
Mode::Lines(ref opts) => {
let r = lines::Render { files, colours, style, opts };
r.render(&mut self.writer)
}
Mode::Grid(ref opts) => {
let r = grid::Render { files, colours, style, opts };
r.render(&mut self.writer)
}
Mode::Details(ref opts) => {
let filter = &self.options.filter;
let recurse = self.options.dir_action.recurse_options();
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
let r = details::Render { dir, files, colours, style, opts, filter, recurse, git_ignoring };
r.render(self.git.as_ref(), &mut self.writer)
}
Mode::GridDetails(ref opts) => {
let grid = &opts.grid;
let filter = &self.options.filter;
let details = &opts.details;
let row_threshold = opts.row_threshold;
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
let r = grid_details::Render { dir, files, colours, style, grid, details, filter, row_threshold, git_ignoring };
r.render(self.git.as_ref(), &mut self.writer)
}
} }
} }
} }

View File

@ -19,7 +19,7 @@ impl DirAction {
if matches.is_strict() { if matches.is_strict() {
// Early check for --level when it wouldnt do anything // Early check for --level when it wouldnt do anything
if !recurse && !tree && matches.count(&flags::LEVEL) > 0 { if ! recurse && ! tree && matches.count(&flags::LEVEL) > 0 {
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)); return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
} }
else if recurse && as_file { else if recurse && as_file {

View File

@ -12,13 +12,13 @@ impl FileFilter {
/// Determines which of all the file filter options to use. /// Determines which of all the file filter options to use.
pub fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> { pub fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
Ok(Self { Ok(Self {
list_dirs_first: matches.has(&flags::DIRS_FIRST)?, list_dirs_first: matches.has(&flags::DIRS_FIRST)?,
reverse: matches.has(&flags::REVERSE)?, reverse: matches.has(&flags::REVERSE)?,
only_dirs: matches.has(&flags::ONLY_DIRS)?, only_dirs: matches.has(&flags::ONLY_DIRS)?,
sort_field: SortField::deduce(matches)?, sort_field: SortField::deduce(matches)?,
dot_filter: DotFilter::deduce(matches)?, dot_filter: DotFilter::deduce(matches)?,
ignore_patterns: IgnorePatterns::deduce(matches)?, ignore_patterns: IgnorePatterns::deduce(matches)?,
git_ignore: GitIgnore::deduce(matches)?, git_ignore: GitIgnore::deduce(matches)?,
}) })
} }
} }
@ -42,29 +42,64 @@ impl SortField {
}; };
let field = match word { let field = match word {
"name" | "filename" => Self::Name(SortCase::AaBbCc), "name" | "filename" => {
"Name" | "Filename" => Self::Name(SortCase::ABCabc), Self::Name(SortCase::AaBbCc)
".name" | ".filename" => Self::NameMixHidden(SortCase::AaBbCc), }
".Name" | ".Filename" => Self::NameMixHidden(SortCase::ABCabc), "Name" | "Filename" => {
"size" | "filesize" => Self::Size, Self::Name(SortCase::ABCabc)
"ext" | "extension" => Self::Extension(SortCase::AaBbCc), }
"Ext" | "Extension" => Self::Extension(SortCase::ABCabc), ".name" | ".filename" => {
Self::NameMixHidden(SortCase::AaBbCc)
}
".Name" | ".Filename" => {
Self::NameMixHidden(SortCase::ABCabc)
}
"size" | "filesize" => {
Self::Size
}
"ext" | "extension" => {
Self::Extension(SortCase::AaBbCc)
}
"Ext" | "Extension" => {
Self::Extension(SortCase::ABCabc)
}
// “new” sorts oldest at the top and newest at the bottom; “old” // “new” sorts oldest at the top and newest at the bottom; “old”
// sorts newest at the top and oldest at the bottom. I think this // sorts newest at the top and oldest at the bottom. I think this
// is the right way round to do this: “size” puts the smallest at // is the right way round to do this: “size” puts the smallest at
// the top and the largest at the bottom, doesnt it? // the top and the largest at the bottom, doesnt it?
"date" | "time" | "mod" | "modified" | "new" | "newest" => Self::ModifiedDate, "date" | "time" | "mod" | "modified" | "new" | "newest" => {
Self::ModifiedDate
}
// Similarly, “age” means that files with the least age (the // Similarly, “age” means that files with the least age (the
// newest files) get sorted at the top, and files with the most // newest files) get sorted at the top, and files with the most
// age (the oldest) at the bottom. // age (the oldest) at the bottom.
"age" | "old" | "oldest" => Self::ModifiedAge, "age" | "old" | "oldest" => {
"ch" | "changed" => Self::ChangedDate, Self::ModifiedAge
"acc" | "accessed" => Self::AccessedDate, }
"cr" | "created" => Self::CreatedDate,
"inode" => Self::FileInode, "ch" | "changed" => {
"type" => Self::FileType, Self::ChangedDate
"none" => Self::Unsorted, }
_ => return Err(Misfire::BadArgument(&flags::SORT, word.into())) "acc" | "accessed" => {
Self::AccessedDate
}
"cr" | "created" => {
Self::CreatedDate
}
"inode" => {
Self::FileInode
}
"type" => {
Self::FileType
}
"none" => {
Self::Unsorted
}
_ => {
return Err(Misfire::BadArgument(&flags::SORT, word.into()));
}
}; };
Ok(field) Ok(field)
@ -103,7 +138,6 @@ impl SortField {
// “apps” first, then “Documents”. // “apps” first, then “Documents”.
// //
// You can get the old behaviour back by sorting with `--sort=Name`. // You can get the old behaviour back by sorting with `--sort=Name`.
impl Default for SortField { impl Default for SortField {
fn default() -> Self { fn default() -> Self {
Self::Name(SortCase::AaBbCc) Self::Name(SortCase::AaBbCc)
@ -151,8 +185,8 @@ impl IgnorePatterns {
// If there are no inputs, we return a set of patterns that doesnt // If there are no inputs, we return a set of patterns that doesnt
// match anything, rather than, say, `None`. // match anything, rather than, say, `None`.
let inputs = match matches.get(&flags::IGNORE_GLOB)? { let inputs = match matches.get(&flags::IGNORE_GLOB)? {
None => return Ok(Self::empty()), Some(is) => is,
Some(is) => is, None => return Ok(Self::empty()),
}; };
// Awkwardly, though, a glob pattern can be invalid, and we need to // Awkwardly, though, a glob pattern can be invalid, and we need to
@ -162,8 +196,8 @@ impl IgnorePatterns {
// It can actually return more than one glob error, // It can actually return more than one glob error,
// but we only use one. (TODO) // but we only use one. (TODO)
match errors.pop() { match errors.pop() {
Some(e) => Err(e.into()), Some(e) => Err(e.into()),
None => Ok(patterns), None => Ok(patterns),
} }
} }
} }
@ -171,13 +205,16 @@ impl IgnorePatterns {
impl GitIgnore { impl GitIgnore {
pub fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> { pub fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
Ok(if matches.has(&flags::GIT_IGNORE)? { Self::CheckAndIgnore } if matches.has(&flags::GIT_IGNORE)? {
else { Self::Off }) Ok(Self::CheckAndIgnore)
}
else {
Ok(Self::Off)
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View File

@ -1,4 +1,4 @@
use crate::options::parser::{Arg, Args, Values, TakesValue}; use crate::options::parser::{Arg, Args, TakesValue, Values};
// exa options // exa options
@ -32,8 +32,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 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_DIRS: Arg = Arg { short: Some(b'D'), long: "only-dirs", takes_value: TakesValue::Forbidden };
const SORTS: Values = &[ "name", "Name", "size", "extension", const SORTS: Values = &[ "name", "Name", "size", "extension",
"Extension", "modified", "changed", "accessed", "Extension", "modified", "changed", "accessed",
"created", "inode", "type", "none" ]; "created", "inode", "type", "none" ];
// display options // display options
pub static BINARY: Arg = Arg { short: Some(b'b'), long: "binary", takes_value: TakesValue::Forbidden }; pub static BINARY: Arg = Arg { short: Some(b'b'), long: "binary", takes_value: TakesValue::Forbidden };

View File

@ -1,8 +1,8 @@
use std::fmt; use std::fmt;
use crate::fs::feature::xattr;
use crate::options::flags; use crate::options::flags;
use crate::options::parser::MatchedFlags; use crate::options::parser::MatchedFlags;
use crate::fs::feature::xattr;
static OPTIONS: &str = r##" static OPTIONS: &str = r##"
@ -106,7 +106,7 @@ impl fmt::Display for HelpString {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
writeln!(f, "Usage:\n exa [options] [files...]")?; writeln!(f, "Usage:\n exa [options] [files...]")?;
if !self.only_long { if ! self.only_long {
write!(f, "{}", OPTIONS)?; write!(f, "{}", OPTIONS)?;
} }
@ -127,7 +127,6 @@ impl fmt::Display for HelpString {
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::options::Options; use crate::options::Options;

View File

@ -6,7 +6,7 @@ use crate::options::{flags, HelpString, VersionString};
use crate::options::parser::{Arg, Flag, ParseError}; use crate::options::parser::{Arg, Flag, ParseError};
/// A **misfire** is a thing that can happen instead of listing files -- a /// A **misfire** is a thing that can happen instead of listing files a
/// catch-all for anything outside the programs normal execution. /// catch-all for anything outside the programs normal execution.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum Misfire { pub enum Misfire {
@ -34,7 +34,7 @@ pub enum Misfire {
Conflict(&'static Arg, &'static Arg), Conflict(&'static Arg, &'static Arg),
/// An option was given that does nothing when another one either is or /// An option was given that does nothing when another one either is or
/// isn't present. /// isnt present.
Useless(&'static Arg, bool, &'static Arg), Useless(&'static Arg, bool, &'static Arg),
/// An option was given that does nothing when either of two other options /// An option was given that does nothing when either of two other options
@ -78,19 +78,19 @@ impl fmt::Display for Misfire {
write!(f, "Option {} has no {:?} setting", arg, attempt) write!(f, "Option {} has no {:?} setting", arg, attempt)
} }
}, },
Self::InvalidOptions(e) => write!(f, "{}", e), Self::InvalidOptions(e) => write!(f, "{}", e),
Self::Unsupported(e) => write!(f, "{}", e), Self::Unsupported(e) => write!(f, "{}", e),
Self::Help(text) => write!(f, "{}", text), Self::Help(text) => write!(f, "{}", text),
Self::Version(version) => write!(f, "{}", version), Self::Version(version) => write!(f, "{}", version),
Self::Conflict(a, b) => write!(f, "Option {} conflicts with option {}", a, b), Self::Conflict(a, b) => write!(f, "Option {} conflicts with option {}", a, b),
Self::Duplicate(a, b) if a == b => write!(f, "Flag {} was given twice", a), Self::Duplicate(a, b) if a == b => write!(f, "Flag {} was given twice", a),
Self::Duplicate(a, b) => write!(f, "Flag {} conflicts with flag {}", a, b), Self::Duplicate(a, b) => write!(f, "Flag {} conflicts with flag {}", a, b),
Self::Useless(a, false, b) => write!(f, "Option {} is useless without option {}", a, b), Self::Useless(a, false, b) => write!(f, "Option {} is useless without option {}", a, b),
Self::Useless(a, true, b) => write!(f, "Option {} is useless given option {}", a, b), Self::Useless(a, true, b) => write!(f, "Option {} is useless given option {}", a, b),
Self::Useless2(a, b1, b2) => write!(f, "Option {} is useless without options {} or {}", a, b1, b2), Self::Useless2(a, b1, b2) => write!(f, "Option {} is useless without options {} or {}", a, b1, b2),
Self::TreeAllAll => write!(f, "Option --tree is useless given --all --all"), Self::TreeAllAll => write!(f, "Option --tree is useless given --all --all"),
Self::FailedParse(ref e) => write!(f, "Failed to parse number: {}", e), Self::FailedParse(ref e) => write!(f, "Failed to parse number: {}", e),
Self::FailedGlobPattern(ref e) => write!(f, "Failed to parse glob pattern: {}", e), Self::FailedGlobPattern(ref e) => write!(f, "Failed to parse glob pattern: {}", e),
} }
} }
} }

View File

@ -60,7 +60,7 @@
//! //!
//! `--sort=size` should override `--sort=Name` because its closer to the end //! `--sort=size` should override `--sort=Name` because its closer to the end
//! of the arguments array. In fact, because theres no way to tell where the //! of the arguments array. In fact, because theres no way to tell where the
//! arguments came from -- its just a heuristic -- this will still work even //! arguments came from — its just a heuristic — this will still work even
//! if no aliases are being used! //! if no aliases are being used!
//! //!
//! Finally, this isnt just useful when options could override each other. //! Finally, this isnt just useful when options could override each other.
@ -72,12 +72,13 @@
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use crate::fs::dir_action::DirAction; use crate::fs::dir_action::DirAction;
use crate::fs::filter::{FileFilter,GitIgnore}; use crate::fs::filter::{FileFilter, GitIgnore};
use crate::output::{View, Mode, details, grid_details}; use crate::output::{View, Mode, details, grid_details};
mod style;
mod dir_action; mod dir_action;
mod filter; mod filter;
mod flags;
mod style;
mod view; mod view;
mod help; mod help;
@ -93,7 +94,6 @@ pub mod vars;
pub use self::vars::Vars; pub use self::vars::Vars;
mod parser; mod parser;
mod flags;
use self::parser::MatchedFlags; use self::parser::MatchedFlags;
@ -120,8 +120,9 @@ impl Options {
/// for extra options. /// for extra options.
#[allow(unused_results)] #[allow(unused_results)]
pub fn parse<'args, I, V>(args: I, vars: &V) -> Result<(Self, Vec<&'args OsStr>), Misfire> pub fn parse<'args, I, V>(args: I, vars: &V) -> Result<(Self, Vec<&'args OsStr>), Misfire>
where I: IntoIterator<Item=&'args OsString>, where I: IntoIterator<Item = &'args OsString>,
V: Vars { V: Vars
{
use crate::options::parser::{Matches, Strictness}; use crate::options::parser::{Matches, Strictness};
let strictness = match vars.get(vars::EXA_STRICT) { let strictness = match vars.get(vars::EXA_STRICT) {
@ -169,7 +170,6 @@ impl Options {
} }
#[cfg(test)] #[cfg(test)]
pub mod test { pub mod test {
use super::{Options, Misfire, flags}; use super::{Options, Misfire, flags};

View File

@ -70,8 +70,8 @@ impl Flag {
impl fmt::Display for Flag { impl fmt::Display for Flag {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self { match self {
Self::Short(short) => write!(f, "-{}", *short as char), Self::Short(short) => write!(f, "-{}", *short as char),
Self::Long(long) => write!(f, "--{}", long), Self::Long(long) => write!(f, "--{}", long),
} }
} }
} }
@ -144,7 +144,8 @@ impl Args {
/// Iterates over the given list of command-line arguments and parses /// Iterates over the given list of command-line arguments and parses
/// them into a list of matched flags and free strings. /// them into a list of matched flags and free strings.
pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError> pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
where I: IntoIterator<Item=&'args OsString> { where I: IntoIterator<Item = &'args OsString>
{
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
let mut parsing = true; let mut parsing = true;
@ -164,7 +165,7 @@ impl Args {
// This allows a file named “--arg” to be specified by passing in // This allows a file named “--arg” to be specified by passing in
// the pair “-- --arg”, without it getting matched as a flag that // the pair “-- --arg”, without it getting matched as a flag that
// doesnt exist. // doesnt exist.
if !parsing { if ! parsing {
frees.push(arg) frees.push(arg)
} }
else if arg == "--" { else if arg == "--" {
@ -348,7 +349,7 @@ pub struct Matches<'args> {
pub flags: MatchedFlags<'args>, pub flags: MatchedFlags<'args>,
/// All the strings that werent matched as arguments, as well as anything /// All the strings that werent matched as arguments, as well as anything
/// after the special "--" string. /// after the special “--” string.
pub frees: Vec<&'args OsStr>, pub frees: Vec<&'args OsStr>,
} }
@ -373,7 +374,8 @@ impl<'a> MatchedFlags<'a> {
/// Returns `true` if it was, `false` if it wasnt, and an error in /// Returns `true` if it was, `false` if it wasnt, and an error in
/// strict mode if it was specified more than once. /// strict mode if it was specified more than once.
pub fn has(&self, arg: &'static Arg) -> Result<bool, Misfire> { pub fn has(&self, arg: &'static Arg) -> Result<bool, Misfire> {
self.has_where(|flag| flag.matches(arg)).map(|flag| flag.is_some()) self.has_where(|flag| flag.matches(arg))
.map(|flag| flag.is_some())
} }
/// Returns the first found argument that satisfies the predicate, or /// Returns the first found argument that satisfies the predicate, or
@ -488,7 +490,7 @@ fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
let (before, after) = input.as_bytes().split_at(index); let (before, after) = input.as_bytes().split_at(index);
// The after string contains the = that we need to remove. // The after string contains the = that we need to remove.
if !before.is_empty() && after.len() >= 2 { if ! before.is_empty() && after.len() >= 2 {
return Some((OsStr::from_bytes(before), return Some((OsStr::from_bytes(before),
OsStr::from_bytes(&after[1..]))) OsStr::from_bytes(&after[1..])))
} }
@ -569,7 +571,9 @@ mod parse_test {
let strictness = Strictness::UseLastArguments; // this isnt even used let strictness = Strictness::UseLastArguments; // this isnt even used
let got = Args(TEST_ARGS).parse(inputs.iter(), strictness); let got = Args(TEST_ARGS).parse(inputs.iter(), strictness);
let expected = Ok(Matches { frees, flags: MatchedFlags { flags, strictness } }); let flags = MatchedFlags { flags, strictness };
let expected = Ok(Matches { frees, flags });
assert_eq!(got, expected); assert_eq!(got, expected);
} }
}; };
@ -580,9 +584,11 @@ mod parse_test {
use self::ParseError::*; use self::ParseError::*;
let strictness = Strictness::UseLastArguments; // this isnt even used let strictness = Strictness::UseLastArguments; // this isnt even used
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>(); let bits = $inputs.as_ref().into_iter()
let got = Args(TEST_ARGS).parse(bits.iter(), strictness); .map(|&o| os(o))
.collect::<Vec<_>>();
let got = Args(TEST_ARGS).parse(bits.iter(), strictness);
assert_eq!(got, Err($error)); assert_eq!(got, Err($error));
} }
}; };
@ -719,6 +725,6 @@ mod matches_test {
fn no_count() { fn no_count() {
let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments }; let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };
assert!(!flags.has(&COUNT).unwrap()); assert_eq!(flags.has(&COUNT).unwrap(), false);
} }
} }

View File

@ -3,7 +3,7 @@ use ansi_term::Style;
use crate::fs::File; use crate::fs::File;
use crate::options::{flags, Vars, Misfire}; use crate::options::{flags, Vars, Misfire};
use crate::options::parser::MatchedFlags; use crate::options::parser::MatchedFlags;
use crate::output::file_name::{FileStyle, Classify}; use crate::output::file_name::{Classify, FileStyle};
use crate::style::Colours; use crate::style::Colours;
@ -38,10 +38,9 @@ impl TerminalColours {
/// Determine which terminal colour conditions to use. /// Determine which terminal colour conditions to use.
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> { fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? { let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
Some(w) => w, Some(w) => w,
None => return Ok(Self::default()), None => return Ok(Self::default()),
}; };
if word == "always" { if word == "always" {
@ -77,7 +76,7 @@ pub struct Styles {
impl Styles { impl Styles {
#[allow(trivial_casts)] // the "as Box<_>" stuff below warns about this for some reason #[allow(trivial_casts)] // the `as Box<_>` stuff below warns about this for some reason
pub fn deduce<V, TW>(matches: &MatchedFlags, vars: &V, widther: TW) -> Result<Self, Misfire> pub fn deduce<V, TW>(matches: &MatchedFlags, vars: &V, widther: TW) -> Result<Self, Misfire>
where TW: Fn() -> Option<usize>, V: Vars { where TW: Fn() -> Option<usize>, V: Vars {
use crate::info::filetype::FileExtensions; use crate::info::filetype::FileExtensions;
@ -89,12 +88,12 @@ impl Styles {
// custom colours at all // custom colours at all
let tc = TerminalColours::deduce(matches)?; let tc = TerminalColours::deduce(matches)?;
if tc == TerminalColours::Never if tc == TerminalColours::Never || (tc == TerminalColours::Automatic && widther().is_none()) {
|| (tc == TerminalColours::Automatic && widther().is_none()) let exts = Box::new(NoFileColours);
{
return Ok(Self { return Ok(Self {
colours: Colours::plain(), colours: Colours::plain(),
style: FileStyle { classify, exts: Box::new(NoFileColours) }, style: FileStyle { classify, exts },
}); });
} }
@ -133,11 +132,16 @@ fn parse_color_vars<V: Vars>(vars: &V, colours: &mut Colours) -> (ExtensionMappi
if let Some(lsc) = vars.get(vars::LS_COLORS) { if let Some(lsc) = vars.get(vars::LS_COLORS) {
let lsc = lsc.to_string_lossy(); let lsc = lsc.to_string_lossy();
LSColors(lsc.as_ref()).each_pair(|pair| { LSColors(lsc.as_ref()).each_pair(|pair| {
if !colours.set_ls(&pair) { if ! colours.set_ls(&pair) {
match glob::Pattern::new(pair.key) { match glob::Pattern::new(pair.key) {
Ok(pat) => exts.add(pat, pair.to_style()), Ok(pat) => {
Err(e) => warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e), exts.add(pat, pair.to_style());
}
Err(e) => {
warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
}
} }
} }
}); });
@ -154,10 +158,14 @@ fn parse_color_vars<V: Vars>(vars: &V, colours: &mut Colours) -> (ExtensionMappi
} }
LSColors(exa.as_ref()).each_pair(|pair| { LSColors(exa.as_ref()).each_pair(|pair| {
if !colours.set_ls(&pair) && !colours.set_exa(&pair) { if ! colours.set_ls(&pair) && ! colours.set_exa(&pair) {
match glob::Pattern::new(pair.key) { match glob::Pattern::new(pair.key) {
Ok(pat) => exts.add(pat, pair.to_style()), Ok(pat) => {
Err(e) => warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e), exts.add(pat, pair.to_style());
}
Err(e) => {
warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
}
} }
}; };
}); });
@ -169,7 +177,7 @@ fn parse_color_vars<V: Vars>(vars: &V, colours: &mut Colours) -> (ExtensionMappi
#[derive(PartialEq, Debug, Default)] #[derive(PartialEq, Debug, Default)]
struct ExtensionMappings { struct ExtensionMappings {
mappings: Vec<(glob::Pattern, Style)> mappings: Vec<(glob::Pattern, Style)>,
} }
// Loop through backwards so that colours specified later in the list override // Loop through backwards so that colours specified later in the list override
@ -178,9 +186,7 @@ struct ExtensionMappings {
use crate::output::file_name::FileColours; use crate::output::file_name::FileColours;
impl FileColours for ExtensionMappings { impl FileColours for ExtensionMappings {
fn colour_file(&self, file: &File) -> Option<Style> { fn colour_file(&self, file: &File) -> Option<Style> {
self.mappings self.mappings.iter().rev()
.iter()
.rev()
.find(|t| t.0.matches(&file.name)) .find(|t| t.0.matches(&file.name))
.map (|t| t.1) .map (|t| t.1)
} }
@ -188,7 +194,7 @@ impl FileColours for ExtensionMappings {
impl ExtensionMappings { impl ExtensionMappings {
fn is_non_empty(&self) -> bool { fn is_non_empty(&self) -> bool {
!self.mappings.is_empty() ! self.mappings.is_empty()
} }
fn add(&mut self, pattern: glob::Pattern, style: Style) { fn add(&mut self, pattern: glob::Pattern, style: Style) {
@ -197,18 +203,16 @@ impl ExtensionMappings {
} }
impl Classify { impl Classify {
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> { fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
let flagged = matches.has(&flags::CLASSIFY)?; let flagged = matches.has(&flags::CLASSIFY)?;
Ok(if flagged { Self::AddFileIndicators } if flagged { Ok(Self::AddFileIndicators) }
else { Self::JustFilenames }) else { Ok(Self::JustFilenames) }
} }
} }
#[cfg(test)] #[cfg(test)]
mod terminal_test { mod terminal_test {
use super::*; use super::*;
@ -338,7 +342,6 @@ mod colour_test {
} }
#[cfg(test)] #[cfg(test)]
mod customs_test { mod customs_test {
use std::ffi::OsString; use std::ffi::OsString;
@ -410,11 +413,11 @@ mod customs_test {
fn get(&self, name: &'static str) -> Option<OsString> { fn get(&self, name: &'static str) -> Option<OsString> {
use crate::options::vars; use crate::options::vars;
if name == vars::LS_COLORS && !self.ls.is_empty() { if name == vars::LS_COLORS && ! self.ls.is_empty() {
OsString::from(self.ls.clone()).into() Some(OsString::from(self.ls.clone()))
} }
else if name == vars::EXA_COLORS && !self.exa.is_empty() { else if name == vars::EXA_COLORS && ! self.exa.is_empty() {
OsString::from(self.exa.clone()).into() Some(OsString::from(self.exa.clone()))
} }
else { else {
None None

View File

@ -15,6 +15,7 @@ pub static COLUMNS: &str = "COLUMNS";
/// Environment variable used to datetime format. /// Environment variable used to datetime format.
pub static TIME_STYLE: &str = "TIME_STYLE"; pub static TIME_STYLE: &str = "TIME_STYLE";
// exa-specific variables // exa-specific variables
/// Environment variable used to colour exas interface when colours are /// Environment variable used to colour exas interface when colours are
@ -39,7 +40,6 @@ pub static EXA_DEBUG: &str = "EXA_DEBUG";
pub static EXA_GRID_ROWS: &str = "EXA_GRID_ROWS"; pub static EXA_GRID_ROWS: &str = "EXA_GRID_ROWS";
/// Mockable wrapper for `std::env::var_os`. /// Mockable wrapper for `std::env::var_os`.
pub trait Vars { pub trait Vars {
fn get(&self, name: &'static str) -> Option<OsString>; fn get(&self, name: &'static str) -> Option<OsString>;

View File

@ -1,5 +1,6 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
use crate::fs::feature::xattr;
use crate::options::{flags, Misfire, Vars}; use crate::options::{flags, Misfire, Vars};
use crate::options::parser::MatchedFlags; use crate::options::parser::MatchedFlags;
use crate::output::{View, Mode, grid, details, lines}; use crate::output::{View, Mode, grid, details, lines};
@ -7,8 +8,6 @@ use crate::output::grid_details::{self, RowThreshold};
use crate::output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions}; use crate::output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
use crate::output::time::TimeFormat; use crate::output::time::TimeFormat;
use crate::fs::feature::xattr;
impl View { impl View {
@ -28,7 +27,7 @@ impl Mode {
/// Determine the mode from the command-line arguments. /// Determine the mode from the command-line arguments.
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Self, Misfire> { pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Self, Misfire> {
let long = || { let long = || {
if matches.has(&flags::ACROSS)? && !matches.has(&flags::GRID)? { if matches.has(&flags::ACROSS)? && ! matches.has(&flags::GRID)? {
Err(Misfire::Useless(&flags::ACROSS, true, &flags::LONG)) Err(Misfire::Useless(&flags::ACROSS, true, &flags::LONG))
} }
else if matches.has(&flags::ONE_LINE)? { else if matches.has(&flags::ONE_LINE)? {
@ -104,7 +103,8 @@ impl Mode {
let other_options_mode = other_options_scan()?; let other_options_mode = other_options_scan()?;
if let Self::Grid(grid) = other_options_mode { if let Self::Grid(grid) = other_options_mode {
let row_threshold = RowThreshold::deduce(vars)?; let row_threshold = RowThreshold::deduce(vars)?;
return Ok(Self::GridDetails(grid_details::Options { grid, details, row_threshold })); let opts = grid_details::Options { grid, details, row_threshold };
return Ok(Self::GridDetails(opts));
} }
else { else {
return Ok(other_options_mode); return Ok(other_options_mode);
@ -125,11 +125,11 @@ impl Mode {
} }
} }
if cfg!(feature="git") && matches.has(&flags::GIT)? { if cfg!(feature = "git") && matches.has(&flags::GIT)? {
return Err(Misfire::Useless(&flags::GIT, false, &flags::LONG)); return Err(Misfire::Useless(&flags::GIT, false, &flags::LONG));
} }
else if matches.has(&flags::LEVEL)? && !matches.has(&flags::RECURSE)? && !matches.has(&flags::TREE)? { else if matches.has(&flags::LEVEL)? && ! matches.has(&flags::RECURSE)? && ! matches.has(&flags::TREE)? {
// TODO: I'm not sure if the code even gets this far. // TODO: Im not sure if the code even gets this far.
// There is an identical check in dir_action // There is an identical check in dir_action
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)); return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
} }
@ -220,7 +220,7 @@ impl TableOptions {
impl Columns { impl Columns {
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> { fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
let time_types = TimeTypes::deduce(matches)?; let time_types = TimeTypes::deduce(matches)?;
let git = cfg!(feature="git") && matches.has(&flags::GIT)?; let git = cfg!(feature = "git") && matches.has(&flags::GIT)?;
let blocks = matches.has(&flags::BLOCKS)?; let blocks = matches.has(&flags::BLOCKS)?;
let group = matches.has(&flags::GROUP)?; let group = matches.has(&flags::GROUP)?;
@ -228,9 +228,9 @@ impl Columns {
let links = matches.has(&flags::LINKS)?; let links = matches.has(&flags::LINKS)?;
let octal = matches.has(&flags::OCTAL)?; let octal = matches.has(&flags::OCTAL)?;
let permissions = !matches.has(&flags::NO_PERMISSIONS)?; let permissions = ! matches.has(&flags::NO_PERMISSIONS)?;
let filesize = !matches.has(&flags::NO_FILESIZE)?; let filesize = ! matches.has(&flags::NO_FILESIZE)?;
let user = !matches.has(&flags::NO_USER)?; let user = ! matches.has(&flags::NO_USER)?;
Ok(Self { time_types, git, octal, blocks, group, inode, links, permissions, filesize, user }) Ok(Self { time_types, git, octal, blocks, group, inode, links, permissions, filesize, user })
} }
@ -266,12 +266,14 @@ impl TimeFormat {
pub use crate::output::time::{DefaultFormat, ISOFormat}; pub use crate::output::time::{DefaultFormat, ISOFormat};
let word = match matches.get(&flags::TIME_STYLE)? { let word = match matches.get(&flags::TIME_STYLE)? {
Some(w) => w.to_os_string(), Some(w) => {
None => { w.to_os_string()
}
None => {
use crate::options::vars; use crate::options::vars;
match vars.get(vars::TIME_STYLE) { match vars.get(vars::TIME_STYLE) {
Some(ref t) if !t.is_empty() => t.clone(), Some(ref t) if ! t.is_empty() => t.clone(),
_ => return Ok(Self::DefaultFormat(DefaultFormat::load())) _ => return Ok(Self::DefaultFormat(DefaultFormat::load()))
} }
}, },
}; };
@ -373,7 +375,6 @@ lazy_static! {
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -600,7 +601,7 @@ mod test {
test!(just_binary: Mode <- ["--binary"], None; Last => like Ok(Mode::Grid(_))); test!(just_binary: Mode <- ["--binary"], None; Last => like Ok(Mode::Grid(_)));
test!(just_bytes: Mode <- ["--bytes"], None; Last => like Ok(Mode::Grid(_))); test!(just_bytes: Mode <- ["--bytes"], None; Last => like Ok(Mode::Grid(_)));
#[cfg(feature="git")] #[cfg(feature = "git")]
test!(just_git: Mode <- ["--git"], None; Last => like Ok(Mode::Grid(_))); test!(just_git: Mode <- ["--git"], None; Last => like Ok(Mode::Grid(_)));
test!(just_header_2: Mode <- ["--header"], None; Complain => err Misfire::Useless(&flags::HEADER, false, &flags::LONG)); test!(just_header_2: Mode <- ["--header"], None; Complain => err Misfire::Useless(&flags::HEADER, false, &flags::LONG));
@ -611,7 +612,7 @@ mod test {
test!(just_binary_2: Mode <- ["--binary"], None; Complain => err Misfire::Useless(&flags::BINARY, false, &flags::LONG)); test!(just_binary_2: Mode <- ["--binary"], None; Complain => err Misfire::Useless(&flags::BINARY, false, &flags::LONG));
test!(just_bytes_2: Mode <- ["--bytes"], None; Complain => err Misfire::Useless(&flags::BYTES, false, &flags::LONG)); test!(just_bytes_2: Mode <- ["--bytes"], None; Complain => err Misfire::Useless(&flags::BYTES, false, &flags::LONG));
#[cfg(feature="git")] #[cfg(feature = "git")]
test!(just_git_2: Mode <- ["--git"], None; Complain => err Misfire::Useless(&flags::GIT, false, &flags::LONG)); test!(just_git_2: Mode <- ["--git"], None; Complain => err Misfire::Useless(&flags::GIT, false, &flags::LONG));
} }
} }

View File

@ -149,7 +149,7 @@ impl Deref for TextCellContents {
} }
} }
// No DerefMut implementation here -- it would be publicly accessible, and as // No DerefMut implementation here it would be publicly accessible, and as
// the contents only get changed in this module, the mutators in the struct // the contents only get changed in this module, the mutators in the struct
// above can just access the value directly. // above can just access the value directly.
@ -188,7 +188,7 @@ impl TextCellContents {
/// when calculating widths for displaying tables in a terminal. /// when calculating widths for displaying tables in a terminal.
/// ///
/// This type is used to ensure that the width, rather than the length, is /// This type is used to ensure that the width, rather than the length, is
/// used when constructing a `TextCell` -- it's too easy to write something /// used when constructing a `TextCell` — its too easy to write something
/// like `file_name.len()` and assume it will work! /// like `file_name.len()` and assume it will work!
/// ///
/// It has `From` impls that convert an input string or fixed with to values /// It has `From` impls that convert an input string or fixed with to values
@ -239,7 +239,9 @@ impl Add<usize> for DisplayWidth {
} }
impl Sum for DisplayWidth { impl Sum for DisplayWidth {
fn sum<I>(iter: I) -> Self where I: Iterator<Item=Self> { fn sum<I>(iter: I) -> Self
where I: Iterator<Item = Self>
{
iter.fold(Self(0), Add::add) iter.fold(Self(0), Add::add)
} }
} }

View File

@ -1,6 +1,6 @@
//! The **Details** output view displays each file as a row in a table. //! The **Details** output view displays each file as a row in a table.
//! //!
//! It's used in the following situations: //! Its used in the following situations:
//! //!
//! - Most commonly, when using the `--long` command-line argument to display the //! - Most commonly, when using the `--long` command-line argument to display the
//! details of each file, which requires using a table view to hold all the data; //! details of each file, which requires using a table view to hold all the data;
@ -66,20 +66,19 @@ use std::path::PathBuf;
use std::vec::IntoIter as VecIntoIter; use std::vec::IntoIter as VecIntoIter;
use ansi_term::{ANSIGenericString, Style}; use ansi_term::{ANSIGenericString, Style};
use scoped_threadpool::Pool;
use crate::fs::{Dir, File}; use crate::fs::{Dir, File};
use crate::fs::dir_action::RecurseOptions; use crate::fs::dir_action::RecurseOptions;
use crate::fs::filter::FileFilter;
use crate::fs::feature::git::GitCache; use crate::fs::feature::git::GitCache;
use crate::fs::feature::xattr::{Attribute, FileAttributes}; use crate::fs::feature::xattr::{Attribute, FileAttributes};
use crate::fs::filter::FileFilter;
use crate::style::Colours; use crate::style::Colours;
use crate::output::cell::TextCell; use crate::output::cell::TextCell;
use crate::output::tree::{TreeTrunk, TreeParams, TreeDepth}; use crate::output::icons::painted_icon;
use crate::output::file_name::FileStyle; use crate::output::file_name::FileStyle;
use crate::output::table::{Table, Options as TableOptions, Row as TableRow}; use crate::output::table::{Table, Options as TableOptions, Row as TableRow};
use crate::output::icons::painted_icon; use crate::output::tree::{TreeTrunk, TreeParams, TreeDepth};
use scoped_threadpool::Pool;
/// With the **Details** view, the output gets formatted into columns, with /// With the **Details** view, the output gets formatted into columns, with
@ -105,15 +104,14 @@ pub struct Options {
/// Whether to show a header line or not. /// Whether to show a header line or not.
pub header: bool, pub header: bool,
/// Whether to show each file's extended attributes. /// Whether to show each files extended attributes.
pub xattr: bool, pub xattr: bool,
/// Enables --icons mode /// Whether icons mode is enabled.
pub icons: bool, pub icons: bool,
} }
pub struct Render<'a> { pub struct Render<'a> {
pub dir: Option<&'a Dir>, pub dir: Option<&'a Dir>,
pub files: Vec<File<'a>>, pub files: Vec<File<'a>>,
@ -157,8 +155,8 @@ impl<'a> Render<'a> {
if let Some(ref table) = self.opts.table { if let Some(ref table) = self.opts.table {
match (git, self.dir) { match (git, self.dir) {
(Some(g), Some(d)) => if !g.has_anything_for(&d.path) { git = None }, (Some(g), Some(d)) => if ! g.has_anything_for(&d.path) { git = None },
(Some(g), None) => if !self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None }, (Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
(None, _) => {/* Keep Git how it is */}, (None, _) => {/* Keep Git how it is */},
} }
@ -248,26 +246,26 @@ impl<'a> Render<'a> {
} }
} }
let table_row = table.as_ref().map(|t| t.row_for_file(file, !xattrs.is_empty())); let table_row = table.as_ref()
.map(|t| t.row_for_file(file, ! xattrs.is_empty()));
if !self.opts.xattr { if ! self.opts.xattr {
xattrs.clear(); xattrs.clear();
} }
let mut dir = None; let mut dir = None;
if let Some(r) = self.recurse { if let Some(r) = self.recurse {
if file.is_directory() && r.tree && !r.is_too_deep(depth.0) { if file.is_directory() && r.tree && ! r.is_too_deep(depth.0) {
match file.to_dir() { match file.to_dir() {
Ok(d) => { dir = Some(d); }, Ok(d) => { dir = Some(d); },
Err(e) => { errors.push((e, None)) }, Err(e) => { errors.push((e, None)) },
} }
} }
}; };
let icon = if self.opts.icons { let icon = if self.opts.icons { Some(painted_icon(file, self.style)) }
Some(painted_icon(file, self.style)) else { None };
} else { None };
let egg = Egg { table_row, xattrs, errors, dir, file, icon }; let egg = Egg { table_row, xattrs, errors, dir, file, icon };
unsafe { std::ptr::write(file_eggs.lock().unwrap()[idx].as_mut_ptr(), egg) } unsafe { std::ptr::write(file_eggs.lock().unwrap()[idx].as_mut_ptr(), egg) }
@ -291,10 +289,12 @@ impl<'a> Render<'a> {
if let Some(icon) = egg.icon { if let Some(icon) = egg.icon {
name_cell.push(ANSIGenericString::from(icon), 2) name_cell.push(ANSIGenericString::from(icon), 2)
} }
name_cell.append(self.style.for_file(egg.file, self.colours)
.with_link_paths() let style = self.style.for_file(egg.file, self.colours)
.paint() .with_link_paths()
.promote()); .paint()
.promote();
name_cell.append(style);
let row = Row { let row = Row {
@ -308,14 +308,18 @@ impl<'a> Render<'a> {
if let Some(ref dir) = egg.dir { if let Some(ref dir) = egg.dir {
for file_to_add in dir.files(self.filter.dot_filter, git, self.git_ignoring) { for file_to_add in dir.files(self.filter.dot_filter, git, self.git_ignoring) {
match file_to_add { match file_to_add {
Ok(f) => files.push(f), Ok(f) => {
Err((path, e)) => errors.push((e, Some(path))) files.push(f);
}
Err((path, e)) => {
errors.push((e, Some(path)));
}
} }
} }
self.filter.filter_child_files(&mut files); self.filter.filter_child_files(&mut files);
if !files.is_empty() { if ! files.is_empty() {
for xattr in egg.xattrs { for xattr in egg.xattrs {
rows.push(self.render_xattr(&xattr, TreeParams::new(depth.deeper(), false))); rows.push(self.render_xattr(&xattr, TreeParams::new(depth.deeper(), false)));
} }
@ -331,12 +335,16 @@ impl<'a> Render<'a> {
let count = egg.xattrs.len(); let count = egg.xattrs.len();
for (index, xattr) in egg.xattrs.into_iter().enumerate() { for (index, xattr) in egg.xattrs.into_iter().enumerate() {
rows.push(self.render_xattr(&xattr, TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1))); let params = TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1);
let r = self.render_xattr(&xattr, params);
rows.push(r);
} }
let count = errors.len(); let count = errors.len();
for (index, (error, path)) in errors.into_iter().enumerate() { for (index, (error, path)) in errors.into_iter().enumerate() {
rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), index == count - 1), path)); let params = TreeParams::new(depth.deeper(), index == count - 1);
let r = self.render_error(&error, params, path);
rows.push(r);
} }
} }
} }
@ -396,13 +404,13 @@ pub struct Row {
/// Vector of cells to display. /// Vector of cells to display.
/// ///
/// Most of the rows will be used to display files' metadata, so this will /// Most of the rows will be used to display files metadata, so this will
/// almost always be `Some`, containing a vector of cells. It will only be /// almost always be `Some`, containing a vector of cells. It will only be
/// `None` for a row displaying an attribute or error, neither of which /// `None` for a row displaying an attribute or error, neither of which
/// have cells. /// have cells.
pub cells: Option<TableRow>, pub cells: Option<TableRow>,
/// This file's name, in coloured output. The name is treated separately /// This files name, in coloured output. The name is treated separately
/// from the other cells, as it never requires padding. /// from the other cells, as it never requires padding.
pub name: TextCell, pub name: TextCell,
@ -441,7 +449,7 @@ impl<'a> Iterator for TableIter<'a> {
// If any tree characters have been printed, then add an extra // If any tree characters have been printed, then add an extra
// space, which makes the output look much better. // space, which makes the output look much better.
if !row.tree.is_at_root() { if ! row.tree.is_at_root() {
cell.add_spaces(1); cell.add_spaces(1);
} }
@ -471,7 +479,7 @@ impl Iterator for Iter {
// If any tree characters have been printed, then add an extra // If any tree characters have been printed, then add an extra
// space, which makes the output look much better. // space, which makes the output look much better.
if !row.tree.is_at_root() { if ! row.tree.is_at_root() {
cell.add_spaces(1); cell.add_spaces(1);
} }

View File

@ -4,22 +4,23 @@ use ansi_term::{ANSIString, Style};
pub fn escape<'a>(string: String, bits: &mut Vec<ANSIString<'a>>, good: Style, bad: Style) { pub fn escape<'a>(string: String, bits: &mut Vec<ANSIString<'a>>, good: Style, bad: Style) {
if string.chars().all(|c| c >= 0x20 as char && c != 0x7f as char) { if string.chars().all(|c| c >= 0x20 as char && c != 0x7f as char) {
bits.push(good.paint(string)); bits.push(good.paint(string));
return;
} }
else {
for c in string.chars() {
// The `escape_default` method on `char` is *almost* what we want here, but
// it still escapes non-ASCII UTF-8 characters, which are still printable.
if c >= 0x20 as char && c != 0x7f as char { for c in string.chars() {
// TODO: This allocates way too much, // The `escape_default` method on `char` is *almost* what we want here, but
// hence the `all` check above. // it still escapes non-ASCII UTF-8 characters, which are still printable.
let mut s = String::new();
s.push(c); if c >= 0x20 as char && c != 0x7f as char {
bits.push(good.paint(s)); // TODO: This allocates way too much,
} else { // hence the `all` check above.
let s = c.escape_default().collect::<String>(); let mut s = String::new();
bits.push(bad.paint(s)); s.push(c);
} bits.push(good.paint(s));
}
else {
let s = c.escape_default().collect::<String>();
bits.push(bad.paint(s));
} }
} }
} }

View File

@ -1,10 +1,12 @@
use std::fmt::Debug;
use std::marker::Sync;
use std::path::Path; use std::path::Path;
use ansi_term::{ANSIString, Style}; use ansi_term::{ANSIString, Style};
use crate::fs::{File, FileTarget}; use crate::fs::{File, FileTarget};
use crate::output::escape;
use crate::output::cell::TextCellContents; use crate::output::cell::TextCellContents;
use crate::output::escape;
use crate::output::render::FiletypeColours; use crate::output::render::FiletypeColours;
@ -25,7 +27,8 @@ impl FileStyle {
/// with the remaining arguments. /// with the remaining arguments.
pub fn for_file<'a, 'dir, C: Colours>(&'a self, file: &'a File<'dir>, colours: &'a C) -> FileName<'a, 'dir, C> { pub fn for_file<'a, 'dir, C: Colours>(&'a self, file: &'a File<'dir>, colours: &'a C) -> FileName<'a, 'dir, C> {
FileName { FileName {
file, colours, file,
colours,
link_style: LinkStyle::JustFilenames, link_style: LinkStyle::JustFilenames,
classify: self.classify, classify: self.classify,
exts: &*self.exts, exts: &*self.exts,
@ -71,7 +74,6 @@ impl Default for Classify {
} }
/// A **file name** holds all the information necessary to display the name /// A **file name** holds all the information necessary to display the name
/// of the given file. This is used in all of the views. /// of the given file. This is used in all of the views.
pub struct FileName<'a, 'dir: 'a, C: Colours+'a> { pub struct FileName<'a, 'dir: 'a, C: Colours+'a> {
@ -120,9 +122,9 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
} }
} }
if !self.file.name.is_empty() { if ! self.file.name.is_empty() {
// The “missing file” colour seems like it should be used here, // The “missing file” colour seems like it should be used here,
// but its not! In a grid view, where there's no space to display // but its not! In a grid view, where theres no space to display
// link targets, the filename has to have a different style to // link targets, the filename has to have a different style to
// indicate this fact. But when showing targets, we can just // indicate this fact. But when showing targets, we can just
// colour the path instead (see below), and leave the broken // colour the path instead (see below), and leave the broken
@ -143,7 +145,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
self.add_parent_bits(&mut bits, parent); self.add_parent_bits(&mut bits, parent);
} }
if !target.name.is_empty() { if ! target.name.is_empty() {
let target = FileName { let target = FileName {
file: target, file: target,
colours: self.colours, colours: self.colours,
@ -157,18 +159,24 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
bits.push(bit); bits.push(bit);
} }
} }
}, }
FileTarget::Broken(broken_path) => { FileTarget::Broken(broken_path) => {
bits.push(Style::default().paint(" ")); bits.push(Style::default().paint(" "));
bits.push(self.colours.broken_symlink().paint("->")); bits.push(self.colours.broken_symlink().paint("->"));
bits.push(Style::default().paint(" ")); bits.push(Style::default().paint(" "));
escape(broken_path.display().to_string(), &mut bits, self.colours.broken_filename(), self.colours.broken_control_char());
}, escape(
broken_path.display().to_string(),
&mut bits,
self.colours.broken_filename(),
self.colours.broken_control_char(),
);
}
FileTarget::Err(_) => { FileTarget::Err(_) => {
// Do nothing -- the error gets displayed on the next line // Do nothing the error gets displayed on the next line
}, }
} }
} }
else if let Classify::AddFileIndicators = self.classify { else if let Classify::AddFileIndicators = self.classify {
@ -190,7 +198,12 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
bits.push(self.colours.symlink_path().paint("/")); bits.push(self.colours.symlink_path().paint("/"));
} }
else if coconut >= 1 { else if coconut >= 1 {
escape(parent.to_string_lossy().to_string(), bits, self.colours.symlink_path(), self.colours.control_char()); escape(
parent.to_string_lossy().to_string(),
bits,
self.colours.symlink_path(),
self.colours.control_char(),
);
bits.push(self.colours.symlink_path().paint("/")); bits.push(self.colours.symlink_path().paint("/"));
} }
} }
@ -201,15 +214,20 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
fn classify_char(&self) -> Option<&'static str> { fn classify_char(&self) -> Option<&'static str> {
if self.file.is_executable_file() { if self.file.is_executable_file() {
Some("*") Some("*")
} else if self.file.is_directory() { }
else if self.file.is_directory() {
Some("/") Some("/")
} else if self.file.is_pipe() { }
else if self.file.is_pipe() {
Some("|") Some("|")
} else if self.file.is_link() { }
else if self.file.is_link() {
Some("@") Some("@")
} else if self.file.is_socket() { }
else if self.file.is_socket() {
Some("=") Some("=")
} else { }
else {
None None
} }
} }
@ -228,13 +246,20 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> { fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
let file_style = self.style(); let file_style = self.style();
let mut bits = Vec::new(); let mut bits = Vec::new();
escape(self.file.name.clone(), &mut bits, file_style, self.colours.control_char());
escape(
self.file.name.clone(),
&mut bits,
file_style,
self.colours.control_char(),
);
bits bits
} }
/// Figures out which colour to paint the filename part of the output, /// Figures out which colour to paint the filename part of the output,
/// depending on which “type” of file it appears to be -- either from the /// depending on which “type” of file it appears to be either from the
/// class on the filesystem or from its name. (Or the broken link colour, /// class on the filesystem or from its name. (Or the broken link colour,
/// if theres nowhere else for that fact to be shown.) /// if theres nowhere else for that fact to be shown.)
pub fn style(&self) -> Style { pub fn style(&self) -> Style {
@ -260,7 +285,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
f if f.is_block_device() => self.colours.block_device(), f if f.is_block_device() => self.colours.block_device(),
f if f.is_char_device() => self.colours.char_device(), f if f.is_char_device() => self.colours.char_device(),
f if f.is_socket() => self.colours.socket(), f if f.is_socket() => self.colours.socket(),
f if !f.is_file() => self.colours.special(), f if ! f.is_file() => self.colours.special(),
_ => return None, _ => return None,
}) })
} }
@ -298,9 +323,7 @@ pub trait Colours: FiletypeColours {
// needs Debug because FileStyle derives it // needs Debug because FileStyle derives it
use std::fmt::Debug; pub trait FileColours: Debug + Sync {
use std::marker::Sync;
pub trait FileColours: Debug+Sync {
fn colour_file(&self, file: &File) -> Option<Style>; fn colour_file(&self, file: &File) -> Option<Style>;
} }
@ -308,7 +331,9 @@ pub trait FileColours: Debug+Sync {
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub struct NoFileColours; pub struct NoFileColours;
impl FileColours for NoFileColours { impl FileColours for NoFileColours {
fn colour_file(&self, _file: &File) -> Option<Style> { None } fn colour_file(&self, _file: &File) -> Option<Style> {
None
}
} }
// When getting the colour of a file from a *pair* of colourisers, try the // When getting the colour of a file from a *pair* of colourisers, try the
@ -316,8 +341,11 @@ impl FileColours for NoFileColours {
// file type associations, while falling back to the default set if not set // file type associations, while falling back to the default set if not set
// explicitly. // explicitly.
impl<A, B> FileColours for (A, B) impl<A, B> FileColours for (A, B)
where A: FileColours, B: FileColours { where A: FileColours,
B: FileColours,
{
fn colour_file(&self, file: &File) -> Option<Style> { fn colour_file(&self, file: &File) -> Option<Style> {
self.0.colour_file(file).or_else(|| self.1.colour_file(file)) self.0.colour_file(file)
.or_else(|| self.1.colour_file(file))
} }
} }

View File

@ -3,10 +3,10 @@ use std::io::{Write, Result as IOResult};
use term_grid as tg; use term_grid as tg;
use crate::fs::File; use crate::fs::File;
use crate::style::Colours; use crate::output::cell::DisplayWidth;
use crate::output::file_name::FileStyle; use crate::output::file_name::FileStyle;
use crate::output::icons::painted_icon; use crate::output::icons::painted_icon;
use crate::output::cell::DisplayWidth; use crate::style::Colours;
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
@ -41,16 +41,16 @@ impl<'a> Render<'a> {
grid.reserve(self.files.len()); grid.reserve(self.files.len());
for file in &self.files { for file in &self.files {
let icon = if self.opts.icons { Some(painted_icon(file, self.style)) } else { None }; let icon = if self.opts.icons { Some(painted_icon(file, self.style)) }
else { None };
let filename = self.style.for_file(file, self.colours).paint(); let filename = self.style.for_file(file, self.colours).paint();
let width = if self.opts.icons {
DisplayWidth::from(2) + filename.width() let width = if self.opts.icons { DisplayWidth::from(2) + filename.width() }
} else { else { filename.width() };
filename.width()
};
grid.add(tg::Cell { grid.add(tg::Cell {
contents: format!("{icon}{filename}", icon=&icon.unwrap_or_default(), filename=filename.strings().to_string()), contents: format!("{}{}", &icon.unwrap_or_default(), filename.strings()),
width: *width, width: *width,
}); });
} }
@ -66,9 +66,11 @@ impl<'a> Render<'a> {
if self.opts.icons { if self.opts.icons {
write!(w, "{}", painted_icon(file, self.style))?; write!(w, "{}", painted_icon(file, self.style))?;
} }
let name_cell = self.style.for_file(file, self.colours).paint(); let name_cell = self.style.for_file(file, self.colours).paint();
writeln!(w, "{}", name_cell.strings())?; writeln!(w, "{}", name_cell.strings())?;
} }
Ok(()) Ok(())
} }
} }

View File

@ -9,15 +9,14 @@ use crate::fs::{Dir, File};
use crate::fs::feature::git::GitCache; use crate::fs::feature::git::GitCache;
use crate::fs::feature::xattr::FileAttributes; use crate::fs::feature::xattr::FileAttributes;
use crate::fs::filter::FileFilter; use crate::fs::filter::FileFilter;
use crate::style::Colours;
use crate::output::cell::TextCell; use crate::output::cell::TextCell;
use crate::output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender}; use crate::output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};
use crate::output::grid::Options as GridOptions;
use crate::output::file_name::FileStyle; use crate::output::file_name::FileStyle;
use crate::output::grid::Options as GridOptions;
use crate::output::icons::painted_icon;
use crate::output::table::{Table, Row as TableRow, Options as TableOptions}; use crate::output::table::{Table, Row as TableRow, Options as TableOptions};
use crate::output::tree::{TreeParams, TreeDepth}; use crate::output::tree::{TreeParams, TreeDepth};
use crate::output::icons::painted_icon; use crate::style::Colours;
#[derive(Debug)] #[derive(Debug)]
@ -84,21 +83,21 @@ pub struct Render<'a> {
impl<'a> Render<'a> { impl<'a> Render<'a> {
/// Create a temporary Details render that gets used for the columns of /// Create a temporary Details render that gets used for the columns of
/// the grid-details render that's being generated. /// the grid-details render thats being generated.
/// ///
/// This includes an empty files vector because the files get added to /// This includes an empty files vector because the files get added to
/// the table in *this* file, not in details: we only want to insert every /// the table in *this* file, not in details: we only want to insert every
/// *n* files into each columns table, not all of them. /// *n* files into each columns table, not all of them.
pub fn details(&self) -> DetailsRender<'a> { pub fn details(&self) -> DetailsRender<'a> {
DetailsRender { DetailsRender {
dir: self.dir, dir: self.dir,
files: Vec::new(), files: Vec::new(),
colours: self.colours, colours: self.colours,
style: self.style, style: self.style,
opts: self.details, opts: self.details,
recurse: None, recurse: None,
filter: self.filter, filter: self.filter,
git_ignoring: self.git_ignoring, git_ignoring: self.git_ignoring,
} }
} }
@ -106,14 +105,14 @@ impl<'a> Render<'a> {
/// in the terminal (or something has gone wrong) and we have given up. /// in the terminal (or something has gone wrong) and we have given up.
pub fn give_up(self) -> DetailsRender<'a> { pub fn give_up(self) -> DetailsRender<'a> {
DetailsRender { DetailsRender {
dir: self.dir, dir: self.dir,
files: self.files, files: self.files,
colours: self.colours, colours: self.colours,
style: self.style, style: self.style,
opts: self.details, opts: self.details,
recurse: None, recurse: None,
filter: self.filter, filter: self.filter,
git_ignoring: self.git_ignoring, git_ignoring: self.git_ignoring,
} }
} }
@ -138,7 +137,7 @@ impl<'a> Render<'a> {
let rows = self.files.iter() let rows = self.files.iter()
.map(|file| first_table.row_for_file(file, file_has_xattrs(file))) .map(|file| first_table.row_for_file(file, file_has_xattrs(file)))
.collect::<Vec<TableRow>>(); .collect::<Vec<_>>();
let file_names = self.files.iter() let file_names = self.files.iter()
.map(|file| { .map(|file| {
@ -148,11 +147,12 @@ impl<'a> Render<'a> {
let file_cell = self.style.for_file(file, self.colours).paint().promote(); let file_cell = self.style.for_file(file, self.colours).paint().promote();
icon_cell.append(file_cell); icon_cell.append(file_cell);
icon_cell icon_cell
} else { }
else {
self.style.for_file(file, self.colours).paint().promote() self.style.for_file(file, self.colours).paint().promote()
} }
}) })
.collect::<Vec<TextCell>>(); .collect::<Vec<_>>();
let mut last_working_table = self.make_grid(1, options, git, &file_names, rows.clone(), &drender); let mut last_working_table = self.make_grid(1, options, git, &file_names, rows.clone(), &drender);
@ -188,8 +188,8 @@ impl<'a> Render<'a> {
fn make_table(&'a self, options: &'a TableOptions, mut git: Option<&'a GitCache>, drender: &DetailsRender) -> (Table<'a>, Vec<DetailsRow>) { fn make_table(&'a self, options: &'a TableOptions, mut git: Option<&'a GitCache>, drender: &DetailsRender) -> (Table<'a>, Vec<DetailsRow>) {
match (git, self.dir) { match (git, self.dir) {
(Some(g), Some(d)) => if !g.has_anything_for(&d.path) { git = None }, (Some(g), Some(d)) => if ! g.has_anything_for(&d.path) { git = None },
(Some(g), None) => if !self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None }, (Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
(None, _) => {/* Keep Git how it is */}, (None, _) => {/* Keep Git how it is */},
} }
@ -206,7 +206,6 @@ impl<'a> Render<'a> {
} }
fn make_grid(&'a self, column_count: usize, options: &'a TableOptions, git: Option<&GitCache>, file_names: &[TextCell], rows: Vec<TableRow>, drender: &DetailsRender) -> grid::Grid { fn make_grid(&'a self, column_count: usize, options: &'a TableOptions, git: Option<&GitCache>, file_names: &[TextCell], rows: Vec<TableRow>, drender: &DetailsRender) -> grid::Grid {
let mut tables = Vec::new(); let mut tables = Vec::new();
for _ in 0 .. column_count { for _ in 0 .. column_count {
tables.push(self.make_table(options, git, drender)); tables.push(self.make_table(options, git, drender));
@ -234,17 +233,19 @@ impl<'a> Render<'a> {
rows.push(details_row); rows.push(details_row);
} }
let columns: Vec<_> = tables.into_iter().map(|(table, details_rows)| { let columns = tables
drender.iterate_with_table(table, details_rows).collect::<Vec<_>>() .into_iter()
}).collect(); .map(|(table, details_rows)| {
drender.iterate_with_table(table, details_rows)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let direction = if self.grid.across { grid::Direction::LeftToRight } let direction = if self.grid.across { grid::Direction::LeftToRight }
else { grid::Direction::TopToBottom }; else { grid::Direction::TopToBottom };
let mut grid = grid::Grid::new(grid::GridOptions { let filling = grid::Filling::Spaces(4);
direction, let mut grid = grid::Grid::new(grid::GridOptions { direction, filling });
filling: grid::Filling::Spaces(4),
});
if self.grid.across { if self.grid.across {
for row in 0 .. height { for row in 0 .. height {
@ -280,14 +281,18 @@ impl<'a> Render<'a> {
fn divide_rounding_up(a: usize, b: usize) -> usize { fn divide_rounding_up(a: usize, b: usize) -> usize {
let mut result = a / b; let mut result = a / b;
if a % b != 0 { result += 1; }
if a % b != 0 {
result += 1;
}
result result
} }
fn file_has_xattrs(file: &File) -> bool { fn file_has_xattrs(file: &File) -> bool {
match file.path.attributes() { match file.path.attributes() {
Ok(attrs) => !attrs.is_empty(), Ok(attrs) => ! attrs.is_empty(),
Err(_) => false, Err(_) => false,
} }
} }

View File

@ -4,10 +4,12 @@ use crate::fs::File;
use crate::info::filetype::FileExtensions; use crate::info::filetype::FileExtensions;
use crate::output::file_name::FileStyle; use crate::output::file_name::FileStyle;
pub trait FileIcon { pub trait FileIcon {
fn icon_file(&self, file: &File) -> Option<char>; fn icon_file(&self, file: &File) -> Option<char>;
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum Icons { pub enum Icons {
Audio, Audio,
@ -18,35 +20,48 @@ pub enum Icons {
impl Icons { impl Icons {
pub fn value(self) -> char { pub fn value(self) -> char {
match self { match self {
Self::Audio => '\u{f001}', Self::Audio => '\u{f001}',
Self::Image => '\u{f1c5}', Self::Image => '\u{f1c5}',
Self::Video => '\u{f03d}', Self::Video => '\u{f03d}',
} }
} }
} }
pub fn painted_icon(file: &File, style: &FileStyle) -> String { pub fn painted_icon(file: &File, style: &FileStyle) -> String {
let file_icon = icon(file).to_string(); let file_icon = icon(file).to_string();
let painted = style.exts let painted = style.exts
.colour_file(file) .colour_file(file)
.map_or(file_icon.to_string(), |c| { .map_or(file_icon.to_string(), |c| {
// Remove underline from icon // Remove underline from icon
if c.is_underline { if c.is_underline {
match c.foreground { match c.foreground {
Some(color) => Style::from(color).paint(file_icon).to_string(), Some(color) => {
None => Style::default().paint(file_icon).to_string(), Style::from(color).paint(file_icon).to_string()
}
None => {
Style::default().paint(file_icon).to_string()
} }
} else {
c.paint(file_icon).to_string()
} }
}); }
else {
c.paint(file_icon).to_string()
}
});
format!("{} ", painted) format!("{} ", painted)
} }
fn icon(file: &File) -> char { fn icon(file: &File) -> char {
let extensions = Box::new(FileExtensions); let extensions = Box::new(FileExtensions);
if file.points_to_directory() { '\u{f115}' }
else if let Some(icon) = extensions.icon_file(file) { icon } if file.points_to_directory() {
'\u{f115}'
}
else if let Some(icon) = extensions.icon_file(file) {
icon
}
else if let Some(ext) = file.ext.as_ref() { else if let Some(ext) = file.ext.as_ref() {
match ext.as_str() { match ext.as_str() {
"ai" => '\u{e7b4}', "ai" => '\u{e7b4}',
@ -189,7 +204,8 @@ fn icon(file: &File) -> char {
"nix" => '\u{f313}', "nix" => '\u{f313}',
_ => '\u{f016}' _ => '\u{f016}'
} }
} else { }
else {
'\u{f016}' '\u{f016}'
} }
} }

View File

@ -3,14 +3,15 @@ use std::io::{Write, Result as IOResult};
use ansi_term::{ANSIStrings, ANSIGenericString}; use ansi_term::{ANSIStrings, ANSIGenericString};
use crate::fs::File; use crate::fs::File;
use crate::output::file_name::{FileName, FileStyle};
use crate::style::Colours;
use crate::output::icons::painted_icon;
use crate::output::cell::TextCell; use crate::output::cell::TextCell;
use crate::output::file_name::{FileName, FileStyle};
use crate::output::icons::painted_icon;
use crate::style::Colours;
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
pub struct Options { pub struct Options {
pub icons: bool pub icons: bool,
} }
/// The lines view literally just displays each file, line-by-line. /// The lines view literally just displays each file, line-by-line.
@ -32,7 +33,8 @@ impl<'a> Render<'a> {
cell.push(ANSIGenericString::from(icon), 2); cell.push(ANSIGenericString::from(icon), 2);
cell.append(name_cell.promote()); cell.append(name_cell.promote());
writeln!(w, "{}", ANSIStrings(&cell))?; writeln!(w, "{}", ANSIStrings(&cell))?;
} else { }
else {
writeln!(w, "{}", ANSIStrings(&name_cell))?; writeln!(w, "{}", ANSIStrings(&name_cell))?;
} }
} }

View File

@ -6,8 +6,8 @@ pub use self::escape::escape;
pub mod details; pub mod details;
pub mod file_name; pub mod file_name;
pub mod grid_details;
pub mod grid; pub mod grid;
pub mod grid_details;
pub mod icons; pub mod icons;
pub mod lines; pub mod lines;
pub mod render; pub mod render;

View File

@ -1,7 +1,7 @@
use ansi_term::Style; use ansi_term::Style;
use crate::output::cell::TextCell;
use crate::fs::fields as f; use crate::fs::fields as f;
use crate::output::cell::TextCell;
impl f::Blocks { impl f::Blocks {

View File

@ -6,14 +6,14 @@ use crate::fs::fields as f;
impl f::Type { impl f::Type {
pub fn render<C: Colours>(self, colours: &C) -> ANSIString<'static> { pub fn render<C: Colours>(self, colours: &C) -> ANSIString<'static> {
match self { match self {
Self::File => colours.normal().paint("."), Self::File => colours.normal().paint("."),
Self::Directory => colours.directory().paint("d"), Self::Directory => colours.directory().paint("d"),
Self::Pipe => colours.pipe().paint("|"), Self::Pipe => colours.pipe().paint("|"),
Self::Link => colours.symlink().paint("l"), Self::Link => colours.symlink().paint("l"),
Self::BlockDevice => colours.block_device().paint("b"), Self::BlockDevice => colours.block_device().paint("b"),
Self::CharDevice => colours.char_device().paint("c"), Self::CharDevice => colours.char_device().paint("c"),
Self::Socket => colours.socket().paint("s"), Self::Socket => colours.socket().paint("s"),
Self::Special => colours.special().paint("?"), Self::Special => colours.special().paint("?"),
} }
} }
} }

View File

@ -65,7 +65,7 @@ pub mod test {
fn renamed(&self) -> Style { Fixed(94).normal() } fn renamed(&self) -> Style { Fixed(94).normal() }
fn type_change(&self) -> Style { Fixed(95).normal() } fn type_change(&self) -> Style { Fixed(95).normal() }
fn ignored(&self) -> Style { Fixed(96).normal() } fn ignored(&self) -> Style { Fixed(96).normal() }
fn conflicted(&self) -> Style { Fixed(93).normal() } fn conflicted(&self) -> Style { Fixed(97).normal() }
} }

View File

@ -12,14 +12,16 @@ impl f::Group {
let mut style = colours.not_yours(); let mut style = colours.not_yours();
let group = match users.get_group_by_gid(self.0) { let group = match users.get_group_by_gid(self.0) {
Some(g) => (*g).clone(), Some(g) => (*g).clone(),
None => return TextCell::paint(style, self.0.to_string()), None => return TextCell::paint(style, self.0.to_string()),
}; };
let current_uid = users.get_current_uid(); let current_uid = users.get_current_uid();
if let Some(current_user) = users.get_user_by_uid(current_uid) { if let Some(current_user) = users.get_user_by_uid(current_uid) {
if current_user.primary_group_id() == group.gid() if current_user.primary_group_id() == group.gid()
|| group.members().iter().any(|u| u == current_user.name()) { || group.members().iter().any(|u| u == current_user.name())
{
style = colours.yours(); style = colours.yours();
} }
} }

View File

@ -1,7 +1,7 @@
use ansi_term::Style; use ansi_term::Style;
use crate::output::cell::TextCell;
use crate::fs::fields as f; use crate::fs::fields as f;
use crate::output::cell::TextCell;
impl f::Inode { impl f::Inode {

View File

@ -1,8 +1,8 @@
use ansi_term::Style; use ansi_term::Style;
use locale::Numeric as NumericLocale; use locale::Numeric as NumericLocale;
use crate::output::cell::TextCell;
use crate::fs::fields as f; use crate::fs::fields as f;
use crate::output::cell::TextCell;
impl f::Links { impl f::Links {

View File

@ -1,26 +1,26 @@
use ansi_term::Style; use ansi_term::Style;
use crate::output::cell::TextCell;
use crate::fs::fields as f; use crate::fs::fields as f;
use crate::output::cell::TextCell;
impl f::OctalPermissions { impl f::OctalPermissions {
fn bits_to_octal(r: bool, w: bool, x: bool) -> u8 { fn bits_to_octal(r: bool, w: bool, x: bool) -> u8 {
(r as u8) * 4 + (w as u8) * 2 + (x as u8) (r as u8) * 4 + (w as u8) * 2 + (x as u8)
} }
pub fn render(&self, style: Style) -> TextCell { pub fn render(&self, style: Style) -> TextCell {
let perm = &self.permissions; let perm = &self.permissions;
let octal_sticky = Self::bits_to_octal(perm.setuid, perm.setgid, perm.sticky); let octal_sticky = Self::bits_to_octal(perm.setuid, perm.setgid, perm.sticky);
let octal_owner = Self::bits_to_octal(perm.user_read, perm.user_write, perm.user_execute); let octal_owner = Self::bits_to_octal(perm.user_read, perm.user_write, perm.user_execute);
let octal_group = Self::bits_to_octal(perm.group_read, perm.group_write, perm.group_execute); let octal_group = Self::bits_to_octal(perm.group_read, perm.group_write, perm.group_execute);
let octal_other = Self::bits_to_octal(perm.other_read, perm.other_write, perm.other_execute); let octal_other = Self::bits_to_octal(perm.other_read, perm.other_write, perm.other_execute);
TextCell::paint(style, format!("{}{}{}{}", octal_sticky, octal_owner, octal_group, octal_other)) TextCell::paint(style, format!("{}{}{}{}", octal_sticky, octal_owner, octal_group, octal_other))
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod test { pub mod test {
use crate::output::cell::TextCell; use crate::output::cell::TextCell;

View File

@ -25,23 +25,23 @@ impl f::PermissionsPlus {
} }
impl f::Permissions { impl f::Permissions {
pub fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>> { pub fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>> {
let bit = |bit, chr: &'static str, style: Style| { let bit = |bit, chr: &'static str, style: Style| {
if bit { style.paint(chr) } else { colours.dash().paint("-") } if bit { style.paint(chr) }
else { colours.dash().paint("-") }
}; };
vec![ vec![
bit(self.user_read, "r", colours.user_read()), bit(self.user_read, "r", colours.user_read()),
bit(self.user_write, "w", colours.user_write()), bit(self.user_write, "w", colours.user_write()),
self.user_execute_bit(colours, is_regular_file), self.user_execute_bit(colours, is_regular_file),
bit(self.group_read, "r", colours.group_read()), bit(self.group_read, "r", colours.group_read()),
bit(self.group_write, "w", colours.group_write()), bit(self.group_write, "w", colours.group_write()),
self.group_execute_bit(colours), self.group_execute_bit(colours),
bit(self.other_read, "r", colours.other_read()), bit(self.other_read, "r", colours.other_read()),
bit(self.other_write, "w", colours.other_write()), bit(self.other_write, "w", colours.other_write()),
self.other_execute_bit(colours) self.other_execute_bit(colours)
] ]
} }

View File

@ -21,20 +21,23 @@ impl f::Size {
SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64), SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64),
SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64), SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64),
SizeFormat::JustBytes => { SizeFormat::JustBytes => {
// Use the binary prefix to select a style. // Use the binary prefix to select a style.
let prefix = match NumberPrefix::binary(size as f64) { let prefix = match NumberPrefix::binary(size as f64) {
NumberPrefix::Standalone(_) => None, NumberPrefix::Standalone(_) => None,
NumberPrefix::Prefixed(p, _) => Some(p), NumberPrefix::Prefixed(p, _) => Some(p),
}; };
// But format the number directly using the locale. // But format the number directly using the locale.
let string = numerics.format_int(size); let string = numerics.format_int(size);
return TextCell::paint(colours.size(prefix), string); return TextCell::paint(colours.size(prefix), string);
}, }
}; };
let (prefix, n) = match result { let (prefix, n) = match result {
NumberPrefix::Standalone(b) => return TextCell::paint(colours.size(None), b.to_string()), NumberPrefix::Standalone(b) => return TextCell::paint(colours.size(None), b.to_string()),
NumberPrefix::Prefixed(p, n) => (p, n) NumberPrefix::Prefixed(p, n) => (p, n),
}; };
let symbol = prefix.symbol(); let symbol = prefix.symbol();
@ -72,6 +75,7 @@ impl f::DeviceIDs {
} }
} }
pub trait Colours { pub trait Colours {
fn size(&self, prefix: Option<Prefix>) -> Style; fn size(&self, prefix: Option<Prefix>) -> Style;
fn unit(&self, prefix: Option<Prefix>) -> Style; fn unit(&self, prefix: Option<Prefix>) -> Style;

View File

@ -1,3 +1,5 @@
use std::time::SystemTime;
use datetime::TimeZone; use datetime::TimeZone;
use ansi_term::Style; use ansi_term::Style;
@ -6,23 +8,20 @@ use crate::output::time::TimeFormat;
pub trait Render { pub trait Render {
fn render(self, style: Style, fn render(self, style: Style, tz: &Option<TimeZone>, format: &TimeFormat) -> TextCell;
tz: &Option<TimeZone>,
format: &TimeFormat) -> TextCell;
} }
impl Render for Option<std::time::SystemTime> { impl Render for Option<SystemTime> {
fn render(self, style: Style, fn render(self, style: Style, tz: &Option<TimeZone>, format: &TimeFormat) -> TextCell {
tz: &Option<TimeZone>,
format: &TimeFormat) -> TextCell {
let datestamp = if let Some(time) = self { let datestamp = if let Some(time) = self {
if let Some(ref tz) = tz { if let Some(ref tz) = tz {
format.format_zoned(time, tz) format.format_zoned(time, tz)
} else { }
else {
format.format_local(time) format.format_local(time)
} }
} else { }
else {
String::from("-") String::from("-")
}; };

View File

@ -5,7 +5,6 @@ use crate::fs::fields as f;
use crate::output::cell::TextCell; use crate::output::cell::TextCell;
impl f::User { impl f::User {
pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U) -> TextCell { pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U) -> TextCell {
let user_name = match users.get_user_by_uid(self.0) { let user_name = match users.get_user_by_uid(self.0) {
@ -13,8 +12,8 @@ impl f::User {
None => self.0.to_string(), None => self.0.to_string(),
}; };
let style = if users.get_current_uid() == self.0 { colours.you() } let style = if users.get_current_uid() == self.0 { colours.you() }
else { colours.someone_else() }; else { colours.someone_else() };
TextCell::paint(style, user_name) TextCell::paint(style, user_name)
} }
} }

View File

@ -11,12 +11,12 @@ use log::*;
use users::UsersCache; use users::UsersCache;
use crate::style::Colours; use crate::fs::{File, fields as f};
use crate::fs::feature::git::GitCache;
use crate::output::cell::TextCell; use crate::output::cell::TextCell;
use crate::output::render::TimeRender; use crate::output::render::TimeRender;
use crate::output::time::TimeFormat; use crate::output::time::TimeFormat;
use crate::fs::{File, fields as f}; use crate::style::Colours;
use crate::fs::feature::git::GitCache;
/// Options for displaying a table. /// Options for displaying a table.
@ -108,7 +108,7 @@ impl Columns {
columns.push(Column::Timestamp(TimeType::Accessed)); columns.push(Column::Timestamp(TimeType::Accessed));
} }
if cfg!(feature="git") && self.git && actually_enable_git { if cfg!(feature = "git") && self.git && actually_enable_git {
columns.push(Column::GitStatus); columns.push(Column::GitStatus);
} }
@ -136,7 +136,8 @@ pub enum Column {
/// right-aligned, and text is left-aligned. /// right-aligned, and text is left-aligned.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum Alignment { pub enum Alignment {
Left, Right, Left,
Right,
} }
impl Column { impl Column {
@ -144,12 +145,12 @@ impl Column {
/// Get the alignment this column should use. /// Get the alignment this column should use.
pub fn alignment(self) -> Alignment { pub fn alignment(self) -> Alignment {
match self { match self {
Self::FileSize | Self::FileSize |
Self::HardLinks | Self::HardLinks |
Self::Inode | Self::Inode |
Self::Blocks | Self::Blocks |
Self::GitStatus => Alignment::Right, Self::GitStatus => Alignment::Right,
_ => Alignment::Left, _ => Alignment::Left,
} }
} }
@ -199,6 +200,7 @@ impl Default for SizeFormat {
/// across most (all?) operating systems. /// across most (all?) operating systems.
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
pub enum TimeType { pub enum TimeType {
/// The files modified time (`st_mtime`). /// The files modified time (`st_mtime`).
Modified, Modified,
@ -229,7 +231,7 @@ impl TimeType {
/// Fields for which of a files time fields should be displayed in the /// Fields for which of a files time fields should be displayed in the
/// columns output. /// columns output.
/// ///
/// There should always be at least one of these--there's no way to disable /// There should always be at least one of these — theres no way to disable
/// the time columns entirely (yet). /// the time columns entirely (yet).
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
pub struct TimeTypes { pub struct TimeTypes {
@ -244,15 +246,18 @@ impl Default for TimeTypes {
/// By default, display just the modified time. This is the most /// By default, display just the modified time. This is the most
/// common option, which is why it has this shorthand. /// common option, which is why it has this shorthand.
fn default() -> Self { fn default() -> Self {
Self { modified: true, changed: false, accessed: false, created: false } Self {
modified: true,
changed: false,
accessed: false,
created: false,
}
} }
} }
/// The **environment** struct contains any data that could change between /// The **environment** struct contains any data that could change between
/// running instances of exa, depending on the user's computer's configuration. /// running instances of exa, depending on the users computers configuration.
/// ///
/// Any environment field should be able to be mocked up for test runs. /// Any environment field should be able to be mocked up for test runs.
pub struct Environment { pub struct Environment {
@ -260,8 +265,8 @@ pub struct Environment {
/// Localisation rules for formatting numbers. /// Localisation rules for formatting numbers.
numeric: locale::Numeric, numeric: locale::Numeric,
/// The computer's current time zone. This gets used to determine how to /// The computers current time zone. This gets used to determine how to
/// offset files' timestamps. /// offset files timestamps.
tz: Option<TimeZone>, tz: Option<TimeZone>,
/// Mapping cache of user IDs to usernames. /// Mapping cache of user IDs to usernames.
@ -275,7 +280,9 @@ impl Environment {
pub fn load_all() -> Self { pub fn load_all() -> Self {
let tz = match determine_time_zone() { let tz = match determine_time_zone() {
Ok(t) => Some(t), Ok(t) => {
Some(t)
}
Err(ref e) => { Err(ref e) => {
println!("Unable to determine time zone: {}", e); println!("Unable to determine time zone: {}", e);
None None
@ -283,7 +290,7 @@ impl Environment {
}; };
let numeric = locale::Numeric::load_user_locale() let numeric = locale::Numeric::load_user_locale()
.unwrap_or_else(|_| locale::Numeric::english()); .unwrap_or_else(|_| locale::Numeric::english());
let users = Mutex::new(UsersCache::new()); let users = Mutex::new(UsersCache::new());
@ -294,7 +301,8 @@ impl Environment {
fn determine_time_zone() -> TZResult<TimeZone> { fn determine_time_zone() -> TZResult<TimeZone> {
if let Ok(file) = env::var("TZ") { if let Ok(file) = env::var("TZ") {
TimeZone::from_file(format!("/usr/share/zoneinfo/{}", file)) TimeZone::from_file(format!("/usr/share/zoneinfo/{}", file))
} else { }
else {
TimeZone::from_file("/etc/localtime") TimeZone::from_file("/etc/localtime")
} }
} }
@ -321,7 +329,10 @@ impl<'a, 'f> Table<'a> {
let widths = TableWidths::zero(columns.len()); let widths = TableWidths::zero(columns.len());
Table { Table {
colours, widths, columns, git, colours,
widths,
columns,
git,
env: &options.env, env: &options.env,
time_format: &options.time_format, time_format: &options.time_format,
size_format: options.size_format, size_format: options.size_format,
@ -368,25 +379,52 @@ impl<'a, 'f> Table<'a> {
fn display(&self, file: &File, column: Column, xattrs: bool) -> TextCell { fn display(&self, file: &File, column: Column, xattrs: bool) -> TextCell {
match column { match column {
Column::Permissions => self.permissions_plus(file, xattrs).render(self.colours), Column::Permissions => {
Column::FileSize => file.size().render(self.colours, self.size_format, &self.env.numeric), self.permissions_plus(file, xattrs).render(self.colours)
Column::HardLinks => file.links().render(self.colours, &self.env.numeric), }
Column::Inode => file.inode().render(self.colours.inode), Column::FileSize => {
Column::Blocks => file.blocks().render(self.colours), file.size().render(self.colours, self.size_format, &self.env.numeric)
Column::User => file.user().render(self.colours, &*self.env.lock_users()), }
Column::Group => file.group().render(self.colours, &*self.env.lock_users()), Column::HardLinks => {
Column::GitStatus => self.git_status(file).render(self.colours), file.links().render(self.colours, &self.env.numeric)
Column::Octal => self.octal_permissions(file).render(self.colours.octal), }
Column::Inode => {
file.inode().render(self.colours.inode)
}
Column::Blocks => {
file.blocks().render(self.colours)
}
Column::User => {
file.user().render(self.colours, &*self.env.lock_users())
}
Column::Group => {
file.group().render(self.colours, &*self.env.lock_users())
}
Column::GitStatus => {
self.git_status(file).render(self.colours)
}
Column::Octal => {
self.octal_permissions(file).render(self.colours.octal)
}
Column::Timestamp(TimeType::Modified) => file.modified_time().render(self.colours.date, &self.env.tz, self.time_format), Column::Timestamp(TimeType::Modified) => {
Column::Timestamp(TimeType::Changed) => file.changed_time() .render(self.colours.date, &self.env.tz, self.time_format), file.modified_time().render(self.colours.date, &self.env.tz, self.time_format)
Column::Timestamp(TimeType::Created) => file.created_time() .render(self.colours.date, &self.env.tz, self.time_format), }
Column::Timestamp(TimeType::Accessed) => file.accessed_time().render(self.colours.date, &self.env.tz, self.time_format), Column::Timestamp(TimeType::Changed) => {
file.changed_time().render(self.colours.date, &self.env.tz, self.time_format)
}
Column::Timestamp(TimeType::Created) => {
file.created_time().render(self.colours.date, &self.env.tz, self.time_format)
}
Column::Timestamp(TimeType::Accessed) => {
file.accessed_time().render(self.colours.date, &self.env.tz, self.time_format)
}
} }
} }
fn git_status(&self, file: &File) -> f::Git { fn git_status(&self, file: &File) -> f::Git {
debug!("Getting Git status for file {:?}", file.path); debug!("Getting Git status for file {:?}", file.path);
self.git self.git
.map(|g| g.get(&file.path, file.is_directory())) .map(|g| g.get(&file.path, file.is_directory()))
.unwrap_or_default() .unwrap_or_default()
@ -395,12 +433,22 @@ impl<'a, 'f> Table<'a> {
pub fn render(&self, row: Row) -> TextCell { pub fn render(&self, row: Row) -> TextCell {
let mut cell = TextCell::default(); let mut cell = TextCell::default();
for (n, (this_cell, width)) in row.cells.into_iter().zip(self.widths.iter()).enumerate() { let iter = row.cells.into_iter()
.zip(self.widths.iter())
.enumerate();
for (n, (this_cell, width)) in iter {
let padding = width - *this_cell.width; let padding = width - *this_cell.width;
match self.columns[n].alignment() { match self.columns[n].alignment() {
Alignment::Left => { cell.append(this_cell); cell.add_spaces(padding); } Alignment::Left => {
Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); } cell.append(this_cell);
cell.add_spaces(padding);
}
Alignment::Right => {
cell.add_spaces(padding);
cell.append(this_cell);
}
} }
cell.add_spaces(1); cell.add_spaces(1);
@ -411,7 +459,6 @@ impl<'a, 'f> Table<'a> {
} }
pub struct TableWidths(Vec<usize>); pub struct TableWidths(Vec<usize>);
impl Deref for TableWidths { impl Deref for TableWidths {
@ -424,7 +471,7 @@ impl Deref for TableWidths {
impl TableWidths { impl TableWidths {
pub fn zero(count: usize) -> Self { pub fn zero(count: usize) -> Self {
Self(vec![ 0; count ]) Self(vec![0; count])
} }
pub fn add_widths(&mut self, row: &Row) { pub fn add_widths(&mut self, row: &Row) {

View File

@ -119,28 +119,25 @@ impl DefaultFormat {
Self { current_year, locale, date_and_time, date_and_year } Self { current_year, locale, date_and_time, date_and_year }
} }
}
impl DefaultFormat {
fn is_recent(&self, date: LocalDateTime) -> bool { fn is_recent(&self, date: LocalDateTime) -> bool {
date.year() == self.current_year date.year() == self.current_year
} }
fn month_to_abbrev(month: datetime::Month) -> &'static str { fn month_to_abbrev(month: datetime::Month) -> &'static str {
match month { match month {
datetime::Month::January => "Jan", datetime::Month::January => "Jan",
datetime::Month::February => "Feb", datetime::Month::February => "Feb",
datetime::Month::March => "Mar", datetime::Month::March => "Mar",
datetime::Month::April => "Apr", datetime::Month::April => "Apr",
datetime::Month::May => "May", datetime::Month::May => "May",
datetime::Month::June => "Jun", datetime::Month::June => "Jun",
datetime::Month::July => "Jul", datetime::Month::July => "Jul",
datetime::Month::August => "Aug", datetime::Month::August => "Aug",
datetime::Month::September => "Sep", datetime::Month::September => "Sep",
datetime::Month::October => "Oct", datetime::Month::October => "Oct",
datetime::Month::November => "Nov", datetime::Month::November => "Nov",
datetime::Month::December => "Dec", datetime::Month::December => "Dec",
} }
} }
@ -217,7 +214,6 @@ fn long_zoned(time: SystemTime, zone: &TimeZone) -> String {
date.hour(), date.minute()) date.hour(), date.minute())
} }
#[allow(trivial_numeric_casts)] #[allow(trivial_numeric_casts)]
fn full_local(time: SystemTime) -> String { fn full_local(time: SystemTime) -> String {
let date = LocalDateTime::at(systemtime_epoch(time)); let date = LocalDateTime::at(systemtime_epoch(time));
@ -240,7 +236,6 @@ fn full_zoned(time: SystemTime, zone: &TimeZone) -> String {
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct ISOFormat { pub struct ISOFormat {

View File

@ -111,18 +111,21 @@ impl TreeTrunk {
// If this isnt our first iteration, then update the tree parts thus // If this isnt our first iteration, then update the tree parts thus
// far to account for there being another row after it. // far to account for there being another row after it.
if let Some(last) = self.last_params { if let Some(last) = self.last_params {
self.stack[last.depth.0] = if last.last { TreePart::Blank } else { TreePart::Line }; self.stack[last.depth.0] = if last.last { TreePart::Blank }
else { TreePart::Line };
} }
// Make sure the stack has enough space, then add or modify another // Make sure the stack has enough space, then add or modify another
// part into it. // part into it.
self.stack.resize(params.depth.0 + 1, TreePart::Edge); self.stack.resize(params.depth.0 + 1, TreePart::Edge);
self.stack[params.depth.0] = if params.last { TreePart::Corner } else { TreePart::Edge }; self.stack[params.depth.0] = if params.last { TreePart::Corner }
else { TreePart::Edge };
self.last_params = Some(params); self.last_params = Some(params);
// Return the tree parts as a slice of the stack. // Return the tree parts as a slice of the stack.
// //
// Ignore the first element here to prevent a 'zeroth level' from // Ignore the first element here to prevent a zeroth level from
// appearing before the very first directory. This level would // appearing before the very first directory. This level would
// join unrelated directories without connecting to anything: // join unrelated directories without connecting to anything:
// //
@ -159,7 +162,8 @@ impl TreeDepth {
/// Creates an iterator that, as well as yielding each value, yields a /// Creates an iterator that, as well as yielding each value, yields a
/// `TreeParams` with the current depth and last flag filled in. /// `TreeParams` with the current depth and last flag filled in.
pub fn iterate_over<I, T>(self, inner: I) -> Iter<I> pub fn iterate_over<I, T>(self, inner: I) -> Iter<I>
where I: ExactSizeIterator+Iterator<Item=T> { where I: ExactSizeIterator + Iterator<Item = T>
{
Iter { current_depth: self, inner } Iter { current_depth: self, inner }
} }
} }
@ -171,14 +175,16 @@ pub struct Iter<I> {
} }
impl<I, T> Iterator for Iter<I> impl<I, T> Iterator for Iter<I>
where I: ExactSizeIterator+Iterator<Item=T> { where I: ExactSizeIterator + Iterator<Item = T>
{
type Item = (TreeParams, T); type Item = (TreeParams, T);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|t| { let t = self.inner.next()?;
// use exact_size_is_empty API soon
(TreeParams::new(self.current_depth, self.inner.len() == 0), t) // TODO: use exact_size_is_empty API soon
}) let params = TreeParams::new(self.current_depth, self.inner.len() == 0);
Some((params, t))
} }
} }
@ -194,20 +200,20 @@ mod trunk_test {
#[test] #[test]
fn empty_at_first() { fn empty_at_first() {
let mut tt = TreeTrunk::default(); let mut tt = TreeTrunk::default();
assert_eq!(tt.new_row(params(0, true)), &[]); assert_eq!(tt.new_row(params(0, true)), &[ ]);
} }
#[test] #[test]
fn one_child() { fn one_child() {
let mut tt = TreeTrunk::default(); let mut tt = TreeTrunk::default();
assert_eq!(tt.new_row(params(0, true)), &[]); assert_eq!(tt.new_row(params(0, true)), &[ ]);
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
} }
#[test] #[test]
fn two_children() { fn two_children() {
let mut tt = TreeTrunk::default(); let mut tt = TreeTrunk::default();
assert_eq!(tt.new_row(params(0, true)), &[]); assert_eq!(tt.new_row(params(0, true)), &[ ]);
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]); assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
} }
@ -215,11 +221,11 @@ mod trunk_test {
#[test] #[test]
fn two_times_two_children() { fn two_times_two_children() {
let mut tt = TreeTrunk::default(); let mut tt = TreeTrunk::default();
assert_eq!(tt.new_row(params(0, false)), &[]); assert_eq!(tt.new_row(params(0, false)), &[ ]);
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]); assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
assert_eq!(tt.new_row(params(0, true)), &[]); assert_eq!(tt.new_row(params(0, true)), &[ ]);
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]); assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
} }
@ -227,7 +233,7 @@ mod trunk_test {
#[test] #[test]
fn two_times_two_nested_children() { fn two_times_two_nested_children() {
let mut tt = TreeTrunk::default(); let mut tt = TreeTrunk::default();
assert_eq!(tt.new_row(params(0, true)), &[]); assert_eq!(tt.new_row(params(0, true)), &[ ]);
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]); assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Line, TreePart::Edge ]); assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Line, TreePart::Edge ]);
@ -240,7 +246,6 @@ mod trunk_test {
} }
#[cfg(test)] #[cfg(test)]
mod iter_test { mod iter_test {
use super::*; use super::*;

View File

@ -1,9 +1,8 @@
use ansi_term::Style;
use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Purple, Fixed}; use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Purple, Fixed};
use ansi_term::Style;
use crate::output::render;
use crate::output::file_name::Colours as FileNameColours; use crate::output::file_name::Colours as FileNameColours;
use crate::output::render;
use crate::style::lsc::Pair; use crate::style::lsc::Pair;
@ -190,11 +189,8 @@ impl Colours {
impl Size { impl Size {
pub fn colourful(scale: bool) -> Self { pub fn colourful(scale: bool) -> Self {
if scale { if scale { Self::colourful_scale() }
Self::colourful_scale() else { Self::colourful_plain() }
} else {
Self::colourful_plain()
}
} }
fn colourful_plain() -> Self { fn colourful_plain() -> Self {
@ -233,7 +229,6 @@ impl Size {
unit_giga: Green.normal(), unit_giga: Green.normal(),
unit_huge: Green.normal(), unit_huge: Green.normal(),
} }
} }
} }
@ -352,6 +347,7 @@ impl Colours {
_ => return false, _ => return false,
} }
true true
} }

View File

@ -1,3 +1,4 @@
use std::iter::Peekable;
use std::ops::FnMut; use std::ops::FnMut;
use ansi_term::{Colour, Style}; use ansi_term::{Colour, Style};
@ -21,7 +22,6 @@ use ansi_term::Colour::*;
// just not worth doing, and there should really be a way to just use slices // just not worth doing, and there should really be a way to just use slices
// of the LS_COLORS string without having to parse them. // of the LS_COLORS string without having to parse them.
pub struct LSColors<'var>(pub &'var str); pub struct LSColors<'var>(pub &'var str);
impl<'var> LSColors<'var> { impl<'var> LSColors<'var> {
@ -33,21 +33,17 @@ impl<'var> LSColors<'var> {
.take(3) .take(3)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if bits.len() == 2 && !bits[0].is_empty() && !bits[1].is_empty() { if bits.len() == 2 && ! bits[0].is_empty() && ! bits[1].is_empty() {
callback(Pair { key: bits[0], value: bits[1] }); callback(Pair { key: bits[0], value: bits[1] });
} }
} }
} }
} }
pub struct Pair<'var> {
pub key: &'var str,
pub value: &'var str,
}
use std::iter::Peekable;
fn parse_into_high_colour<'a, I>(iter: &mut Peekable<I>) -> Option<Colour> fn parse_into_high_colour<'a, I>(iter: &mut Peekable<I>) -> Option<Colour>
where I: Iterator<Item=&'a str> { where I: Iterator<Item = &'a str>
{
match iter.peek() { match iter.peek() {
Some(&"5") => { Some(&"5") => {
let _ = iter.next(); let _ = iter.next();
@ -57,11 +53,12 @@ where I: Iterator<Item=&'a str> {
} }
} }
} }
Some(&"2") => { Some(&"2") => {
let _ = iter.next(); let _ = iter.next();
if let Some(hexes) = iter.next() { if let Some(hexes) = iter.next() {
// Some terminals support R:G:B instead of R;G;B // Some terminals support R:G:B instead of R;G;B
// but this clashes with splitting on ':' in each_pair above. // but this clashes with splitting on : in each_pair above.
/*if hexes.contains(':') { /*if hexes.contains(':') {
let rgb = hexes.splitn(3, ':').collect::<Vec<_>>(); let rgb = hexes.splitn(3, ':').collect::<Vec<_>>();
if rgb.len() != 3 { if rgb.len() != 3 {
@ -79,11 +76,19 @@ where I: Iterator<Item=&'a str> {
} }
} }
} }
_ => {}, _ => {},
} }
None None
} }
pub struct Pair<'var> {
pub key: &'var str,
pub value: &'var str,
}
impl<'var> Pair<'var> { impl<'var> Pair<'var> {
pub fn to_style(&self) -> Style { pub fn to_style(&self) -> Style {
let mut style = Style::default(); let mut style = Style::default();
@ -125,7 +130,7 @@ impl<'var> Pair<'var> {
"47" => style = style.on(White), "47" => style = style.on(White),
"48" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.on(c) }, "48" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.on(c) },
_ => {/* ignore the error and do nothing */}, _ => {/* ignore the error and do nothing */},
} }
} }
@ -185,7 +190,6 @@ mod ansi_test {
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;