Add Extension wrapper that lets us keep tgz instead of forcing tar.gz

This commit is contained in:
Spyros Roum 2021-11-01 01:09:37 +02:00
parent 604616e042
commit 4921d3d3d2
2 changed files with 81 additions and 34 deletions

View File

@ -17,6 +17,7 @@ use crate::{
extension::{
self,
CompressionFormat::{self, *},
Extension,
},
info,
utils::nice_directory_display,
@ -56,9 +57,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
return Err(Error::with_reason(reason));
}
if !formats.get(0).map(CompressionFormat::is_archive_format).unwrap_or(false)
&& represents_several_files(&files)
{
if !formats.get(0).map(Extension::is_archive).unwrap_or(false) && represents_several_files(&files) {
// This piece of code creates a suggestion for compressing multiple files
// It says:
// Change from file.bz.xz
@ -86,7 +85,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
return Err(Error::with_reason(reason));
}
if let Some(format) = formats.iter().skip(1).find(|format| format.is_archive_format()) {
if let Some(format) = formats.iter().skip(1).find(|format| format.is_archive()) {
let reason = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path)))
.detail(format!("Found the format '{}' in an incorrect position.", format))
.detail(format!("'{}' can only be used at the start of the file extension.", format))
@ -108,12 +107,28 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
// `ouch compress file.tar.gz file.tar.gz.xz` should produce `file.tar.gz.xz` and not `file.tar.gz.tar.gz.xz`
let input_extensions = extension::extensions_from_path(&files[0]);
// We calculate the formats that are left if we filter out a sublist at the start of what we have that's the same as the input formats
let mut new_formats = Vec::with_capacity(formats.len());
for (inp_ext, out_ext) in input_extensions.iter().zip(&formats) {
if inp_ext.compression_formats == out_ext.compression_formats {
new_formats.push(out_ext.clone());
} else if inp_ext
.compression_formats
.iter()
.zip(&out_ext.compression_formats)
.all(|(inp, out)| inp == out)
{
let new_ext = Extension::new(
&out_ext.compression_formats[..inp_ext.compression_formats.len()],
&out_ext.display_text,
);
new_formats.push(new_ext);
break;
}
}
// If the input is a sublist at the start of `formats` then remove the extensions
// Note: If input_extensions is empty this counts as true
if !input_extensions.is_empty()
&& input_extensions.len() < formats.len()
&& input_extensions.iter().zip(&formats).all(|(inp, out)| inp == out)
{
// Note: If input_extensions is empty then it will make `formats` empty too, which we don't want
if !input_extensions.is_empty() && new_formats != formats {
// Safety:
// We checked above that input_extensions isn't empty, so files[0] has a extension.
//
@ -124,8 +139,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
to_utf(files[0].as_path().file_name().unwrap()),
to_utf(&output_path)
);
let drain_iter = formats.drain(..input_extensions.len());
drop(drain_iter); // Remove the extensions from `formats`
formats = new_formats;
}
}
let compress_result = compress_files(files, formats, output_file);
@ -185,7 +199,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
Ok(())
}
fn compress_files(files: Vec<PathBuf>, formats: Vec<CompressionFormat>, output_file: fs::File) -> crate::Result<()> {
fn compress_files(files: Vec<PathBuf>, formats: Vec<Extension>, output_file: fs::File) -> crate::Result<()> {
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
let mut writer: Box<dyn Write> = Box::new(file_writer);
@ -208,13 +222,13 @@ fn compress_files(files: Vec<PathBuf>, formats: Vec<CompressionFormat>, output_f
encoder
};
for format in formats.iter().skip(1).rev() {
for format in formats.iter().flat_map(Extension::iter).skip(1).collect::<Vec<_>>().iter().rev() {
writer = chain_writer_encoder(format, writer);
}
match formats[0] {
match formats[0].compression_formats[0] {
Gzip | Bzip | Lzma | Zstd => {
writer = chain_writer_encoder(&formats[0], writer);
writer = chain_writer_encoder(&formats[0].compression_formats[0], writer);
let mut reader = fs::File::open(&files[0]).unwrap();
io::copy(&mut reader, &mut writer)?;
}
@ -248,7 +262,7 @@ fn compress_files(files: Vec<PathBuf>, formats: Vec<CompressionFormat>, output_f
// file_name is only used when extracting single file formats, no archive formats like .tar or .zip
fn decompress_file(
input_file_path: &Path,
formats: Vec<extension::CompressionFormat>,
formats: Vec<Extension>,
output_folder: Option<&Path>,
file_name: &Path,
skip_questions_positively: Option<bool>,
@ -270,7 +284,7 @@ fn decompress_file(
// in-memory decompression/copying first.
//
// Any other Zip decompression done can take up the whole RAM and freeze ouch.
if let [Zip] = *formats.as_slice() {
if formats.len() == 1 && *formats[0].compression_formats.as_slice() == [Zip] {
utils::create_dir_if_non_existent(output_folder)?;
let zip_archive = zip::ZipArchive::new(reader)?;
let _files = crate::archive::zip::unpack_archive(zip_archive, output_folder, skip_questions_positively)?;
@ -294,15 +308,15 @@ fn decompress_file(
Ok(decoder)
};
for format in formats.iter().skip(1).rev() {
for format in formats.iter().flat_map(Extension::iter).skip(1).collect::<Vec<_>>().iter().rev() {
reader = chain_reader_decoder(format, reader)?;
}
utils::create_dir_if_non_existent(output_folder)?;
match formats[0] {
match formats[0].compression_formats[0] {
Gzip | Bzip | Lzma | Zstd => {
reader = chain_reader_decoder(&formats[0], reader)?;
reader = chain_reader_decoder(&formats[0].compression_formats[0], reader)?;
// TODO: improve error treatment
let mut writer = fs::File::create(&output_path)?;

View File

@ -4,7 +4,40 @@ use std::{ffi::OsStr, fmt, path::Path};
use self::CompressionFormat::*;
#[derive(Clone, PartialEq, Eq, Debug)]
/// A wrapper around `CompressionFormat` that allows combinations like `tgz`
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Extension {
pub compression_formats: Vec<CompressionFormat>,
pub display_text: String,
}
impl Extension {
/// # Panics:
/// Will panic if `formats` is empty
pub fn new(formats: impl Into<Vec<CompressionFormat>>, text: impl Into<String>) -> Self {
let formats = formats.into();
assert!(!formats.is_empty());
Self { compression_formats: formats, display_text: text.into() }
}
/// Checks if the first format in `compression_formats` is an archive
pub fn is_archive(&self) -> bool {
// Safety: we check that `compression_formats` is not empty in `Self::new`
self.compression_formats[0].is_archive_format()
}
pub fn iter(&self) -> impl Iterator<Item = &CompressionFormat> {
self.compression_formats.iter()
}
}
impl fmt::Display for Extension {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.display_text)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
/// Accepted extensions for input and output
pub enum CompressionFormat {
Gzip, // .gz
@ -45,7 +78,7 @@ impl fmt::Display for CompressionFormat {
}
}
pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<CompressionFormat>) {
pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Extension>) {
// // TODO: check for file names with the name of an extension
// // TODO2: warn the user that currently .tar.gz is a .gz file named .tar
//
@ -58,17 +91,17 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Compr
// While there is known extensions at the tail, grab them
while let Some(extension) = path.extension().and_then(OsStr::to_str) {
extensions.append(&mut match extension {
"tar" => vec![Tar],
"tgz" => vec![Gzip, Tar],
"tbz" | "tbz2" => vec![Bzip, Tar],
"txz" | "tlz" | "tlzma" => vec![Lzma, Tar],
"tzst" => vec![Zstd, Tar],
"zip" => vec![Zip],
"bz" | "bz2" => vec![Bzip],
"gz" => vec![Gzip],
"xz" | "lzma" | "lz" => vec![Lzma],
"zst" => vec![Zstd],
extensions.push(match extension {
"tar" => Extension::new([Tar], extension),
"tgz" => Extension::new([Tar, Gzip], extension),
"tbz" | "tbz2" => Extension::new([Tar, Bzip], extension),
"txz" | "tlz" | "tlzma" => Extension::new([Tar, Lzma], extension),
"tzst" => Extension::new([Tar, Zstd], ".tzst"),
"zip" => Extension::new([Zip], extension),
"bz" | "bz2" => Extension::new([Bzip], extension),
"gz" => Extension::new([Gzip], extension),
"xz" | "lzma" | "lz" => Extension::new([Lzma], extension),
"zst" => Extension::new([Zstd], extension),
_ => break,
});
@ -81,7 +114,7 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Compr
(path, extensions)
}
pub fn extensions_from_path(path: &Path) -> Vec<CompressionFormat> {
pub fn extensions_from_path(path: &Path) -> Vec<Extension> {
let (_, extensions) = separate_known_extensions_from_name(path);
extensions
}