Adding more checks before compression

This commit is contained in:
João M. Bezerra 2021-08-03 21:43:56 -03:00
parent bb93e46535
commit 33382d06c3
5 changed files with 126 additions and 9 deletions

View File

@ -25,21 +25,27 @@ pub enum Command {
ShowVersion, 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 /// This function is also responsible for treating and checking the cli input
/// Like calling canonicale, checking if it exists. /// Like calling canonicale, checking if it exists.
pub fn parse_args() -> crate::Result<ParsedArgs> { pub fn parse_args() -> crate::Result<ParsedArgs> {
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)?; 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 { match &mut parsed_args.command {
Command::Compress { files, .. } | Command::Decompress { files, .. } => { Command::Compress { files, .. } | Command::Decompress { files, .. } => {
*files = canonicalize_files(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) Ok(parsed_args)
} }

View File

@ -9,6 +9,7 @@ use utils::colors;
use crate::{ use crate::{
archive, archive,
cli::Command, cli::Command,
error::FinalError,
extension::{ extension::{
self, self,
CompressionFormat::{self, *}, CompressionFormat::{self, *},
@ -20,12 +21,115 @@ use crate::{
pub fn run(command: Command, flags: &oof::Flags) -> crate::Result<()> { pub fn run(command: Command, flags: &oof::Flags) -> crate::Result<()> {
match command { match command {
Command::Compress { files, output_path } => { 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 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 formats.is_empty() {
if let Err(_err) = compression_result { FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path)))
fs::remove_file(&output_path).unwrap(); .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 } => { Command::Decompress { files, output_folder } => {
let mut output_paths = vec![]; let mut output_paths = vec![];

View File

@ -28,7 +28,7 @@ pub enum Error {
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
struct FinalError { pub struct FinalError {
title: String, title: String,
details: Vec<String>, details: Vec<String>,
hints: Vec<String>, hints: Vec<String>,
@ -70,6 +70,11 @@ impl FinalError {
// Make sure to fix colors // Make sure to fix colors
eprint!("{}", reset()); eprint!("{}", reset());
} }
pub fn display_and_crash(&self) -> ! {
self.display();
std::process::exit(crate::EXIT_FAILURE)
}
} }
impl fmt::Display for Error { impl fmt::Display for Error {

View File

@ -13,6 +13,8 @@ mod utils;
use dialogs::Confirmation; use dialogs::Confirmation;
pub use error::{Error, Result}; pub use error::{Error, Result};
pub const EXIT_FAILURE: i32 = 127;
const VERSION: &str = "0.1.5"; const VERSION: &str = "0.1.5";
const OVERWRITE_CONFIRMATION: Confirmation = const OVERWRITE_CONFIRMATION: Confirmation =
Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));

View File

@ -6,7 +6,7 @@ use ouch::{
fn main() { fn main() {
if let Err(err) = run() { if let Err(err) = run() {
println!("{}", err); println!("{}", err);
std::process::exit(127); std::process::exit(ouch::EXIT_FAILURE);
} }
} }