From f555d42972b6e7f3c4bb53b3c3cf95eca60cee6a Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Fri, 22 Sep 2023 18:40:30 +0200 Subject: [PATCH] refactor: fix rustfmt issues and place skips where needed Signed-off-by: Sandro-Alessio Gierens --- benches/my_benchmark.rs | 6 +- build.rs | 58 +++--- src/fs/dir.rs | 61 +++---- src/fs/dir_action.rs | 19 +- src/fs/feature/git.rs | 115 ++++++------ src/fs/feature/mod.rs | 8 +- src/fs/feature/xattr.rs | 156 ++++++++-------- src/fs/fields.rs | 31 +--- src/fs/file.rs | 278 +++++++++++++++------------- src/fs/filter.rs | 59 +++--- src/fs/mounts/linux.rs | 22 +-- src/fs/mounts/macos.rs | 16 +- src/fs/mounts/mod.rs | 10 +- src/info/filetype.rs | 22 ++- src/info/sources.rs | 7 +- src/logger.rs | 29 +-- src/main.rs | 172 +++++++++++++----- src/options/dir_action.rs | 50 +++--- src/options/error.rs | 21 ++- src/options/file_name.rs | 43 +++-- src/options/filter.rs | 121 +++++-------- src/options/flags.rs | 2 +- src/options/help.rs | 17 +- src/options/mod.rs | 63 ++++--- src/options/parser.rs | 249 +++++++++++++------------ src/options/theme.rs | 89 ++++----- src/options/vars.rs | 5 +- src/options/version.rs | 16 +- src/options/view.rs | 300 ++++++++++++++++++------------- src/output/cell.rs | 21 +-- src/output/details.rs | 165 +++++++++++------ src/output/escape.rs | 1 - src/output/file_name.rs | 115 ++++++------ src/output/grid.rs | 23 +-- src/output/grid_details.rs | 113 +++++++----- src/output/icons.rs | 9 +- src/output/lines.rs | 3 +- src/output/mod.rs | 7 +- src/output/render/blocks.rs | 98 ++++++---- src/output/render/filetype.rs | 3 +- src/output/render/git.rs | 64 +++---- src/output/render/groups.rs | 75 +++++--- src/output/render/inode.rs | 5 +- src/output/render/links.rs | 50 ++++-- src/output/render/octal.rs | 108 ++++++++--- src/output/render/permissions.rs | 191 +++++++++++++------- src/output/render/securityctx.rs | 14 +- src/output/render/size.rs | 109 +++++++---- src/output/render/times.rs | 6 +- src/output/render/users.rs | 45 +++-- src/output/table.rs | 87 +++++---- src/output/time.rs | 55 +++--- src/output/tree.rs | 44 +++-- src/theme/default_theme.rs | 65 +++---- src/theme/lsc.rs | 61 ++++--- src/theme/mod.rs | 95 ++++++---- src/theme/ui_styles.rs | 15 +- tests/cli_tests.rs | 14 +- 58 files changed, 2106 insertions(+), 1600 deletions(-) diff --git a/benches/my_benchmark.rs b/benches/my_benchmark.rs index 37a19419..bd019dfe 100644 --- a/benches/my_benchmark.rs +++ b/benches/my_benchmark.rs @@ -1,7 +1,11 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("logger", |b| b.iter(|| eza::logger::configure(black_box(std::env::var_os(eza::options::vars::EZA_DEBUG))))); + c.bench_function("logger", |b| { + b.iter(|| { + eza::logger::configure(black_box(std::env::var_os(eza::options::vars::EZA_DEBUG))) + }) + }); } criterion_group!(benches, criterion_benchmark); diff --git a/build.rs b/build.rs index 143a467b..614a99ff 100644 --- a/build.rs +++ b/build.rs @@ -9,7 +9,6 @@ /// /// - https://stackoverflow.com/q/43753491/3484614 /// - https://crates.io/crates/vergen - use std::env; use std::fs::File; use std::io::{self, Write}; @@ -17,31 +16,40 @@ use std::path::PathBuf; use chrono::prelude::*; - /// The build script entry point. fn main() -> io::Result<()> { #![allow(clippy::write_with_newline)] let tagline = "eza - A modern, maintained replacement for ls"; - let url = "https://github.com/eza-community/eza"; + let url = "https://github.com/eza-community/eza"; - let ver = - if is_debug_build() { - format!("{}\nv{} \\1;31m(pre-release debug build!)\\0m\n\\1;4;34m{}\\0m", tagline, version_string(), url) - } - else if is_development_version() { - format!("{}\nv{} [{}] built on {} \\1;31m(pre-release!)\\0m\n\\1;4;34m{}\\0m", tagline, version_string(), git_hash(), build_date(), url) - } - else { - format!("{}\nv{}\n\\1;4;34m{}\\0m", tagline, version_string(), url) - }; + let ver = if is_debug_build() { + format!( + "{}\nv{} \\1;31m(pre-release debug build!)\\0m\n\\1;4;34m{}\\0m", + tagline, + version_string(), + url + ) + } else if is_development_version() { + format!( + "{}\nv{} [{}] built on {} \\1;31m(pre-release!)\\0m\n\\1;4;34m{}\\0m", + tagline, + version_string(), + git_hash(), + build_date(), + url + ) + } else { + format!("{}\nv{}\n\\1;4;34m{}\\0m", tagline, version_string(), url) + }; // We need to create these files in the Cargo output directory. let out = PathBuf::from(env::var("OUT_DIR").unwrap()); let path = &out.join("version_string.txt"); // Bland version text - let mut f = File::create(path).unwrap_or_else(|_| { panic!("{}", path.to_string_lossy().to_string()) }); + let mut f = + File::create(path).unwrap_or_else(|_| panic!("{}", path.to_string_lossy().to_string())); writeln!(f, "{}", strip_codes(&ver))?; Ok(()) @@ -49,9 +57,10 @@ fn main() -> io::Result<()> { /// Removes escape codes from a string. fn strip_codes(input: &str) -> String { - input.replace("\\0m", "") - .replace("\\1;31m", "") - .replace("\\1;4;34m", "") + input + .replace("\\0m", "") + .replace("\\1;31m", "") + .replace("\\1;4;34m", "") } /// Retrieve the project’s current Git hash, as a string. @@ -61,8 +70,12 @@ fn git_hash() -> String { String::from_utf8_lossy( &Command::new("git") .args(["rev-parse", "--short", "HEAD"]) - .output().unwrap() - .stdout).trim().to_string() + .output() + .unwrap() + .stdout, + ) + .trim() + .to_string() } /// Whether we should show pre-release info in the version string. @@ -88,7 +101,7 @@ fn version_string() -> String { let mut ver = cargo_version(); let feats = nonstandard_features_string(); - if ! feats.is_empty() { + if !feats.is_empty() { ver.push_str(&format!(" [{}]", &feats)); } @@ -98,7 +111,7 @@ fn version_string() -> String { /// Finds whether a feature is enabled by examining the Cargo variable. fn feature_enabled(name: &str) -> bool { env::var(format!("CARGO_FEATURE_{}", name)) - .map(|e| ! e.is_empty()) + .map(|e| !e.is_empty()) .unwrap_or(false) } @@ -108,8 +121,7 @@ fn nonstandard_features_string() -> String { if feature_enabled("GIT") { s.push("+git"); - } - else { + } else { s.push("-git"); } diff --git a/src/fs/dir.rs b/src/fs/dir.rs index f00930be..b22245f5 100644 --- a/src/fs/dir.rs +++ b/src/fs/dir.rs @@ -1,7 +1,7 @@ use crate::fs::feature::git::GitCache; use crate::fs::fields::GitStatus; -use std::io; use std::fs; +use std::io; use std::path::{Path, PathBuf}; use std::slice::Iter as SliceIter; @@ -9,7 +9,6 @@ use log::*; use crate::fs::File; - /// A **Dir** provides a cached list of the file paths in a directory that’s /// being listed. /// @@ -17,7 +16,6 @@ use crate::fs::File; /// check the existence of surrounding files, then highlight themselves /// accordingly. (See `File#get_source_files`) pub struct Dir { - /// A vector of the files that have been read from this directory. contents: Vec, @@ -26,7 +24,6 @@ pub struct Dir { } impl Dir { - /// Create a new Dir object filled with all the files in the directory /// pointed to by the given path. Fails if the directory can’t be read, or /// isn’t actually a directory, or if there’s an IO error that occurs at @@ -39,8 +36,8 @@ impl Dir { info!("Reading directory {:?}", &path); let contents = fs::read_dir(&path)? - .map(|result| result.map(|entry| entry.path())) - .collect::>()?; + .map(|result| result.map(|entry| entry.path())) + .collect::>()?; info!("Read directory success {:?}", &path); Ok(Self { contents, path }) @@ -48,12 +45,18 @@ impl Dir { /// Produce an iterator of IO results of trying to read all the files in /// this directory. - pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool, deref_links: bool) -> Files<'dir, 'ig> { + pub fn files<'dir, 'ig>( + &'dir self, + dots: DotFilter, + git: Option<&'ig GitCache>, + git_ignoring: bool, + deref_links: bool, + ) -> Files<'dir, 'ig> { Files { - inner: self.contents.iter(), - dir: self, - dotfiles: dots.shows_dotfiles(), - dots: dots.dots(), + inner: self.contents.iter(), + dir: self, + dotfiles: dots.shows_dotfiles(), + dots: dots.dots(), git, git_ignoring, deref_links, @@ -71,10 +74,8 @@ impl Dir { } } - /// Iterator over reading the contents of a directory as `File` objects. pub struct Files<'dir, 'ig> { - /// The internal iterator over the paths that have been read already. inner: SliceIter<'dir, PathBuf>, @@ -112,26 +113,26 @@ impl<'dir, 'ig> Files<'dir, 'ig> { loop { if let Some(path) = self.inner.next() { let filename = File::filename(path); - if ! self.dotfiles && filename.starts_with('.') { + if !self.dotfiles && filename.starts_with('.') { continue; } // Also hide _prefix files on Windows because it's used by old applications // as an alternative to dot-prefix files. #[cfg(windows)] - if ! self.dotfiles && filename.starts_with('_') { + if !self.dotfiles && filename.starts_with('_') { continue; } if self.git_ignoring { let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default(); if git_status.unstaged == GitStatus::Ignored { - continue; + continue; } } let file = File::from_args(path.clone(), self.dir, filename, self.deref_links) - .map_err(|e| (path.clone(), e)); + .map_err(|e| (path.clone(), e)); // Windows has its own concept of hidden files, when dotfiles are // hidden Windows hidden files should also be filtered out @@ -143,7 +144,7 @@ impl<'dir, 'ig> Files<'dir, 'ig> { return Some(file); } - return None + return None; } } } @@ -151,7 +152,6 @@ impl<'dir, 'ig> Files<'dir, 'ig> { /// The dot directories that need to be listed before actual files, if any. /// If these aren’t being printed, then `FilesNext` is used to skip them. enum DotsNext { - /// List the `.` directory next. Dot, @@ -169,30 +169,24 @@ impl<'dir, 'ig> Iterator for Files<'dir, 'ig> { match self.dots { DotsNext::Dot => { self.dots = DotsNext::DotDot; - Some(File::new_aa_current(self.dir) - .map_err(|e| (Path::new(".").to_path_buf(), e))) + Some(File::new_aa_current(self.dir).map_err(|e| (Path::new(".").to_path_buf(), e))) } DotsNext::DotDot => { self.dots = DotsNext::Files; - Some(File::new_aa_parent(self.parent(), self.dir) - .map_err(|e| (self.parent(), e))) + Some(File::new_aa_parent(self.parent(), self.dir).map_err(|e| (self.parent(), e))) } - DotsNext::Files => { - self.next_visible_file() - } + DotsNext::Files => self.next_visible_file(), } } } - /// Usually files in Unix use a leading dot to be hidden or visible, but two /// entries in particular are “extra-hidden”: `.` and `..`, which only become /// visible after an extra `-a` option. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum DotFilter { - /// Shows files, dotfiles, and `.` and `..`. DotfilesAndDots, @@ -210,12 +204,11 @@ impl Default for DotFilter { } impl DotFilter { - /// Whether this filter should show dotfiles in a listing. fn shows_dotfiles(self) -> bool { match self { - Self::JustFiles => false, - Self::Dotfiles => true, + Self::JustFiles => false, + Self::Dotfiles => true, Self::DotfilesAndDots => true, } } @@ -223,9 +216,9 @@ impl DotFilter { /// Whether this filter should add dot directories to a listing. fn dots(self) -> DotsNext { match self { - Self::JustFiles => DotsNext::Files, - Self::Dotfiles => DotsNext::Files, - Self::DotfilesAndDots => DotsNext::Dot, + Self::JustFiles => DotsNext::Files, + Self::Dotfiles => DotsNext::Files, + Self::DotfilesAndDots => DotsNext::Dot, } } } diff --git a/src/fs/dir_action.rs b/src/fs/dir_action.rs index 6e10403d..507c7979 100644 --- a/src/fs/dir_action.rs +++ b/src/fs/dir_action.rs @@ -21,7 +21,6 @@ /// directories inline, with their contents immediately underneath. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum DirAction { - /// This directory should be listed along with the regular files, instead /// of having its contents queried. AsFile, @@ -37,30 +36,27 @@ pub enum DirAction { } impl DirAction { - /// Gets the recurse options, if this dir action has any. pub fn recurse_options(self) -> Option { match self { - Self::Recurse(o) => Some(o), - _ => None, + Self::Recurse(o) => Some(o), + _ => None, } } /// Whether to treat directories as regular files or not. pub fn treat_dirs_as_files(self) -> bool { match self { - Self::AsFile => true, - Self::Recurse(o) => o.tree, - Self::List => false, + Self::AsFile => true, + Self::Recurse(o) => o.tree, + Self::List => false, } } } - /// The options that determine how to recurse into a directory. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct RecurseOptions { - /// Whether recursion should be done as a tree or as multiple individual /// views of files. pub tree: bool, @@ -71,12 +67,11 @@ pub struct RecurseOptions { } impl RecurseOptions { - /// Returns whether a directory of the given depth would be too deep. pub fn is_too_deep(self, depth: usize) -> bool { match self.max_depth { - None => false, - Some(d) => d <= depth + None => false, + Some(d) => d <= depth, } } } diff --git a/src/fs/feature/git.rs b/src/fs/feature/git.rs index a470d0f7..421fa803 100644 --- a/src/fs/feature/git.rs +++ b/src/fs/feature/git.rs @@ -11,13 +11,11 @@ use log::*; use crate::fs::fields as f; - /// A **Git cache** is assembled based on the user’s input arguments. /// /// This uses vectors to avoid the overhead of hashing: it’s not worth it when the /// expected number of Git repositories per exa invocation is 0 or 1... pub struct GitCache { - /// A list of discovered Git repositories and their paths. repos: Vec, @@ -31,7 +29,8 @@ impl GitCache { } pub fn get(&self, index: &Path, prefix_lookup: bool) -> f::Git { - self.repos.iter() + self.repos + .iter() .find(|repo| repo.has_path(index)) .map(|repo| repo.search(index, prefix_lookup)) .unwrap_or_default() @@ -41,7 +40,8 @@ impl GitCache { use std::iter::FromIterator; impl FromIterator for GitCache { fn from_iter(iter: I) -> Self - where I: IntoIterator + where + I: IntoIterator, { let iter = iter.into_iter(); let mut git = Self { @@ -66,16 +66,17 @@ impl FromIterator for GitCache { for path in iter { if git.misses.contains(&path) { debug!("Skipping {:?} because it already came back Gitless", path); - } - else if git.repos.iter().any(|e| e.has_path(&path)) { + } else if git.repos.iter().any(|e| e.has_path(&path)) { debug!("Skipping {:?} because we already queried it", path); - } - else { + } else { let flags = git2::RepositoryOpenFlags::FROM_ENV; match GitRepo::discover(path, flags) { Ok(r) => { if let Some(r2) = git.repos.iter_mut().find(|e| e.has_workdir(&r.workdir)) { - debug!("Adding to existing repo (workdir matches with {:?})", r2.workdir); + debug!( + "Adding to existing repo (workdir matches with {:?})", + r2.workdir + ); r2.extra_paths.push(r.original_path); continue; } @@ -94,10 +95,8 @@ impl FromIterator for GitCache { } } - /// A **Git repository** is one we’ve discovered somewhere on the filesystem. pub struct GitRepo { - /// The queryable contents of the repository: either a `git2` repo, or the /// cached results from when we queried it last time. contents: Mutex, @@ -118,11 +117,8 @@ pub struct GitRepo { /// A repository’s queried state. enum GitContents { - /// 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 /// repository out of the `Before` variant. @@ -130,13 +126,10 @@ enum GitContents { /// The data we’ve extracted from the repository, but only after we’ve /// actually done so. - After { - statuses: Git, - }, + After { statuses: Git }, } impl GitRepo { - /// Searches through this repository for a path (to a file or directory, /// depending on the prefix-lookup flag) and returns its Git status. /// @@ -171,7 +164,8 @@ impl GitRepo { /// Whether this repository cares about the given path at all. fn has_path(&self, path: &Path) -> bool { - path.starts_with(&self.original_path) || self.extra_paths.iter().any(|e| path.starts_with(e)) + path.starts_with(&self.original_path) + || self.extra_paths.iter().any(|e| path.starts_with(e)) } /// Open a Git repository. Depending on the flags, the path is either @@ -191,16 +185,19 @@ impl GitRepo { if let Some(workdir) = repo.workdir() { let workdir = workdir.to_path_buf(); let contents = Mutex::new(GitContents::Before { repo }); - Ok(Self { contents, workdir, original_path: path, extra_paths: Vec::new() }) - } - else { + Ok(Self { + contents, + workdir, + original_path: path, + extra_paths: Vec::new(), + }) + } else { warn!("Repository has no workdir?"); Err(path) } } } - impl GitContents { /// Assumes that the repository hasn’t been queried, and extracts it /// (consuming the value) if it has. This is needed because the entire @@ -208,8 +205,7 @@ impl GitContents { fn inner_repo(self) -> git2::Repository { if let Self::Before { repo } = self { repo - } - else { + } else { unreachable!("Tried to extract a non-Repository") } } @@ -255,20 +251,21 @@ fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git { // Even inserting another logging line immediately afterwards doesn’t make it // look any faster. - /// Container of Git statuses for all the files in this folder’s Git repository. struct Git { statuses: Vec<(PathBuf, git2::Status)>, } impl Git { - /// Get either the file or directory status for the given path. /// “Prefix lookup” means that it should report an aggregate status of all /// paths starting with the given prefix (in other words, a directory). fn status(&self, index: &Path, prefix_lookup: bool) -> f::Git { - if prefix_lookup { self.dir_status(index) } - else { self.file_status(index) } + if prefix_lookup { + self.dir_status(index) + } else { + self.file_status(index) + } } /// Get the user-facing status of a file. @@ -277,11 +274,15 @@ impl Git { fn file_status(&self, file: &Path) -> f::Git { let path = reorient(file); - let s = self.statuses.iter() - .filter(|p| if p.1 == git2::Status::IGNORED { - path.starts_with(&p.0) - } else { - p.0 == path + let s = self + .statuses + .iter() + .filter(|p| { + if p.1 == git2::Status::IGNORED { + path.starts_with(&p.0) + } else { + p.0 == path + } }) .fold(git2::Status::empty(), |a, b| a | b.1); @@ -298,11 +299,15 @@ impl Git { fn dir_status(&self, dir: &Path) -> f::Git { let path = reorient(dir); - let s = self.statuses.iter() - .filter(|p| if p.1 == git2::Status::IGNORED { - path.starts_with(&p.0) - } else { - p.0.starts_with(&path) + let s = self + .statuses + .iter() + .filter(|p| { + if p.1 == git2::Status::IGNORED { + path.starts_with(&p.0) + } else { + p.0.starts_with(&path) + } }) .fold(git2::Status::empty(), |a, b| a | b.1); @@ -312,7 +317,6 @@ impl Git { } } - /// Converts a path to an absolute path based on the current directory. /// Paths need to be absolute for them to be compared properly, otherwise /// you’d ask a repo about “./README.md” but it only knows about @@ -323,8 +327,8 @@ fn reorient(path: &Path) -> PathBuf { // TODO: I’m not 100% on this func tbh let path = match current_dir() { - Err(_) => Path::new(".").join(path), - Ok(dir) => dir.join(path), + Err(_) => Path::new(".").join(path), + Ok(dir) => dir.join(path), }; path.canonicalize().unwrap_or(path) @@ -334,12 +338,17 @@ fn reorient(path: &Path) -> PathBuf { fn reorient(path: &Path) -> PathBuf { let unc_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); // On Windows UNC path is returned. We need to strip the prefix for it to work. - let normal_path = unc_path.as_os_str().to_str().unwrap().trim_start_matches("\\\\?\\"); + let normal_path = unc_path + .as_os_str() + .to_str() + .unwrap() + .trim_start_matches("\\\\?\\"); PathBuf::from(normal_path) } /// The character to display if the file has been modified, but not staged. fn working_tree_status(status: git2::Status) -> f::GitStatus { + #[rustfmt::skip] match status { s if s.contains(git2::Status::WT_NEW) => f::GitStatus::New, s if s.contains(git2::Status::WT_MODIFIED) => f::GitStatus::Modified, @@ -355,6 +364,7 @@ fn working_tree_status(status: git2::Status) -> f::GitStatus { /// The character to display if the file has been modified and the change /// has been staged. fn index_status(status: git2::Status) -> f::GitStatus { + #[rustfmt::skip] match status { s if s.contains(git2::Status::INDEX_NEW) => f::GitStatus::New, s if s.contains(git2::Status::INDEX_MODIFIED) => f::GitStatus::Modified, @@ -365,21 +375,26 @@ fn index_status(status: git2::Status) -> f::GitStatus { } } -fn current_branch(repo: &git2::Repository) -> Option{ +fn current_branch(repo: &git2::Repository) -> Option { let head = match repo.head() { Ok(head) => Some(head), - Err(ref e) if e.code() == git2::ErrorCode::UnbornBranch || e.code() == git2::ErrorCode::NotFound => return None, + Err(ref e) + if e.code() == git2::ErrorCode::UnbornBranch + || e.code() == git2::ErrorCode::NotFound => + { + return None + } Err(e) => { error!("Error looking up Git branch: {:?}", e); - return None + return None; } }; - if let Some(h) = head{ - if let Some(s) = h.shorthand(){ + if let Some(h) = head { + if let Some(s) = h.shorthand() { let branch_name = s.to_owned(); if branch_name.len() > 10 { - return Some(branch_name[..8].to_string()+".."); + return Some(branch_name[..8].to_string() + ".."); } return Some(branch_name); } diff --git a/src/fs/feature/mod.rs b/src/fs/feature/mod.rs index 9be9d7ae..f2c5aede 100644 --- a/src/fs/feature/mod.rs +++ b/src/fs/feature/mod.rs @@ -10,12 +10,12 @@ pub mod git { use crate::fs::fields as f; - pub struct GitCache; impl FromIterator for GitCache { fn from_iter(_iter: I) -> Self - where I: IntoIterator + where + I: IntoIterator, { Self } @@ -31,8 +31,8 @@ pub mod git { } } - impl f::SubdirGitRepo{ - pub fn from_path(_dir : &Path, _status : bool) -> Self{ + impl f::SubdirGitRepo { + pub fn from_path(_dir: &Path, _status: bool) -> Self { panic!("Tried to get subdir Git status, but Git support is disabled") } } diff --git a/src/fs/feature/xattr.rs b/src/fs/feature/xattr.rs index 3312ca92..622bd8dd 100644 --- a/src/fs/feature/xattr.rs +++ b/src/fs/feature/xattr.rs @@ -1,6 +1,6 @@ //! Extended attribute support for Darwin and Linux systems. -#![allow(trivial_casts)] // for ARM +#![allow(trivial_casts)] // for ARM #[cfg(any(target_os = "macos", target_os = "linux"))] use std::cmp::Ordering; @@ -9,10 +9,8 @@ use std::ffi::CString; use std::io; use std::path::Path; - pub const ENABLED: bool = cfg!(any(target_os = "macos", target_os = "linux")); - pub trait FileAttributes { fn attributes(&self) -> io::Result>; fn symlink_attributes(&self) -> io::Result>; @@ -40,7 +38,6 @@ impl FileAttributes for Path { } } - /// Attributes which can be passed to `Attribute::list_with_flags` #[cfg(any(target_os = "macos", target_os = "linux"))] #[derive(Copy, Clone)] @@ -56,15 +53,13 @@ pub struct Attribute { pub value: String, } - #[cfg(any(target_os = "macos", target_os = "linux"))] fn get_secattr(lister: &lister::Lister, c_path: &std::ffi::CString) -> io::Result> { const SELINUX_XATTR_NAME: &str = "security.selinux"; const ENODATA: i32 = 61; - let c_attr_name = CString::new(SELINUX_XATTR_NAME).map_err(|e| { - io::Error::new(io::ErrorKind::Other, e) - })?; + let c_attr_name = + CString::new(SELINUX_XATTR_NAME).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let size = lister.getxattr_first(c_path, &c_attr_name); let size = match size.cmp(&0) { @@ -72,11 +67,11 @@ fn get_secattr(lister: &lister::Lister, c_path: &std::ffi::CString) -> io::Resul let e = io::Error::last_os_error(); if e.kind() == io::ErrorKind::Other && e.raw_os_error() == Some(ENODATA) { - return Ok(Vec::new()) + return Ok(Vec::new()); } - return Err(e) - }, + return Err(e); + } Ordering::Equal => return Err(io::Error::from(io::ErrorKind::InvalidData)), Ordering::Greater => size as usize, }; @@ -91,32 +86,34 @@ fn get_secattr(lister: &lister::Lister, c_path: &std::ffi::CString) -> io::Resul } Ok(vec![Attribute { - name: String::from(SELINUX_XATTR_NAME), + name: String::from(SELINUX_XATTR_NAME), value: lister.translate_attribute_data(&buf_value), }]) } #[cfg(any(target_os = "macos", target_os = "linux"))] pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result> { - let c_path = CString::new(path.to_str().ok_or(io::Error::new(io::ErrorKind::Other, "Error: path not convertible to string"))?).map_err(|e| { - io::Error::new(io::ErrorKind::Other, e) - })?; + let c_path = CString::new(path.to_str().ok_or(io::Error::new( + io::ErrorKind::Other, + "Error: path not convertible to string", + ))?) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let bufsize = lister.listxattr_first(&c_path); let bufsize = match bufsize.cmp(&0) { - Ordering::Less => return Err(io::Error::last_os_error()), + Ordering::Less => return Err(io::Error::last_os_error()), // Some filesystems, like sysfs, return nothing on listxattr, even though the security // attribute is set. - Ordering::Equal => return get_secattr(lister, &c_path), - Ordering::Greater => bufsize as usize, + Ordering::Equal => return get_secattr(lister, &c_path), + Ordering::Greater => bufsize as usize, }; let mut buf = vec![0_u8; bufsize]; match lister.listxattr_second(&c_path, &mut buf, bufsize).cmp(&0) { - Ordering::Less => return Err(io::Error::last_os_error()), - Ordering::Equal => return Ok(Vec::new()), - Ordering::Greater => {}, + Ordering::Less => return Err(io::Error::last_os_error()), + Ordering::Equal => return Ok(Vec::new()), + Ordering::Greater => {} } let mut names = Vec::new(); @@ -126,9 +123,8 @@ pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result 0 { @@ -138,12 +134,12 @@ pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result io::Result Self { let c_flags: c_int = match do_follow { - FollowSymlinks::Yes => 0x0001, - FollowSymlinks::No => 0x0000, + FollowSymlinks::Yes => 0x0001, + FollowSymlinks::No => 0x0000, }; Self { c_flags } } pub fn translate_attribute_data(&self, input: &[u8]) -> String { - unsafe { std::str::from_utf8_unchecked(input).trim_end_matches('\0').into() } - } - - pub fn listxattr_first(&self, c_path: &CString) -> ssize_t { unsafe { - listxattr( - c_path.as_ptr(), - ptr::null_mut(), - 0, - self.c_flags, - ) + std::str::from_utf8_unchecked(input) + .trim_end_matches('\0') + .into() } } - pub fn listxattr_second(&self, c_path: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t { + pub fn listxattr_first(&self, c_path: &CString) -> ssize_t { + unsafe { listxattr(c_path.as_ptr(), ptr::null_mut(), 0, self.c_flags) } + } + + pub fn listxattr_second( + &self, + c_path: &CString, + buf: &mut [u8], + bufsize: size_t, + ) -> ssize_t { unsafe { listxattr( c_path.as_ptr(), @@ -231,7 +228,13 @@ mod lister { } } - pub fn getxattr_second(&self, c_path: &CString, c_name: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t { + pub fn getxattr_second( + &self, + c_path: &CString, + c_name: &CString, + buf: &mut [u8], + bufsize: size_t, + ) -> ssize_t { unsafe { getxattr( c_path.as_ptr(), @@ -246,26 +249,17 @@ mod lister { } } - #[cfg(target_os = "linux")] mod lister { - use std::ffi::CString; - use libc::{size_t, ssize_t, c_char, c_void}; use super::FollowSymlinks; + use libc::{c_char, c_void, size_t, ssize_t}; + use std::ffi::CString; use std::ptr; extern "C" { - fn listxattr( - path: *const c_char, - list: *mut c_char, - size: size_t, - ) -> ssize_t; + fn listxattr(path: *const c_char, list: *mut c_char, size: size_t) -> ssize_t; - fn llistxattr( - path: *const c_char, - list: *mut c_char, - size: size_t, - ) -> ssize_t; + fn llistxattr(path: *const c_char, list: *mut c_char, size: size_t) -> ssize_t; fn getxattr( path: *const c_char, @@ -297,54 +291,46 @@ mod lister { pub fn listxattr_first(&self, c_path: &CString) -> ssize_t { let listxattr = match self.follow_symlinks { - FollowSymlinks::Yes => listxattr, - FollowSymlinks::No => llistxattr, + FollowSymlinks::Yes => listxattr, + FollowSymlinks::No => llistxattr, }; - unsafe { - listxattr( - c_path.as_ptr(), - ptr::null_mut(), - 0, - ) - } + unsafe { listxattr(c_path.as_ptr(), ptr::null_mut(), 0) } } - pub fn listxattr_second(&self, c_path: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t { + pub fn listxattr_second( + &self, + c_path: &CString, + buf: &mut [u8], + bufsize: size_t, + ) -> ssize_t { let listxattr = match self.follow_symlinks { - FollowSymlinks::Yes => listxattr, - FollowSymlinks::No => llistxattr, + FollowSymlinks::Yes => listxattr, + FollowSymlinks::No => llistxattr, }; - unsafe { - listxattr( - c_path.as_ptr(), - buf.as_mut_ptr().cast(), - bufsize, - ) - } + unsafe { listxattr(c_path.as_ptr(), buf.as_mut_ptr().cast(), bufsize) } } pub fn getxattr_first(&self, c_path: &CString, c_name: &CString) -> ssize_t { let getxattr = match self.follow_symlinks { FollowSymlinks::Yes => getxattr, - FollowSymlinks::No => lgetxattr, + FollowSymlinks::No => lgetxattr, }; - unsafe { - getxattr( - c_path.as_ptr(), - c_name.as_ptr().cast(), - ptr::null_mut(), - 0, - ) - } + unsafe { getxattr(c_path.as_ptr(), c_name.as_ptr().cast(), ptr::null_mut(), 0) } } - pub fn getxattr_second(&self, c_path: &CString, c_name: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t { + pub fn getxattr_second( + &self, + c_path: &CString, + c_name: &CString, + buf: &mut [u8], + bufsize: size_t, + ) -> ssize_t { let getxattr = match self.follow_symlinks { FollowSymlinks::Yes => getxattr, - FollowSymlinks::No => lgetxattr, + FollowSymlinks::No => lgetxattr, }; unsafe { diff --git a/src/fs/fields.rs b/src/fs/fields.rs index 55e959da..f050d8f1 100644 --- a/src/fs/fields.rs +++ b/src/fs/fields.rs @@ -29,7 +29,6 @@ pub type time_t = i64; /// The type of a file’s user ID. pub type uid_t = u32; - /// The file’s base type, which gets displayed in the very first column of the /// details output. /// @@ -56,9 +55,9 @@ impl Type { } } - /// The file’s Unix permission bitfield, with one entry per bit. #[derive(Copy, Clone)] +#[rustfmt::skip] pub struct Permissions { pub user_read: bool, pub user_write: bool, @@ -79,6 +78,7 @@ pub struct Permissions { /// The file's `FileAttributes` field, available only on Windows. #[derive(Copy, Clone)] +#[rustfmt::skip] pub struct Attributes { pub archive: bool, pub directory: bool, @@ -93,15 +93,14 @@ pub struct Attributes { /// little more compressed. #[derive(Copy, Clone)] pub struct PermissionsPlus { - pub file_type: Type, + pub file_type: Type, #[cfg(unix)] pub permissions: Permissions, #[cfg(windows)] - pub attributes: Attributes, - pub xattrs: bool, + pub attributes: Attributes, + pub xattrs: bool, } - /// The permissions encoded as octal values #[derive(Copy, Clone)] pub struct OctalPermissions { @@ -116,7 +115,6 @@ pub struct OctalPermissions { /// block count specifically for this case. #[derive(Copy, Clone)] pub struct Links { - /// The actual link count. pub count: nlink_t, @@ -124,19 +122,16 @@ pub struct Links { pub multiple: bool, } - /// A file’s inode. Every directory entry on a Unix filesystem has an inode, /// including directories and links, so this is applicable to everything exa /// can deal with. #[derive(Copy, Clone)] pub struct Inode(pub ino_t); - /// A file's size of allocated file system blocks. #[derive(Copy, Clone)] #[cfg(unix)] pub enum Blocksize { - /// This file has the given number of blocks. Some(u64), @@ -144,7 +139,6 @@ pub enum Blocksize { None, } - /// The ID of the user that owns a file. This will only ever be a number; /// looking up the username is done in the `display` module. #[derive(Copy, Clone)] @@ -154,12 +148,10 @@ pub struct User(pub uid_t); #[derive(Copy, Clone)] pub struct Group(pub gid_t); - /// A file’s size, in bytes. This is usually formatted by the `number_prefix` /// crate into something human-readable. #[derive(Copy, Clone)] pub enum Size { - /// This file has a defined size. Some(u64), @@ -194,7 +186,6 @@ pub struct DeviceIDs { pub minor: u32, } - /// One of a file’s timestamps (created, accessed, or modified). #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Time { @@ -202,13 +193,11 @@ pub struct Time { pub nanoseconds: time_t, } - /// A file’s status in a Git repository. Whether a file is in a repository or /// not is handled by the Git module, rather than having a “null” variant in /// this enum. #[derive(PartialEq, Eq, Copy, Clone)] pub enum GitStatus { - /// This file hasn’t changed since the last commit. NotModified, @@ -235,18 +224,16 @@ pub enum GitStatus { Conflicted, } - /// A file’s complete Git status. It’s possible to make changes to a file, add /// it to the staging area, then make *more* changes, so we need to list each /// file’s status for both of these. #[derive(Copy, Clone)] pub struct Git { - pub staged: GitStatus, + pub staged: GitStatus, pub unstaged: GitStatus, } impl Default for Git { - /// Create a Git status for a file with nothing done to it. fn default() -> Self { Self { @@ -258,7 +245,7 @@ impl Default for Git { pub enum SecurityContextType<'a> { SELinux(&'a str), - None + None, } pub struct SecurityContext<'a> { @@ -267,7 +254,7 @@ pub struct SecurityContext<'a> { #[allow(dead_code)] #[derive(PartialEq, Copy, Clone)] -pub enum SubdirGitRepoStatus{ +pub enum SubdirGitRepoStatus { NoRepo, GitClean, GitDirty, @@ -279,7 +266,7 @@ pub struct SubdirGitRepo{ pub branch : Option } -impl Default for SubdirGitRepo{ +impl Default for SubdirGitRepo { fn default() -> Self { Self{ status: Some(SubdirGitRepoStatus::NoRepo), diff --git a/src/fs/file.rs b/src/fs/file.rs index 188ffc91..c0f6de1d 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -14,13 +14,12 @@ use log::*; use crate::fs::dir::Dir; use crate::fs::feature::xattr; -use crate::fs::feature::xattr::{FileAttributes, Attribute}; +use crate::fs::feature::xattr::{Attribute, FileAttributes}; use crate::fs::fields as f; use super::mounts::all_mounts; use super::mounts::MountedFs; - /// A **File** is a wrapper around one of Rust’s `PathBuf` values, along with /// associated data about the file. /// @@ -29,7 +28,6 @@ use super::mounts::MountedFs; /// information queried at least once, so it makes sense to do all this at the /// start and hold on to all the information. pub struct File<'dir> { - /// The filename portion of this file’s path, including the extension. /// /// This is used to compare against certain filenames (such as checking if @@ -89,48 +87,84 @@ pub struct File<'dir> { } impl<'dir> File<'dir> { - pub fn from_args(path: PathBuf, parent_dir: PD, filename: FN, deref_links: bool) -> io::Result> - where PD: Into>, - FN: Into> + pub fn from_args( + path: PathBuf, + parent_dir: PD, + filename: FN, + deref_links: bool, + ) -> io::Result> + where + PD: Into>, + FN: Into>, { let parent_dir = parent_dir.into(); - let name = filename.into().unwrap_or_else(|| File::filename(&path)); - let ext = File::ext(&path); + let name = filename.into().unwrap_or_else(|| File::filename(&path)); + let ext = File::ext(&path); debug!("Statting file {:?}", &path); - let metadata = std::fs::symlink_metadata(&path)?; + let metadata = std::fs::symlink_metadata(&path)?; let is_all_all = false; let extended_attributes = OnceLock::new(); let absolute_path = OnceLock::new(); - Ok(File { name, ext, path, metadata, parent_dir, is_all_all, deref_links, extended_attributes, absolute_path }) + Ok(File { + name, + ext, + path, + metadata, + parent_dir, + is_all_all, + deref_links, + extended_attributes, + absolute_path, + }) } pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result> { - let path = parent_dir.path.clone(); - let ext = File::ext(&path); + let path = parent_dir.path.clone(); + let ext = File::ext(&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 parent_dir = Some(parent_dir); let extended_attributes = OnceLock::new(); let absolute_path = OnceLock::new(); - Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, deref_links: false, extended_attributes, absolute_path }) + Ok(File { + path, + parent_dir, + metadata, + ext, + name: ".".into(), + is_all_all, + deref_links: false, + extended_attributes, + absolute_path, + }) } pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result> { - let ext = File::ext(&path); + let ext = File::ext(&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 parent_dir = Some(parent_dir); let extended_attributes = OnceLock::new(); let absolute_path = OnceLock::new(); - Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, deref_links: false, extended_attributes, absolute_path }) + Ok(File { + path, + parent_dir, + metadata, + ext, + name: "..".into(), + is_all_all, + deref_links: false, + extended_attributes, + absolute_path, + }) } /// A file’s name is derived from its string. This needs to handle directories @@ -139,8 +173,7 @@ impl<'dir> File<'dir> { pub fn filename(path: &Path) -> String { if let Some(back) = path.components().next_back() { back.as_os_str().to_string_lossy().to_string() - } - else { + } else { // use the path as fallback error!("Path {:?} has no last component", path); path.display().to_string() @@ -158,9 +191,7 @@ impl<'dir> File<'dir> { fn ext(path: &Path) -> Option { 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()) } /// Read the extended attributes of a file path. @@ -169,7 +200,11 @@ impl<'dir> File<'dir> { match path.symlink_attributes() { Ok(xattrs) => xattrs, Err(e) => { - error!("Error looking up extended attributes for {}: {}", path.display(), e); + error!( + "Error looking up extended attributes for {}: {}", + path.display(), + e + ); Vec::new() } } @@ -180,7 +215,8 @@ impl<'dir> File<'dir> { /// Get the extended attributes of a file path on demand. pub fn extended_attributes(&self) -> &Vec { - self.extended_attributes.get_or_init(||File::gather_extended_attributes(&self.path)) + self.extended_attributes + .get_or_init(|| File::gather_extended_attributes(&self.path)) } /// Whether this file is a directory on the filesystem. @@ -261,19 +297,23 @@ impl<'dir> File<'dir> { /// Determine the full path resolving all symbolic links on demand. pub fn absolute_path(&self) -> Option<&PathBuf> { - self.absolute_path.get_or_init(|| std::fs::canonicalize(&self.path).ok()).as_ref() + self.absolute_path + .get_or_init(|| std::fs::canonicalize(&self.path).ok()) + .as_ref() } /// Whether this file is a mount point pub fn is_mount_point(&self) -> bool { - cfg!(any(target_os = "linux", target_os = "macos")) && - self.is_directory() && - self.absolute_path().is_some_and(|p| all_mounts().contains_key(p)) + cfg!(any(target_os = "linux", target_os = "macos")) + && self.is_directory() + && self + .absolute_path() + .is_some_and(|p| all_mounts().contains_key(p)) } /// The filesystem device and type for a mount point pub fn mount_point_info(&self) -> Option<&MountedFs> { - if cfg!(any(target_os = "linux",target_os = "macos")) { + if cfg!(any(target_os = "linux", target_os = "macos")) { return self.absolute_path().and_then(|p| all_mounts().get(p)); } None @@ -285,14 +325,11 @@ impl<'dir> File<'dir> { fn reorient_target_path(&self, path: &Path) -> PathBuf { if path.is_absolute() { path.to_path_buf() - } - else if let Some(dir) = self.parent_dir { + } else if let Some(dir) = self.parent_dir { dir.join(path) - } - else if let Some(parent) = self.path.parent() { + } else if let Some(parent) = self.path.parent() { parent.join(path) - } - else { + } else { self.path.join(path) } } @@ -308,15 +345,14 @@ impl<'dir> File<'dir> { /// existed. If this file cannot be read at all, returns the error that /// we got when we tried to read it. pub fn link_target(&self) -> FileTarget<'dir> { - // We need to be careful to treat the path actually pointed to by // this file — which could be absolute or relative — to the path // we actually look up and turn into a `File` — which needs to be // absolute to be accessible from any directory. debug!("Reading link {:?}", &self.path); let path = match std::fs::read_link(&self.path) { - Ok(p) => p, - Err(e) => return FileTarget::Err(e), + Ok(p) => p, + Err(e) => return FileTarget::Err(e), }; let absolute_path = self.reorient_target_path(&path); @@ -325,7 +361,7 @@ impl<'dir> File<'dir> { // follow links. match std::fs::metadata(&absolute_path) { Ok(metadata) => { - let ext = File::ext(&path); + let ext = File::ext(&path); let name = File::filename(&path); let extended_attributes = OnceLock::new(); let absolute_path_cell = OnceLock::from(Some(absolute_path)); @@ -401,8 +437,7 @@ impl<'dir> File<'dir> { // for 512 byte blocks according to the POSIX standard // even though the physical block size may be different. f::Blocksize::Some(self.metadata.blocks() * 512) - } - else { + } else { f::Blocksize::None } } @@ -413,8 +448,8 @@ impl<'dir> File<'dir> { pub fn user(&self) -> Option { if self.is_link() && self.deref_links { match self.link_target_recurse() { - FileTarget::Ok(f) => return f.user(), - _ => return None, + FileTarget::Ok(f) => return f.user(), + _ => return None, } } Some(f::User(self.metadata.uid())) @@ -425,8 +460,8 @@ impl<'dir> File<'dir> { pub fn group(&self) -> Option { if self.is_link() && self.deref_links { match self.link_target_recurse() { - FileTarget::Ok(f) => return f.group(), - _ => return None, + FileTarget::Ok(f) => return f.group(), + _ => return None, } } Some(f::Group(self.metadata.gid())) @@ -454,8 +489,7 @@ impl<'dir> File<'dir> { } if self.is_directory() { f::Size::None - } - else if self.is_char_device() || self.is_block_device() { + } else if self.is_char_device() || self.is_block_device() { let device_id = self.metadata.rdev(); // MacOS and Linux have different arguments and return types for the @@ -470,11 +504,10 @@ impl<'dir> File<'dir> { major: unsafe { libc::major(device_id.try_into().unwrap()) } as u32, minor: unsafe { libc::minor(device_id.try_into().unwrap()) } as u32, }) - } - else if self.is_link() && self.deref_links { + } else if self.is_link() && self.deref_links { match self.link_target() { FileTarget::Ok(f) => f.size(), - _ => f::Size::None + _ => f::Size::None, } } else { f::Size::Some(self.metadata.len()) @@ -483,21 +516,20 @@ impl<'dir> File<'dir> { /// Returns the size of the file or indicates no size if it's a directory. /// - /// For Windows platforms, the size of directories is not computed and will + /// For Windows platforms, the size of directories is not computed and will /// return `Size::None`. #[cfg(windows)] pub fn size(&self) -> f::Size { if self.is_directory() { f::Size::None - } - else { + } else { f::Size::Some(self.metadata.len()) } } /// Determines if the directory is empty or not. /// - /// For Unix platforms, this function first checks the link count to quickly + /// For Unix platforms, this function first checks the link count to quickly /// determine non-empty directories. On most UNIX filesystems the link count /// is two plus the number of subdirectories. If the link count is less than /// or equal to 2, it then checks the directory contents to determine if @@ -524,8 +556,8 @@ impl<'dir> File<'dir> { /// Determines if the directory is empty or not. /// - /// For Windows platforms, this function checks the directory contents directly - /// to determine if it's empty. Since certain filesystems on Windows make it + /// For Windows platforms, this function checks the directory contents directly + /// to determine if it's empty. Since certain filesystems on Windows make it /// challenging to infer emptiness based on directory size, this approach is used. #[cfg(windows)] pub fn is_empty_dir(&self) -> bool { @@ -538,7 +570,7 @@ impl<'dir> File<'dir> { /// Checks the contents of the directory to determine if it's empty. /// - /// This function avoids counting '.' and '..' when determining if the directory is + /// This function avoids counting '.' and '..' when determining if the directory is /// empty. If any other entries are found, it returns `false`. /// /// The naive approach, as one would think that this info may have been cached. @@ -548,7 +580,10 @@ impl<'dir> File<'dir> { trace!("is_empty_directory: reading dir"); match Dir::read_dir(self.path.clone()) { // . & .. are skipped, if the returned iterator has .next(), it's not empty - Ok(has_files) => has_files.files(super::DotFilter::Dotfiles, None, false, false).next().is_none(), + Ok(has_files) => has_files + .files(super::DotFilter::Dotfiles, None, false, false) + .next() + .is_none(), Err(_) => false, } } @@ -558,10 +593,13 @@ impl<'dir> File<'dir> { if self.is_link() && self.deref_links { return match self.link_target_recurse() { FileTarget::Ok(f) => f.modified_time(), - _ => None, + _ => None, }; } - self.metadata.modified().map(|st| DateTime::::from(st).naive_utc()).ok() + self.metadata + .modified() + .map(|st| DateTime::::from(st).naive_utc()) + .ok() } /// This file’s last changed timestamp, if available on this platform. @@ -573,10 +611,7 @@ impl<'dir> File<'dir> { _ => None, }; } - NaiveDateTime::from_timestamp_opt( - self.metadata.ctime(), - self.metadata.ctime_nsec() as u32, - ) + NaiveDateTime::from_timestamp_opt(self.metadata.ctime(), self.metadata.ctime_nsec() as u32) } #[cfg(windows)] @@ -589,10 +624,13 @@ impl<'dir> File<'dir> { if self.is_link() && self.deref_links { return match self.link_target_recurse() { FileTarget::Ok(f) => f.accessed_time(), - _ => None, + _ => None, }; } - self.metadata.accessed().map(|st| DateTime::::from(st).naive_utc()).ok() + self.metadata + .accessed() + .map(|st| DateTime::::from(st).naive_utc()) + .ok() } /// This file’s created timestamp, if available on this platform. @@ -603,7 +641,10 @@ impl<'dir> File<'dir> { _ => None, }; } - self.metadata.created().map(|st| DateTime::::from(st).naive_utc()).ok() + self.metadata + .created() + .map(|st| DateTime::::from(st).naive_utc()) + .ok() } /// This file’s ‘type’. @@ -615,26 +656,19 @@ impl<'dir> File<'dir> { pub fn type_char(&self) -> f::Type { if self.is_file() { f::Type::File - } - else if self.is_directory() { + } else if self.is_directory() { f::Type::Directory - } - else if self.is_pipe() { + } else if self.is_pipe() { f::Type::Pipe - } - else if self.is_link() { + } else if self.is_link() { f::Type::Link - } - else if self.is_char_device() { + } else if self.is_char_device() { f::Type::CharDevice - } - else if self.is_block_device() { + } else if self.is_block_device() { f::Type::BlockDevice - } - else if self.is_socket() { + } else if self.is_socket() { f::Type::Socket - } - else { + } else { f::Type::Special } } @@ -643,11 +677,9 @@ impl<'dir> File<'dir> { pub fn type_char(&self) -> f::Type { if self.is_file() { f::Type::File - } - else if self.is_directory() { + } else if self.is_directory() { f::Type::Directory - } - else { + } else { f::Type::Special } } @@ -660,29 +692,29 @@ impl<'dir> File<'dir> { // return the permissions of the original link, as would have been // done if we were not dereferencing. match self.link_target_recurse() { - FileTarget::Ok(f) => return f.permissions(), - _ => return None, + FileTarget::Ok(f) => return f.permissions(), + _ => return None, } } let bits = self.metadata.mode(); let has_bit = |bit| bits & bit == bit; Some(f::Permissions { - user_read: has_bit(modes::USER_READ), - user_write: has_bit(modes::USER_WRITE), - user_execute: has_bit(modes::USER_EXECUTE), + user_read: has_bit(modes::USER_READ), + user_write: has_bit(modes::USER_WRITE), + user_execute: has_bit(modes::USER_EXECUTE), - group_read: has_bit(modes::GROUP_READ), - group_write: has_bit(modes::GROUP_WRITE), - group_execute: has_bit(modes::GROUP_EXECUTE), + group_read: has_bit(modes::GROUP_READ), + group_write: has_bit(modes::GROUP_WRITE), + group_execute: has_bit(modes::GROUP_EXECUTE), - other_read: has_bit(modes::OTHER_READ), - other_write: has_bit(modes::OTHER_WRITE), - other_execute: has_bit(modes::OTHER_EXECUTE), + other_read: has_bit(modes::OTHER_READ), + other_write: has_bit(modes::OTHER_WRITE), + other_execute: has_bit(modes::OTHER_EXECUTE), - sticky: has_bit(modes::STICKY), - setgid: has_bit(modes::SETGID), - setuid: has_bit(modes::SETUID), + sticky: has_bit(modes::STICKY), + setgid: has_bit(modes::SETGID), + setuid: has_bit(modes::SETUID), }) } @@ -693,37 +725,38 @@ impl<'dir> File<'dir> { // https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants f::Attributes { - directory: has_bit(0x10), - archive: has_bit(0x20), - readonly: has_bit(0x1), - hidden: has_bit(0x2), - system: has_bit(0x4), - reparse_point: has_bit(0x400), + directory: has_bit(0x10), + archive: has_bit(0x20), + readonly: has_bit(0x1), + hidden: has_bit(0x2), + system: has_bit(0x4), + reparse_point: has_bit(0x400), } } /// This file’s security context field. pub fn security_context(&self) -> f::SecurityContext<'_> { - let context = match self.extended_attributes().iter().find(|a| a.name == "security.selinux") { + let context = match self + .extended_attributes() + .iter() + .find(|a| a.name == "security.selinux") + { Some(attr) => f::SecurityContextType::SELinux(&attr.value), - None => f::SecurityContextType::None + None => f::SecurityContextType::None, }; f::SecurityContext { context } } } - impl<'a> AsRef> for File<'a> { fn as_ref(&self) -> &File<'a> { self } } - /// The result of following a symlink. pub enum FileTarget<'dir> { - /// The symlink pointed at a file that exists. Ok(Box>), @@ -735,14 +768,12 @@ pub enum FileTarget<'dir> { /// file isn’t a link to begin with, but also if, say, we don’t have /// permission to follow it. Err(io::Error), - // Err is its own variant, instead of having the whole thing be inside an // `io::Result`, because being unable to follow a symlink is not a serious // error — we just display the error message and move on. } impl<'dir> FileTarget<'dir> { - /// Whether this link doesn’t lead to a file, for whatever reason. This /// gets used to determine how to highlight the link in grid views. pub fn is_broken(&self) -> bool { @@ -750,7 +781,6 @@ impl<'dir> FileTarget<'dir> { } } - /// More readable aliases for the permission bits exposed by libc. #[allow(trivial_numeric_casts)] #[cfg(unix)] @@ -760,24 +790,23 @@ mod modes { // from `metadata.permissions().mode()` is always `u32`. pub type Mode = u32; - pub const USER_READ: Mode = libc::S_IRUSR as Mode; - pub const USER_WRITE: Mode = libc::S_IWUSR as Mode; - pub const USER_EXECUTE: Mode = libc::S_IXUSR 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_EXECUTE: Mode = libc::S_IXUSR as Mode; - pub const GROUP_READ: Mode = libc::S_IRGRP as Mode; - pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode; + pub const GROUP_READ: Mode = libc::S_IRGRP as Mode; + pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode; pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode; - pub const OTHER_READ: Mode = libc::S_IROTH as Mode; - pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode; + pub const OTHER_READ: Mode = libc::S_IROTH as Mode; + pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode; pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode; - pub const STICKY: Mode = libc::S_ISVTX as Mode; - pub const SETGID: Mode = libc::S_ISGID as Mode; - pub const SETUID: Mode = libc::S_ISUID as Mode; + pub const STICKY: Mode = libc::S_ISVTX as Mode; + pub const SETGID: Mode = libc::S_ISGID as Mode; + pub const SETUID: Mode = libc::S_ISUID as Mode; } - #[cfg(test)] mod ext_test { use super::File; @@ -799,7 +828,6 @@ mod ext_test { } } - #[cfg(test)] mod filename_test { use super::File; diff --git a/src/fs/filter.rs b/src/fs/filter.rs index 1071d621..b3580ff2 100644 --- a/src/fs/filter.rs +++ b/src/fs/filter.rs @@ -9,7 +9,6 @@ use crate::fs::DotFilter; use crate::fs::File; - /// Flags used to manage the **file filter** process #[derive(PartialEq, Eq, Debug, Clone)] pub enum FileFilterFlags { @@ -25,7 +24,6 @@ pub enum FileFilterFlags { OnlyFiles } - /// The **file filter** processes a list of files before displaying them to /// the user, by removing files they don’t want to see, and putting the list /// in the desired order. @@ -42,7 +40,6 @@ pub enum FileFilterFlags { /// performing the comparison. #[derive(PartialEq, Eq, Debug, Clone)] pub struct FileFilter { - /// Whether directories should be listed first, and other types of file /// second. Some users prefer it like this. pub list_dirs_first: bool, @@ -79,8 +76,8 @@ impl FileFilter { /// filter predicate for files found inside a directory. pub fn filter_child_files(&self, files: &mut Vec>) { use FileFilterFlags::{OnlyDirs, OnlyFiles}; - - files.retain(|f| ! self.ignore_patterns.is_ignored(&f.name)); + + files.retain(|f| !self.ignore_patterns.is_ignored(&f.name)); match (self.flags.contains(&OnlyDirs), self.flags.contains(&OnlyFiles)) { (true, false) => { @@ -93,7 +90,6 @@ impl FileFilter { } _ => {} } - } /// Remove every file in the given vector that does *not* pass the @@ -106,18 +102,15 @@ impl FileFilter { /// `exa -I='*.ogg' music/*` should filter out the ogg files obtained /// from the glob, even though the globbing is done by the shell! pub fn filter_argument_files(&self, files: &mut Vec>) { - 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. pub fn sort_files<'a, F>(&self, files: &mut [F]) - where F: AsRef> + where + F: AsRef>, { - 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.flags.contains(&FileFilterFlags::Reverse) { files.reverse(); @@ -127,18 +120,17 @@ impl FileFilter { // This relies on the fact that `sort_by` is *stable*: it will keep // adjacent elements next to each other. files.sort_by(|a, b| { - b.as_ref().points_to_directory() + b.as_ref() + .points_to_directory() .cmp(&a.as_ref().points_to_directory()) }); } } } - /// User-supplied field to sort by. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum SortField { - /// Don’t apply any sorting. This is usually used as an optimisation in /// scripts, where the order doesn’t matter. Unsorted, @@ -219,7 +211,6 @@ pub enum SortField { /// effects they have. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum SortCase { - /// Sort files case-sensitively with uppercase first, with ‘A’ coming /// before ‘a’. ABCabc, @@ -229,7 +220,6 @@ pub enum SortCase { } impl SortField { - /// Compares two files to determine the order they should be listed in, /// depending on the search field. /// @@ -241,6 +231,7 @@ impl SortField { pub fn compare_files(self, a: &File<'_>, b: &File<'_>) -> Ordering { use self::SortCase::{ABCabc, AaBbCc}; + #[rustfmt::skip] match self { Self::Unsorted => Ordering::Equal, @@ -285,12 +276,11 @@ impl SortField { fn strip_dot(n: &str) -> &str { match n.strip_prefix('.') { Some(s) => s, - None => n, + None => n, } } } - /// The **ignore patterns** are a list of globs that are tested against /// each filename, and if any of them match, that file isn’t displayed. /// This lets a user hide, say, text files by ignoring `*.txt`. @@ -300,9 +290,9 @@ pub struct IgnorePatterns { } impl FromIterator for IgnorePatterns { - fn from_iter(iter: I) -> Self - where I: IntoIterator + where + I: IntoIterator, { let patterns = iter.into_iter().collect(); Self { patterns } @@ -310,18 +300,19 @@ impl FromIterator for IgnorePatterns { } impl IgnorePatterns { - /// Create a new list from the input glob strings, turning the inputs that /// are valid glob patterns into an `IgnorePatterns`. The inputs that /// don’t parse correctly are returned separately. - pub fn parse_from_iter<'a, I: IntoIterator>(iter: I) -> (Self, Vec) { + pub fn parse_from_iter<'a, I: IntoIterator>( + iter: I, + ) -> (Self, Vec) { let iter = iter.into_iter(); // Almost all glob patterns are valid, so it’s worth pre-allocating // the vector with enough space for all of them. let mut patterns = match iter.size_hint() { - (_, Some(count)) => Vec::with_capacity(count), - _ => Vec::new(), + (_, Some(count)) => Vec::with_capacity(count), + _ => Vec::new(), }; // Similarly, assume there won’t be any errors. @@ -330,7 +321,7 @@ impl IgnorePatterns { for input in iter { match glob::Pattern::new(input) { Ok(pat) => patterns.push(pat), - Err(e) => errors.push(e), + Err(e) => errors.push(e), } } @@ -339,7 +330,9 @@ impl IgnorePatterns { /// Create a new empty set of patterns that matches nothing. pub fn empty() -> Self { - Self { patterns: Vec::new() } + Self { + patterns: Vec::new(), + } } /// Test whether the given file should be hidden from the results. @@ -348,11 +341,9 @@ impl IgnorePatterns { } } - /// Whether to ignore or display files that Git would ignore. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum GitIgnore { - /// Ignore files that Git would ignore. CheckAndIgnore, @@ -360,8 +351,6 @@ pub enum GitIgnore { Off, } - - #[cfg(test)] mod test_ignores { use super::*; @@ -375,7 +364,7 @@ mod test_ignores { #[test] fn ignores_a_glob() { - let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "*.mp3" ]); + let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["*.mp3"]); assert!(fails.is_empty()); assert!(!pats.is_ignored("nothing")); assert!(pats.is_ignored("test.mp3")); @@ -383,7 +372,7 @@ mod test_ignores { #[test] fn ignores_an_exact_filename() { - let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing" ]); + let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing"]); assert!(fails.is_empty()); assert!(pats.is_ignored("nothing")); assert!(!pats.is_ignored("test.mp3")); @@ -391,7 +380,7 @@ mod test_ignores { #[test] fn ignores_both() { - let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing", "*.mp3" ]); + let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing", "*.mp3"]); assert!(fails.is_empty()); assert!(pats.is_ignored("nothing")); assert!(pats.is_ignored("test.mp3")); diff --git a/src/fs/mounts/linux.rs b/src/fs/mounts/linux.rs index dbd6eb2b..be595fd0 100644 --- a/src/fs/mounts/linux.rs +++ b/src/fs/mounts/linux.rs @@ -1,16 +1,16 @@ -use proc_mounts::MountList; use crate::fs::mounts::{Error, MountedFs}; +use proc_mounts::MountList; /// Get a list of all mounted filesystems pub fn mounts() -> Result, Error> { Ok(MountList::new() - .map_err(Error::IOError)? - .0.iter() - .map(|mount| MountedFs { - dest: mount.dest.clone(), - fstype: mount.fstype.clone(), - source: mount.source.to_string_lossy().into() - }) - .collect() - ) -} \ No newline at end of file + .map_err(Error::IOError)? + .0 + .iter() + .map(|mount| MountedFs { + dest: mount.dest.clone(), + fstype: mount.fstype.clone(), + source: mount.source.to_string_lossy().into(), + }) + .collect()) +} diff --git a/src/fs/mounts/macos.rs b/src/fs/mounts/macos.rs index 590e9abd..05a44c67 100644 --- a/src/fs/mounts/macos.rs +++ b/src/fs/mounts/macos.rs @@ -1,10 +1,10 @@ -use std::{mem, ptr}; +use crate::fs::mounts::{Error, MountedFs}; +use libc::{__error, getfsstat, statfs, MNT_NOWAIT}; use std::ffi::{CStr, OsStr}; use std::os::raw::{c_char, c_int}; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; -use libc::{__error, getfsstat, MNT_NOWAIT, statfs}; -use crate::fs::mounts::{Error, MountedFs}; +use std::{mem, ptr}; /// Get a list of all mounted filesystem pub fn mounts() -> Result, Error> { @@ -36,7 +36,7 @@ pub fn mounts() -> Result, Error> { for mnt in &mntbuf { let mount_point = OsStr::from_bytes( // SAFETY: Converting null terminated "C" string - unsafe { CStr::from_ptr(mnt.f_mntonname.as_ptr().cast::()) }.to_bytes() + unsafe { CStr::from_ptr(mnt.f_mntonname.as_ptr().cast::()) }.to_bytes(), ); let dest = PathBuf::from(mount_point); // SAFETY: Converting null terminated "C" string @@ -47,8 +47,12 @@ pub fn mounts() -> Result, Error> { let source = unsafe { CStr::from_ptr(mnt.f_mntfromname.as_ptr().cast::()) } .to_string_lossy() .into(); - mounts.push(MountedFs { dest, fstype, source }); + mounts.push(MountedFs { + dest, + fstype, + source, + }); } Ok(mounts) -} \ No newline at end of file +} diff --git a/src/fs/mounts/mod.rs b/src/fs/mounts/mod.rs index 17b60b7d..d006b0fc 100644 --- a/src/fs/mounts/mod.rs +++ b/src/fs/mounts/mod.rs @@ -26,7 +26,7 @@ pub enum Error { #[cfg(target_os = "macos")] GetFSStatError(i32), #[cfg(target_os = "linux")] - IOError(std::io::Error) + IOError(std::io::Error), } impl std::error::Error for Error {} @@ -39,8 +39,8 @@ impl std::fmt::Display for Error { #[cfg(target_os = "macos")] Error::GetFSStatError(err) => write!(f, "getfsstat failed: {err}"), #[cfg(target_os = "linux")] - Error::IOError(err) => write!(f, "failed to read /proc/mounts: {err}"), - _ => write!(f, "Unknown error"), + Error::IOError(err) => write!(f, "failed to read /proc/mounts: {err}"), + _ => write!(f, "Unknown error"), } } } @@ -64,7 +64,7 @@ pub(super) fn all_mounts() -> &'static HashMap { let mut mount_map: HashMap = HashMap::new(); #[cfg(any(target_os = "linux", target_os = "macos"))] - if let Ok(mounts) = mounts() { + if let Ok(mounts) = mounts() { for mount in mounts { mount_map.insert(mount.dest.clone(), mount); } @@ -72,4 +72,4 @@ pub(super) fn all_mounts() -> &'static HashMap { mount_map }) -} \ No newline at end of file +} diff --git a/src/info/filetype.rs b/src/info/filetype.rs index bfb9f439..0f2ae2a2 100644 --- a/src/info/filetype.rs +++ b/src/info/filetype.rs @@ -22,9 +22,9 @@ pub enum FileType { Compressed, Temp, Compiled, - Build // A “build file is something that can be run or activated somehow in order to - // kick off the build of a project. It’s usually only present in directories full of - // source code. + Build, // A “build file is something that can be run or activated somehow in order to + // kick off the build of a project. It’s usually only present in directories full of + // source code. } /// Mapping from full filenames to file type. @@ -270,20 +270,24 @@ impl FileType { pub(crate) fn get_file_type(file: &File<'_>) -> Option { // Case-insensitive readme is checked first for backwards compatibility. if file.name.to_lowercase().starts_with("readme") { - return Some(Self::Build) + return Some(Self::Build); } if let Some(file_type) = FILENAME_TYPES.get(&file.name) { - return Some(file_type.clone()) + return Some(file_type.clone()); } if let Some(file_type) = file.ext.as_ref().and_then(|ext| EXTENSION_TYPES.get(ext)) { - return Some(file_type.clone()) + return Some(file_type.clone()); } if file.name.ends_with('~') || (file.name.starts_with('#') && file.name.ends_with('#')) { - return Some(Self::Temp) + return Some(Self::Temp); } if let Some(dir) = file.parent_dir { - if file.get_source_files().iter().any(|path| dir.contains(path)) { - return Some(Self::Compiled) + if file + .get_source_files() + .iter() + .any(|path| dir.contains(path)) + { + return Some(Self::Compiled); } } None diff --git a/src/info/sources.rs b/src/info/sources.rs index 33ab386b..72e036a7 100644 --- a/src/info/sources.rs +++ b/src/info/sources.rs @@ -2,9 +2,7 @@ use std::path::PathBuf; use crate::fs::File; - impl<'a> File<'a> { - /// For this file, return a vector of alternate file paths that, if any of /// them exist, mean that *this* file should be coloured as “compiled”. /// @@ -37,9 +35,8 @@ impl<'a> File<'a> { _ => vec![], // No source files if none of the above } - } - else { - vec![] // No source files if there’s no extension, either! + } else { + vec![] // No source files if there’s no extension, either! } } } diff --git a/src/logger.rs b/src/logger.rs index 4cafca62..22ae26e4 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -2,8 +2,7 @@ use std::ffi::OsStr; -use ansiterm::{Colour, ANSIString}; - +use ansiterm::{ANSIString, Colour}; /// Sets the internal logger, changing the log level based on the value of an /// environment variable. @@ -17,8 +16,7 @@ pub fn configure>(ev: Option) { if env_var == "trace" { log::set_max_level(log::LevelFilter::Trace); - } - else { + } else { log::set_max_level(log::LevelFilter::Debug); } @@ -28,7 +26,6 @@ pub fn configure>(ev: Option) { } } - #[derive(Debug)] struct Logger; @@ -36,7 +33,7 @@ const GLOBAL_LOGGER: &Logger = &Logger; impl log::Log for Logger { fn enabled(&self, _: &log::Metadata<'_>) -> bool { - true // no need to filter after using ‘set_max_level’. + true // no need to filter after using ‘set_max_level’. } fn log(&self, record: &log::Record<'_>) { @@ -44,7 +41,14 @@ impl log::Log for Logger { let level = level(record.level()); let close = Colour::Fixed(243).paint("]"); - eprintln!("{}{} {}{} {}", open, level, record.target(), close, record.args()); + eprintln!( + "{}{} {}{} {}", + open, + level, + record.target(), + close, + record.args() + ); } fn flush(&self) { @@ -53,11 +57,12 @@ impl log::Log for Logger { } fn level(level: log::Level) -> ANSIString<'static> { + #[rustfmt::skip] match level { - log::Level::Error => Colour::Red.paint("ERROR"), - log::Level::Warn => Colour::Yellow.paint("WARN"), - log::Level::Info => Colour::Cyan.paint("INFO"), - log::Level::Debug => Colour::Blue.paint("DEBUG"), - log::Level::Trace => Colour::Fixed(245).paint("TRACE"), + log::Level::Error => Colour::Red.paint("ERROR"), + log::Level::Warn => Colour::Yellow.paint("WARN"), + log::Level::Info => Colour::Cyan.paint("INFO"), + log::Level::Debug => Colour::Blue.paint("DEBUG"), + log::Level::Trace => Colour::Fixed(245).paint("TRACE"), } } diff --git a/src/main.rs b/src/main.rs index 9ad8f6e8..8481a436 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,6 @@ #![warn(rust_2018_idioms)] #![warn(trivial_casts, trivial_numeric_casts)] #![warn(unused)] - #![warn(clippy::all, clippy::pedantic)] #![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_possible_truncation)] @@ -24,7 +23,7 @@ use std::env; use std::ffi::{OsStr, OsString}; -use std::io::{self, Write, ErrorKind}; +use std::io::{self, ErrorKind, Write}; use std::path::{Component, PathBuf}; use std::process::exit; @@ -32,11 +31,11 @@ use ansiterm::{ANSIStrings, Style}; use log::*; -use crate::fs::{Dir, File}; use crate::fs::feature::git::GitCache; use crate::fs::filter::GitIgnore; -use crate::options::{Options, Vars, vars, OptionsResult}; -use crate::output::{escape, lines, grid, grid_details, details, View, Mode}; +use crate::fs::{Dir, File}; +use crate::options::{vars, Options, OptionsResult, Vars}; +use crate::output::{details, escape, grid, grid_details, lines, Mode, View}; use crate::theme::Theme; mod fs; @@ -62,20 +61,27 @@ fn main() { let args: Vec<_> = env::args_os().skip(1).collect(); match Options::parse(args.iter().map(std::convert::AsRef::as_ref), &LiveVars) { OptionsResult::Ok(options, mut input_paths) => { - // List the current directory by default. // (This has to be done here, otherwise git_options won’t see it.) if input_paths.is_empty() { - input_paths = vec![ OsStr::new(".") ]; + input_paths = vec![OsStr::new(".")]; } let git = git_options(&options, &input_paths); let writer = io::stdout(); let console_width = options.view.width.actual_terminal_width(); - let theme = options.theme.to_theme(terminal_size::terminal_size().is_some()); - let exa = Exa { options, writer, input_paths, theme, console_width, git }; - + let theme = options + .theme + .to_theme(terminal_size::terminal_size().is_some()); + let exa = Exa { + options, + writer, + input_paths, + theme, + console_width, + git, + }; info!("matching on exa.run"); match exa.run() { @@ -117,10 +123,8 @@ fn main() { } } - /// The main program wrapper. pub struct Exa<'args> { - /// List of command-line options, having been successfully parsed. pub options: Options, @@ -161,8 +165,7 @@ impl Vars for LiveVars { fn git_options(options: &Options, args: &[&OsStr]) -> Option { if options.should_scan_for_git() { Some(args.iter().map(PathBuf::from).collect()) - } - else { + } else { None } } @@ -179,25 +182,29 @@ impl<'args> Exa<'args> { let mut exit_status = 0; for file_path in &self.input_paths { - match File::from_args(PathBuf::from(file_path), None, None, self.options.view.deref_links) { + match File::from_args( + PathBuf::from(file_path), + None, + None, + self.options.view.deref_links, + ) { Err(e) => { exit_status = 2; writeln!(io::stderr(), "{file_path:?}: {e}")?; } 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() { trace!("matching on to_dir"); match f.to_dir() { - Ok(d) => dirs.push(d), + Ok(d) => dirs.push(d), Err(e) if e.kind() == ErrorKind::PermissionDenied => { warn!("Permission Denied: {e}"); exit(exits::PERMISSION_DENIED); - }, - Err(e) => writeln!(io::stderr(), "{file_path:?}: {e}")?, + } + Err(e) => writeln!(io::stderr(), "{file_path:?}: {e}")?, } - } - else { + } else { files.push(f); } } @@ -217,52 +224,75 @@ impl<'args> Exa<'args> { self.print_dirs(dirs, no_files, is_only_dir, exit_status) } - fn print_dirs(&mut self, dir_files: Vec, mut first: bool, is_only_dir: bool, exit_status: i32) -> io::Result { + fn print_dirs( + &mut self, + dir_files: Vec, + mut first: bool, + is_only_dir: bool, + exit_status: i32, + ) -> io::Result { for dir in dir_files { - // Put a gap between directories, or between the list of files and // the first directory. if first { first = false; - } - else { + } else { writeln!(&mut self.writer)?; } - if ! is_only_dir { + if !is_only_dir { 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))?; } let mut children = Vec::new(); let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore; - for file in dir.files(self.options.filter.dot_filter, self.git.as_ref(), git_ignore, self.options.view.deref_links) { + for file in dir.files( + self.options.filter.dot_filter, + self.git.as_ref(), + git_ignore, + self.options.view.deref_links, + ) { match file { - Ok(file) => children.push(file), - Err((path, e)) => writeln!(io::stderr(), "[{}: {}]", path.display(), e)?, + Ok(file) => children.push(file), + Err((path, e)) => writeln!(io::stderr(), "[{}: {}]", path.display(), e)?, } - }; + } self.options.filter.filter_child_files(&mut children); self.options.filter.sort_files(&mut children); if let Some(recurse_opts) = self.options.dir_action.recurse_options() { - let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1; - if ! recurse_opts.tree && ! recurse_opts.is_too_deep(depth) { - + let depth = dir + .path + .components() + .filter(|&c| c != Component::CurDir) + .count() + + 1; + if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) { 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() { - Ok(d) => child_dirs.push(d), - Err(e) => writeln!(io::stderr(), "{}: {}", child_dir.path.display(), e)?, + Ok(d) => child_dirs.push(d), + Err(e) => { + writeln!(io::stderr(), "{}: {}", child_dir.path.display(), e)? + } } } self.print_files(Some(&dir), children)?; match self.print_dirs(child_dirs, false, false, exit_status) { - Ok(_) => (), - Err(e) => return Err(e), + Ok(_) => (), + Err(e) => return Err(e), } continue; } @@ -281,19 +311,34 @@ impl<'args> Exa<'args> { } let theme = &self.theme; - let View { ref mode, ref file_style, .. } = self.options.view; + let View { + ref mode, + ref file_style, + .. + } = self.options.view; match (mode, self.console_width) { (Mode::Grid(ref opts), Some(console_width)) => { let filter = &self.options.filter; - let r = grid::Render { files, theme, file_style, opts, console_width, filter }; + let r = grid::Render { + files, + theme, + file_style, + opts, + console_width, + filter, + }; r.render(&mut self.writer) } - (Mode::Grid(_), None) | - (Mode::Lines, _) => { + (Mode::Grid(_), None) | (Mode::Lines, _) => { let filter = &self.options.filter; - let r = lines::Render { files, theme, file_style, filter }; + let r = lines::Render { + files, + theme, + file_style, + filter, + }; r.render(&mut self.writer) } @@ -303,7 +348,17 @@ impl<'args> Exa<'args> { let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore; let git = self.git.as_ref(); - let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git }; + let r = details::Render { + dir, + files, + theme, + file_style, + opts, + recurse, + filter, + git_ignoring, + git, + }; r.render(&mut self.writer) } @@ -316,7 +371,19 @@ impl<'args> Exa<'args> { let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore; let git = self.git.as_ref(); - let r = grid_details::Render { dir, files, theme, file_style, grid, details, filter, row_threshold, git_ignoring, git, console_width }; + let r = grid_details::Render { + dir, + files, + theme, + file_style, + grid, + details, + filter, + row_threshold, + git_ignoring, + git, + console_width, + }; r.render(&mut self.writer) } @@ -327,14 +394,23 @@ impl<'args> Exa<'args> { let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore; let git = self.git.as_ref(); - let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git }; + let r = details::Render { + dir, + files, + theme, + file_style, + opts, + recurse, + filter, + git_ignoring, + git, + }; r.render(&mut self.writer) } } } } - mod exits { /// Exit code for when exa runs OK. diff --git a/src/options/dir_action.rs b/src/options/dir_action.rs index e729b86c..9c56115b 100644 --- a/src/options/dir_action.rs +++ b/src/options/dir_action.rs @@ -1,13 +1,11 @@ //! Parsing the options for `DirAction`. use crate::options::parser::MatchedFlags; -use crate::options::{flags, OptionsError, NumberSource}; +use crate::options::{flags, NumberSource, OptionsError}; use crate::fs::dir_action::{DirAction, RecurseOptions}; - impl DirAction { - /// Determine which action to perform when trying to list a directory. /// There are three possible actions, and they overlap somewhat: the /// `--tree` flag is another form of recursion, so those two are allowed @@ -15,8 +13,9 @@ impl DirAction { pub fn deduce(matches: &MatchedFlags<'_>, can_tree: bool) -> Result { let recurse = matches.has(&flags::RECURSE)?; let as_file = matches.has(&flags::LIST_DIRS)?; - let tree = matches.has(&flags::TREE)?; + let tree = matches.has(&flags::TREE)?; + #[rustfmt::skip] if matches.is_strict() { // Early check for --level when it wouldn’t do anything if ! recurse && ! tree && matches.count(&flags::LEVEL) > 0 { @@ -24,8 +23,7 @@ impl DirAction { } else if recurse && as_file { return Err(OptionsError::Conflict(&flags::RECURSE, &flags::LIST_DIRS)); - } - else if tree && as_file { + } else if tree && as_file { return Err(OptionsError::Conflict(&flags::TREE, &flags::LIST_DIRS)); } } @@ -34,22 +32,17 @@ impl DirAction { // Tree is only appropriate in details mode, so this has to // examine the View, which should have already been deduced by now Ok(Self::Recurse(RecurseOptions::deduce(matches, true)?)) - } - else if recurse { + } else if recurse { Ok(Self::Recurse(RecurseOptions::deduce(matches, false)?)) - } - else if as_file { + } else if as_file { Ok(Self::AsFile) - } - else { + } else { Ok(Self::List) } } } - impl RecurseOptions { - /// Determine which files should be recursed into, based on the `--level` /// flag’s value, and whether the `--tree` flag was passed, which was /// determined earlier. The maximum level should be a number, and this @@ -58,22 +51,24 @@ impl RecurseOptions { if let Some(level) = matches.get(&flags::LEVEL)? { let arg_str = level.to_string_lossy(); match arg_str.parse() { - Ok(l) => { - Ok(Self { tree, max_depth: Some(l) }) - } + Ok(l) => Ok(Self { + tree, + max_depth: Some(l), + }), Err(e) => { let source = NumberSource::Arg(&flags::LEVEL); Err(OptionsError::FailedParse(arg_str.to_string(), source, e)) } } - } - else { - Ok(Self { tree, max_depth: None }) + } else { + Ok(Self { + tree, + max_depth: None, + }) } } } - #[cfg(test)] mod test { use super::*; @@ -88,15 +83,21 @@ mod test { use crate::options::test::parse_for_test; use crate::options::test::Strictnesses::*; - static TEST_ARGS: &[&Arg] = &[&flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL ]; - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, true)) { + static TEST_ARGS: &[&Arg] = &[ + &flags::RECURSE, + &flags::LIST_DIRS, + &flags::TREE, + &flags::LEVEL, + ]; + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf, true) + }) { assert_eq!(result, $result); } } }; } - // Default behaviour test!(empty: DirAction <- []; Both => Ok(DirAction::List)); @@ -125,7 +126,6 @@ mod test { test!(dirs_tree_2: DirAction <- ["--list-dirs", "--tree"]; Complain => Err(OptionsError::Conflict(&flags::TREE, &flags::LIST_DIRS))); test!(just_level_2: DirAction <- ["--level=4"]; Complain => Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE))); - // Overriding levels test!(overriding_1: DirAction <- ["-RL=6", "-L=7"]; Last => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(7) }))); test!(overriding_2: DirAction <- ["-RL=6", "-L=7"]; Complain => Err(OptionsError::Duplicate(Flag::Short(b'L'), Flag::Short(b'L')))); diff --git a/src/options/error.rs b/src/options/error.rs index 420ff3fa..7ef1a4d5 100644 --- a/src/options/error.rs +++ b/src/options/error.rs @@ -5,11 +5,9 @@ use std::num::ParseIntError; use crate::options::flags; use crate::options::parser::{Arg, Flag, ParseError}; - /// Something wrong with the combination of options the user has picked. #[derive(PartialEq, Eq, Debug)] pub enum OptionsError { - /// There was an error (from `getopts`) parsing the arguments. Parse(ParseError), @@ -46,7 +44,6 @@ pub enum OptionsError { /// The source of a string that failed to be parsed as a number. #[derive(PartialEq, Eq, Debug)] pub enum NumberSource { - /// It came... from a command-line argument! Arg(&'static Arg), @@ -73,12 +70,18 @@ impl fmt::Display for OptionsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use crate::options::parser::TakesValue; + #[rustfmt::skip] match self { Self::BadArgument(arg, attempt) => { if let TakesValue::Necessary(Some(values)) = arg.takes_value { - write!(f, "Option {} has no {:?} setting ({})", arg, attempt, Choices(values)) - } - else { + write!( + f, + "Option {} has no {:?} setting ({})", + arg, + attempt, + Choices(values) + ) + } else { write!(f, "Option {arg} has no {attempt:?} setting") } } @@ -98,7 +101,6 @@ impl fmt::Display for OptionsError { } impl OptionsError { - /// Try to second-guess what the user was trying to do, depending on what /// went wrong. pub fn suggestion(&self) -> Option<&'static str> { @@ -110,14 +112,11 @@ impl OptionsError { Self::Parse(ParseError::NeedsValue { ref flag, .. }) if *flag == Flag::Short(b't') => { Some("To sort newest files last, try \"--sort newest\", or just \"-snew\"") } - _ => { - None - } + _ => None, } } } - /// A list of legal choices for an argument-taking option. #[derive(PartialEq, Eq, Debug)] pub struct Choices(pub &'static [&'static str]); diff --git a/src/options/file_name.rs b/src/options/file_name.rs index 18071818..f4214fea 100644 --- a/src/options/file_name.rs +++ b/src/options/file_name.rs @@ -1,9 +1,8 @@ -use crate::options::{flags, OptionsError, NumberSource}; use crate::options::parser::MatchedFlags; use crate::options::vars::{self, Vars}; +use crate::options::{flags, NumberSource, OptionsError}; -use crate::output::file_name::{Options, Classify, ShowIcons, EmbedHyperlinks}; - +use crate::output::file_name::{Classify, EmbedHyperlinks, Options, ShowIcons}; impl Options { pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { @@ -11,7 +10,11 @@ impl Options { let show_icons = ShowIcons::deduce(matches, vars)?; let embed_hyperlinks = EmbedHyperlinks::deduce(matches)?; - Ok(Self { classify, show_icons, embed_hyperlinks }) + Ok(Self { + classify, + show_icons, + embed_hyperlinks, + }) } } @@ -19,8 +22,11 @@ impl Classify { fn deduce(matches: &MatchedFlags<'_>) -> Result { let flagged = matches.has(&flags::CLASSIFY)?; - if flagged { Ok(Self::AddFileIndicators) } - else { Ok(Self::JustFilenames) } + if flagged { + Ok(Self::AddFileIndicators) + } else { + Ok(Self::JustFilenames) + } } } @@ -28,19 +34,21 @@ impl ShowIcons { pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { if matches.has(&flags::NO_ICONS)? || !matches.has(&flags::ICONS)? { Ok(Self::Off) - } - else if let Some(columns) = vars.get_with_fallback(vars::EZA_ICON_SPACING, vars::EXA_ICON_SPACING).and_then(|s| s.into_string().ok()) { + } else if let Some(columns) = vars + .get_with_fallback(vars::EZA_ICON_SPACING, vars::EXA_ICON_SPACING) + .and_then(|s| s.into_string().ok()) + { match columns.parse() { - Ok(width) => { - Ok(Self::On(width)) - } + Ok(width) => Ok(Self::On(width)), Err(e) => { - let source = NumberSource::Env(vars.source(vars::EZA_ICON_SPACING, vars::EXA_ICON_SPACING).unwrap()); + let source = NumberSource::Env( + vars.source(vars::EZA_ICON_SPACING, vars::EXA_ICON_SPACING) + .unwrap(), + ); Err(OptionsError::FailedParse(columns, source, e)) } } - } - else { + } else { Ok(Self::On(1)) } } @@ -50,7 +58,10 @@ impl EmbedHyperlinks { fn deduce(matches: &MatchedFlags<'_>) -> Result { let flagged = matches.has(&flags::HYPERLINK)?; - if flagged { Ok(Self::On) } - else { Ok(Self::Off) } + if flagged { + Ok(Self::On) + } else { + Ok(Self::Off) + } } } diff --git a/src/options/filter.rs b/src/options/filter.rs index 44444d41..2706ad13 100644 --- a/src/options/filter.rs +++ b/src/options/filter.rs @@ -1,29 +1,28 @@ //! Parsing the options for `FileFilter`. +use crate::fs::filter::{FileFilter, FileFilterFlags, GitIgnore, IgnorePatterns, SortCase, SortField}; use crate::fs::DotFilter; -use crate::fs::filter::{FileFilter,FileFilterFlags, SortField, SortCase, IgnorePatterns, GitIgnore}; -use crate::options::{flags, OptionsError}; use crate::options::parser::MatchedFlags; - +use crate::options::{flags, OptionsError}; impl FileFilter { - /// Determines which of all the file filter options to use. pub fn deduce(matches: &MatchedFlags<'_>) -> Result { use FileFilterFlags as FFF; let mut filter_flags:Vec = vec![]; - + for (has,flag) in &[ (matches.has(&flags::REVERSE)?, FFF::Reverse), (matches.has(&flags::ONLY_DIRS)?, FFF::OnlyDirs), (matches.has(&flags::ONLY_FILES)?, FFF::OnlyFiles), ] { - if *has { + if *has { filter_flags.push(flag.clone()); } } + #[rustfmt::skip] Ok(Self { list_dirs_first: matches.has(&flags::DIRS_FIRST)?, flags: filter_flags, @@ -36,7 +35,6 @@ impl FileFilter { } impl SortField { - /// Determines which sort field to use based on the `--sort` argument. /// This argument’s value can be one of several flags, listed above. /// Returns the default sort field if none is given, or `Err` if the @@ -48,62 +46,32 @@ impl SortField { let Some(word) = word.to_str() else { return Err(OptionsError::BadArgument(&flags::SORT, word.into())) }; let field = match word { - "name" | "filename" => { - Self::Name(SortCase::AaBbCc) - } - "Name" | "Filename" => { - Self::Name(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) - } + "name" | "filename" => Self::Name(SortCase::AaBbCc), + "Name" | "Filename" => Self::Name(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” // 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 // the top and the largest at the bottom, doesn’t 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 // newest files) get sorted at the top, and files with the most // age (the oldest) at the bottom. - "age" | "old" | "oldest" => { - Self::ModifiedAge - } + "age" | "old" | "oldest" => Self::ModifiedAge, - "ch" | "changed" => { - Self::ChangedDate - } - "acc" | "accessed" => { - Self::AccessedDate - } - "cr" | "created" => { - Self::CreatedDate - } + "ch" | "changed" => Self::ChangedDate, + "acc" | "accessed" => Self::AccessedDate, + "cr" | "created" => Self::CreatedDate, #[cfg(unix)] - "inode" => { - Self::FileInode - } - "type" => { - Self::FileType - } - "none" => { - Self::Unsorted - } + "inode" => Self::FileInode, + "type" => Self::FileType, + "none" => Self::Unsorted, _ => { return Err(OptionsError::BadArgument(&flags::SORT, word.into())); } @@ -113,7 +81,6 @@ impl SortField { } } - // I’ve gone back and forth between whether to sort case-sensitively or // insensitively by default. The default string sort in most programming // languages takes each character’s ASCII value into account, sorting @@ -151,9 +118,7 @@ impl Default for SortField { } } - impl DotFilter { - /// Determines the dot filter based on how many `--all` options were /// given: one will show dotfiles, but two will show `.` and `..` too. /// --almost-all is equivalent to --all, included for compatibility with @@ -175,25 +140,24 @@ impl DotFilter { // either a single --all or at least one --almost-all is given (1, _) | (0, true) => Ok(Self::Dotfiles), // more than one --all - (c, _) => if matches.count(&flags::TREE) > 0 { - Err(OptionsError::TreeAllAll) - } else if matches.is_strict() && c > 2 { - Err(OptionsError::Conflict(&flags::ALL, &flags::ALL)) - } else { - Ok(Self::DotfilesAndDots) - }, + (c, _) => { + if matches.count(&flags::TREE) > 0 { + Err(OptionsError::TreeAllAll) + } else if matches.is_strict() && c > 2 { + Err(OptionsError::Conflict(&flags::ALL, &flags::ALL)) + } else { + Ok(Self::DotfilesAndDots) + } + } } } } - impl IgnorePatterns { - /// Determines the set of glob patterns to use based on the /// `--ignore-glob` argument’s value. This is a list of strings /// separated by pipe (`|`) characters, given in any order. pub fn deduce(matches: &MatchedFlags<'_>) -> Result { - // If there are no inputs, we return a set of patterns that doesn’t // match anything, rather than, say, `None`. let Some(inputs) = matches.get(&flags::IGNORE_GLOB)? else { return Ok(Self::empty()) }; @@ -205,31 +169,28 @@ impl IgnorePatterns { // It can actually return more than one glob error, // but we only use one. (TODO) match errors.pop() { - Some(e) => Err(e.into()), - None => Ok(patterns), + Some(e) => Err(e.into()), + None => Ok(patterns), } } } - impl GitIgnore { pub fn deduce(matches: &MatchedFlags<'_>) -> Result { if matches.has(&flags::GIT_IGNORE)? { Ok(Self::CheckAndIgnore) - } - else { + } else { Ok(Self::Off) } } } - #[cfg(test)] mod test { use super::*; - use std::ffi::OsString; use crate::options::flags; use crate::options::parser::Flag; + use std::ffi::OsString; macro_rules! test { ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => { @@ -239,8 +200,17 @@ mod test { use crate::options::test::parse_for_test; use crate::options::test::Strictnesses::*; - static TEST_ARGS: &[&Arg] = &[ &flags::SORT, &flags::ALL, &flags::ALMOST_ALL, &flags::TREE, &flags::IGNORE_GLOB, &flags::GIT_IGNORE ]; - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) { + static TEST_ARGS: &[&Arg] = &[ + &flags::SORT, + &flags::ALL, + &flags::ALMOST_ALL, + &flags::TREE, + &flags::IGNORE_GLOB, + &flags::GIT_IGNORE, + ]; + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf) + }) { assert_eq!(result, $result); } } @@ -278,7 +248,6 @@ mod test { test!(overridden_4: SortField <- ["--sort", "none", "--sort=Extension"]; Complain => Err(OptionsError::Duplicate(Flag::Long("sort"), Flag::Long("sort")))); } - mod dot_filters { use super::*; @@ -304,7 +273,6 @@ mod test { test!(almost_all_all_2: DotFilter <- ["-Aaa"]; Both => Ok(DotFilter::DotfilesAndDots)); } - mod ignore_patterns { use super::*; use std::iter::FromIterator; @@ -326,7 +294,6 @@ mod test { test!(overridden_4: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Complain => Err(OptionsError::Duplicate(Flag::Short(b'I'), Flag::Short(b'I')))); } - mod git_ignores { use super::*; diff --git a/src/options/flags.rs b/src/options/flags.rs index eef97493..9fb854c7 100644 --- a/src/options/flags.rs +++ b/src/options/flags.rs @@ -1,6 +1,6 @@ +#![rustfmt::skip] // the entire file becomes less readable with rustfmt use crate::options::parser::{Arg, Args, TakesValue, Values}; - // exa options pub static VERSION: Arg = Arg { short: Some(b'v'), long: "version", takes_value: TakesValue::Forbidden }; pub static HELP: Arg = Arg { short: Some(b'?'), long: "help", takes_value: TakesValue::Forbidden }; diff --git a/src/options/help.rs b/src/options/help.rs index 05897e09..a511db06 100644 --- a/src/options/help.rs +++ b/src/options/help.rs @@ -4,7 +4,6 @@ use crate::fs::feature::xattr; use crate::options::flags; use crate::options::parser::MatchedFlags; - static USAGE_PART1: &str = "Usage: eza [options] [files...] @@ -72,9 +71,9 @@ static GIT_VIEW_HELP: &str = " \ --git list each file's Git status, if tracked or ignored --no-git suppress Git status (always overrides --git, --git-repos, --git-repos-no-status) --git-repos list root of git-tree status"; -static EXTENDED_HELP: &str = " \ +static EXTENDED_HELP: &str = " \ -@, --extended list each file's extended attributes and sizes"; -static SECATTR_HELP: &str = " \ +static SECATTR_HELP: &str = " \ -Z, --context list each file's security context"; /// All the information needed to display the help text, which depends @@ -84,7 +83,6 @@ static SECATTR_HELP: &str = " \ pub struct HelpString; impl HelpString { - /// Determines how to show help, if at all, based on the user’s /// command-line arguments. This one works backwards from the other /// ‘deduce’ functions, returning Err if help needs to be shown. @@ -95,15 +93,13 @@ impl HelpString { pub fn deduce(matches: &MatchedFlags<'_>) -> Option { if matches.count(&flags::HELP) > 0 { Some(Self) - } - else { + } else { None } } } impl fmt::Display for HelpString { - /// Format this help options into an actual string of help /// text to be displayed to the user. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -128,7 +124,6 @@ impl fmt::Display for HelpString { } } - #[cfg(test)] mod test { use crate::options::{Options, OptionsResult}; @@ -136,14 +131,14 @@ mod test { #[test] fn help() { - let args = vec![ OsStr::new("--help") ]; + let args = vec![OsStr::new("--help")]; let opts = Options::parse(args, &None); assert!(matches!(opts, OptionsResult::Help(_))); } #[test] fn help_with_file() { - let args = vec![ OsStr::new("--help"), OsStr::new("me") ]; + let args = vec![OsStr::new("--help"), OsStr::new("me")]; let opts = Options::parse(args, &None); assert!(matches!(opts, OptionsResult::Help(_))); } @@ -152,6 +147,6 @@ mod test { fn unhelpful() { let args = vec![]; let opts = Options::parse(args, &None); - assert!(! matches!(opts, OptionsResult::Help(_))) // no help when --help isn’t passed + assert!(!matches!(opts, OptionsResult::Help(_))) // no help when --help isn’t passed } } diff --git a/src/options/mod.rs b/src/options/mod.rs index 44a0fbca..23d7a1ab 100644 --- a/src/options/mod.rs +++ b/src/options/mod.rs @@ -68,12 +68,11 @@ //! --grid --long` shouldn’t complain about `--long` being given twice when //! it’s clear what the user wants. - use std::ffi::OsStr; use crate::fs::dir_action::DirAction; use crate::fs::filter::{FileFilter, GitIgnore}; -use crate::output::{View, Mode, details, grid_details}; +use crate::output::{details, grid_details, Mode, View}; use crate::theme::Options as ThemeOptions; mod dir_action; @@ -84,7 +83,7 @@ mod theme; mod view; mod error; -pub use self::error::{OptionsError, NumberSource}; +pub use self::error::{NumberSource, OptionsError}; mod help; use self::help::HelpString; @@ -98,12 +97,10 @@ pub use self::vars::Vars; mod version; use self::version::VersionString; - /// These **options** represent a parsed, error-checked versions of the /// user’s command-line options. #[derive(Debug)] pub struct Options { - /// The action to perform when encountering a directory rather than a /// regular file. pub dir_action: DirAction, @@ -122,17 +119,18 @@ pub struct Options { } impl Options { - /// Parse the given iterator of command-line strings into an Options /// struct and a list of free filenames, using the environment variables /// for extra options. #[allow(unused_results)] pub fn parse<'args, I, V>(args: I, vars: &V) -> OptionsResult<'args> - where I: IntoIterator, - V: Vars, + where + I: IntoIterator, + V: Vars, { use crate::options::parser::{Matches, Strictness}; + #[rustfmt::skip] let strictness = match vars.get_with_fallback(vars::EZA_STRICT, vars::EXA_STRICT) { None => Strictness::UseLastArguments, Some(ref t) if t.is_empty() => Strictness::UseLastArguments, @@ -140,8 +138,8 @@ impl Options { }; let Matches { flags, frees } = match flags::ALL_ARGS.parse(args, strictness) { - Ok(m) => m, - Err(pe) => return OptionsResult::InvalidOptions(OptionsError::Parse(pe)), + Ok(m) => m, + Err(pe) => return OptionsResult::InvalidOptions(OptionsError::Parse(pe)), }; if let Some(help) = HelpString::deduce(&flags) { @@ -153,8 +151,8 @@ impl Options { } match Self::deduce(&flags, vars) { - Ok(options) => OptionsResult::Ok(options, frees), - Err(oe) => OptionsResult::InvalidOptions(oe), + Ok(options) => OptionsResult::Ok(options, frees), + Err(oe) => OptionsResult::InvalidOptions(oe), } } @@ -167,8 +165,18 @@ impl Options { } match self.view.mode { - Mode::Details(details::Options { table: Some(ref table), .. }) | - Mode::GridDetails(grid_details::Options { details: details::Options { table: Some(ref table), .. }, .. }) => table.columns.git, + Mode::Details(details::Options { + table: Some(ref table), + .. + }) + | Mode::GridDetails(grid_details::Options { + details: + details::Options { + table: Some(ref table), + .. + }, + .. + }) => table.columns.git, _ => false, } } @@ -176,8 +184,11 @@ impl Options { /// Determines the complete set of options based on the given command-line /// arguments, after they’ve been parsed. fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { - if cfg!(not(feature = "git")) && - matches.has_where_any(|f| f.matches(&flags::GIT) || f.matches(&flags::GIT_IGNORE)).is_some() { + if cfg!(not(feature = "git")) + && matches + .has_where_any(|f| f.matches(&flags::GIT) || f.matches(&flags::GIT_IGNORE)) + .is_some() + { return Err(OptionsError::Unsupported(String::from( "Options --git and --git-ignore can't be used because `git` feature was disabled in this build of exa" ))); @@ -188,15 +199,18 @@ impl Options { let filter = FileFilter::deduce(matches)?; let theme = ThemeOptions::deduce(matches, vars)?; - Ok(Self { dir_action, filter, view, theme }) + Ok(Self { + dir_action, + filter, + view, + theme, + }) } } - /// The result of the `Options::getopts` function. #[derive(Debug)] pub enum OptionsResult<'args> { - /// The options were parsed successfully. Ok(Options, Vec<&'args OsStr>), @@ -210,7 +224,6 @@ pub enum OptionsResult<'args> { Version(VersionString), } - #[cfg(test)] pub mod test { use crate::options::parser::{Arg, MatchedFlags}; @@ -229,8 +242,14 @@ pub mod test { /// /// It returns a vector with one or two elements in. /// These elements can then be tested with `assert_eq` or what have you. - pub fn parse_for_test(inputs: &[&str], args: &'static [&'static Arg], strictnesses: Strictnesses, get: F) -> Vec - where F: Fn(&MatchedFlags<'_>) -> T + pub fn parse_for_test( + inputs: &[&str], + args: &'static [&'static Arg], + strictnesses: Strictnesses, + get: F, + ) -> Vec + where + F: Fn(&MatchedFlags<'_>) -> T, { use self::Strictnesses::*; use crate::options::parser::{Args, Strictness}; diff --git a/src/options/parser.rs b/src/options/parser.rs index e7722014..1a52f58d 100644 --- a/src/options/parser.rs +++ b/src/options/parser.rs @@ -27,12 +27,10 @@ //! command-line options, as all the options and their values (such as //! `--sort size`) are guaranteed to just be 8-bit ASCII. - use std::ffi::{OsStr, OsString}; use std::fmt; -use crate::options::error::{OptionsError, Choices}; - +use crate::options::error::{Choices, OptionsError}; /// A **short argument** is a single ASCII character. pub type ShortArg = u8; @@ -61,8 +59,8 @@ pub enum Flag { impl Flag { pub fn matches(&self, arg: &Arg) -> bool { match self { - Self::Short(short) => arg.short == Some(*short), - Self::Long(long) => arg.long == *long, + Self::Short(short) => arg.short == Some(*short), + Self::Long(long) => arg.long == *long, } } } @@ -70,8 +68,8 @@ impl Flag { impl fmt::Display for Flag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { - Self::Short(short) => write!(f, "-{}", *short as char), - Self::Long(long) => write!(f, "--{long}"), + Self::Short(short) => write!(f, "-{}", *short as char), + Self::Long(long) => write!(f, "--{long}"), } } } @@ -79,7 +77,6 @@ impl fmt::Display for Flag { /// Whether redundant arguments should be considered a problem. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum Strictness { - /// Throw an error when an argument doesn’t do anything, either because /// it requires another argument to be specified, or because two conflict. ComplainAboutRedundantArguments, @@ -93,7 +90,6 @@ pub enum Strictness { /// arguments. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum TakesValue { - /// This flag has to be followed by a value. /// If there’s a fixed set of possible values, they can be printed out /// with the error text. @@ -106,11 +102,9 @@ pub enum TakesValue { Optional(Option), } - /// An **argument** can be matched by one of the user’s input strings. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct Arg { - /// The short argument that matches it, if any. pub short: Option, @@ -134,17 +128,20 @@ impl fmt::Display for Arg { } } - /// Literally just several args. #[derive(PartialEq, Eq, Debug)] pub struct Args(pub &'static [&'static Arg]); impl Args { - /// Iterates over the given list of command-line arguments and parses /// them into a list of matched flags and free strings. - pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result, ParseError> - where I: IntoIterator + pub fn parse<'args, I>( + &self, + inputs: I, + strictness: Strictness, + ) -> Result, ParseError> + where + I: IntoIterator, { let mut parsing = true; @@ -163,13 +160,11 @@ impl Args { // This allows a file named “--arg” to be specified by passing in // the pair “-- --arg”, without it getting matched as a flag that // doesn’t exist. - if ! parsing { + if !parsing { frees.push(arg); - } - else if arg == "--" { + } else if arg == "--" { parsing = false; } - // If the string starts with *two* dashes then it’s a long argument. else if bytes.starts_with(b"--") { let long_arg_name = bytes_to_os_str(&bytes[2..]); @@ -181,12 +176,12 @@ impl Args { let arg = self.lookup_long(before)?; let flag = Flag::Long(arg.long); match arg.takes_value { - TakesValue::Necessary(_) | - TakesValue::Optional(_) => result_flags.push((flag, Some(after))), - TakesValue::Forbidden => return Err(ParseError::ForbiddenValue { flag }), + TakesValue::Necessary(_) | TakesValue::Optional(_) => { + result_flags.push((flag, Some(after))) + } + TakesValue::Forbidden => return Err(ParseError::ForbiddenValue { flag }), } } - // If there’s no equals, then the entire string (apart from // the dashes) is the argument name. else { @@ -199,23 +194,20 @@ impl Args { TakesValue::Necessary(values) => { if let Some(next_arg) = inputs.next() { result_flags.push((flag, Some(next_arg))); - } - else { - return Err(ParseError::NeedsValue { flag, values }) + } else { + return Err(ParseError::NeedsValue { flag, values }); } } TakesValue::Optional(_) => { if let Some(next_arg) = inputs.next() { result_flags.push((flag, Some(next_arg))); - } - else { + } else { result_flags.push((flag, None)); } } } } } - // If the string starts with *one* dash then it’s one or more // short arguments. else if bytes.starts_with(b"-") && arg != "-" { @@ -234,15 +226,15 @@ impl Args { // it’s an error if any of the first set of arguments actually // takes a value. if let Some((before, after)) = split_on_equals(short_arg) { - let (arg_with_value, other_args) = os_str_to_bytes(before).split_last().unwrap(); + let (arg_with_value, other_args) = + os_str_to_bytes(before).split_last().unwrap(); // Process the characters immediately following the dash... for byte in other_args { let arg = self.lookup_short(*byte)?; let flag = Flag::Short(*byte); match arg.takes_value { - TakesValue::Forbidden | - TakesValue::Optional(_) => { + TakesValue::Forbidden | TakesValue::Optional(_) => { result_flags.push((flag, None)); } TakesValue::Necessary(values) => { @@ -255,8 +247,7 @@ impl Args { let arg = self.lookup_short(*arg_with_value)?; let flag = Flag::Short(arg.short.unwrap()); match arg.takes_value { - TakesValue::Necessary(_) | - TakesValue::Optional(_) => { + TakesValue::Necessary(_) | TakesValue::Optional(_) => { result_flags.push((flag, Some(after))); } TakesValue::Forbidden => { @@ -264,7 +255,6 @@ impl Args { } } } - // If there’s no equals, then every character is parsed as // its own short argument. However, if any of the arguments // takes a value, then the *rest* of the string is used as @@ -285,17 +275,14 @@ impl Args { TakesValue::Forbidden => { result_flags.push((flag, None)); } - TakesValue::Necessary(values) | - TakesValue::Optional(values) => { + TakesValue::Necessary(values) | TakesValue::Optional(values) => { if index < bytes.len() - 1 { - let remnants = &bytes[index+1 ..]; + let remnants = &bytes[index + 1..]; result_flags.push((flag, Some(bytes_to_os_str(remnants)))); break; - } - else if let Some(next_arg) = inputs.next() { + } else if let Some(next_arg) = inputs.next() { result_flags.push((flag, Some(next_arg))); - } - else { + } else { match arg.takes_value { TakesValue::Forbidden => { unreachable!() @@ -313,36 +300,41 @@ impl Args { } } } - // Otherwise, it’s a free string, usually a file name. else { frees.push(arg); } } - Ok(Matches { frees, flags: MatchedFlags { flags: result_flags, strictness } }) + Ok(Matches { + frees, + flags: MatchedFlags { + flags: result_flags, + strictness, + }, + }) } fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> { match self.0.iter().find(|arg| arg.short == Some(short)) { - Some(arg) => Ok(arg), - None => Err(ParseError::UnknownShortArgument { attempt: short }) + Some(arg) => Ok(arg), + None => Err(ParseError::UnknownShortArgument { attempt: short }), } } fn lookup_long(&self, long: &OsStr) -> Result<&Arg, ParseError> { match self.0.iter().find(|arg| arg.long == long) { - Some(arg) => Ok(arg), - None => Err(ParseError::UnknownArgument { attempt: long.to_os_string() }) + Some(arg) => Ok(arg), + None => Err(ParseError::UnknownArgument { + attempt: long.to_os_string(), + }), } } } - /// The **matches** are the result of parsing the user’s command-line strings. #[derive(PartialEq, Eq, Debug)] pub struct Matches<'args> { - /// The flags that were parsed from the user’s input. pub flags: MatchedFlags<'args>, @@ -353,7 +345,6 @@ pub struct Matches<'args> { #[derive(PartialEq, Eq, Debug)] pub struct MatchedFlags<'args> { - /// The individual flags from the user’s input, in the order they were /// originally given. /// @@ -367,7 +358,6 @@ pub struct MatchedFlags<'args> { } impl<'a> MatchedFlags<'a> { - /// Whether the given argument was specified. /// Returns `true` if it was, `false` if it wasn’t, and an error in /// strict mode if it was specified more than once. @@ -382,16 +372,22 @@ impl<'a> MatchedFlags<'a> { /// /// You’ll have to test the resulting flag to see which argument it was. pub fn has_where

