#![doc = include_str!("../examples/unpack.rs")]
#![doc = include_str!("../examples/unzip.rs")]
#![allow(clippy::missing_const_for_fn)]
#![allow(clippy::module_name_repetitions)]
pub mod decompressors;
use derive_builder::Builder;
use std::borrow::Cow;
use std::{convert::Infallible, io, path::Path};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DecompressError {
#[error("could not decompress: `{0}`")]
IO(#[from] io::Error),
#[error("could not decompress: `{0}`")]
Error(String),
#[error("could not decompress: `{0}`")]
Infallible(#[from] Infallible),
#[error("no compressor found")]
MissingCompressor,
}
pub type FilterFn = dyn Fn(&Path) -> bool;
pub type MapFn = dyn Fn(&Path) -> Cow<'_, Path>;
#[derive(Builder)]
#[builder(pattern = "owned")]
pub struct ExtractOpts {
#[builder(default)]
pub strip: usize,
#[builder(setter(custom), default = "Box::new(|_| true)")]
pub filter: Box<FilterFn>,
#[builder(setter(custom), default = "Box::new(|path| Cow::from(path))")]
pub map: Box<MapFn>,
}
impl ExtractOptsBuilder {
#[must_use]
pub fn filter(mut self, value: impl Fn(&Path) -> bool + 'static) -> Self {
self.filter = Some(Box::new(value));
self
}
#[must_use]
pub fn map(mut self, value: impl Fn(&Path) -> Cow<'_, Path> + 'static) -> Self {
self.map = Some(Box::new(value));
self
}
}
#[derive(Debug)]
pub struct Decompression {
pub id: &'static str,
pub files: Vec<String>,
}
#[derive(Debug)]
pub struct Listing {
pub id: &'static str,
pub entries: Vec<String>,
}
pub trait Decompressor {
fn test(&self, archive: &Path) -> bool;
fn list(&self, archive: &Path) -> Result<Listing, DecompressError>;
fn decompress(
&self,
archive: &Path,
to: &Path,
opts: &ExtractOpts,
) -> Result<Decompression, DecompressError>;
}
pub struct Decompress {
decompressors: Vec<Box<dyn Decompressor>>,
}
impl Default for Decompress {
fn default() -> Self {
Self {
decompressors: vec![
#[cfg(feature = "zip")]
Box::<decompressors::zip::Zip>::default(),
#[cfg(feature = "targz")]
Box::<decompressors::targz::Targz>::default(),
#[cfg(feature = "tarball")]
Box::<decompressors::tarball::Tarball>::default(),
#[cfg(feature = "tarxz")]
Box::<decompressors::tarxz::Tarxz>::default(),
#[cfg(feature = "tarbz")]
Box::<decompressors::tarbz::Tarbz>::default(),
#[cfg(feature = "tarzst")]
Box::<decompressors::tarzst::Tarzst>::default(),
#[cfg(feature = "gz")]
Box::<decompressors::gz::Gz>::default(),
#[cfg(feature = "ar")]
Box::<decompressors::ar::Ar>::default(),
#[cfg(feature = "bz2")]
Box::<decompressors::bz2::Bz2>::default(),
#[cfg(feature = "xz")]
Box::<decompressors::xz::Xz>::default(),
#[cfg(feature = "zstd")]
Box::<decompressors::zstd::Zstd>::default(),
],
}
}
}
impl Decompress {
#[must_use]
pub fn build(decompressors: Vec<Box<dyn Decompressor>>) -> Self {
Self { decompressors }
}
pub fn list<P: AsRef<Path>>(&self, archive: P) -> Result<Listing, DecompressError> {
if let Some(dec) = self
.decompressors
.iter()
.find(|dec| dec.test(archive.as_ref()))
{
return dec.list(archive.as_ref());
}
Err(DecompressError::MissingCompressor)
}
pub fn decompress<P: AsRef<Path>>(
&self,
archive: P,
to: P,
opts: &ExtractOpts,
) -> Result<Decompression, DecompressError> {
if let Some(dec) = self
.decompressors
.iter()
.find(|dec| dec.test(archive.as_ref()))
{
return dec.decompress(archive.as_ref(), to.as_ref(), opts);
}
Err(DecompressError::MissingCompressor)
}
pub fn can_decompress<P: AsRef<Path>>(&self, archive: P) -> bool {
self.decompressors
.iter()
.any(|dec| dec.test(archive.as_ref()))
}
}
pub fn decompress<P: AsRef<Path>>(
archive: P,
to: P,
opts: &ExtractOpts,
) -> Result<Decompression, DecompressError> {
Decompress::default().decompress(archive, to, opts)
}
pub fn list<P: AsRef<Path>>(archive: P) -> Result<Listing, DecompressError> {
Decompress::default().list(archive)
}