sax 0.1.0

Smart archiving and extracting utility
use anyhow::{Context, Result, bail};
use std::{
    fs::{self, File},
    path::Path,
};
use zip::ZipArchive;

pub fn extract_archive(path: &Path, out: &Path) -> Result<()> {
    if !path.exists() {
        bail!("'{}' does not exist.", path.display());
    }

    let archive_type = detect_archive_type(path)?;

    match archive_type {
        ArchiveType::Zip => extract_zip(path, out),
    }
}

fn extract_zip(path: &Path, out: &Path) -> Result<()> {
    let file = File::open(path).context("Failed to open ZIP archive")?;
    let mut archive = ZipArchive::new(file).context("Failed to read ZIP archive")?;

    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let out_path = out.join(file.mangled_name());

        if file.name().ends_with('/') {
            fs::create_dir_all(&out_path)?;
        } else {
            if let Some(p) = out_path.parent() {
                fs::create_dir_all(p)?;
            }

            let mut out_file = fs::File::create(&out_path)?;
            std::io::copy(&mut file, &mut out_file)?;
        }
    }

    Ok(())
}

fn detect_archive_type(path: &Path) -> Result<ArchiveType> {
    let name = path
        .file_name()
        .and_then(|s| s.to_str())
        .map(|s| s.to_ascii_lowercase())
        .ok_or_else(|| anyhow::anyhow!("invalid archive filename"))?;

    match () {
        _ if name.ends_with(".zip") => Ok(ArchiveType::Zip),
        _ => bail!("unsupported archive type: {}", path.display()),
    }
}

pub enum ArchiveType {
    Zip,
}