From e3dddc3dd9898a073e24d835b1cc36e4af11690c Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Sat, 24 Feb 2024 01:32:13 -0500 Subject: [PATCH] Display sizes in human readable form --- README.md | 3 +- src/cli.rs | 5 ++ src/haggis.rs | 33 +++++++-- src/humansize.rs | 10 +-- src/listing.rs | 185 ++++++++++++++++++++++++++--------------------- 5 files changed, 141 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 7a8b6a0..addc3ce 100644 --- a/README.md +++ b/README.md @@ -153,4 +153,5 @@ to see Haggis implemented in other languages. - [x] Add path to error message when passing between threads - [x] Add ability to write archives to stdout - [x] Add ability to read archives from stdin -- [ ] Add option to display total size to archive listings +- [x] Add option to display total size to archive listings +- [x] Optionally display sizes in human readable form diff --git a/src/cli.rs b/src/cli.rs index fe9918a..a43e001 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -164,6 +164,11 @@ pub fn list() -> Command { .short('t') .long("total") .action(ArgAction::SetTrue), + Arg::new("human") + .help("Display sizes in human-readable form") + .short('H') + .long("human") + .action(ArgAction::SetTrue), Arg::new("archive") .num_args(1) .required(true) diff --git a/src/haggis.rs b/src/haggis.rs index 1a30ac8..1f58158 100644 --- a/src/haggis.rs +++ b/src/haggis.rs @@ -11,7 +11,10 @@ use { io::{self, BufReader, BufWriter}, os::fd::{AsRawFd, FromRawFd}, process, - sync::{Arc, atomic::{AtomicU64, Ordering}, mpsc}, + sync::{ + atomic::{AtomicU64, Ordering}, + mpsc, Arc, + }, thread, }, walkdir::WalkDir, @@ -212,7 +215,10 @@ fn extract(matches: &ArgMatches) -> Result<(), haggis::Error> { match handle.join() { Ok(_) => { if matches.get_flag("total") { - println!("{} extracted", HumanSize::from(total.load(Ordering::Relaxed))); + println!( + "{} extracted", + HumanSize::from(total.load(Ordering::Relaxed)) + ); } else if matches.get_flag("quiet") { println!("Archive extracted successfully"); } @@ -276,12 +282,12 @@ fn print_listing(li: &Listing, matches: &ArgMatches) -> Result<(), haggis::Error } if matches.get_flag("color") { if matches.get_flag("long") { - li.print_color()?; + li.print_color(matches.get_flag("human"))?; } else { li.print_color_simple()?; } } else if matches.get_flag("long") { - println!("{li}"); + li.print(matches.get_flag("human")); } else { println!("{}", li.name); } @@ -289,15 +295,22 @@ fn print_listing(li: &Listing, matches: &ArgMatches) -> Result<(), haggis::Error } fn list_unsorted(matches: &ArgMatches) -> Result<(), haggis::Error> { + let mut total: u64 = 0; let file = matches.get_one::("archive").unwrap(); - let fd = File::open(file)?; - if matches.get_flag("zstd") { + let mut fd = File::open(file)?; + let zst = matches.get_flag("zstd") || haggis::detect_zstd(&mut fd)?; + if zst { let reader = Decoder::new(fd)?; let stream = NodeStream::new(reader)?; for node in stream { let node = node?; let li = Listing::from(node); print_listing(&li, matches)?; + if matches.get_flag("total") { + if let ListingKind::Normal(s) = li.kind { + total += s; + } + } } } else { let reader = BufReader::new(fd); @@ -305,8 +318,16 @@ fn list_unsorted(matches: &ArgMatches) -> Result<(), haggis::Error> { for li in stream { let li = li?; print_listing(&li, matches)?; + if matches.get_flag("total") { + if let ListingKind::Normal(s) = li.kind { + total += s; + } + } } } + if matches.get_flag("total") { + println!("Total: {}", HumanSize::from(total)); + } Ok(()) } diff --git a/src/humansize.rs b/src/humansize.rs index ee5d3ef..1f9328d 100644 --- a/src/humansize.rs +++ b/src/humansize.rs @@ -17,11 +17,11 @@ pub enum HumanSize { impl fmt::Display for HumanSize { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Bytes(b) => write!(f, "{b}"), - Self::KiloBytes(k) => write!(f, "{k:.1}K"), - Self::MegaBytes(m) => write!(f, "{m:.1}M"), - Self::GigaBytes(g) => write!(f, "{g:.1}G"), - Self::TeraBytes(t) => write!(f, "{t:.1}T"), + Self::Bytes(b) => write!(f, "{b:>7}"), + Self::KiloBytes(k) => write!(f, "{k:>6.1}K"), + Self::MegaBytes(m) => write!(f, "{m:>6.1}M"), + Self::GigaBytes(g) => write!(f, "{g:>6.1}G"), + Self::TeraBytes(t) => write!(f, "{t:>6.1}T"), } } } diff --git a/src/listing.rs b/src/listing.rs index 0001e49..e0ee47b 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -5,11 +5,10 @@ use { }; use { - crate::{filetype::Flag, Error, FileType, Node, Special}, + crate::{filetype::Flag, Error, FileType, HumanSize, Node, Special}, chrono::NaiveDateTime, std::{ cmp::Ordering, - fmt, io::{Read, Seek, SeekFrom}, }, }; @@ -172,84 +171,6 @@ impl Ord for Listing { } } -impl fmt::Display for Listing { - #[allow(clippy::cast_possible_wrap)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} ", - match self.kind { - Kind::Normal(_) => "-", - Kind::HardLink(_) => "L", - Kind::SoftLink(_) => "l", - Kind::Directory => "d", - Kind::Character(_) => "c", - Kind::Block(_) => "b", - Kind::Fifo => "p", - Kind::Eof => return Ok(()), - }, - match self.mode { - m if m & 0o400 != 0 => "r", - _ => "-", - }, - match self.mode { - m if m & 0o200 != 0 => "w", - _ => "-", - }, - match self.mode { - m if m & 0o4000 != 0 => "S", - m if m & 0o100 != 0 => "x", - _ => "-", - }, - match self.mode { - m if m & 0o40 != 0 => "r", - _ => "-", - }, - match self.mode { - m if m & 0o20 != 0 => "w", - _ => "-", - }, - match self.mode { - m if m & 0o2000 != 0 => "S", - m if m & 0o10 != 0 => "x", - _ => "-", - }, - match self.mode { - m if m & 0o4 != 0 => "r", - _ => "-", - }, - match self.mode { - m if m & 0o2 != 0 => "w", - _ => "-", - }, - match self.mode { - m if m & 0o1000 != 0 => "t", - m if m & 0o1 != 0 => "x", - _ => "-", - }, - self.uid, - self.gid, - )?; - match self.kind { - Kind::Normal(s) => write!(f, "{s:>10} "), - _ => write!(f, "{:>10}", "-"), - }?; - match NaiveDateTime::from_timestamp_opt(self.mtime as i64, 0) { - Some(dt) => write!(f, "{dt} ")?, - _ => write!(f, "{:>19} ", self.mtime)?, - } - match self.kind { - Kind::Directory | Kind::Fifo | Kind::Normal(_) => write!(f, "{}", self.name), - Kind::HardLink(ref tgt) => write!(f, "{}=>{}", self.name, tgt), - Kind::SoftLink(ref tgt) => write!(f, "{}->{}", self.name, tgt), - Kind::Character(ref sp) | Kind::Block(ref sp) => { - write!(f, "{} {},{}", self.name, sp.major, sp.minor) - } - Kind::Eof => unreachable!(), - } - } -} - impl Listing { /// Reads all metadata from a haggis Node without reading the file's actual /// data. @@ -292,6 +213,92 @@ impl Listing { }) } + pub fn print(&self, human: bool) { + print!( + "{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} ", + match self.kind { + Kind::Normal(_) => "-", + Kind::HardLink(_) => "L", + Kind::SoftLink(_) => "l", + Kind::Directory => "d", + Kind::Character(_) => "c", + Kind::Block(_) => "b", + Kind::Fifo => "p", + Kind::Eof => return, + }, + match self.mode { + m if m & 0o400 != 0 => "r", + _ => "-", + }, + match self.mode { + m if m & 0o200 != 0 => "w", + _ => "-", + }, + match self.mode { + m if m & 0o4000 != 0 => "S", + m if m & 0o100 != 0 => "x", + _ => "-", + }, + match self.mode { + m if m & 0o40 != 0 => "r", + _ => "-", + }, + match self.mode { + m if m & 0o20 != 0 => "w", + _ => "-", + }, + match self.mode { + m if m & 0o2000 != 0 => "S", + m if m & 0o10 != 0 => "x", + _ => "-", + }, + match self.mode { + m if m & 0o4 != 0 => "r", + _ => "-", + }, + match self.mode { + m if m & 0o2 != 0 => "w", + _ => "-", + }, + match self.mode { + m if m & 0o1000 != 0 => "t", + m if m & 0o1 != 0 => "x", + _ => "-", + }, + self.uid, + self.gid, + ); + match self.kind { + Kind::Normal(s) => { + if human { + print!("{} ", HumanSize::from(s)); + } else { + print!("{s:>10} "); + } + } + _ => { + if human { + print!("{:>7} ", "-"); + } else { + print!("{:>10} ", "-"); + } + } + }; + match NaiveDateTime::from_timestamp_opt(self.mtime as i64, 0) { + Some(dt) => print!("{dt} "), + _ => print!("{:>19} ", self.mtime), + } + match self.kind { + Kind::Directory | Kind::Fifo | Kind::Normal(_) => println!("{}", self.name), + Kind::HardLink(ref tgt) => println!("{}=>{}", self.name, tgt), + Kind::SoftLink(ref tgt) => println!("{}->{}", self.name, tgt), + Kind::Character(ref sp) | Kind::Block(ref sp) => { + println!("{} {},{}", self.name, sp.major, sp.minor) + } + Kind::Eof => unreachable!(), + } + } + /// Prints a line with relavent metadata included, /// colorizing the filename based on the filetype. /// Similar to GNU `ls --long --color auto` @@ -299,7 +306,7 @@ impl Listing { /// Can return an `Error` if stdout is unavailable #[allow(clippy::cast_possible_wrap)] #[cfg(feature = "color")] - pub fn print_color(&self) -> Result<(), Error> { + pub fn print_color(&self, human: bool) -> Result<(), Error> { print!( "{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} ", match self.kind { @@ -355,8 +362,20 @@ impl Listing { self.gid, ); match self.kind { - Kind::Normal(s) => print!("{s:>10} "), - _ => print!("{:>10} ", "-"), + Kind::Normal(s) => { + if human { + print!("{:>10} ", HumanSize::from(s)); + } else { + print!("{s:>10} "); + } + } + _ => { + if human { + print!("{:>7} ", "-"); + } else { + print!("{:>10} ", "-"); + } + } } match NaiveDateTime::from_timestamp_opt(self.mtime as i64, 0) { Some(dt) => print!("{dt} "),