diff --git a/Cargo.toml b/Cargo.toml index 4d83dd09..68e4f6a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ description = """ erdtree (erd) is a cross-platform multi-threaded filesystem and disk usage analysis tool that respects gitignore and hidden file rules. """ +categories = ["command-line-utilities"] documentation = "https://github.com/solidiquis/erdtree" homepage = "https://github.com/solidiquis/erdtree" repository = "https://github.com/solidiquis/erdtree" diff --git a/README.md b/README.md index 6ae1707b..6810d927 100644 --- a/README.md +++ b/README.md @@ -350,7 +350,7 @@ To add extra granularity to how directories are sorted relative to other file-ty ``` --dir-order Sort directories before or after all other file types - + [default: none] Possible values: diff --git a/src/main.rs b/src/main.rs index aace35df..e0b37f56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,8 @@ clippy::complexity, clippy::perf, clippy::pedantic, - clippy::nursery + clippy::nursery, + clippy::cargo )] #![allow( clippy::struct_excessive_bools, @@ -15,6 +16,7 @@ clippy::cast_sign_loss, clippy::cast_possible_truncation )] + use clap::CommandFactory; use render::{ context::Context, @@ -23,7 +25,7 @@ use render::{ Tree, }, }; -use std::{io::stdout, process::ExitCode}; +use std::{error::Error, io::stdout}; /// Operations to wrangle ANSI escaped strings. mod ansi; @@ -43,16 +45,7 @@ mod tty; /// Common utilities across all modules. mod utils; -fn main() -> ExitCode { - if let Err(e) = run() { - eprintln!("{e}"); - return ExitCode::FAILURE; - } - - ExitCode::SUCCESS -} - -fn run() -> Result<(), Box> { +fn main() -> Result<(), Box> { let ctx = Context::init()?; if let Some(shell) = ctx.completions { diff --git a/src/render/context/mod.rs b/src/render/context/mod.rs index 9b971302..591663c4 100644 --- a/src/render/context/mod.rs +++ b/src/render/context/mod.rs @@ -47,10 +47,11 @@ mod test; /// Defines the CLI. #[derive(Parser, Debug)] -#[command(name = "erdtree")] -#[command(author = "Benjamin Nguyen. ")] -#[command(version = "2.0.0")] -#[command(about = "erdtree (erd) is a cross-platform multi-threaded filesystem and disk usage analysis tool.", long_about = None)] +#[command(name = env!("CARGO_PKG_NAME", "The Package Name is missing!"))] +#[command(author = env!("CARGO_PKG_AUTHORS", "The Author of the Package is missing!"))] +#[command(version = env!("CARGO_PKG_VERSION_MAJOR", "The Package version is missing!"))] +#[command(about = "erdtree (erd) is a cross-platform multi-threaded filesystem and disk usage analysis tool.", + long_about = env!("CARGO_PKG_DESCRIPTION", "The Long Package Description is missing!"))] pub struct Context { /// Directory to traverse; defaults to current working directory dir: Option, @@ -60,7 +61,7 @@ pub struct Context { pub color: Coloring, /// Print physical or logical file size - #[arg(short, long, value_enum, default_value_t = DiskUsage::default())] + #[arg(short, long, value_enum, default_value_t)] pub disk_usage: DiskUsage, /// Follow symlinks @@ -107,11 +108,10 @@ pub struct Context { pub pattern: Option, /// Enables glob based searching - #[arg(long, requires = "pattern")] + #[arg(group = "searching", long, requires = "pattern")] pub glob: bool, - /// Enables case-insensitive glob based searching - #[arg(long, requires = "pattern")] + #[arg(group = "searching", long, requires = "pattern")] pub iglob: bool, /// Restrict regex or glob search to a particular file-type @@ -123,7 +123,7 @@ pub struct Context { pub prune: bool, /// Sort-order to display directory content - #[arg(short, long, value_enum, default_value_t = sort::Type::default())] + #[arg(short, long, value_enum, default_value_t)] pub sort: sort::Type, /// Sort directories before or after all other file types @@ -208,6 +208,16 @@ pub struct Context { #[clap(skip)] pub window_width: Option, } + +trait AsVecOfStr { + fn as_vec_of_str(&self) -> Vec<&str>; +} +impl AsVecOfStr for ArgMatches { + fn as_vec_of_str(&self) -> Vec<&str> { + self.ids().map(Id::as_str).collect() + } +} + type Predicate = Result bool + Send + Sync + 'static>, Error>; impl Context { @@ -238,16 +248,13 @@ impl Context { // user arguments. let mut args = vec![OsString::from("--")]; - let mut ids = user_args.ids().map(Id::as_str).collect::>(); + let mut ids = user_args.as_vec_of_str(); - ids.extend(config_args.ids().map(Id::as_str).collect::>()); + ids.extend(config_args.as_vec_of_str()); ids = crate::utils::uniq(ids); - for id in ids { - if id == "Context" { - continue; - } + for id in ids.into_iter().filter(|&id| id != "Context") { if id == "dir" { if let Ok(Some(raw)) = user_args.try_get_raw(id) { let raw_args = raw.map(OsStr::to_owned).collect::>(); @@ -388,10 +395,7 @@ impl Context { let mut negated_glob = false; - let overrides = if !self.glob && !self.iglob { - // Shouldn't really ever be hit but placing here as a safeguard. - return Err(Error::EmptyGlob); - } else { + let overrides = { if self.iglob { builder.case_insensitive(true)?; } diff --git a/src/render/context/sort.rs b/src/render/context/sort.rs index 937f2e98..79aab23d 100644 --- a/src/render/context/sort.rs +++ b/src/render/context/sort.rs @@ -3,13 +3,29 @@ 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 + /// Sort entries by file name in lexicographical order. Name, + /// Sort entries by file name in reversed lexicographical order. + NameRev, /// Sort entries by size smallest to largest, top to bottom #[default] Size, - /// Sort entries by size largest to smallest, bottom to top SizeRev, + + /// Sort entries by newer to older Accessing Date + Access, + /// Sort entries by older to newer Accessing Date + AccessRev, + + /// Sort entries by newer to older Creation Date + Creation, + /// Sort entries by older to newer Creation Date + CreationRev, + + /// Sort entries by newer to older Alteration Date + Modification, + /// Sort entries by older to newer Alteration Date + ModificationRev, } diff --git a/src/render/tree/display/theme.rs b/src/render/tree/display/theme.rs index 3df91c43..f8b04de6 100644 --- a/src/render/tree/display/theme.rs +++ b/src/render/tree/display/theme.rs @@ -3,15 +3,17 @@ use crate::render::{ tree::Node, }; +type Theme = Box &'static ThemesMap>; + /// Returns a closure that retrieves the regular theme. -pub fn regular_theme_getter() -> Box &'static ThemesMap> { +pub fn regular_theme_getter() -> Theme { Box::new(|_node: &Node| styles::get_tree_theme().unwrap()) } /// Returns a closure that can smartly determine when a symlink is being followed and when it is /// not being followed. When a symlink is being followed, all of its descendents should have tree /// branches that are colored differently. -pub fn link_theme_getter() -> Box &'static ThemesMap> { +pub fn link_theme_getter() -> Theme { let mut link_depth = None; Box::new(move |node: &Node| { diff --git a/src/render/tree/mod.rs b/src/render/tree/mod.rs index 9104c4b9..34f745e6 100644 --- a/src/render/tree/mod.rs +++ b/src/render/tree/mod.rs @@ -282,19 +282,14 @@ where /// Function to remove empty directories. fn prune_directories(root_id_id: NodeId, tree: &mut Arena) { - let mut to_prune = vec![]; - - for node_id in root_id_id.descendants(tree).skip(1) { - let node = tree[node_id].get(); - - if !node.is_dir() { - continue; - } - - if node_id.children(tree).count() == 0 { - to_prune.push(node_id); - } - } + let to_prune = root_id_id + .descendants(tree) + .skip(1) + .map(|node_id| (node_id, tree[node_id].get())) + .filter(|(_, node)| node.is_dir()) + .map(|(node_id, _)| node_id) + .filter(|node_id| node_id.children(tree).count() == 0) + .collect::>(); if to_prune.is_empty() { return; @@ -309,13 +304,11 @@ where /// Filter for only directories. fn filter_directories(root_id: NodeId, tree: &mut Arena) { - let mut to_detach = vec![]; - - for descendant_id in root_id.descendants(tree).skip(1) { - if !tree[descendant_id].get().is_dir() { - to_detach.push(descendant_id); - } - } + let to_detach = root_id + .descendants(tree) + .skip(1) + .filter(|&descendant_id| !tree[descendant_id].get().is_dir()) + .collect::>(); for descendant_id in to_detach { descendant_id.detach(tree); diff --git a/src/render/tree/node/cmp.rs b/src/render/tree/node/cmp.rs index 69f8bc0e..811aed57 100644 --- a/src/render/tree/node/cmp.rs +++ b/src/render/tree/node/cmp.rs @@ -22,15 +22,6 @@ pub fn comparator(ctx: &Context) -> Box { base_comparator(sort_type) } -/// Grabs the comparator for two non-dir type [Node]s. -fn base_comparator(sort_type: sort::Type) -> Box { - match sort_type { - sort::Type::Name => Box::new(name_comparator), - sort::Type::Size => Box::new(size_comparator), - sort::Type::SizeRev => Box::new(size_rev_comparator), - } -} - /// Orders directories first. Provides a fallback if inputs are not directories. fn dir_first_comparator( a: &Node, @@ -57,22 +48,109 @@ fn dir_last_comparator( } } -/// Comparator that sorts [Node]s by size, smallest to largest. -fn size_rev_comparator(a: &Node, b: &Node) -> Ordering { - let a_size = a.file_size().map_or(0, |fs| fs.bytes); - let b_size = b.file_size().map_or(0, |fs| fs.bytes); +/// Grabs the comparator for two non-dir type [Node]s. +fn base_comparator(sort_type: sort::Type) -> Box { + Box::new(match sort_type { + sort::Type::Name => naming::comparator, + sort::Type::NameRev => naming::rev_comparator, + + sort::Type::Size => sizing::comparator, + sort::Type::SizeRev => sizing::rev_comparator, + + sort::Type::Access => time_stamping::accessed::comparator, + sort::Type::AccessRev => time_stamping::accessed::rev_comparator, + + sort::Type::Creation => time_stamping::created::comparator, + sort::Type::CreationRev => time_stamping::created::rev_comparator, + + sort::Type::Modification => time_stamping::modified::comparator, + sort::Type::ModificationRev => time_stamping::modified::rev_comparator, + }) +} + +mod time_stamping { + pub mod accessed { + use crate::render::tree::node::Node; + use core::cmp::Ordering; + use std::time::SystemTime; + + /// Comparator that sorts [Node]s by Last Access timestamp, newer to older. + pub fn comparator(a: &Node, b: &Node) -> Ordering { + let a_stamp = a.accessed().unwrap_or_else(SystemTime::now); + let b_stamp = b.accessed().unwrap_or_else(SystemTime::now); + a_stamp.cmp(&b_stamp) + } + + /// Comparator that sorts [Node]s by Access timestamp, older to newer. + pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { + comparator(b, a) + } + } + + pub mod created { + use crate::render::tree::node::Node; + use core::cmp::Ordering; + use std::time::SystemTime; + + /// Comparator that sorts [Node]s by Creation timestamp, newer to older. + pub fn comparator(a: &Node, b: &Node) -> Ordering { + let a_stamp = a.created().unwrap_or_else(SystemTime::now); + let b_stamp = b.created().unwrap_or_else(SystemTime::now); + a_stamp.cmp(&b_stamp) + } + + /// Comparator that sorts [Node]s by Creation timestamp, older to newer. + pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { + comparator(b, a) + } + } + + pub mod modified { + use crate::render::tree::node::Node; + use core::cmp::Ordering; + use std::time::SystemTime; + + /// Comparator that sorts [Node]s by Alteration timestamp, newer to older. + pub fn comparator(a: &Node, b: &Node) -> Ordering { + let a_stamp = a.modified().unwrap_or_else(SystemTime::now); + let b_stamp = b.modified().unwrap_or_else(SystemTime::now); + a_stamp.cmp(&b_stamp) + } - a_size.cmp(&b_size) + /// Comparator that sorts [Node]s by Alteration timestamp, older to newer. + pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { + comparator(b, a) + } + } } -/// Comparator that sorts [Node]s by size, largest to smallest. -fn size_comparator(a: &Node, b: &Node) -> Ordering { - let a_size = a.file_size().map_or(0, |fs| fs.bytes); - let b_size = b.file_size().map_or(0, |fs| fs.bytes); - b_size.cmp(&a_size) +mod sizing { + use crate::render::tree::node::Node; + use core::cmp::Ordering; + + /// Comparator that sorts [Node]s by size, largest to smallest. + pub fn comparator(a: &Node, b: &Node) -> Ordering { + let a_size = a.file_size().map_or(0, |fs| fs.bytes); + let b_size = b.file_size().map_or(0, |fs| fs.bytes); + b_size.cmp(&a_size) + } + /// Comparator that sorts [Node]s by size, smallest to largest. + pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { + comparator(b, a) + } } -/// Comparator based on [Node] file names. -fn name_comparator(a: &Node, b: &Node) -> Ordering { - a.file_name().cmp(b.file_name()) +mod naming { + use crate::render::tree::node::Node; + use core::cmp::Ordering; + + /// Comparator based on [Node] file names in lexicographical order. + pub fn comparator(a: &Node, b: &Node) -> Ordering { + a.file_name().cmp(b.file_name()) + } + + /// Comparator based on [Node] file names in reversed lexicographical order. + pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { + comparator(b, a) + } } diff --git a/src/render/tree/node/mod.rs b/src/render/tree/node/mod.rs index 244a0268..8787ff1e 100644 --- a/src/render/tree/node/mod.rs +++ b/src/render/tree/node/mod.rs @@ -128,13 +128,21 @@ impl Node { } /// Returns the underlying `ino` of the [`DirEntry`]. - pub fn ino(&self) -> Option { - self.inode.map(|inode| inode.ino) + pub const fn ino(&self) -> Option { + if let Some(inode) = self.inode { + Some(inode.ino) + } else { + None + } } /// Returns the underlying `nlink` of the [`DirEntry`]. - pub fn nlink(&self) -> Option { - self.inode.map(|inode| inode.nlink) + pub const fn nlink(&self) -> Option { + if let Some(inode) = self.inode { + Some(inode.nlink) + } else { + None + } } /// Returns `true` if node is a directory. diff --git a/src/tty/windows.rs b/src/tty/windows.rs index fb793e3e..796236b2 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -26,8 +26,7 @@ pub(super) unsafe fn win_width() -> Option { }; (GetConsoleScreenBufferInfo(stdout_handle, &mut console_data) != 0) - .then(|| console_data.srWindow.Right - console_data.srWindow.Left + 1) + .then_some(console_data.srWindow.Right - console_data.srWindow.Left + 1) .map(usize::try_from) - .map(Result::ok) - .flatten() + .and_then(Result::ok) }