summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Nguyen <benjamin.van.nguyen@gmail.com>2023-11-28 22:41:31 -0800
committerBenjamin Nguyen <benjamin.van.nguyen@gmail.com>2023-11-28 22:41:31 -0800
commit93d79b8a64d4fee8f8f404fb7b4f18cd27c25f44 (patch)
tree2f42847c895745e2b7fa5fd1791bae840859cf64
parent42970274da8c379da162941e45b6f907cc3e93c5 (diff)
sorting
-rw-r--r--src/file/mod.rs36
-rw-r--r--src/file/order.rs74
-rw-r--r--src/file/tree/mod.rs73
-rw-r--r--src/render/mod.rs14
-rw-r--r--src/render/row/mod.rs150
-rw-r--r--src/user/args.rs19
-rw-r--r--src/user/mod.rs6
7 files changed, 275 insertions, 97 deletions
diff --git a/src/file/mod.rs b/src/file/mod.rs
index d307022..279b1f4 100644
--- a/src/file/mod.rs
+++ b/src/file/mod.rs
@@ -43,10 +43,17 @@ pub struct File {
unix_attrs: unix::Attrs,
}
+/// [`Display`] implementation concerned with human-readable presentation of the file-name.
pub struct DisplayName<'a> {
file: &'a File,
}
+/// [`Display`] implementation concerned with human-readable presentation of the file-path.
+pub struct DisplayPath<'a> {
+ file: &'a File,
+ path_prefix: Option<&'a Path>,
+}
+
impl File {
/// Plain Jane constructor for [`File`].
pub fn new(
@@ -139,6 +146,10 @@ impl File {
DisplayName { file: self }
}
+ pub fn display_path<'a>(&'a self, path_prefix: Option<&'a Path>) -> DisplayPath<'a> {
+ DisplayPath { file: self, path_prefix }
+ }
+
#[cfg(unix)]
pub fn unix_attrs(&self) -> &unix::Attrs {
&self.unix_attrs
@@ -181,11 +192,32 @@ impl Deref for File {
impl Display for DisplayName<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let file_name = self.file.file_name().to_string_lossy();
+ let link_target = self.file.symlink_target().map(|p| p.canonicalize());
- if let Some(link_target) = self.file.symlink_target() {
- write!(f, "{file_name} \u{2192} {}", link_target.display())
+ if let Some(Ok(target)) = link_target {
+ write!(f, "{file_name} \u{2192} {}", target.display())
} else {
write!(f, "{file_name}")
}
}
}
+
+impl Display for DisplayPath<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let display = match self.path_prefix {
+ Some(prefix) => {
+ let path = self.file.path();
+ path.strip_prefix(prefix).map_or_else(|_| path.display(), |p| p.display())
+ },
+ None => self.file.path().display()
+ };
+
+ let link_target = self.file.symlink_target().map(|p| p.canonicalize());
+
+ if let Some(Ok(target)) = link_target {
+ write!(f, "{display} \u{2192} {}", target.display())
+ } else {
+ write!(f, "{display}")
+ }
+ }
+}
diff --git a/src/file/order.rs b/src/file/order.rs
index 5004e02..de22546 100644
--- a/src/file/order.rs
+++ b/src/file/order.rs
@@ -1,5 +1,5 @@
use super::File;
-use crate::user::{args::{Sort, DirOrder}, Context};
+use crate::user::args::{DirOrder, Sort};
use std::cmp::Ordering;
/// Comparator type used to sort [File]s.
@@ -7,23 +7,26 @@ pub type FileComparator = dyn Fn(&File, &File) -> Ordering;
/// Yields function pointer to the appropriate `File` comparator.
pub fn comparator(sort: Sort, dir_order: DirOrder) -> 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::First if matches!(sort, Sort::None) => Some(Box::new(move |a, b| {
+ dir_first_comparator(a, b)
+ })),
+ DirOrder::First => Some(Box::new(move |a, b| {
+ dir_first_comparator_with_fallback(a, b, base_comparator(sort))
+ })),
+ DirOrder::Last if matches!(sort, Sort::None) => Some(Box::new(move |a, b| {
+ dir_last_comparator(a, b)
+ })),
+ DirOrder::Last => Some(Box::new(move |a, b| {
+ dir_last_comparator_with_fallback(a, b, base_comparator(sort))
+ })),
+ DirOrder::None if matches!(sort, Sort::None) => None,
DirOrder::None => Some(base_comparator(sort)),
}
}
/// Orders directories first. Provides a fallback if inputs are not directories.
-fn dir_first_comparator(
+fn dir_first_comparator_with_fallback(
a: &File,
b: &File,
fallback: impl Fn(&File, &File) -> Ordering,
@@ -35,8 +38,17 @@ fn dir_first_comparator(
}
}
+/// Orders directories first relative to all other file-types.
+fn dir_first_comparator(a: &File, b: &File) -> Ordering {
+ match (a.is_dir(), b.is_dir()) {
+ (true, false) => Ordering::Greater,
+ (false, true) => Ordering::Less,
+ _ => Ordering::Equal
+ }
+}
+
/// Orders directories last. Provides a fallback if inputs are not directories.
-fn dir_last_comparator(
+fn dir_last_comparator_with_fallback(
a: &File,
b: &File,
fallback: impl Fn(&File, &File) -> Ordering,
@@ -48,6 +60,18 @@ fn dir_last_comparator(
}
}
+/// Orders directories last relative to all other file-types.
+fn dir_last_comparator(
+ a: &File,
+ b: &File,
+) -> Ordering {
+ match (a.is_dir(), b.is_dir()) {
+ (true, false) => Ordering::Less,
+ (false, true) => Ordering::Greater,
+ _ => Ordering::Equal
+ }
+}
+
/// Grabs the comparator for two non-dir type [File]s.
fn base_comparator(sort_type: Sort) -> Box<FileComparator> {
Box::new(match sort_type {
@@ -61,9 +85,7 @@ fn base_comparator(sort_type: Sort) -> Box<FileComparator> {
Sort::Rcreate => time_stamping::created::rev_comparator,
Sort::Mod => time_stamping::modified::comparator,
Sort::Rmod => time_stamping::modified::rev_comparator,
-
- // Hacky...
- Sort::None => unreachable!(),
+ Sort::None => |_: &File, _: &File| Ordering::Equal,
})
}
@@ -77,8 +99,14 @@ mod time_stamping {
/// 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());
+ 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)
}
@@ -109,8 +137,14 @@ mod time_stamping {
/// 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());
+ 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)
}
diff --git a/src/file/tree/mod.rs b/src/file/tree/mod.rs
index f296fe6..aaf2912 100644
--- a/src/file/tree/mod.rs
+++ b/src/file/tree/mod.rs
@@ -1,9 +1,9 @@
+use super::order::{self, FileComparator};
use crate::{
error::prelude::*,
file::File,
- user::{args::Sort, column, Context},
+ user::{args::{Layout, SortType, Sort}, column, Context},
};
-use super::order::{self, FileComparator};
use ahash::{HashMap, HashSet};
use indextree::{Arena, NodeId};
use std::{ops::Deref, path::PathBuf};
@@ -108,23 +108,63 @@ impl Tree {
}
}
- for (dir_id, dirsize) in dirsize_map.into_iter() {
- let dir = arena[dir_id].get_mut();
- *dir.size_mut() += dirsize;
-
- if let Some(dirents) = branches.remove(dir.path()) {
- for dirent_id in dirents {
- dir_id.append(dirent_id, &mut arena);
+ match order::comparator(ctx.sort, ctx.dir_order) {
+ Some(comparator) => match ctx.sort_type {
+ SortType::Flat if matches!(ctx.layout, Layout::Flat) => {
+ for (dir_id, dirsize) in dirsize_map.into_iter() {
+ let dir = arena[dir_id].get_mut();
+ *dir.size_mut() += dirsize;
+ }
+
+ let mut all_dirents = branches
+ .values()
+ .flatten()
+ .filter_map(|n| (*n != root_id).then_some(*n))
+ .collect::<Vec<_>>();
+
+ all_dirents.sort_by(|id_a, id_b| {
+ let node_a = arena[*id_a].get();
+ let node_b = arena[*id_b].get();
+ comparator(node_a, node_b)
+ });
+
+ all_dirents.into_iter().for_each(|n| root_id.append(n, &mut arena));
}
- }
+ _ => {
+ for (dir_id, dirsize) in dirsize_map.into_iter() {
+ let dir = arena[dir_id].get_mut();
+ *dir.size_mut() += dirsize;
+
+ if let Some(mut dirents) = branches.remove(dir.path()) {
+ dirents.sort_by(|id_a, id_b| {
+ let node_a = arena[*id_a].get();
+ let node_b = arena[*id_b].get();
+ comparator(node_a, node_b)
+ });
+
+ for dirent_id in dirents {
+ dir_id.append(dirent_id, &mut arena);
+ }
+ }
+ }
+ },
+ },
+ None => {
+ for (dir_id, dirsize) in dirsize_map.into_iter() {
+ let dir = arena[dir_id].get_mut();
+ *dir.size_mut() += dirsize;
+
+ if let Some(dirents) = branches.remove(dir.path()) {
+ for dirent_id in dirents {
+ dir_id.append(dirent_id, &mut arena);
+ }
+ }
+ }
+ },
}
column_metadata.update_size_width(arena[root_id].get(), ctx);
- //if let Some(comparator) = order::comparator(ctx.sort, ctx.dir_order) {
- //Self::tree_sort(root_id, &mut arena, comparator);
- //}
-
let tree = Self { root_id, arena };
Ok((tree, column_metadata))
@@ -236,9 +276,7 @@ impl Tree {
let to_prune = self
.root_id
.descendants(&self.arena)
- .filter(|n| {
- self.arena[*n].get().is_dir() && n.children(&self.arena).count() == 0
- })
+ .filter(|n| self.arena[*n].get().is_dir() && n.children(&self.arena).count() == 0)
.collect::<Vec<_>>();
to_prune
@@ -258,7 +296,6 @@ impl Tree {
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 8b66539..7345d84 100644
--- a/src/render/mod.rs
+++ b/src/render/mod.rs
@@ -26,13 +26,13 @@ mod row;
pub fn output(file_tree: &file::Tree, ctx: &Context) -> Result<String> {
match ctx.layout {
- Layout::Regular => tree(file_tree, ctx),
- Layout::Inverted => inverted_tree(file_tree, ctx),
+ Layout::Tree => tree(file_tree, ctx),
+ Layout::InvertedTree => inverted_tree(file_tree, ctx),
Layout::Flat => flat(file_tree, ctx),
}
}
-fn tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> {
+fn inverted_tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> {
let arena = file_tree.arena();
let root = file_tree.root_id();
let max_depth = ctx.level();
@@ -47,7 +47,7 @@ fn tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> {
let mut inherited_prefix_components = vec![""];
- let mut formatter = row::formatter(&mut buf, ctx);
+ let mut formatter = row::formatter(&mut buf, ctx)?;
let mut reverse_traverse = root.reverse_traverse(arena);
reverse_traverse.next();
@@ -105,7 +105,7 @@ fn tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> {
Ok(buf)
}
-pub fn inverted_tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> {
+pub fn tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> {
let arena = file_tree.arena();
let root = file_tree.root_id();
let max_depth = ctx.level();
@@ -120,7 +120,7 @@ pub fn inverted_tree(file_tree: &file::Tree, ctx: &Context) -> Result<String> {
let mut inherited_prefix_components = vec![""];
- let mut formatter = row::formatter(&mut buf, ctx);
+ let mut formatter = row::formatter(&mut buf, ctx)?;
let mut traverse = root.traverse(arena);
traverse.next();
@@ -188,7 +188,7 @@ fn flat(file_tree: &file::Tree, ctx: &Context) -> Result<String> {
let max_depth = ctx.level();
let mut buf = String::new();
- let mut formatter = row::formatter(&mut buf, ctx);
+ let mut formatter = row::formatter(&mut buf, ctx)?;
for node_edge in root.traverse(arena) {
let node_id = match node_edge {
diff --git a/src/render/row/mod.rs b/src/render/row/mod.rs
index 0be6391..f392340 100644
--- a/src/render/row/mod.rs
+++ b/src/render/row/mod.rs
@@ -1,6 +1,7 @@
use crate::{
+ error::prelude::*,
file::File,
- user::{column, Context},
+ user::{args::Layout, column, Context},
};
use std::fmt::{self, Write};
@@ -10,51 +11,112 @@ mod long;
pub type RowFormatter<'a> = Box<dyn FnMut(&File, String) -> fmt::Result + 'a>;
-#[cfg(windows)]
-pub fn formatter<'a>(buf: &'a mut String, ctx: &'a Context) -> RowFormatter<'a> {
- if ctx.suppress_size {
- Box::new(|file, prefix| {
- let name = file.display_name();
- writeln!(buf, "{prefix}{name}")
- })
- } else {
- Box::new(|file, prefix| {
- let size = format!("{}", file.size());
- let name = file.display_name();
- let column::Metadata { max_size_width, .. } = ctx.column_metadata;
- writeln!(buf, "{size:>max_size_width$} {prefix}{name}")
- })
+#[cfg(unix)]
+pub fn formatter<'a>(buf: &'a mut String, ctx: &'a Context) -> Result<RowFormatter<'a>> {
+ match ctx.layout {
+ Layout::Flat => {
+ let root = ctx.dir_canonical()?;
+
+ match (ctx.long, ctx.suppress_size) {
+ (false, false) => Ok(Box::new(move |file, prefix| {
+ let size = format!("{}", file.size());
+ let base = root.ancestors().nth(1);
+ let name = file.display_path(base);
+ let column::Metadata { max_size_width, .. } = ctx.column_metadata;
+ writeln!(buf, "{size:>max_size_width$} {prefix}{name}")
+ })),
+
+ (false, true) => Ok(Box::new(move |file, prefix| {
+ let base = root.ancestors().nth(1);
+ let name = file.display_path(base);
+ writeln!(buf, "{prefix}{name}")
+ })),
+
+ (true, false) => Ok(Box::new(move |file, prefix| {
+ let size = format!("{}", file.size());
+ let base = root.ancestors().nth(1);
+ let name = file.display_path(base);
+ let col_widths = ctx.column_metadata;
+ let column::Metadata { max_size_width, .. } = col_widths;
+ let long_format = long::Format::new(file, ctx);
+ writeln!(buf, "{long_format} {size:>max_size_width$} {prefix}{name}")
+ })),
+
+ (true, true) => Ok(Box::new(move |file, prefix| {
+ let base = root.ancestors().nth(1);
+ let name = file.display_path(base);
+ let long_format = long::Format::new(file, ctx);
+ writeln!(buf, "{long_format} {prefix}{name}")
+ })),
+ }
+ },
+ _ => match (ctx.long, ctx.suppress_size) {
+ (false, false) => Ok(Box::new(|file, prefix| {
+ let size = format!("{}", file.size());
+ let name = file.display_name();
+ let column::Metadata { max_size_width, .. } = ctx.column_metadata;
+ writeln!(buf, "{size:>max_size_width$} {prefix}{name}")
+ })),
+
+ (false, true) => Ok(Box::new(|file, prefix| {
+ let name = file.display_name();
+ writeln!(buf, "{prefix}{name}")
+ })),
+
+ (true, false) => Ok(Box::new(|file, prefix| {
+ let size = format!("{}", file.size());
+ let name = file.display_name();
+ let col_widths = ctx.column_metadata;
+ let column::Metadata { max_size_width, .. } = col_widths;
+ let long_format = long::Format::new(file, ctx);
+ writeln!(buf, "{long_format} {size:>max_size_width$} {prefix}{name}")
+ })),
+
+ (true, true) => Ok(Box::new(|file, prefix| {
+ let name = file.display_name();
+ let long_format = long::Format::new(file, ctx);
+ writeln!(buf, "{long_format} {prefix}{name}")
+ })),
+ }
}
}
-#[cfg(unix)]
-pub fn formatter<'a>(buf: &'a mut String, ctx: &'a Context) -> RowFormatter<'a> {
- match (ctx.long, ctx.suppress_size) {
- (false, false) => Box::new(|file, prefix| {
- let size = format!("{}", file.size());
- let name = file.display_name();
- let column::Metadata { max_size_width, .. } = ctx.column_metadata;
- writeln!(buf, "{size:>max_size_width$} {prefix}{name}")
- }),
-
- (false, true) => Box::new(|file, prefix| {
- let name = file.display_name();
- writeln!(buf, "{prefix}{name}")
- }),
-
- (true, false) => Box::new(|file, prefix| {
- let size = format!("{}", file.size());
- let name = file.display_name();
- let col_widths = ctx.column_metadata;
- let column::Metadata { max_size_width, .. } = col_widths;
- let long_format = long::Format::new(file, ctx);
- writeln!(buf, "{long_format} {size:>max_size_width$} {prefix}{name}")
- }),
-
- (true, true) => Box::new(|file, prefix| {
- let name = file.display_name();
- let long_format = long::Format::new(file, ctx);
- writeln!(buf, "{long_format} {prefix}{name}")
- })
+#[cfg(windows)]
+pub fn formatter<'a>(buf: &'a mut String, ctx: &'a Context) -> Result<RowFormatter<'a>> {
+ match ctx.layout {
+ Layout::Flat => {
+ let root = ctx.dir_canonical()?;
+
+ if ctx.suppress_size {
+ Ok(Box::new(move |file, prefix| {
+ let base = root.ancestors().nth(1);
+ let name = file.display_path(base);
+ writeln!(buf, "{prefix}{name}")
+ }))
+ } else {
+ Ok(Box::new(move |file, prefix| {
+ let size = format!("{}", file.size());
+ let base = root.ancestors().nth(1);
+ let name = file.display_path(base);
+ let column::Metadata { max_size_width, .. } = ctx.column_metadata;
+ writeln!(buf, "{size:>max_size_width$} {prefix}{name}")
+ }))
+ }
+ },
+ _ => {
+ if ctx.suppress_size {
+ Ok(Box::new(|file, prefix| {
+ let name = file.display_name();
+ writeln!(buf, "{prefix}{name}")
+ }))
+ } else {
+ Ok(Box::new(|file, prefix| {
+ let size = format!("{}", file.size());
+ let name = file.display_name();
+ let column::Metadata { max_size_width, .. } = ctx.column_metadata;
+ writeln!(buf, "{size:>max_size_width$} {prefix}{name}")
+ }))
+ }
+ }
}
}
diff --git a/src/user/args.rs b/src/user/args.rs
index d4753bb..4484be4 100644
--- a/src/user/args.rs
+++ b/src/user/args.rs
@@ -71,22 +71,21 @@ pub enum TimeFormat {
Default,
}
-/// Which layout to use when rendering the tree.
+/// Which layout to use when rendering the file-tree
#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Layout {
/// Outputs the tree with the root node at the bottom of the output
#[default]
- Regular,
+ InvertedTree,
/// Outputs the tree with the root node at the top of the output
- Inverted,
+ Tree,
/// 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).
+/// Which field to sort directory entries by
#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Sort {
/// No ordering.
@@ -129,6 +128,16 @@ pub enum Sort {
Rmod,
}
+/// Whether to sort directory entries relative either to their siblings or all directory entries
+#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)]
+pub enum SortType {
+ /// Sort directory entries relative to their siblings
+ #[default]
+ Tree,
+ /// Sort directory entries relative to all directory entries
+ Flat
+}
+
/// How directories should be ordered relative to regular files.
#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, Default)]
pub enum DirOrder {
diff --git a/src/user/mod.rs b/src/user/mod.rs
index e88ad89..86938ad 100644
--- a/src/user/mod.rs
+++ b/src/user/mod.rs
@@ -89,10 +89,14 @@ pub struct Context {
#[arg(short, long)]
pub prune: bool,
- /// How to sort entries
+ /// Field whereby to sort entries
#[arg(short, long, value_enum, default_value_t)]
pub sort: args::Sort,
+ /// Sort entries relative either to their siblings or all other entries
+ #[arg(long, value_enum, default_value_t)]
+ pub sort_type: args::SortType,
+
/// Sort directories before or after all other file types
#[arg(short, long, value_enum, default_value_t)]
pub dir_order: args::DirOrder,