rloc 0.1.0

A fast, modern Rust implementation of cloc (Count Lines of Code)
Documentation
use flate2::read::GzDecoder;
use std::fs::File;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use tar::Archive as TarArchive;
use zip::ZipArchive;

pub fn is_archive(path: &Path) -> bool {
    let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
    let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");

    matches!(ext, "zip" | "tar" | "gz" | "tgz") || name.ends_with(".tar.gz")
}

pub fn extract_archive(path: &Path, dest: &Path) -> io::Result<Vec<PathBuf>> {
    let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
    let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");

    if ext == "zip" {
        extract_zip(path, dest)
    } else if ext == "tgz" || name.ends_with(".tar.gz") {
        extract_tar_gz(path, dest)
    } else if ext == "tar" {
        extract_tar(path, dest)
    } else if ext == "gz" {
        extract_tar_gz(path, dest)
    } else {
        Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "Unknown archive format",
        ))
    }
}

fn extract_zip(path: &Path, dest: &Path) -> io::Result<Vec<PathBuf>> {
    let file = File::open(path)?;
    let mut archive = ZipArchive::new(file)?;
    let mut extracted = Vec::new();

    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let outpath = match file.enclosed_name() {
            Some(path) => dest.join(path),
            None => continue,
        };

        if file.is_dir() {
            std::fs::create_dir_all(&outpath)?;
        } else {
            if let Some(parent) = outpath.parent() {
                std::fs::create_dir_all(parent)?;
            }
            let mut outfile = File::create(&outpath)?;
            io::copy(&mut file, &mut outfile)?;
            extracted.push(outpath);
        }
    }

    Ok(extracted)
}

fn extract_tar(path: &Path, dest: &Path) -> io::Result<Vec<PathBuf>> {
    let file = File::open(path)?;
    extract_tar_from_reader(file, dest)
}

fn extract_tar_gz(path: &Path, dest: &Path) -> io::Result<Vec<PathBuf>> {
    let file = File::open(path)?;
    let decoder = GzDecoder::new(file);
    extract_tar_from_reader(decoder, dest)
}

fn extract_tar_from_reader<R: Read>(reader: R, dest: &Path) -> io::Result<Vec<PathBuf>> {
    let mut archive = TarArchive::new(reader);
    let mut extracted = Vec::new();

    for entry in archive.entries()? {
        let mut entry = entry?;
        let path = entry.path()?;
        let outpath = dest.join(&path);

        if entry.header().entry_type().is_dir() {
            std::fs::create_dir_all(&outpath)?;
        } else if entry.header().entry_type().is_file() {
            if let Some(parent) = outpath.parent() {
                std::fs::create_dir_all(parent)?;
            }
            entry.unpack(&outpath)?;
            extracted.push(outpath);
        }
    }

    Ok(extracted)
}