#[cfg(feature = "flate2")]
pub mod gzip;
#[cfg(feature = "zstd")]
pub mod zstd;
#[cfg(feature = "xz2")]
pub mod lzma;
#[cfg(feature = "bzip2")]
pub mod bzip2;
pub mod detection;
use std::io::{self, Read};
use std::path::Path;
use crate::grep::error::{GrepError, GrepResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CompressionFormat {
None,
Gzip,
Zstd,
Xz,
Bzip2,
}
impl CompressionFormat {
pub fn from_extension(path: &Path) -> Self {
match path.extension().and_then(|e| e.to_str()) {
Some("gz") | Some("gzip") => Self::Gzip,
Some("zst") | Some("zstd") => Self::Zstd,
Some("xz") | Some("lzma") => Self::Xz,
Some("bz2") | Some("bzip2") => Self::Bzip2,
_ => Self::None,
}
}
pub fn extension(&self) -> Option<&'static str> {
match self {
Self::None => None,
Self::Gzip => Some("gz"),
Self::Zstd => Some("zst"),
Self::Xz => Some("xz"),
Self::Bzip2 => Some("bz2"),
}
}
pub fn name(&self) -> &'static str {
match self {
Self::None => "plain",
Self::Gzip => "gzip",
Self::Zstd => "zstd",
Self::Xz => "xz",
Self::Bzip2 => "bzip2",
}
}
pub fn is_available(&self) -> bool {
match self {
Self::None => true,
Self::Gzip => cfg!(feature = "flate2"),
Self::Zstd => cfg!(feature = "zstd"),
Self::Xz => cfg!(feature = "xz2"),
Self::Bzip2 => cfg!(feature = "bzip2"),
}
}
pub fn required_feature(&self) -> Option<&'static str> {
match self {
Self::None => None,
Self::Gzip => Some("flate2"),
Self::Zstd => Some("zstd"),
Self::Xz => Some("xz2"),
Self::Bzip2 => Some("bzip2"),
}
}
}
impl std::fmt::Display for CompressionFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
pub type BoxedReader = Box<dyn Read + Send>;
pub struct PassthroughReader<R> {
inner: R,
}
impl<R: Read> PassthroughReader<R> {
pub fn new(reader: R) -> Self {
Self { inner: reader }
}
}
impl<R: Read> Read for PassthroughReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
pub fn create_decompressor<R: Read + Send + 'static>(
reader: R,
format: CompressionFormat,
) -> GrepResult<BoxedReader> {
match format {
CompressionFormat::None => Ok(Box::new(PassthroughReader::new(reader))),
#[cfg(feature = "flate2")]
CompressionFormat::Gzip => Ok(Box::new(gzip::GzipDecompressor::new(reader))),
#[cfg(not(feature = "flate2"))]
CompressionFormat::Gzip => Err(GrepError::FeatureNotEnabled {
feature: "flate2".to_string(),
}),
#[cfg(feature = "zstd")]
CompressionFormat::Zstd => {
let decoder = zstd::ZstdDecompressor::new(reader)
.map_err(|e| GrepError::decompression("", e.to_string()))?;
Ok(Box::new(decoder))
}
#[cfg(not(feature = "zstd"))]
CompressionFormat::Zstd => Err(GrepError::FeatureNotEnabled {
feature: "zstd".to_string(),
}),
#[cfg(feature = "xz2")]
CompressionFormat::Xz => Ok(Box::new(lzma::XzDecompressor::new(reader))),
#[cfg(not(feature = "xz2"))]
CompressionFormat::Xz => Err(GrepError::FeatureNotEnabled {
feature: "xz2".to_string(),
}),
#[cfg(feature = "bzip2")]
CompressionFormat::Bzip2 => Ok(Box::new(bzip2::Bzip2Decompressor::new(reader))),
#[cfg(not(feature = "bzip2"))]
CompressionFormat::Bzip2 => Err(GrepError::FeatureNotEnabled {
feature: "bzip2".to_string(),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_from_extension() {
assert_eq!(
CompressionFormat::from_extension(Path::new("file.gz")),
CompressionFormat::Gzip
);
assert_eq!(
CompressionFormat::from_extension(Path::new("file.zst")),
CompressionFormat::Zstd
);
assert_eq!(
CompressionFormat::from_extension(Path::new("file.xz")),
CompressionFormat::Xz
);
assert_eq!(
CompressionFormat::from_extension(Path::new("file.bz2")),
CompressionFormat::Bzip2
);
assert_eq!(
CompressionFormat::from_extension(Path::new("file.txt")),
CompressionFormat::None
);
}
#[test]
fn test_format_display() {
assert_eq!(CompressionFormat::Gzip.to_string(), "gzip");
assert_eq!(CompressionFormat::None.to_string(), "plain");
}
#[test]
fn test_passthrough_reader() {
let data = b"hello world";
let mut reader = PassthroughReader::new(&data[..]);
let mut buf = Vec::new();
reader.read_to_end(&mut buf).expect("read should succeed");
assert_eq!(buf, data);
}
}