mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-07 12:05:46 +00:00
Add support for decompressing .tar.{bz, xz, lz} and .zip.{bz, xz, lz}
This commit is contained in:
parent
77d7613967
commit
e08703850c
@ -91,6 +91,7 @@ impl TryFrom<clap::ArgMatches<'static>> for Command {
|
|||||||
let output_file = matches.value_of("output").unwrap(); // Safe unwrap since we've established that output was supplied
|
let output_file = matches.value_of("output").unwrap(); // Safe unwrap since we've established that output was supplied
|
||||||
|
|
||||||
let output_file_extension = Extension::new(output_file);
|
let output_file_extension = Extension::new(output_file);
|
||||||
|
|
||||||
let output_is_compressible = output_file_extension.is_ok();
|
let output_is_compressible = output_file_extension.is_ok();
|
||||||
if output_is_compressible {
|
if output_is_compressible {
|
||||||
// The supplied output is compressible, so we'll compress our inputs to it
|
// The supplied output is compressible, so we'll compress our inputs to it
|
||||||
@ -103,14 +104,6 @@ impl TryFrom<clap::ArgMatches<'static>> for Command {
|
|||||||
|
|
||||||
let input_files = input_files.map(PathBuf::from).collect();
|
let input_files = input_files.map(PathBuf::from).collect();
|
||||||
|
|
||||||
// return Ok(Command {
|
|
||||||
// kind: CommandKind::Compression(input_files),
|
|
||||||
// output: Some(File::WithExtension((
|
|
||||||
// output_file.into(),
|
|
||||||
// output_file_extension.unwrap(),
|
|
||||||
// ))),
|
|
||||||
// });
|
|
||||||
|
|
||||||
return Ok(Command {
|
return Ok(Command {
|
||||||
kind: CommandKind::Compression(input_files),
|
kind: CommandKind::Compression(input_files),
|
||||||
output: Some(File {
|
output: Some(File {
|
||||||
|
@ -8,5 +8,5 @@ pub enum DecompressionResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Decompressor {
|
pub trait Decompressor {
|
||||||
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult>;
|
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult>;
|
||||||
}
|
}
|
@ -42,7 +42,7 @@ impl NifflerDecompressor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Decompressor for NifflerDecompressor {
|
impl Decompressor for NifflerDecompressor {
|
||||||
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
||||||
let destination_path = utils::get_destination_path(into);
|
let destination_path = utils::get_destination_path(into);
|
||||||
|
|
||||||
utils::create_path_if_non_existent(destination_path)?;
|
utils::create_path_if_non_existent(destination_path)?;
|
||||||
|
@ -12,13 +12,20 @@ pub struct TarDecompressor {}
|
|||||||
|
|
||||||
impl TarDecompressor {
|
impl TarDecompressor {
|
||||||
|
|
||||||
fn unpack_files(from: &File, into: &Path) -> OuchResult<Vec<PathBuf>> {
|
fn unpack_files(from: File, into: &Path) -> OuchResult<Vec<PathBuf>> {
|
||||||
|
|
||||||
println!("{}: attempting to decompress {:?}", "ouch".bright_blue(), from);
|
println!("{}: attempting to decompress {:?}", "ouch".bright_blue(), &from.path);
|
||||||
let mut files_unpacked = vec![];
|
let mut files_unpacked = vec![];
|
||||||
|
|
||||||
|
let mut archive: Archive<Box<dyn Read>> = match from.contents {
|
||||||
|
Some(bytes) => {
|
||||||
|
tar::Archive::new(Box::new(Cursor::new(bytes)))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
let file = fs::File::open(&from.path)?;
|
let file = fs::File::open(&from.path)?;
|
||||||
let mut archive = tar::Archive::new(file);
|
tar::Archive::new(Box::new(file))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for file in archive.entries()? {
|
for file in archive.entries()? {
|
||||||
let mut file = file?;
|
let mut file = file?;
|
||||||
@ -42,12 +49,12 @@ impl TarDecompressor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Decompressor for TarDecompressor {
|
impl Decompressor for TarDecompressor {
|
||||||
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
||||||
let destination_path = utils::get_destination_path(into);
|
let destination_path = utils::get_destination_path(into);
|
||||||
|
|
||||||
utils::create_path_if_non_existent(destination_path)?;
|
utils::create_path_if_non_existent(destination_path)?;
|
||||||
|
|
||||||
let files_unpacked = Self::unpack_files(&from, destination_path)?;
|
let files_unpacked = Self::unpack_files(from, destination_path)?;
|
||||||
|
|
||||||
Ok(DecompressionResult::FilesUnpacked(files_unpacked))
|
Ok(DecompressionResult::FilesUnpacked(files_unpacked))
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,36 @@
|
|||||||
use std::{fs, io, path::{Path, PathBuf}};
|
use std::{fs, io::{self, Cursor, Read, Seek}, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use zip::{self, read::ZipFile};
|
use zip::{self, ZipArchive, read::ZipFile};
|
||||||
|
|
||||||
use crate::{error::{self, OuchResult}, utils};
|
use crate::{error, file::File};
|
||||||
use crate::file::File;
|
use crate::{error::OuchResult, utils};
|
||||||
|
|
||||||
use super::decompressor::{DecompressionResult, Decompressor};
|
use super::decompressor::{DecompressionResult, Decompressor};
|
||||||
|
|
||||||
pub struct ZipDecompressor {}
|
pub struct ZipDecompressor {}
|
||||||
|
|
||||||
impl ZipDecompressor {
|
impl ZipDecompressor {
|
||||||
|
|
||||||
fn check_for_comments(file: &ZipFile) {
|
fn check_for_comments(file: &ZipFile) {
|
||||||
let comment = file.comment();
|
let comment = file.comment();
|
||||||
if !comment.is_empty() {
|
if !comment.is_empty() {
|
||||||
println!("{}: Comment in {}: {}", "info".yellow(), file.name(), comment);
|
println!(
|
||||||
|
"{}: Comment in {}: {}",
|
||||||
|
"info".yellow(),
|
||||||
|
file.name(),
|
||||||
|
comment
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unpack_files(from: &Path, into: &Path) -> OuchResult<Vec<PathBuf>> {
|
pub fn zip_decompress<T>(
|
||||||
|
archive: &mut ZipArchive<T>,
|
||||||
|
into: &Path,
|
||||||
|
) -> error::OuchResult<Vec<PathBuf>>
|
||||||
|
where
|
||||||
|
T: Read + Seek,
|
||||||
|
{
|
||||||
let mut unpacked_files = vec![];
|
let mut unpacked_files = vec![];
|
||||||
|
|
||||||
println!("{}: attempting to decompress {:?}", "ouch".bright_blue(), from);
|
|
||||||
|
|
||||||
let file = fs::File::open(from)?;
|
|
||||||
let mut archive = zip::ZipArchive::new(file)?;
|
|
||||||
|
|
||||||
for idx in 0..archive.len() {
|
for idx in 0..archive.len() {
|
||||||
let mut file = archive.by_index(idx)?;
|
let mut file = archive.by_index(idx)?;
|
||||||
let file_path = match file.enclosed_name() {
|
let file_path = match file.enclosed_name() {
|
||||||
@ -68,16 +71,39 @@ impl ZipDecompressor {
|
|||||||
|
|
||||||
Ok(unpacked_files)
|
Ok(unpacked_files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unpack_files(from: File, into: &Path) -> OuchResult<Vec<PathBuf>> {
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}: attempting to decompress {:?}",
|
||||||
|
"ouch".bright_blue(),
|
||||||
|
from
|
||||||
|
);
|
||||||
|
|
||||||
|
match from.contents {
|
||||||
|
Some(bytes) => {
|
||||||
|
let mut archive = zip::ZipArchive::new(Cursor::new(bytes))?;
|
||||||
|
Ok(Self::zip_decompress(&mut archive, into)?)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let file = fs::File::open(&from.path)?;
|
||||||
|
let mut archive = zip::ZipArchive::new(file)?;
|
||||||
|
Ok(Self::zip_decompress(&mut archive, into)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Decompressor for ZipDecompressor {
|
impl Decompressor for ZipDecompressor {
|
||||||
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
|
||||||
let destination_path = utils::get_destination_path(into);
|
let destination_path = utils::get_destination_path(into);
|
||||||
|
|
||||||
utils::create_path_if_non_existent(destination_path)?;
|
utils::create_path_if_non_existent(destination_path)?;
|
||||||
|
|
||||||
let files_unpacked = Self::unpack_files(&from.path, destination_path)?;
|
let files_unpacked = Self::unpack_files(from, destination_path)?;
|
||||||
|
|
||||||
Ok(DecompressionResult::FilesUnpacked(files_unpacked))
|
Ok(DecompressionResult::FilesUnpacked(files_unpacked))
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::{ffi::OsStr, fs, io::Write};
|
use std::{ffi::OsStr, fs, io::Write, path::PathBuf};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
use crate::decompressors::Decompressor;
|
use crate::{decompressors::Decompressor, extension::Extension};
|
||||||
use crate::decompressors::TarDecompressor;
|
use crate::decompressors::TarDecompressor;
|
||||||
use crate::decompressors::ZipDecompressor;
|
use crate::decompressors::ZipDecompressor;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -15,7 +15,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct Evaluator {
|
pub struct Evaluator {
|
||||||
command: Command,
|
|
||||||
// verbosity: Verbosity
|
// verbosity: Verbosity
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ impl Evaluator {
|
|||||||
);
|
);
|
||||||
return Err(error::Error::InvalidInput);
|
return Err(error::Error::InvalidInput);
|
||||||
}
|
}
|
||||||
let extension = file.extension.clone().unwrap();
|
let extension = Extension::new(&file.path.to_str().unwrap())?;
|
||||||
|
|
||||||
let decompressor_from_format = |ext| -> Box<dyn Decompressor> {
|
let decompressor_from_format = |ext| -> Box<dyn Decompressor> {
|
||||||
match ext {
|
match ext {
|
||||||
@ -58,33 +57,46 @@ impl Evaluator {
|
|||||||
// todo: move this folder into decompressors/ later on
|
// todo: move this folder into decompressors/ later on
|
||||||
fn decompress_file_in_memory(
|
fn decompress_file_in_memory(
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
file: &File,
|
file_path: PathBuf,
|
||||||
decompressor: Option<Box<dyn Decompressor>>,
|
decompressor: Option<Box<dyn Decompressor>>,
|
||||||
output_file: &Option<File>,
|
output_file: &Option<File>,
|
||||||
|
extension: Option<Extension>,
|
||||||
) -> OuchResult<()> {
|
) -> OuchResult<()> {
|
||||||
|
|
||||||
let output_file = utils::get_destination_path(output_file);
|
let output_file_path = utils::get_destination_path(output_file);
|
||||||
|
|
||||||
let mut filename = file.path.file_stem().unwrap_or(output_file.as_os_str());
|
let mut filename = file_path.file_stem().unwrap_or(output_file_path.as_os_str());
|
||||||
if filename == "." {
|
if filename == "." {
|
||||||
// I believe this is only possible when the supplied inout has a name
|
// I believe this is only possible when the supplied inout has a name
|
||||||
// of the sort `.tar` or `.zip' and no output has been supplied.
|
// of the sort `.tar` or `.zip' and no output has been supplied.
|
||||||
filename = OsStr::new("ouch-output");
|
filename = OsStr::new("ouch-output");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filename = PathBuf::from(filename);
|
||||||
|
|
||||||
if decompressor.is_none() {
|
if decompressor.is_none() {
|
||||||
// There is no more processing to be done on the input file (or there is but currently unsupported)
|
// There is no more processing to be done on the input file (or there is but currently unsupported)
|
||||||
// Therefore, we'll save what we have in memory into a file.
|
// Therefore, we'll save what we have in memory into a file.
|
||||||
|
|
||||||
println!("{}: saving to {:?}.", "info".yellow(), filename);
|
println!("{}: saving to {:?}.", "info".yellow(), filename);
|
||||||
|
|
||||||
let mut f = fs::File::create(output_file.join(filename))?;
|
let mut f = fs::File::create(output_file_path.join(filename))?;
|
||||||
f.write_all(&bytes)?;
|
f.write_all(&bytes)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a decompressor to use, we'll create a file in-memory (to-do) and decompress it
|
let file = File {
|
||||||
// TODO: change decompressor logic to use BufReader or something like that
|
path: filename,
|
||||||
|
contents: Some(bytes),
|
||||||
|
extension,
|
||||||
|
};
|
||||||
|
|
||||||
|
let decompressor = decompressor.unwrap();
|
||||||
|
|
||||||
|
// If there is a decompressor to use, we'll create a file in-memory and decompress it
|
||||||
|
|
||||||
|
|
||||||
|
let decompression_result = decompressor.decompress(file, output_file)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -93,15 +105,18 @@ impl Evaluator {
|
|||||||
// let output_file = &command.output;
|
// let output_file = &command.output;
|
||||||
let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?;
|
let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?;
|
||||||
|
|
||||||
let decompression_result = second_decompressor.decompress(&file, output)?;
|
let file_path = file.path.clone();
|
||||||
|
let extension = file.extension.clone();
|
||||||
|
|
||||||
|
let decompression_result = second_decompressor.decompress(file, output)?;
|
||||||
|
|
||||||
match decompression_result {
|
match decompression_result {
|
||||||
DecompressionResult::FileInMemory(bytes) => {
|
DecompressionResult::FileInMemory(bytes) => {
|
||||||
// We'll now decompress a file currently in memory.
|
// We'll now decompress a file currently in memory.
|
||||||
// This will currently happen in the case of .bz, .xz and .lzma
|
// This will currently happen in the case of .bz, .xz and .lzma
|
||||||
Self::decompress_file_in_memory(bytes, &file, first_decompressor, output)?;
|
Self::decompress_file_in_memory(bytes, file_path, first_decompressor, output, extension)?;
|
||||||
}
|
}
|
||||||
DecompressionResult::FilesUnpacked(files) => {
|
DecompressionResult::FilesUnpacked(_files) => {
|
||||||
// If the file's last extension was an archival method,
|
// If the file's last extension was an archival method,
|
||||||
// such as .tar, .zip or (to-do) .rar, then we won't look for
|
// such as .tar, .zip or (to-do) .rar, then we won't look for
|
||||||
// further processing.
|
// further processing.
|
||||||
|
@ -30,5 +30,8 @@ fn main() -> OuchResult<()>{
|
|||||||
print_error(err)
|
print_error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// let extension = dbg!(Extension::new("file.tar.gz"));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
18
src/test.rs
18
src/test.rs
@ -84,7 +84,6 @@ mod cli {
|
|||||||
"file2.jpeg".into(),
|
"file2.jpeg".into(),
|
||||||
"file3.ok".into()
|
"file3.ok".into()
|
||||||
]),
|
]),
|
||||||
// output: Some(File::WithExtension(("file.tar".into(), Extension::from(Tar))))
|
|
||||||
output: Some(
|
output: Some(
|
||||||
File {
|
File {
|
||||||
path: "file.tar".into(),
|
path: "file.tar".into(),
|
||||||
@ -126,7 +125,7 @@ mod cli_errors {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod extension_extraction {
|
mod extension_extraction {
|
||||||
use crate::error::OuchResult;
|
use crate::{error::OuchResult, extension::Extension};
|
||||||
use crate::extension::CompressionFormat;
|
use crate::extension::CompressionFormat;
|
||||||
use std::{convert::TryFrom, path::PathBuf, str::FromStr};
|
use std::{convert::TryFrom, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
@ -141,6 +140,21 @@ mod extension_extraction {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tar_gz() -> OuchResult<()> {
|
||||||
|
let extension = Extension::new("folder.tar.gz")?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
extension,
|
||||||
|
Extension {
|
||||||
|
first_ext: Some(CompressionFormat::Tar),
|
||||||
|
second_ext: CompressionFormat::Gzip
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tar() -> OuchResult<()> {
|
fn tar() -> OuchResult<()> {
|
||||||
let path = PathBuf::from_str("pictures.tar").unwrap();
|
let path = PathBuf::from_str("pictures.tar").unwrap();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{fs, path::{Component, Path, PathBuf}};
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use crate::{error::OuchResult, file::File};
|
use crate::{error::OuchResult, file::File};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user