use anyhow::Result;
use clap::{ArgAction, Args};
use log::debug;
use nuts_archive::{Archive, Entry, Group};
use std::cmp;
use std::fmt::{self, Write};
use crate::backend::PluginBackend;
use crate::cli::open_container;
use crate::say;
use crate::time::TimeFormat;
const SIZE_WIDTH: usize = 9;
struct Type<'a>(&'a Entry<'a, PluginBackend>);
impl<'a> fmt::Display for Type<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Entry::File(_) => fmt.write_char('-'),
Entry::Directory(_) => fmt.write_char('d'),
Entry::Symlink(_) => fmt.write_char('l'),
}
}
}
struct Permission<'a>(&'a Entry<'a, PluginBackend>, Group);
impl<'a> fmt::Display for Permission<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if self.0.can_read(self.1) {
fmt.write_char('r')?;
} else {
fmt.write_char('-')?;
};
if self.0.can_write(self.1) {
fmt.write_char('w')?;
} else {
fmt.write_char('-')?;
};
if self.0.can_execute(self.1) {
fmt.write_char('x')?;
} else {
fmt.write_char('-')?;
};
Ok(())
}
}
struct Name<'a>(&'a Entry<'a, PluginBackend>);
impl<'a> fmt::Display for Name<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if let Some(symlink) = self.0.as_symlink() {
write!(fmt, "{} -> {}", self.0.name(), symlink.target())
} else {
write!(fmt, "{}", self.0.name())
}
}
}
#[derive(Debug)]
struct PrintLongContext {
size_width: usize,
}
impl Default for PrintLongContext {
fn default() -> Self {
Self {
size_width: SIZE_WIDTH,
}
}
}
#[derive(Args, Debug)]
pub struct ArchiveListArgs {
#[clap(short, long, action = ArgAction::SetTrue)]
long: bool,
#[clap(short, long, action = ArgAction::SetTrue)]
appended: bool,
#[clap(short = 'r', long, action = ArgAction::SetTrue)]
created: bool,
#[clap(short = 'n', long, action = ArgAction::SetTrue)]
changed: bool,
#[clap(
short,
long,
value_parser,
value_name = "FORMAT",
default_value = "local"
)]
time_format: TimeFormat,
#[clap(short, long, env = "NUTS_CONTAINER")]
container: String,
#[clap(from_global)]
verbose: u8,
}
impl ArchiveListArgs {
fn print_short(&self, entry: &Entry<PluginBackend>) {
say!("{}", entry.name());
}
fn print_long(&self, entry: &Entry<PluginBackend>, ctx: &mut PrintLongContext) {
let tstamp = if self.appended {
entry.appended()
} else if self.created {
entry.created()
} else if self.changed {
entry.changed()
} else {
entry.modified()
};
let size = entry.size().to_string();
ctx.size_width = cmp::max(ctx.size_width, size.len());
say!(
"{}{}{}{} {:>size_width$} {} {}",
Type(entry),
Permission(entry, Group::User),
Permission(entry, Group::Group),
Permission(entry, Group::Other),
size,
self.time_format.format(tstamp, "%d %b %H:%M"),
Name(entry),
size_width = ctx.size_width,
);
}
pub fn run(&self) -> Result<()> {
debug!("args: {:?}", self);
let container = open_container(&self.container, self.verbose)?;
let mut archive = Archive::open(container)?;
let mut entry_opt = archive.first();
let mut ctx_opt = None;
loop {
match entry_opt {
Some(Ok(entry)) => {
if self.long {
let ctx = ctx_opt.get_or_insert_with(Default::default);
self.print_long(&entry, ctx);
} else {
self.print_short(&entry);
}
entry_opt = entry.next();
}
Some(Err(err)) => return Err(err.into()),
None => break,
}
}
Ok(())
}
}