diff options
author | Benjamin Nguyen <benjamin.van.nguyen@gmail.com> | 2023-11-27 13:37:46 -0800 |
---|---|---|
committer | Benjamin Nguyen <benjamin.van.nguyen@gmail.com> | 2023-11-27 13:37:46 -0800 |
commit | ef7b333ac9635cdf2cd78b204cedf42a2da8fcc1 (patch) | |
tree | 56c96544cd443336d8981e3b797a431b5a9e9a6e | |
parent | 9c62bb3f4c3d78eeb379840afb105d9d26946d1d (diff) |
order wip
-rw-r--r-- | src/file/mod.rs | 7 | ||||
-rw-r--r-- | src/file/order.rs | 153 | ||||
-rw-r--r-- | src/file/tree/mod.rs | 30 | ||||
-rw-r--r-- | src/render/mod.rs | 14 | ||||
-rw-r--r-- | src/user/args.rs | 58 | ||||
-rw-r--r-- | src/user/mod.rs | 8 |
6 files changed, 251 insertions, 19 deletions
diff --git a/src/file/mod.rs b/src/file/mod.rs index 70ac5d8..d307022 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -18,6 +18,9 @@ use std::{ pub mod inode; use inode::{INodeError, Inode}; +/// Rules on how to order entries relative to their siblings or all other files. +pub mod order; + /// Concerned with the tree data structure that is used to produce the program output. pub mod tree; pub use tree::Tree; @@ -161,6 +164,10 @@ impl File { }) .map(|dt| format!("{dt}")) } + + pub fn is_dir(&self) -> bool { + self.file_type().is_some_and(|ft| ft.is_dir()) + } } impl Deref for File { diff --git a/src/file/order.rs b/src/file/order.rs new file mode 100644 index 0000000..c982147 --- /dev/null +++ b/src/file/order.rs @@ -0,0 +1,153 @@ +use super::File; +use crate::user::{args::{Sort, DirOrder}, Context}; +use std::cmp::Ordering; + +/// Comparator type used to sort [File]s. +pub type FileComparator = dyn Fn(&File, &File) -> Ordering; + +/// Yields function pointer to the appropriate `File` comparator. +pub fn comparator(Context { sort, dir_order, .. }: &Context) -> Option<Box<FileComparator>> { + if matches!(sort, Sort::None) { + return None; + } + + match dir_order { + DirOrder::First => { + Some(Box::new(move |a, b| dir_first_comparator(a, b, base_comparator(*sort)))) + }, + DirOrder::Last => { + Some(Box::new(move |a, b| dir_last_comparator(a, b, base_comparator(*sort)))) + }, + DirOrder::None => Some(base_comparator(*sort)), + } +} + +/// Orders directories first. Provides a fallback if inputs are not directories. +fn dir_first_comparator( + a: &File, + b: &File, + fallback: impl Fn(&File, &File) -> Ordering, +) -> Ordering { + match (a.is_dir(), b.is_dir()) { + (true, false) => Ordering::Greater, + (false, true) => Ordering::Less, + _ => fallback(a, b), + } +} + +/// Orders directories last. Provides a fallback if inputs are not directories. +fn dir_last_comparator( + a: &File, + b: &File, + fallback: impl Fn(&File, &File) -> Ordering, +) -> Ordering { + match (a.is_dir(), b.is_dir()) { + (true, false) => Ordering::Less, + (false, true) => Ordering::Greater, + _ => fallback(a, b), + } +} + +/// Grabs the comparator for two non-dir type [File]s. +fn base_comparator(sort_type: Sort) -> Box<FileComparator> { + Box::new(match sort_type { + Sort::Name => naming::comparator, + Sort::Rname => naming::rev_comparator, + Sort::Size => sizing::comparator, + Sort::Rsize => sizing::rev_comparator, + Sort::Access => time_stamping::accessed::comparator, + Sort::Raccess => time_stamping::accessed::rev_comparator, + Sort::Create => time_stamping::created::comparator, + Sort::Rcreate => time_stamping::created::rev_comparator, + Sort::Mod => time_stamping::modified::comparator, + Sort::Rmod => time_stamping::modified::rev_comparator, + + // Hacky... + Sort::None => unreachable!(), + }) +} + +mod time_stamping { + pub(self) use super::File; + pub(self) use core::cmp::Ordering; + pub(self) use std::time::SystemTime; + + pub mod accessed { + use super::*; + + /// Comparator that sorts [File]s by Last Access timestamp, newer to older. + pub fn comparator(a: &File, b: &File) -> Ordering { + let a_stamp = a.metadata().accessed().unwrap_or_else(|_| SystemTime::now()); + let b_stamp = b.metadata().accessed().unwrap_or_else(|_| SystemTime::now()); + b_stamp.cmp(&a_stamp) + } + + /// Comparator that sorts [File]s by Access timestamp, older to newer. + pub fn rev_comparator(a: &File, b: &File) -> Ordering { + comparator(b, a) + } + } + + pub mod created { + use super::*; + + /// Comparator that sorts [File]s by Creation timestamp, newer to older. + pub fn comparator(a: &File, b: &File) -> Ordering { + let a_stamp = a.metadata().created().unwrap_or_else(|_| SystemTime::now()); + let b_stamp = b.metadata().created().unwrap_or_else(|_| SystemTime::now()); + b_stamp.cmp(&a_stamp) + } + + /// Comparator that sorts [File]s by Creation timestamp, older to newer. + pub fn rev_comparator(a: &File, b: &File) -> Ordering { + comparator(b, a) + } + } + + pub mod modified { + use super::*; + + /// Comparator that sorts [File]s by Alteration timestamp, newer to older. + pub fn comparator(a: &File, b: &File) -> Ordering { + let a_stamp = a.metadata().modified().unwrap_or_else(|_| SystemTime::now()); + let b_stamp = b.metadata().modified().unwrap_or_else(|_| SystemTime::now()); + b_stamp.cmp(&a_stamp) + } + + /// Comparator that sorts [File]s by Alteration timestamp, older to newer. + pub fn rev_comparator(a: &File, b: &File) -> Ordering { + comparator(b, a) + } + } +} + +mod sizing { + use super::File; + use std::cmp::Ordering; + + /// Comparator that sorts [File]s by size, largest to smallest. + pub fn comparator(a: &File, b: &File) -> Ordering { + let a_size = a.size().value(); + let b_size = b.size().value(); + b_size.cmp(&a_size) + } + /// Comparator that sorts [File]s by size, smallest to largest. + pub fn rev_comparator(a: &File, b: &File) -> Ordering { + comparator(b, a) + } +} + +mod naming { + use super::File; + use std::cmp::Ordering; + + /// Comparator based on [File] file names in lexicographical order. + pub fn comparator(a: &File, b: &File) -> Ordering { + a.file_name().cmp(b.file_name()) + } + + /// Comparator based on [File] file names in reversed lexicographical order. + pub fn rev_comparator(a: &File, b: &File) -> Ordering { + comparator(b, a) + } +} diff --git a/src/file/tree/mod.rs b/src/file/tree/mod.rs index ccbda92..6a6888a 100644 --- a/src/file/tree/mod.rs +++ b/src/file/tree/mod.rs @@ -1,8 +1,9 @@ use crate::{ error::prelude::*, file::File, - user::{column, Context}, + user::{args::Sort, column, Context}, }; +use super::order::{self, FileComparator}; use ahash::{HashMap, HashSet}; use indextree::{Arena, NodeId}; use std::{ops::Deref, path::PathBuf}; @@ -104,6 +105,10 @@ impl Tree { column_metadata.update_size_width(arena[root_id].get(), ctx); + if let Some(comparator) = order::comparator(ctx) { + Self::tree_sort(root_id, &mut arena, comparator); + } + let tree = Self { root_id, arena }; Ok((tree, column_metadata)) @@ -134,15 +139,15 @@ impl Tree { let child_node = arena[child_node_id].get(); - let inode = match child_node.inode() { + #[cfg(unix)] + match child_node.inode() { Ok(value) => { - #[cfg(unix)] column_metadata.update_inode_attr_widths(&value); value }, Err(err) => { log::warn!( - "Failed to query inode of {}", + "Failed to query inode of {}: {err}", child_node.path().display(), ); continue; @@ -158,6 +163,12 @@ impl Tree { dfs_stack.pop(); } + if !matches!(ctx.sort, Sort::Size | Sort::Rsize) { + if let Some(comparator) = order::comparator(ctx) { + Self::tree_sort(root_id, &mut arena, comparator); + } + } + let tree = Self { root_id, arena }; Ok((tree, column_metadata)) @@ -210,10 +221,7 @@ impl Tree { .root_id .descendants(&self.arena) .filter(|n| { - self.arena[*n] - .get() - .file_type() - .is_some_and(|ft| ft.is_dir() && n.children(&self.arena).count() == 0) + self.arena[*n].get().is_dir() && n.children(&self.arena).count() == 0 }) .collect::<Vec<_>>(); @@ -229,6 +237,12 @@ impl Tree { pub fn arena(&self) -> &Arena<File> { &self.arena } + + /// Sort [`File`]s in the `arena` with the provided `comparator`. + pub fn tree_sort(root_id: NodeId, arena: &mut Arena<File>, comparator: Box<FileComparator>) { + todo!() + } + } impl Deref for Tree { diff --git a/src/render/mod.rs b/src/render/mod.rs index 01d689f..24a5358 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -58,7 +58,7 @@ fn tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> { let node = arena[node_id].get(); let depth = node.depth(); - if utils::node_is_dir(node) { + if node.is_dir() { inherited_prefix_components.pop(); } @@ -72,7 +72,7 @@ fn tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> { let node = arena[node_id].get(); let depth = node.depth(); - if utils::node_is_dir(node) { + if node.is_dir() { if is_first_sibling(node_id, depth) { inherited_prefix_components.push(SEP); } else { @@ -145,7 +145,7 @@ pub fn inverted_tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> { let node = arena[node_id].get(); let depth = node.depth(); - if utils::node_is_dir(node) && depth < max_depth { + if node.is_dir() && depth < max_depth { inherited_prefix_components.pop(); } continue; @@ -208,11 +208,3 @@ fn flat(file_tree: &file::Tree, ctx: &Context) -> Result<String> { Ok(buf) } - -mod utils { - use crate::file::File; - - pub fn node_is_dir(node: &File) -> bool { - node.file_type().is_some_and(|ft| ft.is_dir()) - } -} diff --git a/src/user/args.rs b/src/user/args.rs index 4c30fde..d4753bb 100644 --- a/src/user/args.rs +++ b/src/user/args.rs @@ -84,3 +84,61 @@ pub enum Layout { /// Outputs a flat layout using paths rather than an ASCII tree Flat, } + +/// Order in which to print entries relative to their siblings (tree layouts) or all others (flat +/// layout). +#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)] +pub enum Sort { + /// No ordering. + #[default] + None, + + /// 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 + 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, +} + +/// How directories should be ordered relative to regular files. +#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, Default)] +pub enum DirOrder { + /// No particular ordering for directories relative to other files + #[default] + None, + + /// Sort directories above files + First, + + /// Sort directories below files + Last, +} diff --git a/src/user/mod.rs b/src/user/mod.rs index e34ff91..e88ad89 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -89,6 +89,14 @@ pub struct Context { #[arg(short, long)] pub prune: bool, + /// How to sort entries + #[arg(short, long, value_enum, default_value_t)] + pub sort: args::Sort, + + /// Sort directories before or after all other file types + #[arg(short, long, value_enum, default_value_t)] + pub dir_order: args::DirOrder, + /// Which kind of layout to use when rendering the output #[arg(short = 'y', long, value_enum, default_value_t)] pub layout: args::Layout, |