diff --git a/src/cli.rs b/src/cli.rs index 5bdfa0f7..f23080f5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -59,6 +59,10 @@ pub struct Clargs { #[arg(short, long, value_enum, default_value_t = Order::None)] sort: Order, + /// Always sorts directories above files + #[arg(long)] + dirs_first: bool, + /// Traverse symlink directories and consider their disk usage; disabled by default #[arg(short = 'S', long)] follow_links: bool, @@ -96,6 +100,10 @@ impl Clargs { self.sort } + pub fn dirs_first(&self) -> bool { + self.dirs_first + } + /// 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) -> Option { diff --git a/src/fs/erdtree/order.rs b/src/fs/erdtree/order.rs index 4813172c..c568b4ec 100644 --- a/src/fs/erdtree/order.rs +++ b/src/fs/erdtree/order.rs @@ -1,24 +1,58 @@ -use crate::cli; use super::node::Node; -use std::{ - convert::From, - cmp::Ordering, -}; +use crate::cli; +use std::{cmp::Ordering, convert::From}; /// Order in which to print nodes. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum Order { +pub enum SortType { Name, Size, None, } +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Order { + sort: SortType, + dir_first: bool, +} + impl Order { /// Yields function pointer to the appropriate `Node` comparator. - pub fn comparator(&self) -> Option Ordering> { + pub fn comparator(&self) -> Option Ordering + '_>> { + if self.dir_first { + Some(Box::new(|a, b| { + Self::dir_comparator(a, b, self.sort.comparator()) + })) + } else { + 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, + _ => { + if let Some(sort) = fallback { + sort(a, b) + } else { + Ordering::Equal + } + } + } + } +} + +impl SortType { + /// Yields function pointer to the appropriate `Node` comparator. + pub fn comparator(&self) -> Option Ordering>> { match self { - Self::Name => Some(Self::name_comparator), - Self::Size => Some(Self::size_comparator), + Self::Name => Some(Box::new(Self::name_comparator)), + Self::Size => Some(Box::new(Self::size_comparator)), _ => None, } } @@ -37,12 +71,21 @@ impl Order { } } -impl From for Order { +impl From<(cli::Order, bool)> for Order { + fn from((order, dir_first): (cli::Order, bool)) -> Self { + Order { + sort: order.into(), + dir_first, + } + } +} + +impl From for SortType { fn from(ord: cli::Order) -> Self { match ord { - cli::Order::Name => Order::Name, - cli::Order::Size => Order::Size, - cli::Order::None => Order::None + cli::Order::Name => SortType::Name, + cli::Order::Size => SortType::Size, + cli::Order::None => SortType::None, } } } diff --git a/src/fs/erdtree/tree/mod.rs b/src/fs/erdtree/tree/mod.rs index de68d229..8684785e 100644 --- a/src/fs/erdtree/tree/mod.rs +++ b/src/fs/erdtree/tree/mod.rs @@ -150,7 +150,7 @@ impl TryFrom for Tree { fn try_from(clargs: Clargs) -> Result { let walker = WalkParallel::try_from(&clargs)?; - let order = Order::from(clargs.sort()); + let order = Order::from((clargs.sort(), clargs.dirs_first())); let tree = Tree::new(walker, order, clargs.level(), clargs.icons)?; Ok(tree) } diff --git a/tests/data/lipsum/lipsum.txt b/tests/data/lipsum/lipsum.txt new file mode 100644 index 00000000..1b376877 --- /dev/null +++ b/tests/data/lipsum/lipsum.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/tests/glob.rs b/tests/glob.rs index d9b3e293..ca1ca0be 100644 --- a/tests/glob.rs +++ b/tests/glob.rs @@ -8,9 +8,11 @@ fn glob() { utils::run_cmd(&["--sort", "name", "--glob", "*.txt", "tests/data"]), indoc!( " - data (652.00 B) + data (1.10 KB) ├─ dream_cycle (308.00 B) │ └─ polaris.txt (308.00 B) + ├─ lipsum (446.00 B) + │ └─ lipsum.txt (446.00 B) ├─ necronomicon.txt (83.00 B) ├─ nemesis.txt (161.00 B) ├─ nylarlathotep.txt (100.00 B) @@ -27,6 +29,7 @@ fn glob_negative() { " data (143.00 B) ├─ dream_cycle + ├─ lipsum └─ the_yellow_king (143.00 B) └─ cassildas_song.md (143.00 B)" ) @@ -46,9 +49,11 @@ fn glob_case_insensitive() { ]), indoc!( " - data (652.00 B) + data (1.10 KB) ├─ dream_cycle (308.00 B) │ └─ polaris.txt (308.00 B) + ├─ lipsum (446.00 B) + │ └─ lipsum.txt (446.00 B) ├─ necronomicon.txt (83.00 B) ├─ nemesis.txt (161.00 B) ├─ nylarlathotep.txt (100.00 B) @@ -63,9 +68,11 @@ fn iglob() { utils::run_cmd(&["--sort", "name", "--iglob", "*.TXT", "tests/data"]), indoc!( " - data (652.00 B) + data (1.10 KB) ├─ dream_cycle (308.00 B) │ └─ polaris.txt (308.00 B) + ├─ lipsum (446.00 B) + │ └─ lipsum.txt (446.00 B) ├─ necronomicon.txt (83.00 B) ├─ nemesis.txt (161.00 B) ├─ nylarlathotep.txt (100.00 B) diff --git a/tests/level.rs b/tests/level.rs index 78db5f3b..6779c560 100644 --- a/tests/level.rs +++ b/tests/level.rs @@ -8,8 +8,9 @@ fn level() { utils::run_cmd(&["--sort", "name", "--level", "1", "tests/data"]), indoc!( " - data (795.00 B) + data (1.24 KB) ├─ dream_cycle (308.00 B) + ├─ lipsum (446.00 B) ├─ necronomicon.txt (83.00 B) ├─ nemesis.txt (161.00 B) ├─ nylarlathotep.txt (100.00 B) diff --git a/tests/sort.rs b/tests/sort.rs index 7fb45fb4..5117221a 100644 --- a/tests/sort.rs +++ b/tests/sort.rs @@ -8,9 +8,11 @@ fn sort_name() { utils::run_cmd(&["--sort", "name", "tests/data"]), indoc!( " - data (795.00 B) + data (1.24 KB) ├─ dream_cycle (308.00 B) │ └─ polaris.txt (308.00 B) + ├─ lipsum (446.00 B) + │ └─ lipsum.txt (446.00 B) ├─ necronomicon.txt (83.00 B) ├─ nemesis.txt (161.00 B) ├─ nylarlathotep.txt (100.00 B) @@ -21,13 +23,36 @@ fn sort_name() { ) } +#[test] +fn sort_name_dir_first() { + assert_eq!( + utils::run_cmd(&["--sort", "name", "--dirs-first", "tests/data"]), + indoc!( + " + data (1.24 KB) + ├─ dream_cycle (308.00 B) + │ └─ polaris.txt (308.00 B) + ├─ lipsum (446.00 B) + │ └─ lipsum.txt (446.00 B) + ├─ the_yellow_king (143.00 B) + │ └─ cassildas_song.md (143.00 B) + ├─ necronomicon.txt (83.00 B) + ├─ nemesis.txt (161.00 B) + └─ nylarlathotep.txt (100.00 B)" + ), + "Failed to sort by directory and alphabetically by file name" + ) +} + #[test] fn sort_size() { assert_eq!( utils::run_cmd(&["--sort", "size", "tests/data"]), indoc!( " - data (795.00 B) + data (1.24 KB) + ├─ lipsum (446.00 B) + │ └─ lipsum.txt (446.00 B) ├─ dream_cycle (308.00 B) │ └─ polaris.txt (308.00 B) ├─ nemesis.txt (161.00 B) @@ -39,3 +64,24 @@ fn sort_size() { "Failed to sort by descending size" ) } + +#[test] +fn sort_size_dir_first() { + assert_eq!( + utils::run_cmd(&["--sort", "size", "--dirs-first", "tests/data"]), + indoc!( + " + data (1.24 KB) + ├─ lipsum (446.00 B) + │ └─ lipsum.txt (446.00 B) + ├─ dream_cycle (308.00 B) + │ └─ polaris.txt (308.00 B) + ├─ the_yellow_king (143.00 B) + │ └─ cassildas_song.md (143.00 B) + ├─ nemesis.txt (161.00 B) + ├─ nylarlathotep.txt (100.00 B) + └─ necronomicon.txt (83.00 B)" + ), + "Failed to sort by directory and descending size" + ) +}