diff --git a/src/cli.rs b/src/cli.rs index fb49d51..af6222f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -22,6 +22,16 @@ pub enum CommandKind { ), } +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum Flags { + // No flags supplied + None, + // Flag -y, --yes supplied + AlwaysYes, + // Flag -n, --no supplied + AlwaysNo +} + #[derive(PartialEq, Eq, Debug)] pub struct Command { pub kind: CommandKind, @@ -63,12 +73,43 @@ Please relate any issues or contribute at https://github.com/vrmiguel/ouch") .help("The output directory or compressed file.") .takes_value(true), ) + .arg( + Arg::with_name("yes") + .required(false) + .multiple(false) + .long("yes") + .short("y") + .help("Says yes to all confirmation dialogs.") + .conflicts_with("no") + .takes_value(false), + ) + .arg( + Arg::with_name("no") + .required(false) + .multiple(false) + .long("no") + .short("n") + .help("Says no to all confirmation dialogs.") + .conflicts_with("yes") + .takes_value(false), + ) } pub fn get_matches() -> clap::ArgMatches<'static> { clap_app().get_matches() } +pub fn parse_matches(matches: clap::ArgMatches<'static>) -> crate::Result<(Command, Flags)> { + let flag = match (matches.is_present("yes"), matches.is_present("no")) { + (true, true) => unreachable!(), + (true, _) => Flags::AlwaysYes, + (_, true) => Flags::AlwaysNo, + (_, _) => Flags::None + }; + + Ok((Command::try_from(matches)?, flag)) +} + impl TryFrom> for Command { type Error = crate::Error; diff --git a/src/decompressors/decompressor.rs b/src/decompressors/decompressor.rs index 5d2def1..ea9436b 100644 --- a/src/decompressors/decompressor.rs +++ b/src/decompressors/decompressor.rs @@ -1,6 +1,9 @@ use std::path::PathBuf; -use crate::file::File; +use crate::{ + cli::Flags, + file::File +}; pub enum DecompressionResult { FilesUnpacked(Vec), @@ -8,5 +11,5 @@ pub enum DecompressionResult { } pub trait Decompressor { - fn decompress(&self, from: File, into: &Option) -> crate::Result; + fn decompress(&self, from: File, into: &Option, flags: Flags) -> crate::Result; } diff --git a/src/decompressors/tar.rs b/src/decompressors/tar.rs index 13ef979..b3947e3 100644 --- a/src/decompressors/tar.rs +++ b/src/decompressors/tar.rs @@ -8,13 +8,13 @@ use colored::Colorize; use tar::{self, Archive}; use super::decompressor::{DecompressionResult, Decompressor}; -use crate::{file::File, utils, dialogs::Confirmation}; +use crate::{cli::Flags, dialogs::Confirmation, file::File, utils}; #[derive(Debug)] pub struct TarDecompressor {} impl TarDecompressor { - fn unpack_files(from: File, into: &Path) -> crate::Result> { + fn unpack_files(from: File, into: &Path, flags: Flags) -> crate::Result> { println!( "{}: attempting to decompress {:?}", "ouch".bright_blue(), @@ -24,7 +24,9 @@ impl TarDecompressor { let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); let mut archive: Archive> = match from.contents_in_memory { - Some(bytes) => tar::Archive::new(Box::new(Cursor::new(bytes))), + Some(bytes) => { + tar::Archive::new(Box::new(Cursor::new(bytes))) + }, None => { let file = fs::File::open(&from.path)?; tar::Archive::new(Box::new(file)) @@ -36,8 +38,7 @@ impl TarDecompressor { let file_path = PathBuf::from(into).join(file.path()?); if file_path.exists() { - let file_path_str = &*file_path.to_string_lossy(); - if !confirm.ask(Some(file_path_str))? { + if !utils::permission_for_overwriting(&file_path, flags, &confirm)? { // The user does not want to overwrite the file continue; } @@ -61,12 +62,12 @@ impl TarDecompressor { } impl Decompressor for TarDecompressor { - fn decompress(&self, from: File, into: &Option) -> crate::Result { + fn decompress(&self, from: File, into: &Option, flags: Flags) -> crate::Result { let destination_path = utils::get_destination_path(into); utils::create_path_if_non_existent(destination_path)?; - let files_unpacked = Self::unpack_files(from, destination_path)?; + let files_unpacked = Self::unpack_files(from, destination_path, flags)?; Ok(DecompressionResult::FilesUnpacked(files_unpacked)) } diff --git a/src/decompressors/to_memory.rs b/src/decompressors/to_memory.rs index 81a0e27..e8b9fb9 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::utils; +use crate::{cli::Flags, utils}; // use niffler; use crate::{extension::CompressionFormat, file::File}; @@ -62,19 +62,19 @@ impl DecompressorToMemory { } impl Decompressor for GzipDecompressor { - fn decompress(&self, from: File, into: &Option) -> crate::Result { + fn decompress(&self, from: File, into: &Option, _: Flags) -> crate::Result { DecompressorToMemory::decompress(from, CompressionFormat::Gzip, into) } } impl Decompressor for BzipDecompressor { - fn decompress(&self, from: File, into: &Option) -> crate::Result { + fn decompress(&self, from: File, into: &Option, _: Flags) -> crate::Result { DecompressorToMemory::decompress(from, CompressionFormat::Bzip, into) } } impl Decompressor for LzmaDecompressor { - fn decompress(&self, from: File, into: &Option) -> crate::Result { + fn decompress(&self, from: File, into: &Option, _: Flags) -> crate::Result { DecompressorToMemory::decompress(from, CompressionFormat::Lzma, into) } } diff --git a/src/decompressors/zip.rs b/src/decompressors/zip.rs index 40e8441..50dd278 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::{dialogs::Confirmation, file::File, utils}; +use crate::{cli::Flags, dialogs::Confirmation, file::File, utils}; #[cfg(unix)] fn __unix_set_permissions(file_path: &PathBuf, file: &ZipFile) { @@ -37,6 +37,7 @@ impl ZipDecompressor { pub fn zip_decompress( archive: &mut ZipArchive, into: &Path, + flags: Flags, ) -> crate::Result> where T: Read + Seek, @@ -52,8 +53,7 @@ impl ZipDecompressor { let file_path = into.join(file_path); if file_path.exists() { - let file_path_str = &*file_path.as_path().to_string_lossy(); - if !confirm.ask(Some(file_path_str))? { + if !utils::permission_for_overwriting(&file_path, flags, &confirm)? { // The user does not want to overwrite the file continue; } @@ -94,35 +94,42 @@ impl ZipDecompressor { Ok(unpacked_files) } - fn unpack_files(from: File, into: &Path) -> crate::Result> { + fn unpack_files(from: File, into: &Path, flags: Flags) -> crate::Result> { println!( - "{}: attempting to decompress {:?}", - "ouch".bright_blue(), + "{} decompressing {:?}", + "[OUCH]".bright_blue(), &from.path ); match from.contents_in_memory { Some(bytes) => { + // Decompressing a .zip archive loaded up in memory let mut archive = zip::ZipArchive::new(Cursor::new(bytes))?; - Ok(Self::zip_decompress(&mut archive, into)?) + Ok(Self::zip_decompress(&mut archive, into, flags)?) } None => { + // Decompressing a .zip archive from the file system let file = fs::File::open(&from.path)?; let mut archive = zip::ZipArchive::new(file)?; - Ok(Self::zip_decompress(&mut archive, into)?) + Ok(Self::zip_decompress(&mut archive, into, flags)?) } } } } impl Decompressor for ZipDecompressor { - fn decompress(&self, from: File, into: &Option) -> crate::Result { + fn decompress( + &self, + from: File, + into: &Option, + flags: Flags, + ) -> crate::Result { let destination_path = utils::get_destination_path(into); utils::create_path_if_non_existent(destination_path)?; - let files_unpacked = Self::unpack_files(from, destination_path)?; + let files_unpacked = Self::unpack_files(from, destination_path, flags)?; Ok(DecompressionResult::FilesUnpacked(files_unpacked)) } diff --git a/src/dialogs.rs b/src/dialogs.rs index b603b9a..2902a41 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -31,7 +31,7 @@ impl<'a> Confirmation<'a> { let mut answer = String::new(); io::stdin().read_line(&mut answer)?; - let trimmed_answer = answer.trim().to_ascii_lowercase(); + let trimmed_answer = answer.trim(); if trimmed_answer.is_empty() { return Ok(true); diff --git a/src/evaluator.rs b/src/evaluator.rs index d913bea..959a939 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -3,7 +3,7 @@ use std::{ffi::OsStr, fs, io::Write, path::PathBuf}; use colored::Colorize; use crate::{ - cli::{Command, CommandKind}, + cli::{Command, CommandKind, Flags}, compressors::{ BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor, ZipCompressor, @@ -12,8 +12,8 @@ use crate::{ BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor, TarDecompressor, ZipDecompressor, }, - extension::{CompressionFormat, Extension}, dialogs::Confirmation, + extension::{CompressionFormat, Extension}, file::File, utils, }; @@ -98,13 +98,13 @@ impl Evaluator { Ok((first_decompressor, second_decompressor)) } - // todo: move this folder into decompressors/ later on fn decompress_file_in_memory( bytes: Vec, file_path: PathBuf, decompressor: Option>, output_file: &Option, extension: Option, + flags: Flags, ) -> crate::Result<()> { let output_file_path = utils::get_destination_path(output_file); @@ -113,7 +113,7 @@ impl Evaluator { .unwrap_or_else(|| output_file_path.as_os_str()); if filename == "." { - // I believe this is only possible when the supplied inout has a name + // 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. filename = OsStr::new("ouch-output"); } @@ -127,6 +127,14 @@ impl Evaluator { // Therefore, we'll save what we have in memory into a file. println!("{}: saving to {:?}.", "info".yellow(), filename); + if filename.exists() { + let confirm = + Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); + if !utils::permission_for_overwriting(&filename, flags, &confirm)? { + return Ok(()); + } + } + let mut f = fs::File::create(output_file_path.join(filename))?; f.write_all(&bytes)?; return Ok(()); @@ -139,7 +147,7 @@ impl Evaluator { extension, }; - let decompression_result = decompressor.decompress(file, output_file)?; + let decompression_result = decompressor.decompress(file, output_file, flags)?; if let DecompressionResult::FileInMemory(_) = decompression_result { // Should not be reachable. unreachable!(); @@ -148,20 +156,19 @@ impl Evaluator { Ok(()) } - fn compress_files(files: Vec, mut output: File) -> crate::Result<()> { + fn compress_files(files: Vec, mut output: File, flags: Flags) -> crate::Result<()> { let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); let (first_compressor, second_compressor) = Self::get_compressor(&output)?; + // TODO: use -y and -n here let output_path = output.path.clone(); if output_path.exists() { - let output_path_str = &*output_path.to_string_lossy(); - if !confirm.ask(Some(output_path_str))? { + if !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); @@ -188,14 +195,13 @@ impl Evaluator { Ok(()) } - fn decompress_file(file: File, output: &Option) -> crate::Result<()> { - // let output_file = &command.output; + fn decompress_file(file: File, output: &Option, flags: Flags) -> crate::Result<()> { let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?; let file_path = file.path.clone(); let extension = file.extension.clone(); - let decompression_result = second_decompressor.decompress(file, output)?; + let decompression_result = second_decompressor.decompress(file, output, flags)?; match decompression_result { DecompressionResult::FileInMemory(bytes) => { @@ -207,6 +213,7 @@ impl Evaluator { first_decompressor, output, extension, + flags, )?; } DecompressionResult::FilesUnpacked(_files) => { @@ -223,18 +230,18 @@ impl Evaluator { Ok(()) } - pub fn evaluate(command: Command) -> crate::Result<()> { + pub fn evaluate(command: Command, flags: Flags) -> crate::Result<()> { let output = command.output.clone(); match command.kind { CommandKind::Compression(files_to_compress) => { // Safe to unwrap since output is mandatory for compression let output = output.unwrap(); - Self::compress_files(files_to_compress, output)?; + Self::compress_files(files_to_compress, output, flags)?; } CommandKind::Decompression(files_to_decompress) => { for file in files_to_decompress { - Self::decompress_file(file, &output)?; + Self::decompress_file(file, &output, flags)?; } } } diff --git a/src/main.rs b/src/main.rs index a13504f..4df4f69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,24 +9,11 @@ mod file; mod test; mod utils; -use std::convert::TryFrom; - use error::{Error, Result}; use evaluator::Evaluator; fn main() -> crate::Result<()> { let matches = cli::get_matches(); - let command = cli::Command::try_from(matches)?; - Evaluator::evaluate(command) + let (command, flags) = cli::parse_matches(matches)?; + Evaluator::evaluate(command, flags) } - -// fn main() -> crate::Result<()> { -// let dialog = dialogs::Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); - -// match dialog.ask(Some("file.tar.gz"))? { -// true => println!("deleting"), -// false => println!("keeping") -// }; - -// Ok(()) -// } diff --git a/src/utils.rs b/src/utils.rs index 60bdb2c..cc1afe5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,7 +5,7 @@ use std::{ use colored::Colorize; -use crate::{extension::CompressionFormat, file::File}; +use crate::{cli::Flags, dialogs::Confirmation, extension::CompressionFormat, file::File}; pub(crate) fn ensure_exists<'a, P>(path: P) -> crate::Result<()> where @@ -90,3 +90,14 @@ pub(crate) fn change_dir_and_return_parent(filename: &PathBuf) -> crate::Result< env::set_current_dir(parent)?; Ok(previous_location) } + +pub fn permission_for_overwriting(path: &PathBuf, flags: Flags, confirm: &Confirmation) -> crate::Result { + match flags { + Flags::AlwaysYes => return Ok(true), + Flags::AlwaysNo => return Ok(false), + Flags::None => {} + } + + let file_path_str = &*path.as_path().to_string_lossy(); + Ok(confirm.ask(Some(file_path_str))?) +} \ No newline at end of file