use crate::utils::*;
use clap::Args;
use flate2::write::GzEncoder;
use flate2::{read::GzDecoder, Compression};
use std::fs::File;
use std::io::{self, Read, Write};
#[derive(Args, Debug)]
pub struct GzipArgs {
#[clap(flatten)]
pub common_args: CommonArgs,
#[clap(flatten)]
pub level_args: LevelArgs,
}
pub struct Gzip {
pub compression_level: u32,
}
impl Default for Gzip {
fn default() -> Self {
Gzip {
compression_level: 6,
}
}
}
impl Gzip {
pub fn new(args: &GzipArgs) -> Gzip {
Gzip {
compression_level: args.level_args.level.level,
}
}
}
impl Compressor for Gzip {
fn extension(&self) -> &str {
"gz"
}
fn name(&self) -> &str {
"gzip"
}
fn default_extracted_filename(&self, in_path: &std::path::Path) -> String {
if in_path.extension().is_none() {
return "archive".to_string();
}
in_path.file_stem().unwrap().to_str().unwrap().to_string()
}
fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> {
if let CmprssOutput::Path(out_path) = &output {
if out_path.is_dir() {
return cmprss_error("Gzip does not support compressing to a directory. Please specify an output file.");
}
}
if let CmprssInput::Path(input_paths) = &input {
for x in input_paths {
if x.is_dir() {
return cmprss_error(
"Gzip does not support compressing a directory. Please specify only files.",
);
}
}
}
let mut input_stream = match input {
CmprssInput::Path(paths) => {
if paths.len() > 1 {
return cmprss_error("only 1 file can be compressed at a time");
}
Box::new(File::open(paths[0].as_path())?)
}
CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>,
};
let output_stream = match output {
CmprssOutput::Path(path) => Box::new(File::create(path)?),
CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>,
};
let mut encoder = GzEncoder::new(output_stream, Compression::new(self.compression_level));
std::io::copy(&mut input_stream, &mut encoder)?;
encoder.finish()?;
Ok(())
}
fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> {
let input_stream = match input {
CmprssInput::Path(paths) => {
if paths.len() > 1 {
return cmprss_error("only 1 file can be extracted at a time");
}
Box::new(File::open(paths[0].as_path())?)
}
CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>,
};
let mut output_stream = match output {
CmprssOutput::Path(path) => Box::new(File::create(path)?),
CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>,
};
let mut decoder = GzDecoder::new(input_stream);
std::io::copy(&mut decoder, &mut output_stream)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_fs::prelude::*;
use predicates::prelude::*;
#[test]
fn roundtrip() -> Result<(), Box<dyn std::error::Error>> {
let compressor = Gzip::default();
let file = assert_fs::NamedTempFile::new("test.txt")?;
file.write_str("garbage data for testing")?;
let working_dir = assert_fs::TempDir::new()?;
let archive = working_dir.child("archive.".to_owned() + compressor.extension());
archive.assert(predicate::path::missing());
compressor.compress(
CmprssInput::Path(vec![file.path().to_path_buf()]),
CmprssOutput::Path(archive.path().to_path_buf()),
)?;
archive.assert(predicate::path::is_file());
compressor.extract(
CmprssInput::Path(vec![archive.path().to_path_buf()]),
CmprssOutput::Path(working_dir.child("test.txt").path().to_path_buf()),
)?;
working_dir
.child("test.txt")
.assert(predicate::path::eq_file(file.path()));
Ok(())
}
}