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) }