Display sizes in human readable form

This commit is contained in:
Nathan Fisher 2024-02-24 01:32:13 -05:00
parent 3e9a34af7e
commit e3dddc3dd9
5 changed files with 141 additions and 95 deletions

View File

@ -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

View File

@ -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)

View File

@ -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::<String>("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(())
}

View File

@ -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"),
}
}
}

View File

@ -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} "),