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 path to error message when passing between threads
- [x] Add ability to write archives to stdout - [x] Add ability to write archives to stdout
- [x] Add ability to read archives from stdin - [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') .short('t')
.long("total") .long("total")
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
Arg::new("human")
.help("Display sizes in human-readable form")
.short('H')
.long("human")
.action(ArgAction::SetTrue),
Arg::new("archive") Arg::new("archive")
.num_args(1) .num_args(1)
.required(true) .required(true)

View File

@ -11,7 +11,10 @@ use {
io::{self, BufReader, BufWriter}, io::{self, BufReader, BufWriter},
os::fd::{AsRawFd, FromRawFd}, os::fd::{AsRawFd, FromRawFd},
process, process,
sync::{Arc, atomic::{AtomicU64, Ordering}, mpsc}, sync::{
atomic::{AtomicU64, Ordering},
mpsc, Arc,
},
thread, thread,
}, },
walkdir::WalkDir, walkdir::WalkDir,
@ -212,7 +215,10 @@ fn extract(matches: &ArgMatches) -> Result<(), haggis::Error> {
match handle.join() { match handle.join() {
Ok(_) => { Ok(_) => {
if matches.get_flag("total") { 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") { } else if matches.get_flag("quiet") {
println!("Archive extracted successfully"); 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("color") {
if matches.get_flag("long") { if matches.get_flag("long") {
li.print_color()?; li.print_color(matches.get_flag("human"))?;
} else { } else {
li.print_color_simple()?; li.print_color_simple()?;
} }
} else if matches.get_flag("long") { } else if matches.get_flag("long") {
println!("{li}"); li.print(matches.get_flag("human"));
} else { } else {
println!("{}", li.name); 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> { fn list_unsorted(matches: &ArgMatches) -> Result<(), haggis::Error> {
let mut total: u64 = 0;
let file = matches.get_one::<String>("archive").unwrap(); let file = matches.get_one::<String>("archive").unwrap();
let fd = File::open(file)?; let mut fd = File::open(file)?;
if matches.get_flag("zstd") { let zst = matches.get_flag("zstd") || haggis::detect_zstd(&mut fd)?;
if zst {
let reader = Decoder::new(fd)?; let reader = Decoder::new(fd)?;
let stream = NodeStream::new(reader)?; let stream = NodeStream::new(reader)?;
for node in stream { for node in stream {
let node = node?; let node = node?;
let li = Listing::from(node); let li = Listing::from(node);
print_listing(&li, matches)?; print_listing(&li, matches)?;
if matches.get_flag("total") {
if let ListingKind::Normal(s) = li.kind {
total += s;
}
}
} }
} else { } else {
let reader = BufReader::new(fd); let reader = BufReader::new(fd);
@ -305,8 +318,16 @@ fn list_unsorted(matches: &ArgMatches) -> Result<(), haggis::Error> {
for li in stream { for li in stream {
let li = li?; let li = li?;
print_listing(&li, matches)?; 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(()) Ok(())
} }

View File

@ -17,11 +17,11 @@ pub enum HumanSize {
impl fmt::Display for HumanSize { impl fmt::Display for HumanSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Bytes(b) => write!(f, "{b}"), Self::Bytes(b) => write!(f, "{b:>7}"),
Self::KiloBytes(k) => write!(f, "{k:.1}K"), Self::KiloBytes(k) => write!(f, "{k:>6.1}K"),
Self::MegaBytes(m) => write!(f, "{m:.1}M"), Self::MegaBytes(m) => write!(f, "{m:>6.1}M"),
Self::GigaBytes(g) => write!(f, "{g:.1}G"), Self::GigaBytes(g) => write!(f, "{g:>6.1}G"),
Self::TeraBytes(t) => write!(f, "{t:.1}T"), Self::TeraBytes(t) => write!(f, "{t:>6.1}T"),
} }
} }
} }

View File

@ -5,11 +5,10 @@ use {
}; };
use { use {
crate::{filetype::Flag, Error, FileType, Node, Special}, crate::{filetype::Flag, Error, FileType, HumanSize, Node, Special},
chrono::NaiveDateTime, chrono::NaiveDateTime,
std::{ std::{
cmp::Ordering, cmp::Ordering,
fmt,
io::{Read, Seek, SeekFrom}, 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 { impl Listing {
/// Reads all metadata from a haggis Node without reading the file's actual /// Reads all metadata from a haggis Node without reading the file's actual
/// data. /// 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, /// Prints a line with relavent metadata included,
/// colorizing the filename based on the filetype. /// colorizing the filename based on the filetype.
/// Similar to GNU `ls --long --color auto` /// Similar to GNU `ls --long --color auto`
@ -299,7 +306,7 @@ impl Listing {
/// Can return an `Error` if stdout is unavailable /// Can return an `Error` if stdout is unavailable
#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_possible_wrap)]
#[cfg(feature = "color")] #[cfg(feature = "color")]
pub fn print_color(&self) -> Result<(), Error> { pub fn print_color(&self, human: bool) -> Result<(), Error> {
print!( print!(
"{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} ", "{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} ",
match self.kind { match self.kind {
@ -355,8 +362,20 @@ impl Listing {
self.gid, self.gid,
); );
match self.kind { match self.kind {
Kind::Normal(s) => print!("{s:>10} "), Kind::Normal(s) => {
_ => print!("{:>10} ", "-"), 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) { match NaiveDateTime::from_timestamp_opt(self.mtime as i64, 0) {
Some(dt) => print!("{dt} "), Some(dt) => print!("{dt} "),