(&self, predicate: P) -> Result, OptionsError> - where P: Fn(&Flag) -> bool { + where + P: Fn(&Flag) -> bool, + { if self.is_strict() { - let all = self.flags.iter() - .filter(|tuple| tuple.1.is_none() && predicate(&tuple.0)) - .collect::>(); + let all = self + .flags + .iter() + .filter(|tuple| tuple.1.is_none() && predicate(&tuple.0)) + .collect::>(); - if all.len() < 2 { Ok(all.first().map(|t| &t.0)) } - else { Err(OptionsError::Duplicate(all[0].0, all[1].0)) } - } - else { + if all.len() < 2 { + Ok(all.first().map(|t| &t.0)) + } else { + Err(OptionsError::Duplicate(all[0].0, all[1].0)) + } + } else { Ok(self.has_where_any(predicate)) } } @@ -401,8 +397,12 @@ impl<'a> MatchedFlags<'a> { /// /// You’ll have to test the resulting flag to see which argument it was. pub fn has_where_any

(&self, predicate: P) -> Option<&Flag> - where P: Fn(&Flag) -> bool { - self.flags.iter().rev() + where + P: Fn(&Flag) -> bool, + { + self.flags + .iter() + .rev() .find(|tuple| tuple.1.is_none() && predicate(&tuple.0)) .map(|tuple| &tuple.0) } @@ -424,19 +424,28 @@ impl<'a> MatchedFlags<'a> { /// /// It’s not possible to tell which flag the value belonged to from this. pub fn get_where

(&self, predicate: P) -> Result, OptionsError> - where P: Fn(&Flag) -> bool { + where + P: Fn(&Flag) -> bool, + { if self.is_strict() { - let those = self.flags.iter() - .filter(|tuple| tuple.1.is_some() && predicate(&tuple.0)) - .collect::>(); + let those = self + .flags + .iter() + .filter(|tuple| tuple.1.is_some() && predicate(&tuple.0)) + .collect::>(); - if those.len() < 2 { Ok(those.first().copied().map(|t| t.1.unwrap())) } - else { Err(OptionsError::Duplicate(those[0].0, those[1].0)) } - } - else { - let found = self.flags.iter().rev() - .find(|tuple| tuple.1.is_some() && predicate(&tuple.0)) - .map(|tuple| tuple.1.unwrap()); + if those.len() < 2 { + Ok(those.first().copied().map(|t| t.1.unwrap())) + } else { + Err(OptionsError::Duplicate(those[0].0, those[1].0)) + } + } else { + let found = self + .flags + .iter() + .rev() + .find(|tuple| tuple.1.is_some() && predicate(&tuple.0)) + .map(|tuple| tuple.1.unwrap()); Ok(found) } } @@ -447,7 +456,8 @@ impl<'a> MatchedFlags<'a> { /// Counts the number of occurrences of the given argument, even in /// strict mode. pub fn count(&self, arg: &Arg) -> usize { - self.flags.iter() + self.flags + .iter() .filter(|tuple| tuple.0.matches(arg)) .count() } @@ -459,12 +469,10 @@ impl<'a> MatchedFlags<'a> { } } - /// A problem with the user’s input that meant it couldn’t be parsed into a /// coherent list of arguments. #[derive(PartialEq, Eq, Debug)] pub enum ParseError { - /// A flag that has to take a value was not given one. NeedsValue { flag: Flag, values: Option }, @@ -484,36 +492,43 @@ pub enum ParseError { impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::NeedsValue { flag, values: None } => write!(f, "Flag {flag} needs a value"), - Self::NeedsValue { flag, values: Some(cs) } => write!(f, "Flag {flag} needs a value ({})", Choices(cs)), - Self::ForbiddenValue { flag } => write!(f, "Flag {flag} cannot take a value"), - Self::UnknownShortArgument { attempt } => write!(f, "Unknown argument -{}", *attempt as char), - Self::UnknownArgument { attempt } => write!(f, "Unknown argument --{}", attempt.to_string_lossy()), + Self::NeedsValue { flag, values: None } => write!(f, "Flag {flag} needs a value"), + Self::NeedsValue { + flag, + values: Some(cs), + } => write!(f, "Flag {flag} needs a value ({})", Choices(cs)), + Self::ForbiddenValue { flag } => write!(f, "Flag {flag} cannot take a value"), + Self::UnknownShortArgument { attempt } => { + write!(f, "Unknown argument -{}", *attempt as char) + } + Self::UnknownArgument { attempt } => { + write!(f, "Unknown argument --{}", attempt.to_string_lossy()) + } } } } #[cfg(unix)] -fn os_str_to_bytes(s: &OsStr) -> &[u8]{ +fn os_str_to_bytes(s: &OsStr) -> &[u8] { use std::os::unix::ffi::OsStrExt; - return s.as_bytes() + return s.as_bytes(); } #[cfg(unix)] -fn bytes_to_os_str(b: &[u8]) -> &OsStr{ +fn bytes_to_os_str(b: &[u8]) -> &OsStr { use std::os::unix::ffi::OsStrExt; return OsStr::from_bytes(b); } #[cfg(windows)] -fn os_str_to_bytes(s: &OsStr) -> &[u8]{ - return s.to_str().unwrap().as_bytes() +fn os_str_to_bytes(s: &OsStr) -> &[u8] { + return s.to_str().unwrap().as_bytes(); } #[cfg(windows)] -fn bytes_to_os_str(b: &[u8]) -> &OsStr{ +fn bytes_to_os_str(b: &[u8]) -> &OsStr { use std::str; return OsStr::new(str::from_utf8(b).unwrap()); @@ -526,16 +541,14 @@ fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> { let (before, after) = os_str_to_bytes(input).split_at(index); // The after string contains the = that we need to remove. - if ! before.is_empty() && after.len() >= 2 { - return Some((bytes_to_os_str(before), - bytes_to_os_str(&after[1..]))) + if !before.is_empty() && after.len() >= 2 { + return Some((bytes_to_os_str(before), bytes_to_os_str(&after[1..]))); } } None } - #[cfg(test)] mod split_test { use super::split_on_equals; @@ -545,16 +558,17 @@ mod split_test { ($name:ident: $input:expr => None) => { #[test] fn $name() { - assert_eq!(split_on_equals(&OsString::from($input)), - None); + assert_eq!(split_on_equals(&OsString::from($input)), None); } }; ($name:ident: $input:expr => $before:expr, $after:expr) => { #[test] fn $name() { - assert_eq!(split_on_equals(&OsString::from($input)), - Some((OsStr::new($before), OsStr::new($after)))); + assert_eq!( + split_on_equals(&OsString::from($input)), + Some((OsStr::new($before), OsStr::new($after))) + ); } }; } @@ -571,7 +585,6 @@ mod split_test { test_split!(more: "this=that=other" => "this", "that=other"); } - #[cfg(test)] mod parse_test { use super::*; @@ -580,16 +593,15 @@ mod parse_test { ($name:ident: $inputs:expr => frees: $frees:expr, flags: $flags:expr) => { #[test] fn $name() { - let inputs: &[&'static str] = $inputs.as_ref(); let inputs = inputs.iter().map(OsStr::new); let frees: &[&'static str] = $frees.as_ref(); - let frees = frees.iter().map(OsStr::new).collect(); + let frees = frees.iter().map(OsStr::new).collect(); let flags = <[_]>::into_vec(Box::new($flags)); - let strictness = Strictness::UseLastArguments; // this isn’t even used + let strictness = Strictness::UseLastArguments; // this isn’t even used let got = Args(TEST_ARGS).parse(inputs, strictness); let flags = MatchedFlags { flags, strictness }; @@ -605,15 +617,16 @@ mod parse_test { let inputs = $inputs.iter().map(OsStr::new); - let strictness = Strictness::UseLastArguments; // this isn’t even used + let strictness = Strictness::UseLastArguments; // this isn’t even used let got = Args(TEST_ARGS).parse(inputs, strictness); assert_eq!(got, Err($error)); } }; } - const SUGGESTIONS: Values = &[ "example" ]; + const SUGGESTIONS: Values = &["example"]; + #[rustfmt::skip] static TEST_ARGS: &[&Arg] = &[ &Arg { short: Some(b'l'), long: "long", takes_value: TakesValue::Forbidden }, &Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden }, @@ -621,7 +634,6 @@ mod parse_test { &Arg { short: Some(b't'), long: "type", takes_value: TakesValue::Necessary(Some(SUGGESTIONS)) } ]; - // Just filenames test!(empty: [] => frees: [], flags: []); test!(one_arg: ["exa"] => frees: [ "exa" ], flags: []); @@ -633,7 +645,6 @@ mod parse_test { test!(two_arg_l: ["--", "--long"] => frees: [ "--long" ], flags: []); test!(two_arg_s: ["--", "-l"] => frees: [ "-l" ], flags: []); - // Long args test!(long: ["--long"] => frees: [], flags: [ (Flag::Long("long"), None) ]); test!(long_then: ["--long", "4"] => frees: [ "4" ], flags: [ (Flag::Long("long"), None) ]); @@ -650,7 +661,6 @@ mod parse_test { test!(arg_equals_s: ["--type=exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]); test!(arg_then_s: ["--type", "exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]); - // Short args test!(short: ["-l"] => frees: [], flags: [ (Flag::Short(b'l'), None) ]); test!(short_then: ["-l", "4"] => frees: [ "4" ], flags: [ (Flag::Short(b'l'), None) ]); @@ -672,7 +682,6 @@ mod parse_test { test!(short_two_equals_s: ["-t=exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]); test!(short_two_next_s: ["-t", "exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]); - // Unknown args test!(unknown_long: ["--quiet"] => error UnknownArgument { attempt: OsString::from("quiet") }); test!(unknown_long_eq: ["--quiet=shhh"] => error UnknownArgument { attempt: OsString::from("quiet") }); @@ -682,7 +691,6 @@ mod parse_test { test!(unknown_short_2nd_eq: ["-lq=shhh"] => error UnknownShortArgument { attempt: b'q' }); } - #[cfg(test)] mod matches_test { use super::*; @@ -701,9 +709,16 @@ mod matches_test { }; } - static VERBOSE: Arg = Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden }; - static COUNT: Arg = Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary(None) }; - + static VERBOSE: Arg = Arg { + short: Some(b'v'), + long: "verbose", + takes_value: TakesValue::Forbidden, + }; + static COUNT: Arg = Arg { + short: Some(b'c'), + long: "count", + takes_value: TakesValue::Necessary(None), + }; test!(short_never: [], has VERBOSE => false); test!(short_once: [(Flag::Short(b'v'), None)], has VERBOSE => true); @@ -712,13 +727,12 @@ mod matches_test { test!(long_twice: [(Flag::Long("verbose"), None), (Flag::Long("verbose"), None)], has VERBOSE => true); test!(long_mixed: [(Flag::Long("verbose"), None), (Flag::Short(b'v'), None)], has VERBOSE => true); - #[test] fn only_count() { let everything = OsString::from("everything"); let flags = MatchedFlags { - flags: vec![ (Flag::Short(b'c'), Some(&*everything)) ], + flags: vec![(Flag::Short(b'c'), Some(&*everything))], strictness: Strictness::UseLastArguments, }; @@ -728,11 +742,13 @@ mod matches_test { #[test] fn rightmost_count() { let everything = OsString::from("everything"); - let nothing = OsString::from("nothing"); + let nothing = OsString::from("nothing"); let flags = MatchedFlags { - flags: vec![ (Flag::Short(b'c'), Some(&*everything)), - (Flag::Short(b'c'), Some(&*nothing)) ], + flags: vec![ + (Flag::Short(b'c'), Some(&*everything)), + (Flag::Short(b'c'), Some(&*nothing)), + ], strictness: Strictness::UseLastArguments, }; @@ -741,7 +757,10 @@ mod matches_test { #[test] 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()); } diff --git a/src/options/theme.rs b/src/options/theme.rs index 2ddce813..11276b75 100644 --- a/src/options/theme.rs +++ b/src/options/theme.rs @@ -1,7 +1,6 @@ -use crate::options::{flags, vars, Vars, OptionsError}; use crate::options::parser::MatchedFlags; -use crate::theme::{Options, UseColours, ColourScale, Definitions}; - +use crate::options::{flags, vars, OptionsError, Vars}; +use crate::theme::{ColourScale, Definitions, Options, UseColours}; impl Options { pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { @@ -9,17 +8,19 @@ impl Options { let colour_scale = ColourScale::deduce(matches)?; let definitions = if use_colours == UseColours::Never { - Definitions::default() - } - else { - Definitions::deduce(vars) - }; + Definitions::default() + } else { + Definitions::deduce(vars) + }; - Ok(Self { use_colours, colour_scale, definitions }) + Ok(Self { + use_colours, + colour_scale, + definitions, + }) } } - impl UseColours { fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let default_value = match vars.get(vars::NO_COLOR) { @@ -31,61 +32,65 @@ impl UseColours { if word == "always" { Ok(Self::Always) - } - else if word == "auto" || word == "automatic" { + } else if word == "auto" || word == "automatic" { Ok(Self::Automatic) - } - else if word == "never" { + } else if word == "never" { Ok(Self::Never) - } - else { + } else { Err(OptionsError::BadArgument(&flags::COLOR, word.into())) } } } - impl ColourScale { fn deduce(matches: &MatchedFlags<'_>) -> Result { - if matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?.is_some() { + if matches + .has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))? + .is_some() + { Ok(Self::Gradient) - } - else { + } else { Ok(Self::Fixed) } } } - impl Definitions { fn deduce(vars: &V) -> Self { - let ls = vars.get(vars::LS_COLORS) + let ls = vars + .get(vars::LS_COLORS) .map(|e| e.to_string_lossy().to_string()); - let exa = vars.get_with_fallback(vars::EZA_COLORS, vars::EXA_COLORS) + let exa = vars + .get_with_fallback(vars::EZA_COLORS, vars::EXA_COLORS) .map(|e| e.to_string_lossy().to_string()); Self { ls, exa } } } - #[cfg(test)] mod terminal_test { use super::*; - use std::ffi::OsString; use crate::options::flags; - use crate::options::parser::{Flag, Arg}; + use crate::options::parser::{Arg, Flag}; + use std::ffi::OsString; use crate::options::test::parse_for_test; use crate::options::test::Strictnesses::*; - static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR, - &flags::COLOR_SCALE, &flags::COLOUR_SCALE, ]; + static TEST_ARGS: &[&Arg] = &[ + &flags::COLOR, + &flags::COLOUR, + &flags::COLOR_SCALE, + &flags::COLOUR_SCALE, + ]; macro_rules! test { ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => { #[test] fn $name() { - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf) + }) { assert_eq!(result, $result); } } @@ -95,7 +100,9 @@ mod terminal_test { #[test] fn $name() { let env = $env; - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &env)) { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf, &env) + }) { assert_eq!(result, $result); } } @@ -104,7 +111,9 @@ mod terminal_test { ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => { #[test] fn $name() { - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf) + }) { assert_eq!(result.unwrap_err(), $result); } } @@ -114,7 +123,9 @@ mod terminal_test { #[test] fn $name() { let env = $env; - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &env)) { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf, &env) + }) { assert_eq!(result.unwrap_err(), $result); } } @@ -147,23 +158,19 @@ mod terminal_test { // Test impl that just returns the value it has. impl Vars for MockVars { fn get(&self, name: &'static str) -> Option { - if name == vars::LS_COLORS && ! self.ls.is_empty() { + if name == vars::LS_COLORS && !self.ls.is_empty() { Some(OsString::from(self.ls)) - } - else if (name == vars::EZA_COLORS || name == vars::EXA_COLORS) && ! self.exa.is_empty() { + } else if (name == vars::EZA_COLORS || name == vars::EXA_COLORS) && !self.exa.is_empty() + { Some(OsString::from(self.exa)) - } - else if name == vars::NO_COLOR && ! self.no_color.is_empty() { + } else if name == vars::NO_COLOR && !self.no_color.is_empty() { Some(OsString::from(self.no_color)) - } - else { + } else { None } } } - - // Default test!(empty: UseColours <- [], MockVars::empty(); Both => Ok(UseColours::Automatic)); test!(empty_with_no_color: UseColours <- [], MockVars::with_no_color(); Both => Ok(UseColours::Never)); diff --git a/src/options/vars.rs b/src/options/vars.rs index 54cf7c57..44ecf0ac 100644 --- a/src/options/vars.rs +++ b/src/options/vars.rs @@ -1,6 +1,5 @@ use std::ffi::OsString; - // General variables /// Environment variable used to colour files, both by their filesystem type @@ -53,7 +52,6 @@ pub static EZA_GRID_ROWS: &str = "EZA_GRID_ROWS"; pub static EXA_ICON_SPACING: &str = "EXA_ICON_SPACING"; pub static EZA_ICON_SPACING: &str = "EZA_ICON_SPACING"; - /// Mockable wrapper for `std::env::var_os`. pub trait Vars { fn get(&self, name: &'static str) -> Option; @@ -69,12 +67,11 @@ pub trait Vars { fn source(&self, name: &'static str, fallback: &'static str) -> Option<&'static str> { match self.get(name) { Some(_) => Some(name), - None => self.get(fallback).and(Some(fallback)), + None => self.get(fallback).and(Some(fallback)), } } } - // Test impl that just returns the value it has. #[cfg(test)] impl Vars for Option { diff --git a/src/options/version.rs b/src/options/version.rs index 3b78c3a4..9c7a7bcc 100644 --- a/src/options/version.rs +++ b/src/options/version.rs @@ -7,13 +7,11 @@ use std::fmt; use crate::options::flags; use crate::options::parser::MatchedFlags; - #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct VersionString; // There were options here once, but there aren’t anymore! impl VersionString { - /// Determines how to show the version, if at all, based on the user’s /// command-line arguments. This one works backwards from the other /// ‘deduce’ functions, returning Err if help needs to be shown. @@ -22,8 +20,7 @@ impl VersionString { pub fn deduce(matches: &MatchedFlags<'_>) -> Option { if matches.count(&flags::VERSION) > 0 { Some(Self) - } - else { + } else { None } } @@ -31,11 +28,14 @@ impl VersionString { impl fmt::Display for VersionString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", include_str!(concat!(env!("OUT_DIR"), "/version_string.txt"))) + write!( + f, + "{}", + include_str!(concat!(env!("OUT_DIR"), "/version_string.txt")) + ) } } - #[cfg(test)] mod test { use crate::options::{Options, OptionsResult}; @@ -43,14 +43,14 @@ mod test { #[test] fn version() { - let args = vec![ OsStr::new("--version") ]; + let args = vec![OsStr::new("--version")]; let opts = Options::parse(args, &None); assert!(matches!(opts, OptionsResult::Version(_))); } #[test] fn version_with_file() { - let args = vec![ OsStr::new("--version"), OsStr::new("me") ]; + let args = vec![OsStr::new("--version"), OsStr::new("me")]; let opts = Options::parse(args, &None); assert!(matches!(opts, OptionsResult::Version(_))); } diff --git a/src/options/view.rs b/src/options/view.rs index 687c9de4..c0111213 100644 --- a/src/options/view.rs +++ b/src/options/view.rs @@ -1,12 +1,11 @@ use crate::fs::feature::xattr; -use crate::options::{flags, OptionsError, NumberSource, Vars}; use crate::options::parser::MatchedFlags; -use crate::output::{View, Mode, TerminalWidth, grid, details}; -use crate::output::grid_details::{self, RowThreshold}; +use crate::options::{flags, NumberSource, OptionsError, Vars}; use crate::output::file_name::Options as FileStyle; -use crate::output::table::{TimeTypes, SizeFormat, UserFormat, Columns, Options as TableOptions}; +use crate::output::grid_details::{self, RowThreshold}; +use crate::output::table::{Columns, Options as TableOptions, SizeFormat, TimeTypes, UserFormat}; use crate::output::time::TimeFormat; - +use crate::output::{details, grid, Mode, TerminalWidth, View}; impl View { pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { @@ -14,13 +13,16 @@ impl View { let width = TerminalWidth::deduce(matches, vars)?; let file_style = FileStyle::deduce(matches, vars)?; let deref_links = matches.has(&flags::DEREF_LINKS)?; - Ok(Self { mode, width, file_style, deref_links }) + Ok(Self { + mode, + width, + file_style, + deref_links, + }) } } - impl Mode { - /// Determine which viewing mode to use based on the user’s options. /// /// As with the other options, arguments are scanned right-to-left and the @@ -30,8 +32,12 @@ impl Mode { /// This is complicated a little by the fact that `--grid` and `--tree` /// can also combine with `--long`, so care has to be taken to use the pub fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { - let flag = matches.has_where_any(|f| f.matches(&flags::LONG) || f.matches(&flags::ONE_LINE) - || f.matches(&flags::GRID) || f.matches(&flags::TREE)); + let flag = matches.has_where_any(|f| { + f.matches(&flags::LONG) + || f.matches(&flags::ONE_LINE) + || f.matches(&flags::GRID) + || f.matches(&flags::TREE) + }); let Some(flag) = flag else { Self::strict_check_long_flags(matches)?; @@ -40,19 +46,24 @@ impl Mode { }; if flag.matches(&flags::LONG) - || (flag.matches(&flags::TREE) && matches.has(&flags::LONG)?) - || (flag.matches(&flags::GRID) && matches.has(&flags::LONG)?) + || (flag.matches(&flags::TREE) && matches.has(&flags::LONG)?) + || (flag.matches(&flags::GRID) && matches.has(&flags::LONG)?) { let _ = matches.has(&flags::LONG)?; let details = details::Options::deduce_long(matches, vars)?; - let flag = matches.has_where_any(|f| f.matches(&flags::GRID) || f.matches(&flags::TREE)); + let flag = + matches.has_where_any(|f| f.matches(&flags::GRID) || f.matches(&flags::TREE)); if flag.is_some() && flag.unwrap().matches(&flags::GRID) { let _ = matches.has(&flags::GRID)?; let grid = grid::Options::deduce(matches)?; let row_threshold = RowThreshold::deduce(vars)?; - let grid_details = grid_details::Options { grid, details, row_threshold }; + let grid_details = grid_details::Options { + grid, + details, + row_threshold, + }; return Ok(Self::GridDetails(grid_details)); } @@ -81,9 +92,18 @@ impl Mode { // If --long hasn’t been passed, then check if we need to warn the // user about flags that won’t have any effect. if matches.is_strict() { - for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS, - &flags::HEADER, &flags::BLOCKSIZE, &flags::TIME, &flags::GROUP, &flags::NUMERIC, - &flags::MOUNTS ] { + for option in &[ + &flags::BINARY, + &flags::BYTES, + &flags::INODE, + &flags::LINKS, + &flags::HEADER, + &flags::BLOCKSIZE, + &flags::TIME, + &flags::GROUP, + &flags::NUMERIC, + &flags::MOUNTS, + ] { if matches.has(option)? { return Err(OptionsError::Useless(option, false, &flags::LONG)); } @@ -91,9 +111,15 @@ impl Mode { if matches.has(&flags::GIT)? && !matches.has(&flags::NO_GIT)? { return Err(OptionsError::Useless(&flags::GIT, false, &flags::LONG)); - } - else if matches.has(&flags::LEVEL)? && ! matches.has(&flags::RECURSE)? && ! matches.has(&flags::TREE)? { - return Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)); + } else if matches.has(&flags::LEVEL)? + && !matches.has(&flags::RECURSE)? + && !matches.has(&flags::TREE)? + { + return Err(OptionsError::Useless2( + &flags::LEVEL, + &flags::RECURSE, + &flags::TREE, + )); } } @@ -101,7 +127,6 @@ impl Mode { } } - impl grid::Options { fn deduce(matches: &MatchedFlags<'_>) -> Result { let grid = grid::Options { @@ -112,7 +137,6 @@ impl grid::Options { } } - impl details::Options { fn deduce_tree(matches: &MatchedFlags<'_>) -> Result { let details = details::Options { @@ -128,10 +152,9 @@ impl details::Options { fn deduce_long(matches: &MatchedFlags<'_>, vars: &V) -> Result { if matches.is_strict() { - if matches.has(&flags::ACROSS)? && ! matches.has(&flags::GRID)? { + if matches.has(&flags::ACROSS)? && !matches.has(&flags::GRID)? { return Err(OptionsError::Useless(&flags::ACROSS, true, &flags::LONG)); - } - else if matches.has(&flags::ONE_LINE)? { + } else if matches.has(&flags::ONE_LINE)? { return Err(OptionsError::Useless(&flags::ONE_LINE, true, &flags::LONG)); } } @@ -146,7 +169,6 @@ impl details::Options { } } - impl TerminalWidth { fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { use crate::options::vars; @@ -166,84 +188,99 @@ impl TerminalWidth { Err(OptionsError::FailedParse(arg_str.to_string(), source, e)) } } - } - else if let Some(columns) = vars.get(vars::COLUMNS).and_then(|s| s.into_string().ok()) { + } else if let Some(columns) = vars.get(vars::COLUMNS).and_then(|s| s.into_string().ok()) { match columns.parse() { - Ok(width) => { - Ok(Self::Set(width)) - } + Ok(width) => Ok(Self::Set(width)), Err(e) => { let source = NumberSource::Env(vars::COLUMNS); Err(OptionsError::FailedParse(columns, source, e)) } } - } - else { + } else { Ok(Self::Automatic) } } } - impl RowThreshold { fn deduce(vars: &V) -> Result { use crate::options::vars; - if let Some(columns) = vars.get_with_fallback(vars::EZA_GRID_ROWS, vars::EXA_GRID_ROWS).and_then(|s| s.into_string().ok()) { + if let Some(columns) = vars + .get_with_fallback(vars::EZA_GRID_ROWS, vars::EXA_GRID_ROWS) + .and_then(|s| s.into_string().ok()) + { match columns.parse() { - Ok(rows) => { - Ok(Self::MinimumRows(rows)) - } + Ok(rows) => Ok(Self::MinimumRows(rows)), Err(e) => { - let source = NumberSource::Env(vars.source(vars::EZA_GRID_ROWS, vars::EXA_GRID_ROWS).unwrap()); + let source = NumberSource::Env( + vars.source(vars::EZA_GRID_ROWS, vars::EXA_GRID_ROWS) + .unwrap(), + ); Err(OptionsError::FailedParse(columns, source, e)) } } - } - else { + } else { Ok(Self::AlwaysGrid) } } } - impl TableOptions { fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { let time_format = TimeFormat::deduce(matches, vars)?; let size_format = SizeFormat::deduce(matches)?; let user_format = UserFormat::deduce(matches)?; let columns = Columns::deduce(matches)?; - Ok(Self { size_format, time_format, user_format, columns }) + Ok(Self { + size_format, + time_format, + user_format, + columns, + }) } } - impl Columns { fn deduce(matches: &MatchedFlags<'_>) -> Result { let time_types = TimeTypes::deduce(matches)?; let git = matches.has(&flags::GIT)? && !matches.has(&flags::NO_GIT)?; let subdir_git_repos = matches.has(&flags::GIT_REPOS)? && !matches.has(&flags::NO_GIT)?; - let subdir_git_repos_no_stat = !subdir_git_repos && matches.has(&flags::GIT_REPOS_NO_STAT)? && !matches.has(&flags::NO_GIT)?; + let subdir_git_repos_no_stat = !subdir_git_repos + && matches.has(&flags::GIT_REPOS_NO_STAT)? + && !matches.has(&flags::NO_GIT)?; - let blocksize = matches.has(&flags::BLOCKSIZE)?; - let group = matches.has(&flags::GROUP)?; - let inode = matches.has(&flags::INODE)?; - let links = matches.has(&flags::LINKS)?; - let octal = matches.has(&flags::OCTAL)?; + let blocksize = matches.has(&flags::BLOCKSIZE)?; + let group = matches.has(&flags::GROUP)?; + let inode = matches.has(&flags::INODE)?; + let links = matches.has(&flags::LINKS)?; + let octal = matches.has(&flags::OCTAL)?; let security_context = xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?; - let permissions = ! matches.has(&flags::NO_PERMISSIONS)?; - let filesize = ! matches.has(&flags::NO_FILESIZE)?; - let user = ! matches.has(&flags::NO_USER)?; + let permissions = !matches.has(&flags::NO_PERMISSIONS)?; + let filesize = !matches.has(&flags::NO_FILESIZE)?; + let user = !matches.has(&flags::NO_USER)?; - Ok(Self { time_types, inode, links, blocksize, group, git, subdir_git_repos, subdir_git_repos_no_stat, octal, security_context, permissions, filesize, user }) + Ok(Self { + time_types, + inode, + links, + blocksize, + group, + git, + subdir_git_repos, + subdir_git_repos_no_stat, + octal, + security_context, + permissions, + filesize, + user, + }) } } - impl SizeFormat { - /// Determine which file size to use in the file size column based on /// the user’s options. /// @@ -256,42 +293,37 @@ impl SizeFormat { let flag = matches.has_where(|f| f.matches(&flags::BINARY) || f.matches(&flags::BYTES))?; Ok(match flag { - Some(f) if f.matches(&flags::BINARY) => Self::BinaryBytes, - Some(f) if f.matches(&flags::BYTES) => Self::JustBytes, - _ => Self::DecimalBytes, + Some(f) if f.matches(&flags::BINARY) => Self::BinaryBytes, + Some(f) if f.matches(&flags::BYTES) => Self::JustBytes, + _ => Self::DecimalBytes, }) } } - impl TimeFormat { - /// Determine how time should be formatted in timestamp columns. fn deduce(matches: &MatchedFlags<'_>, vars: &V) -> Result { - let word = - if let Some(w) = matches.get(&flags::TIME_STYLE)? { - w.to_os_string() + let word = if let Some(w) = matches.get(&flags::TIME_STYLE)? { + w.to_os_string() + } else { + use crate::options::vars; + match vars.get(vars::TIME_STYLE) { + Some(ref t) if !t.is_empty() => t.clone(), + _ => return Ok(Self::DefaultFormat), } - else { - use crate::options::vars; - match vars.get(vars::TIME_STYLE) { - Some(ref t) if ! t.is_empty() => t.clone(), - _ => return Ok(Self::DefaultFormat) - } - }; + }; match word.to_string_lossy().as_ref() { - "default" => Ok(Self::DefaultFormat), + "default" => Ok(Self::DefaultFormat), "relative" => Ok(Self::Relative), - "iso" => Ok(Self::ISOFormat), + "iso" => Ok(Self::ISOFormat), "long-iso" => Ok(Self::LongISO), "full-iso" => Ok(Self::FullISO), - _ => Err(OptionsError::BadArgument(&flags::TIME_STYLE, word)) + _ => Err(OptionsError::BadArgument(&flags::TIME_STYLE, word)), } } } - impl UserFormat { fn deduce(matches: &MatchedFlags<'_>) -> Result { let flag = matches.has(&flags::NUMERIC)?; @@ -299,9 +331,7 @@ impl UserFormat { } } - impl TimeTypes { - /// Determine which of a file’s time fields should be displayed for it /// based on the user’s options. /// @@ -315,47 +345,48 @@ impl TimeTypes { fn deduce(matches: &MatchedFlags<'_>) -> Result { let possible_word = matches.get(&flags::TIME)?; let modified = matches.has(&flags::MODIFIED)?; - let changed = matches.has(&flags::CHANGED)?; + let changed = matches.has(&flags::CHANGED)?; let accessed = matches.has(&flags::ACCESSED)?; - let created = matches.has(&flags::CREATED)?; + let created = matches.has(&flags::CREATED)?; let no_time = matches.has(&flags::NO_TIME)?; let time_types = if no_time { - Self { modified: false, changed: false, accessed: false, created: false } + Self { + modified: false, + changed: false, + accessed: false, + created: false, + } } else if let Some(word) = possible_word { + #[rustfmt::skip] if modified { return Err(OptionsError::Useless(&flags::MODIFIED, true, &flags::TIME)); - } - else if changed { + } else if changed { return Err(OptionsError::Useless(&flags::CHANGED, true, &flags::TIME)); - } - else if accessed { + } else if accessed { return Err(OptionsError::Useless(&flags::ACCESSED, true, &flags::TIME)); - } - else if created { + } else if created { return Err(OptionsError::Useless(&flags::CREATED, true, &flags::TIME)); - } - else if word == "mod" || word == "modified" { + } else if word == "mod" || word == "modified" { Self { modified: true, changed: false, accessed: false, created: false } - } - else if word == "ch" || word == "changed" { + } else if word == "ch" || word == "changed" { Self { modified: false, changed: true, accessed: false, created: false } - } - else if word == "acc" || word == "accessed" { + } else if word == "acc" || word == "accessed" { Self { modified: false, changed: false, accessed: true, created: false } - } - else if word == "cr" || word == "created" { + } else if word == "cr" || word == "created" { Self { modified: false, changed: false, accessed: false, created: true } - } - else { + } else { return Err(OptionsError::BadArgument(&flags::TIME, word.into())); } - } - else if modified || changed || accessed || created { - Self { modified, changed, accessed, created } - } - else { + } else if modified || changed || accessed || created { + Self { + modified, + changed, + accessed, + created, + } + } else { Self::default() }; @@ -363,33 +394,49 @@ impl TimeTypes { } } - #[cfg(test)] mod test { use super::*; - use std::ffi::OsString; use crate::options::flags; - use crate::options::parser::{Flag, Arg}; + use crate::options::parser::{Arg, Flag}; + use std::ffi::OsString; use crate::options::test::parse_for_test; use crate::options::test::Strictnesses::*; - static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE, - &flags::TIME, &flags::MODIFIED, &flags::CHANGED, - &flags::CREATED, &flags::ACCESSED, - &flags::HEADER, &flags::GROUP, &flags::INODE, &flags::GIT, - &flags::LINKS, &flags::BLOCKSIZE, &flags::LONG, &flags::LEVEL, - &flags::GRID, &flags::ACROSS, &flags::ONE_LINE, &flags::TREE, - &flags::NUMERIC ]; + static TEST_ARGS: &[&Arg] = &[ + &flags::BINARY, + &flags::BYTES, + &flags::TIME_STYLE, + &flags::TIME, + &flags::MODIFIED, + &flags::CHANGED, + &flags::CREATED, + &flags::ACCESSED, + &flags::HEADER, + &flags::GROUP, + &flags::INODE, + &flags::GIT, + &flags::LINKS, + &flags::BLOCKSIZE, + &flags::LONG, + &flags::LEVEL, + &flags::GRID, + &flags::ACROSS, + &flags::ONE_LINE, + &flags::TREE, + &flags::NUMERIC, + ]; macro_rules! test { - ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => { /// Macro that writes a test. /// If testing both strictnesses, they’ll both be done in the same function. #[test] fn $name() { - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf) + }) { assert_eq!(result, $result); } } @@ -400,7 +447,9 @@ mod test { /// This is needed because sometimes the Ok type doesn’t implement `PartialEq`. #[test] fn $name() { - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf) + }) { assert_eq!(result.unwrap_err(), $result); } } @@ -411,22 +460,25 @@ mod test { /// Instead of using `PartialEq`, this just tests if it matches a pat. #[test] fn $name() { - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf) + }) { println!("Testing {:?}", result); match result { $pat => assert!(true), - _ => assert!(false), + _ => assert!(false), } } } }; - ($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => err $result:expr) => { /// Like above, but with $vars. #[test] fn $name() { - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf, &$vars) + }) { assert_eq!(result.unwrap_err(), $result); } } @@ -436,18 +488,19 @@ mod test { /// Like further above, but with $vars. #[test] fn $name() { - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| { + $type::deduce(mf, &$vars) + }) { println!("Testing {:?}", result); match result { $pat => assert!(true), - _ => assert!(false), + _ => assert!(false), } } } }; } - mod size_formats { use super::*; @@ -470,7 +523,6 @@ mod test { test!(both_8: SizeFormat <- ["--bytes", "--bytes"]; Complain => err OptionsError::Duplicate(Flag::Long("bytes"), Flag::Long("bytes"))); } - mod time_formats { use super::*; @@ -505,7 +557,6 @@ mod test { test!(override_env: TimeFormat <- ["--time-style=full-iso"], Some("long-iso".into()); Both => like Ok(TimeFormat::FullISO)); } - mod time_types { use super::*; @@ -541,7 +592,6 @@ mod test { // Multiples test!(time_uu: TimeTypes <- ["-u", "--modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: true, created: false })); - // Errors test!(time_tea: TimeTypes <- ["--time=tea"]; Both => err OptionsError::BadArgument(&flags::TIME, OsString::from("tea"))); test!(t_ea: TimeTypes <- ["-tea"]; Both => err OptionsError::BadArgument(&flags::TIME, OsString::from("ea"))); @@ -551,13 +601,11 @@ mod test { test!(overridden_2: TimeTypes <- ["-tcr", "-tmod"]; Complain => err OptionsError::Duplicate(Flag::Short(b't'), Flag::Short(b't'))); } - mod views { use super::*; use crate::output::grid::Options as GridOptions; - // Default test!(empty: Mode <- [], None; Both => like Ok(Mode::Grid(_))); diff --git a/src/output/cell.rs b/src/output/cell.rs index 9e576061..2c720e10 100644 --- a/src/output/cell.rs +++ b/src/output/cell.rs @@ -3,10 +3,9 @@ use std::iter::Sum; use std::ops::{Add, Deref, DerefMut}; -use ansiterm::{Style, ANSIString, ANSIStrings}; +use ansiterm::{ANSIString, ANSIStrings, Style}; use unicode_width::UnicodeWidthStr; - /// An individual cell that holds text in a table, used in the details and /// lines views to store ANSI-terminal-formatted data before it is printed. /// @@ -19,7 +18,6 @@ use unicode_width::UnicodeWidthStr; /// type by that name too.) #[derive(PartialEq, Debug, Clone, Default)] pub struct TextCell { - /// The contents of this cell, as a vector of ANSI-styled strings. pub contents: TextCellContents, @@ -36,14 +34,13 @@ impl Deref for TextCell { } impl TextCell { - /// Creates a new text cell that holds the given text in the given style, /// computing the Unicode width of the text. pub fn paint(style: Style, text: String) -> Self { let width = DisplayWidth::from(&*text); Self { - contents: vec![ style.paint(text) ].into(), + contents: vec![style.paint(text)].into(), width, } } @@ -55,7 +52,7 @@ impl TextCell { let width = DisplayWidth::from(text); Self { - contents: vec![ style.paint(text) ].into(), + contents: vec![style.paint(text)].into(), width, } } @@ -67,6 +64,7 @@ impl TextCell { /// This is used in place of empty table cells, as it is easier to read /// tabular data when there is *something* in each cell. pub fn blank(style: Style) -> Self { + #[rustfmt::skip] Self { contents: vec![ style.paint("-") ].into(), width: DisplayWidth::from(1), @@ -96,7 +94,6 @@ impl TextCell { } } - // I’d like to eventually abstract cells so that instead of *every* cell // storing a vector, only variable-length cells would, and individual cells // would just store an array of a fixed length (which would usually be just 1 @@ -123,7 +120,6 @@ impl TextCell { // // But exa still has bugs and I need to fix those first :( - /// The contents of a text cell, as a vector of ANSI-styled strings. /// /// It’s possible to use this type directly in the case where you want a @@ -152,7 +148,6 @@ impl Deref for TextCellContents { // above can just access the value directly. impl TextCellContents { - /// Produces an `ANSIStrings` value that can be used to print the styled /// values of this cell as an ANSI-terminal-formatted string. pub fn strings(&self) -> ANSIStrings<'_> { @@ -162,7 +157,8 @@ impl TextCellContents { /// Calculates the width that a cell with these contents would take up, by /// counting the number of characters in each unformatted ANSI string. pub fn width(&self) -> DisplayWidth { - self.0.iter() + self.0 + .iter() .map(|anstr| DisplayWidth::from(&**anstr)) .sum() } @@ -177,7 +173,6 @@ impl TextCellContents { } } - /// The Unicode “display width” of a string. /// /// This is related to the number of *graphemes* of a string, rather than the @@ -238,13 +233,13 @@ impl Add for DisplayWidth { impl Sum for DisplayWidth { fn sum(iter: I) -> Self - where I: Iterator + where + I: Iterator, { iter.fold(Self(0), Add::add) } } - #[cfg(test)] mod width_unit_test { use super::DisplayWidth; diff --git a/src/output/details.rs b/src/output/details.rs index 043572dd..6a1c0aa5 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -59,7 +59,6 @@ //! means that we must wait until every row has been added to the table before it //! can be displayed, in order to make sure that every column is wide enough. - use std::io::{self, Write}; use std::mem::MaybeUninit; use std::path::PathBuf; @@ -70,19 +69,18 @@ use scoped_threadpool::Pool; use log::*; -use crate::fs::{Dir, File}; use crate::fs::dir_action::RecurseOptions; use crate::fs::feature::git::GitCache; use crate::fs::feature::xattr::Attribute; use crate::fs::fields::SecurityContextType; use crate::fs::filter::FileFilter; +use crate::fs::{Dir, File}; use crate::output::cell::TextCell; use crate::output::file_name::Options as FileStyle; -use crate::output::table::{Table, Options as TableOptions, Row as TableRow}; -use crate::output::tree::{TreeTrunk, TreeParams, TreeDepth}; +use crate::output::table::{Options as TableOptions, Row as TableRow, Table}; +use crate::output::tree::{TreeDepth, TreeParams, TreeTrunk}; use crate::theme::Theme; - /// With the **Details** view, the output gets formatted into columns, with /// each `Column` object showing some piece of information about the file, /// such as its size, or its permissions. @@ -94,10 +92,10 @@ use crate::theme::Theme; /// /// Almost all the heavy lifting is done in a Table object, which handles the /// columns for each row. -#[allow(clippy::struct_excessive_bools)] /// This clearly isn't a state machine +#[allow(clippy::struct_excessive_bools)] +/// This clearly isn't a state machine #[derive(PartialEq, Eq, Debug)] pub struct Options { - /// Options specific to drawing a table. /// /// Directories themselves can pick which columns are *added* to this @@ -117,7 +115,6 @@ pub struct Options { pub mounts: bool, } - pub struct Render<'a> { pub dir: Option<&'a Dir>, pub files: Vec>, @@ -139,7 +136,7 @@ pub struct Render<'a> { pub git: Option<&'a GitCache>, } - +#[rustfmt::skip] struct Egg<'a> { table_row: Option, xattrs: &'a [Attribute], @@ -154,7 +151,6 @@ impl<'a> AsRef> for Egg<'a> { } } - impl<'a> Render<'a> { pub fn render(mut self, w: &mut W) -> io::Result<()> { let n_cpus = match num_cpus::get() as u32 { @@ -165,6 +161,7 @@ impl<'a> Render<'a> { let mut rows = Vec::new(); if let Some(ref table) = self.opts.table { + #[rustfmt::skip] match (self.git, self.dir) { (Some(g), Some(d)) => if ! g.has_anything_for(&d.path) { self.git = None }, (Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { self.git = None }, @@ -182,14 +179,25 @@ impl<'a> Render<'a> { // This is weird, but I can’t find a way around it: // https://internals.rust-lang.org/t/should-option-mut-t-implement-copy/3715/6 let mut table = Some(table); - self.add_files_to_table(&mut pool, &mut table, &mut rows, &self.files, TreeDepth::root()); + self.add_files_to_table( + &mut pool, + &mut table, + &mut rows, + &self.files, + TreeDepth::root(), + ); for row in self.iterate_with_table(table.unwrap(), rows) { writeln!(w, "{}", row.strings())?; } - } - else { - self.add_files_to_table(&mut pool, &mut None, &mut rows, &self.files, TreeDepth::root()); + } else { + self.add_files_to_table( + &mut pool, + &mut None, + &mut rows, + &self.files, + TreeDepth::root(), + ); for row in self.iterate(rows) { writeln!(w, "{}", row.strings())?; @@ -204,20 +212,30 @@ impl<'a> Render<'a> { // Do not show the hint '@' if the only extended attribute is the security // attribute and the security attribute column is active. let xattr_count = file.extended_attributes().len(); - let selinux_ctx_shown = self.opts.secattr && match file.security_context().context { - SecurityContextType::SELinux(_) => true, - SecurityContextType::None => false, - }; + let selinux_ctx_shown = self.opts.secattr + && match file.security_context().context { + SecurityContextType::SELinux(_) => true, + SecurityContextType::None => false, + }; xattr_count > 1 || (xattr_count == 1 && !selinux_ctx_shown) } /// Adds files to the table, possibly recursively. This is easily /// parallelisable, and uses a pool of threads. - fn add_files_to_table<'dir>(&self, pool: &mut Pool, table: &mut Option>, rows: &mut Vec, src: &[File<'dir>], depth: TreeDepth) { - use std::sync::{Arc, Mutex}; + fn add_files_to_table<'dir>( + &self, + pool: &mut Pool, + table: &mut Option>, + rows: &mut Vec, + src: &[File<'dir>], + depth: TreeDepth, + ) { use crate::fs::feature::xattr; + use std::sync::{Arc, Mutex}; - let mut file_eggs = (0..src.len()).map(|_| MaybeUninit::uninit()).collect::>(); + let mut file_eggs = (0..src.len()) + .map(|_| MaybeUninit::uninit()) + .collect::>(); pool.scoped(|scoped| { let file_eggs = Arc::new(Mutex::new(&mut file_eggs)); @@ -257,12 +275,13 @@ impl<'a> Render<'a> { &[] }; - let table_row = table.as_ref() - .map(|t| t.row_for_file(file, self.show_xattr_hint(file))); + let table_row = table + .as_ref() + .map(|t| t.row_for_file(file, self.show_xattr_hint(file))); let mut dir = None; 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) { trace!("matching on to_dir"); match file.to_dir() { Ok(d) => { @@ -275,7 +294,13 @@ impl<'a> Render<'a> { } }; - let egg = Egg { table_row, xattrs, errors, dir, file }; + let egg = Egg { + table_row, + xattrs, + errors, + dir, + file, + }; unsafe { std::ptr::write(file_eggs.lock().unwrap()[idx].as_mut_ptr(), egg) } }); } @@ -293,22 +318,29 @@ impl<'a> Render<'a> { t.add_widths(row); } - let file_name = self.file_style.for_file(egg.file, self.theme) - .with_link_paths() - .with_mount_details(self.opts.mounts) - .paint() - .promote(); + let file_name = self + .file_style + .for_file(egg.file, self.theme) + .with_link_paths() + .with_mount_details(self.opts.mounts) + .paint() + .promote(); let row = Row { - tree: tree_params, - cells: egg.table_row, - name: file_name, + tree: tree_params, + cells: egg.table_row, + name: file_name, }; rows.push(row); if let Some(ref dir) = egg.dir { - for file_to_add in dir.files(self.filter.dot_filter, self.git, self.git_ignoring, egg.file.deref_links) { + for file_to_add in dir.files( + self.filter.dot_filter, + self.git, + self.git_ignoring, + egg.file.deref_links, + ) { match file_to_add { Ok(f) => { files.push(f); @@ -321,13 +353,17 @@ impl<'a> Render<'a> { self.filter.filter_child_files(&mut files); - if ! files.is_empty() { + if !files.is_empty() { for xattr in egg.xattrs { rows.push(self.render_xattr(xattr, TreeParams::new(depth.deeper(), false))); } for (error, path) in errors { - rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), false), path)); + rows.push(self.render_error( + &error, + TreeParams::new(depth.deeper(), false), + path, + )); } self.add_files_to_table(pool, table, rows, &files, depth.deeper()); @@ -337,7 +373,8 @@ impl<'a> Render<'a> { let count = egg.xattrs.len(); for (index, xattr) in egg.xattrs.iter().enumerate() { - let params = 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); } @@ -353,9 +390,9 @@ impl<'a> Render<'a> { pub fn render_header(&self, header: TableRow) -> Row { Row { - tree: TreeParams::new(TreeDepth::root(), false), - cells: Some(header), - name: TextCell::paint_str(self.theme.ui.header, "Name"), + tree: TreeParams::new(TreeDepth::root(), false), + cells: Some(header), + name: TextCell::paint_str(self.theme.ui.header, "Name"), } } @@ -371,16 +408,31 @@ impl<'a> Render<'a> { // TODO: broken_symlink() doesn’t quite seem like the right name for // the style that’s being used here. Maybe split it in two? let name = TextCell::paint(self.theme.broken_symlink(), error_message); - Row { cells: None, name, tree } + Row { + cells: None, + name, + tree, + } } fn render_xattr(&self, xattr: &Attribute, tree: TreeParams) -> Row { - let name = TextCell::paint(self.theme.ui.perms.attribute, format!("{}=\"{}\"", xattr.name, xattr.value)); - Row { cells: None, name, tree } + let name = TextCell::paint( + self.theme.ui.perms.attribute, + format!("{}=\"{}\"", xattr.name, xattr.value), + ); + Row { + cells: None, + name, + tree, + } } pub fn render_file(&self, cells: TableRow, name: TextCell, tree: TreeParams) -> Row { - Row { cells: Some(cells), name, tree } + Row { + cells: Some(cells), + name, + tree, + } } pub fn iterate_with_table(&'a self, table: Table<'a>, rows: Vec) -> TableIter<'a> { @@ -402,9 +454,7 @@ impl<'a> Render<'a> { } } - pub struct Row { - /// Vector of cells to display. /// /// Most of the rows will be used to display files’ metadata, so this will @@ -421,7 +471,7 @@ pub struct Row { pub tree: TreeParams, } - +#[rustfmt::skip] pub struct TableIter<'a> { inner: VecIntoIter, table: Table<'a>, @@ -436,15 +486,13 @@ impl<'a> Iterator for TableIter<'a> { fn next(&mut self) -> Option { self.inner.next().map(|row| { - let mut cell = - if let Some(cells) = row.cells { - self.table.render(cells) - } - else { - let mut cell = TextCell::default(); - cell.add_spaces(self.total_width); - cell - }; + let mut cell = if let Some(cells) = row.cells { + self.table.render(cells) + } else { + let mut cell = TextCell::default(); + cell.add_spaces(self.total_width); + cell + }; for tree_part in self.tree_trunk.new_row(row.tree) { cell.push(self.tree_style.paint(tree_part.ascii_art()), 4); @@ -452,7 +500,7 @@ impl<'a> Iterator for TableIter<'a> { // If any tree characters have been printed, then add an extra // space, which makes the output look much better. - if ! row.tree.is_at_root() { + if !row.tree.is_at_root() { cell.add_spaces(1); } @@ -462,7 +510,6 @@ impl<'a> Iterator for TableIter<'a> { } } - pub struct Iter { tree_trunk: TreeTrunk, tree_style: Style, @@ -482,7 +529,7 @@ impl Iterator for Iter { // If any tree characters have been printed, then add an extra // space, which makes the output look much better. - if ! row.tree.is_at_root() { + if !row.tree.is_at_root() { cell.add_spaces(1); } diff --git a/src/output/escape.rs b/src/output/escape.rs index 05d598a3..5a15264b 100644 --- a/src/output/escape.rs +++ b/src/output/escape.rs @@ -1,6 +1,5 @@ use ansiterm::{ANSIString, Style}; - pub fn escape(string: String, bits: &mut Vec>, good: Style, bad: Style) { // if the string has no control character if string.chars().all(|c| !c.is_control()) { diff --git a/src/output/file_name.rs b/src/output/file_name.rs index 8b4ce791..ab438a4d 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -13,29 +13,34 @@ use crate::output::render::FiletypeColours; /// Basically a file name factory. #[derive(Debug, Copy, Clone)] pub struct Options { - /// Whether to append file class characters to file names. pub classify: Classify, /// Whether to prepend icon characters before file names. pub show_icons: ShowIcons, - + /// Whether to make file names hyperlinks. pub embed_hyperlinks: EmbedHyperlinks, } impl Options { - /// Create a new `FileName` that prints the given file’s name, painting it /// with the remaining arguments. - pub fn for_file<'a, 'dir, C>(self, file: &'a File<'dir>, colours: &'a C) -> FileName<'a, 'dir, C> { + pub fn for_file<'a, 'dir, C>( + self, + file: &'a File<'dir>, + colours: &'a C, + ) -> FileName<'a, 'dir, C> { FileName { file, colours, link_style: LinkStyle::JustFilenames, - options: self, - target: if file.is_link() { Some(file.link_target()) } - else { None }, + options: self, + target: if file.is_link() { + Some(file.link_target()) + } else { + None + }, mount_style: MountStyle::JustDirectoryNames, mounted_fs: file.mount_point_info(), } @@ -46,7 +51,6 @@ impl Options { /// links, depending on how long the resulting Cell can be. #[derive(PartialEq, Debug, Copy, Clone)] enum LinkStyle { - /// Just display the file names, but colour them differently if they’re /// a broken link or can’t be followed. JustFilenames, @@ -57,11 +61,9 @@ enum LinkStyle { FullLinkPaths, } - /// Whether to append file class characters to the file names. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum Classify { - /// Just display the file names, without any characters. JustFilenames, @@ -80,7 +82,6 @@ impl Default for Classify { /// mount details, depending on how long the resulting Cell can be. #[derive(PartialEq, Debug, Copy, Clone)] enum MountStyle { - /// Just display the directory names. JustDirectoryNames, @@ -92,7 +93,6 @@ enum MountStyle { /// Whether and how to show icons. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum ShowIcons { - /// Don’t show icons at all. Off, @@ -103,8 +103,7 @@ pub enum ShowIcons { /// Whether to embed hyperlinks. #[derive(PartialEq, Eq, Debug, Copy, Clone)] -pub enum EmbedHyperlinks{ - +pub enum EmbedHyperlinks { Off, On, } @@ -112,7 +111,6 @@ pub enum EmbedHyperlinks{ /// A **file name** holds all the information necessary to display the name /// of the given file. This is used in all of the views. pub struct FileName<'a, 'dir, C> { - /// A reference to the file that we’re getting the name of. file: &'a File<'dir>, @@ -120,7 +118,7 @@ pub struct FileName<'a, 'dir, C> { colours: &'a C, /// The file that this file points to if it’s a link. - target: Option>, // todo: remove? + target: Option>, // todo: remove? /// How to handle displaying links. link_style: LinkStyle, @@ -135,7 +133,6 @@ pub struct FileName<'a, 'dir, C> { } impl<'a, 'dir, C> FileName<'a, 'dir, C> { - /// Sets the flag on this file name to display link targets with an /// arrow followed by their path. pub fn with_link_paths(mut self) -> Self { @@ -156,7 +153,6 @@ impl<'a, 'dir, C> FileName<'a, 'dir, C> { } impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { - /// Paints the name of the file using the colours, resulting in a vector /// of coloured cells that can be printed to the terminal. /// @@ -185,13 +181,13 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { } } - if ! self.file.name.is_empty() { - // The “missing file” colour seems like it should be used here, - // but it’s not! In a grid view, where there’s no space to display - // link targets, the filename has to have a different style to - // indicate this fact. But when showing targets, we can just - // colour the path instead (see below), and leave the broken - // link’s filename as the link colour. + if !self.file.name.is_empty() { + // The “missing file” colour seems like it should be used here, + // but it’s not! In a grid view, where there’s no space to display + // link targets, the filename has to have a different style to + // indicate this fact. But when showing targets, we can just + // colour the path instead (see below), and leave the broken + // link’s filename as the link colour. for bit in self.escaped_file_name() { bits.push(bit); } @@ -208,7 +204,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { self.add_parent_bits(&mut bits, parent); } - if ! target.name.is_empty() { + if !target.name.is_empty() { let target_options = Options { classify: Classify::JustFilenames, show_icons: ShowIcons::Off, @@ -254,14 +250,15 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { // Do nothing — the error gets displayed on the next line } } - } - else if let Classify::AddFileIndicators = self.options.classify { + } else if let Classify::AddFileIndicators = self.options.classify { if let Some(class) = self.classify_char(self.file) { bits.push(Style::default().paint(class)); } } - if let (MountStyle::MountInfo, Some(mount_details)) = (self.mount_style, self.mounted_fs.as_ref()) { + if let (MountStyle::MountInfo, Some(mount_details)) = + (self.mount_style, self.mounted_fs.as_ref()) + { // This is a filesystem mounted on the directory, output its details bits.push(Style::default().paint(" [")); bits.push(Style::default().paint(mount_details.source.clone())); @@ -279,16 +276,23 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { let coconut = parent.components().count(); if coconut == 1 && parent.has_root() { - bits.push(self.colours.symlink_path().paint(std::path::MAIN_SEPARATOR.to_string())); - } - else if coconut >= 1 { + bits.push( + self.colours + .symlink_path() + .paint(std::path::MAIN_SEPARATOR.to_string()), + ); + } else if coconut >= 1 { escape( parent.to_string_lossy().to_string(), bits, self.colours.symlink_path(), self.colours.control_char(), ); - bits.push(self.colours.symlink_path().paint(std::path::MAIN_SEPARATOR.to_string())); + bits.push( + self.colours + .symlink_path() + .paint(std::path::MAIN_SEPARATOR.to_string()), + ); } } @@ -298,20 +302,15 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { fn classify_char(&self, file: &File<'_>) -> Option<&'static str> { if file.is_executable_file() { Some("*") - } - else if file.is_directory() { + } else if file.is_directory() { Some("/") - } - else if file.is_pipe() { + } else if file.is_pipe() { Some("|") - } - else if file.is_link() { + } else if file.is_link() { Some("@") - } - else if file.is_socket() { + } else if file.is_socket() { Some("=") - } - else { + } else { None } } @@ -320,11 +319,9 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { fn classify_char(&self, file: &File<'_>) -> Option<&'static str> { if file.is_directory() { Some("/") - } - else if file.is_link() { + } else if file.is_link() { Some("@") - } - else { + } else { None } } @@ -342,7 +339,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { /// So in that situation, those characters will be escaped and highlighted in /// a different colour. fn escaped_file_name<'unused>(&self) -> Vec> { - use percent_encoding::{CONTROLS, utf8_percent_encode}; + use percent_encoding::{utf8_percent_encode, CONTROLS}; const HYPERLINK_START: &str = "\x1B]8;;"; const HYPERLINK_END: &str = "\x1B\x5C"; @@ -352,7 +349,11 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { let mut display_hyperlink = false; if self.options.embed_hyperlinks == EmbedHyperlinks::On { - if let Some(abs_path) = self.file.absolute_path().and_then(|p| p.as_os_str().to_str()) { + if let Some(abs_path) = self + .file + .absolute_path() + .and_then(|p| p.as_os_str().to_str()) + { let abs_path = utf8_percent_encode(abs_path, CONTROLS).to_string(); // On Windows, `std::fs::canonicalize` adds the Win32 File prefix, which we need to remove @@ -375,7 +376,9 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { ); if display_hyperlink { - bits.push(ANSIString::from(format!("{HYPERLINK_START}{HYPERLINK_END}"))); + bits.push(ANSIString::from(format!( + "{HYPERLINK_START}{HYPERLINK_END}" + ))); } bits @@ -394,6 +397,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { } } + #[rustfmt::skip] match self.file { f if f.is_mount_point() => self.colours.mount_point(), f if f.is_directory() => self.colours.directory(), @@ -419,10 +423,8 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { } } - /// The set of colours that are needed to paint a file name. pub trait Colours: FiletypeColours { - /// The style to paint the path of a symlink’s target, up to but not /// including the file’s name. fn symlink_path(&self) -> Style; @@ -430,9 +432,9 @@ pub trait Colours: FiletypeColours { /// The style to paint the arrow between a link and its target. fn normal_arrow(&self) -> Style; - /// The style to paint the filenames of broken links in views that don’t - /// show link targets, and the style to paint the *arrow* between the link - /// and its target in views that *do* show link targets. + /// The style to paint the filenames of broken links in views that don’t + /// show link targets, and the style to paint the *arrow* between the link + /// and its target in views that *do* show link targets. fn broken_symlink(&self) -> Style; /// The style to paint the entire filename of a broken link. @@ -454,8 +456,7 @@ pub trait Colours: FiletypeColours { fn colour_file(&self, file: &File<'_>) -> Style; } - /// Generate a string made of `n` spaces. fn spaces(width: u32) -> String { - (0 .. width).map(|_| ' ').collect() + (0..width).map(|_| ' ').collect() } diff --git a/src/output/grid.rs b/src/output/grid.rs index ec5cdc51..21d513e1 100644 --- a/src/output/grid.rs +++ b/src/output/grid.rs @@ -2,13 +2,12 @@ use std::io::{self, Write}; use term_grid as tg; -use crate::fs::File; use crate::fs::filter::FileFilter; +use crate::fs::File; use crate::output::file_name::Options as FileStyle; -use crate::output::file_name::{ShowIcons, EmbedHyperlinks}; +use crate::output::file_name::{EmbedHyperlinks, ShowIcons}; use crate::theme::Theme; - #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct Options { pub across: bool, @@ -16,12 +15,14 @@ pub struct Options { impl Options { pub fn direction(self) -> tg::Direction { - if self.across { tg::Direction::LeftToRight } - else { tg::Direction::TopToBottom } + if self.across { + tg::Direction::LeftToRight + } else { + tg::Direction::TopToBottom + } } } - pub struct Render<'a> { pub files: Vec>, pub theme: &'a Theme, @@ -34,8 +35,8 @@ pub struct Render<'a> { impl<'a> Render<'a> { pub fn render(mut self, w: &mut W) -> io::Result<()> { let mut grid = tg::Grid::new(tg::GridOptions { - direction: self.opts.direction(), - filling: tg::Filling::Spaces(2), + direction: self.opts.direction(), + filling: tg::Filling::Spaces(2), }); grid.reserve(self.files.len()); @@ -44,6 +45,7 @@ impl<'a> Render<'a> { for file in &self.files { let filename = self.file_style.for_file(file, self.theme); let contents = filename.paint(); + #[rustfmt::skip] let width = match (filename.options.embed_hyperlinks, filename.options.show_icons) { (EmbedHyperlinks::On, ShowIcons::On(spacing)) => filename.bare_width() + 1 + (spacing as usize), (EmbedHyperlinks::On, ShowIcons::Off) => filename.bare_width(), @@ -51,7 +53,7 @@ impl<'a> Render<'a> { }; grid.add(tg::Cell { - contents: contents.strings().to_string(), + contents: contents.strings().to_string(), // with hyperlink escape sequences, // the actual *contents.width() is larger than actually needed, so we take only the filename width, @@ -60,8 +62,7 @@ impl<'a> Render<'a> { if let Some(display) = grid.fit_into_width(self.console_width) { write!(w, "{display}") - } - else { + } else { // File names too long for a grid - drop down to just listing them! // This isn’t *quite* the same as the lines view, which also // displays full link paths. diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs index 2b29c1b8..e638304f 100644 --- a/src/output/grid_details.rs +++ b/src/output/grid_details.rs @@ -5,19 +5,20 @@ use std::io::{self, Write}; use ansiterm::ANSIStrings; use term_grid as grid; -use crate::fs::{Dir, File}; use crate::fs::feature::git::GitCache; use crate::fs::filter::FileFilter; -use crate::output::cell::{TextCell, DisplayWidth}; -use crate::output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender}; +use crate::fs::{Dir, File}; +use crate::output::cell::{DisplayWidth, TextCell}; +use crate::output::details::{ + Options as DetailsOptions, Render as DetailsRender, Row as DetailsRow, +}; use crate::output::file_name::Options as FileStyle; -use crate::output::file_name::{ShowIcons, EmbedHyperlinks}; +use crate::output::file_name::{EmbedHyperlinks, ShowIcons}; use crate::output::grid::Options as GridOptions; -use crate::output::table::{Table, Row as TableRow, Options as TableOptions}; -use crate::output::tree::{TreeParams, TreeDepth}; +use crate::output::table::{Options as TableOptions, Row as TableRow, Table}; +use crate::output::tree::{TreeDepth, TreeParams}; use crate::theme::Theme; - #[derive(PartialEq, Eq, Debug)] pub struct Options { pub grid: GridOptions, @@ -31,7 +32,6 @@ impl Options { } } - /// The grid-details view can be configured to revert to just a details view /// (with one column) if it wouldn’t produce enough rows of output. /// @@ -41,7 +41,6 @@ impl Options { /// larger directory listings. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum RowThreshold { - /// Only use grid-details view if it would result in at least this many /// rows of output. MinimumRows(usize), @@ -50,9 +49,7 @@ pub enum RowThreshold { AlwaysGrid, } - pub struct Render<'a> { - /// The directory that’s being rendered here. /// We need this to know which columns to put in the output. pub dir: Option<&'a Dir>, @@ -91,7 +88,6 @@ pub struct Render<'a> { } impl<'a> Render<'a> { - /// Create a temporary Details render that gets used for the columns of /// the grid-details render that’s being generated. /// @@ -99,6 +95,7 @@ impl<'a> Render<'a> { /// the table in *this* file, not in details: we only want to insert every /// *n* files into each column’s table, not all of them. fn details_for_column(&self) -> DetailsRender<'a> { + #[rustfmt::skip] DetailsRender { dir: self.dir, files: Vec::new(), @@ -117,6 +114,7 @@ impl<'a> Render<'a> { /// when the user asked for a grid-details view but the terminal width is /// not available, so we downgrade. pub fn give_up(self) -> DetailsRender<'a> { + #[rustfmt::skip] DetailsRender { dir: self.dir, files: self.files, @@ -136,27 +134,35 @@ impl<'a> Render<'a> { pub fn render(mut self, w: &mut W) -> io::Result<()> { if let Some((grid, width)) = self.find_fitting_grid() { write!(w, "{}", grid.fit_into_columns(width)) - } - else { + } else { self.give_up().render(w) } } pub fn find_fitting_grid(&mut self) -> Option<(grid::Grid, grid::Width)> { - let options = self.details.table.as_ref().expect("Details table options not given!"); + let options = self + .details + .table + .as_ref() + .expect("Details table options not given!"); let drender = self.details_for_column(); let (first_table, _) = self.make_table(options, &drender); - let rows = self.files.iter() - .map(|file| first_table.row_for_file(file, drender.show_xattr_hint(file))) - .collect::>(); + let rows = self + .files + .iter() + .map(|file| first_table.row_for_file(file, drender.show_xattr_hint(file))) + .collect::>(); - let file_names = self.files.iter() + let file_names = self + .files + .iter() .map(|file| { let filename = self.file_style.for_file(file, self.theme); let contents = filename.paint(); + #[rustfmt::skip] let width = match (filename.options.embed_hyperlinks, filename.options.show_icons) { (EmbedHyperlinks::On, ShowIcons::On(spacing)) => filename.bare_width() + 1 + (spacing as usize), (EmbedHyperlinks::On, ShowIcons::Off) => filename.bare_width(), @@ -193,12 +199,20 @@ impl<'a> Render<'a> { } if !the_grid_fits || column_count == file_names.len() { - let last_column_count = if the_grid_fits { column_count } else { column_count - 1 }; + let last_column_count = if the_grid_fits { + column_count + } else { + column_count - 1 + }; // If we’ve figured out how many columns can fit in the user’s terminal, // and it turns out there aren’t enough rows to make it worthwhile // (according to EZA_GRID_ROWS), then just resort to the lines view. if let RowThreshold::MinimumRows(thresh) = self.row_threshold { - if last_working_grid.fit_into_columns(last_column_count).row_count() < thresh { + if last_working_grid + .fit_into_columns(last_column_count) + .row_count() + < thresh + { return None; } } @@ -210,7 +224,12 @@ impl<'a> Render<'a> { None } - fn make_table(&mut self, options: &'a TableOptions, drender: &DetailsRender<'_>) -> (Table<'a>, Vec) { + fn make_table( + &mut self, + options: &'a TableOptions, + drender: &DetailsRender<'_>, + ) -> (Table<'a>, Vec) { + #[rustfmt::skip] match (self.git, self.dir) { (Some(g), Some(d)) => if ! g.has_anything_for(&d.path) { self.git = None }, (Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { self.git = None }, @@ -229,9 +248,16 @@ impl<'a> Render<'a> { (table, rows) } - fn make_grid(&mut self, column_count: usize, options: &'a TableOptions, file_names: &[TextCell], rows: Vec, drender: &DetailsRender<'_>) -> grid::Grid { + fn make_grid( + &mut self, + column_count: usize, + options: &'a TableOptions, + file_names: &[TextCell], + rows: Vec, + drender: &DetailsRender<'_>, + ) -> grid::Grid { let mut tables = Vec::new(); - for _ in 0 .. column_count { + for _ in 0..column_count { tables.push(self.make_table(options, drender)); } @@ -245,52 +271,58 @@ impl<'a> Render<'a> { for (i, (file_name, row)) in file_names.iter().zip(rows).enumerate() { let index = if self.grid.across { - i % column_count - } - else { - i / original_height - }; + i % column_count + } else { + i / original_height + }; let (ref mut table, ref mut rows) = tables[index]; table.add_widths(&row); - let details_row = drender.render_file(row, file_name.clone(), TreeParams::new(TreeDepth::root(), false)); + let details_row = drender.render_file( + row, + file_name.clone(), + TreeParams::new(TreeDepth::root(), false), + ); rows.push(details_row); } let columns = tables .into_iter() .map(|(table, details_rows)| { - drender.iterate_with_table(table, details_rows) - .collect::>() - }) + drender + .iterate_with_table(table, details_rows) + .collect::>() + }) .collect::>(); - let direction = if self.grid.across { grid::Direction::LeftToRight } - else { grid::Direction::TopToBottom }; + let direction = if self.grid.across { + grid::Direction::LeftToRight + } else { + grid::Direction::TopToBottom + }; let filling = grid::Filling::Spaces(4); let mut grid = grid::Grid::new(grid::GridOptions { direction, filling }); if self.grid.across { - for row in 0 .. height { + for row in 0..height { for column in &columns { if row < column.len() { let cell = grid::Cell { contents: ANSIStrings(&column[row].contents).to_string(), - width: *column[row].width, + width: *column[row].width, }; grid.add(cell); } } } - } - else { + } else { for column in &columns { for cell in column { let cell = grid::Cell { contents: ANSIStrings(&cell.contents).to_string(), - width: *cell.width, + width: *cell.width, }; grid.add(cell); @@ -302,7 +334,6 @@ impl<'a> Render<'a> { } } - fn divide_rounding_up(a: usize, b: usize) -> usize { let mut result = a / b; diff --git a/src/output/icons.rs b/src/output/icons.rs index 64a0948e..80b82099 100644 --- a/src/output/icons.rs +++ b/src/output/icons.rs @@ -6,6 +6,7 @@ use crate::fs::File; #[non_exhaustive] struct Icons; +#[rustfmt::skip] impl Icons { const AUDIO: char = '\u{f001}'; //  const BINARY: char = '\u{eae8}'; //  @@ -703,9 +704,11 @@ const EXTENSION_ICONS: Map<&'static str, char> = phf_map! { /// - Attributes such as bold or underline should not be used to paint the /// icon, as they can make it look weird. pub fn iconify_style(style: Style) -> Style { - style.background.or(style.foreground) - .map(Style::from) - .unwrap_or_default() + style + .background + .or(style.foreground) + .map(Style::from) + .unwrap_or_default() } /// Lookup the icon for a file based on the file's name, if the entry is a diff --git a/src/output/lines.rs b/src/output/lines.rs index a6ffe193..6f6ab00d 100644 --- a/src/output/lines.rs +++ b/src/output/lines.rs @@ -2,13 +2,12 @@ use std::io::{self, Write}; use ansiterm::ANSIStrings; -use crate::fs::File; use crate::fs::filter::FileFilter; +use crate::fs::File; use crate::output::cell::TextCellContents; use crate::output::file_name::Options as FileStyle; use crate::theme::Theme; - /// The lines view literally just displays each file, line-by-line. pub struct Render<'a> { pub files: Vec>, diff --git a/src/output/mod.rs b/src/output/mod.rs index 35f2a053..158d2229 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -1,4 +1,4 @@ -pub use self::cell::{TextCell, TextCellContents, DisplayWidth}; +pub use self::cell::{DisplayWidth, TextCell, TextCellContents}; pub use self::escape::escape; pub mod details; @@ -15,7 +15,6 @@ mod cell; mod escape; mod tree; - /// The **view** contains all information about how to format output. #[derive(Debug)] pub struct View { @@ -25,7 +24,6 @@ pub struct View { pub deref_links: bool, } - /// The **mode** is the “type” of output. #[derive(PartialEq, Eq, Debug)] #[allow(clippy::large_enum_variant)] @@ -36,11 +34,9 @@ pub enum Mode { Lines, } - /// The width of the terminal requested by the user. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum TerminalWidth { - /// The user requested this specific number of columns. Set(usize), @@ -54,6 +50,7 @@ impl TerminalWidth { // terminal, but we’re only interested in stdout because it’s // where the output goes. + #[rustfmt::skip] match self { Self::Set(width) => Some(width), Self::Automatic => terminal_size::terminal_size().map(|(w, _)| w.0.into()), diff --git a/src/output/render/blocks.rs b/src/output/render/blocks.rs index af3d7b3f..b158e65f 100644 --- a/src/output/render/blocks.rs +++ b/src/output/render/blocks.rs @@ -3,28 +3,31 @@ use locale::Numeric as NumericLocale; use number_prefix::Prefix; use crate::fs::fields as f; -use crate::output::cell::{TextCell, DisplayWidth}; +use crate::output::cell::{DisplayWidth, TextCell}; use crate::output::table::SizeFormat; - impl f::Blocksize { - pub fn render(self, colours: &C, size_format: SizeFormat, numerics: &NumericLocale) -> TextCell { + pub fn render( + self, + colours: &C, + size_format: SizeFormat, + numerics: &NumericLocale, + ) -> TextCell { use number_prefix::NumberPrefix; let size = match self { - Self::Some(s) => s, - Self::None => return TextCell::blank(colours.no_blocksize()), + Self::Some(s) => s, + Self::None => return TextCell::blank(colours.no_blocksize()), }; let result = match size_format { - SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64), - SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64), - SizeFormat::JustBytes => { - + SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64), + SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64), + SizeFormat::JustBytes => { // Use the binary prefix to select a style. let prefix = match NumberPrefix::binary(size as f64) { - NumberPrefix::Standalone(_) => None, - NumberPrefix::Prefixed(p, _) => Some(p), + NumberPrefix::Standalone(_) => None, + NumberPrefix::Prefixed(p, _) => Some(p), }; // But format the number directly using the locale. @@ -35,8 +38,10 @@ impl f::Blocksize { }; let (prefix, n) = match result { - NumberPrefix::Standalone(b) => return TextCell::paint(colours.blocksize(None), numerics.format_int(b)), - NumberPrefix::Prefixed(p, n) => (p, n), + NumberPrefix::Standalone(b) => { + return TextCell::paint(colours.blocksize(None), numerics.format_int(b)) + } + NumberPrefix::Prefixed(p, n) => (p, n), }; let symbol = prefix.symbol(); @@ -52,89 +57,106 @@ impl f::Blocksize { contents: vec![ colours.blocksize(Some(prefix)).paint(number), colours.unit(Some(prefix)).paint(symbol), - ].into(), + ] + .into(), } } } - +#[rustfmt::skip] pub trait Colours { fn blocksize(&self, prefix: Option) -> Style; fn unit(&self, prefix: Option) -> Style; fn no_blocksize(&self) -> Style; } - #[cfg(test)] pub mod test { - use ansiterm::Style; use ansiterm::Colour::*; + use ansiterm::Style; use super::Colours; - use crate::output::cell::{TextCell, DisplayWidth}; - use crate::output::table::SizeFormat; use crate::fs::fields as f; + use crate::output::cell::{DisplayWidth, TextCell}; + use crate::output::table::SizeFormat; use locale::Numeric as NumericLocale; use number_prefix::Prefix; struct TestColours; + #[rustfmt::skip] impl Colours for TestColours { fn blocksize(&self, _prefix: Option) -> Style { Fixed(66).normal() } fn unit(&self, _prefix: Option) -> Style { Fixed(77).bold() } fn no_blocksize(&self) -> Style { Black.italic() } } - #[test] fn directory() { let directory = f::Blocksize::None; let expected = TextCell::blank(Black.italic()); - assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english())) + assert_eq!( + expected, + directory.render( + &TestColours, + SizeFormat::JustBytes, + &NumericLocale::english() + ) + ) } - #[test] fn file_decimal() { let directory = f::Blocksize::Some(2_100_000); let expected = TextCell { width: DisplayWidth::from(4), - contents: vec![ - Fixed(66).paint("2.1"), - Fixed(77).bold().paint("M"), - ].into(), + contents: vec![Fixed(66).paint("2.1"), Fixed(77).bold().paint("M")].into(), }; - assert_eq!(expected, directory.render(&TestColours, SizeFormat::DecimalBytes, &NumericLocale::english())) + assert_eq!( + expected, + directory.render( + &TestColours, + SizeFormat::DecimalBytes, + &NumericLocale::english() + ) + ) } - #[test] fn file_binary() { let directory = f::Blocksize::Some(1_048_576); let expected = TextCell { width: DisplayWidth::from(5), - contents: vec![ - Fixed(66).paint("1.0"), - Fixed(77).bold().paint("Mi"), - ].into(), + contents: vec![Fixed(66).paint("1.0"), Fixed(77).bold().paint("Mi")].into(), }; - assert_eq!(expected, directory.render(&TestColours, SizeFormat::BinaryBytes, &NumericLocale::english())) + assert_eq!( + expected, + directory.render( + &TestColours, + SizeFormat::BinaryBytes, + &NumericLocale::english() + ) + ) } - #[test] fn file_bytes() { let directory = f::Blocksize::Some(1_048_576); let expected = TextCell { width: DisplayWidth::from(9), - contents: vec![ - Fixed(66).paint("1,048,576"), - ].into(), + contents: vec![Fixed(66).paint("1,048,576")].into(), }; - assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english())) + assert_eq!( + expected, + directory.render( + &TestColours, + SizeFormat::JustBytes, + &NumericLocale::english() + ) + ) } } diff --git a/src/output/render/filetype.rs b/src/output/render/filetype.rs index 183a0198..80ab5752 100644 --- a/src/output/render/filetype.rs +++ b/src/output/render/filetype.rs @@ -2,9 +2,9 @@ use ansiterm::{ANSIString, Style}; use crate::fs::fields as f; - impl f::Type { pub fn render(self, colours: &C) -> ANSIString<'static> { + #[rustfmt::skip] match self { Self::File => colours.normal().paint("."), Self::Directory => colours.directory().paint("d"), @@ -18,7 +18,6 @@ impl f::Type { } } - pub trait Colours { fn normal(&self) -> Style; fn directory(&self) -> Style; diff --git a/src/output/render/git.rs b/src/output/render/git.rs index f5104208..74549c01 100644 --- a/src/output/render/git.rs +++ b/src/output/render/git.rs @@ -1,23 +1,20 @@ use ansiterm::{ANSIString, Style}; -use crate::output::cell::{TextCell, DisplayWidth}; use crate::fs::fields as f; - +use crate::output::cell::{DisplayWidth, TextCell}; impl f::Git { pub fn render(self, colours: &dyn Colours) -> TextCell { TextCell { width: DisplayWidth::from(2), - contents: vec![ - self.staged.render(colours), - self.unstaged.render(colours), - ].into(), + contents: vec![self.staged.render(colours), self.unstaged.render(colours)].into(), } } } impl f::GitStatus { fn render(self, colours: &dyn Colours) -> ANSIString<'static> { + #[rustfmt::skip] match self { Self::NotModified => colours.not_modified().paint("-"), Self::New => colours.new().paint("N"), @@ -35,7 +32,7 @@ pub trait Colours { fn not_modified(&self) -> Style; // FIXME: this amount of allows needed to keep clippy happy should be enough // of an argument that new needs to be renamed. - #[allow(clippy::new_ret_no_self,clippy::wrong_self_convention)] + #[allow(clippy::new_ret_no_self, clippy::wrong_self_convention)] fn new(&self) -> Style; fn modified(&self) -> Style; fn deleted(&self) -> Style; @@ -45,7 +42,6 @@ pub trait Colours { fn conflicted(&self) -> Style; } - impl f::SubdirGitRepo { pub fn render(self, colours: &dyn RepoColours) -> TextCell { let branch_name = match self.branch { @@ -92,63 +88,69 @@ pub trait RepoColours { fn git_dirty(&self) -> Style; } - #[cfg(test)] pub mod test { use super::Colours; - use crate::output::cell::{TextCell, DisplayWidth}; use crate::fs::fields as f; + use crate::output::cell::{DisplayWidth, TextCell}; use ansiterm::Colour::*; use ansiterm::Style; - struct TestColours; impl Colours for TestColours { - fn not_modified(&self) -> Style { Fixed(90).normal() } - fn new(&self) -> Style { Fixed(91).normal() } - fn modified(&self) -> Style { Fixed(92).normal() } - fn deleted(&self) -> Style { Fixed(93).normal() } - fn renamed(&self) -> Style { Fixed(94).normal() } - fn type_change(&self) -> Style { Fixed(95).normal() } - fn ignored(&self) -> Style { Fixed(96).normal() } - fn conflicted(&self) -> Style { Fixed(97).normal() } + fn not_modified(&self) -> Style { + Fixed(90).normal() + } + fn new(&self) -> Style { + Fixed(91).normal() + } + fn modified(&self) -> Style { + Fixed(92).normal() + } + fn deleted(&self) -> Style { + Fixed(93).normal() + } + fn renamed(&self) -> Style { + Fixed(94).normal() + } + fn type_change(&self) -> Style { + Fixed(95).normal() + } + fn ignored(&self) -> Style { + Fixed(96).normal() + } + fn conflicted(&self) -> Style { + Fixed(97).normal() + } } - #[test] fn git_blank() { let stati = f::Git { - staged: f::GitStatus::NotModified, + staged: f::GitStatus::NotModified, unstaged: f::GitStatus::NotModified, }; let expected = TextCell { width: DisplayWidth::from(2), - contents: vec![ - Fixed(90).paint("-"), - Fixed(90).paint("-"), - ].into(), + contents: vec![Fixed(90).paint("-"), Fixed(90).paint("-")].into(), }; assert_eq!(expected, stati.render(&TestColours)) } - #[test] fn git_new_changed() { let stati = f::Git { - staged: f::GitStatus::New, + staged: f::GitStatus::New, unstaged: f::GitStatus::Modified, }; let expected = TextCell { width: DisplayWidth::from(2), - contents: vec![ - Fixed(91).paint("N"), - Fixed(92).paint("M"), - ].into(), + contents: vec![Fixed(91).paint("N"), Fixed(92).paint("M")].into(), }; assert_eq!(expected, stati.render(&TestColours)) diff --git a/src/output/render/groups.rs b/src/output/render/groups.rs index e32b122d..73cc3e33 100644 --- a/src/output/render/groups.rs +++ b/src/output/render/groups.rs @@ -1,16 +1,26 @@ use ansiterm::Style; -use uzers::{Users, Groups}; +use uzers::{Groups, Users}; use crate::fs::fields as f; use crate::output::cell::TextCell; use crate::output::table::UserFormat; -pub trait Render{ - fn render(self, colours: &C, users: &U, format: UserFormat) -> TextCell; +pub trait Render { + fn render( + self, + colours: &C, + users: &U, + format: UserFormat, + ) -> TextCell; } impl Render for Option { - fn render(self, colours: &C, users: &U, format: UserFormat) -> TextCell { + fn render( + self, + colours: &C, + users: &U, + format: UserFormat, + ) -> TextCell { use uzers::os::unix::GroupExt; let mut style = colours.not_yours(); @@ -18,17 +28,15 @@ impl Render for Option { let group = match self { Some(g) => match users.get_group_by_gid(g.0) { Some(g) => (*g).clone(), - None => return TextCell::paint(style, g.0.to_string()), + None => return TextCell::paint(style, g.0.to_string()), }, None => return TextCell::blank(colours.no_group()), }; - let current_uid = users.get_current_uid(); if let Some(current_user) = users.get_user_by_uid(current_uid) { - 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(); } @@ -43,14 +51,12 @@ impl Render for Option { } } - pub trait Colours { fn yours(&self) -> Style; fn not_yours(&self) -> Style; fn no_group(&self) -> Style; } - #[cfg(test)] #[allow(unused_results)] pub mod test { @@ -59,22 +65,21 @@ pub mod test { use crate::output::cell::TextCell; use crate::output::table::UserFormat; - use uzers::{User, Group}; - use uzers::mock::MockUsers; - use uzers::os::unix::GroupExt; use ansiterm::Colour::*; use ansiterm::Style; - + use uzers::mock::MockUsers; + use uzers::os::unix::GroupExt; + use uzers::{Group, User}; struct TestColours; + #[rustfmt::skip] impl Colours for TestColours { fn yours(&self) -> Style { Fixed(80).normal() } fn not_yours(&self) -> Style { Fixed(81).normal() } fn no_group(&self) -> Style { Black.italic() } } - #[test] fn named() { let mut users = MockUsers::with_current_uid(1000); @@ -82,21 +87,32 @@ pub mod test { let group = Some(f::Group(100)); let expected = TextCell::paint_str(Fixed(81).normal(), "folk"); - assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name)); + assert_eq!( + expected, + group.render(&TestColours, &users, UserFormat::Name) + ); let expected = TextCell::paint_str(Fixed(81).normal(), "100"); - assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Numeric)); + assert_eq!( + expected, + group.render(&TestColours, &users, UserFormat::Numeric) + ); } - #[test] fn unnamed() { let users = MockUsers::with_current_uid(1000); let group = Some(f::Group(100)); let expected = TextCell::paint_str(Fixed(81).normal(), "100"); - assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name)); - assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Numeric)); + assert_eq!( + expected, + group.render(&TestColours, &users, UserFormat::Name) + ); + assert_eq!( + expected, + group.render(&TestColours, &users, UserFormat::Numeric) + ); } #[test] @@ -107,7 +123,10 @@ pub mod test { let group = Some(f::Group(100)); let expected = TextCell::paint_str(Fixed(80).normal(), "folk"); - assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name)) + assert_eq!( + expected, + group.render(&TestColours, &users, UserFormat::Name) + ) } #[test] @@ -120,13 +139,23 @@ pub mod test { let group = Some(f::Group(100)); let expected = TextCell::paint_str(Fixed(80).normal(), "folk"); - assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name)) + assert_eq!( + expected, + group.render(&TestColours, &users, UserFormat::Name) + ) } #[test] fn overflow() { let group = Some(f::Group(2_147_483_648)); let expected = TextCell::paint_str(Fixed(81).normal(), "2147483648"); - assert_eq!(expected, group.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric)); + assert_eq!( + expected, + group.render( + &TestColours, + &MockUsers::with_current_uid(0), + UserFormat::Numeric + ) + ); } } diff --git a/src/output/render/inode.rs b/src/output/render/inode.rs index e3c7a7a4..57bea8b3 100644 --- a/src/output/render/inode.rs +++ b/src/output/render/inode.rs @@ -3,22 +3,19 @@ use ansiterm::Style; use crate::fs::fields as f; use crate::output::cell::TextCell; - impl f::Inode { pub fn render(self, style: Style) -> TextCell { TextCell::paint(style, self.0.to_string()) } } - #[cfg(test)] pub mod test { - use crate::output::cell::TextCell; use crate::fs::fields as f; + use crate::output::cell::TextCell; use ansiterm::Colour::*; - #[test] fn blocklessness() { let io = f::Inode(1_414_213); diff --git a/src/output/render/links.rs b/src/output/render/links.rs index 6e8e748a..a1b07c5f 100644 --- a/src/output/render/links.rs +++ b/src/output/render/links.rs @@ -10,87 +10,99 @@ use crate::output::cell::TextCell; #[cfg(unix)] impl f::Links { pub fn render(&self, colours: &C, numeric: &NumericLocale) -> TextCell { - let style = if self.multiple { colours.multi_link_file() } - else { colours.normal() }; + let style = if self.multiple { + colours.multi_link_file() + } else { + colours.normal() + }; TextCell::paint(style, numeric.format_int(self.count)) } } - pub trait Colours { fn normal(&self) -> Style; fn multi_link_file(&self) -> Style; } - #[cfg(test)] pub mod test { use super::Colours; - #[cfg(unix)] - use crate::output::cell::{TextCell, DisplayWidth}; #[cfg(unix)] use crate::fs::fields as f; + #[cfg(unix)] + use crate::output::cell::{DisplayWidth, TextCell}; use ansiterm::Colour::*; use ansiterm::Style; #[cfg(unix)] use locale; - struct TestColours; impl Colours for TestColours { - fn normal(&self) -> Style { Blue.normal() } - fn multi_link_file(&self) -> Style { Blue.on(Red) } + fn normal(&self) -> Style { + Blue.normal() + } + fn multi_link_file(&self) -> Style { + Blue.on(Red) + } } - #[test] #[cfg(unix)] fn regular_file() { let stati = f::Links { - count: 1, + count: 1, multiple: false, }; let expected = TextCell { width: DisplayWidth::from(1), - contents: vec![ Blue.paint("1") ].into(), + contents: vec![Blue.paint("1")].into(), }; - assert_eq!(expected, stati.render(&TestColours, &locale::Numeric::english())); + assert_eq!( + expected, + stati.render(&TestColours, &locale::Numeric::english()) + ); } #[test] #[cfg(unix)] fn regular_directory() { let stati = f::Links { - count: 3005, + count: 3005, multiple: false, }; let expected = TextCell { width: DisplayWidth::from(5), - contents: vec![ Blue.paint("3,005") ].into(), + contents: vec![Blue.paint("3,005")].into(), }; - assert_eq!(expected, stati.render(&TestColours, &locale::Numeric::english())); + assert_eq!( + expected, + stati.render(&TestColours, &locale::Numeric::english()) + ); } #[test] #[cfg(unix)] fn popular_file() { let stati = f::Links { - count: 3005, + count: 3005, multiple: true, }; let expected = TextCell { width: DisplayWidth::from(5), - contents: vec![ Blue.on(Red).paint("3,005") ].into(), + contents: vec![Blue.on(Red).paint("3,005")].into(), }; - assert_eq!(expected, stati.render(&TestColours, &locale::Numeric::english())); + assert_eq!( + expected, + stati.render(&TestColours, &locale::Numeric::english()) + ); } } diff --git a/src/output/render/octal.rs b/src/output/render/octal.rs index 17f56d8b..50534917 100644 --- a/src/output/render/octal.rs +++ b/src/output/render/octal.rs @@ -9,6 +9,7 @@ pub trait Render { impl Render for Option { fn render(&self, style: Style) -> TextCell { + #[rustfmt::skip] match self { Some(p) => { let perm = &p.permissions; @@ -30,25 +31,32 @@ impl f::OctalPermissions { } } - #[cfg(test)] pub mod test { use super::Render; - use crate::output::cell::TextCell; use crate::fs::fields as f; + use crate::output::cell::TextCell; use ansiterm::Colour::*; - #[test] fn normal_folder() { let bits = f::Permissions { - user_read: true, user_write: true, user_execute: true, setuid: false, - group_read: true, group_write: false, group_execute: true, setgid: false, - other_read: true, other_write: false, other_execute: true, sticky: false, + user_read: true, + user_write: true, + user_execute: true, + setuid: false, + group_read: true, + group_write: false, + group_execute: true, + setgid: false, + other_read: true, + other_write: false, + other_execute: true, + sticky: false, }; - let octal = Some(f::OctalPermissions{ permissions: bits }); + let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "0755"); assert_eq!(expected, octal.render(Purple.bold())); @@ -57,12 +65,21 @@ pub mod test { #[test] fn normal_file() { let bits = f::Permissions { - user_read: true, user_write: true, user_execute: false, setuid: false, - group_read: true, group_write: false, group_execute: false, setgid: false, - other_read: true, other_write: false, other_execute: false, sticky: false, + user_read: true, + user_write: true, + user_execute: false, + setuid: false, + group_read: true, + group_write: false, + group_execute: false, + setgid: false, + other_read: true, + other_write: false, + other_execute: false, + sticky: false, }; - let octal = Some(f::OctalPermissions{ permissions: bits }); + let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "0644"); assert_eq!(expected, octal.render(Purple.bold())); @@ -71,12 +88,21 @@ pub mod test { #[test] fn secret_file() { let bits = f::Permissions { - user_read: true, user_write: true, user_execute: false, setuid: false, - group_read: false, group_write: false, group_execute: false, setgid: false, - other_read: false, other_write: false, other_execute: false, sticky: false, + user_read: true, + user_write: true, + user_execute: false, + setuid: false, + group_read: false, + group_write: false, + group_execute: false, + setgid: false, + other_read: false, + other_write: false, + other_execute: false, + sticky: false, }; - let octal = Some(f::OctalPermissions{ permissions: bits }); + let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "0600"); assert_eq!(expected, octal.render(Purple.bold())); @@ -85,27 +111,44 @@ pub mod test { #[test] fn sticky1() { let bits = f::Permissions { - user_read: true, user_write: true, user_execute: true, setuid: true, - group_read: true, group_write: true, group_execute: true, setgid: false, - other_read: true, other_write: true, other_execute: true, sticky: false, + user_read: true, + user_write: true, + user_execute: true, + setuid: true, + group_read: true, + group_write: true, + group_execute: true, + setgid: false, + other_read: true, + other_write: true, + other_execute: true, + sticky: false, }; - let octal = Some(f::OctalPermissions{ permissions: bits }); + let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "4777"); assert_eq!(expected, octal.render(Purple.bold())); - } #[test] fn sticky2() { let bits = f::Permissions { - user_read: true, user_write: true, user_execute: true, setuid: false, - group_read: true, group_write: true, group_execute: true, setgid: true, - other_read: true, other_write: true, other_execute: true, sticky: false, + user_read: true, + user_write: true, + user_execute: true, + setuid: false, + group_read: true, + group_write: true, + group_execute: true, + setgid: true, + other_read: true, + other_write: true, + other_execute: true, + sticky: false, }; - let octal = Some(f::OctalPermissions{ permissions: bits }); + let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "2777"); assert_eq!(expected, octal.render(Purple.bold())); @@ -114,12 +157,21 @@ pub mod test { #[test] fn sticky3() { let bits = f::Permissions { - user_read: true, user_write: true, user_execute: true, setuid: false, - group_read: true, group_write: true, group_execute: true, setgid: false, - other_read: true, other_write: true, other_execute: true, sticky: true, + user_read: true, + user_write: true, + user_execute: true, + setuid: false, + group_read: true, + group_write: true, + group_execute: true, + setgid: false, + other_read: true, + other_write: true, + other_execute: true, + sticky: true, }; - let octal = Some(f::OctalPermissions{ permissions: bits }); + let octal = Some(f::OctalPermissions { permissions: bits }); let expected = TextCell::paint_str(Purple.bold(), "1777"); assert_eq!(expected, octal.render(Purple.bold())); diff --git a/src/output/render/permissions.rs b/src/output/render/permissions.rs index b6c0ec02..5ce25d64 100644 --- a/src/output/render/permissions.rs +++ b/src/output/render/permissions.rs @@ -3,38 +3,38 @@ use std::iter; use ansiterm::{ANSIString, Style}; use crate::fs::fields as f; -use crate::output::cell::{TextCell, DisplayWidth}; +use crate::output::cell::{DisplayWidth, TextCell}; use crate::output::render::FiletypeColours; pub trait PermissionsPlusRender { - fn render(&self, colours: &C) -> TextCell; + fn render(&self, colours: &C) -> TextCell; } impl PermissionsPlusRender for Option { #[cfg(unix)] - fn render(&self, colours: &C) -> TextCell { + fn render(&self, colours: &C) -> TextCell { match self { Some(p) => { - let mut chars = vec![ p.file_type.render(colours) ]; + let mut chars = vec![p.file_type.render(colours)]; let permissions = p.permissions; chars.extend(Some(permissions).render(colours, p.file_type.is_regular_file())); if p.xattrs { - chars.push(colours.attribute().paint("@")); + chars.push(colours.attribute().paint("@")); } // As these are all ASCII characters, we can guarantee that they’re // all going to be one character wide, and don’t need to compute the // cell’s display width. TextCell { - width: DisplayWidth::from(chars.len()), + width: DisplayWidth::from(chars.len()), contents: chars.into(), } - }, + } None => { let chars: Vec<_> = iter::repeat(colours.dash().paint("-")).take(10).collect(); TextCell { - width: DisplayWidth::from(chars.len()), + width: DisplayWidth::from(chars.len()), contents: chars.into(), } } @@ -42,23 +42,21 @@ impl PermissionsPlusRender for Option { } #[cfg(windows)] - fn render(&self, colours: &C) -> TextCell { + fn render(&self, colours: &C) -> TextCell { match self { Some(p) => { - let mut chars = vec![ p.attributes.render_type(colours) ]; + let mut chars = vec![p.attributes.render_type(colours)]; chars.extend(p.attributes.render(colours)); TextCell { - width: DisplayWidth::from(chars.len()), + width: DisplayWidth::from(chars.len()), contents: chars.into(), - } - }, - None => { - TextCell { - width: DisplayWidth::from(0), - contents: vec![].into(), - } + } } + None => TextCell { + width: DisplayWidth::from(0), + contents: vec![].into(), + }, } } } @@ -72,10 +70,14 @@ impl RenderPermissions for Option { match self { Some(p) => { 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("-") + } }; + #[rustfmt::skip] vec![ bit(p.user_read, "r", colours.user_read()), bit(p.user_write, "w", colours.user_write()), @@ -87,16 +89,19 @@ impl RenderPermissions for Option { bit(p.other_write, "w", colours.other_write()), p.other_execute_bit(colours) ] - }, - None => { - iter::repeat(colours.dash().paint("-")).take(9).collect() } + None => iter::repeat(colours.dash().paint("-")).take(9).collect(), } } } impl f::Permissions { - fn user_execute_bit(&self, colours: &C, is_regular_file: bool) -> ANSIString<'static> { + fn user_execute_bit( + &self, + colours: &C, + is_regular_file: bool, + ) -> ANSIString<'static> { + #[rustfmt::skip] match (self.user_execute, self.setuid, is_regular_file) { (false, false, _) => colours.dash().paint("-"), (true, false, false) => colours.user_execute_other().paint("x"), @@ -108,6 +113,7 @@ impl f::Permissions { } fn group_execute_bit(&self, colours: &C) -> ANSIString<'static> { + #[rustfmt::skip] match (self.group_execute, self.setgid) { (false, false) => colours.dash().paint("-"), (true, false) => colours.group_execute().paint("x"), @@ -117,6 +123,7 @@ impl f::Permissions { } fn other_execute_bit(&self, colours: &C) -> ANSIString<'static> { + #[rustfmt::skip] match (self.other_execute, self.sticky) { (false, false) => colours.dash().paint("-"), (true, false) => colours.other_execute().paint("x"), @@ -128,12 +135,16 @@ impl f::Permissions { #[cfg(windows)] impl f::Attributes { - pub fn render(self, colours: &C) -> Vec> { + pub fn render(self, colours: &C) -> Vec> { 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("-") + } }; + #[rustfmt::skip] vec![ bit(self.archive, "a", colours.normal()), bit(self.readonly, "r", colours.user_read()), @@ -142,12 +153,11 @@ impl f::Attributes { ] } - pub fn render_type(self, colours: &C) -> ANSIString<'static> { + pub fn render_type(self, colours: &C) -> ANSIString<'static> { if self.reparse_point { - return colours.pipe().paint("l") - } - else if self.directory { - return colours.directory().paint("d") + return colours.pipe().paint("l"); + } else if self.directory { + return colours.directory().paint("d"); } colours.dash().paint("-") } @@ -175,20 +185,19 @@ pub trait Colours { fn attribute(&self) -> Style; } - #[cfg(test)] #[allow(unused_results)] pub mod test { use super::{Colours, RenderPermissions}; - use crate::output::cell::TextCellContents; use crate::fs::fields as f; + use crate::output::cell::TextCellContents; use ansiterm::Colour::*; use ansiterm::Style; - struct TestColours; + #[rustfmt::skip] impl Colours for TestColours { fn dash(&self) -> Style { Fixed(11).normal() } fn user_read(&self) -> Style { Fixed(101).normal() } @@ -206,73 +215,129 @@ pub mod test { fn attribute(&self) -> Style { Fixed(112).normal() } } - #[test] fn negate() { let bits = Some(f::Permissions { - user_read: false, user_write: false, user_execute: false, setuid: false, - group_read: false, group_write: false, group_execute: false, setgid: false, - other_read: false, other_write: false, other_execute: false, sticky: false, + user_read: false, + user_write: false, + user_execute: false, + setuid: false, + group_read: false, + group_write: false, + group_execute: false, + setgid: false, + other_read: false, + other_write: false, + other_execute: false, + sticky: false, }); let expected = TextCellContents::from(vec![ - Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), - Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), - Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), ]); assert_eq!(expected, bits.render(&TestColours, false).into()) } - #[test] fn affirm() { let bits = Some(f::Permissions { - user_read: true, user_write: true, user_execute: true, setuid: false, - group_read: true, group_write: true, group_execute: true, setgid: false, - other_read: true, other_write: true, other_execute: true, sticky: false, + user_read: true, + user_write: true, + user_execute: true, + setuid: false, + group_read: true, + group_write: true, + group_execute: true, + setgid: false, + other_read: true, + other_write: true, + other_execute: true, + sticky: false, }); let expected = TextCellContents::from(vec![ - Fixed(101).paint("r"), Fixed(102).paint("w"), Fixed(103).paint("x"), - Fixed(104).paint("r"), Fixed(105).paint("w"), Fixed(106).paint("x"), - Fixed(107).paint("r"), Fixed(108).paint("w"), Fixed(109).paint("x"), + Fixed(101).paint("r"), + Fixed(102).paint("w"), + Fixed(103).paint("x"), + Fixed(104).paint("r"), + Fixed(105).paint("w"), + Fixed(106).paint("x"), + Fixed(107).paint("r"), + Fixed(108).paint("w"), + Fixed(109).paint("x"), ]); assert_eq!(expected, bits.render(&TestColours, true).into()) } - #[test] fn specials() { let bits = Some(f::Permissions { - user_read: false, user_write: false, user_execute: true, setuid: true, - group_read: false, group_write: false, group_execute: true, setgid: true, - other_read: false, other_write: false, other_execute: true, sticky: true, + user_read: false, + user_write: false, + user_execute: true, + setuid: true, + group_read: false, + group_write: false, + group_execute: true, + setgid: true, + other_read: false, + other_write: false, + other_execute: true, + sticky: true, }); let expected = TextCellContents::from(vec![ - Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(110).paint("s"), - Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("s"), - Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("t"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(110).paint("s"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(111).paint("s"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(111).paint("t"), ]); assert_eq!(expected, bits.render(&TestColours, true).into()) } - #[test] fn extra_specials() { let bits = Some(f::Permissions { - user_read: false, user_write: false, user_execute: false, setuid: true, - group_read: false, group_write: false, group_execute: false, setgid: true, - other_read: false, other_write: false, other_execute: false, sticky: true, + user_read: false, + user_write: false, + user_execute: false, + setuid: true, + group_read: false, + group_write: false, + group_execute: false, + setgid: true, + other_read: false, + other_write: false, + other_execute: false, + sticky: true, }); let expected = TextCellContents::from(vec![ - Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("S"), - Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("S"), - Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("T"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(111).paint("S"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(111).paint("S"), + Fixed(11).paint("-"), + Fixed(11).paint("-"), + Fixed(111).paint("T"), ]); assert_eq!(expected, bits.render(&TestColours, true).into()) diff --git a/src/output/render/securityctx.rs b/src/output/render/securityctx.rs index 3427a7e5..df729c3c 100644 --- a/src/output/render/securityctx.rs +++ b/src/output/render/securityctx.rs @@ -1,15 +1,12 @@ use ansiterm::Style; use crate::fs::fields as f; -use crate::output::cell::{TextCell, DisplayWidth}; - +use crate::output::cell::{DisplayWidth, TextCell}; impl f::SecurityContext<'_> { pub fn render(&self, colours: &C) -> TextCell { match &self.context { - f::SecurityContextType::None => { - TextCell::paint_str(colours.none(), "?") - } + f::SecurityContextType::None => TextCell::paint_str(colours.none(), "?"), f::SecurityContextType::SELinux(context) => { let mut chars = Vec::with_capacity(7); @@ -18,7 +15,7 @@ impl f::SecurityContext<'_> { 0 => colours.selinux_user(), 1 => colours.selinux_role(), 2 => colours.selinux_type(), - _ => colours.selinux_range() + _ => colours.selinux_range(), }; if i > 0 { chars.push(colours.selinux_colon().paint(":")); @@ -28,15 +25,16 @@ impl f::SecurityContext<'_> { TextCell { contents: chars.into(), - width: DisplayWidth::from(context.len()) + width: DisplayWidth::from(context.len()), } } } } } +#[rustfmt::skip] pub trait Colours { - fn none(&self) -> Style; + fn none(&self) -> Style; fn selinux_colon(&self) -> Style; fn selinux_user(&self) -> Style; fn selinux_role(&self) -> Style; diff --git a/src/output/render/size.rs b/src/output/render/size.rs index 538dbcd0..6a223dc4 100644 --- a/src/output/render/size.rs +++ b/src/output/render/size.rs @@ -3,20 +3,25 @@ use locale::Numeric as NumericLocale; use number_prefix::Prefix; use crate::fs::fields as f; -use crate::output::cell::{TextCell, DisplayWidth}; +use crate::output::cell::{DisplayWidth, TextCell}; use crate::output::table::SizeFormat; - impl f::Size { - pub fn render(self, colours: &C, size_format: SizeFormat, numerics: &NumericLocale) -> TextCell { + pub fn render( + self, + colours: &C, + size_format: SizeFormat, + numerics: &NumericLocale, + ) -> TextCell { use number_prefix::NumberPrefix; let size = match self { - Self::Some(s) => s, - Self::None => return TextCell::blank(colours.no_size()), - Self::DeviceIDs(ref ids) => return ids.render(colours), + Self::Some(s) => s, + Self::None => return TextCell::blank(colours.no_size()), + Self::DeviceIDs(ref ids) => return ids.render(colours), }; + #[rustfmt::skip] let result = match size_format { SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64), SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64), @@ -35,6 +40,7 @@ impl f::Size { } }; + #[rustfmt::skip] let (prefix, n) = match result { NumberPrefix::Standalone(b) => return TextCell::paint(colours.size(None), numerics.format_int(b)), NumberPrefix::Prefixed(p, n) => (p, n), @@ -53,12 +59,12 @@ impl f::Size { contents: vec![ colours.size(Some(prefix)).paint(number), colours.unit(Some(prefix)).paint(symbol), - ].into(), + ] + .into(), } } } - impl f::DeviceIDs { fn render(self, colours: &C) -> TextCell { let major = self.major.to_string(); @@ -70,12 +76,12 @@ impl f::DeviceIDs { colours.major().paint(major), colours.comma().paint(","), colours.minor().paint(minor), - ].into(), + ] + .into(), } } } - pub trait Colours { fn size(&self, prefix: Option) -> Style; fn unit(&self, prefix: Option) -> Style; @@ -86,97 +92,122 @@ pub trait Colours { fn minor(&self) -> Style; } - #[cfg(test)] pub mod test { use super::Colours; - use crate::output::cell::{TextCell, DisplayWidth}; - use crate::output::table::SizeFormat; use crate::fs::fields as f; + use crate::output::cell::{DisplayWidth, TextCell}; + use crate::output::table::SizeFormat; - use locale::Numeric as NumericLocale; use ansiterm::Colour::*; use ansiterm::Style; + use locale::Numeric as NumericLocale; use number_prefix::Prefix; - struct TestColours; + #[rustfmt::skip] impl Colours for TestColours { fn size(&self, _prefix: Option) -> Style { Fixed(66).normal() } fn unit(&self, _prefix: Option) -> Style { Fixed(77).bold() } - fn no_size(&self) -> Style { Black.italic() } + fn no_size(&self) -> Style { Black.italic() } fn major(&self) -> Style { Blue.on(Red) } fn comma(&self) -> Style { Green.italic() } fn minor(&self) -> Style { Cyan.on(Yellow) } } - #[test] fn directory() { let directory = f::Size::None; let expected = TextCell::blank(Black.italic()); - assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english())) + assert_eq!( + expected, + directory.render( + &TestColours, + SizeFormat::JustBytes, + &NumericLocale::english() + ) + ) } - #[test] fn file_decimal() { let directory = f::Size::Some(2_100_000); let expected = TextCell { width: DisplayWidth::from(4), - contents: vec![ - Fixed(66).paint("2.1"), - Fixed(77).bold().paint("M"), - ].into(), + contents: vec![Fixed(66).paint("2.1"), Fixed(77).bold().paint("M")].into(), }; - assert_eq!(expected, directory.render(&TestColours, SizeFormat::DecimalBytes, &NumericLocale::english())) + assert_eq!( + expected, + directory.render( + &TestColours, + SizeFormat::DecimalBytes, + &NumericLocale::english() + ) + ) } - #[test] fn file_binary() { let directory = f::Size::Some(1_048_576); let expected = TextCell { width: DisplayWidth::from(5), - contents: vec![ - Fixed(66).paint("1.0"), - Fixed(77).bold().paint("Mi"), - ].into(), + contents: vec![Fixed(66).paint("1.0"), Fixed(77).bold().paint("Mi")].into(), }; - assert_eq!(expected, directory.render(&TestColours, SizeFormat::BinaryBytes, &NumericLocale::english())) + assert_eq!( + expected, + directory.render( + &TestColours, + SizeFormat::BinaryBytes, + &NumericLocale::english() + ) + ) } - #[test] fn file_bytes() { let directory = f::Size::Some(1_048_576); let expected = TextCell { width: DisplayWidth::from(9), - contents: vec![ - Fixed(66).paint("1,048,576"), - ].into(), + contents: vec![Fixed(66).paint("1,048,576")].into(), }; - assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english())) + assert_eq!( + expected, + directory.render( + &TestColours, + SizeFormat::JustBytes, + &NumericLocale::english() + ) + ) } - #[test] fn device_ids() { - let directory = f::Size::DeviceIDs(f::DeviceIDs { major: 10, minor: 80 }); + let directory = f::Size::DeviceIDs(f::DeviceIDs { + major: 10, + minor: 80, + }); let expected = TextCell { width: DisplayWidth::from(5), contents: vec![ Blue.on(Red).paint("10"), Green.italic().paint(","), Cyan.on(Yellow).paint("80"), - ].into(), + ] + .into(), }; - assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english())) + assert_eq!( + expected, + directory.render( + &TestColours, + SizeFormat::JustBytes, + &NumericLocale::english() + ) + ) } } diff --git a/src/output/render/times.rs b/src/output/render/times.rs index c4b0090c..92480d90 100644 --- a/src/output/render/times.rs +++ b/src/output/render/times.rs @@ -4,7 +4,6 @@ use crate::output::time::TimeFormat; use ansiterm::Style; use chrono::prelude::*; - pub trait Render { fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell; } @@ -12,7 +11,10 @@ pub trait Render { impl Render for Option { fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell { let datestamp = if let Some(time) = self { - time_format.format(&DateTime::::from_naive_utc_and_offset(time, time_offset)) + time_format.format(&DateTime::::from_naive_utc_and_offset( + time, + time_offset, + )) } else { String::from("-") }; diff --git a/src/output/render/users.rs b/src/output/render/users.rs index 46356d7f..bf5f19ca 100644 --- a/src/output/render/users.rs +++ b/src/output/render/users.rs @@ -11,30 +11,33 @@ pub trait Render { impl Render for Option { fn render(self, colours: &C, users: &U, format: UserFormat) -> TextCell { + #[rustfmt::skip] let uid = match self { Some(u) => u.0, None => return TextCell::blank(colours.no_user()), }; + #[rustfmt::skip] let user_name = match (format, users.get_user_by_uid(uid)) { (_, None) => uid.to_string(), (UserFormat::Numeric, _) => uid.to_string(), (UserFormat::Name, Some(user)) => user.name().to_string_lossy().into(), }; - let style = if users.get_current_uid() == uid { colours.you() } - else { colours.someone_else() }; + let style = if users.get_current_uid() == uid { + colours.you() + } else { + colours.someone_else() + }; TextCell::paint(style, user_name) } } - pub trait Colours { fn you(&self) -> Style; fn someone_else(&self) -> Style; fn no_user(&self) -> Style; } - #[cfg(test)] #[allow(unused_results)] pub mod test { @@ -43,21 +46,20 @@ pub mod test { use crate::output::cell::TextCell; use crate::output::table::UserFormat; - use uzers::User; - use uzers::mock::MockUsers; use ansiterm::Colour::*; use ansiterm::Style; - + use uzers::mock::MockUsers; + use uzers::User; struct TestColours; + #[rustfmt::skip] impl Colours for TestColours { fn you(&self) -> Style { Red.bold() } fn someone_else(&self) -> Style { Blue.underline() } fn no_user(&self) -> Style { Black.italic() } } - #[test] fn named() { let mut users = MockUsers::with_current_uid(1000); @@ -65,9 +67,11 @@ pub mod test { let user = Some(f::User(1000)); let expected = TextCell::paint_str(Red.bold(), "enoch"); + #[rustfmt::skip] assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name)); let expected = TextCell::paint_str(Red.bold(), "1000"); + #[rustfmt::skip] assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric)); } @@ -77,7 +81,9 @@ pub mod test { let user = Some(f::User(1000)); let expected = TextCell::paint_str(Red.bold(), "1000"); + #[rustfmt::skip] assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name)); + #[rustfmt::skip] assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric)); } @@ -88,20 +94,37 @@ pub mod test { let user = Some(f::User(1000)); let expected = TextCell::paint_str(Blue.underline(), "enoch"); - assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name)); + assert_eq!( + expected, + user.render(&TestColours, &users, UserFormat::Name) + ); } #[test] fn different_unnamed() { let user = Some(f::User(1000)); let expected = TextCell::paint_str(Blue.underline(), "1000"); - assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric)); + assert_eq!( + expected, + user.render( + &TestColours, + &MockUsers::with_current_uid(0), + UserFormat::Numeric + ) + ); } #[test] fn overflow() { let user = Some(f::User(2_147_483_648)); let expected = TextCell::paint_str(Blue.underline(), "2147483648"); - assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric)); + assert_eq!( + expected, + user.render( + &TestColours, + &MockUsers::with_current_uid(0), + UserFormat::Numeric + ) + ); } } diff --git a/src/output/table.rs b/src/output/table.rs index c6bac924..dd5226fc 100644 --- a/src/output/table.rs +++ b/src/output/table.rs @@ -10,22 +10,15 @@ use log::*; #[cfg(unix)] use uzers::UsersCache; -use crate::fs::{File, fields as f}; use crate::fs::feature::git::GitCache; +use crate::fs::{fields as f, File}; use crate::output::cell::TextCell; -use crate::output::render::{PermissionsPlusRender, TimeRender}; #[cfg(unix)] -use crate::output::render::{ - GroupRender, - OctalPermissionsRender, - UserRender -}; +use crate::output::render::{GroupRender, OctalPermissionsRender, UserRender}; +use crate::output::render::{PermissionsPlusRender, TimeRender}; use crate::output::time::TimeFormat; use crate::theme::Theme; - - - /// Options for displaying a table. #[derive(PartialEq, Eq, Debug)] pub struct Options { @@ -39,7 +32,6 @@ pub struct Options { #[allow(clippy::struct_excessive_bools)] #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub struct Columns { - /// At least one of these timestamps will be shown. pub time_types: TimeTypes, @@ -139,7 +131,6 @@ impl Columns { } } - /// A table contains these. #[derive(Debug, Copy, Clone)] pub enum Column { @@ -173,24 +164,25 @@ pub enum Alignment { } impl Column { - /// Get the alignment this column should use. #[cfg(unix)] pub fn alignment(self) -> Alignment { #[allow(clippy::wildcard_in_or_patterns)] + #[rustfmt::skip] match self { Self::FileSize | Self::HardLinks | Self::Inode | Self::Blocksize | Self::GitStatus => Alignment::Right, - Self::Timestamp(_) | + Self::Timestamp(_) | _ => Alignment::Left, } } #[cfg(windows)] pub fn alignment(self) -> Alignment { + #[rustfmt::skip] match self { Self::FileSize | Self::GitStatus => Alignment::Right, @@ -201,6 +193,7 @@ impl Column { /// Get the text that should be printed at the top, when the user elects /// to have a header row printed. pub fn header(self) -> &'static str { + #[rustfmt::skip] match self { #[cfg(unix)] Self::Permissions => "Permissions", @@ -228,12 +221,10 @@ impl Column { } } - /// Formatting options for file sizes. #[allow(clippy::enum_variant_names)] #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum SizeFormat { - /// Format the file size using **decimal** prefixes, such as “kilo”, /// “mega”, or “giga”. DecimalBytes, @@ -261,12 +252,10 @@ impl Default for SizeFormat { } } - /// The types of a file’s time fields. These three fields are standard /// across most (all?) operating systems. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum TimeType { - /// The file’s modified time (`st_mtime`). Modified, @@ -281,9 +270,9 @@ pub enum TimeType { } impl TimeType { - /// Returns the text to use for a column’s heading in the columns output. pub fn header(self) -> &'static str { + #[rustfmt::skip] match self { Self::Modified => "Date Modified", Self::Changed => "Date Changed", @@ -293,13 +282,13 @@ impl TimeType { } } - /// Fields for which of a file’s time fields should be displayed in the /// columns output. /// /// There should always be at least one of these — there’s no way to disable /// the time columns entirely (yet). #[derive(PartialEq, Eq, Debug, Copy, Clone)] +#[rustfmt::skip] #[allow(clippy::struct_excessive_bools)] pub struct TimeTypes { pub modified: bool, @@ -309,10 +298,10 @@ pub struct TimeTypes { } impl Default for TimeTypes { - /// By default, display just the ‘modified’ time. This is the most /// common option, which is why it has this shorthand. fn default() -> Self { + #[rustfmt::skip] Self { modified: true, changed: false, @@ -322,13 +311,11 @@ impl Default for TimeTypes { } } - /// The **environment** struct contains any data that could change between /// running instances of exa, depending on the user’s computer’s configuration. /// /// Any environment field should be able to be mocked up for test runs. pub struct Environment { - /// The computer’s current time offset, determined from time zone. time_offset: FixedOffset, @@ -349,13 +336,18 @@ impl Environment { fn load_all() -> Self { let time_offset = *Local::now().offset(); - let numeric = locale::Numeric::load_user_locale() - .unwrap_or_else(|_| locale::Numeric::english()); + let numeric = + locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english()); #[cfg(unix)] let users = Mutex::new(UsersCache::new()); - Self { time_offset, numeric, #[cfg(unix)] users } + Self { + time_offset, + numeric, + #[cfg(unix)] + users, + } } } @@ -363,7 +355,6 @@ lazy_static! { static ref ENVIRONMENT: Environment = Environment::load_all(); } - pub struct Table<'a> { columns: Vec, theme: &'a Theme, @@ -405,17 +396,21 @@ impl<'a> Table<'a> { } pub fn header_row(&self) -> Row { - let cells = self.columns.iter() - .map(|c| TextCell::paint_str(self.theme.ui.header, c.header())) - .collect(); + let cells = self + .columns + .iter() + .map(|c| TextCell::paint_str(self.theme.ui.header, c.header())) + .collect(); Row { cells } } pub fn row_for_file(&self, file: &File<'_>, xattrs: bool) -> Row { - let cells = self.columns.iter() - .map(|c| self.display(file, *c, xattrs)) - .collect(); + let cells = self + .columns + .iter() + .map(|c| self.display(file, *c, xattrs)) + .collect(); Row { cells } } @@ -429,7 +424,7 @@ impl<'a> Table<'a> { file.permissions().map(|p| f::PermissionsPlus { file_type: file.type_char(), permissions: p, - xattrs + xattrs, }) } @@ -446,12 +441,12 @@ impl<'a> Table<'a> { #[cfg(unix)] fn octal_permissions(&self, file: &File<'_>) -> Option { - file.permissions().map(|p| f::OctalPermissions { - permissions: p, - }) + file.permissions() + .map(|p| f::OctalPermissions { permissions: p }) } fn display(&self, file: &File<'_>, column: Column, xattrs: bool) -> TextCell { + #[rustfmt::skip] match column { Column::Permissions => { self.permissions_plus(file, xattrs).render(self.theme) @@ -469,15 +464,18 @@ impl<'a> Table<'a> { } #[cfg(unix)] Column::Blocksize => { - file.blocksize().render(self.theme, self.size_format, &self.env.numeric) + file.blocksize() + .render(self.theme, self.size_format, &self.env.numeric) } #[cfg(unix)] Column::User => { - file.user().render(self.theme, &*self.env.lock_users(), self.user_format) + file.user() + .render(self.theme, &*self.env.lock_users(), self.user_format) } #[cfg(unix)] Column::Group => { - file.group().render(self.theme, &*self.env.lock_users(), self.user_format) + file.group() + .render(self.theme, &*self.env.lock_users(), self.user_format) } #[cfg(unix)] Column::SecurityContext => { @@ -517,10 +515,10 @@ impl<'a> Table<'a> { .unwrap_or_default() } - fn subdir_git_repo(&self, file: &File<'_>, status : bool) -> f::SubdirGitRepo { + fn subdir_git_repo(&self, file: &File<'_>, status: bool) -> f::SubdirGitRepo { debug!("Getting subdir repo status for path {:?}", file.path); - if file.is_directory(){ + if file.is_directory() { return f::SubdirGitRepo::from_path(&file.path, status); } f::SubdirGitRepo::default() @@ -529,9 +527,7 @@ impl<'a> Table<'a> { pub fn render(&self, row: Row) -> TextCell { let mut cell = TextCell::default(); - let iter = 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; @@ -554,7 +550,6 @@ impl<'a> Table<'a> { } } - pub struct TableWidths(Vec); impl Deref for TableWidths { diff --git a/src/output/time.rs b/src/output/time.rs index a3f177ad..76a35c02 100644 --- a/src/output/time.rs +++ b/src/output/time.rs @@ -1,12 +1,11 @@ //! Timestamp formatting. -use core::cmp::max; -use std::time::Duration; use chrono::prelude::*; +use core::cmp::max; use lazy_static::lazy_static; +use std::time::Duration; use unicode_width::UnicodeWidthStr; - /// Every timestamp in exa needs to be rendered by a **time format**. /// Formatting times is tricky, because how a timestamp is rendered can /// depend on one or more of the following: @@ -25,7 +24,6 @@ use unicode_width::UnicodeWidthStr; /// format string in an environment variable or something. Just these four. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum TimeFormat { - /// The **default format** uses the user’s locale to print month names, /// and specifies the timestamp down to the minute for recent times, and /// day for older times. @@ -51,6 +49,7 @@ pub enum TimeFormat { impl TimeFormat { pub fn format(self, time: &DateTime) -> String { + #[rustfmt::skip] match self { Self::DefaultFormat => default(time), Self::ISOFormat => iso(time), @@ -99,21 +98,19 @@ fn long(time: &DateTime) -> String { fn relative(time: &DateTime) -> String { timeago::Formatter::new() .ago("") - .convert( - Duration::from_secs( - max(0, Local::now().timestamp() - time.timestamp()) - // this .unwrap is safe since the call above can never result in a + .convert(Duration::from_secs( + max(0, Local::now().timestamp() - time.timestamp()) + // this .unwrap is safe since the call above can never result in a // value < 0 - .try_into().unwrap() - ) - ) + .try_into() + .unwrap(), + )) } fn full(time: &DateTime) -> String { time.format("%Y-%m-%d %H:%M:%S.%f %z").to_string() } - lazy_static! { static ref CURRENT_YEAR: i32 = Local::now().year(); @@ -147,25 +144,29 @@ mod test { #[test] fn short_month_width_hindi() { let max_month_width = 4; - assert_eq!(true, [ - "\u{091C}\u{0928}\u{0970}", // जन॰ - "\u{092B}\u{093C}\u{0930}\u{0970}", // फ़र॰ - "\u{092E}\u{093E}\u{0930}\u{094D}\u{091A}", // मार्च - "\u{0905}\u{092A}\u{094D}\u{0930}\u{0948}\u{0932}", // अप्रैल - "\u{092E}\u{0908}", // मई - "\u{091C}\u{0942}\u{0928}", // जून - "\u{091C}\u{0941}\u{0932}\u{0970}", // जुल॰ - "\u{0905}\u{0917}\u{0970}", // अग॰ - "\u{0938}\u{093F}\u{0924}\u{0970}", // सित॰ - "\u{0905}\u{0915}\u{094D}\u{0924}\u{0942}\u{0970}", // अक्तू॰ - "\u{0928}\u{0935}\u{0970}", // नव॰ - "\u{0926}\u{093F}\u{0938}\u{0970}", // दिस॰ - ].iter() + assert_eq!( + true, + [ + "\u{091C}\u{0928}\u{0970}", // जन॰ + "\u{092B}\u{093C}\u{0930}\u{0970}", // फ़र॰ + "\u{092E}\u{093E}\u{0930}\u{094D}\u{091A}", // मार्च + "\u{0905}\u{092A}\u{094D}\u{0930}\u{0948}\u{0932}", // अप्रैल + "\u{092E}\u{0908}", // मई + "\u{091C}\u{0942}\u{0928}", // जून + "\u{091C}\u{0941}\u{0932}\u{0970}", // जुल॰ + "\u{0905}\u{0917}\u{0970}", // अग॰ + "\u{0938}\u{093F}\u{0924}\u{0970}", // सित॰ + "\u{0905}\u{0915}\u{094D}\u{0924}\u{0942}\u{0970}", // अक्तू॰ + "\u{0928}\u{0935}\u{0970}", // नव॰ + "\u{0926}\u{093F}\u{0938}\u{0970}", // दिस॰ + ] + .iter() .map(|month| format!( "{: &'static str { + #[rustfmt::skip] match self { Self::Edge => "├──", Self::Line => "│ ", @@ -69,11 +67,9 @@ impl TreePart { } } - /// A **tree trunk** builds up arrays of tree parts over multiple depths. #[derive(Debug, Default)] pub struct TreeTrunk { - /// A stack tracks which tree characters should be printed. It’s /// necessary to maintain information about the previously-printed /// lines, as the output will change based on any previous entries. @@ -85,7 +81,6 @@ pub struct TreeTrunk { #[derive(Debug, Copy, Clone)] pub struct TreeParams { - /// How many directories deep into the tree structure this is. Directories /// on top have depth 0. depth: TreeDepth, @@ -98,7 +93,6 @@ pub struct TreeParams { pub struct TreeDepth(pub usize); impl TreeTrunk { - /// Calculates the tree parts for an entry at the given depth and /// last-ness. The depth is used to determine where in the stack the tree /// part should be inserted, and the last-ness is used to determine which @@ -107,19 +101,24 @@ impl TreeTrunk { /// This takes a `&mut self` because the results of each file are stored /// and used in future rows. pub fn new_row(&mut self, params: TreeParams) -> &[TreePart] { - // If this isn’t our first iteration, then update the tree parts thus // far to account for there being another row after it. 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 // part into it. 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); @@ -162,20 +161,24 @@ impl TreeDepth { /// Creates an iterator that, as well as yielding each value, yields a /// `TreeParams` with the current depth and last flag filled in. pub fn iterate_over(self, inner: I) -> Iter - where I: ExactSizeIterator + Iterator + where + I: ExactSizeIterator + Iterator, { - Iter { current_depth: self, inner } + Iter { + current_depth: self, + inner, + } } } - pub struct Iter { current_depth: TreeDepth, inner: I, } impl Iterator for Iter -where I: ExactSizeIterator + Iterator +where + I: ExactSizeIterator + Iterator, { type Item = (TreeParams, T); @@ -188,7 +191,6 @@ where I: ExactSizeIterator + Iterator } } - #[cfg(test)] mod trunk_test { use super::*; @@ -197,12 +199,14 @@ mod trunk_test { TreeParams::new(TreeDepth(depth), last) } + #[rustfmt::skip] #[test] fn empty_at_first() { let mut tt = TreeTrunk::default(); assert_eq!(tt.new_row(params(0, true)), &[ ]); } + #[rustfmt::skip] #[test] fn one_child() { let mut tt = TreeTrunk::default(); @@ -210,6 +214,7 @@ mod trunk_test { assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); } + #[rustfmt::skip] #[test] fn two_children() { let mut tt = TreeTrunk::default(); @@ -218,6 +223,7 @@ mod trunk_test { assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); } + #[rustfmt::skip] #[test] fn two_times_two_children() { let mut tt = TreeTrunk::default(); @@ -230,6 +236,7 @@ mod trunk_test { assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]); } + #[rustfmt::skip] #[test] fn two_times_two_nested_children() { let mut tt = TreeTrunk::default(); @@ -245,14 +252,13 @@ mod trunk_test { } } - #[cfg(test)] mod iter_test { use super::*; #[test] fn test_iteration() { - let foos = &[ "first", "middle", "last" ]; + let foos = &["first", "middle", "last"]; let mut iter = TreeDepth::root().iterate_over(foos.iter()); let next = iter.next().unwrap(); diff --git a/src/theme/default_theme.rs b/src/theme/default_theme.rs index 6d0f9946..ba963ac2 100644 --- a/src/theme/default_theme.rs +++ b/src/theme/default_theme.rs @@ -1,15 +1,15 @@ -use ansiterm::Style; use ansiterm::Colour::*; +use ansiterm::Style; -use crate::theme::ColourScale; use crate::theme::ui_styles::*; - +use crate::theme::ColourScale; impl UiStyles { pub fn default_theme(scale: ColourScale) -> Self { Self { colourful: true, + #[rustfmt::skip] filekinds: FileKinds { normal: Style::default(), directory: Blue.bold(), @@ -23,6 +23,7 @@ impl UiStyles { mount_point: Blue.bold().underline(), }, + #[rustfmt::skip] perms: Permissions { user_read: Yellow.bold(), user_write: Red.bold(), @@ -45,6 +46,7 @@ impl UiStyles { size: Size::colourful(scale), + #[rustfmt::skip] users: Users { user_you: Yellow.bold(), user_someone_else: Style::default(), @@ -52,11 +54,13 @@ impl UiStyles { group_not_yours: Style::default(), }, + #[rustfmt::skip] links: Links { normal: Red.bold(), multi_link_file: Red.on(Yellow), }, + #[rustfmt::skip] git: Git { new: Green.normal(), modified: Blue.normal(), @@ -75,7 +79,8 @@ impl UiStyles { }, security_context: SecurityContext { - none: Style::default(), + none: Style::default(), + #[rustfmt::skip] selinux: SELinuxContext { colon: Style::default().dimmed(), user: Blue.normal(), @@ -85,47 +90,47 @@ impl UiStyles { }, }, + #[rustfmt::skip] file_type: FileType { - image: Purple.normal(), - video: Purple.bold(), - music: Cyan.normal(), - lossless: Cyan.bold(), - crypto: Green.bold(), - document: Green.normal(), + image: Purple.normal(), + video: Purple.bold(), + music: Cyan.normal(), + lossless: Cyan.bold(), + crypto: Green.bold(), + document: Green.normal(), compressed: Red.normal(), - temp: White.normal(), - compiled: Yellow.normal(), - build: Yellow.bold().underline() + temp: White.normal(), + compiled: Yellow.normal(), + build: Yellow.bold().underline(), }, - punctuation: DarkGray.bold(), - date: Blue.normal(), - inode: Purple.normal(), - blocks: Cyan.normal(), - octal: Purple.normal(), - header: Style::default().underline(), + punctuation: DarkGray.bold(), + date: Blue.normal(), + inode: Purple.normal(), + blocks: Cyan.normal(), + octal: Purple.normal(), + header: Style::default().underline(), - symlink_path: Cyan.normal(), - control_char: Red.normal(), - broken_symlink: Red.normal(), - broken_path_overlay: Style::default().underline(), + symlink_path: Cyan.normal(), + control_char: Red.normal(), + broken_symlink: Red.normal(), + broken_path_overlay: Style::default().underline(), } } } - impl Size { pub fn colourful(scale: ColourScale) -> Self { match scale { - ColourScale::Gradient => Self::colourful_gradient(), - ColourScale::Fixed => Self::colourful_fixed(), + ColourScale::Gradient => Self::colourful_gradient(), + ColourScale::Fixed => Self::colourful_fixed(), } } fn colourful_fixed() -> Self { Self { - major: Green.bold(), - minor: Green.normal(), + major: Green.bold(), + minor: Green.normal(), number_byte: Green.bold(), number_kilo: Green.bold(), @@ -143,8 +148,8 @@ impl Size { fn colourful_gradient() -> Self { Self { - major: Green.bold(), - minor: Green.normal(), + major: Green.bold(), + minor: Green.normal(), number_byte: Green.normal(), number_kilo: Green.bold(), diff --git a/src/theme/lsc.rs b/src/theme/lsc.rs index 7abdfa11..36af316f 100644 --- a/src/theme/lsc.rs +++ b/src/theme/lsc.rs @@ -1,9 +1,8 @@ use std::iter::Peekable; use std::ops::FnMut; -use ansiterm::{Colour, Style}; use ansiterm::Colour::*; - +use ansiterm::{Colour, Style}; // Parsing the LS_COLORS environment variable into a map of names to Style values. // @@ -26,23 +25,25 @@ pub struct LSColors<'var>(pub &'var str); impl<'var> LSColors<'var> { pub fn each_pair(&mut self, mut callback: C) - where C: FnMut(Pair<'var>) + where + C: FnMut(Pair<'var>), { for next in self.0.split(':') { - let bits = next.split('=') - .take(3) - .collect::>(); + let bits = next.split('=').take(3).collect::>(); - if bits.len() == 2 && ! bits[0].is_empty() && ! bits[1].is_empty() { - callback(Pair { key: bits[0], value: bits[1] }); + if bits.len() == 2 && !bits[0].is_empty() && !bits[1].is_empty() { + callback(Pair { + key: bits[0], + value: bits[1], + }); } } } } - fn parse_into_high_colour<'a, I>(iter: &mut Peekable) -> Option -where I: Iterator +where + I: Iterator, { match iter.peek() { Some(&"5") => { @@ -69,22 +70,22 @@ where I: Iterator } }*/ - if let (Some(r), Some(g), Some(b)) = (hexes.parse().ok(), - iter.next().and_then(|s| s.parse().ok()), - iter.next().and_then(|s| s.parse().ok())) - { + if let (Some(r), Some(g), Some(b)) = ( + hexes.parse().ok(), + iter.next().and_then(|s| s.parse().ok()), + iter.next().and_then(|s| s.parse().ok()), + ) { return Some(RGB(r, g, b)); } } } - _ => {}, + _ => {} } None } - pub struct Pair<'var> { pub key: &'var str, pub value: &'var str, @@ -97,7 +98,6 @@ impl<'var> Pair<'var> { while let Some(num) = iter.next() { match num.trim_start_matches('0') { - // Bold and italic "1" => style = style.bold(), "2" => style = style.dimmed(), @@ -127,7 +127,11 @@ impl<'var> Pair<'var> { "95" => style = style.fg(BrightPurple), "96" => style = style.fg(BrightCyan), "97" => style = style.fg(BrightGray), - "38" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.fg(c) }, + "38" => { + if let Some(c) = parse_into_high_colour(&mut iter) { + style = style.fg(c) + } + } // Background colours "40" => style = style.on(Black), @@ -147,8 +151,12 @@ impl<'var> Pair<'var> { "105" => style = style.on(BrightPurple), "106" => style = style.on(BrightCyan), "107" => style = style.on(BrightGray), - "48" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.on(c) }, - _ => {/* ignore the error and do nothing */}, + "48" => { + if let Some(c) = parse_into_high_colour(&mut iter) { + style = style.on(c) + } + } + _ => { /* ignore the error and do nothing */ } } } @@ -156,7 +164,6 @@ impl<'var> Pair<'var> { } } - #[cfg(test)] mod ansi_test { use super::*; @@ -166,7 +173,14 @@ mod ansi_test { ($name:ident: $input:expr => $result:expr) => { #[test] fn $name() { - assert_eq!(Pair { key: "", value: $input }.to_style(), $result); + assert_eq!( + Pair { + key: "", + value: $input + } + .to_style(), + $result + ); } }; } @@ -207,7 +221,6 @@ mod ansi_test { test!(toohi: "48;5;999" => Style::default()); } - #[cfg(test)] mod test { use super::*; @@ -217,7 +230,7 @@ mod test { #[test] fn $name() { let mut lscs = Vec::new(); - LSColors($input).each_pair(|p| lscs.push( (p.key.clone(), p.to_style()) )); + LSColors($input).each_pair(|p| lscs.push((p.key.clone(), p.to_style()))); assert_eq!(lscs, $result.to_vec()); } }; diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 81fa6f21..170c7c4c 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -13,10 +13,8 @@ pub use self::lsc::LSColors; mod default_theme; - #[derive(PartialEq, Eq, Debug)] pub struct Options { - pub use_colours: UseColours, pub colour_scale: ColourScale, @@ -33,7 +31,6 @@ pub struct Options { /// this check and only displays colours when they can be truly appreciated. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum UseColours { - /// Display them even when output isn’t going to a terminal. Always, @@ -56,17 +53,17 @@ pub struct Definitions { pub exa: Option, } - pub struct Theme { pub ui: UiStyles, pub exts: Box, } impl Options { - - #[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 to_theme(&self, isatty: bool) -> Theme { - if self.use_colours == UseColours::Never || (self.use_colours == UseColours::Automatic && ! isatty) { + if self.use_colours == UseColours::Never + || (self.use_colours == UseColours::Automatic && !isatty) + { let ui = UiStyles::plain(); let exts = Box::new(NoFileStyle); return Theme { ui, exts }; @@ -77,6 +74,7 @@ impl Options { let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui); // Use between 0 and 2 file name highlighters + #[rustfmt::skip] let exts = match (exts.is_non_empty(), use_default_filetypes) { (false, false) => Box::new(NoFileStyle) as Box<_>, (false, true) => Box::new(FileTypes) as Box<_>, @@ -89,7 +87,6 @@ impl Options { } impl Definitions { - /// Parse the environment variables into `LS_COLORS` pairs, putting file glob /// colours into the `ExtensionMappings` that gets returned, and using the /// two-character UI codes to modify the mutable `Colours`. @@ -103,7 +100,7 @@ impl Definitions { if let Some(lsc) = &self.ls { LSColors(lsc).each_pair(|pair| { - if ! colours.set_ls(&pair) { + if !colours.set_ls(&pair) { match glob::Pattern::new(pair.key) { Ok(pat) => { exts.add(pat, pair.to_style()); @@ -125,7 +122,7 @@ impl Definitions { } LSColors(exa).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) { Ok(pat) => { exts.add(pat, pair.to_style()); @@ -142,7 +139,6 @@ impl Definitions { } } - /// Determine the style to paint the text for the filename part of the output. pub trait FileStyle: Sync { /// Return the style to paint the filename text for `file` from the given @@ -164,16 +160,17 @@ impl FileStyle for NoFileStyle { // file type associations, while falling back to the default set if not set // explicitly. impl FileStyle for (A, B) -where A: FileStyle, - B: FileStyle, +where + A: FileStyle, + B: FileStyle, { fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option