Merge pull request #8 from vrmiguel/yes-and-no

Add `-y, --yes` and `-n, --no` flags
This commit is contained in:
Vinícius Miguel 2021-03-29 02:54:12 -03:00 committed by GitHub
commit 70d8e37cea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 112 additions and 55 deletions

View File

@ -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)] #[derive(PartialEq, Eq, Debug)]
pub struct Command { pub struct Command {
pub kind: CommandKind, 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.") .help("The output directory or compressed file.")
.takes_value(true), .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> { pub fn get_matches() -> clap::ArgMatches<'static> {
clap_app().get_matches() 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<clap::ArgMatches<'static>> for Command { impl TryFrom<clap::ArgMatches<'static>> for Command {
type Error = crate::Error; type Error = crate::Error;

View File

@ -1,6 +1,9 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::file::File; use crate::{
cli::Flags,
file::File
};
pub enum DecompressionResult { pub enum DecompressionResult {
FilesUnpacked(Vec<PathBuf>), FilesUnpacked(Vec<PathBuf>),
@ -8,5 +11,5 @@ pub enum DecompressionResult {
} }
pub trait Decompressor { pub trait Decompressor {
fn decompress(&self, from: File, into: &Option<File>) -> crate::Result<DecompressionResult>; fn decompress(&self, from: File, into: &Option<File>, flags: Flags) -> crate::Result<DecompressionResult>;
} }

View File

@ -8,13 +8,13 @@ use colored::Colorize;
use tar::{self, Archive}; use tar::{self, Archive};
use super::decompressor::{DecompressionResult, Decompressor}; use super::decompressor::{DecompressionResult, Decompressor};
use crate::{file::File, utils, dialogs::Confirmation}; use crate::{cli::Flags, dialogs::Confirmation, file::File, utils};
#[derive(Debug)] #[derive(Debug)]
pub struct TarDecompressor {} pub struct TarDecompressor {}
impl TarDecompressor { impl TarDecompressor {
fn unpack_files(from: File, into: &Path) -> crate::Result<Vec<PathBuf>> { fn unpack_files(from: File, into: &Path, flags: Flags) -> crate::Result<Vec<PathBuf>> {
println!( println!(
"{}: attempting to decompress {:?}", "{}: attempting to decompress {:?}",
"ouch".bright_blue(), "ouch".bright_blue(),
@ -24,7 +24,9 @@ impl TarDecompressor {
let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
let mut archive: Archive<Box<dyn Read>> = match from.contents_in_memory { let mut archive: Archive<Box<dyn Read>> = 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 => { None => {
let file = fs::File::open(&from.path)?; let file = fs::File::open(&from.path)?;
tar::Archive::new(Box::new(file)) tar::Archive::new(Box::new(file))
@ -36,8 +38,7 @@ impl TarDecompressor {
let file_path = PathBuf::from(into).join(file.path()?); let file_path = PathBuf::from(into).join(file.path()?);
if file_path.exists() { if file_path.exists() {
let file_path_str = &*file_path.to_string_lossy(); if !utils::permission_for_overwriting(&file_path, flags, &confirm)? {
if !confirm.ask(Some(file_path_str))? {
// The user does not want to overwrite the file // The user does not want to overwrite the file
continue; continue;
} }
@ -61,12 +62,12 @@ impl TarDecompressor {
} }
impl Decompressor for TarDecompressor { impl Decompressor for TarDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> crate::Result<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>, flags: Flags) -> crate::Result<DecompressionResult> {
let destination_path = utils::get_destination_path(into); let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?; 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)) Ok(DecompressionResult::FilesUnpacked(files_unpacked))
} }

View File

@ -6,7 +6,7 @@ use std::{
use colored::Colorize; use colored::Colorize;
use super::decompressor::{DecompressionResult, Decompressor}; use super::decompressor::{DecompressionResult, Decompressor};
use crate::utils; use crate::{cli::Flags, utils};
// use niffler; // use niffler;
use crate::{extension::CompressionFormat, file::File}; use crate::{extension::CompressionFormat, file::File};
@ -62,19 +62,19 @@ impl DecompressorToMemory {
} }
impl Decompressor for GzipDecompressor { impl Decompressor for GzipDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> crate::Result<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>, _: Flags) -> crate::Result<DecompressionResult> {
DecompressorToMemory::decompress(from, CompressionFormat::Gzip, into) DecompressorToMemory::decompress(from, CompressionFormat::Gzip, into)
} }
} }
impl Decompressor for BzipDecompressor { impl Decompressor for BzipDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> crate::Result<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>, _: Flags) -> crate::Result<DecompressionResult> {
DecompressorToMemory::decompress(from, CompressionFormat::Bzip, into) DecompressorToMemory::decompress(from, CompressionFormat::Bzip, into)
} }
} }
impl Decompressor for LzmaDecompressor { impl Decompressor for LzmaDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> crate::Result<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>, _: Flags) -> crate::Result<DecompressionResult> {
DecompressorToMemory::decompress(from, CompressionFormat::Lzma, into) DecompressorToMemory::decompress(from, CompressionFormat::Lzma, into)
} }
} }

View File

@ -8,7 +8,7 @@ use colored::Colorize;
use zip::{self, read::ZipFile, ZipArchive}; use zip::{self, read::ZipFile, ZipArchive};
use super::decompressor::{DecompressionResult, Decompressor}; use super::decompressor::{DecompressionResult, Decompressor};
use crate::{dialogs::Confirmation, file::File, utils}; use crate::{cli::Flags, dialogs::Confirmation, file::File, utils};
#[cfg(unix)] #[cfg(unix)]
fn __unix_set_permissions(file_path: &PathBuf, file: &ZipFile) { fn __unix_set_permissions(file_path: &PathBuf, file: &ZipFile) {
@ -37,6 +37,7 @@ impl ZipDecompressor {
pub fn zip_decompress<T>( pub fn zip_decompress<T>(
archive: &mut ZipArchive<T>, archive: &mut ZipArchive<T>,
into: &Path, into: &Path,
flags: Flags,
) -> crate::Result<Vec<PathBuf>> ) -> crate::Result<Vec<PathBuf>>
where where
T: Read + Seek, T: Read + Seek,
@ -52,8 +53,7 @@ impl ZipDecompressor {
let file_path = into.join(file_path); let file_path = into.join(file_path);
if file_path.exists() { if file_path.exists() {
let file_path_str = &*file_path.as_path().to_string_lossy(); if !utils::permission_for_overwriting(&file_path, flags, &confirm)? {
if !confirm.ask(Some(file_path_str))? {
// The user does not want to overwrite the file // The user does not want to overwrite the file
continue; continue;
} }
@ -94,35 +94,42 @@ impl ZipDecompressor {
Ok(unpacked_files) Ok(unpacked_files)
} }
fn unpack_files(from: File, into: &Path) -> crate::Result<Vec<PathBuf>> { fn unpack_files(from: File, into: &Path, flags: Flags) -> crate::Result<Vec<PathBuf>> {
println!( println!(
"{}: attempting to decompress {:?}", "{} decompressing {:?}",
"ouch".bright_blue(), "[OUCH]".bright_blue(),
&from.path &from.path
); );
match from.contents_in_memory { match from.contents_in_memory {
Some(bytes) => { Some(bytes) => {
// Decompressing a .zip archive loaded up in memory
let mut archive = zip::ZipArchive::new(Cursor::new(bytes))?; 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 => { None => {
// Decompressing a .zip archive from the file system
let file = fs::File::open(&from.path)?; let file = fs::File::open(&from.path)?;
let mut archive = zip::ZipArchive::new(file)?; 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 { impl Decompressor for ZipDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> crate::Result<DecompressionResult> { fn decompress(
&self,
from: File,
into: &Option<File>,
flags: Flags,
) -> crate::Result<DecompressionResult> {
let destination_path = utils::get_destination_path(into); let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?; 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)) Ok(DecompressionResult::FilesUnpacked(files_unpacked))
} }

View File

@ -31,7 +31,7 @@ impl<'a> Confirmation<'a> {
let mut answer = String::new(); let mut answer = String::new();
io::stdin().read_line(&mut answer)?; io::stdin().read_line(&mut answer)?;
let trimmed_answer = answer.trim().to_ascii_lowercase(); let trimmed_answer = answer.trim();
if trimmed_answer.is_empty() { if trimmed_answer.is_empty() {
return Ok(true); return Ok(true);

View File

@ -3,7 +3,7 @@ use std::{ffi::OsStr, fs, io::Write, path::PathBuf};
use colored::Colorize; use colored::Colorize;
use crate::{ use crate::{
cli::{Command, CommandKind}, cli::{Command, CommandKind, Flags},
compressors::{ compressors::{
BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor, BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor,
ZipCompressor, ZipCompressor,
@ -12,8 +12,8 @@ use crate::{
BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor, BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor,
TarDecompressor, ZipDecompressor, TarDecompressor, ZipDecompressor,
}, },
extension::{CompressionFormat, Extension},
dialogs::Confirmation, dialogs::Confirmation,
extension::{CompressionFormat, Extension},
file::File, file::File,
utils, utils,
}; };
@ -98,13 +98,13 @@ impl Evaluator {
Ok((first_decompressor, second_decompressor)) Ok((first_decompressor, second_decompressor))
} }
// todo: move this folder into decompressors/ later on
fn decompress_file_in_memory( fn decompress_file_in_memory(
bytes: Vec<u8>, bytes: Vec<u8>,
file_path: PathBuf, file_path: PathBuf,
decompressor: Option<Box<dyn Decompressor>>, decompressor: Option<Box<dyn Decompressor>>,
output_file: &Option<File>, output_file: &Option<File>,
extension: Option<Extension>, extension: Option<Extension>,
flags: Flags,
) -> crate::Result<()> { ) -> crate::Result<()> {
let output_file_path = utils::get_destination_path(output_file); 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()); .unwrap_or_else(|| output_file_path.as_os_str());
if filename == "." { 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. // of the sort `.tar` or `.zip' and no output has been supplied.
filename = OsStr::new("ouch-output"); filename = OsStr::new("ouch-output");
} }
@ -127,6 +127,14 @@ impl Evaluator {
// Therefore, we'll save what we have in memory into a file. // Therefore, we'll save what we have in memory into a file.
println!("{}: saving to {:?}.", "info".yellow(), filename); 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))?; let mut f = fs::File::create(output_file_path.join(filename))?;
f.write_all(&bytes)?; f.write_all(&bytes)?;
return Ok(()); return Ok(());
@ -139,7 +147,7 @@ impl Evaluator {
extension, extension,
}; };
let decompression_result = decompressor.decompress(file, output_file)?; let decompression_result = decompressor.decompress(file, output_file, flags)?;
if let DecompressionResult::FileInMemory(_) = decompression_result { if let DecompressionResult::FileInMemory(_) = decompression_result {
// Should not be reachable. // Should not be reachable.
unreachable!(); unreachable!();
@ -148,20 +156,19 @@ impl Evaluator {
Ok(()) Ok(())
} }
fn compress_files(files: Vec<PathBuf>, mut output: File) -> crate::Result<()> { fn compress_files(files: Vec<PathBuf>, mut output: File, flags: Flags) -> crate::Result<()> {
let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
let (first_compressor, second_compressor) = Self::get_compressor(&output)?; let (first_compressor, second_compressor) = Self::get_compressor(&output)?;
// TODO: use -y and -n here
let output_path = output.path.clone(); let output_path = output.path.clone();
if output_path.exists() { if output_path.exists() {
let output_path_str = &*output_path.to_string_lossy(); if !utils::permission_for_overwriting(&output_path, flags, &confirm)? {
if !confirm.ask(Some(output_path_str))? {
// The user does not want to overwrite the file // The user does not want to overwrite the file
return Ok(()); return Ok(());
} }
} }
let bytes = match first_compressor { let bytes = match first_compressor {
Some(first_compressor) => { Some(first_compressor) => {
let mut entry = Entry::Files(files); let mut entry = Entry::Files(files);
@ -188,14 +195,13 @@ impl Evaluator {
Ok(()) Ok(())
} }
fn decompress_file(file: File, output: &Option<File>) -> crate::Result<()> { fn decompress_file(file: File, output: &Option<File>, flags: Flags) -> crate::Result<()> {
// let output_file = &command.output;
let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?; let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?;
let file_path = file.path.clone(); let file_path = file.path.clone();
let extension = file.extension.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 { match decompression_result {
DecompressionResult::FileInMemory(bytes) => { DecompressionResult::FileInMemory(bytes) => {
@ -207,6 +213,7 @@ impl Evaluator {
first_decompressor, first_decompressor,
output, output,
extension, extension,
flags,
)?; )?;
} }
DecompressionResult::FilesUnpacked(_files) => { DecompressionResult::FilesUnpacked(_files) => {
@ -223,18 +230,18 @@ impl Evaluator {
Ok(()) Ok(())
} }
pub fn evaluate(command: Command) -> crate::Result<()> { pub fn evaluate(command: Command, flags: Flags) -> crate::Result<()> {
let output = command.output.clone(); let output = command.output.clone();
match command.kind { match command.kind {
CommandKind::Compression(files_to_compress) => { CommandKind::Compression(files_to_compress) => {
// Safe to unwrap since output is mandatory for compression // Safe to unwrap since output is mandatory for compression
let output = output.unwrap(); let output = output.unwrap();
Self::compress_files(files_to_compress, output)?; Self::compress_files(files_to_compress, output, flags)?;
} }
CommandKind::Decompression(files_to_decompress) => { CommandKind::Decompression(files_to_decompress) => {
for file in files_to_decompress { for file in files_to_decompress {
Self::decompress_file(file, &output)?; Self::decompress_file(file, &output, flags)?;
} }
} }
} }

View File

@ -9,24 +9,11 @@ mod file;
mod test; mod test;
mod utils; mod utils;
use std::convert::TryFrom;
use error::{Error, Result}; use error::{Error, Result};
use evaluator::Evaluator; use evaluator::Evaluator;
fn main() -> crate::Result<()> { fn main() -> crate::Result<()> {
let matches = cli::get_matches(); let matches = cli::get_matches();
let command = cli::Command::try_from(matches)?; let (command, flags) = cli::parse_matches(matches)?;
Evaluator::evaluate(command) 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(())
// }

View File

@ -5,7 +5,7 @@ use std::{
use colored::Colorize; 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<()> pub(crate) fn ensure_exists<'a, P>(path: P) -> crate::Result<()>
where where
@ -90,3 +90,14 @@ pub(crate) fn change_dir_and_return_parent(filename: &PathBuf) -> crate::Result<
env::set_current_dir(parent)?; env::set_current_dir(parent)?;
Ok(previous_location) Ok(previous_location)
} }
pub fn permission_for_overwriting(path: &PathBuf, flags: Flags, confirm: &Confirmation) -> crate::Result<bool> {
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))?)
}