From 1b5b2b08a4e3d6b538a46a4661983f1d623e6acb Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Tue, 10 Oct 2023 12:16:59 -0700 Subject: [PATCH 01/28] error handling --- Cargo.lock | 19 +- Cargo.toml | 3 +- example/.erdtreerc | 9 - scripts/performance_metrics.sh | 64 --- src/ansi.rs | 64 --- src/cli/mod.rs | 41 ++ src/context/args.rs | 129 ----- src/context/color.rs | 25 - src/context/column.rs | 40 -- src/context/config/mod.rs | 22 - src/context/config/rc.rs | 108 ---- src/context/config/toml/error.rs | 19 - src/context/config/toml/mod.rs | 251 ---------- src/context/config/toml/test.rs | 91 ---- src/context/dir.rs | 15 - src/context/error.rs | 37 -- src/context/file.rs | 15 - src/context/layout.rs | 18 - src/context/mod.rs | 500 ------------------- src/context/sort.rs | 41 -- src/context/time.rs | 35 -- src/disk_usage/file_size/block.rs | 22 - src/disk_usage/file_size/byte.rs | 207 -------- src/disk_usage/file_size/line_count.rs | 45 -- src/disk_usage/file_size/mod.rs | 110 ---- src/disk_usage/file_size/word_count.rs | 47 -- src/disk_usage/mod.rs | 6 - src/disk_usage/units.rs | 140 ------ src/error.rs | 111 ++++ src/file/mod.rs | 7 + src/fs/inode.rs | 51 -- src/fs/mod.rs | 28 -- src/fs/permissions/class.rs | 151 ------ src/fs/permissions/error.rs | 7 - src/fs/permissions/file_type.rs | 55 -- src/fs/permissions/mod.rs | 137 ----- src/fs/permissions/test.rs | 96 ---- src/fs/ug.rs | 84 ---- src/fs/xattr.rs | 38 -- src/icons/fs.rs | 95 ---- src/icons/mod.rs | 321 ------------ src/main.rs | 121 +---- src/progress.rs | 222 -------- src/render/grid/cell.rs | 410 --------------- src/render/grid/mod.rs | 146 ------ src/render/layout/flat.rs | 44 -- src/render/layout/flat_inverted.rs | 44 -- src/render/layout/inverted.rs | 92 ---- src/render/layout/mod.rs | 11 - src/render/layout/regular.rs | 103 ---- src/render/long/mod.rs | 138 ----- src/render/mod.rs | 58 --- src/render/theme.rs | 110 ---- src/styles/error.rs | 5 - src/styles/mod.rs | 279 ----------- src/tree/count.rs | 119 ----- src/tree/error.rs | 43 -- src/tree/mod.rs | 416 +-------------- src/tree/node/cmp.rs | 149 ------ src/tree/node/mod.rs | 292 ----------- src/tree/node/unix.rs | 45 -- src/tree/traversal/mod.rs | 2 + src/tree/traversal/multi_threaded.rs | 1 + src/tree/traversal/single_threaded.rs | 9 + src/tree/visitor.rs | 62 --- src/tty.rs | 16 - src/utils.rs | 29 -- tests/data/.dagon | 1 - tests/data/dream_cycle/polaris.txt | 10 - tests/data/lipsum/lipsum.txt | 1 - tests/data/necronomicon.txt | 2 - tests/data/nemesis.txt | 4 - tests/data/nylarlathotep.txt | 1 - tests/data/the_yellow_king/cassildas_song.md | 6 - tests/dirs_only.rs | 18 - tests/flat.rs | 63 --- tests/glob.rs | 121 ----- tests/hardlink.rs | 38 -- tests/hardlinks/kadath.txt | 1 - tests/level.rs | 22 - tests/line_count.rs | 24 - tests/prune.rs | 22 - tests/regex.rs | 69 --- tests/sort.rs | 86 ---- tests/suppress_size.rs | 25 - tests/symlink.rs | 39 -- tests/utils/mod.rs | 41 -- 87 files changed, 200 insertions(+), 6664 deletions(-) delete mode 100644 example/.erdtreerc delete mode 100755 scripts/performance_metrics.sh delete mode 100644 src/ansi.rs create mode 100644 src/cli/mod.rs delete mode 100644 src/context/args.rs delete mode 100644 src/context/color.rs delete mode 100644 src/context/column.rs delete mode 100644 src/context/config/mod.rs delete mode 100644 src/context/config/rc.rs delete mode 100644 src/context/config/toml/error.rs delete mode 100644 src/context/config/toml/mod.rs delete mode 100644 src/context/config/toml/test.rs delete mode 100644 src/context/dir.rs delete mode 100644 src/context/error.rs delete mode 100644 src/context/file.rs delete mode 100644 src/context/layout.rs delete mode 100644 src/context/mod.rs delete mode 100644 src/context/sort.rs delete mode 100644 src/context/time.rs delete mode 100644 src/disk_usage/file_size/block.rs delete mode 100644 src/disk_usage/file_size/byte.rs delete mode 100644 src/disk_usage/file_size/line_count.rs delete mode 100644 src/disk_usage/file_size/mod.rs delete mode 100644 src/disk_usage/file_size/word_count.rs delete mode 100644 src/disk_usage/mod.rs delete mode 100644 src/disk_usage/units.rs create mode 100644 src/error.rs create mode 100644 src/file/mod.rs delete mode 100644 src/fs/inode.rs delete mode 100644 src/fs/mod.rs delete mode 100644 src/fs/permissions/class.rs delete mode 100644 src/fs/permissions/error.rs delete mode 100644 src/fs/permissions/file_type.rs delete mode 100644 src/fs/permissions/mod.rs delete mode 100644 src/fs/permissions/test.rs delete mode 100644 src/fs/ug.rs delete mode 100644 src/fs/xattr.rs delete mode 100644 src/icons/fs.rs delete mode 100644 src/icons/mod.rs delete mode 100644 src/progress.rs delete mode 100644 src/render/grid/cell.rs delete mode 100644 src/render/grid/mod.rs delete mode 100644 src/render/layout/flat.rs delete mode 100644 src/render/layout/flat_inverted.rs delete mode 100644 src/render/layout/inverted.rs delete mode 100644 src/render/layout/mod.rs delete mode 100644 src/render/layout/regular.rs delete mode 100644 src/render/long/mod.rs delete mode 100644 src/render/mod.rs delete mode 100644 src/render/theme.rs delete mode 100644 src/styles/error.rs delete mode 100644 src/styles/mod.rs delete mode 100644 src/tree/count.rs delete mode 100644 src/tree/error.rs delete mode 100644 src/tree/node/cmp.rs delete mode 100644 src/tree/node/mod.rs delete mode 100644 src/tree/node/unix.rs create mode 100644 src/tree/traversal/mod.rs create mode 100644 src/tree/traversal/multi_threaded.rs create mode 100644 src/tree/traversal/single_threaded.rs delete mode 100644 src/tree/visitor.rs delete mode 100644 src/tty.rs delete mode 100644 src/utils.rs delete mode 100644 tests/data/.dagon delete mode 100644 tests/data/dream_cycle/polaris.txt delete mode 100644 tests/data/lipsum/lipsum.txt delete mode 100644 tests/data/necronomicon.txt delete mode 100644 tests/data/nemesis.txt delete mode 100644 tests/data/nylarlathotep.txt delete mode 100644 tests/data/the_yellow_king/cassildas_song.md delete mode 100644 tests/dirs_only.rs delete mode 100644 tests/flat.rs delete mode 100644 tests/glob.rs delete mode 100644 tests/hardlink.rs delete mode 100644 tests/hardlinks/kadath.txt delete mode 100644 tests/level.rs delete mode 100644 tests/line_count.rs delete mode 100644 tests/prune.rs delete mode 100644 tests/regex.rs delete mode 100644 tests/sort.rs delete mode 100644 tests/suppress_size.rs delete mode 100644 tests/symlink.rs delete mode 100644 tests/utils/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 9e60f9fb..ccc6de6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "arrayvec" version = "0.5.2" @@ -43,7 +49,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.10", + "syn 2.0.12", ] [[package]] @@ -236,7 +242,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.10", + "syn 2.0.12", ] [[package]] @@ -253,7 +259,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.10", + "syn 2.0.12", ] [[package]] @@ -281,6 +287,7 @@ name = "erdtree" version = "3.1.2" dependencies = [ "ansi_term", + "anyhow", "chrono", "clap", "clap_complete", @@ -878,9 +885,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.10" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" dependencies = [ "proc-macro2", "quote", @@ -936,7 +943,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.10", + "syn 2.0.12", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dfd6d96e..74fe2b7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["tree", "find", "ls", "du", "commandline"] exclude = ["assets/*", "scripts/*", "example/*"] readme = "README.md" license = "MIT" -rust-version = "1.70.0" +rust-version = "1.73.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -25,6 +25,7 @@ path = "src/main.rs" [dependencies] ansi_term = "0.12.1" +anyhow = "1.0.75" chrono = "0.4.24" clap = { version = "4.1.1", features = ["derive"] } clap_complete = "4.1.1" diff --git a/example/.erdtreerc b/example/.erdtreerc deleted file mode 100644 index 184b15ef..00000000 --- a/example/.erdtreerc +++ /dev/null @@ -1,9 +0,0 @@ -# Long argument ---icons ---human - -# or short argument --l - -# args can be passed like this --d logical diff --git a/scripts/performance_metrics.sh b/scripts/performance_metrics.sh deleted file mode 100755 index 9d23bd7c..00000000 --- a/scripts/performance_metrics.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash - -if [[ ! "$OSTYPE" =~ "darwin" ]]; then - printf "Error: Script requires a darwin operating system.\n" - exit 1 -fi - -if [[ $(/usr/bin/id -u) != 0 ]]; then - printf "Error: Script requires root privilege.\n" - exit 1 -fi - -printf "This script will purge your disk cache. Continue? [y/n]: " - -read -r proceed - -if [[ "$proceed" != "y" ]]; then - echo "Aborted." - exit 0 -fi - -cargo build --release - -# Clear disk cache -purge - -fifo="$TMPDIR" -fifo+="erd_performance" - -if [[ -f "$fifo" ]]; then - rm "$fifo" -fi - -exec 3<>$fifo - -trap "rm -f $fifo" SIGINT - -iostat_output= -while read -r line; do - iostat_output+="$line\n" - - read -r -u3 -t1 finished - - if [[ "$finished" == "1" ]]; then - printf "$iostat_output" - rm "$fifo" - exit 0 - fi -done < <(iostat -w1) & - -iostat_job="$!" - -trap "kill $iostat_job 2> /dev/null" SIGINT - -echo "Executing command: target/release/erd ${@}" -echo - -/usr/bin/time -p target/release/erd "$@" 1> /dev/null - -echo - -echo "1" >> "$fifo" - -wait "$iostat_job" diff --git a/src/ansi.rs b/src/ansi.rs deleted file mode 100644 index a5682e3c..00000000 --- a/src/ansi.rs +++ /dev/null @@ -1,64 +0,0 @@ -/// Trait that provides functionality to ANSI escaped strings to be truncated in a manner that -/// preserves the ANSI color/style escape sequences. Consider the following: -/// -/// ``` -/// // "\u{1b}[1;31mHello World\u{1b}[0m" -/// ansi_term::Color::Red.bold().paint("Hello") -/// ``` -/// -/// Truncating the above to a length of 5 would result in: -/// -/// `"\u{1b}[1;31mHello\u{1b}[0m"` -/// -/// NOTE: This is being used for a very particular use-case and isn't comprehensive enough to -/// handle all types of ANSI escaped sequences, only color/style related ones. It also makes some -/// assumptions that are valid only for this program, namely that all relevant grapheme clusters -/// are at most sized to a single `char`, so truncating to any arbitrary length will always result -/// in a coherent output. -pub trait Escaped: AsRef { - fn truncate(&self, new_len: usize) -> String { - let mut open_sequence = false; - let mut resultant = String::new(); - let mut char_count = 0; - let mut chars = self.as_ref().chars(); - - 'outer: while let Some(ch) = chars.next() { - resultant.push(ch); - - if ch == '\u{1b}' { - for code in chars.by_ref() { - resultant.push(code); - - if code == 'm' { - open_sequence = !open_sequence; - continue 'outer; - } - } - } - char_count += 1; - - if char_count == new_len { - break; - } - } - - if open_sequence { - resultant.push_str("\u{1b}[0m"); - } - - resultant - } -} - -impl Escaped for str {} - -#[test] -fn truncate() { - use ansi_term::Color::Red; - - let control = Red.bold().paint("Hello").to_string(); - let base = format!("{}!!!", Red.bold().paint("Hello World")); - let trunc = ::truncate(&base, 5); - - assert_eq!(control, trunc); -} diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 00000000..8cfb8965 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,41 @@ +use crate::error::{self, ErrorReport, Result, WithContext}; +use clap::Parser; +use std::{env, path::PathBuf}; + +#[derive(Parser, Debug)] +#[command(name = "erdtree")] +#[command(author = "Benjamin Nguyen. ")] +#[command(version = "4.0.0")] +#[command( + about = "erdtree (erd) is a cross-platform, multi-threaded, and general purpose filesystem and disk usage utility.", + long_about = None, +)] +pub struct Args { + /// Directory to traverse; defaults to current working directory + dir: Option, +} + +impl Args { + pub fn init() -> Result { + let mut clargs = Self::parse(); + clargs.set_dir()?; + Ok(clargs) + } + + fn set_dir(&mut self) -> Result<()> { + let current_dir = env::current_dir().into_report(error::Category::System)?; + + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "This is the underlying error", + )) + .into_report(error::Category::User) + .context("Oh my god...") + .context("Look at her butt") + .context("omg..")?; + + //.into_report_ctx(error::Category::Internal, "hmmmm")?; + + Ok(()) + } +} diff --git a/src/context/args.rs b/src/context/args.rs deleted file mode 100644 index ce202b14..00000000 --- a/src/context/args.rs +++ /dev/null @@ -1,129 +0,0 @@ -use super::{config, error::Error, Context}; -use clap::{ - builder::ArgAction, parser::ValueSource, ArgMatches, Command, CommandFactory, FromArgMatches, -}; -use std::{ - ffi::{OsStr, OsString}, - path::PathBuf, -}; - -/// Allows the implementor to compute [`ArgMatches`] that reconciles arguments from both the -/// command-line as well as the config file that gets loaded. -pub trait Reconciler: CommandFactory + FromArgMatches { - /// Loads in arguments from both the command-line as well as the config file and reconciles - /// identical arguments between the two using these rules: - /// - /// 1. If no config file is present, use arguments strictly from the command-line. - /// 2. If an argument was provided via the CLI then override the argument from the config. - /// 3. If an argument is sourced from its default value because a user didn't provide it via - /// the CLI, then select the argument from the config if it exists. - fn compute_args() -> Result { - let cmd = Self::command().args_override_self(true); - - let user_args = Command::clone(&cmd).get_matches(); - - if user_args.get_one::("no_config").is_some_and(|b| *b) { - return Ok(user_args); - } - - let maybe_config_args = { - let named_table = user_args.get_one::("config"); - - if let Some(rc) = load_rc_config_args() { - if named_table.is_some() { - return Err(Error::Rc); - } - - Some(rc) - } else { - let toml = load_toml_config_args(named_table.map(String::as_str))?; - - if named_table.is_some() && toml.is_none() { - return Err(Error::NoToml); - } - - toml - } - }; - - let Some(config_args) = maybe_config_args else { - return Ok(user_args); - }; - - let mut final_args = init_empty_args(); - - for arg in cmd.get_arguments() { - let arg_id = arg.get_id(); - let id_str = arg_id.as_str(); - - if id_str == "dir" { - if let Some(dir) = user_args.try_get_one::(id_str)? { - final_args.push(OsString::from(dir)); - } - continue; - } - - let argument_source = user_args - .value_source(id_str) - .map_or(&config_args, |source| { - if source == ValueSource::CommandLine { - &user_args - } else { - &config_args - } - }); - - let Some(key) = arg.get_long().map(|l| format!("--{l}")).map(OsString::from) else { - continue - }; - - match arg.get_action() { - ArgAction::SetTrue => { - if argument_source - .try_get_one::(id_str)? - .is_some_and(|b| *b) - { - final_args.push(key); - }; - }, - ArgAction::SetFalse => continue, - _ => { - let Ok(Some(raw)) = argument_source.try_get_raw(id_str) else { - continue; - }; - final_args.push(key); - final_args.extend(raw.map(OsStr::to_os_string)); - }, - } - } - - Ok(cmd.get_matches_from(final_args)) - } -} - -impl Reconciler for Context {} - -/// Creates a properly formatted `Vec` that [`clap::Command`] would understand. -#[inline] -fn init_empty_args() -> Vec { - vec![OsString::from("--")] -} - -/// Loads an [`ArgMatches`] from `.erdtreerc`. -#[inline] -fn load_rc_config_args() -> Option { - config::rc::read_config_to_string().map(|rc_config| { - let parsed_args = config::rc::parse(&rc_config); - Context::command().get_matches_from(parsed_args) - }) -} - -/// Loads an [`ArgMatches`] from `.erdtree.toml`. -#[inline] -fn load_toml_config_args(named_table: Option<&str>) -> Result, Error> { - let toml_config = config::toml::load()?; - let parsed_args = config::toml::parse(toml_config, named_table)?; - let config_args = Context::command().get_matches_from(parsed_args); - - Ok(Some(config_args)) -} diff --git a/src/context/color.rs b/src/context/color.rs deleted file mode 100644 index caf39c73..00000000 --- a/src/context/color.rs +++ /dev/null @@ -1,25 +0,0 @@ -use clap::ValueEnum; -use once_cell::sync::OnceCell; -use std::{env, ffi::OsString}; - -pub static NO_COLOR: OnceCell> = OnceCell::new(); - -/// Reads in the `NO_COLOR` environment variable to determine whether or not to display color in -/// the output. -pub fn no_color_env() { - let _ = NO_COLOR.set(env::var_os("NO_COLOR")); -} - -/// Enum to determine how the output should be colorized. -#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, Default)] -pub enum Coloring { - /// Print plainly without ANSI escapes - None, - - /// Attempt to colorize output - #[default] - Auto, - - /// Turn on colorization always - Force, -} diff --git a/src/context/column.rs b/src/context/column.rs deleted file mode 100644 index c66daa40..00000000 --- a/src/context/column.rs +++ /dev/null @@ -1,40 +0,0 @@ -use super::{Context, PrefixKind}; -use std::convert::From; - -/// Utility struct to help store maximum column widths for attributes of each node. Each width is -/// measured as the number of columns of the tty's window. -#[derive(Default)] -pub struct Properties { - pub max_size_width: usize, - pub max_size_unit_width: usize, - - #[cfg(unix)] - pub max_nlink_width: usize, - - #[cfg(unix)] - pub max_ino_width: usize, - - #[cfg(unix)] - pub max_block_width: usize, - - #[cfg(unix)] - pub max_owner_width: usize, - - #[cfg(unix)] - pub max_group_width: usize, -} - -impl From<&Context> for Properties { - fn from(ctx: &Context) -> Self { - let unit_width = match ctx.unit { - PrefixKind::Bin if ctx.human => 3, - PrefixKind::Si if ctx.human => 2, - _ => 1, - }; - - Self { - max_size_unit_width: unit_width, - ..Default::default() - } - } -} diff --git a/src/context/config/mod.rs b/src/context/config/mod.rs deleted file mode 100644 index c28c3a7a..00000000 --- a/src/context/config/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -const ERDTREE_CONFIG_TOML: &str = ".erdtree.toml"; -const ERDTREE_TOML_PATH: &str = "ERDTREE_TOML_PATH"; - -const ERDTREE_CONFIG_NAME: &str = ".erdtreerc"; -const ERDTREE_CONFIG_PATH: &str = "ERDTREE_CONFIG_PATH"; - -const ERDTREE_DIR: &str = "erdtree"; - -#[cfg(unix)] -const CONFIG_DIR: &str = ".config"; - -#[cfg(unix)] -const HOME: &str = "HOME"; - -#[cfg(unix)] -const XDG_CONFIG_HOME: &str = "XDG_CONFIG_HOME"; - -/// Concerned with loading `.erdtreerc`. -pub mod rc; - -/// Concerned with loading `.erdtree.toml`. -pub mod toml; diff --git a/src/context/config/rc.rs b/src/context/config/rc.rs deleted file mode 100644 index 27192b75..00000000 --- a/src/context/config/rc.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::{env, fs, path::PathBuf}; - -/// Reads the config file into a `String` if there is one, otherwise returns `None`. -/// is looked for in the following locations in order: -/// -/// - `$ERDTREE_CONFIG_PATH` -/// - `$XDG_CONFIG_HOME/erdtree/.erdtreerc` -/// - `$XDG_CONFIG_HOME/.erdtreerc` -/// - `$HOME/.config/erdtree/.erdtreerc` -/// - `$HOME/.erdtreerc` -#[cfg(unix)] -pub fn read_config_to_string() -> Option { - config_from_config_path() - .or_else(config_from_xdg_path) - .or_else(config_from_home) - .map(|e| prepend_arg_prefix(&e)) -} -/// is looked for in the following locations in order (Windows specific): -/// -/// - `$ERDTREE_CONFIG_PATH` -/// - `%APPDATA%/erdtree/.erdtreerc` -#[cfg(windows)] -pub fn read_config_to_string() -> Option { - config_from_config_path() - .or_else(config_from_appdata) - .map(|e| prepend_arg_prefix(&e)) -} - -/// Parses the config `str`, removing comments and preparing it as a format understood by -/// [`get_matches_from`]. -/// -/// [`get_matches_from`]: clap::builder::Command::get_matches_from -pub fn parse<'a>(config: &'a str) -> Vec<&'a str> { - config - .lines() - .filter(|line| { - line.trim_start() - .chars() - .next() - .map_or(true, |ch| ch != '#') - }) - .flat_map(str::split_whitespace) - .collect::>() -} - -/// Try to read in config from `ERDTREE_CONFIG_PATH`. -fn config_from_config_path() -> Option { - env::var_os(super::ERDTREE_CONFIG_PATH) - .map(PathBuf::from) - .map(fs::read_to_string) - .and_then(Result::ok) -} - -/// Try to read in config from either one of the following locations: -/// - `$HOME/.config/erdtree/.erdtreerc` -/// - `$HOME/.erdtreerc` -#[cfg(not(windows))] -fn config_from_home() -> Option { - let home = env::var_os(super::HOME).map(PathBuf::from)?; - - let config_path = home - .join(super::CONFIG_DIR) - .join(super::ERDTREE_DIR) - .join(super::ERDTREE_CONFIG_NAME); - - fs::read_to_string(config_path).ok().or_else(|| { - let config_path = home.join(super::ERDTREE_CONFIG_NAME); - fs::read_to_string(config_path).ok() - }) -} - -/// Windows specific: Try to read in config from the following location: -/// - `%APPDATA%/erdtree/.erdtreerc` -#[cfg(windows)] -fn config_from_appdata() -> Option { - let app_data = dirs::config_dir()?; - - let config_path = app_data - .join(super::ERDTREE_DIR) - .join(super::ERDTREE_CONFIG_NAME); - - fs::read_to_string(config_path).ok() -} - -/// Try to read in config from either one of the following locations: -/// - `$XDG_CONFIG_HOME/erdtree/.erdtreerc` -/// - `$XDG_CONFIG_HOME/.erdtreerc` -#[cfg(unix)] -fn config_from_xdg_path() -> Option { - let xdg_config = env::var_os(super::XDG_CONFIG_HOME).map(PathBuf::from)?; - - let config_path = xdg_config - .join(super::ERDTREE_DIR) - .join(super::ERDTREE_CONFIG_NAME); - - fs::read_to_string(config_path).ok().or_else(|| { - let config_path = xdg_config.join(super::ERDTREE_CONFIG_NAME); - fs::read_to_string(config_path).ok() - }) -} - -/// Prepends "--\n" to the config string which is required for proper parsing by -/// [`get_matches_from`]. -/// -/// [`get_matches_from`]: clap::builder::Command::get_matches_from -fn prepend_arg_prefix(config: &str) -> String { - format!("--\n{config}") -} diff --git a/src/context/config/toml/error.rs b/src/context/config/toml/error.rs deleted file mode 100644 index 3757c98e..00000000 --- a/src/context/config/toml/error.rs +++ /dev/null @@ -1,19 +0,0 @@ -use config::ConfigError; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Failed to load .erdtree.toml")] - LoadConfig, - - #[error("The configuration file is improperly formatted: {0}")] - InvalidFormat(#[from] ConfigError), - - #[error("Named table '{0}' was not found in '.erdtree.toml'")] - MissingAltConfig(String), - - #[error("'#{0}' is required to be a pointer-sized unsigned integer type")] - InvalidInteger(String), - - #[error("'#{0}' has a type that is invalid")] - InvalidArgument(String), -} diff --git a/src/context/config/toml/mod.rs b/src/context/config/toml/mod.rs deleted file mode 100644 index 95b8b2f5..00000000 --- a/src/context/config/toml/mod.rs +++ /dev/null @@ -1,251 +0,0 @@ -use config::{Config, File, Value, ValueKind}; -use error::Error; -use std::{env, ffi::OsString}; - -/// Errors associated with loading and parsing the toml config file. -pub mod error; - -/// Testing related to `.erdtree.toml`. -pub mod test; - -/// Represents an instruction on how to handle a single key-value pair, which makes up a single -/// command-line argument, when constructing the arguments vector. -enum ArgInstructions { - /// Used for bool arguments such as `--icons`. When `icons = true` is set in `.erdtree.toml`, - /// we only want `--icons` to be pushed into the ultimate arguments vector. - PushKeyOnly, - - /// Used for arguments such as `--threads 10`. - PushKeyValue { parsed_value: OsString }, - - /// If a bool field is set to false in `.erdtree.toml` (e.g. `icons = false`) then we want to - /// completely omit the key-value pair from the arguments that we ultimately use. - Pass, -} - -/// Takes in a `Config` that is generated from [`load`] returning a `Vec` which -/// represents command-line arguments from `.erdtree.toml`. If a `named_table` is provided then -/// the top-level table in `.erdtree.toml` is ignored and the configurations specified in the -/// `named_table` will be used instead. -pub fn parse(config: Config, named_table: Option<&str>) -> Result, Error> { - let mut args_map = config.cache.into_table()?; - - if let Some(table) = named_table { - let new_conf = args_map - .get(table) - .and_then(|conf| conf.clone().into_table().ok()) - .ok_or_else(|| Error::MissingAltConfig(table.to_owned()))?; - - args_map = new_conf; - } else { - args_map.retain(|_k, v| !matches!(v.kind, ValueKind::Table(_))); - } - - let mut parsed_args = vec![OsString::from("--")]; - - let process_key = |s| OsString::from(format!("--{s}").replace('_', "-")); - - for (k, v) in &args_map { - match parse_argument(k, v)? { - ArgInstructions::PushKeyValue { parsed_value } => { - let fmt_key = process_key(k); - parsed_args.push(fmt_key); - parsed_args.push(parsed_value); - }, - - ArgInstructions::PushKeyOnly => { - let fmt_key = process_key(k); - parsed_args.push(fmt_key); - }, - - ArgInstructions::Pass => continue, - } - } - - Ok(parsed_args) -} - -/// Reads in `.erdtree.toml` file. -pub fn load() -> Result { - #[cfg(windows)] - return windows::load_toml(); - - #[cfg(unix)] - unix::load_toml() -} - -/// Attempts to load in `.erdtree.toml` from `$ERDTREE_TOML_PATH`. -fn toml_from_env() -> Result { - let config = env::var_os(super::ERDTREE_TOML_PATH) - .map(OsString::into_string) - .transpose() - .map_err(|_| Error::LoadConfig)? - .ok_or(Error::LoadConfig)?; - - let file = config - .strip_suffix(".toml") - .map(File::with_name) - .ok_or(Error::LoadConfig)?; - - Config::builder() - .add_source(file) - .build() - .map_err(Error::from) -} - -/// Simple utility used to extract the underlying value from the [`Value`] enum that we get when -/// loading in the values from `.erdtree.toml`, returning instructions on how the argument should -/// be processed into the ultimate arguments vector. -fn parse_argument(keyword: &str, arg: &Value) -> Result { - macro_rules! try_parse_num { - ($n:expr) => { - usize::try_from($n) - .map_err(|_e| Error::InvalidInteger(keyword.to_owned())) - .map(|num| { - let parsed = OsString::from(format!("{num}")); - ArgInstructions::PushKeyValue { - parsed_value: parsed, - } - }) - }; - } - - match &arg.kind { - ValueKind::Boolean(val) => { - if *val { - Ok(ArgInstructions::PushKeyOnly) - } else { - Ok(ArgInstructions::Pass) - } - }, - ValueKind::String(val) => Ok(ArgInstructions::PushKeyValue { - parsed_value: OsString::from(val), - }), - ValueKind::I64(val) => try_parse_num!(*val), - ValueKind::I128(val) => try_parse_num!(*val), - ValueKind::U64(val) => try_parse_num!(*val), - ValueKind::U128(val) => try_parse_num!(*val), - _ => Err(Error::InvalidArgument(keyword.to_owned())), - } -} - -/// Concerned with how to load `.erdtree.toml` on Unix systems. -#[cfg(unix)] -mod unix { - use super::super::{CONFIG_DIR, ERDTREE_CONFIG_TOML, ERDTREE_DIR, HOME, XDG_CONFIG_HOME}; - use super::Error; - use config::{Config, ConfigError, File}; - use std::{env, path::PathBuf}; - - /// Looks for `.erdtree.toml` in the following locations in order: - /// - /// - `$ERDTREE_TOML_PATH` - /// - `$XDG_CONFIG_HOME/erdtree/.erdtree.toml` - /// - `$XDG_CONFIG_HOME/.erdtree.toml` - /// - `$HOME/.config/erdtree/.erdtree.toml` - /// - `$HOME/.erdtree.toml` - pub(super) fn load_toml() -> Result { - super::toml_from_env() - .or_else(|_| toml_from_xdg_path()) - .or_else(|_| toml_from_home()) - } - - /// Looks for `.erdtree.toml` in the following locations in order: - /// - /// - `$XDG_CONFIG_HOME/erdtree/.erdtree.toml` - /// - `$XDG_CONFIG_HOME/.erdtree.toml` - fn toml_from_xdg_path() -> Result { - let config = env::var_os(XDG_CONFIG_HOME) - .map(PathBuf::from) - .ok_or(Error::LoadConfig)?; - - let mut file = config - .join(ERDTREE_DIR) - .join(ERDTREE_CONFIG_TOML) - .to_str() - .and_then(|s| s.strip_suffix(".toml")) - .map(File::with_name); - - if file.is_none() { - file = config - .join(ERDTREE_CONFIG_TOML) - .to_str() - .and_then(|s| s.strip_suffix(".toml")) - .map(File::with_name); - } - - file.map_or_else( - || Err(Error::LoadConfig), - |f| Config::builder().add_source(f).build().map_err(Error::from), - ) - } - - /// Looks for `.erdtree.toml` in the following locations in order: - /// - /// - `$HOME/.config/erdtree/.erdtree.toml` - /// - `$HOME/.erdtree.toml` - fn toml_from_home() -> Result { - let home = env::var_os(HOME) - .map(PathBuf::from) - .ok_or(Error::LoadConfig)?; - - let mut file = home - .join(CONFIG_DIR) - .join(ERDTREE_DIR) - .join(ERDTREE_CONFIG_TOML) - .to_str() - .and_then(|s| s.strip_suffix(".toml")) - .map(File::with_name); - - if file.is_none() { - file = home - .join(ERDTREE_CONFIG_TOML) - .to_str() - .and_then(|s| s.strip_suffix(".toml")) - .map(File::with_name); - } - - file.map_or_else( - || Err(Error::LoadConfig), - |f| Config::builder() - .add_source(f) - .build() - .map_err(|err| match err { - ConfigError::FileParse { .. } | ConfigError::Type { .. } => Error::from(err), - _ => Error::LoadConfig, - }), - ) - } -} - -/// Concerned with how to load `.erdtree.toml` on Windows. -#[cfg(windows)] -mod windows { - use super::super::{ERDTREE_CONFIG_TOML, ERDTREE_DIR}; - use super::Error; - use config::{Config, File}; - - /// Try to read in config from the following location: - /// - `%APPDATA%\erdtree\.erdtree.toml` - pub(super) fn load_toml() -> Result { - super::toml_from_env().or_else(toml_from_appdata) - } - - /// Try to read in config from the following location: - /// - `%APPDATA%\erdtree\.erdtree.toml` - fn toml_from_appdata() -> Result { - let app_data = dirs::config_dir()?; - - let file = app_data - .join(ERDTREE_DIR) - .join(ERDTREE_CONFIG_TOML) - .to_str() - .and_then(|s| s.strip_suffix(".toml")) - .map(File::with_name)?; - - Config::builder() - .add_source(file) - .build() - .map_err(Error::from) - } -} diff --git a/src/context/config/toml/test.rs b/src/context/config/toml/test.rs deleted file mode 100644 index 368c9db7..00000000 --- a/src/context/config/toml/test.rs +++ /dev/null @@ -1,91 +0,0 @@ -#[test] -fn parse_toml() -> Result<(), Box> { - use config::{Config, File}; - use std::{ffi::OsString, io::Write}; - use tempfile::Builder; - - let mut config_file = Builder::new() - .prefix(".erdtree") - .suffix(".toml") - .tempfile()?; - - let toml_contents = r#" - icons = true - human = true - threads = 10 - - [grogoroth] - disk_usage = "block" - icons = true - human = false - threads = 10 - "#; - - config_file.write_all(toml_contents.as_bytes())?; - - let file = config_file - .path() - .to_str() - .and_then(|s| s.strip_suffix(".toml")) - .map(File::with_name) - .unwrap(); - - let config = Config::builder().add_source(file).build()?; - - // TOP-LEVEL TABLE - let mut toml = super::parse(config.clone(), None)?; - - let expected = vec![ - OsString::from("--"), - OsString::from("--icons"), - OsString::from("--human"), - OsString::from("--threads"), - OsString::from("10"), - ]; - - for (i, outer_item) in expected.iter().enumerate() { - for j in 0..toml.len() { - let inner_item = &toml[j]; - - if outer_item == inner_item { - toml.swap(i, j); - } - } - } - - assert_eq!(toml.len(), expected.len()); - - for (lhs, rhs) in toml.iter().zip(expected.iter()) { - assert_eq!(lhs, rhs); - } - - // NAMED-TABLE - let mut toml = super::parse(config, Some("grogoroth"))?; - - let expected = vec![ - OsString::from("--"), - OsString::from("--disk-usage"), - OsString::from("block"), - OsString::from("--icons"), - OsString::from("--threads"), - OsString::from("10"), - ]; - - for (i, outer_item) in expected.iter().enumerate() { - for j in 0..toml.len() { - let inner_item = &toml[j]; - - if outer_item == inner_item { - toml.swap(i, j); - } - } - } - - assert_eq!(toml.len(), expected.len()); - - for (lhs, rhs) in toml.iter().zip(expected.iter()) { - assert_eq!(lhs, rhs); - } - - Ok(()) -} diff --git a/src/context/dir.rs b/src/context/dir.rs deleted file mode 100644 index 0a87dd32..00000000 --- a/src/context/dir.rs +++ /dev/null @@ -1,15 +0,0 @@ -use clap::ValueEnum; - -/// Enum to determine how directories should be ordered relative to regular files in output. -#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, Default)] -pub enum Order { - /// Directories are ordered as if they were regular nodes. - #[default] - None, - - /// Sort directories above files - First, - - /// Sort directories below files - Last, -} diff --git a/src/context/error.rs b/src/context/error.rs deleted file mode 100644 index 89fbb740..00000000 --- a/src/context/error.rs +++ /dev/null @@ -1,37 +0,0 @@ -use super::config::toml::error::Error as TomlError; -use clap::{parser::MatchesError, Error as ClapError}; -use ignore::Error as IgnoreError; -use regex::Error as RegexError; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("{0}")] - ArgParse(ClapError), - - #[error("A configuration file was found but failed to parse: {0}")] - Config(ClapError), - - #[error("No glob was provided")] - EmptyGlob, - - #[error("{0}")] - IgnoreError(#[from] IgnoreError), - - #[error("{0}")] - InvalidRegularExpression(#[from] RegexError), - - #[error("Missing '--pattern' argument")] - PatternNotProvided, - - #[error("{0}")] - ConfigError(#[from] TomlError), - - #[error("{0}")] - MatchError(#[from] MatchesError), - - #[error("'--config' was specified but a `.erdtree.toml` file could not be found")] - NoToml, - - #[error("Please migrate from `erdtreerc` to `.erdtree.toml` to make use of `--config`")] - Rc, -} diff --git a/src/context/file.rs b/src/context/file.rs deleted file mode 100644 index 01c29e3c..00000000 --- a/src/context/file.rs +++ /dev/null @@ -1,15 +0,0 @@ -use clap::ValueEnum; - -/// File-types found in both Unix and Windows. -#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)] -pub enum Type { - /// A regular file. - #[default] - File, - - /// A directory. - Dir, - - /// A symlink. - Link, -} diff --git a/src/context/layout.rs b/src/context/layout.rs deleted file mode 100644 index 7e88bcc2..00000000 --- a/src/context/layout.rs +++ /dev/null @@ -1,18 +0,0 @@ -use clap::ValueEnum; - -/// Which layout to use when rendering the tree. -#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)] -pub enum Type { - /// Outputs the tree with the root node at the bottom of the output - #[default] - Regular, - - /// Outputs the tree with the root node at the top of the output - Inverted, - - /// Outputs a flat layout using paths rather than an ASCII tree - Flat, - - /// Outputs an inverted flat layout with the root at the top of the output - Iflat, -} diff --git a/src/context/mod.rs b/src/context/mod.rs deleted file mode 100644 index fbcfd1e2..00000000 --- a/src/context/mod.rs +++ /dev/null @@ -1,500 +0,0 @@ -use super::disk_usage::{file_size::DiskUsage, units::PrefixKind}; - -use args::Reconciler; -use clap::{FromArgMatches, Parser}; -use color::Coloring; -use error::Error; -use ignore::{ - overrides::{Override, OverrideBuilder}, - DirEntry, -}; -use regex::Regex; -use std::{ - borrow::Borrow, - convert::From, - io::{stdin, stdout, IsTerminal}, - num::NonZeroUsize, - path::{Path, PathBuf}, - thread::available_parallelism, -}; - -/// Concerned with figuring out how to reconcile arguments provided via the command-line with -/// arguments that come from a config file. -pub mod args; - -/// Operations to load in defaults from configuration file. -pub mod config; - -/// Controlling color of output. -pub mod color; - -/// Controlling order of directories in output. -pub mod dir; - -/// [Context] related errors. -pub mod error; - -/// Common cross-platform file-types. -pub mod file; - -/// For determining the output layout. -pub mod layout; - -/// Utilities to print output. -pub mod column; - -/// Printing order kinds. -pub mod sort; - -/// Different types of timestamps available in long view. -#[cfg(unix)] -pub mod time; - -/// Defines the CLI. -#[derive(Parser, Debug)] -#[command(name = "erdtree")] -#[command(author = "Benjamin Nguyen. ")] -#[command(version = "3.1.2")] -#[command(about = "erdtree (erd) is a cross-platform, multi-threaded, and general purpose filesystem and disk usage utility.", long_about = None)] -pub struct Context { - /// Directory to traverse; defaults to current working directory - dir: Option, - - /// Use configuration of named table rather than the top-level table in .erdtree.toml - #[arg(short = 'c', long)] - pub config: Option, - - /// Mode of coloring output - #[arg(short = 'C', long, value_enum, default_value_t)] - pub color: Coloring, - - /// Print physical or logical file size - #[arg(short, long, value_enum, default_value_t)] - pub disk_usage: DiskUsage, - - /// Follow symlinks - #[arg(short = 'f', long)] - pub follow: bool, - - /// Print disk usage in human-readable format - #[arg(short = 'H', long)] - pub human: bool, - - /// Do not respect .gitignore files - #[arg(short = 'i', long)] - pub no_ignore: bool, - - /// Display file icons - #[arg(short = 'I', long)] - pub icons: bool, - - /// Show extended metadata and attributes - #[cfg(unix)] - #[arg(short, long)] - pub long: bool, - - /// Show file's groups - #[cfg(unix)] - #[arg(long)] - pub group: bool, - - /// Show each file's ino - #[cfg(unix)] - #[arg(long)] - pub ino: bool, - - /// Show the total number of hardlinks to the underlying inode - #[cfg(unix)] - #[arg(long)] - pub nlink: bool, - - /// Show permissions in numeric octal format instead of symbolic - #[cfg(unix)] - #[arg(long, requires = "long")] - pub octal: bool, - - /// Which kind of timestamp to use; modified by default - #[cfg(unix)] - #[arg(long, value_enum, requires = "long")] - pub time: Option, - - /// Which format to use for the timestamp; default by default - #[cfg(unix)] - #[arg(long = "time-format", value_enum, requires = "long")] - pub time_format: Option, - - /// Maximum depth to display - #[arg(short = 'L', long, value_name = "NUM")] - level: Option, - - /// Regular expression (or glob if '--glob' or '--iglob' is used) used to match files - #[arg(short, long)] - pub pattern: Option, - - /// Enables glob based searching - #[arg(group = "searching", long, requires = "pattern")] - pub glob: bool, - - /// Enables case-insensitive glob based searching - #[arg(group = "searching", long, requires = "pattern")] - pub iglob: bool, - - /// Restrict regex or glob search to a particular file-type - #[arg(short = 't', long, requires = "pattern", value_enum)] - pub file_type: Option, - - /// Remove empty directories from output - #[arg(short = 'P', long)] - pub prune: bool, - - /// How to sort entries - #[arg(short, long, value_enum, default_value_t)] - pub sort: sort::Type, - - /// Sort directories before or after all other file types - #[arg(long, value_enum, default_value_t)] - pub dir_order: dir::Order, - - /// Number of threads to use - #[arg(short = 'T', long, default_value_t = Context::num_threads())] - pub threads: usize, - - /// Report disk usage in binary or SI units - #[arg(short, long, value_enum, default_value_t)] - pub unit: PrefixKind, - - /// Prevent traversal into directories that are on different filesystems - #[arg(short = 'x', long = "one-file-system")] - pub same_fs: bool, - - /// Which kind of layout to use when rendering the output - #[arg(short = 'y', long, value_enum, default_value_t)] - pub layout: layout::Type, - - /// Show hidden files - #[arg(short = '.', long)] - pub hidden: bool, - - /// Disable traversal of .git directory when traversing hidden files - #[arg(long, requires = "hidden")] - pub no_git: bool, - - #[arg(long)] - /// Print completions for a given shell to stdout - pub completions: Option, - - /// Only print directories - #[arg(long)] - pub dirs_only: bool, - - /// Don't read configuration file - #[arg(long)] - pub no_config: bool, - - /// Hides the progress indicator - #[arg(long)] - pub no_progress: bool, - - /// Omit disk usage from output - #[arg(long)] - pub suppress_size: bool, - - /// Truncate output to fit terminal emulator window - #[arg(long)] - pub truncate: bool, - - ////////////////////////// - /* INTERNAL USAGE BELOW */ - ////////////////////////// - /// Is stdin in a tty? - #[clap(skip = stdin().is_terminal())] - pub stdin_is_tty: bool, - - /// Is stdin in a tty? - #[clap(skip = stdout().is_terminal())] - pub stdout_is_tty: bool, - - /// Restricts column width of size not including units - #[clap(skip = usize::default())] - pub max_size_width: usize, - - /// Restricts column width of disk_usage units - #[clap(skip = usize::default())] - pub max_size_unit_width: usize, - - /// Restricts column width of nlink for long view - #[clap(skip = usize::default())] - #[cfg(unix)] - pub max_nlink_width: usize, - - /// Restricts column width of ino for long view - #[clap(skip = usize::default())] - #[cfg(unix)] - pub max_ino_width: usize, - - /// Restricts column width of block for long view - #[clap(skip = usize::default())] - #[cfg(unix)] - pub max_block_width: usize, - - /// Restricts column width of file owner for long view - #[clap(skip = usize::default())] - #[cfg(unix)] - pub max_owner_width: usize, - - /// Restricts column width of file group for long view - #[clap(skip = usize::default())] - #[cfg(unix)] - pub max_group_width: usize, - - /// Width of the terminal emulator's window - #[clap(skip)] - pub window_width: Option, -} - -type Predicate = Result bool + Send + Sync + 'static>, Error>; - -impl Context { - /// Initializes [Context], optionally reading in the configuration file to override defaults. - /// Arguments provided will take precedence over config. - pub fn try_init() -> Result { - Self::compute_args().and_then(|args| { - color::no_color_env(); - Self::from_arg_matches(&args).map_err(Error::Config) - }) - } - - /// Determines whether or not it's appropriate to display color in output based on - /// the Coloring, and whether or not stdout is connected to a tty. - /// - /// If Coloring is Force then this will always evaluate to `false`. - pub fn no_color(&self) -> bool { - if let Some(Some(var)) = color::NO_COLOR.get() { - return !var.is_empty(); - } - - match self.color { - Coloring::Auto if !self.stdout_is_tty => true, - Coloring::None => true, - Coloring::Auto | Coloring::Force => false, - } - } - - /// Returns [Path] of the root directory to be traversed. - pub fn dir(&self) -> &Path { - self.dir - .as_ref() - .map_or_else(|| Path::new("."), |pb| pb.as_path()) - } - - /// Returns canonical [Path] of the root directory to be traversed. - pub fn dir_canonical(&self) -> PathBuf { - std::fs::canonicalize(self.dir()).unwrap_or_else(|_| self.dir().to_path_buf()) - } - - /// The max depth to print. Note that all directories are fully traversed to compute file - /// sizes; this just determines how much to print. - pub fn level(&self) -> usize { - self.level.unwrap_or(usize::MAX) - } - - /// Which timestamp type to use for long view; defaults to modified. - #[cfg(unix)] - pub fn time(&self) -> time::Stamp { - self.time.unwrap_or_default() - } - - /// Which format to use for the timestamp; default by default - #[cfg(unix)] - pub fn time_format(&self) -> time::Format { - self.time_format.unwrap_or_default() - } - - /// Which `FileType` to filter on; defaults to regular file. - pub fn file_type(&self) -> file::Type { - self.file_type.unwrap_or_default() - } - - /// Predicate used for filtering via regular expressions and file-type. When matching regular - /// files, directories will always be included since matched files will need to be bridged back - /// to the root node somehow. Empty sets not producing an output is handled by [`Tree`]. - /// - /// [`Tree`]: crate::tree::Tree - pub fn regex_predicate(&self) -> Predicate { - let Some(pattern) = self.pattern.as_ref() else { - return Err(Error::PatternNotProvided); - }; - - let re = Regex::new(pattern)?; - - let file_type = self.file_type(); - - Ok(match file_type { - file::Type::Dir => Box::new(move |dir_entry| { - let is_dir = dir_entry.file_type().map_or(false, |ft| ft.is_dir()); - if is_dir { - return Self::ancestor_regex_match(dir_entry.path(), &re, 0); - } - - Self::ancestor_regex_match(dir_entry.path(), &re, 1) - }), - - _ => Box::new(move |dir_entry| { - let entry_type = dir_entry.file_type(); - let is_dir = entry_type.map_or(false, |ft| ft.is_dir()); - - if is_dir { - return true; - } - - match file_type { - file::Type::File if entry_type.map_or(true, |ft| !ft.is_file()) => { - return false - }, - file::Type::Link if entry_type.map_or(true, |ft| !ft.is_symlink()) => { - return false - }, - _ => {}, - } - let file_name = dir_entry.file_name().to_string_lossy(); - re.is_match(&file_name) - }), - }) - } - - /// Predicate used for filtering via globs and file-types. - pub fn glob_predicate(&self) -> Predicate { - let mut builder = OverrideBuilder::new(self.dir()); - - let mut negated_glob = false; - - let overrides = { - if self.iglob { - builder.case_insensitive(true)?; - } - - if let Some(ref glob) = self.pattern { - let trim = glob.trim_start(); - negated_glob = trim.starts_with('!'); - - if negated_glob { - builder.add(trim.trim_start_matches('!'))?; - } else { - builder.add(trim)?; - } - } - - builder.build()? - }; - - let file_type = self.file_type(); - - match file_type { - file::Type::Dir => Ok(Box::new(move |dir_entry| { - let is_dir = dir_entry.file_type().map_or(false, |ft| ft.is_dir()); - - if is_dir { - if negated_glob { - return !Self::ancestor_glob_match(dir_entry.path(), &overrides, 0); - } - return Self::ancestor_glob_match(dir_entry.path(), &overrides, 0); - } - let matched = Self::ancestor_glob_match(dir_entry.path(), &overrides, 1); - - if negated_glob { - !matched - } else { - matched - } - })), - - _ => Ok(Box::new(move |dir_entry| { - let entry_type = dir_entry.file_type(); - let is_dir = entry_type.map_or(false, |ft| ft.is_dir()); - - if is_dir { - return true; - } - - match file_type { - file::Type::File if entry_type.map_or(true, |ft| !ft.is_file()) => { - return false - }, - file::Type::Link if entry_type.map_or(true, |ft| !ft.is_symlink()) => { - return false - }, - _ => {}, - } - - let matched = overrides.matched(dir_entry.path(), false); - - if negated_glob { - !matched.is_whitelist() - } else { - matched.is_whitelist() - } - })), - } - } - - /// Special override to toggle the visibility of the git directory. - pub fn no_git_override(&self) -> Result { - let mut builder = OverrideBuilder::new(self.dir()); - - if self.no_git { - builder.add("!.git")?; - } - - Ok(builder.build()?) - } - - /// Update column width properties. - pub fn update_column_properties(&mut self, col_props: &column::Properties) { - self.max_size_width = col_props.max_size_width; - self.max_size_unit_width = col_props.max_size_unit_width; - - #[cfg(unix)] - { - self.max_owner_width = col_props.max_owner_width; - self.max_group_width = col_props.max_group_width; - self.max_nlink_width = col_props.max_nlink_width; - self.max_block_width = col_props.max_block_width; - self.max_ino_width = col_props.max_ino_width; - } - } - - /// Setter for `window_width` which is set to the current terminal emulator's window width. - #[inline] - pub fn set_window_width(&mut self) { - self.window_width = crate::tty::get_window_width(); - } - - /// Answers whether disk usage is asked to be reported in bytes. - pub const fn byte_metric(&self) -> bool { - matches!(self.disk_usage, DiskUsage::Logical | DiskUsage::Physical) - } - - /// Do any of the components of a path match the provided glob? This is used for ensuring that - /// all children of a directory that a glob targets gets captured. - #[inline] - fn ancestor_glob_match(path: &Path, ovr: &Override, skip: usize) -> bool { - path.components() - .rev() - .skip(skip) - .any(|c| ovr.matched(c, false).is_whitelist()) - } - - /// Like [`Self::ancestor_glob_match`] except uses [Regex] rather than [Override]. - #[inline] - fn ancestor_regex_match(path: &Path, re: &Regex, skip: usize) -> bool { - path.components() - .rev() - .skip(skip) - .any(|comp| re.is_match(comp.as_os_str().to_string_lossy().borrow())) - } - - /// The default number of threads to use for disk-reads and parallel processing. - fn num_threads() -> usize { - available_parallelism().map(NonZeroUsize::get).unwrap_or(3) - } -} diff --git a/src/context/sort.rs b/src/context/sort.rs deleted file mode 100644 index b7d9043f..00000000 --- a/src/context/sort.rs +++ /dev/null @@ -1,41 +0,0 @@ -use clap::ValueEnum; - -/// Order in which to print nodes. -#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)] -pub enum Type { - /// Sort entries by file name in lexicographical order. - Name, - /// Sort entries by file name in reversed lexicographical order. - Rname, - - /// Sort entries by size smallest to largest, top to bottom - #[default] - Size, - - /// Sort entries by size largest to smallest, bottom to top - Rsize, - - /// Sort entries by newer to older Accessing Date - #[value(alias("atime"))] - Access, - - /// Sort entries by older to newer Accessing Date - #[value(alias("ratime"))] - Raccess, - - /// Sort entries by newer to older Creation Date - #[value(alias("ctime"))] - Create, - - /// Sort entries by older to newer Creation Date - #[value(alias("rctime"))] - Rcreate, - - /// Sort entries by newer to older Alteration Date - #[value(alias("mtime"))] - Mod, - - /// Sort entries by older to newer Alteration Date - #[value(alias("rmtime"))] - Rmod, -} diff --git a/src/context/time.rs b/src/context/time.rs deleted file mode 100644 index 88f9657c..00000000 --- a/src/context/time.rs +++ /dev/null @@ -1,35 +0,0 @@ -use clap::ValueEnum; - -/// Different types of timestamps available in long-view. -#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)] -pub enum Stamp { - /// Time created (alias: ctime) - #[value(alias("ctime"))] - Create, - - /// Time last accessed (alias: atime) - #[value(alias("atime"))] - Access, - - /// Time last modified (alias: mtime) - #[default] - #[value(alias("mtime"))] - Mod, -} - -/// Different formatting options for timestamps -#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)] -pub enum Format { - /// Timestamp formatted following the iso8601, with slight differences and the time-zone omitted - Iso, - - /// Timestamp formatted following the exact iso8601 specifications - IsoStrict, - - /// Timestamp only shows date without time in YYYY-MM-DD format - Short, - - /// Timestamp is shown in DD MMM HH:MM format - #[default] - Default, -} diff --git a/src/disk_usage/file_size/block.rs b/src/disk_usage/file_size/block.rs deleted file mode 100644 index bcb982ec..00000000 --- a/src/disk_usage/file_size/block.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::{ - fmt::{self, Display}, - fs::Metadata, - os::unix::fs::MetadataExt, -}; - -#[derive(Default)] -pub struct Metric { - pub value: u64, -} - -impl Metric { - pub fn init(md: &Metadata) -> Self { - Self { value: md.blocks() } - } -} - -impl Display for Metric { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(&self.value, f) - } -} diff --git a/src/disk_usage/file_size/byte.rs b/src/disk_usage/file_size/byte.rs deleted file mode 100644 index 3301a890..00000000 --- a/src/disk_usage/file_size/byte.rs +++ /dev/null @@ -1,207 +0,0 @@ -use super::super::units::{BinPrefix, PrefixKind, SiPrefix, UnitPrefix}; -use filesize::PathExt; -use std::{ - cell::{Ref, RefCell}, - fmt::{self, Display}, - fs::Metadata, - path::Path, -}; - -/// Concerned with measuring file size in bytes, whether logical or physical determined by `kind`. -/// Binary or SI units used for reporting determined by `prefix_kind`. -pub struct Metric { - pub value: u64, - pub human_readable: bool, - #[allow(dead_code)] - kind: MetricKind, - prefix_kind: PrefixKind, - - /// To prevent allocating the same string twice. We allocate the first time - /// in [`crate::tree::Tree::update_column_properties`] in order to compute the max column width for - /// human-readable size and cache it. It will then be used again when preparing the output. - cached_display: RefCell, -} - -/// Represents the appropriate method in which to compute bytes. `Logical` represent the total amount -/// of bytes in a file; `Physical` represents how many bytes are actually used to store the file on -/// disk. -pub enum MetricKind { - Logical, - Physical, -} - -impl Metric { - /// Initializes a [Metric] that stores the total amount of bytes in a file. - pub fn init_logical( - metadata: &Metadata, - prefix_kind: PrefixKind, - human_readable: bool, - ) -> Self { - let value = metadata.len(); - let kind = MetricKind::Logical; - - Self { - value, - human_readable, - kind, - prefix_kind, - cached_display: RefCell::default(), - } - } - - /// Initializes an empty [Metric] used to represent the total amount of bytes of a file. - pub fn init_empty_logical(human_readable: bool, prefix_kind: PrefixKind) -> Self { - Self { - value: 0, - human_readable, - kind: MetricKind::Logical, - prefix_kind, - cached_display: RefCell::default(), - } - } - - /// Initializes an empty [Metric] used to represent the total disk space of a file in bytes. - pub fn init_empty_physical(human_readable: bool, prefix_kind: PrefixKind) -> Self { - Self { - value: 0, - human_readable, - kind: MetricKind::Physical, - prefix_kind, - cached_display: RefCell::default(), - } - } - - /// Initializes a [Metric] that stores the total amount of bytes used to store a file on disk. - pub fn init_physical( - path: &Path, - metadata: &Metadata, - prefix_kind: PrefixKind, - human_readable: bool, - ) -> Self { - let value = path.size_on_disk_fast(metadata).unwrap_or(metadata.len()); - let kind = MetricKind::Physical; - - Self { - value, - human_readable, - kind, - prefix_kind, - cached_display: RefCell::default(), - } - } - - /// Returns an immutable borrow of the `cached_display`. - pub fn cached_display(&self) -> Ref<'_, String> { - self.cached_display.borrow() - } -} - -impl Display for Metric { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - { - let cached_display = self.cached_display(); - - if cached_display.len() > 0 { - return write!(f, "{cached_display}"); - } - } - - let value = self.value as f64; - - let display = match self.prefix_kind { - PrefixKind::Si => { - if self.human_readable { - let unit = SiPrefix::from(self.value); - - if unit == SiPrefix::Base { - format!("{} {unit}", self.value) - } else { - let base_value = unit.base_value(); - let size = value / (base_value as f64); - format!("{size:.1} {unit}") - } - } else { - format!("{} {}", self.value, SiPrefix::Base) - } - }, - PrefixKind::Bin => { - if self.human_readable { - let unit = BinPrefix::from(self.value); - - if unit == BinPrefix::Base { - format!("{} {unit}", self.value) - } else { - let base_value = unit.base_value(); - let size = value / (base_value as f64); - format!("{size:.1} {unit}") - } - } else { - format!("{} {}", self.value, BinPrefix::Base) - } - }, - }; - - write!(f, "{display}")?; - - self.cached_display.replace(display); - - Ok(()) - } -} - -#[test] -fn test_metric() { - let metric = Metric { - value: 100, - kind: MetricKind::Logical, - human_readable: false, - prefix_kind: PrefixKind::Bin, - cached_display: RefCell::::default(), - }; - assert_eq!(format!("{metric}"), "100 B"); - - let metric = Metric { - value: 1000, - kind: MetricKind::Logical, - human_readable: true, - prefix_kind: PrefixKind::Si, - cached_display: RefCell::::default(), - }; - assert_eq!(format!("{metric}"), "1.0 KB"); - - let metric = Metric { - value: 1000, - kind: MetricKind::Logical, - human_readable: true, - prefix_kind: PrefixKind::Bin, - cached_display: RefCell::::default(), - }; - assert_eq!(format!("{metric}"), "1000 B"); - - let metric = Metric { - value: 1024, - kind: MetricKind::Logical, - human_readable: true, - prefix_kind: PrefixKind::Bin, - cached_display: RefCell::::default(), - }; - assert_eq!(format!("{metric}"), "1.0 KiB"); - - let metric = Metric { - value: 2_u64.pow(20), - kind: MetricKind::Logical, - human_readable: true, - prefix_kind: PrefixKind::Bin, - cached_display: RefCell::::default(), - }; - assert_eq!(format!("{metric}"), "1.0 MiB"); - - let metric = Metric { - value: 123_454, - kind: MetricKind::Logical, - human_readable: false, - prefix_kind: PrefixKind::Bin, - cached_display: RefCell::::default(), - }; - assert_eq!(format!("{metric}"), "123454 B"); -} diff --git a/src/disk_usage/file_size/line_count.rs b/src/disk_usage/file_size/line_count.rs deleted file mode 100644 index 377a5ea3..00000000 --- a/src/disk_usage/file_size/line_count.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::{ - convert::{AsRef, From}, - fmt::{self, Display}, - fs, - path::Path, -}; - -/// Concerned with measuring file size using line count as a metric. -#[derive(Default)] -pub struct Metric { - pub value: u64, -} - -impl Metric { - /// Reads in contents of a file given by `path` and attempts to compute the total number of - /// lines in that file. If a file is not UTF-8 encoded as in the case of a binary jpeg file - /// then `None` will be returned. - pub fn init(path: impl AsRef) -> Option { - let data = fs::read_to_string(path.as_ref()).ok()?; - - let lines = data.lines().count(); - - u64::try_from(lines).map(|value| Self { value }).ok() - } -} - -impl From for Metric { - fn from(value: u64) -> Self { - Self { value } - } -} - -impl Display for Metric { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(&self.value, f) - } -} - -#[test] -fn test_line_count() { - let metric = - Metric::init("tests/data/nemesis.txt").expect("Expected 'tests/data/nemesis.txt' to exist"); - - assert_eq!(metric.value, 4); -} diff --git a/src/disk_usage/file_size/mod.rs b/src/disk_usage/file_size/mod.rs deleted file mode 100644 index fc47b48f..00000000 --- a/src/disk_usage/file_size/mod.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::context::Context; -use clap::ValueEnum; -use std::{ - convert::From, - fmt::{self, Display}, - ops::AddAssign, -}; - -/// Concerned with measuring file size in blocks. -#[cfg(unix)] -pub mod block; - -/// Concerned with measuring file size in bytes, logical or physical. -pub mod byte; - -/// Concerned with measuring file size by line count. -pub mod line_count; - -/// Concerned with measuring file size by word count. -pub mod word_count; - -#[cfg(unix)] -pub const BLOCK_SIZE_BYTES: u16 = 512; - -/// Represents all the different ways in which a filesize could be reported using various metrics. -pub enum FileSize { - Word(word_count::Metric), - Line(line_count::Metric), - Byte(byte::Metric), - #[cfg(unix)] - Block(block::Metric), -} - -/// Determines between logical or physical size for display -#[derive(Copy, Clone, Debug, ValueEnum, Default)] -pub enum DiskUsage { - /// How many bytes does a file contain - Logical, - - /// How many actual bytes on disk, taking into account blocks, sparse files, and compression. - #[default] - Physical, - - /// How many total lines a file contains - Line, - - /// How many total words a file contains - Word, - - /// How many blocks are allocated to store the file - #[cfg(unix)] - Block, -} - -impl FileSize { - /// Extracts the inner value of [`FileSize`] which represents the file size for various metrics. - #[inline] - pub const fn value(&self) -> u64 { - match self { - Self::Byte(metric) => metric.value, - Self::Line(metric) => metric.value, - Self::Word(metric) => metric.value, - - #[cfg(unix)] - Self::Block(metric) => metric.value, - } - } -} - -impl AddAssign<&Self> for FileSize { - fn add_assign(&mut self, rhs: &Self) { - match self { - Self::Byte(metric) => metric.value += rhs.value(), - Self::Line(metric) => metric.value += rhs.value(), - Self::Word(metric) => metric.value += rhs.value(), - - #[cfg(unix)] - Self::Block(metric) => metric.value += rhs.value(), - } - } -} - -impl From<&Context> for FileSize { - fn from(ctx: &Context) -> Self { - use DiskUsage::{Line, Logical, Physical, Word}; - - match ctx.disk_usage { - Logical => Self::Byte(byte::Metric::init_empty_logical(ctx.human, ctx.unit)), - Physical => Self::Byte(byte::Metric::init_empty_physical(ctx.human, ctx.unit)), - Line => Self::Line(line_count::Metric::default()), - Word => Self::Word(word_count::Metric::default()), - - #[cfg(unix)] - DiskUsage::Block => Self::Block(block::Metric::default()), - } - } -} - -impl Display for FileSize { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Word(metric) => write!(f, "{metric}"), - Self::Line(metric) => write!(f, "{metric}"), - Self::Byte(metric) => write!(f, "{metric}"), - - #[cfg(unix)] - Self::Block(metric) => write!(f, "{metric}"), - } - } -} diff --git a/src/disk_usage/file_size/word_count.rs b/src/disk_usage/file_size/word_count.rs deleted file mode 100644 index 05abe9b9..00000000 --- a/src/disk_usage/file_size/word_count.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::{ - convert::{AsRef, From}, - fmt::{self, Display}, - fs, - path::Path, -}; - -/// Concerned with measuring file size using word count as a metric. -#[derive(Default)] -pub struct Metric { - pub value: u64, -} - -impl Metric { - /// Reads in contents of a file given by `path` and attempts to compute the total number of - /// words in that file. If a file is not UTF-8 encoded as in the case of a binary jpeg file - /// then `None` will be returned. - /// - /// Words are UTF-8 encoded byte sequences delimited by Unicode Derived Core Property `White_Space`. - pub fn init(path: impl AsRef) -> Option { - let data = fs::read_to_string(path.as_ref()).ok()?; - - let words = data.split_whitespace().count(); - - u64::try_from(words).map(|value| Self { value }).ok() - } -} - -impl From for Metric { - fn from(value: u64) -> Self { - Self { value } - } -} - -impl Display for Metric { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(&self.value, f) - } -} - -#[test] -fn test_line_count() { - let metric = - Metric::init("tests/data/nemesis.txt").expect("Expected 'tests/data/nemesis.txt' to exist"); - - assert_eq!(metric.value, 27); -} diff --git a/src/disk_usage/mod.rs b/src/disk_usage/mod.rs deleted file mode 100644 index c855943f..00000000 --- a/src/disk_usage/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -/// Binary and SI prefixes -pub mod units; - -/// Concerned with all of the different ways to measure file size: bytes, word-count, line-count, -/// blocks (unix), etc.. -pub mod file_size; diff --git a/src/disk_usage/units.rs b/src/disk_usage/units.rs deleted file mode 100644 index 365668ff..00000000 --- a/src/disk_usage/units.rs +++ /dev/null @@ -1,140 +0,0 @@ -use clap::ValueEnum; -use std::{ - convert::From, - fmt::{self, Display}, -}; - -/// Determines whether to use SI prefixes or binary prefixes. -#[derive(Copy, Clone, Debug, ValueEnum, Default)] -pub enum PrefixKind { - /// Displays disk usage using binary prefixes. - #[default] - Bin, - - /// Displays disk usage using SI prefixes. - Si, -} - -/// Binary prefixes. -#[derive(Debug, PartialEq, Eq)] -pub enum BinPrefix { - Base, - Kibi, - Mebi, - Gibi, - Tebi, -} - -/// SI prefixes. -#[derive(Debug, PartialEq, Eq)] -pub enum SiPrefix { - Base, - Kilo, - Mega, - Giga, - Tera, -} - -impl SiPrefix { - /// Returns the human readable representation of the SI prefix. - pub const fn as_str(&self) -> &str { - match self { - Self::Base => "B", - Self::Kilo => "KB", - Self::Mega => "MB", - Self::Giga => "GB", - Self::Tera => "TB", - } - } -} - -impl BinPrefix { - /// Returns the human readable representation of the binary prefix. - pub const fn as_str(&self) -> &str { - match self { - Self::Base => "B", - Self::Kibi => "KiB", - Self::Mebi => "MiB", - Self::Gibi => "GiB", - Self::Tebi => "TiB", - } - } -} - -pub trait UnitPrefix { - fn base_value(&self) -> u64; -} - -impl UnitPrefix for SiPrefix { - fn base_value(&self) -> u64 { - match self { - Self::Base => 1, - Self::Kilo => 10_u64.pow(3), - Self::Mega => 10_u64.pow(6), - Self::Giga => 10_u64.pow(9), - Self::Tera => 10_u64.pow(12), - } - } -} - -impl UnitPrefix for BinPrefix { - fn base_value(&self) -> u64 { - match self { - Self::Base => 1, - Self::Kibi => 2_u64.pow(10), - Self::Mebi => 2_u64.pow(20), - Self::Gibi => 2_u64.pow(30), - Self::Tebi => 2_u64.pow(40), - } - } -} - -/// Get the closest human-readable unit prefix for value. -impl From for BinPrefix { - fn from(value: u64) -> Self { - let log = (value as f64).log2(); - - if log < 10. { - Self::Base - } else if log < 20. { - Self::Kibi - } else if log < 30. { - Self::Mebi - } else if log < 40. { - Self::Gibi - } else { - Self::Tebi - } - } -} - -/// Get the closest human-readable unit prefix for value. -impl From for SiPrefix { - fn from(value: u64) -> Self { - let log = (value as f64).log10(); - - if log < 3. { - Self::Base - } else if log < 6. { - Self::Kilo - } else if log < 9. { - Self::Mega - } else if log < 12. { - Self::Giga - } else { - Self::Tera - } - } -} - -impl Display for BinPrefix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -impl Display for SiPrefix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..f3c56347 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,111 @@ +use ansi_term::{Color, Style}; +use std::{convert::From, error::Error as StdError, fmt, result::Result as StdResult}; + +/// General result type to be used through the application. +pub type Result = std::result::Result; + +/// General error type to be used throughout the application. Depending on the `category`, a +/// different format of the error will be presented to the end-user. +#[derive(Debug)] +pub struct Error { + source: anyhow::Error, + category: Category, + help_text: Option, +} + +/// Category of errors with which to generate a report. +#[derive(Debug)] +pub enum Category { + /// Errors due to logical errors within the application. + Internal, + /// User-specific errors to be used in relation to command-line arguments and configs. + User, + /// Errors related to environment such as the missing of the `$HOME` environment variable. + System, +} + +impl Error { + pub fn new(category: Category, source: anyhow::Error, help_text: Option) -> Self { + Self { source, category, help_text } + } + + fn internal_error_help_message() -> String { + format!( + "Please submit the error output to {}", + Style::default() + .bold() + .paint("https://github.com/solidiquis/erdtree/issues") + ) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let icon = Color::Red.bold().paint("\u{2715}"); + let prefix = Style::default().bold().paint(format!("{}", self.category)); + let help = Color::Cyan.bold().paint("help"); + + if let Some(ref help_txt) = self.help_text { + writeln!(f, "{icon} {prefix}: {:?}\n\n{help}: {help_txt}", self.source) + } else { + writeln!(f, "{} {:?}", icon, self.source) + } + } +} + +impl fmt::Display for Category { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Internal => write!(f, "Internal error"), + Self::User => write!(f, "Error"), + Self::System => write!(f, "System error"), + } + } +} + +/// Convenience trait to generate a [`Result`] from any type that implements [`std::error::Error`]. +pub trait ErrorReport { + fn into_report(self, category: Category) -> Result; +} + +/// Allows the chaining of contexts to [`Error`]'s underlying [`anyhow::Error`]. +pub trait WithContext +where + C: fmt::Display + Send + Sync + 'static, +{ + /// Chain together contexts. + fn context(self, ctx: C) -> Result; + + /// Set some help text to display with the error output. + fn set_help(self, msg: C) -> Result; +} + +impl ErrorReport for StdResult { + fn into_report(self, category: Category) -> Result { + self.map_err(|e| { + let help_text = matches!(category, Category::Internal).then(Error::internal_error_help_message); + let anyhow_err = anyhow::Error::from(e); + Error::new(category, anyhow_err, help_text) + }) + } +} + +impl WithContext for Result +where + C: fmt::Display + Send + Sync + 'static, +{ + fn context(self, ctx: C) -> Self { + self.map_err(|mut err| { + let cause = err.source.context(ctx); + err.source = cause; + err + }) + } + + fn set_help(self, msg: C) -> Self { + self.map_err(|mut err| { + err.help_text = Some(format!("{msg}")); + err + }) + } +} diff --git a/src/file/mod.rs b/src/file/mod.rs new file mode 100644 index 00000000..5a582bd2 --- /dev/null +++ b/src/file/mod.rs @@ -0,0 +1,7 @@ +use ignore::DirEntry; +use std::fs::Metadata; + +pub struct File { + inner: DirEntry, + metadata: Metadata, +} diff --git a/src/fs/inode.rs b/src/fs/inode.rs deleted file mode 100644 index 8f9ff6b9..00000000 --- a/src/fs/inode.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::{convert::TryFrom, fs::Metadata}; - -/// Represents a file's underlying inode. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Inode { - pub ino: u64, - pub dev: u64, - pub nlink: u64, -} - -impl Inode { - /// Initializer for an inode given all the properties that make it unique. - pub const fn new(ino: u64, dev: u64, nlink: u64) -> Self { - Self { ino, dev, nlink } - } -} - -#[derive(Debug, thiserror::Error)] -#[error("Insufficient information to compute inode")] -pub struct Error; - -impl TryFrom<&Metadata> for Inode { - type Error = Error; - - #[cfg(unix)] - fn try_from(md: &Metadata) -> Result { - use std::os::unix::fs::MetadataExt; - - Ok(Self::new(md.ino(), md.dev(), md.nlink())) - } - - #[cfg(windows)] - fn try_from(md: &Metadata) -> Result { - use std::os::windows::fs::MetadataExt; - - if let (Some(dev), Some(ino), Some(nlink)) = ( - md.volume_serial_number(), - md.file_index(), - md.number_of_links(), - ) { - return Ok(Self::new(ino, dev.into(), nlink.into())); - } - - Err(Error {}) - } - - #[cfg(not(any(unix, windows)))] - fn try_from(md: &Metadata) -> Result { - Err(Error {}) - } -} diff --git a/src/fs/mod.rs b/src/fs/mod.rs deleted file mode 100644 index 9c262071..00000000 --- a/src/fs/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -use ignore::DirEntry; -use std::{fs, path::PathBuf}; - -/// Operations pertaining to underlying inodes of files. -pub mod inode; - -/// Unix file permissions. -#[cfg(unix)] -pub mod permissions; - -/// Determining whether or not a file has extended attributes. -#[cfg(unix)] -pub mod xattr; - -/// Concerned with determining group and owner of file. -#[cfg(unix)] -pub mod ug; - -/// Returns the path to the target of the soft link. Returns `None` if provided `dir_entry` isn't a -/// symlink. -pub fn symlink_target(dir_entry: &DirEntry) -> Option { - dir_entry - .path_is_symlink() - .then(|| fs::read_link(dir_entry.path())) - .transpose() - .ok() - .flatten() -} diff --git a/src/fs/permissions/class.rs b/src/fs/permissions/class.rs deleted file mode 100644 index a299ab2e..00000000 --- a/src/fs/permissions/class.rs +++ /dev/null @@ -1,151 +0,0 @@ -use std::fmt::{self, Display}; - -/// The set of permissions for a particular class i.e. user, group, or other. -#[derive(Debug)] -pub struct Permissions { - class: Class, - attr: Option, - pub(super) triad: PermissionsTriad, -} - -/// The class type that is associated with a permissions triad. -#[derive(Debug)] -pub enum Class { - User, - Group, - Other, -} - -/// Represents the special attributes that exist on the overall file corresponding to the setuid, -/// setgid, and the sticky bit. -#[derive(Debug, PartialEq, Eq)] -#[allow(clippy::upper_case_acronyms)] -pub enum Attribute { - SUID, - SGID, - Sticky, -} - -/// Read, write, execute permissions. -#[derive(Debug, PartialEq, Eq)] -pub enum PermissionsTriad { - Read, - Write, - Execute, - ReadWrite, - ReadExecute, - WriteExecute, - ReadWriteExecute, - None, -} - -/// All `permissions_mask` arguments represents the bits of `st_mode` which excludes the file-type -/// and the setuid, setgid, and sticky bit. -impl Permissions { - /// Computes user permissions. - pub fn user_permissions_from(st_mode: u32) -> Self { - let read = Self::enabled(st_mode, libc::S_IRUSR); - let write = Self::enabled(st_mode, libc::S_IWUSR); - let execute = Self::enabled(st_mode, libc::S_IXUSR); - let suid = Self::enabled(st_mode, libc::S_ISUID).then_some(Attribute::SUID); - - Self::permissions_from_rwx(Class::User, read, write, execute, suid) - } - - /// Computes group permissions. - pub fn group_permissions_from(st_mode: u32) -> Self { - let read = Self::enabled(st_mode, libc::S_IRGRP); - let write = Self::enabled(st_mode, libc::S_IWGRP); - let execute = Self::enabled(st_mode, libc::S_IXGRP); - let sgid = Self::enabled(st_mode, libc::S_ISGID).then_some(Attribute::SGID); - - Self::permissions_from_rwx(Class::Group, read, write, execute, sgid) - } - - /// Computes other permissions. - pub fn other_permissions_from(st_mode: u32) -> Self { - let read = Self::enabled(st_mode, libc::S_IROTH); - let write = Self::enabled(st_mode, libc::S_IWOTH); - let execute = Self::enabled(st_mode, libc::S_IXOTH); - let sticky = Self::enabled(st_mode, libc::S_ISVTX).then_some(Attribute::Sticky); - - Self::permissions_from_rwx(Class::Other, read, write, execute, sticky) - } - - /// Checks if a particular mode (read, write, or execute) is enabled. - fn enabled(st_mode: u32, mask: N) -> bool - where - N: Copy + Into, - { - st_mode & mask.into() == mask.into() - } - - /// Returns `true` if sticky bit is enabled. - pub fn attr_is_sticky(&self) -> bool { - self.attr - .as_ref() - .map_or(false, |attr| attr == &Attribute::Sticky) - } - - /// Helper function to compute permissions. - const fn permissions_from_rwx( - class: Class, - r: bool, - w: bool, - x: bool, - attr: Option, - ) -> Self { - let triad = match (r, w, x) { - (true, false, false) => PermissionsTriad::Read, - (false, true, false) => PermissionsTriad::Write, - (false, false, true) => PermissionsTriad::Execute, - (true, true, false) => PermissionsTriad::ReadWrite, - (true, false, true) => PermissionsTriad::ReadExecute, - (false, true, true) => PermissionsTriad::WriteExecute, - (true, true, true) => PermissionsTriad::ReadWriteExecute, - (false, false, false) => PermissionsTriad::None, - }; - - Self { class, attr, triad } - } -} - -/// The symbolic representation of a [`PermissionsTriad`]. -impl Display for Permissions { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.class { - Class::Other if self.attr_is_sticky() => match self.triad { - PermissionsTriad::Read => write!(f, "r-T"), - PermissionsTriad::Write => write!(f, "-wT"), - PermissionsTriad::Execute => write!(f, "--t"), - PermissionsTriad::ReadWrite => write!(f, "rwT"), - PermissionsTriad::ReadExecute => write!(f, "r-t"), - PermissionsTriad::WriteExecute => write!(f, "-wt"), - PermissionsTriad::ReadWriteExecute => write!(f, "rwt"), - PermissionsTriad::None => write!(f, "--T"), - }, - - _ if self.attr.is_some() => match self.triad { - PermissionsTriad::Read => write!(f, "r-S"), - PermissionsTriad::Write => write!(f, "-wS"), - PermissionsTriad::Execute => write!(f, "--s"), - PermissionsTriad::ReadWrite => write!(f, "rwS"), - PermissionsTriad::ReadExecute => write!(f, "r-s"), - PermissionsTriad::WriteExecute => write!(f, "-ws"), - PermissionsTriad::ReadWriteExecute => write!(f, "rws"), - PermissionsTriad::None => write!(f, "--S"), - }, - - _ => match self.triad { - PermissionsTriad::Read => write!(f, "r--"), - PermissionsTriad::Write => write!(f, "-w-"), - PermissionsTriad::Execute => write!(f, "--x"), - PermissionsTriad::ReadWrite => write!(f, "rw-"), - PermissionsTriad::ReadExecute => write!(f, "r-x"), - PermissionsTriad::WriteExecute => write!(f, "-wx"), - PermissionsTriad::ReadWriteExecute => write!(f, "rwx"), - PermissionsTriad::None => write!(f, "---"), - }, - } - } -} diff --git a/src/fs/permissions/error.rs b/src/fs/permissions/error.rs deleted file mode 100644 index 075f70f2..00000000 --- a/src/fs/permissions/error.rs +++ /dev/null @@ -1,7 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum Error { - #[error("Encountered an unknown file type.")] - UnknownFileType, -} diff --git a/src/fs/permissions/file_type.rs b/src/fs/permissions/file_type.rs deleted file mode 100644 index 08fe8ce9..00000000 --- a/src/fs/permissions/file_type.rs +++ /dev/null @@ -1,55 +0,0 @@ -use super::error::Error; - -/// Unix file types. -#[derive(Debug, PartialEq, Eq)] -pub enum FileType { - Directory, - File, - Symlink, - Fifo, - Socket, - CharDevice, - BlockDevice, -} - -impl FileType { - /// Unix file identifiers that you'd find in the `ls -l` command. - pub const fn identifier(&self) -> char { - match self { - Self::Directory => 'd', - Self::File => '.', - Self::Symlink => 'l', - Self::Fifo => 'p', - Self::Socket => 's', - Self::CharDevice => 'c', - Self::BlockDevice => 'b', - } - } -} - -/// The argument `mode` is meant to come from the `mode` method of [`std::fs::Permissions`]. -impl TryFrom for FileType { - type Error = Error; - - fn try_from(mode: u32) -> Result { - let file_mask = mode & u32::from(libc::S_IFMT); - - if file_mask == u32::from(libc::S_IFIFO) { - Ok(Self::Fifo) - } else if file_mask == u32::from(libc::S_IFCHR) { - Ok(Self::CharDevice) - } else if file_mask == u32::from(libc::S_IFDIR) { - Ok(Self::Directory) - } else if file_mask == u32::from(libc::S_IFBLK) { - Ok(Self::BlockDevice) - } else if file_mask == u32::from(libc::S_IFREG) { - Ok(Self::File) - } else if file_mask == u32::from(libc::S_IFLNK) { - Ok(Self::Symlink) - } else if file_mask == u32::from(libc::S_IFSOCK) { - Ok(Self::Socket) - } else { - Err(Error::UnknownFileType) - } - } -} diff --git a/src/fs/permissions/mod.rs b/src/fs/permissions/mod.rs deleted file mode 100644 index 4beea3ba..00000000 --- a/src/fs/permissions/mod.rs +++ /dev/null @@ -1,137 +0,0 @@ -use error::Error; -use file_type::FileType; -use std::{ - convert::TryFrom, - fmt::{self, Display, Octal}, - os::unix::fs::PermissionsExt, -}; - -/// For working with permissions for a particular class i.e. user, group, or other. -pub mod class; - -/// File permission related errors. -pub mod error; - -/// For working with Unix file identifiers. -pub mod file_type; - -#[cfg(test)] -mod test; - -impl SymbolicNotation for std::fs::Permissions {} - -/// Trait that is used to extend [`std::fs::Permissions`] behavior such that it allows for `mode` to -/// be expressed in Unix's symbolic notation for file permissions. -pub trait SymbolicNotation: PermissionsExt { - /// Attempts to return a [`FileMode`] which implements [Display] allowing it to be presented in - /// symbolic notation for file permissions. - fn try_mode_symbolic_notation(&self) -> Result { - let mode = self.mode(); - FileMode::try_from(mode) - } -} - -/// A struct which holds information about the permissions of a particular file. [`FileMode`] -/// implements [Display] which allows it to be conveniently presented in symbolic notation when -/// expressing file permissions. -pub struct FileMode { - pub st_mode: u32, - file_type: FileType, - user_permissions: class::Permissions, - group_permissions: class::Permissions, - other_permissions: class::Permissions, -} - -/// Implements [Display] which presents symbolic notation of file permissions with the extended -/// attributes. -pub struct FileModeXAttrs<'a>(pub &'a FileMode); - -impl FileMode { - /// Constructor for [`FileMode`]. - pub const fn new( - st_mode: u32, - file_type: FileType, - user_permissions: class::Permissions, - group_permissions: class::Permissions, - other_permissions: class::Permissions, - ) -> Self { - Self { - st_mode, - file_type, - user_permissions, - group_permissions, - other_permissions, - } - } - - /// Returns a reference to `file_type`. - pub const fn file_type(&self) -> &FileType { - &self.file_type - } - - /// Returns a reference to a [`class::Permissions`] which represents the permissions of the user class. - pub const fn user_permissions(&self) -> &class::Permissions { - &self.user_permissions - } - - /// Returns a reference to a [`class::Permissions`] which represents the permissions of the group class. - pub const fn group_permissions(&self) -> &class::Permissions { - &self.group_permissions - } - - /// Returns a reference to a [`class::Permissions`] which represents the permissions of the other class. - pub const fn other_permissions(&self) -> &class::Permissions { - &self.other_permissions - } -} - -/// For representing [`FileMode`] in symbolic notation. -impl Display for FileMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let file_iden = self.file_type().identifier(); - let user_permissions = self.user_permissions(); - let group_permissions = self.group_permissions(); - let other_permissions = self.other_permissions(); - - write!( - f, - "{file_iden}{user_permissions}{group_permissions}{other_permissions}" - ) - } -} - -/// For representing file permissions with extended attributes in symbolic notation. -impl Display for FileModeXAttrs<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mode = self.0; - write!(f, "{mode}@") - } -} - -/// For the octal representation of permissions -impl Octal for FileMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let modes_mask = self.st_mode & !u32::from(libc::S_IFMT); - fmt::Octal::fmt(&modes_mask, f) - } -} - -/// The argument `st_mode` is meant to come from the `mode` method of [`std::fs::Permissions`]. -impl TryFrom for FileMode { - type Error = Error; - - fn try_from(st_mode: u32) -> Result { - let file_type = FileType::try_from(st_mode)?; - let user_permissions = class::Permissions::user_permissions_from(st_mode); - let group_permissions = class::Permissions::group_permissions_from(st_mode); - let other_permissions = class::Permissions::other_permissions_from(st_mode); - - Ok(Self::new( - st_mode, - file_type, - user_permissions, - group_permissions, - other_permissions, - )) - } -} diff --git a/src/fs/permissions/test.rs b/src/fs/permissions/test.rs deleted file mode 100644 index 31a17f94..00000000 --- a/src/fs/permissions/test.rs +++ /dev/null @@ -1,96 +0,0 @@ -use super::{class::PermissionsTriad, file_type::FileType, SymbolicNotation}; -use std::{error::Error, fs::File, os::unix::fs::PermissionsExt}; - -#[test] -fn test_symbolic_notation() -> Result<(), Box> { - let temp = std::env::temp_dir().join("yogsothoth.hpl"); - - // File is created with read + write for user and read-only for all others. - let file = File::create(temp)?; - let metadata = file.metadata()?; - - let permissions = metadata.permissions(); - - let file_mode = permissions.try_mode_symbolic_notation()?; - - let file_type = file_mode.file_type(); - let user = file_mode.user_permissions(); - let group = file_mode.group_permissions(); - let other = file_mode.other_permissions(); - - assert_eq!(file_type, &FileType::File); - assert_eq!(&user.triad, &PermissionsTriad::ReadWrite); - assert_eq!(&group.triad, &PermissionsTriad::Read); - assert_eq!(&other.triad, &PermissionsTriad::Read); - - let rwx = format!("{file_mode}"); - assert_eq!(rwx, ".rw-r--r--"); - - let octal = format!("{file_mode:o}"); - assert_eq!(octal, "644"); - - Ok(()) -} - -#[test] -fn test_symbolic_notation_special_attr() -> Result<(), Box> { - let temp = std::env::temp_dir().join("sub-niggurath.hpl"); - - // File is created with read + write for user and read-only for all others. - let file = File::create(temp)?; - - let metadata = file.metadata()?; - let mut permissions = metadata.permissions(); - - // Set the sticky bit - permissions.set_mode(0o101_644); - - let file_mode = permissions.try_mode_symbolic_notation()?; - let rwx = format!("{file_mode}"); - assert_eq!(rwx, ".rw-r--r-T"); - - let octal = format!("{file_mode:o}"); - assert_eq!(octal, "1644"); - - // Set the getuid bit - permissions.set_mode(0o102_644); - - let file_mode = permissions.try_mode_symbolic_notation()?; - let rwx = format!("{file_mode}"); - assert_eq!(rwx, ".rw-r-Sr--"); - - let octal = format!("{file_mode:o}"); - assert_eq!(octal, "2644"); - - // Set the setuid bit - permissions.set_mode(0o104_644); - - let file_mode = permissions.try_mode_symbolic_notation()?; - let rwx = format!("{file_mode}"); - assert_eq!(rwx, ".rwSr--r--"); - - let octal = format!("{file_mode:o}"); - assert_eq!(octal, "4644"); - - // Set the all the attr bits - permissions.set_mode(0o107_644); - - let file_mode = permissions.try_mode_symbolic_notation()?; - let rwx = format!("{file_mode}"); - assert_eq!(rwx, ".rwSr-Sr-T"); - - let octal = format!("{file_mode:o}"); - assert_eq!(octal, "7644"); - - // Set all the attr bits and give all classes execute permissions - permissions.set_mode(0o107_777); - - let file_mode = permissions.try_mode_symbolic_notation()?; - let rwx = format!("{file_mode}"); - assert_eq!(rwx, ".rwsrwsrwt"); - - let octal = format!("{file_mode:o}"); - assert_eq!(octal, "7777"); - - Ok(()) -} diff --git a/src/fs/ug.rs b/src/fs/ug.rs deleted file mode 100644 index 5d7e5343..00000000 --- a/src/fs/ug.rs +++ /dev/null @@ -1,84 +0,0 @@ -use errno::{errno, set_errno, Errno}; -use std::{ffi::CStr, fs::Metadata, os::unix::fs::MetadataExt}; - -type Owner = String; -type Group = String; - -impl UserGroupInfo for Metadata {} - -/// Trait that allows for files to query their owner and group. -pub trait UserGroupInfo: MetadataExt { - /// Attemps to query the owner of the implementor. - fn try_get_owner(&self) -> Result { - unsafe { - let uid = self.uid(); - try_get_user(uid) - } - } - - /// Attempts to query both the owner and group of the implementor. - fn try_get_owner_and_group(&self) -> Result<(Owner, Group), Error> { - unsafe { - let uid = self.uid(); - let gid = self.gid(); - let user = try_get_user(uid)?; - let group = try_get_group(gid)?; - - Ok((user, group)) - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("libc error")] - LibC(Errno), - - #[error("Invalid user")] - InvalidUser, - - #[error("Invalid group")] - InvalidGroup, -} - -/// Attempts to return the name of the group associated with `gid`. -unsafe fn try_get_group(gid: libc::gid_t) -> Result { - set_errno(Errno(0)); - - let group = libc::getgrgid(gid); - - let errno = errno(); - - if errno.0 != 0 { - return Err(Error::LibC(errno)); - } - - if group.is_null() { - return Err(Error::InvalidGroup); - } - - let libc::group { gr_name, .. } = *group; - - Ok(CStr::from_ptr(gr_name).to_string_lossy().to_string()) -} - -/// Attempts to return the name of the user associated with `uid`. -unsafe fn try_get_user(uid: libc::uid_t) -> Result { - set_errno(Errno(0)); - - let pwd = libc::getpwuid(uid); - - let errno = errno(); - - if errno.0 != 0 { - return Err(Error::LibC(errno)); - } - - if pwd.is_null() { - return Err(Error::InvalidUser); - } - - let libc::passwd { pw_name, .. } = *pwd; - - Ok(CStr::from_ptr(pw_name).to_string_lossy().to_string()) -} diff --git a/src/fs/xattr.rs b/src/fs/xattr.rs deleted file mode 100644 index d6ae4eff..00000000 --- a/src/fs/xattr.rs +++ /dev/null @@ -1,38 +0,0 @@ -use ignore::DirEntry; -use std::{os::unix::ffi::OsStrExt, path::Path, ptr}; - -/// Allow extended attributes to be queried directly from the directory entry. -impl ExtendedAttr for DirEntry { - fn path(&self) -> &Path { - self.path() - } -} - -/// Simple trait that allows files to query extended attributes if it exists. -pub trait ExtendedAttr { - fn path(&self) -> &Path; - - /// Queries the filesystem to check if there exists extended attributes for the implementor's - /// path. - fn has_xattrs(&self) -> bool { - unsafe { has_xattrs(self.path()) } - } -} - -/// Checks to see if a directory entry referred to by `path` has extended attributes. If the file -/// at the provided `path` is symlink the file it points to is interrogated. -unsafe fn has_xattrs(path: &Path) -> bool { - use libc::{c_char, listxattr}; - - let path_ptr = { - let slice = path.as_os_str().as_bytes(); - let slice_ptr = slice.as_ptr(); - slice_ptr.cast::() - }; - - #[cfg(not(target_os = "macos"))] - return 0 < listxattr(path_ptr, ptr::null_mut::(), 0); - - #[cfg(target_os = "macos")] - return 0 < listxattr(path_ptr, ptr::null_mut::(), 0, 0); -} diff --git a/src/icons/fs.rs b/src/icons/fs.rs deleted file mode 100644 index aa56ac70..00000000 --- a/src/icons/fs.rs +++ /dev/null @@ -1,95 +0,0 @@ -use ansi_term::{ANSIGenericString, Style}; -use ignore::DirEntry; -use std::{borrow::Cow, path::Path}; - -/// Computes a plain, colorless icon with given parameters. -/// -/// The precedent from highest to lowest in terms of which parameters determine the icon used -/// is as followed: file-type, file-extension, and then file-name. If an icon cannot be -/// computed the fall-back default icon is used. -/// -/// If a directory entry is a link and the link target is provided, the link target will be -/// used to determine the icon. -pub fn compute(entry: &DirEntry, link_target: Option<&Path>) -> Cow<'static, str> { - let icon = entry - .file_type() - .and_then(super::icon_from_file_type) - .map(Cow::from); - - if let Some(i) = icon { - return i; - } - - let ext = match link_target { - Some(target) if entry.path_is_symlink() => target.extension(), - _ => entry.path().extension(), - }; - - let icon = ext - .and_then(super::icon_from_ext) - .map(|(_, i)| Cow::from(i)); - - if let Some(i) = icon { - return i; - } - - let icon = super::icon_from_file_name(entry.file_name()).map(Cow::from); - - if let Some(i) = icon { - return i; - } - - Cow::from(super::get_default_icon().1) -} - -/// Computes a plain, colored icon with given parameters. See [compute] for more details. -pub fn compute_with_color( - entry: &DirEntry, - link_target: Option<&Path>, - style: Option