xzip 0.4.1

ZIP CLI with explicit filename encoding for pack and unpack workflows.
Documentation
use std::io::{self, Write};
use std::path::Path;

use crate::archive::{collect_filtered_entries, compression_label, format_datetime, open_archive};
use crate::codec::EncodingKind;
use crate::error::XzipError;
use crate::filter::PathFilter;
use crate::verbosity::Verbosity;

pub fn list_archive(
    input_zip: &Path,
    encoding: EncodingKind,
    include: &[String],
    exclude: &[String],
    verbosity: Verbosity,
) -> Result<(), XzipError> {
    let mut archive = open_archive(input_zip)?;
    let filter = PathFilter::new(include, exclude)?;
    let entries = collect_filtered_entries(&mut archive, encoding, &filter)?;

    let show_compressed = verbosity.is_verbose();
    let show_method = verbosity.is_extra();

    let mut stdout = io::stdout().lock();
    write_table_header(&mut stdout, show_compressed, show_method)?;

    let mut total_size = 0u64;
    for entry in &entries {
        if !entry.is_dir {
            total_size += entry.size;
        }
        let (date, time) = format_datetime(entry.modified);
        write_table_row(
            &mut stdout,
            entry.size,
            &date,
            &time,
            &entry.name,
            if show_compressed {
                Some(entry.compressed_size)
            } else {
                None
            },
            if show_method {
                Some(compression_label(entry.compression))
            } else {
                None
            },
        )?;
    }

    write_table_footer(&mut stdout, total_size, entries.len())?;
    Ok(())
}

fn write_table_header(
    out: &mut impl Write,
    show_compressed: bool,
    show_method: bool,
) -> io::Result<()> {
    if show_method {
        writeln!(
            out,
            "  Length   Compressed      Date    Time    Method     Name"
        )?;
        writeln!(
            out,
            "---------  ----------  ---------- -----   ------   ----"
        )?;
    } else if show_compressed {
        writeln!(out, "  Length   Compressed      Date    Time    Name")?;
        writeln!(out, "---------  ----------  ---------- -----   ----")?;
    } else {
        writeln!(out, "  Length      Date    Time    Name")?;
        writeln!(out, "---------  ---------- -----   ----")?;
    }
    Ok(())
}

fn write_table_row(
    out: &mut impl Write,
    size: u64,
    date: &str,
    time: &str,
    name: &str,
    compressed: Option<u64>,
    method: Option<&str>,
) -> io::Result<()> {
    if let (Some(compressed), Some(method)) = (compressed, method) {
        writeln!(
            out,
            "{size:>9}  {compressed:>10}  {date} {time}   {method:<6}   {name}"
        )?;
    } else if let Some(compressed) = compressed {
        writeln!(out, "{size:>9}  {compressed:>10}  {date} {time}   {name}")?;
    } else {
        writeln!(out, "{size:>9}  {date} {time}   {name}")?;
    }
    Ok(())
}

fn write_table_footer(out: &mut impl Write, total_size: u64, count: usize) -> io::Result<()> {
    let label = if count == 1 { "file" } else { "files" };
    writeln!(out, "---------                     -------")?;
    writeln!(
        out,
        "{total_size:>9}                        {count} {label}"
    )?;
    Ok(())
}