diff --git a/src/cli.rs b/src/cli.rs index 9ea21e9..96d757b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -25,21 +25,27 @@ pub enum Command { ShowVersion, } -/// Calls parse_args_and_flags_from using std::env::args_os ( argv ) +/// Calls parse_args_and_flags_from using argv (std::env::args_os) /// /// This function is also responsible for treating and checking the cli input /// Like calling canonicale, checking if it exists. pub fn parse_args() -> crate::Result { - let args = env::args_os().skip(1).collect(); + // From argv, but ignoring empty arguments + let args = env::args_os().skip(1).filter(|arg| !arg.is_empty()).collect(); let mut parsed_args = parse_args_from(args)?; - // If has a list of files, canonicalize them, reporting error if they do now exist + // If has a list of files, canonicalize them, reporting error if they do not exist match &mut parsed_args.command { Command::Compress { files, .. } | Command::Decompress { files, .. } => { *files = canonicalize_files(files)?; }, _ => {}, } + + if parsed_args.flags.is_present("yes") && parsed_args.flags.is_present("no") { + todo!("conflicting flags, better error message."); + } + Ok(parsed_args) } diff --git a/src/commands.rs b/src/commands.rs index 119d207..b4f1a18 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -9,6 +9,7 @@ use utils::colors; use crate::{ archive, cli::Command, + error::FinalError, extension::{ self, CompressionFormat::{self, *}, @@ -20,12 +21,115 @@ use crate::{ pub fn run(command: Command, flags: &oof::Flags) -> crate::Result<()> { match command { Command::Compress { files, output_path } => { + // Formats from path extension, like "file.tar.gz.xz" -> vec![Tar, Gzip, Lzma] let formats = extension::extensions_from_path(&output_path); - let output_file = fs::File::create(&output_path)?; - let compression_result = compress_files(files, formats, output_file, flags); - if let Err(_err) = compression_result { - fs::remove_file(&output_path).unwrap(); + + if formats.is_empty() { + FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) + .detail("You shall supply the compression format via the extension.") + .hint("Try adding something like .tar.gz or .zip to the output file.") + .hint("") + .hint("Examples:") + .hint(format!(" ouch compress ... {}.tar.gz", to_utf(&output_path))) + .hint(format!(" ouch compress ... {}.zip", to_utf(&output_path))) + .display_and_crash(); } + + if matches!(&formats[0], Bzip | Gzip | Lzma) && files.len() > 1 { + // This piece of code creates a sugestion for compressing multiple files + // It says: + // Change from file.bz.xz + // To file.tar.bz.xz + let extensions_text: String = + formats.iter().map(|format| format.to_string()).collect(); + + let output_path = to_utf(output_path); + + // Breaks if Lzma is .lz or .lzma and not .xz + // Or if Bzip is .bz2 and not .bz + let extensions_start_position = output_path.rfind(&extensions_text).unwrap(); + let pos = extensions_start_position; + let empty_range = pos..pos; + let mut suggested_output_path = output_path.clone(); + suggested_output_path.replace_range(empty_range, ".tar"); + + FinalError::with_title(format!( + "Cannot compress to '{}'.", + to_utf(&output_path) + )) + .detail("You are trying to compress multiple files.") + .detail(format!( + "The compression format '{}' cannot receive multiple files.", + &formats[0] + )) + .detail("The only supported formats that bundle files into an archive are .tar and .zip.") + .hint(format!( + "Try inserting '.tar' or '.zip' before '{}'.", + &formats[0] + )) + .hint(format!("From: {}", output_path)) + .hint(format!(" To : {}", suggested_output_path)) + .display_and_crash(); + } + + if let Some(format) = + formats.iter().skip(1).position(|format| matches!(format, Tar | Zip)) + { + 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 + )) + .hint(format!( + "If you wish to compress multiple files, start the extension with {}.", + format + )) + .hint(format!("Otherwise, remove {} from '{}'.", format, to_utf(&output_path))) + .display_and_crash(); + } + + if output_path.exists() && !utils::permission_for_overwriting(&output_path, flags)? { + // The user does not want to overwrite the file + return Ok(()); + } + + let output_file = fs::File::create(&output_path).unwrap_or_else(|err| { + FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) + .detail(format!("Could not open file '{}' for writing.", to_utf(&output_path))) + .detail(format!("Error: {}.", err)) + .display_and_crash() + }); + let compress_result = compress_files(files, formats, output_file, flags); + + // If any error occurred, delete incomplete file + if compress_result.is_err() { + // Print an extra alert message pointing out that we left a possibly + // CORRUPTED FILE at `output_path` + if let Err(err) = fs::remove_file(&output_path) { + eprintln!("{red}FATAL ERROR:\n", red = colors::red()); + eprintln!(" Please manually delete '{}'.", to_utf(&output_path)); + eprintln!( + " Compression failed and we could not delete '{}'.", + to_utf(&output_path), + ); + eprintln!( + " Error:{reset} {}{red}.{reset}\n", + err, + reset = colors::reset(), + red = colors::red() + ); + } + } else { + println!( + "{}[INFO]{} Successfully compressed '{}'.", + colors::yellow(), + colors::reset(), + to_utf(output_path), + ); + } + + compress_result?; }, Command::Decompress { files, output_folder } => { let mut output_paths = vec![]; diff --git a/src/error.rs b/src/error.rs index 39c5c47..fef3f23 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,7 +28,7 @@ pub enum Error { pub type Result = std::result::Result; -struct FinalError { +pub struct FinalError { title: String, details: Vec, hints: Vec, @@ -70,6 +70,11 @@ impl FinalError { // Make sure to fix colors eprint!("{}", reset()); } + + pub fn display_and_crash(&self) -> ! { + self.display(); + std::process::exit(crate::EXIT_FAILURE) + } } impl fmt::Display for Error { diff --git a/src/lib.rs b/src/lib.rs index 4116bb1..a5881f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ mod utils; use dialogs::Confirmation; pub use error::{Error, Result}; +pub const EXIT_FAILURE: i32 = 127; + const VERSION: &str = "0.1.5"; const OVERWRITE_CONFIRMATION: Confirmation = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); diff --git a/src/main.rs b/src/main.rs index af7fc43..57a5c60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use ouch::{ fn main() { if let Err(err) = run() { println!("{}", err); - std::process::exit(127); + std::process::exit(ouch::EXIT_FAILURE); } }