From 8c88d5cb0fbf5a77efaf8356a6fb9d6daa663af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Tue, 6 Apr 2021 19:01:44 -0300 Subject: [PATCH 01/11] evaluator: Accept the other compression formats as 'first compressors' --- src/evaluator.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/evaluator.rs b/src/evaluator.rs index 697c0f1..6093dc5 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -50,10 +50,9 @@ impl Evaluator { Some(ext) => match ext { CompressionFormat::Tar => Some(Box::new(TarCompressor {})), CompressionFormat::Zip => Some(Box::new(ZipCompressor {})), - // _other => Some(Box::new(NifflerCompressor {})), - _other => { - todo!(); - } + CompressionFormat::Bzip => Some(Box::new(BzipCompressor {})), + CompressionFormat::Gzip => Some(Box::new(GzipCompressor {})), + CompressionFormat::Lzma => Some(Box::new(LzmaCompressor {})), }, None => None, }; From 784217143a475de3d12b8caa47d381994b2627b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Tue, 6 Apr 2021 19:05:36 -0300 Subject: [PATCH 02/11] Better error message for MissingArgumentsForDecompression --- src/error.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 6d1c547..3b7c3e0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -66,7 +66,10 @@ impl fmt::Display for Error { } Error::MissingArgumentsForCompression => { write!(f, "{} ", "[ERROR]".red())?; - write!(f,"The compress subcommands demands at least 2 arguments, see usage: ") + let spacing = " "; + writeln!(f,"The compress subcommands demands at least 2 arguments, an input file and an output file.")?; + writeln!(f,"{}Example: `ouch compress img.jpeg img.zip", spacing)?; + write!(f,"{}For more information, run `ouch --help`", spacing) } Error::InternalError => { write!(f, "{} ", "[ERROR]".red())?; From 575abeb4546667442487ed25dec77079f9ff8fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 6 Apr 2021 19:24:50 -0300 Subject: [PATCH 03/11] Testing bytes formatting --- src/bytes.rs | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index cd7e9b3..25ec575 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -1,6 +1,6 @@ use std::cmp; -const UNITS: [&str; 4] = ["B", "kB", "MB", "GB"]; +const UNIT_PREFIXES: [&str; 6] = ["", "k", "M", "G", "T", "P"]; pub struct Bytes { bytes: f64, @@ -25,6 +25,47 @@ impl std::fmt::Display for Bytes { let exponent = cmp::min((num.ln() / 6.90775).floor() as i32, 4); write!(f, "{:.2} ", num / delimiter.powi(exponent))?; - write!(f, "{}", UNITS[exponent as usize]) + write!(f, "{}B", UNIT_PREFIXES[exponent as usize]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bytes_formatting() { + fn format_bytes(bytes: u64) -> String { + format!("{}", Bytes::new(bytes)) + } + let b = 1; + let kb = b * 1000; + let mb = kb * 1000; + let gb = mb * 1000; + + assert_eq!("0 B", format_bytes(0)); // This is weird + assert_eq!("1.00 B", format_bytes(b)); + assert_eq!("999.00 B", format_bytes(b * 999)); + assert_eq!("12.00 MB", format_bytes(mb * 12)); + assert_eq!("123.00 MB", format_bytes(mb * 123)); + assert_eq!("5.50 MB", format_bytes(mb * 5 + kb * 500)); + assert_eq!("7.54 GB", format_bytes(gb * 7 + 540 * mb)); + assert_eq!("1.20 TB", format_bytes(gb * 1200)); + + // bytes + assert_eq!("234.00 B", format_bytes(234)); + assert_eq!("999.00 B", format_bytes(999)); + // kilobytes + assert_eq!("2.23 kB", format_bytes(2234)); + assert_eq!("62.50 kB", format_bytes(62500)); + assert_eq!("329.99 kB", format_bytes(329990)); + // megabytes + assert_eq!("2.75 MB", format_bytes(2750000)); + assert_eq!("55.00 MB", format_bytes(55000000)); + assert_eq!("987.65 MB", format_bytes(987654321)); + // gigabytes + assert_eq!("5.28 GB", format_bytes(5280000000)); + assert_eq!("95.20 GB", format_bytes(95200000000)); + assert_eq!("302.00 GB", format_bytes(302000000000)); } } From 78d5f435ee120edabbba102c70a5316bf2b6d85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 6 Apr 2021 20:14:33 -0300 Subject: [PATCH 04/11] Minor style changes --- src/cli.rs | 6 +---- src/compressors/bzip.rs | 2 +- src/compressors/compressor.rs | 6 ----- src/compressors/gzip.rs | 2 +- src/compressors/lzma.rs | 2 +- src/compressors/mod.rs | 1 + src/compressors/tar.rs | 2 +- src/compressors/zip.rs | 2 +- src/decompressors/mod.rs | 13 +++++------ src/decompressors/tar.rs | 2 +- src/decompressors/to_memory.rs | 12 +++++----- src/decompressors/zip.rs | 2 +- src/evaluator.rs | 40 +++++++++++++++++----------------- src/extension.rs | 15 +++++-------- src/main.rs | 2 ++ src/utils.rs | 9 +++++++- 16 files changed, 54 insertions(+), 64 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index ef54fe3..0eb4cc5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -7,8 +7,6 @@ use std::{ use oof::{arg_flag, flag}; -pub const VERSION: &str = "0.1.5"; - #[derive(PartialEq, Eq, Debug)] pub enum Command { /// Files to be compressed @@ -29,7 +27,6 @@ pub enum Command { pub struct CommandInfo { pub command: Command, pub flags: oof::Flags, - // pub config: Config, // From .TOML, maybe, in the future } /// Calls parse_args_and_flags_from using std::env::args_os ( argv ) @@ -41,7 +38,6 @@ pub fn parse_args() -> crate::Result { pub struct ParsedArgs { pub command: Command, pub flags: oof::Flags, - // pub program_called: OsString, // Useful? } fn canonicalize<'a, P>(path: P) -> crate::Result @@ -54,7 +50,7 @@ where if !path.as_ref().exists() { Err(crate::Error::FileNotFound(PathBuf::from(path.as_ref()))) } else { - eprintln!("{} {}", "[ERROR]", io_err); + eprintln!("[ERROR] {}", io_err); Err(crate::Error::IoError) } } diff --git a/src/compressors/bzip.rs b/src/compressors/bzip.rs index 28afd51..43fea59 100644 --- a/src/compressors/bzip.rs +++ b/src/compressors/bzip.rs @@ -10,7 +10,7 @@ use crate::{ utils::{check_for_multiple_files, ensure_exists}, }; -pub struct BzipCompressor {} +pub struct BzipCompressor; impl BzipCompressor { fn compress_files(files: Vec, format: CompressionFormat) -> crate::Result> { diff --git a/src/compressors/compressor.rs b/src/compressors/compressor.rs index 77ff2b8..7773dce 100644 --- a/src/compressors/compressor.rs +++ b/src/compressors/compressor.rs @@ -2,12 +2,6 @@ use std::path::PathBuf; use crate::file::File; -// pub enum CompressionResult { -// ZipArchive(Vec), -// TarArchive(Vec), -// FileInMemory(Vec) -// } - pub enum Entry<'a> { Files(Vec), InMemory(File<'a>), diff --git a/src/compressors/gzip.rs b/src/compressors/gzip.rs index a390971..6824d2c 100644 --- a/src/compressors/gzip.rs +++ b/src/compressors/gzip.rs @@ -10,7 +10,7 @@ use crate::{ utils::{check_for_multiple_files, ensure_exists}, }; -pub struct GzipCompressor {} +pub struct GzipCompressor; impl GzipCompressor { pub fn compress_files( diff --git a/src/compressors/lzma.rs b/src/compressors/lzma.rs index 71d93c1..5517730 100644 --- a/src/compressors/lzma.rs +++ b/src/compressors/lzma.rs @@ -10,7 +10,7 @@ use crate::{ utils::{check_for_multiple_files, ensure_exists}, }; -pub struct LzmaCompressor {} +pub struct LzmaCompressor; impl LzmaCompressor { pub fn compress_files( diff --git a/src/compressors/mod.rs b/src/compressors/mod.rs index f4c73bb..ecfed8b 100644 --- a/src/compressors/mod.rs +++ b/src/compressors/mod.rs @@ -1,3 +1,4 @@ +//! This module contains the Compressor trait and an implementor for each format. mod bzip; mod compressor; mod gzip; diff --git a/src/compressors/tar.rs b/src/compressors/tar.rs index 5de2537..0e0c417 100644 --- a/src/compressors/tar.rs +++ b/src/compressors/tar.rs @@ -7,7 +7,7 @@ use walkdir::WalkDir; use super::compressor::Entry; use crate::{compressors::Compressor, file::File, utils}; -pub struct TarCompressor {} +pub struct TarCompressor; impl TarCompressor { // TODO: implement this diff --git a/src/compressors/zip.rs b/src/compressors/zip.rs index be9f1fe..1d814d5 100644 --- a/src/compressors/zip.rs +++ b/src/compressors/zip.rs @@ -8,7 +8,7 @@ use walkdir::WalkDir; use super::compressor::Entry; use crate::{compressors::Compressor, file::File, utils}; -pub struct ZipCompressor {} +pub struct ZipCompressor; impl ZipCompressor { // TODO: this function does not seem to be working correctly ;/ diff --git a/src/decompressors/mod.rs b/src/decompressors/mod.rs index 689109e..c996f41 100644 --- a/src/decompressors/mod.rs +++ b/src/decompressors/mod.rs @@ -1,3 +1,5 @@ +//! This module contains the Decompressor trait and an implementor for each format. + mod decompressor; mod tar; mod to_memory; @@ -5,11 +7,6 @@ mod zip; pub use decompressor::{DecompressionResult, Decompressor}; -// These decompressors only decompress to memory, -// unlike {Tar, Zip}Decompressor which are capable of -// decompressing directly to storage -pub use self::{ - tar::TarDecompressor, - to_memory::{BzipDecompressor, GzipDecompressor, LzmaDecompressor}, - zip::ZipDecompressor, -}; +pub use self::to_memory::{BzipDecompressor, GzipDecompressor, LzmaDecompressor}; +// The .tar and .zip decompressors are capable of decompressing directly to storage +pub use self::{tar::TarDecompressor, zip::ZipDecompressor}; diff --git a/src/decompressors/tar.rs b/src/decompressors/tar.rs index 5461b08..d3374d3 100644 --- a/src/decompressors/tar.rs +++ b/src/decompressors/tar.rs @@ -11,7 +11,7 @@ use super::decompressor::{DecompressionResult, Decompressor}; use crate::{bytes::Bytes, dialogs::Confirmation, file::File, utils}; #[derive(Debug)] -pub struct TarDecompressor {} +pub struct TarDecompressor; impl TarDecompressor { fn unpack_files(from: File, into: &Path, flags: &oof::Flags) -> crate::Result> { diff --git a/src/decompressors/to_memory.rs b/src/decompressors/to_memory.rs index d7af622..3a3f47f 100644 --- a/src/decompressors/to_memory.rs +++ b/src/decompressors/to_memory.rs @@ -6,14 +6,12 @@ use std::{ use colored::Colorize; use super::decompressor::{DecompressionResult, Decompressor}; -use crate::bytes::Bytes; -use crate::utils; -use crate::{extension::CompressionFormat, file::File}; +use crate::{bytes::Bytes, extension::CompressionFormat, file::File, utils}; -struct DecompressorToMemory {} -pub struct GzipDecompressor {} -pub struct LzmaDecompressor {} -pub struct BzipDecompressor {} +struct DecompressorToMemory; +pub struct GzipDecompressor; +pub struct LzmaDecompressor; +pub struct BzipDecompressor; fn get_decoder<'a>( format: CompressionFormat, diff --git a/src/decompressors/zip.rs b/src/decompressors/zip.rs index dcf1329..e3d2cb9 100644 --- a/src/decompressors/zip.rs +++ b/src/decompressors/zip.rs @@ -19,7 +19,7 @@ fn __unix_set_permissions(file_path: &Path, file: &ZipFile) { } } -pub struct ZipDecompressor {} +pub struct ZipDecompressor; impl ZipDecompressor { fn check_for_comments(file: &ZipFile) { diff --git a/src/evaluator.rs b/src/evaluator.rs index 6093dc5..e90b400 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -8,7 +8,7 @@ use colored::Colorize; use crate::{ bytes::Bytes, - cli::{Command, VERSION}, + cli::Command, compressors::{ BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor, ZipCompressor, @@ -23,7 +23,7 @@ use crate::{ utils, }; -pub struct Evaluator {} +pub struct Evaluator; type BoxedCompressor = Box; type BoxedDecompressor = Box; @@ -48,11 +48,11 @@ impl Evaluator { // .tar and .zip let first_compressor: Option> = match extension.first_ext { Some(ext) => match ext { - CompressionFormat::Tar => Some(Box::new(TarCompressor {})), - CompressionFormat::Zip => Some(Box::new(ZipCompressor {})), - CompressionFormat::Bzip => Some(Box::new(BzipCompressor {})), - CompressionFormat::Gzip => Some(Box::new(GzipCompressor {})), - CompressionFormat::Lzma => Some(Box::new(LzmaCompressor {})), + CompressionFormat::Tar => Some(Box::new(TarCompressor)), + CompressionFormat::Zip => Some(Box::new(ZipCompressor)), + CompressionFormat::Bzip => Some(Box::new(BzipCompressor)), + CompressionFormat::Gzip => Some(Box::new(GzipCompressor)), + CompressionFormat::Lzma => Some(Box::new(LzmaCompressor)), }, None => None, }; @@ -60,11 +60,11 @@ impl Evaluator { // Supported second compressors: // any let second_compressor: Box = match extension.second_ext { - CompressionFormat::Tar => Box::new(TarCompressor {}), - CompressionFormat::Zip => Box::new(ZipCompressor {}), - CompressionFormat::Bzip => Box::new(BzipCompressor {}), - CompressionFormat::Gzip => Box::new(GzipCompressor {}), - CompressionFormat::Lzma => Box::new(LzmaCompressor {}), + CompressionFormat::Tar => Box::new(TarCompressor), + CompressionFormat::Zip => Box::new(ZipCompressor), + CompressionFormat::Bzip => Box::new(BzipCompressor), + CompressionFormat::Gzip => Box::new(GzipCompressor), + CompressionFormat::Lzma => Box::new(LzmaCompressor), }; Ok((first_compressor, second_compressor)) @@ -86,17 +86,17 @@ impl Evaluator { }; let second_decompressor: Box = match extension.second_ext { - CompressionFormat::Tar => Box::new(TarDecompressor {}), - CompressionFormat::Zip => Box::new(ZipDecompressor {}), - CompressionFormat::Gzip => Box::new(GzipDecompressor {}), - CompressionFormat::Lzma => Box::new(LzmaDecompressor {}), - CompressionFormat::Bzip => Box::new(BzipDecompressor {}), + CompressionFormat::Tar => Box::new(TarDecompressor), + CompressionFormat::Zip => Box::new(ZipDecompressor), + CompressionFormat::Gzip => Box::new(GzipDecompressor), + CompressionFormat::Lzma => Box::new(LzmaDecompressor), + CompressionFormat::Bzip => Box::new(BzipDecompressor), }; let first_decompressor: Option> = match extension.first_ext { Some(ext) => match ext { - CompressionFormat::Tar => Some(Box::new(TarDecompressor {})), - CompressionFormat::Zip => Some(Box::new(ZipDecompressor {})), + CompressionFormat::Tar => Some(Box::new(TarDecompressor)), + CompressionFormat::Zip => Some(Box::new(ZipDecompressor)), _other => None, }, None => None, @@ -282,7 +282,7 @@ impl Evaluator { #[inline] fn version_message() { - println!("ouch {}", VERSION); + println!("ouch {}", crate::VERSION); } fn help_message() { diff --git a/src/extension.rs b/src/extension.rs index 43e0e1a..6ca7e4d 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -86,16 +86,11 @@ impl Extension { #[derive(Clone, PartialEq, Eq, Debug)] /// Accepted extensions for input and output pub enum CompressionFormat { - // .gz - Gzip, - // .bz - Bzip, - // .lzma - Lzma, - // .tar (technically not a compression extension, but will do for now) - Tar, - // .zip - Zip, + Gzip, // .gz + Bzip, // .bz + Lzma, // .lzma + Tar, // .tar (technically not a compression extension, but will do for now) + Zip, // .zip } fn extension_from_os_str(ext: &OsStr) -> Result { diff --git a/src/main.rs b/src/main.rs index 984f191..784e59b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,8 @@ mod file; mod test; mod utils; +pub const VERSION: &str = "0.1.5"; + use error::{Error, Result}; use evaluator::Evaluator; diff --git a/src/utils.rs b/src/utils.rs index 3b6d4d1..1953ccf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -41,7 +41,14 @@ pub(crate) fn check_for_multiple_files( format: &CompressionFormat, ) -> crate::Result<()> { 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); + 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(crate::Error::InvalidInput); } From aa03d2723e3ed56b0cd3859fbd786313fcf147ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 6 Apr 2021 20:22:03 -0300 Subject: [PATCH 05/11] Separate lib from binary Makes integration testing easier --- src/lib.rs | 18 ++++++++++++++++++ src/main.rs | 25 ++++++------------------- 2 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..946f03c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,18 @@ +// Public modules +pub mod cli; +pub mod evaluator; + +// Private modules +mod bytes; +mod compressors; +mod decompressors; +mod dialogs; +mod error; +mod extension; +mod file; +mod test; +mod utils; + +const VERSION: &str = "0.1.5"; + +pub use error::{Error, Result}; diff --git a/src/main.rs b/src/main.rs index 784e59b..506ebc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,8 @@ -mod bytes; -mod cli; -mod compressors; -mod decompressors; -mod dialogs; -mod error; -mod evaluator; -mod extension; -mod file; -mod test; -mod utils; - -pub const VERSION: &str = "0.1.5"; - -use error::{Error, Result}; -use evaluator::Evaluator; - -use crate::cli::ParsedArgs; +use ouch::{ + cli::{parse_args, ParsedArgs}, + evaluator::Evaluator, + Result, +}; fn main() { if let Err(err) = run() { @@ -25,6 +12,6 @@ fn main() { } fn run() -> crate::Result<()> { - let ParsedArgs { command, flags } = cli::parse_args()?; + let ParsedArgs { command, flags } = parse_args()?; Evaluator::evaluate(command, &flags) } From 20bcf1ecde5cb58992593ab61f1e33d545840a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Tue, 6 Apr 2021 20:36:47 -0300 Subject: [PATCH 06/11] evaluator: Fix an error message --- src/evaluator.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/evaluator.rs b/src/evaluator.rs index e90b400..beb04a1 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -35,12 +35,8 @@ impl Evaluator { let extension = match &file.extension { Some(extension) => extension.clone(), None => { - // This block *should* be unreachable - eprintln!( - "{} reached Evaluator::get_decompressor without known extension.", - "[internal error]".red() - ); - return Err(crate::Error::InternalError); + // This is reached when the output file given does not have an extension or has an unsupported one + return Err(crate::Error::MissingExtensionError(file.path.to_path_buf())); } }; From 3869c2502e3e6608c571e735fce866afca6f6fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 6 Apr 2021 21:16:33 -0300 Subject: [PATCH 07/11] Moving bytes.rs implementation to utils.rs --- src/bytes.rs | 71 ----------------------------- src/compressors/bzip.rs | 13 ++---- src/compressors/gzip.rs | 13 ++---- src/compressors/lzma.rs | 13 ++---- src/decompressors/tar.rs | 4 +- src/decompressors/to_memory.rs | 4 +- src/decompressors/zip.rs | 4 +- src/evaluator.rs | 3 +- src/extension.rs | 4 +- src/lib.rs | 2 - src/utils.rs | 82 +++++++++++++++++++++++++++++++--- 11 files changed, 97 insertions(+), 116 deletions(-) delete mode 100644 src/bytes.rs diff --git a/src/bytes.rs b/src/bytes.rs deleted file mode 100644 index 25ec575..0000000 --- a/src/bytes.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::cmp; - -const UNIT_PREFIXES: [&str; 6] = ["", "k", "M", "G", "T", "P"]; - -pub struct Bytes { - bytes: f64, -} - -impl Bytes { - pub fn new(bytes: u64) -> Self { - Self { - bytes: bytes as f64, - } - } -} - -impl std::fmt::Display for Bytes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let num = self.bytes; - debug_assert!(num >= 0.0); - if num < 1_f64 { - return write!(f, "{} B", num); - } - let delimiter = 1000_f64; - let exponent = cmp::min((num.ln() / 6.90775).floor() as i32, 4); - - write!(f, "{:.2} ", num / delimiter.powi(exponent))?; - write!(f, "{}B", UNIT_PREFIXES[exponent as usize]) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_bytes_formatting() { - fn format_bytes(bytes: u64) -> String { - format!("{}", Bytes::new(bytes)) - } - let b = 1; - let kb = b * 1000; - let mb = kb * 1000; - let gb = mb * 1000; - - assert_eq!("0 B", format_bytes(0)); // This is weird - assert_eq!("1.00 B", format_bytes(b)); - assert_eq!("999.00 B", format_bytes(b * 999)); - assert_eq!("12.00 MB", format_bytes(mb * 12)); - assert_eq!("123.00 MB", format_bytes(mb * 123)); - assert_eq!("5.50 MB", format_bytes(mb * 5 + kb * 500)); - assert_eq!("7.54 GB", format_bytes(gb * 7 + 540 * mb)); - assert_eq!("1.20 TB", format_bytes(gb * 1200)); - - // bytes - assert_eq!("234.00 B", format_bytes(234)); - assert_eq!("999.00 B", format_bytes(999)); - // kilobytes - assert_eq!("2.23 kB", format_bytes(2234)); - assert_eq!("62.50 kB", format_bytes(62500)); - assert_eq!("329.99 kB", format_bytes(329990)); - // megabytes - assert_eq!("2.75 MB", format_bytes(2750000)); - assert_eq!("55.00 MB", format_bytes(55000000)); - assert_eq!("987.65 MB", format_bytes(987654321)); - // gigabytes - assert_eq!("5.28 GB", format_bytes(5280000000)); - assert_eq!("95.20 GB", format_bytes(95200000000)); - assert_eq!("302.00 GB", format_bytes(302000000000)); - } -} diff --git a/src/compressors/bzip.rs b/src/compressors/bzip.rs index 43fea59..a08c7c9 100644 --- a/src/compressors/bzip.rs +++ b/src/compressors/bzip.rs @@ -3,20 +3,15 @@ use std::{fs, io::Write, path::PathBuf}; use colored::Colorize; use super::{Compressor, Entry}; -use crate::{ - bytes::Bytes, - extension::CompressionFormat, - file::File, - utils::{check_for_multiple_files, ensure_exists}, -}; +use crate::{extension::CompressionFormat, file::File, utils}; pub struct BzipCompressor; impl BzipCompressor { fn compress_files(files: Vec, format: CompressionFormat) -> crate::Result> { - check_for_multiple_files(&files, &format)?; + utils::check_for_multiple_files(&files, &format)?; let path = &files[0]; - ensure_exists(path)?; + utils::ensure_exists(path)?; let contents = { let bytes = fs::read(path)?; Self::compress_bytes(&*bytes)? @@ -26,7 +21,7 @@ impl BzipCompressor { "{}: compressed {:?} into memory ({})", "info".yellow(), &path, - Bytes::new(contents.len() as u64) + utils::Bytes::new(contents.len() as u64) ); Ok(contents) diff --git a/src/compressors/gzip.rs b/src/compressors/gzip.rs index 6824d2c..9399469 100644 --- a/src/compressors/gzip.rs +++ b/src/compressors/gzip.rs @@ -3,12 +3,7 @@ use std::{fs, io::Write, path::PathBuf}; use colored::Colorize; use super::{Compressor, Entry}; -use crate::{ - bytes::Bytes, - extension::CompressionFormat, - file::File, - utils::{check_for_multiple_files, ensure_exists}, -}; +use crate::{extension::CompressionFormat, file::File, utils}; pub struct GzipCompressor; @@ -17,10 +12,10 @@ impl GzipCompressor { files: Vec, format: CompressionFormat, ) -> crate::Result> { - check_for_multiple_files(&files, &format)?; + utils::check_for_multiple_files(&files, &format)?; let path = &files[0]; - ensure_exists(path)?; + utils::ensure_exists(path)?; let bytes = { let bytes = fs::read(path)?; @@ -31,7 +26,7 @@ impl GzipCompressor { "{}: compressed {:?} into memory ({})", "info".yellow(), &path, - Bytes::new(bytes.len() as u64) + utils::Bytes::new(bytes.len() as u64) ); Ok(bytes) diff --git a/src/compressors/lzma.rs b/src/compressors/lzma.rs index 5517730..d18fc0d 100644 --- a/src/compressors/lzma.rs +++ b/src/compressors/lzma.rs @@ -3,12 +3,7 @@ use std::{fs, io::Write, path::PathBuf}; use colored::Colorize; use super::{Compressor, Entry}; -use crate::{ - bytes::Bytes, - extension::CompressionFormat, - file::File, - utils::{check_for_multiple_files, ensure_exists}, -}; +use crate::{extension::CompressionFormat, file::File, utils}; pub struct LzmaCompressor; @@ -17,10 +12,10 @@ impl LzmaCompressor { files: Vec, format: CompressionFormat, ) -> crate::Result> { - check_for_multiple_files(&files, &format)?; + utils::check_for_multiple_files(&files, &format)?; let path = &files[0]; - ensure_exists(path)?; + utils::ensure_exists(path)?; let bytes = { let bytes = fs::read(path)?; @@ -31,7 +26,7 @@ impl LzmaCompressor { "{}: compressed {:?} into memory ({})", "info".yellow(), &path, - Bytes::new(bytes.len() as u64) + utils::Bytes::new(bytes.len() as u64) ); Ok(bytes) diff --git a/src/decompressors/tar.rs b/src/decompressors/tar.rs index d3374d3..7981750 100644 --- a/src/decompressors/tar.rs +++ b/src/decompressors/tar.rs @@ -8,7 +8,7 @@ use colored::Colorize; use tar::{self, Archive}; use super::decompressor::{DecompressionResult, Decompressor}; -use crate::{bytes::Bytes, dialogs::Confirmation, file::File, utils}; +use crate::{dialogs::Confirmation, file::File, utils}; #[derive(Debug)] pub struct TarDecompressor; @@ -48,7 +48,7 @@ impl TarDecompressor { "{}: {:?} extracted. ({})", "info".yellow(), into.join(file.path()?), - Bytes::new(file.size()) + utils::Bytes::new(file.size()) ); let file_path = fs::canonicalize(file_path)?; diff --git a/src/decompressors/to_memory.rs b/src/decompressors/to_memory.rs index 3a3f47f..a76e060 100644 --- a/src/decompressors/to_memory.rs +++ b/src/decompressors/to_memory.rs @@ -6,7 +6,7 @@ use std::{ use colored::Colorize; use super::decompressor::{DecompressionResult, Decompressor}; -use crate::{bytes::Bytes, extension::CompressionFormat, file::File, utils}; +use crate::{extension::CompressionFormat, file::File, utils}; struct DecompressorToMemory; pub struct GzipDecompressor; @@ -38,7 +38,7 @@ impl DecompressorToMemory { "{}: {:?} extracted into memory ({}).", "info".yellow(), path, - Bytes::new(bytes_read as u64) + utils::Bytes::new(bytes_read as u64) ); Ok(buffer) diff --git a/src/decompressors/zip.rs b/src/decompressors/zip.rs index e3d2cb9..ac2120c 100644 --- a/src/decompressors/zip.rs +++ b/src/decompressors/zip.rs @@ -8,7 +8,7 @@ use colored::Colorize; use zip::{self, read::ZipFile, ZipArchive}; use super::decompressor::{DecompressionResult, Decompressor}; -use crate::{bytes::Bytes, dialogs::Confirmation, file::File, utils}; +use crate::{dialogs::Confirmation, file::File, utils}; #[cfg(unix)] fn __unix_set_permissions(file_path: &Path, file: &ZipFile) { @@ -76,7 +76,7 @@ impl ZipDecompressor { "{}: \"{}\" extracted. ({})", "info".yellow(), file_path.display(), - Bytes::new(file.size()) + utils::Bytes::new(file.size()) ); let mut output_file = fs::File::create(&file_path)?; diff --git a/src/evaluator.rs b/src/evaluator.rs index beb04a1..1fbf017 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -7,7 +7,6 @@ use std::{ use colored::Colorize; use crate::{ - bytes::Bytes, cli::Command, compressors::{ BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor, @@ -195,7 +194,7 @@ impl Evaluator { "{}: writing to {:?}. ({})", "info".yellow(), output_path, - Bytes::new(bytes.len() as u64) + utils::Bytes::new(bytes.len() as u64) ); fs::write(output_path, bytes)?; diff --git a/src/extension.rs b/src/extension.rs index 6ca7e4d..06de63d 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -7,7 +7,7 @@ use std::{ use CompressionFormat::*; -use crate::utils::to_utf; +use crate::utils; /// Represents the extension of a file, but only really caring about /// compression formats (and .tar). @@ -49,7 +49,7 @@ impl Extension { _ if ext == "gz" => Ok(Gzip), _ if ext == "bz" || ext == "bz2" => Ok(Bzip), _ if ext == "xz" || ext == "lz" || ext == "lzma" => Ok(Lzma), - other => Err(crate::Error::UnknownExtensionError(to_utf(other))), + other => Err(crate::Error::UnknownExtensionError(utils::to_utf(other))), }; let (first_ext, second_ext) = match get_extension_from_filename(&file_name) { diff --git a/src/lib.rs b/src/lib.rs index 946f03c..9c51d9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,14 +3,12 @@ pub mod cli; pub mod evaluator; // Private modules -mod bytes; mod compressors; mod decompressors; mod dialogs; mod error; mod extension; mod file; -mod test; mod utils; const VERSION: &str = "0.1.5"; diff --git a/src/utils.rs b/src/utils.rs index 1953ccf..64df0e0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,5 @@ use std::{ - env, + cmp, env, ffi::OsStr, fs, path::{Path, PathBuf}, @@ -25,7 +25,7 @@ macro_rules! debug { }; } -pub(crate) fn ensure_exists<'a, P>(path: P) -> crate::Result<()> +pub fn ensure_exists<'a, P>(path: P) -> crate::Result<()> where P: AsRef + 'a, { @@ -36,7 +36,7 @@ where Ok(()) } -pub(crate) fn check_for_multiple_files( +pub fn check_for_multiple_files( files: &[PathBuf], format: &CompressionFormat, ) -> crate::Result<()> { @@ -55,7 +55,7 @@ pub(crate) fn check_for_multiple_files( Ok(()) } -pub(crate) fn create_path_if_non_existent(path: &Path) -> crate::Result<()> { +pub fn create_path_if_non_existent(path: &Path) -> crate::Result<()> { if !path.exists() { println!( "{}: attempting to create folder {:?}.", @@ -72,7 +72,7 @@ pub(crate) fn create_path_if_non_existent(path: &Path) -> crate::Result<()> { Ok(()) } -pub(crate) fn get_destination_path<'a>(dest: &'a Option) -> &'a Path { +pub fn get_destination_path<'a>(dest: &'a Option) -> &'a Path { match dest { Some(output_file) => { // Must be None according to the way command-line arg. parsing in Ouch works @@ -83,7 +83,7 @@ pub(crate) fn get_destination_path<'a>(dest: &'a Option) -> &'a Path { } } -pub(crate) fn change_dir_and_return_parent(filename: &Path) -> crate::Result { +pub fn change_dir_and_return_parent(filename: &Path) -> crate::Result { let previous_location = env::current_dir()?; let parent = if let Some(parent) = filename.parent() { @@ -121,3 +121,73 @@ pub fn to_utf(os_str: impl AsRef) -> String { let text = format!("{:?}", os_str.as_ref()); text.trim_matches('"').to_string() } + +pub struct Bytes { + bytes: f64, +} + +impl Bytes { + const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"]; + + pub fn new(bytes: u64) -> Self { + Self { + bytes: bytes as f64, + } + } +} + +impl std::fmt::Display for Bytes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let num = self.bytes; + debug_assert!(num >= 0.0); + if num < 1_f64 { + return write!(f, "{} B", num); + } + let delimiter = 1000_f64; + let exponent = cmp::min((num.ln() / 6.90775).floor() as i32, 4); + + write!(f, "{:.2} ", num / delimiter.powi(exponent))?; + write!(f, "{}B", Bytes::UNIT_PREFIXES[exponent as usize]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pretty_bytes_formatting() { + fn format_bytes(bytes: u64) -> String { + format!("{}", Bytes::new(bytes)) + } + let b = 1; + let kb = b * 1000; + let mb = kb * 1000; + let gb = mb * 1000; + + assert_eq!("0 B", format_bytes(0)); // This is weird + assert_eq!("1.00 B", format_bytes(b)); + assert_eq!("999.00 B", format_bytes(b * 999)); + assert_eq!("12.00 MB", format_bytes(mb * 12)); + assert_eq!("123.00 MB", format_bytes(mb * 123)); + assert_eq!("5.50 MB", format_bytes(mb * 5 + kb * 500)); + assert_eq!("7.54 GB", format_bytes(gb * 7 + 540 * mb)); + assert_eq!("1.20 TB", format_bytes(gb * 1200)); + + // bytes + assert_eq!("234.00 B", format_bytes(234)); + assert_eq!("999.00 B", format_bytes(999)); + // kilobytes + assert_eq!("2.23 kB", format_bytes(2234)); + assert_eq!("62.50 kB", format_bytes(62500)); + assert_eq!("329.99 kB", format_bytes(329990)); + // megabytes + assert_eq!("2.75 MB", format_bytes(2750000)); + assert_eq!("55.00 MB", format_bytes(55000000)); + assert_eq!("987.65 MB", format_bytes(987654321)); + // gigabytes + assert_eq!("5.28 GB", format_bytes(5280000000)); + assert_eq!("95.20 GB", format_bytes(95200000000)); + assert_eq!("302.00 GB", format_bytes(302000000000)); + } +} From 973af5fe1cb1505cc82d34ae50c8ad3c882f00d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 6 Apr 2021 21:18:56 -0300 Subject: [PATCH 08/11] Change evaluator.rs to commands.rs --- src/commands.rs | 281 +++++++++++++++++++++++++++++++++++++++++++++ src/evaluator.rs | 292 ----------------------------------------------- src/lib.rs | 2 +- src/main.rs | 5 +- 4 files changed, 284 insertions(+), 296 deletions(-) create mode 100644 src/commands.rs delete mode 100644 src/evaluator.rs diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..8e00634 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,281 @@ +use std::{ + fs, + io::Write, + path::{Path, PathBuf}, +}; + +use colored::Colorize; + +use crate::{ + cli::Command, + compressors::{ + BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor, + ZipCompressor, + }, + decompressors::{ + BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor, + TarDecompressor, ZipDecompressor, + }, + dialogs::Confirmation, + extension::{CompressionFormat, Extension}, + file::File, + utils, +}; + +pub fn run(command: Command, flags: &oof::Flags) -> crate::Result<()> { + match command { + Command::Compress { + files, + compressed_output_path, + } => compress_files(files, &compressed_output_path, flags)?, + Command::Decompress { + files, + output_folder, + } => { + // From Option to Option<&Path> + let output_folder = output_folder.as_ref().map(|path| Path::new(path)); + for file in files.iter() { + decompress_file(file, output_folder, flags)?; + } + } + Command::ShowHelp => help_command(), + Command::ShowVersion => version_command(), + } + Ok(()) +} + +fn help_command() { + version_command(); + println!("Vinícius R. M. & João M. Bezerra"); + println!("ouch is a unified compression & decompression utility"); + println!(); + println!(" COMPRESSION USAGE:"); + println!(" ouch compress output-file"); + println!("DECOMPRESSION USAGE:"); + println!(" ouch [-o/--output output-folder]"); +} + +#[inline] +fn version_command() { + println!("ouch {}", crate::VERSION); +} + +type BoxedCompressor = Box; +type BoxedDecompressor = Box; + +fn get_compressor(file: &File) -> crate::Result<(Option, BoxedCompressor)> { + let extension = match &file.extension { + Some(extension) => extension.clone(), + None => { + // This is reached when the output file given does not have an extension or has an unsupported one + return Err(crate::Error::MissingExtensionError(file.path.to_path_buf())); + } + }; + + // Supported first compressors: + // .tar and .zip + let first_compressor: Option> = match extension.first_ext { + Some(ext) => match ext { + CompressionFormat::Tar => Some(Box::new(TarCompressor)), + CompressionFormat::Zip => Some(Box::new(ZipCompressor)), + CompressionFormat::Bzip => Some(Box::new(BzipCompressor)), + CompressionFormat::Gzip => Some(Box::new(GzipCompressor)), + CompressionFormat::Lzma => Some(Box::new(LzmaCompressor)), + }, + None => None, + }; + + // Supported second compressors: + // any + let second_compressor: Box = match extension.second_ext { + CompressionFormat::Tar => Box::new(TarCompressor), + CompressionFormat::Zip => Box::new(ZipCompressor), + CompressionFormat::Bzip => Box::new(BzipCompressor), + CompressionFormat::Gzip => Box::new(GzipCompressor), + CompressionFormat::Lzma => Box::new(LzmaCompressor), + }; + + Ok((first_compressor, second_compressor)) +} + +fn get_decompressor(file: &File) -> crate::Result<(Option, BoxedDecompressor)> { + let extension = match &file.extension { + Some(extension) => extension.clone(), + None => { + // This block *should* be unreachable + eprintln!( + "{} reached Evaluator::get_decompressor without known extension.", + "[internal error]".red() + ); + return Err(crate::Error::InvalidInput); + } + }; + + let second_decompressor: Box = match extension.second_ext { + CompressionFormat::Tar => Box::new(TarDecompressor), + CompressionFormat::Zip => Box::new(ZipDecompressor), + CompressionFormat::Gzip => Box::new(GzipDecompressor), + CompressionFormat::Lzma => Box::new(LzmaDecompressor), + CompressionFormat::Bzip => Box::new(BzipDecompressor), + }; + + let first_decompressor: Option> = match extension.first_ext { + Some(ext) => match ext { + CompressionFormat::Tar => Some(Box::new(TarDecompressor)), + CompressionFormat::Zip => Some(Box::new(ZipDecompressor)), + _other => None, + }, + None => None, + }; + + Ok((first_decompressor, second_decompressor)) +} + +fn decompress_file_in_memory( + bytes: Vec, + file_path: &Path, + decompressor: Option>, + output_file: Option, + extension: Option, + flags: &oof::Flags, +) -> crate::Result<()> { + let output_file_path = utils::get_destination_path(&output_file); + + let file_name = file_path + .file_stem() + .map(Path::new) + .unwrap_or(output_file_path); + + if "." == file_name.as_os_str() { + // I believe this is only possible when the supplied input has a name + // of the sort `.tar` or `.zip' and no output has been supplied. + // file_name = OsStr::new("ouch-output"); + todo!("Pending review, what is this supposed to do??"); + } + + // If there is a decompressor to use, we'll create a file in-memory and decompress it + let decompressor = match decompressor { + Some(decompressor) => decompressor, + None => { + // 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. + println!("{}: saving to {:?}.", "info".yellow(), file_name); + + if file_name.exists() { + let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); + if !utils::permission_for_overwriting(&file_name, flags, &confirm)? { + return Ok(()); + } + } + + let mut f = fs::File::create(output_file_path.join(file_name))?; + f.write_all(&bytes)?; + return Ok(()); + } + }; + + let file = File { + path: file_name, + contents_in_memory: Some(bytes), + extension, + }; + + let decompression_result = decompressor.decompress(file, &output_file, flags)?; + if let DecompressionResult::FileInMemory(_) = decompression_result { + unreachable!("Shouldn't"); + } + + Ok(()) +} + +fn compress_files( + files: Vec, + output_path: &Path, + flags: &oof::Flags, +) -> crate::Result<()> { + let mut output = File::from(output_path)?; + + let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); + let (first_compressor, second_compressor) = get_compressor(&output)?; + + if output_path.exists() && !utils::permission_for_overwriting(&output_path, flags, &confirm)? { + // The user does not want to overwrite the file + return Ok(()); + } + + let bytes = match first_compressor { + Some(first_compressor) => { + let mut entry = Entry::Files(files); + let bytes = first_compressor.compress(entry)?; + + output.contents_in_memory = Some(bytes); + entry = Entry::InMemory(output); + second_compressor.compress(entry)? + } + None => { + let entry = Entry::Files(files); + second_compressor.compress(entry)? + } + }; + + println!( + "{}: writing to {:?}. ({})", + "info".yellow(), + output_path, + utils::Bytes::new(bytes.len() as u64) + ); + fs::write(output_path, bytes)?; + + Ok(()) +} + +fn decompress_file( + file_path: &Path, + output: Option<&Path>, + flags: &oof::Flags, +) -> crate::Result<()> { + // The file to be decompressed + let file = File::from(file_path)?; + // The file must have a supported decompressible format + if file.extension == None { + return Err(crate::Error::MissingExtensionError(PathBuf::from( + file_path, + ))); + } + + let output = match output { + Some(inner) => Some(File::from(inner)?), + None => None, + }; + let (first_decompressor, second_decompressor) = get_decompressor(&file)?; + + let extension = file.extension.clone(); + + let decompression_result = second_decompressor.decompress(file, &output, &flags)?; + + match decompression_result { + DecompressionResult::FileInMemory(bytes) => { + // We'll now decompress a file currently in memory. + // This will currently happen in the case of .bz, .xz and .lzma + decompress_file_in_memory( + bytes, + file_path, + first_decompressor, + output, + extension, + flags, + )?; + } + DecompressionResult::FilesUnpacked(_files) => { + // If the file's last extension was an archival method, + // such as .tar, .zip or (to-do) .rar, then we won't look for + // further processing. + // The reason for this is that cases such as "file.xz.tar" are too rare + // to worry about, at least at the moment. + + // TODO: use the `files` variable for something + } + } + + Ok(()) +} diff --git a/src/evaluator.rs b/src/evaluator.rs deleted file mode 100644 index 1fbf017..0000000 --- a/src/evaluator.rs +++ /dev/null @@ -1,292 +0,0 @@ -use std::{ - fs, - io::Write, - path::{Path, PathBuf}, -}; - -use colored::Colorize; - -use crate::{ - cli::Command, - compressors::{ - BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor, - ZipCompressor, - }, - decompressors::{ - BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor, - TarDecompressor, ZipDecompressor, - }, - dialogs::Confirmation, - extension::{CompressionFormat, Extension}, - file::File, - utils, -}; - -pub struct Evaluator; - -type BoxedCompressor = Box; -type BoxedDecompressor = Box; - -impl Evaluator { - pub fn get_compressor( - file: &File, - ) -> crate::Result<(Option, BoxedCompressor)> { - let extension = match &file.extension { - Some(extension) => extension.clone(), - None => { - // This is reached when the output file given does not have an extension or has an unsupported one - return Err(crate::Error::MissingExtensionError(file.path.to_path_buf())); - } - }; - - // Supported first compressors: - // .tar and .zip - let first_compressor: Option> = match extension.first_ext { - Some(ext) => match ext { - CompressionFormat::Tar => Some(Box::new(TarCompressor)), - CompressionFormat::Zip => Some(Box::new(ZipCompressor)), - CompressionFormat::Bzip => Some(Box::new(BzipCompressor)), - CompressionFormat::Gzip => Some(Box::new(GzipCompressor)), - CompressionFormat::Lzma => Some(Box::new(LzmaCompressor)), - }, - None => None, - }; - - // Supported second compressors: - // any - let second_compressor: Box = match extension.second_ext { - CompressionFormat::Tar => Box::new(TarCompressor), - CompressionFormat::Zip => Box::new(ZipCompressor), - CompressionFormat::Bzip => Box::new(BzipCompressor), - CompressionFormat::Gzip => Box::new(GzipCompressor), - CompressionFormat::Lzma => Box::new(LzmaCompressor), - }; - - Ok((first_compressor, second_compressor)) - } - - pub fn get_decompressor( - file: &File, - ) -> crate::Result<(Option, BoxedDecompressor)> { - let extension = match &file.extension { - Some(extension) => extension.clone(), - None => { - // This block *should* be unreachable - eprintln!( - "{} reached Evaluator::get_decompressor without known extension.", - "[internal error]".red() - ); - return Err(crate::Error::InvalidInput); - } - }; - - let second_decompressor: Box = match extension.second_ext { - CompressionFormat::Tar => Box::new(TarDecompressor), - CompressionFormat::Zip => Box::new(ZipDecompressor), - CompressionFormat::Gzip => Box::new(GzipDecompressor), - CompressionFormat::Lzma => Box::new(LzmaDecompressor), - CompressionFormat::Bzip => Box::new(BzipDecompressor), - }; - - let first_decompressor: Option> = match extension.first_ext { - Some(ext) => match ext { - CompressionFormat::Tar => Some(Box::new(TarDecompressor)), - CompressionFormat::Zip => Some(Box::new(ZipDecompressor)), - _other => None, - }, - None => None, - }; - - Ok((first_decompressor, second_decompressor)) - } - - fn decompress_file_in_memory( - bytes: Vec, - file_path: &Path, - decompressor: Option>, - output_file: Option, - extension: Option, - flags: &oof::Flags, - ) -> crate::Result<()> { - let output_file_path = utils::get_destination_path(&output_file); - - let file_name = file_path - .file_stem() - .map(Path::new) - .unwrap_or(output_file_path); - - if "." == file_name.as_os_str() { - // I believe this is only possible when the supplied input has a name - // of the sort `.tar` or `.zip' and no output has been supplied. - // file_name = OsStr::new("ouch-output"); - todo!("Pending review, what is this supposed to do??"); - } - - // If there is a decompressor to use, we'll create a file in-memory and decompress it - let decompressor = match decompressor { - Some(decompressor) => decompressor, - None => { - // 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. - println!("{}: saving to {:?}.", "info".yellow(), file_name); - - if file_name.exists() { - let confirm = - Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); - if !utils::permission_for_overwriting(&file_name, flags, &confirm)? { - return Ok(()); - } - } - - let mut f = fs::File::create(output_file_path.join(file_name))?; - f.write_all(&bytes)?; - return Ok(()); - } - }; - - let file = File { - path: file_name, - contents_in_memory: Some(bytes), - extension, - }; - - let decompression_result = decompressor.decompress(file, &output_file, flags)?; - if let DecompressionResult::FileInMemory(_) = decompression_result { - unreachable!("Shouldn't"); - } - - Ok(()) - } - - fn compress_files( - files: Vec, - output_path: &Path, - flags: &oof::Flags, - ) -> crate::Result<()> { - let mut output = File::from(output_path)?; - - let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); - let (first_compressor, second_compressor) = Self::get_compressor(&output)?; - - if output_path.exists() - && !utils::permission_for_overwriting(&output_path, flags, &confirm)? - { - // The user does not want to overwrite the file - return Ok(()); - } - - let bytes = match first_compressor { - Some(first_compressor) => { - let mut entry = Entry::Files(files); - let bytes = first_compressor.compress(entry)?; - - output.contents_in_memory = Some(bytes); - entry = Entry::InMemory(output); - second_compressor.compress(entry)? - } - None => { - let entry = Entry::Files(files); - second_compressor.compress(entry)? - } - }; - - println!( - "{}: writing to {:?}. ({})", - "info".yellow(), - output_path, - utils::Bytes::new(bytes.len() as u64) - ); - fs::write(output_path, bytes)?; - - Ok(()) - } - - fn decompress_file( - file_path: &Path, - output: Option<&Path>, - flags: &oof::Flags, - ) -> crate::Result<()> { - // The file to be decompressed - let file = File::from(file_path)?; - // The file must have a supported decompressible format - if file.extension == None { - return Err(crate::Error::MissingExtensionError(PathBuf::from( - file_path, - ))); - } - - let output = match output { - Some(inner) => Some(File::from(inner)?), - None => None, - }; - let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?; - - let extension = file.extension.clone(); - - let decompression_result = second_decompressor.decompress(file, &output, &flags)?; - - match decompression_result { - DecompressionResult::FileInMemory(bytes) => { - // We'll now decompress a file currently in memory. - // This will currently happen in the case of .bz, .xz and .lzma - Self::decompress_file_in_memory( - bytes, - file_path, - first_decompressor, - output, - extension, - flags, - )?; - } - DecompressionResult::FilesUnpacked(_files) => { - // If the file's last extension was an archival method, - // such as .tar, .zip or (to-do) .rar, then we won't look for - // further processing. - // The reason for this is that cases such as "file.xz.tar" are too rare - // to worry about, at least at the moment. - - // TODO: use the `files` variable for something - } - } - - Ok(()) - } - - pub fn evaluate(command: Command, flags: &oof::Flags) -> crate::Result<()> { - match command { - Command::Compress { - files, - compressed_output_path, - } => Self::compress_files(files, &compressed_output_path, flags)?, - Command::Decompress { - files, - output_folder, - } => { - // From Option to Option<&Path> - let output_folder = output_folder.as_ref().map(|path| Path::new(path)); - for file in files.iter() { - Self::decompress_file(file, output_folder, flags)?; - } - } - Command::ShowHelp => help_message(), - Command::ShowVersion => version_message(), - } - Ok(()) - } -} - -#[inline] -fn version_message() { - println!("ouch {}", crate::VERSION); -} - -fn help_message() { - version_message(); - println!("Vinícius R. M. & João M. Bezerra"); - println!("ouch is a unified compression & decompression utility"); - println!(); - println!(" COMPRESSION USAGE:"); - println!(" ouch compress output-file"); - println!("DECOMPRESSION USAGE:"); - println!(" ouch [-o/--output output-folder]"); -} diff --git a/src/lib.rs b/src/lib.rs index 9c51d9a..77090c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ // Public modules pub mod cli; -pub mod evaluator; +pub mod commands; // Private modules mod compressors; diff --git a/src/main.rs b/src/main.rs index 506ebc2..af7fc43 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use ouch::{ cli::{parse_args, ParsedArgs}, - evaluator::Evaluator, - Result, + commands, Result, }; fn main() { @@ -13,5 +12,5 @@ fn main() { fn run() -> crate::Result<()> { let ParsedArgs { command, flags } = parse_args()?; - Evaluator::evaluate(command, &flags) + commands::run(command, &flags) } From df1bc879cbfc91286f0570e944df113b28b638db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 6 Apr 2021 22:41:21 -0300 Subject: [PATCH 09/11] New --help message --- Cargo.toml | 1 + src/commands.rs | 20 ++------------- src/lib.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++- src/utils.rs | 33 ++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd05ee3..b161754 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ description = "A command-line utility for easily compressing and decompressing f [dependencies] colored = "2.0.0" +termion = "1.5.6" walkdir = "2.3.2" tar = "0.4.33" xz2 = "0.1.6" diff --git a/src/commands.rs b/src/commands.rs index 8e00634..2547226 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -38,28 +38,12 @@ pub fn run(command: Command, flags: &oof::Flags) -> crate::Result<()> { decompress_file(file, output_folder, flags)?; } } - Command::ShowHelp => help_command(), - Command::ShowVersion => version_command(), + Command::ShowHelp => crate::help_command(), + Command::ShowVersion => crate::version_command(), } Ok(()) } -fn help_command() { - version_command(); - println!("Vinícius R. M. & João M. Bezerra"); - println!("ouch is a unified compression & decompression utility"); - println!(); - println!(" COMPRESSION USAGE:"); - println!(" ouch compress output-file"); - println!("DECOMPRESSION USAGE:"); - println!(" ouch [-o/--output output-folder]"); -} - -#[inline] -fn version_command() { - println!("ouch {}", crate::VERSION); -} - type BoxedCompressor = Box; type BoxedDecompressor = Box; diff --git a/src/lib.rs b/src/lib.rs index 77090c3..fa9bad7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,71 @@ mod extension; mod file; mod utils; +pub use error::{Error, Result}; + const VERSION: &str = "0.1.5"; -pub use error::{Error, Result}; +fn help_command() { + use utils::colors::*; + /* + ouch - Obvious Unified Compressed files Helper + + USAGE: + ouch Decompresses files. + + ouch compress OUTPUT.EXT Compresses files into OUTPUT.EXT, + where EXT must be a supported format. + + FLAGS: + -h, --help Display this help information. + -y, --yes Skip overwrite questions. + -n, --no Skip overwrite questions. + --version Display version information. + + SPECIFIC FLAGS: + -o, --output FOLDER_PATH When decompressing, to decompress files to + another folder. + + Visit https://github.com/vrmiguel/ouch for more usage examples. + */ + + println!( + "\ +{cyan}ouch{reset} - Obvious Unified Compression files Helper + +{cyan}USAGE:{reset} + {green}ouch {magenta}{reset} Decompresses files. + + {green}ouch compress {magenta} OUTPUT.EXT{reset} Compresses files into {magenta}OUTPUT.EXT{reset}, + where {magenta}EXT{reset} must be a supported format. + +{cyan}FLAGS:{reset} + {yellow}-h{white}, {yellow}--help{reset} Display this help information. + {yellow}-y{white}, {yellow}--yes{reset} Skip overwrite questions. + {yellow}-n{white}, {yellow}--no{reset} Skip overwrite questions. + {yellow}--version{reset} Display version information. + +{cyan}SPECIFIC FLAGS:{reset} + {yellow}-o{reset}, {yellow}--output{reset} FOLDER_PATH When decompressing, to decompress files to + another folder. + +Visit https://github.com/vrmiguel/ouch for more usage examples.", + magenta = magenta(), + white = white(), + green = green(), + yellow = yellow(), + reset = reset(), + cyan = cyan() + ); +} + +#[inline] +fn version_command() { + use utils::colors::*; + println!( + "{green}ouch{reset} {}", + crate::VERSION, + green = green(), + reset = reset(), + ); +} diff --git a/src/utils.rs b/src/utils.rs index 64df0e0..736628b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -126,6 +126,39 @@ pub struct Bytes { bytes: f64, } +#[allow(dead_code)] +pub mod colors { + use termion::color::*; + + pub fn reset() -> &'static str { + Reset.fg_str() + } + pub fn black() -> &'static str { + LightBlack.fg_str() + } + pub fn blue() -> &'static str { + LightBlue.fg_str() + } + pub fn cyan() -> &'static str { + LightCyan.fg_str() + } + pub fn green() -> &'static str { + LightGreen.fg_str() + } + pub fn magenta() -> &'static str { + LightMagenta.fg_str() + } + pub fn red() -> &'static str { + LightRed.fg_str() + } + pub fn white() -> &'static str { + LightWhite.fg_str() + } + pub fn yellow() -> &'static str { + LightYellow.fg_str() + } +} + impl Bytes { const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"]; From bb004dc78c1c43567c6b3b0d2a273c3ed1150cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 6 Apr 2021 23:06:53 -0300 Subject: [PATCH 10/11] Fixing Windows build: no termion support --- Cargo.toml | 3 ++- src/utils.rs | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b161754..7441402 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ description = "A command-line utility for easily compressing and decompressing f [dependencies] colored = "2.0.0" -termion = "1.5.6" walkdir = "2.3.2" tar = "0.4.33" xz2 = "0.1.6" @@ -25,6 +24,8 @@ zip = "0.5.11" # Dependency from workspace oof = { path = "./oof" } +[target.'cfg(unix)'.dependencies] +termion = "1.5.6" [profile.release] lto = true codegen-units = 1 diff --git a/src/utils.rs b/src/utils.rs index 736628b..6446f1a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -126,7 +126,9 @@ pub struct Bytes { bytes: f64, } -#[allow(dead_code)] +/// Module with a list of bright colors. +#[allow(dead_code, non_upper_case_globals)] +#[cfg(target_family = "unix")] pub mod colors { use termion::color::*; @@ -158,6 +160,22 @@ pub mod colors { LightYellow.fg_str() } } +// Termion does not support Windows +#[cfg(not(target_family = "unix"))] +pub mod colors { + pub fn empty() -> &'static str { + "" + } + pub const reset: fn() -> &'static str = empty; + pub const black: fn() -> &'static str = empty; + pub const blue: fn() -> &'static str = empty; + pub const cyan: fn() -> &'static str = empty; + pub const green: fn() -> &'static str = empty; + pub const magenta: fn() -> &'static str = empty; + pub const red: fn() -> &'static str = empty; + pub const white: fn() -> &'static str = empty; + pub const yellow: fn() -> &'static str = empty; +} impl Bytes { const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"]; From 3ac28b8d5c2a2b71bc833990cddaf2250a2218f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 6 Apr 2021 23:16:39 -0300 Subject: [PATCH 11/11] Fixing Windows build warnings --- src/utils.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 6446f1a..a607180 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -127,7 +127,7 @@ pub struct Bytes { } /// Module with a list of bright colors. -#[allow(dead_code, non_upper_case_globals)] +#[allow(dead_code)] #[cfg(target_family = "unix")] pub mod colors { use termion::color::*; @@ -161,6 +161,7 @@ pub mod colors { } } // Termion does not support Windows +#[allow(dead_code, non_upper_case_globals)] #[cfg(not(target_family = "unix"))] pub mod colors { pub fn empty() -> &'static str {