mirror of
https://github.com/ouch-org/ouch.git
synced 2025-07-23 10:00:37 +00:00
Add support for Bzip compression (includes .tar.bz2 and .zip.bz2 and etc)
This commit is contained in:
parent
433f8b05b0
commit
bdc16fdb17
103
src/compressors/bzip.rs
Normal file
103
src/compressors/bzip.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use std::{fs, io::{self, Read, Write}, path::PathBuf};
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
use crate::{error::{Error, OuchResult}, extension::CompressionFormat, file::File};
|
||||||
|
use crate::utils::ensure_exists;
|
||||||
|
|
||||||
|
use super::{Compressor, Entry};
|
||||||
|
|
||||||
|
pub struct BzipCompressor {}
|
||||||
|
|
||||||
|
struct CompressorToMemory {}
|
||||||
|
|
||||||
|
// impl CompressorToMemory {
|
||||||
|
// pub fn compress_files(files: Vec<PathBuf>, format: CompressionFormat) -> OuchResult<Vec<u8>> {
|
||||||
|
// let mut buffer = vec![];
|
||||||
|
|
||||||
|
// if files.len() != 1 {
|
||||||
|
// eprintln!("{}: cannot compress multiple files directly to {:#?}.\n Try using an intermediate archival method such as Tar.\n Example: filename.tar{}", "error".red(), format, format);
|
||||||
|
// return Err(Error::InvalidInput);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let mut contents = Vec::new();
|
||||||
|
// let path = &files[0];
|
||||||
|
// ensure_exists(path)?;
|
||||||
|
|
||||||
|
// let bytes_read = {
|
||||||
|
// let bytes = fs::read(path)?;
|
||||||
|
// let mut encoder = get_encoder(&format, Box::new(&mut buffer));
|
||||||
|
// encoder.write_all(&*bytes)?;
|
||||||
|
// bytes.as_slice().read_to_end(&mut contents)?
|
||||||
|
// };
|
||||||
|
|
||||||
|
// println!("{}: compressed {:?} into memory ({} bytes)", "info".yellow(), &path, bytes_read);
|
||||||
|
|
||||||
|
// Ok(contents)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn compress_bytes(file: File) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl BzipCompressor {
|
||||||
|
fn compress_files(files: Vec<PathBuf>, format: CompressionFormat) -> OuchResult<Vec<u8>> {
|
||||||
|
if files.len() != 1 {
|
||||||
|
eprintln!("{}: cannot compress multiple files directly to {:#?}.\n Try using an intermediate archival method such as Tar.\n Example: filename.tar{}", "error".red(), format, format);
|
||||||
|
return Err(Error::InvalidInput);
|
||||||
|
}
|
||||||
|
let path = &files[0];
|
||||||
|
ensure_exists(path)?;
|
||||||
|
let contents = {
|
||||||
|
let bytes = fs::read(path)?;
|
||||||
|
Self::compress_bytes(&*bytes)?
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{}: compressed {:?} into memory ({} bytes)", "info".yellow(), &path, contents.len());
|
||||||
|
|
||||||
|
Ok(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compress_file_in_memory(file: File) -> OuchResult<Vec<u8>> {
|
||||||
|
// Ensure that our file has in-memory content
|
||||||
|
let bytes = match file.contents_in_memory {
|
||||||
|
Some(bytes) => bytes,
|
||||||
|
None => {
|
||||||
|
// TODO: error message,
|
||||||
|
return Err(Error::InvalidInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self::compress_bytes(&*bytes)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compress_bytes(bytes: &[u8]) -> OuchResult<Vec<u8>> {
|
||||||
|
let buffer = vec![];
|
||||||
|
let mut encoder = bzip2::write::BzEncoder::new(buffer, bzip2::Compression::new(6));
|
||||||
|
encoder.write_all(bytes)?;
|
||||||
|
Ok(encoder.finish()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: customizable compression level
|
||||||
|
fn get_encoder<'a>(format: &CompressionFormat, buffer: Box<dyn io::Write + Send + 'a>) -> Box<dyn io::Write + Send + 'a> {
|
||||||
|
match format {
|
||||||
|
CompressionFormat::Bzip => Box::new(bzip2::write::BzEncoder::new(buffer, bzip2::Compression::new(4))),
|
||||||
|
_other => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Compressor for BzipCompressor {
|
||||||
|
fn compress(&self, from: Entry) -> OuchResult<Vec<u8>> {
|
||||||
|
match from {
|
||||||
|
Entry::Files(files) => Ok(
|
||||||
|
Self::compress_files(files, CompressionFormat::Bzip)?
|
||||||
|
),
|
||||||
|
Entry::InMemory(file) => Ok(
|
||||||
|
Self::compress_file_in_memory(file)?
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
mod tar;
|
mod tar;
|
||||||
mod zip;
|
mod zip;
|
||||||
mod unified;
|
mod bzip;
|
||||||
mod compressor;
|
mod compressor;
|
||||||
|
|
||||||
pub use compressor::Compressor;
|
pub use compressor::Compressor;
|
||||||
pub use self::compressor::Entry;
|
pub use self::compressor::Entry;
|
||||||
pub use self::tar::TarCompressor;
|
pub use self::tar::TarCompressor;
|
||||||
pub use self::zip::ZipCompressor;
|
pub use self::zip::ZipCompressor;
|
||||||
|
pub use self::bzip::BzipCompressor;
|
||||||
|
@ -16,7 +16,7 @@ use crate::{
|
|||||||
use super::decompressor::DecompressionResult;
|
use super::decompressor::DecompressionResult;
|
||||||
use super::decompressor::Decompressor;
|
use super::decompressor::Decompressor;
|
||||||
|
|
||||||
pub struct UnifiedDecompressor {}
|
struct DecompressorToMemory {}
|
||||||
pub struct GzipDecompressor {}
|
pub struct GzipDecompressor {}
|
||||||
pub struct LzmaDecompressor {}
|
pub struct LzmaDecompressor {}
|
||||||
pub struct BzipDecompressor {}
|
pub struct BzipDecompressor {}
|
||||||
@ -30,7 +30,7 @@ fn get_decoder<'a>(format: CompressionFormat, buffer: Box<dyn io::Read + Send +
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnifiedDecompressor {
|
impl DecompressorToMemory {
|
||||||
fn unpack_file(from: &Path, format: CompressionFormat) -> OuchResult<Vec<u8>> {
|
fn unpack_file(from: &Path, format: CompressionFormat) -> OuchResult<Vec<u8>> {
|
||||||
let file = std::fs::read(from)?;
|
let file = std::fs::read(from)?;
|
||||||
|
|
||||||
@ -62,18 +62,18 @@ impl UnifiedDecompressor {
|
|||||||
|
|
||||||
impl Decompressor for GzipDecompressor {
|
impl Decompressor for GzipDecompressor {
|
||||||
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
||||||
UnifiedDecompressor::decompress(from, CompressionFormat::Gzip, into)
|
DecompressorToMemory::decompress(from, CompressionFormat::Gzip, into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decompressor for BzipDecompressor {
|
impl Decompressor for BzipDecompressor {
|
||||||
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
||||||
UnifiedDecompressor::decompress(from, CompressionFormat::Bzip, into)
|
DecompressorToMemory::decompress(from, CompressionFormat::Bzip, into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decompressor for LzmaDecompressor {
|
impl Decompressor for LzmaDecompressor {
|
||||||
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
||||||
UnifiedDecompressor::decompress(from, CompressionFormat::Lzma, into)
|
DecompressorToMemory::decompress(from, CompressionFormat::Lzma, into)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,7 +6,8 @@ use crate::compressors::{
|
|||||||
Entry,
|
Entry,
|
||||||
Compressor,
|
Compressor,
|
||||||
TarCompressor,
|
TarCompressor,
|
||||||
ZipCompressor
|
ZipCompressor,
|
||||||
|
BzipCompressor,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::decompressors::{
|
use crate::decompressors::{
|
||||||
@ -69,6 +70,7 @@ impl Evaluator {
|
|||||||
let second_compressor: Box<dyn Compressor> = match extension.second_ext {
|
let second_compressor: Box<dyn Compressor> = match extension.second_ext {
|
||||||
CompressionFormat::Tar => Box::new(TarCompressor {}),
|
CompressionFormat::Tar => Box::new(TarCompressor {}),
|
||||||
CompressionFormat::Zip => Box::new(ZipCompressor {}),
|
CompressionFormat::Zip => Box::new(ZipCompressor {}),
|
||||||
|
CompressionFormat::Bzip => Box::new(BzipCompressor {}),
|
||||||
_other => todo!()
|
_other => todo!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
use std::{convert::TryFrom, ffi::OsStr, path::{Path, PathBuf}};
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
|
ffi::OsStr,
|
||||||
|
fmt::Display,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use CompressionFormat::*;
|
use CompressionFormat::*;
|
||||||
@ -9,15 +14,13 @@ use CompressionFormat::*;
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Extension {
|
pub struct Extension {
|
||||||
pub first_ext: Option<CompressionFormat>,
|
pub first_ext: Option<CompressionFormat>,
|
||||||
pub second_ext: CompressionFormat
|
pub second_ext: CompressionFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_extension_from_filename(filename: &str) -> Option<(&str, &str)> {
|
pub fn get_extension_from_filename(filename: &str) -> Option<(&str, &str)> {
|
||||||
let path = Path::new(filename);
|
let path = Path::new(filename);
|
||||||
|
|
||||||
let ext = path
|
let ext = path.extension().and_then(OsStr::to_str)?;
|
||||||
.extension()
|
|
||||||
.and_then(OsStr::to_str)?;
|
|
||||||
|
|
||||||
let previous_extension = path
|
let previous_extension = path
|
||||||
.file_stem()
|
.file_stem()
|
||||||
@ -35,34 +38,28 @@ impl From<CompressionFormat> for Extension {
|
|||||||
fn from(second_ext: CompressionFormat) -> Self {
|
fn from(second_ext: CompressionFormat) -> Self {
|
||||||
Self {
|
Self {
|
||||||
first_ext: None,
|
first_ext: None,
|
||||||
second_ext
|
second_ext,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Extension {
|
impl Extension {
|
||||||
pub fn new(filename: &str) -> error::OuchResult<Self> {
|
pub fn new(filename: &str) -> error::OuchResult<Self> {
|
||||||
let ext_from_str = |ext| {
|
let ext_from_str = |ext| match ext {
|
||||||
match ext {
|
|
||||||
"zip" => Ok(Zip),
|
"zip" => Ok(Zip),
|
||||||
"tar" => Ok(Tar),
|
"tar" => Ok(Tar),
|
||||||
"gz" => Ok(Gzip),
|
"gz" => Ok(Gzip),
|
||||||
"bz" | "bz2" => Ok(Bzip),
|
"bz" | "bz2" => Ok(Bzip),
|
||||||
"lz" | "lzma" => Ok(Lzma),
|
"lz" | "lzma" => Ok(Lzma),
|
||||||
other => Err(error::Error::UnknownExtensionError(other.into())),
|
other => Err(error::Error::UnknownExtensionError(other.into())),
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (first_ext, second_ext) = match get_extension_from_filename(filename) {
|
let (first_ext, second_ext) = match get_extension_from_filename(filename) {
|
||||||
Some(extension_tuple) => {
|
Some(extension_tuple) => match extension_tuple {
|
||||||
match extension_tuple {
|
|
||||||
("", snd) => (None, snd),
|
("", snd) => (None, snd),
|
||||||
(fst, snd)=> (Some(fst), snd)
|
(fst, snd) => (Some(fst), snd),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
None => {
|
None => return Err(error::Error::MissingExtensionError(filename.into())),
|
||||||
return Err(error::Error::MissingExtensionError(filename.into()))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (first_ext, second_ext) = match (first_ext, second_ext) {
|
let (first_ext, second_ext) = match (first_ext, second_ext) {
|
||||||
@ -77,12 +74,10 @@ impl Extension {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(
|
Ok(Self {
|
||||||
Self {
|
|
||||||
first_ext,
|
first_ext,
|
||||||
second_ext
|
second_ext,
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +118,6 @@ impl TryFrom<&PathBuf> for CompressionFormat {
|
|||||||
type Error = error::Error;
|
type Error = error::Error;
|
||||||
|
|
||||||
fn try_from(ext: &PathBuf) -> Result<Self, Self::Error> {
|
fn try_from(ext: &PathBuf) -> Result<Self, Self::Error> {
|
||||||
|
|
||||||
let ext = match ext.extension() {
|
let ext = match ext.extension() {
|
||||||
Some(ext) => ext,
|
Some(ext) => ext,
|
||||||
None => {
|
None => {
|
||||||
@ -147,3 +141,19 @@ impl TryFrom<&str> for CompressionFormat {
|
|||||||
extension_from_os_str(ext)
|
extension_from_os_str(ext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for CompressionFormat {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Gzip => ".gz",
|
||||||
|
Bzip => ".bz",
|
||||||
|
Lzma => ".lz",
|
||||||
|
Tar => ".tar",
|
||||||
|
Zip => ".zip",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
10
src/utils.rs
10
src/utils.rs
@ -3,6 +3,16 @@ use std::{fs, path::Path};
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use crate::{error::OuchResult, file::File};
|
use crate::{error::OuchResult, file::File};
|
||||||
|
|
||||||
|
pub (crate) fn ensure_exists<'a, P>(path: P) -> OuchResult<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path> + 'a {
|
||||||
|
let exists = path.as_ref().exists();
|
||||||
|
if !exists {
|
||||||
|
eprintln!("{}: could not find file {:?}", "error".red(), path.as_ref());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub (crate) fn create_path_if_non_existent(path: &Path) -> OuchResult<()> {
|
pub (crate) fn create_path_if_non_existent(path: &Path) -> OuchResult<()> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
println!(
|
println!(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user