From 02657ee5bcfa70e1785bcf119202e2ca738582cf Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Fri, 22 Oct 2021 14:44:13 +0300 Subject: [PATCH 1/6] Remove the variants from `CompressionFormat` --- src/commands.rs | 40 ---------------------------------------- src/extension.rs | 18 +++++------------- 2 files changed, 5 insertions(+), 53 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 2a627dd..ba4e0b8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -222,26 +222,6 @@ fn compress_files(files: Vec, formats: Vec, output_f let mut writer = archive::tar::build_archive_from_paths(&files, writer)?; writer.flush()?; } - Tgz => { - let encoder = flate2::write::GzEncoder::new(writer, Default::default()); - let writer = archive::tar::build_archive_from_paths(&files, encoder)?; - writer.finish()?.flush()?; - } - Tbz => { - let encoder = bzip2::write::BzEncoder::new(writer, Default::default()); - let writer = archive::tar::build_archive_from_paths(&files, encoder)?; - writer.finish()?.flush()?; - } - Tlzma => { - let encoder = xz2::write::XzEncoder::new(writer, 6); - let writer = archive::tar::build_archive_from_paths(&files, encoder)?; - writer.finish()?.flush()?; - } - Tzst => { - let encoder = zstd::stream::write::Encoder::new(writer, Default::default())?; - let writer = archive::tar::build_archive_from_paths(&files, encoder)?; - writer.finish()?.flush()?; - } Zip => { eprintln!("{yellow}Warning:{reset}", yellow = *colors::YELLOW, reset = *colors::RESET); eprintln!("\tCompressing .zip entirely in memory."); @@ -334,26 +314,6 @@ fn decompress_file( let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); } - Tgz => { - let reader = chain_reader_decoder(&Gzip, reader)?; - let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; - info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); - } - Tbz => { - let reader = chain_reader_decoder(&Bzip, reader)?; - let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; - info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); - } - Tlzma => { - let reader = chain_reader_decoder(&Lzma, reader)?; - let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; - info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); - } - Tzst => { - let reader = chain_reader_decoder(&Zstd, reader)?; - let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; - info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); - } Zip => { eprintln!("Compressing first into .zip."); eprintln!("Warning: .zip archives with extra extensions have a downside."); diff --git a/src/extension.rs b/src/extension.rs index 9ff200f..1349a28 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -11,17 +11,13 @@ pub enum CompressionFormat { Bzip, // .bz Lzma, // .lzma Tar, // .tar (technically not a compression extension, but will do for now) - Tgz, // .tgz - Tbz, // .tbz - Tlzma, // .tlzma - Tzst, // .tzst Zstd, // .zst Zip, // .zip } impl CompressionFormat { pub fn is_archive_format(&self) -> bool { - matches!(self, Tar | Tgz | Tbz | Tlzma | Tzst | Zip) + matches!(self, Tar | Zip) } } @@ -36,10 +32,6 @@ impl fmt::Display for CompressionFormat { Zstd => ".zst", Lzma => ".lz", Tar => ".tar", - Tgz => ".tgz", - Tbz => ".tbz", - Tlzma => ".tlz", - Tzst => ".tzst", Zip => ".zip", } ) @@ -61,10 +53,10 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec Tar, - "tgz" => Tgz, - "tbz" | "tbz2" => Tbz, - "txz" | "tlz" | "tlzma" => Tlzma, - "tzst" => Tzst, + // "tgz" => Tgz, + // "tbz" | "tbz2" => Tbz, + // "txz" | "tlz" | "tlzma" => Tlzma, + // "tzst" => Tzst, "zip" => Zip, "bz" | "bz2" => Bzip, "gz" => Gzip, From caca7901c487f183526e9a1ec2c9e2432653cc6b Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Fri, 22 Oct 2021 14:47:44 +0300 Subject: [PATCH 2/6] Run `cargo fmt` --- src/extension.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/extension.rs b/src/extension.rs index 1349a28..137b963 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -7,12 +7,12 @@ use self::CompressionFormat::*; #[derive(Clone, PartialEq, Eq, Debug)] /// Accepted extensions for input and output pub enum CompressionFormat { - Gzip, // .gz - Bzip, // .bz - Lzma, // .lzma - Tar, // .tar (technically not a compression extension, but will do for now) - Zstd, // .zst - Zip, // .zip + Gzip, // .gz + Bzip, // .bz + Lzma, // .lzma + Tar, // .tar (technically not a compression extension, but will do for now) + Zstd, // .zst + Zip, // .zip } impl CompressionFormat { From 6b6ade8c9ae7c442452e50a835874e29f2bbfa0d Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Fri, 22 Oct 2021 14:49:32 +0300 Subject: [PATCH 3/6] Break down `tgz`, `tbz`, etc to `tar + gz/bz` --- src/extension.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/extension.rs b/src/extension.rs index 137b963..445fde1 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -51,17 +51,17 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec Tar, - // "tgz" => Tgz, - // "tbz" | "tbz2" => Tbz, - // "txz" | "tlz" | "tlzma" => Tlzma, - // "tzst" => Tzst, - "zip" => Zip, - "bz" | "bz2" => Bzip, - "gz" => Gzip, - "xz" | "lzma" | "lz" => Lzma, - "zst" => Zstd, + 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], _ => break, }); From 604616e04234d9df442aa4aa4433366ad8f881ed Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Sun, 31 Oct 2021 11:09:35 +0200 Subject: [PATCH 4/6] Use proper `match` with no wildcard when detecting if it's archive --- src/extension.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/extension.rs b/src/extension.rs index 445fde1..54ba75e 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -17,7 +17,14 @@ pub enum CompressionFormat { impl CompressionFormat { pub fn is_archive_format(&self) -> bool { - matches!(self, Tar | Zip) + // Keep this match like that without a wildcard `_` so we don't forget to update it + match self { + Tar | Zip => true, + Gzip => false, + Bzip => false, + Lzma => false, + Zstd => false, + } } } From 4921d3d3d22429ae7ebff7bd7913195c796dc98b Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Mon, 1 Nov 2021 01:09:37 +0200 Subject: [PATCH 5/6] Add `Extension` wrapper that lets us keep tgz instead of forcing tar.gz --- src/commands.rs | 54 ++++++++++++++++++++++++++---------------- src/extension.rs | 61 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 34 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index ba4e0b8..28872cf 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -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) -> 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) -> 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) -> 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) -> 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) -> crate::Result Ok(()) } -fn compress_files(files: Vec, formats: Vec, output_file: fs::File) -> crate::Result<()> { +fn compress_files(files: Vec, formats: Vec, output_file: fs::File) -> crate::Result<()> { let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file); let mut writer: Box = Box::new(file_writer); @@ -208,13 +222,13 @@ fn compress_files(files: Vec, formats: Vec, output_f encoder }; - for format in formats.iter().skip(1).rev() { + for format in formats.iter().flat_map(Extension::iter).skip(1).collect::>().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, formats: Vec, 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, + formats: Vec, output_folder: Option<&Path>, file_name: &Path, skip_questions_positively: Option, @@ -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::>().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)?; diff --git a/src/extension.rs b/src/extension.rs index 54ba75e..0ac1ec1 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -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, + pub display_text: String, +} + +impl Extension { + /// # Panics: + /// Will panic if `formats` is empty + pub fn new(formats: impl Into>, text: impl Into) -> 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 { + 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) { +pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec) { // // 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 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 Vec { +pub fn extensions_from_path(path: &Path) -> Vec { let (_, extensions) = separate_known_extensions_from_name(path); extensions } From 15e922b7ba75134218515b9d8be5d915de46d758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 01:16:42 -0300 Subject: [PATCH 6/6] Fixing some Extension tests --- src/extension.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/extension.rs b/src/extension.rs index d0ae41b..07beb03 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -85,14 +85,6 @@ impl fmt::Display for CompressionFormat { /// Extracts extensions from a path, /// return both the remaining path and the list of extension objects -/// -/// ```rust -/// use ouch::extension::{separate_known_extensions_from_name, CompressionFormat}; -/// use std::path::Path; -/// -/// let mut path = Path::new("bolovo.tar.gz"); -/// assert_eq!(separate_known_extensions_from_name(&path), (Path::new("bolovo"), vec![CompressionFormat::Tar, CompressionFormat::Gzip])); -/// ``` pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec) { // // 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 @@ -130,15 +122,23 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec Vec { let (_, extensions) = separate_known_extensions_from_name(path); extensions } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extensions_from_path() { + use CompressionFormat::*; + let path = Path::new("bolovo.tar.gz"); + + let extensions: Vec = extensions_from_path(&path); + let formats: Vec<&CompressionFormat> = extensions.iter().flat_map(Extension::iter).collect::>(); + + assert_eq!(formats, vec![&Tar, &Gzip]); + } +}