diff --git a/src/render/context/mod.rs b/src/render/context/mod.rs index 7cd1e3c8..a54a7f66 100644 --- a/src/render/context/mod.rs +++ b/src/render/context/mod.rs @@ -1,6 +1,6 @@ use super::{ disk_usage::{DiskUsage, PrefixKind}, - order::SortType, + order::{DirectoryOrdering, SortType}, }; use clap::{ArgMatches, CommandFactory, Error as ClapError, FromArgMatches, Parser}; use ignore::overrides::{Override, OverrideBuilder}; @@ -81,9 +81,9 @@ pub struct Context { #[arg(short, long, value_enum)] sort: Option, - /// Always sorts directories above files - #[arg(long)] - dirs_first: bool, + /// Orders directories within branch arms + #[arg(short = 'D', long, value_name = "ORDER")] + dir_order: Option, /// Traverse symlink directories and consider their disk usage; disabled by default #[arg(short = 'S', long)] @@ -151,9 +151,9 @@ impl Context { self.sort } - /// Getter for `dirs_first` field. - pub fn dirs_first(&self) -> bool { - self.dirs_first + /// Getter for `dir_order` field. + pub fn dir_ordering(&self) -> Option { + self.dir_order } /// The max depth to print. Note that all directories are fully traversed to compute file diff --git a/src/render/order.rs b/src/render/order.rs index a2e0e8f7..20bf6fce 100644 --- a/src/render/order.rs +++ b/src/render/order.rs @@ -1,4 +1,4 @@ -use super::tree::node::Node; +use super::{context::Context, tree::node::Node}; use clap::ValueEnum; use std::{cmp::Ordering, convert::From}; @@ -15,48 +15,49 @@ pub enum SortType { SizeRev, } +/// Order in which to print directories. +#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)] +pub enum DirectoryOrdering { + /// Order directories before files + First, + + /// Order directories after files + Last, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Order { - sort: SortType, - dir_first: bool, + sort: Option, + dir: Option, } /// Comparator type used to sort [Node]s. -pub type NodeComparator<'a> = dyn Fn(&Node, &Node) -> Ordering + 'a; +pub type NodeComparator = dyn Fn(&Node, &Node) -> Ordering; impl Order { /// Yields function pointer to the appropriate `Node` comparator. - pub fn comparator(&self) -> Option>> { - if self.dir_first { - return Some(Box::new(|a, b| { - Self::dir_comparator(a, b, self.sort.comparator()) - })); - } - - self.sort.comparator() - } - - fn dir_comparator( - a: &Node, - b: &Node, - fallback: Option Ordering>, - ) -> Ordering { - match (a.is_dir(), b.is_dir()) { - (true, false) => Ordering::Less, - (false, true) => Ordering::Greater, - _ => fallback.map_or_else(|| Ordering::Equal, |sort| sort(a, b)), - } + pub fn comparators(&self) -> impl Iterator> { + [ + self.sort.as_ref().map(SortType::comparator), + self.dir.as_ref().map(DirectoryOrdering::comparator), + ] + .into_iter() + .filter(|comparator| comparator.is_some()) + // UNWRAP: we just filtered Nones out + .map(|comparator| comparator.unwrap()) } } impl SortType { /// Yields function pointer to the appropriate `Node` comparator. - pub fn comparator(&self) -> Option Ordering>> { - match self { - Self::Name => Some(Box::new(Self::name_comparator)), - Self::Size => Some(Box::new(Self::size_comparator)), - Self::SizeRev => Some(Box::new(Self::size_rev_comparator)), - } + pub fn comparator(&self) -> Box Ordering> { + let comparator = match self { + Self::Name => Self::name_comparator, + Self::Size => Self::size_comparator, + Self::SizeRev => Self::size_rev_comparator, + }; + + Box::new(comparator) } /// Comparator based on `Node` file names. @@ -80,8 +81,47 @@ impl SortType { } } -impl From<(SortType, bool)> for Order { - fn from((sort, dir_first): (SortType, bool)) -> Self { - Order { sort, dir_first } +impl DirectoryOrdering { + /// Yields function pointer to the appropriate directory comparator. + pub fn comparator(&self) -> Box { + let comparator = match self { + Self::First => Self::first_comparator, + Self::Last => Self::last_comparator, + }; + + Box::new(comparator) + } + + /// Comparator based on directory presedence. + fn first_comparator(a: &Node, b: &Node) -> Ordering { + match (a.is_dir(), b.is_dir()) { + (true, false) => Ordering::Less, + (false, true) => Ordering::Greater, + _ => Ordering::Equal, + } + } + + /// Comparator based on non-directory presedence. + fn last_comparator(a: &Node, b: &Node) -> Ordering { + match (a.is_dir(), b.is_dir()) { + (false, true) => Ordering::Less, + (true, false) => Ordering::Greater, + _ => Ordering::Equal, + } + } +} + +impl<'a> From<&'a Context> for Order { + fn from(ctx: &'a Context) -> Self { + Self { + sort: ctx.sort(), + dir: ctx.dir_ordering(), + } + } +} + +impl From<(Option, Option)> for Order { + fn from((sort, dir): (Option, Option)) -> Self { + Self { sort, dir } } } diff --git a/src/render/tree/mod.rs b/src/render/tree/mod.rs index dea93f95..25e4626f 100644 --- a/src/render/tree/mod.rs +++ b/src/render/tree/mod.rs @@ -1,4 +1,8 @@ -use crate::render::{context::Context, disk_usage::FileSize, order::Order}; +use crate::render::{ + context::Context, + disk_usage::FileSize, + order::{Order, SortType}, +}; use crossbeam::channel::{self, Sender}; use error::Error; use ignore::{WalkBuilder, WalkParallel, WalkState}; @@ -13,6 +17,8 @@ use std::{ thread, }; +use super::order::DirectoryOrdering; + /// Errors related to traversal, [Tree] construction, and the like. pub mod error; @@ -169,10 +175,10 @@ impl Tree { current_node.set_file_size(dir_size) } - if let Some(ordr) = ctx.sort().map(|s| Order::from((s, ctx.dirs_first()))) { - ordr.comparator() - .map(|func| current_node.sort_children(func)); - } + let apply_comparator = |comparator| current_node.sort_children(comparator); + Order::from((ctx.sort(), ctx.dir_ordering())) + .comparators() + .for_each(apply_comparator); } } diff --git a/src/render/tree/node.rs b/src/render/tree/node.rs index c277a08d..85650c67 100644 --- a/src/render/tree/node.rs +++ b/src/render/tree/node.rs @@ -86,7 +86,7 @@ impl Node { } /// Sorts `children` given comparator. - pub fn sort_children(&mut self, comparator: Box>) { + pub fn sort_children(&mut self, comparator: Box) { self.children.sort_by(comparator) }