From e1c72a6a6b649fa86e0eb8892373483999a92783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 6 Apr 2021 23:36:44 -0300 Subject: [PATCH 01/37] Removing unused cli::CommandInfo --- src/cli.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 0eb4cc560..20594b5e4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -23,12 +23,6 @@ pub enum Command { ShowVersion, } -#[derive(PartialEq, Eq, Debug)] -pub struct CommandInfo { - pub command: Command, - pub flags: oof::Flags, -} - /// Calls parse_args_and_flags_from using std::env::args_os ( argv ) pub fn parse_args() -> crate::Result { let args = env::args_os().skip(1).collect(); From 8a4ac5d6d1c996f9c860ca49f38413deeb842b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Wed, 7 Apr 2021 00:26:53 -0300 Subject: [PATCH 02/37] cli: Check for typos on `ouch compress` --- Cargo.toml | 6 ++++-- src/cli.rs | 47 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 744140279..6d4de9935 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,14 @@ description = "A command-line utility for easily compressing and decompressing f [dependencies] colored = "2.0.0" walkdir = "2.3.2" +strsim = "0.10.0" +flate2 = "1.0.14" +bzip2 = "0.4.2" tar = "0.4.33" xz2 = "0.1.6" -bzip2 = "0.4.2" -flate2 = "1.0.14" zip = "0.5.11" + # Dependency from workspace oof = { path = "./oof" } diff --git a/src/cli.rs b/src/cli.rs index 20594b5e4..d9340f435 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,7 +5,9 @@ use std::{ vec::Vec, }; -use oof::{arg_flag, flag}; +use strsim::normalized_damerau_levenshtein; +use oof::{Flag, arg_flag, flag}; + #[derive(PartialEq, Eq, Debug)] pub enum Command { @@ -34,6 +36,24 @@ pub struct ParsedArgs { pub flags: oof::Flags, } + +/// check_for_typo checks if the first argument is +/// a typo for the compress subcommand. +/// Returns true if the arg is probably a typo or false otherwise. +fn is_typo<'a, P>(path: P) -> bool +where + P: AsRef + 'a, +{ + if path.as_ref().exists() { + // If the file exists then we won't check for a typo + return false; + } + + let path = path.as_ref().to_string_lossy(); + // We'll consider it a typo if the word is somewhat 'close' to "compress" + normalized_damerau_levenshtein("compress", &path) > 0.625 +} + fn canonicalize<'a, P>(path: P) -> crate::Result where P: AsRef + 'a, @@ -51,6 +71,8 @@ where } } + + fn canonicalize_files<'a, P>(files: Vec

) -> crate::Result> where P: AsRef + 'a, @@ -77,9 +99,8 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { let mut flags_info = vec![flag!('y', "yes"), flag!('n', "no")]; - let parsed_args = match oof::pop_subcommand(&mut args, subcommands) { - Some(&"compress") => { - let (args, flags) = oof::filter_flags(args, &flags_info)?; + let process_compression_command = |args, flags_info: Vec| { + let (args, flags) = oof::filter_flags(args, &flags_info)?; let mut files: Vec = args.into_iter().map(PathBuf::from).collect(); if files.len() < 2 { @@ -95,12 +116,28 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { files, compressed_output_path, }; - ParsedArgs { command, flags } + Ok(ParsedArgs { command, flags }) + }; + + let parsed_args = match oof::pop_subcommand(&mut args, subcommands) { + Some(&"compress") => { + process_compression_command(args, flags_info)? } // Defaults to decompression when there is no subcommand None => { flags_info.push(arg_flag!('o', "output")); + { + let first_arg = args.first().unwrap(); + if is_typo(first_arg) { + println!("Did you mean `ouch compress`?"); + // TODO: ask for permission ? + args.remove(0); + return process_compression_command(args, flags_info); + } + } + + // Parse flags let (args, mut flags) = oof::filter_flags(args, &flags_info)?; From 0026e4d4de6d4fe72b0024b2892d4a09bdc9419a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Wed, 7 Apr 2021 02:02:50 -0300 Subject: [PATCH 03/37] Drop dependency on Colored --- Cargo.toml | 1 - src/commands.rs | 14 ++++++++------ src/compressors/bzip.rs | 7 ++++--- src/compressors/gzip.rs | 7 ++++--- src/compressors/lzma.rs | 7 ++++--- src/compressors/tar.rs | 7 ++++--- src/decompressors/tar.rs | 13 ++++++++----- src/decompressors/to_memory.rs | 8 +++++--- src/decompressors/zip.rs | 14 ++++++++------ src/dialogs.rs | 4 ++-- src/error.rs | 24 ++++++++++++------------ src/utils.rs | 19 ++++++++++--------- 12 files changed, 69 insertions(+), 56 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d4de9935..3cb83f47a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ description = "A command-line utility for easily compressing and decompressing f # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -colored = "2.0.0" walkdir = "2.3.2" strsim = "0.10.0" flate2 = "1.0.14" diff --git a/src/commands.rs b/src/commands.rs index 254722627..ad5dee862 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -4,7 +4,7 @@ use std::{ path::{Path, PathBuf}, }; -use colored::Colorize; +use utils::colors; use crate::{ cli::Command, @@ -88,8 +88,9 @@ fn get_decompressor(file: &File) -> crate::Result<(Option, Bo None => { // This block *should* be unreachable eprintln!( - "{} reached Evaluator::get_decompressor without known extension.", - "[internal error]".red() + "{}[internal error]{} reached Evaluator::get_decompressor without known extension.", + colors::red(), + colors::reset() ); return Err(crate::Error::InvalidInput); } @@ -143,7 +144,7 @@ fn decompress_file_in_memory( 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); + println!("{}[INFO]{} saving to {:?}.", colors::yellow(), colors::reset(), file_name); if file_name.exists() { let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE")); @@ -203,8 +204,9 @@ fn compress_files( }; println!( - "{}: writing to {:?}. ({})", - "info".yellow(), + "{}[INFO]{} writing to {:?}. ({})", + colors::yellow(), + colors::reset(), output_path, utils::Bytes::new(bytes.len() as u64) ); diff --git a/src/compressors/bzip.rs b/src/compressors/bzip.rs index a08c7c92d..102de37c6 100644 --- a/src/compressors/bzip.rs +++ b/src/compressors/bzip.rs @@ -1,6 +1,6 @@ use std::{fs, io::Write, path::PathBuf}; -use colored::Colorize; +use utils::colors; use super::{Compressor, Entry}; use crate::{extension::CompressionFormat, file::File, utils}; @@ -18,8 +18,9 @@ impl BzipCompressor { }; println!( - "{}: compressed {:?} into memory ({})", - "info".yellow(), + "{}[INFO]{} compressed {:?} into memory ({})", + colors::yellow(), + colors::reset(), &path, utils::Bytes::new(contents.len() as u64) ); diff --git a/src/compressors/gzip.rs b/src/compressors/gzip.rs index 9399469f2..8e16970c3 100644 --- a/src/compressors/gzip.rs +++ b/src/compressors/gzip.rs @@ -1,6 +1,6 @@ use std::{fs, io::Write, path::PathBuf}; -use colored::Colorize; +use utils::colors; use super::{Compressor, Entry}; use crate::{extension::CompressionFormat, file::File, utils}; @@ -23,8 +23,9 @@ impl GzipCompressor { }; println!( - "{}: compressed {:?} into memory ({})", - "info".yellow(), + "{}[INFO]{} compressed {:?} into memory ({})", + colors::yellow(), + colors::reset(), &path, utils::Bytes::new(bytes.len() as u64) ); diff --git a/src/compressors/lzma.rs b/src/compressors/lzma.rs index d18fc0d94..e8f18551c 100644 --- a/src/compressors/lzma.rs +++ b/src/compressors/lzma.rs @@ -1,6 +1,6 @@ use std::{fs, io::Write, path::PathBuf}; -use colored::Colorize; +use utils::colors; use super::{Compressor, Entry}; use crate::{extension::CompressionFormat, file::File, utils}; @@ -23,8 +23,9 @@ impl LzmaCompressor { }; println!( - "{}: compressed {:?} into memory ({})", - "info".yellow(), + "{}[INFO]{} compressed {:?} into memory ({})", + colors::yellow(), + colors::reset(), &path, utils::Bytes::new(bytes.len() as u64) ); diff --git a/src/compressors/tar.rs b/src/compressors/tar.rs index 0e0c4178f..60d891dab 100644 --- a/src/compressors/tar.rs +++ b/src/compressors/tar.rs @@ -1,7 +1,7 @@ use std::{env, fs, path::PathBuf}; -use colored::Colorize; use tar::Builder; +use utils::colors; use walkdir::WalkDir; use super::compressor::Entry; @@ -13,8 +13,9 @@ impl TarCompressor { // TODO: implement this fn make_archive_from_memory(_input: File) -> crate::Result> { println!( - "{} .tar.tar and .zip.tar is currently unimplemented.", - "[ERROR]".red() + "{}[ERROR]{} .tar.tar and .zip.tar is currently unimplemented.", + colors::red(), + colors::reset() ); Err(crate::Error::InvalidZipArchive("")) } diff --git a/src/decompressors/tar.rs b/src/decompressors/tar.rs index 798175007..4d9135864 100644 --- a/src/decompressors/tar.rs +++ b/src/decompressors/tar.rs @@ -4,8 +4,9 @@ use std::{ path::{Path, PathBuf}, }; -use colored::Colorize; + use tar::{self, Archive}; +use utils::colors; use super::decompressor::{DecompressionResult, Decompressor}; use crate::{dialogs::Confirmation, file::File, utils}; @@ -16,8 +17,9 @@ pub struct TarDecompressor; impl TarDecompressor { fn unpack_files(from: File, into: &Path, flags: &oof::Flags) -> crate::Result> { println!( - "{}: attempting to decompress {:?}", - "ouch".bright_blue(), + "{}[INFO]{} attempting to decompress {:?}", + colors::blue(), + colors::reset(), &from.path ); let mut files_unpacked = vec![]; @@ -45,8 +47,9 @@ impl TarDecompressor { file.unpack_in(into)?; println!( - "{}: {:?} extracted. ({})", - "info".yellow(), + "{}[INFO]{} {:?} extracted. ({})", + colors::yellow(), + colors::reset(), into.join(file.path()?), utils::Bytes::new(file.size()) ); diff --git a/src/decompressors/to_memory.rs b/src/decompressors/to_memory.rs index a76e06006..d7aaf639e 100644 --- a/src/decompressors/to_memory.rs +++ b/src/decompressors/to_memory.rs @@ -3,7 +3,8 @@ use std::{ path::Path, }; -use colored::Colorize; + +use utils::colors; use super::decompressor::{DecompressionResult, Decompressor}; use crate::{extension::CompressionFormat, file::File, utils}; @@ -35,8 +36,9 @@ impl DecompressorToMemory { let bytes_read = reader.read_to_end(&mut buffer)?; println!( - "{}: {:?} extracted into memory ({}).", - "info".yellow(), + "{}[INFO]{} {:?} extracted into memory ({}).", + colors::yellow(), + colors::reset(), path, utils::Bytes::new(bytes_read as u64) ); diff --git a/src/decompressors/zip.rs b/src/decompressors/zip.rs index ac2120c07..501f25df6 100644 --- a/src/decompressors/zip.rs +++ b/src/decompressors/zip.rs @@ -4,7 +4,7 @@ use std::{ path::{Path, PathBuf}, }; -use colored::Colorize; +use utils::colors; use zip::{self, read::ZipFile, ZipArchive}; use super::decompressor::{DecompressionResult, Decompressor}; @@ -26,8 +26,9 @@ impl ZipDecompressor { let comment = file.comment(); if !comment.is_empty() { println!( - "{}: Comment in {}: {}", - "info".yellow(), + "{}[INFO]{} Comment in {}: {}", + colors::yellow(), + colors::reset(), file.name(), comment ); @@ -73,8 +74,9 @@ impl ZipDecompressor { } } println!( - "{}: \"{}\" extracted. ({})", - "info".yellow(), + "{}[INFO]{} \"{}\" extracted. ({})", + colors::yellow(), + colors::reset(), file_path.display(), utils::Bytes::new(file.size()) ); @@ -95,7 +97,7 @@ impl ZipDecompressor { } fn unpack_files(from: File, into: &Path, flags: &oof::Flags) -> crate::Result> { - println!("{} decompressing {:?}", "[OUCH]".bright_blue(), &from.path); + println!("{}[INFO]{} decompressing {:?}", colors::blue(), colors::reset(), &from.path); match from.contents_in_memory { Some(bytes) => { diff --git a/src/dialogs.rs b/src/dialogs.rs index 2902a4142..645a43250 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -1,6 +1,6 @@ use std::io::{self, Write}; -use colored::Colorize; +use crate::utils::colors; pub struct Confirmation<'a> { pub prompt: &'a str, @@ -26,7 +26,7 @@ impl<'a> Confirmation<'a> { }; loop { - print!("{} [{}/{}] ", message, "Y".bright_green(), "n".bright_red()); + print!("{} [{}Y{}/{}n{}] ", message, colors::green(), colors::reset(), colors::red(), colors::reset()); io::stdout().flush()?; let mut answer = String::new(); diff --git a/src/error.rs b/src/error.rs index 3b7c3e0f1..565631487 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,5 @@ use std::{fmt, path::PathBuf}; - -use colored::Colorize; +use crate::utils::colors; #[derive(PartialEq, Eq)] pub enum Error { @@ -33,7 +32,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::MissingExtensionError(filename) => { - write!(f, "{} ", "[ERROR]".red())?; + write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; // TODO: show MIME type of the unsupported file write!(f, "cannot compress to {:?}, likely because it has an unsupported (or missing) extension.", filename) } @@ -42,14 +41,14 @@ impl fmt::Display for Error { write!(f, "") } Error::FileNotFound(file) => { - write!(f, "{} ", "[ERROR]".red())?; + write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; if file == &PathBuf::from("") { return write!(f, "file not found!"); } write!(f, "file {:?} not found!", file) } Error::CompressingRootFolder => { - write!(f, "{} ", "[ERROR]".red())?; + write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; let spacing = " "; writeln!(f, "It seems you're trying to compress the root folder.")?; writeln!( @@ -59,21 +58,22 @@ impl fmt::Display for Error { )?; write!( f, - "{}Use a more appropriate tool for this, such as {}.", + "{}Use a more appropriate tool for this, such as {}rsync{}.", spacing, - "rsync".green() + colors::green(), + colors::reset() ) } Error::MissingArgumentsForCompression => { - write!(f, "{} ", "[ERROR]".red())?; + write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; let spacing = " "; writeln!(f,"The compress subcommands demands at least 2 arguments, an input file and an output file.")?; writeln!(f,"{}Example: `ouch compress img.jpeg img.zip", spacing)?; write!(f,"{}For more information, run `ouch --help`", spacing) } Error::InternalError => { - write!(f, "{} ", "[ERROR]".red())?; - write!(f, "You've reached an internal error! This really should not have happened.\nPlease file an issue at {}", "https://github.com/vrmiguel/ouch".green()) + write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; + write!(f, "You've reached an internal error! This really should not have happened.\nPlease file an issue at {}https://github.com/vrmiguel/ouch{}", colors::green(), colors::reset()) } _err => { // TODO @@ -90,7 +90,7 @@ impl From for Error { std::io::ErrorKind::PermissionDenied => Self::PermissionDenied, std::io::ErrorKind::AlreadyExists => Self::AlreadyExists, _other => { - println!("{} {}", "[IO error]".red(), err); + println!("{}[IO error]{} {}", colors::red(), colors::reset(), err); Self::IoError } } @@ -111,7 +111,7 @@ impl From for Error { impl From for Error { fn from(err: walkdir::Error) -> Self { - eprintln!("{} {}", "[ERROR]".red(), err); + eprintln!("{}[ERROR]{} {}", colors::red(), colors::reset(), err); Self::WalkdirError } } diff --git a/src/utils.rs b/src/utils.rs index a6071807a..3afc69320 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,8 +5,6 @@ use std::{ path::{Path, PathBuf}, }; -use colored::Colorize; - use crate::{dialogs::Confirmation, extension::CompressionFormat, file::File}; #[macro_export] @@ -42,10 +40,11 @@ pub fn check_for_multiple_files( ) -> crate::Result<()> { if files.len() != 1 { eprintln!( - "{}: cannot compress multiple files directly to {:#?}.\n\ + "{}[ERROR]{} cannot compress multiple files directly to {:#?}.\n\ Try using an intermediate archival method such as Tar.\n\ Example: filename.tar{}", - "[ERROR]".red(), + colors::red(), + colors::reset(), format, format ); @@ -58,14 +57,16 @@ pub fn check_for_multiple_files( pub fn create_path_if_non_existent(path: &Path) -> crate::Result<()> { if !path.exists() { println!( - "{}: attempting to create folder {:?}.", - "[INFO]".yellow(), + "{}[INFO]{} attempting to create folder {:?}.", + colors::yellow(), + colors::reset(), &path ); std::fs::create_dir_all(path)?; println!( - "{}: directory {:#?} created.", - "[INFO]".yellow(), + "{}[INFO]{} directory {:#?} created.", + colors::yellow(), + colors::reset(), fs::canonicalize(&path)? ); } @@ -106,7 +107,7 @@ pub fn permission_for_overwriting( ) -> crate::Result { match (flags.is_present("yes"), flags.is_present("false")) { (true, true) => { - unreachable!("This shoul've been cutted out in the ~/src/cli.rs filter flags function.") + unreachable!("This should've been cutted out in the ~/src/cli.rs filter flags function.") } (true, _) => return Ok(true), (_, true) => return Ok(false), From d9b39706e2f237963bdc5c3f7957d79338b3cabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Wed, 7 Apr 2021 02:23:50 -0300 Subject: [PATCH 04/37] Drop dependency on Termion --- Cargo.toml | 2 -- src/lib.rs | 1 + src/test.rs | 2 +- src/utils.rs | 40 +++++++++++++++++++--------------------- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3cb83f47a..0e70d9407 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,6 @@ zip = "0.5.11" # Dependency from workspace oof = { path = "./oof" } -[target.'cfg(unix)'.dependencies] -termion = "1.5.6" [profile.release] lto = true codegen-units = 1 diff --git a/src/lib.rs b/src/lib.rs index fa9bad72a..70fc3476a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ mod error; mod extension; mod file; mod utils; +mod test; pub use error::{Error, Result}; diff --git a/src/test.rs b/src/test.rs index 4d6154054..95700a0ba 100644 --- a/src/test.rs +++ b/src/test.rs @@ -126,7 +126,7 @@ mod argparsing { #[cfg(test)] mod byte_pretty_printing { - use crate::bytes::Bytes; + use crate::utils::Bytes; #[test] fn bytes() { assert_eq!(&format!("{}", Bytes::new(234)), "234.00 B"); diff --git a/src/utils.rs b/src/utils.rs index 3afc69320..99020aa79 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -131,37 +131,35 @@ pub struct Bytes { #[allow(dead_code)] #[cfg(target_family = "unix")] pub mod colors { - use termion::color::*; - - pub fn reset() -> &'static str { - Reset.fg_str() + pub const fn reset() -> &'static str { + "\u{1b}[39m" } - pub fn black() -> &'static str { - LightBlack.fg_str() + pub const fn black() -> &'static str { + "\u{1b}[38;5;8m" } - pub fn blue() -> &'static str { - LightBlue.fg_str() + pub const fn blue() -> &'static str { + "\u{1b}[38;5;12m" } - pub fn cyan() -> &'static str { - LightCyan.fg_str() + pub const fn cyan() -> &'static str { + "\u{1b}[38;5;14m" } - pub fn green() -> &'static str { - LightGreen.fg_str() + pub const fn green() -> &'static str { + "\u{1b}[38;5;10m" } - pub fn magenta() -> &'static str { - LightMagenta.fg_str() + pub const fn magenta() -> &'static str { + "\u{1b}[38;5;13m" } - pub fn red() -> &'static str { - LightRed.fg_str() + pub const fn red() -> &'static str { + "\u{1b}[38;5;9m" } - pub fn white() -> &'static str { - LightWhite.fg_str() + pub const fn white() -> &'static str { + "\u{1b}[38;5;15m" } - pub fn yellow() -> &'static str { - LightYellow.fg_str() + pub const fn yellow() -> &'static str { + "\u{1b}[38;5;11m" } } -// Termion does not support Windows +// Windows does not support ANSI escape codes #[allow(dead_code, non_upper_case_globals)] #[cfg(not(target_family = "unix"))] pub mod colors { From c08eb9163251a4a6e20812d392c7ddd93cfd90c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Wed, 7 Apr 2021 21:59:23 -0300 Subject: [PATCH 05/37] oof: add some error variants to replace panics --- oof/src/error.rs | 10 +++++++++- oof/src/lib.rs | 24 +++++++++++------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/oof/src/error.rs b/oof/src/error.rs index abc867166..3403771f8 100644 --- a/oof/src/error.rs +++ b/oof/src/error.rs @@ -3,12 +3,17 @@ use std::{error, ffi::OsString, fmt}; use crate::Flag; #[derive(Debug)] -pub enum OofError { +pub enum OofError{ FlagValueConflict { flag: Flag, previous_value: OsString, new_value: OsString, }, + /// User supplied a flag containing invalid Unicode + InvalidUnicode(OsString), + /// User supplied an unrecognized short flag + UnknownShortFlag(char), + MisplacedShortArgFlagError(char) } impl error::Error for OofError { @@ -30,6 +35,9 @@ impl fmt::Display for OofError { "CLI flag value conflicted for flag '--{}', previous: {:?}, new: {:?}.", flag.long, previous_value, new_value ), + OofError::InvalidUnicode(flag) => write!(f, "{:?} is not valid Unicode.", flag), + OofError::UnknownShortFlag(ch) => write!(f, "Unknown argument '-{}'", ch), + OofError::MisplacedShortArgFlagError(ch) => write!(f, "Invalid placement of `-{}`.\nOnly the last letter in a sequence of short flags can take values.", ch), } } } diff --git a/oof/src/lib.rs b/oof/src/lib.rs index ccd43646f..ef509acd3 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -7,10 +7,7 @@ mod error; mod flags; pub mod util; -use std::{ - collections::BTreeMap, - ffi::{OsStr, OsString}, -}; +use std::{collections::BTreeMap, ffi::{OsStr, OsString}}; pub use error::OofError; pub use flags::{ArgFlag, Flag, FlagType, Flags}; @@ -105,10 +102,11 @@ pub fn filter_flags( continue; } - // If it is a flag, now we try to interpret as valid utf-8 - let flag: &str = arg - .to_str() - .unwrap_or_else(|| panic!("User error: The flag needs to be valid utf8")); + // If it is a flag, now we try to interpret it as valid utf-8 + let flag= match arg.to_str() { + Some(arg) => arg, + None => return Err(OofError::InvalidUnicode(arg)) + }; // Only one hyphen in the flag // A short flag can be of form "-", "-abcd", "-h", "-v", etc @@ -129,14 +127,14 @@ pub fn filter_flags( // Safety: this loop only runs when len >= 1, so this subtraction is safe let is_last_letter = i == letters.len() - 1; - let flag_info = short_flags_info.get(&letter).unwrap_or_else(|| { - panic!("User error: Unexpected/UNKNOWN flag `letter`, error") - }); + let flag_info = short_flags_info.get(&letter).ok_or_else(|| { + OofError::UnknownShortFlag(letter) + })?; if !is_last_letter && flag_info.takes_value { - panic!("User error: Only the last letter can refer to flag that takes values"); + return Err(OofError::MisplacedShortArgFlagError(letter)) // Because "-AB argument" only works if B takes values, not A. - // That is, the short flag that takes values need to come at the end + // That is, the short flag that takes values needs to come at the end // of this piece of text } From 5cca3c42e4f506a53cacdcc1e5df14a682b5c563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Wed, 7 Apr 2021 23:12:30 -0300 Subject: [PATCH 06/37] oof: impl fmt::Display for Flag --- oof/src/flags.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/oof/src/flags.rs b/oof/src/flags.rs index b31a8d610..0b3e7cca8 100644 --- a/oof/src/flags.rs +++ b/oof/src/flags.rs @@ -29,6 +29,15 @@ pub struct Flag { pub takes_value: bool, } +impl std::fmt::Display for Flag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.short { + Some(short_flag) => write!(f, "-{}, --{}", short_flag, self.long), + None => write!(f, "--{}", self.long), + } + } +} + impl Flag { pub fn long(name: &'static str) -> Self { Self { From faae7c088ffc055095d3ab7c0326295d040da084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Wed, 7 Apr 2021 23:28:37 -0300 Subject: [PATCH 07/37] oof: add error variant MissingValueToFlag --- oof/src/error.rs | 10 ++++++---- oof/src/lib.rs | 9 +++------ src/error.rs | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/oof/src/error.rs b/oof/src/error.rs index 3403771f8..fd3675c91 100644 --- a/oof/src/error.rs +++ b/oof/src/error.rs @@ -3,7 +3,7 @@ use std::{error, ffi::OsString, fmt}; use crate::Flag; #[derive(Debug)] -pub enum OofError{ +pub enum OofError<'t> { FlagValueConflict { flag: Flag, previous_value: OsString, @@ -13,16 +13,17 @@ pub enum OofError{ InvalidUnicode(OsString), /// User supplied an unrecognized short flag UnknownShortFlag(char), - MisplacedShortArgFlagError(char) + MisplacedShortArgFlagError(char), + MissingValueToFlag(&'t Flag) } -impl error::Error for OofError { +impl<'t> error::Error for OofError<'t> { fn source(&self) -> Option<&(dyn error::Error + 'static)> { None } } -impl fmt::Display for OofError { +impl<'t> fmt::Display for OofError<'t> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO: implement proper debug messages match self { @@ -38,6 +39,7 @@ impl fmt::Display for OofError { OofError::InvalidUnicode(flag) => write!(f, "{:?} is not valid Unicode.", flag), OofError::UnknownShortFlag(ch) => write!(f, "Unknown argument '-{}'", ch), OofError::MisplacedShortArgFlagError(ch) => write!(f, "Invalid placement of `-{}`.\nOnly the last letter in a sequence of short flags can take values.", ch), + OofError::MissingValueToFlag(flag) => write!(f, "Flag {} takes value but none was supplied.", flag) } } } diff --git a/oof/src/lib.rs b/oof/src/lib.rs index ef509acd3..2763f30ba 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -147,12 +147,9 @@ pub fn filter_flags( } // pop the next one - let flag_argument = iter.next().unwrap_or_else(|| { - panic!( - "USer errror: argument flag `argument_flag` came at last, but it \ - requires an argument" - ) - }); + let flag_argument = iter.next().ok_or_else(|| { + OofError::MissingValueToFlag(flag_info) + })?; // Otherwise, insert it. result_flags.argument_flags.insert(flag_name, flag_argument); diff --git a/src/error.rs b/src/error.rs index 565631487..ea6d69e65 100644 --- a/src/error.rs +++ b/src/error.rs @@ -116,7 +116,7 @@ impl From for Error { } } -impl From for Error { +impl<'t> From> for Error { fn from(_err: oof::OofError) -> Self { todo!("We need to implement this properly"); } From 613074dff126f4bc6cb96a65d745154d23ff2dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Wed, 7 Apr 2021 23:36:58 -0300 Subject: [PATCH 08/37] oof: add error variant DuplicatedFlag --- oof/src/error.rs | 6 ++++-- oof/src/lib.rs | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/oof/src/error.rs b/oof/src/error.rs index fd3675c91..f1435ca66 100644 --- a/oof/src/error.rs +++ b/oof/src/error.rs @@ -14,7 +14,8 @@ pub enum OofError<'t> { /// User supplied an unrecognized short flag UnknownShortFlag(char), MisplacedShortArgFlagError(char), - MissingValueToFlag(&'t Flag) + MissingValueToFlag(&'t Flag), + DuplicatedFlag(&'t Flag) } impl<'t> error::Error for OofError<'t> { @@ -39,7 +40,8 @@ impl<'t> fmt::Display for OofError<'t> { OofError::InvalidUnicode(flag) => write!(f, "{:?} is not valid Unicode.", flag), OofError::UnknownShortFlag(ch) => write!(f, "Unknown argument '-{}'", ch), OofError::MisplacedShortArgFlagError(ch) => write!(f, "Invalid placement of `-{}`.\nOnly the last letter in a sequence of short flags can take values.", ch), - OofError::MissingValueToFlag(flag) => write!(f, "Flag {} takes value but none was supplied.", flag) + OofError::MissingValueToFlag(flag) => write!(f, "Flag {} takes value but none was supplied.", flag), + OofError::DuplicatedFlag(flag) => write!(f, "Duplicated usage of {}.", flag), } } } diff --git a/oof/src/lib.rs b/oof/src/lib.rs index 2763f30ba..4fc65d151 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -143,7 +143,7 @@ pub fn filter_flags( if flag_info.takes_value { // If it was already inserted if result_flags.argument_flags.contains_key(flag_name) { - panic!("User error: duplicated, found this flag TWICE!"); + return Err(OofError::DuplicatedFlag(flag_info)); } // pop the next one @@ -156,7 +156,7 @@ pub fn filter_flags( } else { // If it was already inserted if result_flags.boolean_flags.contains(flag_name) { - panic!("User error: duplicated, found this flag TWICE!"); + return Err(OofError::DuplicatedFlag(flag_info)); } // Otherwise, insert it result_flags.boolean_flags.insert(flag_name); @@ -176,7 +176,7 @@ pub fn filter_flags( if flag_info.takes_value { // If it was already inserted if result_flags.argument_flags.contains_key(&flag_name) { - panic!("User error: duplicated, found this flag TWICE!"); + return Err(OofError::DuplicatedFlag(flag_info)); } let flag_argument = iter.next().unwrap_or_else(|| { @@ -189,7 +189,7 @@ pub fn filter_flags( } else { // If it was already inserted if result_flags.boolean_flags.contains(&flag_name) { - panic!("User error: duplicated, found this flag TWICE!"); + return Err(OofError::DuplicatedFlag(flag_info)); } // Otherwise, insert it result_flags.boolean_flags.insert(&flag_name); From 2f6ac5e54ca254ea2ed4822572195ebd7996091b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Thu, 8 Apr 2021 00:26:02 -0300 Subject: [PATCH 09/37] error: Save std::io::Error as a crate::Error variant --- src/cli.rs | 3 +-- src/error.rs | 17 +++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index d9340f435..5015dd419 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -64,8 +64,7 @@ where if !path.as_ref().exists() { Err(crate::Error::FileNotFound(PathBuf::from(path.as_ref()))) } else { - eprintln!("[ERROR] {}", io_err); - Err(crate::Error::IoError) + Err(crate::Error::IoError(io_err)) } } } diff --git a/src/error.rs b/src/error.rs index ea6d69e65..3e1bec5eb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,20 +1,20 @@ use std::{fmt, path::PathBuf}; use crate::utils::colors; -#[derive(PartialEq, Eq)] pub enum Error { UnknownExtensionError(String), MissingExtensionError(PathBuf), // TODO: get rid of this error variant InvalidUnicode, InvalidInput, - IoError, + IoError(std::io::Error), FileNotFound(PathBuf), AlreadyExists, InvalidZipArchive(&'static str), PermissionDenied, UnsupportedZipArchive(&'static str), InternalError, + OofError, CompressingRootFolder, MissingArgumentsForCompression, WalkdirError, @@ -75,6 +75,9 @@ impl fmt::Display for Error { write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; write!(f, "You've reached an internal error! This really should not have happened.\nPlease file an issue at {}https://github.com/vrmiguel/ouch{}", colors::green(), colors::reset()) } + Error::IoError(io_err) => { + write!(f, "{}[ERROR]{} {}", colors::red(), colors::reset(), io_err) + } _err => { // TODO write!(f, "") @@ -90,8 +93,7 @@ impl From for Error { std::io::ErrorKind::PermissionDenied => Self::PermissionDenied, std::io::ErrorKind::AlreadyExists => Self::AlreadyExists, _other => { - println!("{}[IO error]{} {}", colors::red(), colors::reset(), err); - Self::IoError + Self::IoError(err) } } } @@ -117,7 +119,10 @@ impl From for Error { } impl<'t> From> for Error { - fn from(_err: oof::OofError) -> Self { - todo!("We need to implement this properly"); + fn from(err: oof::OofError) -> Self { + // To avoid entering a lifetime hell, we'll just print the Oof error here + // and skip saving it into a variant of Self + println!("{}[ERROR]{} {}", colors::red(), colors::reset(), err); + Self::OofError } } From 908f8e4e93deb11ea2368e3b065315bf730bddd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Thu, 8 Apr 2021 00:38:24 -0300 Subject: [PATCH 10/37] oof: Add variant UnknownLongFlag and remove missing panics --- oof/src/error.rs | 2 ++ oof/src/flags.rs | 2 +- oof/src/lib.rs | 15 ++++++--------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/oof/src/error.rs b/oof/src/error.rs index f1435ca66..86ead8c96 100644 --- a/oof/src/error.rs +++ b/oof/src/error.rs @@ -13,6 +13,7 @@ pub enum OofError<'t> { InvalidUnicode(OsString), /// User supplied an unrecognized short flag UnknownShortFlag(char), + UnknownLongFlag(String), MisplacedShortArgFlagError(char), MissingValueToFlag(&'t Flag), DuplicatedFlag(&'t Flag) @@ -42,6 +43,7 @@ impl<'t> fmt::Display for OofError<'t> { OofError::MisplacedShortArgFlagError(ch) => write!(f, "Invalid placement of `-{}`.\nOnly the last letter in a sequence of short flags can take values.", ch), OofError::MissingValueToFlag(flag) => write!(f, "Flag {} takes value but none was supplied.", flag), OofError::DuplicatedFlag(flag) => write!(f, "Duplicated usage of {}.", flag), + OofError::UnknownLongFlag(flag) => write!(f, "Unknown argument '--{}'", flag), } } } diff --git a/oof/src/flags.rs b/oof/src/flags.rs index 0b3e7cca8..043896e92 100644 --- a/oof/src/flags.rs +++ b/oof/src/flags.rs @@ -32,7 +32,7 @@ pub struct Flag { impl std::fmt::Display for Flag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.short { - Some(short_flag) => write!(f, "-{}, --{}", short_flag, self.long), + Some(short_flag) => write!(f, "-{}/--{}", short_flag, self.long), None => write!(f, "--{}", self.long), } } diff --git a/oof/src/lib.rs b/oof/src/lib.rs index 4fc65d151..69084b6ce 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -167,9 +167,9 @@ pub fn filter_flags( if let FlagType::Long = flag_type { let flag = trim_double_hyphen(flag); - let flag_info = long_flags_info.get(flag).unwrap_or_else(|| { - panic!("User error: Unexpected/UNKNOWN flag '{}'", flag); - }); + let flag_info = long_flags_info.get(flag).ok_or_else(|| { + OofError::UnknownLongFlag(String::from(flag)) + })?; let flag_name = flag_info.long; @@ -179,12 +179,9 @@ pub fn filter_flags( return Err(OofError::DuplicatedFlag(flag_info)); } - let flag_argument = iter.next().unwrap_or_else(|| { - panic!( - "USer errror: argument flag `argument_flag` came at last, but it requires \ - an argument" - ) - }); + let flag_argument = iter.next().ok_or_else(|| { + OofError::MissingValueToFlag(flag_info) + })?; result_flags.argument_flags.insert(flag_name, flag_argument); } else { // If it was already inserted From e7eb55a4cf9daa0da4a9bda6b82946221fc0a43b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Thu, 8 Apr 2021 02:55:34 -0300 Subject: [PATCH 11/37] test: make_dummy_files: return an error instead of panicking --- src/test.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 95700a0ba..254f67df0 100644 --- a/src/test.rs +++ b/src/test.rs @@ -20,7 +20,6 @@ where let _ = paths .iter() .map(make_dummy_file) - .map(Result::unwrap) .collect::>(); Ok(()) } From 5ca99c101e8e65fb50f6f77b380d4e7b06f11843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Thu, 8 Apr 2021 03:03:07 -0300 Subject: [PATCH 12/37] oof: remove unnecessary closures (((and undo last commit))) --- oof/src/lib.rs | 12 ++++++------ src/test.rs | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/oof/src/lib.rs b/oof/src/lib.rs index 69084b6ce..87445bd99 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -127,9 +127,9 @@ pub fn filter_flags( // Safety: this loop only runs when len >= 1, so this subtraction is safe let is_last_letter = i == letters.len() - 1; - let flag_info = short_flags_info.get(&letter).ok_or_else(|| { + let flag_info = short_flags_info.get(&letter).ok_or( OofError::UnknownShortFlag(letter) - })?; + )?; if !is_last_letter && flag_info.takes_value { return Err(OofError::MisplacedShortArgFlagError(letter)) @@ -147,9 +147,9 @@ pub fn filter_flags( } // pop the next one - let flag_argument = iter.next().ok_or_else(|| { + let flag_argument = iter.next().ok_or( OofError::MissingValueToFlag(flag_info) - })?; + )?; // Otherwise, insert it. result_flags.argument_flags.insert(flag_name, flag_argument); @@ -179,9 +179,9 @@ pub fn filter_flags( return Err(OofError::DuplicatedFlag(flag_info)); } - let flag_argument = iter.next().ok_or_else(|| { + let flag_argument = iter.next().ok_or( OofError::MissingValueToFlag(flag_info) - })?; + )?; result_flags.argument_flags.insert(flag_name, flag_argument); } else { // If it was already inserted diff --git a/src/test.rs b/src/test.rs index 254f67df0..95700a0ba 100644 --- a/src/test.rs +++ b/src/test.rs @@ -20,6 +20,7 @@ where let _ = paths .iter() .map(make_dummy_file) + .map(Result::unwrap) .collect::>(); Ok(()) } From c94b49f01377c6f45890e04a54f9be1d595b0e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Thu, 8 Apr 2021 12:48:52 -0300 Subject: [PATCH 13/37] cli: Only suggest the typo correction and exit --- src/cli.rs | 21 +++++++-------------- src/error.rs | 4 ++++ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 5015dd419..1fdcaa3a5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -6,7 +6,7 @@ use std::{ }; use strsim::normalized_damerau_levenshtein; -use oof::{Flag, arg_flag, flag}; +use oof::{arg_flag, flag}; #[derive(PartialEq, Eq, Debug)] @@ -98,8 +98,10 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { let mut flags_info = vec![flag!('y', "yes"), flag!('n', "no")]; - let process_compression_command = |args, flags_info: Vec| { - let (args, flags) = oof::filter_flags(args, &flags_info)?; + let parsed_args = match oof::pop_subcommand(&mut args, subcommands) { + Some(&"compress") => { + // `ouch compress` subcommand + let (args, flags) = oof::filter_flags(args, &flags_info)?; let mut files: Vec = args.into_iter().map(PathBuf::from).collect(); if files.len() < 2 { @@ -115,24 +117,15 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { files, compressed_output_path, }; - Ok(ParsedArgs { command, flags }) - }; - - let parsed_args = match oof::pop_subcommand(&mut args, subcommands) { - Some(&"compress") => { - process_compression_command(args, flags_info)? + ParsedArgs { command, flags } } // Defaults to decompression when there is no subcommand None => { flags_info.push(arg_flag!('o', "output")); - { let first_arg = args.first().unwrap(); if is_typo(first_arg) { - println!("Did you mean `ouch compress`?"); - // TODO: ask for permission ? - args.remove(0); - return process_compression_command(args, flags_info); + return Err(crate::Error::CompressionTypo); } } diff --git a/src/error.rs b/src/error.rs index 3e1bec5eb..2c869c0aa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,6 +17,7 @@ pub enum Error { OofError, CompressingRootFolder, MissingArgumentsForCompression, + CompressionTypo, WalkdirError, } @@ -78,6 +79,9 @@ impl fmt::Display for Error { Error::IoError(io_err) => { write!(f, "{}[ERROR]{} {}", colors::red(), colors::reset(), io_err) } + Error::CompressionTypo =>{ + write!(f, "Did you mean {}ouch compress{}?", colors::magenta(), colors::reset()) + } _err => { // TODO write!(f, "") From b614533fd5935792dcb2056f15b5949247893c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Thu, 8 Apr 2021 22:31:10 -0300 Subject: [PATCH 14/37] utils: fix usage of the -n, --no flag --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 99020aa79..011d3ed15 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -105,7 +105,7 @@ pub fn permission_for_overwriting( flags: &oof::Flags, confirm: &Confirmation, ) -> crate::Result { - match (flags.is_present("yes"), flags.is_present("false")) { + match (flags.is_present("yes"), flags.is_present("no")) { (true, true) => { unreachable!("This should've been cutted out in the ~/src/cli.rs filter flags function.") } From 38e7009a27753d0f2186424e960a7b411ec9f4d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Thu, 8 Apr 2021 22:46:47 -0300 Subject: [PATCH 15/37] cli: add an alias for the compress subcommand --- src/cli.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 1fdcaa3a5..560296606 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -94,12 +94,14 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { }); } - let subcommands = &["compress"]; + let subcommands = &[ + "c", "compress" + ]; let mut flags_info = vec![flag!('y', "yes"), flag!('n', "no")]; let parsed_args = match oof::pop_subcommand(&mut args, subcommands) { - Some(&"compress") => { + Some(&"c") | Some(&"compress") => { // `ouch compress` subcommand let (args, flags) = oof::filter_flags(args, &flags_info)?; let mut files: Vec = args.into_iter().map(PathBuf::from).collect(); From 3cb0bfa1e570d27db2833ee3ec288606178c6c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Fri, 9 Apr 2021 02:05:31 -0300 Subject: [PATCH 16/37] cli: refactor path canonicalizing logic --- src/cli.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 560296606..43fddd82a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -135,13 +135,10 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { // Parse flags let (args, mut flags) = oof::filter_flags(args, &flags_info)?; - let files = args.into_iter().map(canonicalize); - for file in files.clone() { - if let Err(err) = file { - return Err(err); - } - } - let files = files.map(Result::unwrap).collect(); + let files = args + .into_iter() + .map(canonicalize) + .collect::, _>>()?; let output_folder = flags.take_arg("output").map(PathBuf::from); From 2ba91644e854429c6f0090e6529278c3879c7f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= Date: Fri, 9 Apr 2021 12:05:49 -0300 Subject: [PATCH 17/37] commands: remove two unnecessary clones --- src/commands.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index ad5dee862..10ef89cac 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -49,7 +49,7 @@ type BoxedDecompressor = Box; fn get_compressor(file: &File) -> crate::Result<(Option, BoxedCompressor)> { let extension = match &file.extension { - Some(extension) => extension.clone(), + Some(extension) => extension, 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())); @@ -58,7 +58,7 @@ fn get_compressor(file: &File) -> crate::Result<(Option, BoxedC // Supported first compressors: // .tar and .zip - let first_compressor: Option> = match extension.first_ext { + let first_compressor: Option> = match &extension.first_ext { Some(ext) => match ext { CompressionFormat::Tar => Some(Box::new(TarCompressor)), CompressionFormat::Zip => Some(Box::new(ZipCompressor)), @@ -84,7 +84,7 @@ fn get_compressor(file: &File) -> crate::Result<(Option, BoxedC fn get_decompressor(file: &File) -> crate::Result<(Option, BoxedDecompressor)> { let extension = match &file.extension { - Some(extension) => extension.clone(), + Some(extension) => extension, None => { // This block *should* be unreachable eprintln!( @@ -104,7 +104,7 @@ fn get_decompressor(file: &File) -> crate::Result<(Option, Bo CompressionFormat::Bzip => Box::new(BzipDecompressor), }; - let first_decompressor: Option> = match extension.first_ext { + let first_decompressor: Option> = match &extension.first_ext { Some(ext) => match ext { CompressionFormat::Tar => Some(Box::new(TarDecompressor)), CompressionFormat::Zip => Some(Box::new(ZipDecompressor)), From 8548f73870b0c718c725c2ef26aabc56642939a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Miguel?= <36349314+vrmiguel@users.noreply.github.com> Date: Sat, 10 Apr 2021 12:01:04 -0300 Subject: [PATCH 18/37] Update README.md --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d2c1039cf..9a43ccf07 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -`ouch` loosely stands for Obvious Unified Compression (ᵃⁿᵈ ᵈᵉᶜᵒᵐᵖʳᵉˢˢᶦᵒⁿ) Helper and aims to be an easy and intuitive way of compressing and decompressing files on the command-line. +`ouch` loosely stands for Obvious Unified Compression files Helper and aims to be an easy and intuitive way of compressing and decompressing files on the command-line. - [Usage](#Usage) - [Decompressing files](#Decompressing-files) @@ -35,6 +35,8 @@ ouch a.zip b.tar.gz c.tar.bz2 -o new_folder The `compress` subcommand is able to compress files and folders. The **last** argument will be the **output file**. +You can also use the `c` alias for this subcommand. + The compression format employed will be defined according to the output file's extension. ```bash @@ -46,11 +48,14 @@ ouch compress a.mp4 b.jpg c.png files.tar.bz2 # Compress a folder and a file into `videos.tar.xz` ouch compress Videos/ funny_meme.mp4 videos.tar.xz + +# Compress two folders into a lzma file +ouch c src/ target/ build.tar.lz ``` ### Listing the elements of an archive -(TODO -- not implemented at all) +* **Upcoming feature** ``` # Shows the files and folders contained in videos.tar.xz @@ -98,11 +103,17 @@ cd ouch && cargo run --release ## Supported operating systems `ouch` runs on Linux, macOS and Windows 10. Binaries are available on our [Releases](https://github.com/vrmiguel/ouch/releases) page. -Binaries are also available at the end of each (successful) [GitHub Actions](https://github.com/vrmiguel/ouch/actions) run. -**Note on Windows**: colors are currently messed up on PowerShell but work fine on [ConEmu](https://conemu.github.io/). A feature for disabling colors is planned. +Binaries are also available at the end of each (successful) [GitHub Actions](https://github.com/vrmiguel/ouch/actions) run for these targets: +* Linux x86-64 statically linked (musl libc) +* macOS x86-64 dynamically linked +* Windows 10 +* Linux ARMv7 dynamically linked (glibc) + +One must be logged into GitHub to access build artifacts. ## Limitations `ouch` does encoding and decoding in-memory, so decompressing very large files with it is not advisable. + From 864fc1a29b7e9224f008e3125790707c01ce55fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Sat, 15 May 2021 01:12:03 -0300 Subject: [PATCH 19/37] decompression: panic if there are no arguments --- src/cli.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 43fddd82a..886209a20 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,9 +5,8 @@ use std::{ vec::Vec, }; -use strsim::normalized_damerau_levenshtein; use oof::{arg_flag, flag}; - +use strsim::normalized_damerau_levenshtein; #[derive(PartialEq, Eq, Debug)] pub enum Command { @@ -36,11 +35,10 @@ pub struct ParsedArgs { pub flags: oof::Flags, } - -/// check_for_typo checks if the first argument is -/// a typo for the compress subcommand. +/// check_for_typo checks if the first argument is +/// a typo for the compress subcommand. /// Returns true if the arg is probably a typo or false otherwise. -fn is_typo<'a, P>(path: P) -> bool +fn is_typo<'a, P>(path: P) -> bool where P: AsRef + 'a, { @@ -70,8 +68,6 @@ where } } - - fn canonicalize_files<'a, P>(files: Vec

) -> crate::Result> where P: AsRef + 'a, @@ -94,9 +90,7 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { }); } - let subcommands = &[ - "c", "compress" - ]; + let subcommands = &["c", "compress"]; let mut flags_info = vec![flag!('y', "yes"), flag!('n', "no")]; @@ -124,13 +118,14 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { // Defaults to decompression when there is no subcommand None => { flags_info.push(arg_flag!('o', "output")); - { - let first_arg = args.first().unwrap(); + + if let Some(first_arg) = args.first() { if is_typo(first_arg) { return Err(crate::Error::CompressionTypo); } + } else { + todo!("Complain that no decompression arguments were given."); } - // Parse flags let (args, mut flags) = oof::filter_flags(args, &flags_info)?; From de28e573d850c8c4f01bb4b9c44008aaa7eba363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Sat, 15 May 2021 01:13:40 -0300 Subject: [PATCH 20/37] Fix error message typo --- src/error.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/error.rs b/src/error.rs index 2c869c0aa..89953c0c1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,5 @@ -use std::{fmt, path::PathBuf}; use crate::utils::colors; +use std::{fmt, path::PathBuf}; pub enum Error { UnknownExtensionError(String), @@ -69,8 +69,8 @@ impl fmt::Display for Error { write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; let spacing = " "; writeln!(f,"The compress subcommands demands at least 2 arguments, an input file and an output file.")?; - writeln!(f,"{}Example: `ouch compress img.jpeg img.zip", spacing)?; - write!(f,"{}For more information, run `ouch --help`", spacing) + writeln!(f, "{}Example: `ouch compress img.jpeg img.zip`", spacing)?; + write!(f, "{}For more information, run `ouch --help`", spacing) } Error::InternalError => { write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; @@ -79,12 +79,16 @@ impl fmt::Display for Error { Error::IoError(io_err) => { write!(f, "{}[ERROR]{} {}", colors::red(), colors::reset(), io_err) } - Error::CompressionTypo =>{ - write!(f, "Did you mean {}ouch compress{}?", colors::magenta(), colors::reset()) + Error::CompressionTypo => { + write!( + f, + "Did you mean {}ouch compress{}?", + colors::magenta(), + colors::reset() + ) } _err => { - // TODO - write!(f, "") + todo!(); } } } @@ -96,9 +100,7 @@ impl From for Error { std::io::ErrorKind::NotFound => panic!("{}", err), std::io::ErrorKind::PermissionDenied => Self::PermissionDenied, std::io::ErrorKind::AlreadyExists => Self::AlreadyExists, - _other => { - Self::IoError(err) - } + _other => Self::IoError(err), } } } From 3dc9a78539c43a40fd53ffa1e83a30c021e74a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Sat, 15 May 2021 01:15:19 -0300 Subject: [PATCH 21/37] Removing src/test.rs --- src/lib.rs | 1 - src/test.rs | 351 ---------------------------------------------------- 2 files changed, 352 deletions(-) delete mode 100644 src/test.rs diff --git a/src/lib.rs b/src/lib.rs index 70fc3476a..fa9bad72a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ mod error; mod extension; mod file; mod utils; -mod test; pub use error::{Error, Result}; diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index 95700a0ba..000000000 --- a/src/test.rs +++ /dev/null @@ -1,351 +0,0 @@ -use std::{fs, path::Path}; - -#[allow(dead_code)] -// ouch's command-line logic uses fs::canonicalize on its inputs so we cannot -// use made-up files for testing. -// make_dummy_file therefores creates a small temporary file to bypass fs::canonicalize errors -fn make_dummy_file<'a, P>(path: P) -> crate::Result<()> -where - P: AsRef + 'a, -{ - fs::write(path.as_ref(), &[2, 3, 4, 5, 6, 7, 8, 9, 10])?; - Ok(()) -} - -#[allow(dead_code)] -fn make_dummy_files<'a, P>(paths: &[P]) -> crate::Result<()> -where - P: AsRef + 'a, -{ - let _ = paths - .iter() - .map(make_dummy_file) - .map(Result::unwrap) - .collect::>(); - Ok(()) -} - -#[cfg(test)] -mod argparsing { - use super::make_dummy_files; - use crate::cli; - use crate::cli::Command; - use std::{ffi::OsString, fs, path::PathBuf}; - - fn gen_args(text: &str) -> Vec { - let args = text.split_whitespace(); - args.map(OsString::from).collect() - } - - macro_rules! parse { - ($input_text:expr) => {{ - let args = gen_args($input_text); - cli::parse_args_from(args).unwrap() - }}; - } - - #[test] - // The absolute flags that ignore all the other argparsing rules are --help and --version - fn test_absolute_flags() { - let expected = Command::ShowHelp; - assert_eq!(expected, parse!("").command); - assert_eq!(expected, parse!("-h").command); - assert_eq!(expected, parse!("--help").command); - assert_eq!(expected, parse!("aaaaaaaa --help -o -e aaa").command); - assert_eq!(expected, parse!("aaaaaaaa -h").command); - assert_eq!(expected, parse!("--help compress aaaaaaaa").command); - assert_eq!(expected, parse!("compress --help").command); - assert_eq!(expected, parse!("--version --help").command); - assert_eq!(expected, parse!("aaaaaaaa -v aaaa -h").command); - - let expected = Command::ShowVersion; - assert_eq!(expected, parse!("ouch --version").command); - assert_eq!(expected, parse!("ouch a --version b").command); - } - - #[test] - fn test_arg_parsing_compress_subcommand() -> crate::Result<()> { - let files = vec!["a", "b", "c"]; - make_dummy_files(&*files)?; - let files = files - .iter() - .map(fs::canonicalize) - .map(Result::unwrap) - .collect(); - - let expected = Command::Compress { - files, - compressed_output_path: "d".into(), - }; - assert_eq!(expected, parse!("compress a b c d").command); - - fs::remove_file("a")?; - fs::remove_file("b")?; - fs::remove_file("c")?; - Ok(()) - } - - #[test] - fn test_arg_parsing_decompress_subcommand() -> crate::Result<()> { - let files = vec!["d", "e", "f"]; - make_dummy_files(&*files)?; - - let files: Vec<_> = files.iter().map(PathBuf::from).collect(); - - let expected = Command::Decompress { - files: files - .iter() - .map(fs::canonicalize) - .map(Result::unwrap) - .collect(), - output_folder: None, - }; - - assert_eq!(expected, parse!("d e f").command); - - let expected = Command::Decompress { - files: files.iter().map(fs::canonicalize).map(Result::unwrap).collect(), - output_folder: Some("folder".into()), - }; - assert_eq!(expected, parse!("d e f --output folder").command); - assert_eq!(expected, parse!("d e --output folder f").command); - assert_eq!(expected, parse!("d --output folder e f").command); - assert_eq!(expected, parse!("--output folder d e f").command); - - assert_eq!(expected, parse!("d e f -o folder").command); - assert_eq!(expected, parse!("d e -o folder f").command); - assert_eq!(expected, parse!("d -o folder e f").command); - assert_eq!(expected, parse!("-o folder d e f").command); - - fs::remove_file("d")?; - fs::remove_file("e")?; - fs::remove_file("f")?; - Ok(()) - } -} - -#[cfg(test)] -mod byte_pretty_printing { - use crate::utils::Bytes; - #[test] - fn bytes() { - assert_eq!(&format!("{}", Bytes::new(234)), "234.00 B"); - - assert_eq!(&format!("{}", Bytes::new(999)), "999.00 B"); - } - - #[test] - fn kilobytes() { - assert_eq!(&format!("{}", Bytes::new(2234)), "2.23 kB"); - - assert_eq!(&format!("{}", Bytes::new(62500)), "62.50 kB"); - - assert_eq!(&format!("{}", Bytes::new(329990)), "329.99 kB"); - } - - #[test] - fn megabytes() { - assert_eq!(&format!("{}", Bytes::new(2750000)), "2.75 MB"); - - assert_eq!(&format!("{}", Bytes::new(55000000)), "55.00 MB"); - - assert_eq!(&format!("{}", Bytes::new(987654321)), "987.65 MB"); - } - - #[test] - fn gigabytes() { - assert_eq!(&format!("{}", Bytes::new(5280000000)), "5.28 GB"); - - assert_eq!(&format!("{}", Bytes::new(95200000000)), "95.20 GB"); - - assert_eq!(&format!("{}", Bytes::new(302000000000)), "302.00 GB"); - } -} - -// #[cfg(test)] -// mod cli { -// use super::*; - -// #[test] -// fn decompress_files_into_folder() -> crate::Result<()> { -// make_dummy_file("file.zip")?; -// let args = gen_args("ouch -i file.zip -o folder/"); -// let (command, flags) = cli::parse_args_and_flags_from(args)?; - -// assert_eq!( -// command, -// Command::Decompress { -// files: args, -// compressed_output_path: PathBuf, -// } // kind: Decompress(vec![File { -// // path: fs::canonicalize("file.zip")?, -// // contents_in_memory: None, -// // extension: Some(Extension::from(Zip)) -// // }]), -// // output: Some(File { -// // path: "folder".into(), -// // contents_in_memory: None, -// // extension: None -// // }), -// // } -// ); - -// fs::remove_file("file.zip")?; - -// Ok(()) -// } - -// #[test] -// fn decompress_files() -> crate::Result<()> { -// make_dummy_file("my-cool-file.zip")?; -// make_dummy_file("file.tar")?; -// let matches = -// clap_app().get_matches_from(vec!["ouch", "-i", "my-cool-file.zip", "file.tar"]); -// let command_from_matches = Command::try_from(matches)?; - -// assert_eq!( -// command_from_matches, -// Command { -// kind: Decompress(vec![ -// File { -// path: fs::canonicalize("my-cool-file.zip")?, -// contents_in_memory: None, -// extension: Some(Extension::from(Zip)) -// }, -// File { -// path: fs::canonicalize("file.tar")?, -// contents_in_memory: None, -// extension: Some(Extension::from(Tar)) -// } -// ],), -// output: None, -// } -// ); - -// fs::remove_file("my-cool-file.zip")?; -// fs::remove_file("file.tar")?; - -// Ok(()) -// } - -// #[test] -// fn compress_files() -> crate::Result<()> { -// make_dummy_file("file")?; -// make_dummy_file("file2.jpeg")?; -// make_dummy_file("file3.ok")?; - -// let matches = clap_app().get_matches_from(vec![ -// "ouch", -// "-i", -// "file", -// "file2.jpeg", -// "file3.ok", -// "-o", -// "file.tar", -// ]); -// let command_from_matches = Command::try_from(matches)?; - -// assert_eq!( -// command_from_matches, -// Command { -// kind: Compress(vec![ -// fs::canonicalize("file")?, -// fs::canonicalize("file2.jpeg")?, -// fs::canonicalize("file3.ok")? -// ]), -// output: Some(File { -// path: "file.tar".into(), -// contents_in_memory: None, -// extension: Some(Extension::from(Tar)) -// }), -// } -// ); - -// fs::remove_file("file")?; -// fs::remove_file("file2.jpeg")?; -// fs::remove_file("file3.ok")?; - -// Ok(()) -// } -// } - -// #[cfg(test)] -// mod cli_errors { - -// #[test] -// fn compress_files() -> crate::Result<()> { -// let matches = -// clap_app().get_matches_from(vec!["ouch", "-i", "a_file", "file2.jpeg", "file3.ok"]); -// let res = Command::try_from(matches); - -// assert_eq!( -// res, -// Err(crate::Error::InputsMustHaveBeenDecompressible( -// "a_file".into() -// )) -// ); - -// Ok(()) -// } -// } - -// #[cfg(test)] -// mod extension_extraction { - -// #[test] -// fn test_extension_zip() { -// let path = "filename.tar.zip"; -// assert_eq!( -// CompressionFormat::try_from(path), -// Ok(CompressionFormat::Zip) -// ); -// } - -// #[test] -// fn test_extension_tar_gz() { -// let extension = Extension::from(OsStr::new("folder.tar.gz")).unwrap(); -// assert_eq!( -// extension, -// Extension { -// first_ext: Some(CompressionFormat::Tar), -// second_ext: CompressionFormat::Gzip -// } -// ); -// } - -// #[test] -// fn test_extension_tar() { -// let path = "pictures.tar"; -// assert_eq!( -// CompressionFormat::try_from(path), -// Ok(CompressionFormat::Tar) -// ); -// } - -// #[test] -// fn test_extension_gz() { -// let path = "passwords.tar.gz"; -// assert_eq!( -// CompressionFormat::try_from(path), -// Ok(CompressionFormat::Gzip) -// ); -// } - -// #[test] -// fn test_extension_lzma() { -// let path = "mygame.tar.lzma"; -// assert_eq!( -// CompressionFormat::try_from(path), -// Ok(CompressionFormat::Lzma) -// ); -// } - -// #[test] -// fn test_extension_bz() { -// let path = "songs.tar.bz"; -// assert_eq!( -// CompressionFormat::try_from(path), -// Ok(CompressionFormat::Bzip) -// ); -// } -// } From d01947d9e14c87b5183c3debd0c392082ce7473b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Sun, 16 May 2021 17:52:29 -0300 Subject: [PATCH 22/37] Adding rustfmt.toml and reformatting --- oof/src/error.rs | 2 +- oof/src/flags.rs | 12 ++------ oof/src/lib.rs | 56 +++++++++++++--------------------- rustfmt.toml | 13 ++++++++ src/cli.rs | 31 +++++-------------- src/commands.rs | 43 +++++++++----------------- src/compressors/bzip.rs | 2 +- src/compressors/gzip.rs | 2 +- src/compressors/lzma.rs | 2 +- src/compressors/zip.rs | 2 +- src/decompressors/tar.rs | 3 +- src/decompressors/to_memory.rs | 1 - src/decompressors/zip.rs | 8 ++--- src/dialogs.rs | 16 ++++++---- src/error.rs | 28 ++++++++--------- src/extension.rs | 40 ++++++++---------------- src/file.rs | 6 +--- src/lib.rs | 7 +---- src/utils.rs | 18 +++++------ 19 files changed, 114 insertions(+), 178 deletions(-) create mode 100644 rustfmt.toml diff --git a/oof/src/error.rs b/oof/src/error.rs index 86ead8c96..306f17629 100644 --- a/oof/src/error.rs +++ b/oof/src/error.rs @@ -16,7 +16,7 @@ pub enum OofError<'t> { UnknownLongFlag(String), MisplacedShortArgFlagError(char), MissingValueToFlag(&'t Flag), - DuplicatedFlag(&'t Flag) + DuplicatedFlag(&'t Flag), } impl<'t> error::Error for OofError<'t> { diff --git a/oof/src/flags.rs b/oof/src/flags.rs index 043896e92..b70af33b7 100644 --- a/oof/src/flags.rs +++ b/oof/src/flags.rs @@ -13,11 +13,7 @@ pub struct ArgFlag; impl ArgFlag { pub fn long(name: &'static str) -> Flag { - Flag { - long: name, - short: None, - takes_value: true, - } + Flag { long: name, short: None, takes_value: true } } } @@ -40,11 +36,7 @@ impl std::fmt::Display for Flag { impl Flag { pub fn long(name: &'static str) -> Self { - Self { - long: name, - short: None, - takes_value: false, - } + Self { long: name, short: None, takes_value: false } } pub fn short(mut self, short_flag_char: char) -> Self { diff --git a/oof/src/lib.rs b/oof/src/lib.rs index 87445bd99..662186791 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -7,7 +7,10 @@ mod error; mod flags; pub mod util; -use std::{collections::BTreeMap, ffi::{OsStr, OsString}}; +use std::{ + collections::BTreeMap, + ffi::{OsStr, OsString}, +}; pub use error::OofError; pub use flags::{ArgFlag, Flag, FlagType, Flags}; @@ -103,9 +106,9 @@ pub fn filter_flags( } // If it is a flag, now we try to interpret it as valid utf-8 - let flag= match arg.to_str() { + let flag = match arg.to_str() { Some(arg) => arg, - None => return Err(OofError::InvalidUnicode(arg)) + None => return Err(OofError::InvalidUnicode(arg)), }; // Only one hyphen in the flag @@ -127,12 +130,11 @@ pub fn filter_flags( // Safety: this loop only runs when len >= 1, so this subtraction is safe let is_last_letter = i == letters.len() - 1; - let flag_info = short_flags_info.get(&letter).ok_or( - OofError::UnknownShortFlag(letter) - )?; + let flag_info = + short_flags_info.get(&letter).ok_or(OofError::UnknownShortFlag(letter))?; if !is_last_letter && flag_info.takes_value { - return Err(OofError::MisplacedShortArgFlagError(letter)) + return Err(OofError::MisplacedShortArgFlagError(letter)); // Because "-AB argument" only works if B takes values, not A. // That is, the short flag that takes values needs to come at the end // of this piece of text @@ -147,9 +149,8 @@ pub fn filter_flags( } // pop the next one - let flag_argument = iter.next().ok_or( - OofError::MissingValueToFlag(flag_info) - )?; + let flag_argument = + iter.next().ok_or(OofError::MissingValueToFlag(flag_info))?; // Otherwise, insert it. result_flags.argument_flags.insert(flag_name, flag_argument); @@ -167,9 +168,9 @@ pub fn filter_flags( if let FlagType::Long = flag_type { let flag = trim_double_hyphen(flag); - let flag_info = long_flags_info.get(flag).ok_or_else(|| { - OofError::UnknownLongFlag(String::from(flag)) - })?; + let flag_info = long_flags_info + .get(flag) + .ok_or_else(|| OofError::UnknownLongFlag(String::from(flag)))?; let flag_name = flag_info.long; @@ -179,9 +180,7 @@ pub fn filter_flags( return Err(OofError::DuplicatedFlag(flag_info)); } - let flag_argument = iter.next().ok_or( - OofError::MissingValueToFlag(flag_info) - )?; + let flag_argument = iter.next().ok_or(OofError::MissingValueToFlag(flag_info))?; result_flags.argument_flags.insert(flag_name, flag_argument); } else { // If it was already inserted @@ -209,9 +208,7 @@ where T: AsRef, U: AsRef, { - texts - .iter() - .any(|text| args.iter().any(|arg| arg.as_ref() == text.as_ref())) + texts.iter().any(|text| args.iter().any(|arg| arg.as_ref() == text.as_ref())) } #[cfg(test)] @@ -237,14 +234,8 @@ mod tests { assert_eq!(args, gen_args("ouch a.zip b.tar.gz c.tar")); assert!(flags.is_present("output_file")); - assert_eq!( - Some(&OsString::from("new_folder")), - flags.arg("output_file") - ); - assert_eq!( - Some(OsString::from("new_folder")), - flags.take_arg("output_file") - ); + assert_eq!(Some(&OsString::from("new_folder")), flags.arg("output_file")); + assert_eq!(Some(OsString::from("new_folder")), flags.take_arg("output_file")); assert!(!flags.is_present("output_file")); } @@ -291,10 +282,7 @@ mod tests { // TODO: remove should_panic and use proper error handling inside of filter_args #[should_panic] fn test_flag_info_with_long_flag_conflict() { - let flags_info = [ - ArgFlag::long("verbose").short('a'), - Flag::long("verbose").short('b'), - ]; + let flags_info = [ArgFlag::long("verbose").short('a'), Flag::long("verbose").short('b')]; // Should panic here let result = filter_flags(vec![], &flags_info); @@ -305,10 +293,8 @@ mod tests { // TODO: remove should_panic and use proper error handling inside of filter_args #[should_panic] fn test_flag_info_with_short_flag_conflict() { - let flags_info = [ - ArgFlag::long("output_file").short('o'), - Flag::long("verbose").short('o'), - ]; + let flags_info = + [ArgFlag::long("output_file").short('o'), Flag::long("verbose").short('o')]; // Should panic here filter_flags(vec![], &flags_info).unwrap_err(); diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..213e7f34e --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,13 @@ +# Normal features +max_width = 100 +imports_granularity = "Crate" +match_block_trailing_comma = true +overflow_delimited_expr = true +reorder_impl_items = true +use_field_init_shorthand = true +newline_style = "Unix" +edition = "2018" +reorder_imports = true +reorder_modules = true +use_try_shorthand = true +use_small_heuristics = "Max" diff --git a/src/cli.rs b/src/cli.rs index 886209a20..b79d58866 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -64,7 +64,7 @@ where } else { Err(crate::Error::IoError(io_err)) } - } + }, } } @@ -77,17 +77,11 @@ where pub fn parse_args_from(mut args: Vec) -> crate::Result { if oof::matches_any_arg(&args, &["--help", "-h"]) || args.is_empty() { - return Ok(ParsedArgs { - command: Command::ShowHelp, - flags: oof::Flags::default(), - }); + return Ok(ParsedArgs { command: Command::ShowHelp, flags: oof::Flags::default() }); } if oof::matches_any_arg(&args, &["--version"]) { - return Ok(ParsedArgs { - command: Command::ShowVersion, - flags: oof::Flags::default(), - }); + return Ok(ParsedArgs { command: Command::ShowVersion, flags: oof::Flags::default() }); } let subcommands = &["c", "compress"]; @@ -109,12 +103,9 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { let files = canonicalize_files(files)?; - let command = Command::Compress { - files, - compressed_output_path, - }; + let command = Command::Compress { files, compressed_output_path }; ParsedArgs { command, flags } - } + }, // Defaults to decompression when there is no subcommand None => { flags_info.push(arg_flag!('o', "output")); @@ -130,21 +121,15 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { // Parse flags let (args, mut flags) = oof::filter_flags(args, &flags_info)?; - let files = args - .into_iter() - .map(canonicalize) - .collect::, _>>()?; + let files = args.into_iter().map(canonicalize).collect::, _>>()?; let output_folder = flags.take_arg("output").map(PathBuf::from); // TODO: ensure all files are decompressible - let command = Command::Decompress { - files, - output_folder, - }; + let command = Command::Decompress { files, output_folder }; ParsedArgs { command, flags } - } + }, _ => unreachable!("You should match each subcommand passed."), }; diff --git a/src/commands.rs b/src/commands.rs index 10ef89cac..ee36c60c6 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -24,20 +24,16 @@ use crate::{ 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, - } => { + 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 => crate::help_command(), Command::ShowVersion => crate::version_command(), } @@ -53,7 +49,7 @@ fn get_compressor(file: &File) -> crate::Result<(Option, BoxedC 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: @@ -93,7 +89,7 @@ fn get_decompressor(file: &File) -> crate::Result<(Option, Bo colors::reset() ); return Err(crate::Error::InvalidInput); - } + }, }; let second_decompressor: Box = match extension.second_ext { @@ -126,10 +122,7 @@ fn decompress_file_in_memory( ) -> 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); + 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 @@ -156,14 +149,10 @@ fn decompress_file_in_memory( 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 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 { @@ -196,11 +185,11 @@ fn compress_files( 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!( @@ -224,9 +213,7 @@ fn decompress_file( 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, - ))); + return Err(crate::Error::MissingExtensionError(PathBuf::from(file_path))); } let output = match output { @@ -251,7 +238,7 @@ fn decompress_file( 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 @@ -260,7 +247,7 @@ fn decompress_file( // to worry about, at least at the moment. // TODO: use the `files` variable for something - } + }, } Ok(()) diff --git a/src/compressors/bzip.rs b/src/compressors/bzip.rs index 102de37c6..66a2ca43a 100644 --- a/src/compressors/bzip.rs +++ b/src/compressors/bzip.rs @@ -34,7 +34,7 @@ impl BzipCompressor { Some(bytes) => bytes, None => { return Err(crate::Error::InternalError); - } + }, }; Self::compress_bytes(&*bytes) diff --git a/src/compressors/gzip.rs b/src/compressors/gzip.rs index 8e16970c3..398ed58a5 100644 --- a/src/compressors/gzip.rs +++ b/src/compressors/gzip.rs @@ -38,7 +38,7 @@ impl GzipCompressor { Some(bytes) => bytes, None => { unreachable!(); - } + }, }; Self::compress_bytes(file_contents) diff --git a/src/compressors/lzma.rs b/src/compressors/lzma.rs index e8f18551c..45d36b2aa 100644 --- a/src/compressors/lzma.rs +++ b/src/compressors/lzma.rs @@ -38,7 +38,7 @@ impl LzmaCompressor { Some(bytes) => bytes, None => { unreachable!(); - } + }, }; Self::compress_bytes(file_contents) diff --git a/src/compressors/zip.rs b/src/compressors/zip.rs index 1d814d571..cb5a7178e 100644 --- a/src/compressors/zip.rs +++ b/src/compressors/zip.rs @@ -37,7 +37,7 @@ impl ZipCompressor { // TODO: error description, although this block should not be // reachable return Err(crate::Error::InvalidInput); - } + }, }; writer.write_all(&*input_bytes)?; diff --git a/src/decompressors/tar.rs b/src/decompressors/tar.rs index 4d9135864..fe2cff7f5 100644 --- a/src/decompressors/tar.rs +++ b/src/decompressors/tar.rs @@ -4,7 +4,6 @@ use std::{ path::{Path, PathBuf}, }; - use tar::{self, Archive}; use utils::colors; @@ -30,7 +29,7 @@ impl TarDecompressor { None => { let file = fs::File::open(&from.path)?; tar::Archive::new(Box::new(file)) - } + }, }; for file in archive.entries()? { diff --git a/src/decompressors/to_memory.rs b/src/decompressors/to_memory.rs index d7aaf639e..50105a5b7 100644 --- a/src/decompressors/to_memory.rs +++ b/src/decompressors/to_memory.rs @@ -3,7 +3,6 @@ use std::{ path::Path, }; - use utils::colors; use super::decompressor::{DecompressionResult, Decompressor}; diff --git a/src/decompressors/zip.rs b/src/decompressors/zip.rs index 501f25df6..d31115cbc 100644 --- a/src/decompressors/zip.rs +++ b/src/decompressors/zip.rs @@ -66,7 +66,7 @@ impl ZipDecompressor { _is_dir @ true => { println!("File {} extracted to \"{}\"", idx, file_path.display()); fs::create_dir_all(&file_path)?; - } + }, _is_file @ false => { if let Some(path) = file_path.parent() { if !path.exists() { @@ -83,7 +83,7 @@ impl ZipDecompressor { let mut output_file = fs::File::create(&file_path)?; io::copy(&mut file, &mut output_file)?; - } + }, } #[cfg(unix)] @@ -104,14 +104,14 @@ impl ZipDecompressor { // Decompressing a .zip archive loaded up in memory let mut archive = zip::ZipArchive::new(Cursor::new(bytes))?; 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, flags)?) - } + }, } } } diff --git a/src/dialogs.rs b/src/dialogs.rs index 645a43250..5d5e44ed8 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -12,10 +12,7 @@ pub struct Error; impl<'a> Confirmation<'a> { pub fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self { - Self { - prompt, - placeholder: pattern, - } + Self { prompt, placeholder: pattern } } pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result { @@ -26,7 +23,14 @@ impl<'a> Confirmation<'a> { }; loop { - print!("{} [{}Y{}/{}n{}] ", message, colors::green(), colors::reset(), colors::red(), colors::reset()); + print!( + "{} [{}Y{}/{}n{}] ", + message, + colors::green(), + colors::reset(), + colors::red(), + colors::reset() + ); io::stdout().flush()?; let mut answer = String::new(); @@ -40,7 +44,7 @@ impl<'a> Confirmation<'a> { match trimmed_answer.to_ascii_lowercase().as_ref() { "y" | "yes" => return Ok(true), "n" | "no" => return Ok(false), - _ => {} + _ => {}, } } } diff --git a/src/error.rs b/src/error.rs index 89953c0c1..83657e1b5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,7 @@ -use crate::utils::colors; use std::{fmt, path::PathBuf}; +use crate::utils::colors; + pub enum Error { UnknownExtensionError(String), MissingExtensionError(PathBuf), @@ -36,18 +37,18 @@ impl fmt::Display for Error { write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; // TODO: show MIME type of the unsupported file write!(f, "cannot compress to {:?}, likely because it has an unsupported (or missing) extension.", filename) - } + }, Error::WalkdirError => { // Already printed in the From block write!(f, "") - } + }, Error::FileNotFound(file) => { write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; if file == &PathBuf::from("") { return write!(f, "file not found!"); } write!(f, "file {:?} not found!", file) - } + }, Error::CompressingRootFolder => { write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; let spacing = " "; @@ -64,32 +65,27 @@ impl fmt::Display for Error { colors::green(), colors::reset() ) - } + }, Error::MissingArgumentsForCompression => { write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; let spacing = " "; writeln!(f,"The compress subcommands demands at least 2 arguments, an input file and an output file.")?; writeln!(f, "{}Example: `ouch compress img.jpeg img.zip`", spacing)?; write!(f, "{}For more information, run `ouch --help`", spacing) - } + }, Error::InternalError => { write!(f, "{}[ERROR]{} ", colors::red(), colors::reset())?; write!(f, "You've reached an internal error! This really should not have happened.\nPlease file an issue at {}https://github.com/vrmiguel/ouch{}", colors::green(), colors::reset()) - } + }, Error::IoError(io_err) => { write!(f, "{}[ERROR]{} {}", colors::red(), colors::reset(), io_err) - } + }, Error::CompressionTypo => { - write!( - f, - "Did you mean {}ouch compress{}?", - colors::magenta(), - colors::reset() - ) - } + write!(f, "Did you mean {}ouch compress{}?", colors::magenta(), colors::reset()) + }, _err => { todo!(); - } + }, } } } diff --git a/src/extension.rs b/src/extension.rs index 06de63d08..e6a313ae0 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -34,10 +34,7 @@ pub fn get_extension_from_filename(file_name: &OsStr) -> Option<(&OsStr, &OsStr) impl From for Extension { fn from(second_ext: CompressionFormat) -> Self { - Self { - first_ext: None, - second_ext, - } + Self { first_ext: None, second_ext } } } @@ -57,29 +54,22 @@ impl Extension { (os_str, snd) if os_str.is_empty() => (None, snd), (fst, snd) => (Some(fst), snd), }, - None => { - return Err(crate::Error::MissingExtensionError(PathBuf::from( - file_name, - ))) - } + None => return Err(crate::Error::MissingExtensionError(PathBuf::from(file_name))), }; let (first_ext, second_ext) = match (first_ext, second_ext) { (None, snd) => { let ext = compression_format_from(snd)?; (None, ext) - } + }, (Some(fst), snd) => { let snd = compression_format_from(snd)?; let fst = compression_format_from(fst).ok(); (fst, snd) - } + }, }; - Ok(Self { - first_ext, - second_ext, - }) + Ok(Self { first_ext, second_ext }) } } @@ -119,7 +109,7 @@ impl TryFrom<&PathBuf> for CompressionFormat { Some(ext) => ext, None => { return Err(crate::Error::MissingExtensionError(PathBuf::new())); - } + }, }; extension_from_os_str(ext) } @@ -141,16 +131,12 @@ impl TryFrom<&str> for CompressionFormat { impl Display for CompressionFormat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Gzip => ".gz", - Bzip => ".bz", - Lzma => ".lz", - Tar => ".tar", - Zip => ".zip", - } - ) + write!(f, "{}", match self { + Gzip => ".gz", + Bzip => ".bz", + Lzma => ".lz", + Tar => ".tar", + Zip => ".zip", + }) } } diff --git a/src/file.rs b/src/file.rs index a3d262371..c14d2d8c2 100644 --- a/src/file.rs +++ b/src/file.rs @@ -21,10 +21,6 @@ impl<'a> File<'a> { pub fn from(path: &'a Path) -> crate::Result { let extension = Extension::from(path.as_ref()).ok(); - Ok(File { - path, - contents_in_memory: None, - extension, - }) + Ok(File { path, contents_in_memory: None, extension }) } } diff --git a/src/lib.rs b/src/lib.rs index fa9bad72a..c22759886 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,10 +72,5 @@ Visit https://github.com/vrmiguel/ouch for more usage examples.", #[inline] fn version_command() { use utils::colors::*; - println!( - "{green}ouch{reset} {}", - crate::VERSION, - green = green(), - reset = reset(), - ); + println!("{green}ouch{reset} {}", crate::VERSION, green = green(), reset = reset(),); } diff --git a/src/utils.rs b/src/utils.rs index 011d3ed15..d9895439b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -79,7 +79,7 @@ pub fn get_destination_path<'a>(dest: &'a Option) -> &'a Path { // Must be None according to the way command-line arg. parsing in Ouch works assert_eq!(output_file.extension, None); Path::new(&output_file.path) - } + }, None => Path::new("."), } } @@ -93,9 +93,7 @@ pub fn change_dir_and_return_parent(filename: &Path) -> crate::Result { return Err(crate::Error::CompressingRootFolder); }; - env::set_current_dir(parent) - .ok() - .ok_or(crate::Error::CompressingRootFolder)?; + env::set_current_dir(parent).ok().ok_or(crate::Error::CompressingRootFolder)?; Ok(previous_location) } @@ -107,11 +105,13 @@ pub fn permission_for_overwriting( ) -> crate::Result { match (flags.is_present("yes"), flags.is_present("no")) { (true, true) => { - unreachable!("This should've been cutted out in the ~/src/cli.rs filter flags function.") - } + unreachable!( + "This should've been cutted out in the ~/src/cli.rs filter flags function." + ) + }, (true, _) => return Ok(true), (_, true) => return Ok(false), - _ => {} + _ => {}, } let file_path_str = to_utf(path); @@ -181,9 +181,7 @@ impl Bytes { const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"]; pub fn new(bytes: u64) -> Self { - Self { - bytes: bytes as f64, - } + Self { bytes: bytes as f64 } } } From 2e66cec4744ee1039ebe6f15ccbf42fb299fa00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Mon, 17 May 2021 14:03:27 -0300 Subject: [PATCH 23/37] Adding testing dependencies rand: to create random files tempdir: create temporary test directories --- Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0e70d9407..f07521a44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,10 +21,13 @@ tar = "0.4.33" xz2 = "0.1.6" zip = "0.5.11" - # Dependency from workspace oof = { path = "./oof" } +[dev-dependencies] +tempdir = "0.3.7" +rand = "0.8.3" + [profile.release] lto = true codegen-units = 1 From a4ca0a6621357bb67716e948d36f7b8ccbd1cf9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Mon, 17 May 2021 14:41:29 -0300 Subject: [PATCH 24/37] Adding tests for compression and decompression --- tests/compress_and_decompress.rs | 136 +++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 tests/compress_and_decompress.rs diff --git a/tests/compress_and_decompress.rs b/tests/compress_and_decompress.rs new file mode 100644 index 000000000..8476d2e3e --- /dev/null +++ b/tests/compress_and_decompress.rs @@ -0,0 +1,136 @@ +use std::{ + env, fs, + io::prelude::*, + path::{Path, PathBuf}, +}; + +use ouch::{cli::Command, commands::run}; +use rand::random; +use tempdir::TempDir; + +#[test] +/// Tests each format that supports multiple files with random input. +/// TODO: test the remaining formats. +/// TODO2: Fix testing of .tar.zip and .zip.zip +fn test_each_format() { + test_compression_and_decompression("tar"); + test_compression_and_decompression("tar.gz"); + test_compression_and_decompression("tar.bz"); + test_compression_and_decompression("tar.bz2"); + test_compression_and_decompression("tar.xz"); + test_compression_and_decompression("tar.lz"); + test_compression_and_decompression("tar.lzma"); + // test_compression_and_decompression("tar.zip"); + test_compression_and_decompression("zip"); + test_compression_and_decompression("zip.gz"); + test_compression_and_decompression("zip.bz"); + test_compression_and_decompression("zip.bz2"); + test_compression_and_decompression("zip.xz"); + test_compression_and_decompression("zip.lz"); + test_compression_and_decompression("zip.lzma"); + // test_compression_and_decompression("zip.zip"); +} + +type FileContent = Vec; + +fn test_compression_and_decompression(format: &str) { + // System temporary directory depends on the platform + // For linux it is /tmp + let system_tmp = env::temp_dir(); + // Create a folder that will be deleted on drop + let testing_dir = String::from("ouch-testing-") + format; + let testing_dir = TempDir::new_in(system_tmp, &testing_dir).expect("Could not create tempdir"); + let testing_dir = testing_dir.path(); + + // Quantity of compressed files vary from 1 to 10 + let quantity_of_files = random::() % 10 + 1; + + let contents_of_files: Vec = + (0..quantity_of_files).map(|_| generate_random_file_content()).collect(); + + let mut file_paths = create_files(&testing_dir, &contents_of_files); + let archive_path = compress_files(&testing_dir, &file_paths, &format); + let mut extracted_paths = extract_files(&archive_path); + + // // If you want to visualize the compressed and extracted files before auto-destruction: + // std::thread::sleep(std::time::Duration::from_secs(40)); + + file_paths.sort(); + extracted_paths.sort(); + + compare_paths(&file_paths, &extracted_paths); + compare_file_contents(&extracted_paths, &contents_of_files); +} + +// Crate file contents from 1024 up to 8192 random bytes +fn generate_random_file_content() -> FileContent { + let quantity = 1024 + random::() % (8192 - 1024); + (0..quantity).map(|_| random()).collect() +} + +// Create files using the indexes as file names (eg. 0, 1, 2 and 3) +// Returns the paths +fn create_files(at: &Path, contents: &[FileContent]) -> Vec { + contents + .iter() + .enumerate() + .map(|(i, content)| { + let path = at.join(i.to_string()); + let mut file = fs::File::create(&path).expect("Could not create dummy test file"); + file.write_all(content).expect("Could not write to dummy test file"); + path + }) + .collect() +} + +fn compress_files(at: &Path, paths_to_compress: &[PathBuf], format: &str) -> PathBuf { + let archive_path = String::from("archive.") + format; + let archive_path = at.join(archive_path); + + let command = Command::Compress { + files: paths_to_compress.to_vec(), + compressed_output_path: archive_path.to_path_buf(), + }; + run(command, &oof::Flags::default()).expect("Failed to compress test dummy files"); + + archive_path +} + +fn extract_files(archive_path: &Path) -> Vec { + // We will extract in the same folder as the archive + // If the archive is at: + // /tmp/ouch-testing-tar.Rbq4DusBrtF8/archive.tar + // Then the extraction_output_folder will be: + // /tmp/ouch-testing-tar.Rbq4DusBrtF8/extraction_results/ + let mut extraction_output_folder = archive_path.to_path_buf(); + // Remove the name of the extracted archive + assert!(extraction_output_folder.pop()); + // Add the suffix "results" + extraction_output_folder.push("extraction_results"); + + let command = Command::Decompress { + files: vec![archive_path.to_owned()], + output_folder: Some(extraction_output_folder.clone()), + }; + run(command, &oof::Flags::default()).expect("Failed to extract"); + + fs::read_dir(extraction_output_folder) + .unwrap() + .map(Result::unwrap) + .map(|entry| entry.path()) + .collect() +} + +fn compare_paths(original: &[PathBuf], extracted: &[PathBuf]) { + assert_eq!(original.len(), extracted.len()); + for (original, extracted) in original.iter().zip(extracted) { + assert_eq!(original.file_name(), extracted.file_name()); + } +} + +fn compare_file_contents(extracted: &[PathBuf], contents: &[FileContent]) { + for (extracted_path, expected_content) in extracted.iter().zip(contents) { + let read_content = fs::read(extracted_path).expect("Failed to read from file"); + assert_eq!(&read_content, expected_content); + } +} From f4e1798cc7d9de9c81c89a07141689a429f6f5d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Mon, 17 May 2021 14:42:19 -0300 Subject: [PATCH 25/37] Improving tests performance --- Cargo.toml | 2 +- tests/compress_and_decompress.rs | 49 +++++++++++++++++--------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f07521a44..2be213b2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ oof = { path = "./oof" } [dev-dependencies] tempdir = "0.3.7" -rand = "0.8.3" +rand = { version = "0.8.3", default-features = false, features = ["small_rng", "std"] } [profile.release] lto = true diff --git a/tests/compress_and_decompress.rs b/tests/compress_and_decompress.rs index 8476d2e3e..98ab149f9 100644 --- a/tests/compress_and_decompress.rs +++ b/tests/compress_and_decompress.rs @@ -5,7 +5,7 @@ use std::{ }; use ouch::{cli::Command, commands::run}; -use rand::random; +use rand::{rngs::SmallRng, RngCore, SeedableRng}; use tempdir::TempDir; #[test] @@ -13,27 +13,28 @@ use tempdir::TempDir; /// TODO: test the remaining formats. /// TODO2: Fix testing of .tar.zip and .zip.zip fn test_each_format() { - test_compression_and_decompression("tar"); - test_compression_and_decompression("tar.gz"); - test_compression_and_decompression("tar.bz"); - test_compression_and_decompression("tar.bz2"); - test_compression_and_decompression("tar.xz"); - test_compression_and_decompression("tar.lz"); - test_compression_and_decompression("tar.lzma"); - // test_compression_and_decompression("tar.zip"); - test_compression_and_decompression("zip"); - test_compression_and_decompression("zip.gz"); - test_compression_and_decompression("zip.bz"); - test_compression_and_decompression("zip.bz2"); - test_compression_and_decompression("zip.xz"); - test_compression_and_decompression("zip.lz"); - test_compression_and_decompression("zip.lzma"); - // test_compression_and_decompression("zip.zip"); + let mut rng = SmallRng::from_entropy(); + test_compression_and_decompression(&mut rng, "tar"); + test_compression_and_decompression(&mut rng, "tar.gz"); + test_compression_and_decompression(&mut rng, "tar.bz"); + test_compression_and_decompression(&mut rng, "tar.bz2"); + test_compression_and_decompression(&mut rng, "tar.xz"); + test_compression_and_decompression(&mut rng, "tar.lz"); + test_compression_and_decompression(&mut rng, "tar.lzma"); + // test_compression_and_decompression(&mut rng, "tar.zip"); + test_compression_and_decompression(&mut rng, "zip"); + test_compression_and_decompression(&mut rng, "zip.gz"); + test_compression_and_decompression(&mut rng, "zip.bz"); + test_compression_and_decompression(&mut rng, "zip.bz2"); + test_compression_and_decompression(&mut rng, "zip.xz"); + test_compression_and_decompression(&mut rng, "zip.lz"); + test_compression_and_decompression(&mut rng, "zip.lzma"); + // test_compression_and_decompression(&mut rng, "zip.zip"); } type FileContent = Vec; -fn test_compression_and_decompression(format: &str) { +fn test_compression_and_decompression(rng: &mut impl RngCore, format: &str) { // System temporary directory depends on the platform // For linux it is /tmp let system_tmp = env::temp_dir(); @@ -43,10 +44,10 @@ fn test_compression_and_decompression(format: &str) { let testing_dir = testing_dir.path(); // Quantity of compressed files vary from 1 to 10 - let quantity_of_files = random::() % 10 + 1; + let quantity_of_files = rng.next_u32() % 10 + 1; let contents_of_files: Vec = - (0..quantity_of_files).map(|_| generate_random_file_content()).collect(); + (0..quantity_of_files).map(|_| generate_random_file_content(rng)).collect(); let mut file_paths = create_files(&testing_dir, &contents_of_files); let archive_path = compress_files(&testing_dir, &file_paths, &format); @@ -63,9 +64,11 @@ fn test_compression_and_decompression(format: &str) { } // Crate file contents from 1024 up to 8192 random bytes -fn generate_random_file_content() -> FileContent { - let quantity = 1024 + random::() % (8192 - 1024); - (0..quantity).map(|_| random()).collect() +fn generate_random_file_content(rng: &mut impl RngCore) -> FileContent { + let quantity = 1024 + rng.next_u32() % (8192 - 1024); + let mut vec = vec![0; quantity as usize]; + rng.fill_bytes(&mut vec); + vec } // Create files using the indexes as file names (eg. 0, 1, 2 and 3) From 1e11a99991064cb8326d42c380374e122029179b Mon Sep 17 00:00:00 2001 From: Fabricio Dematte Date: Mon, 17 May 2021 21:56:22 -0300 Subject: [PATCH 26/37] Add unknown short flag test --- oof/src/lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/oof/src/lib.rs b/oof/src/lib.rs index 662186791..2158ae232 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -239,6 +239,19 @@ mod tests { assert!(!flags.is_present("output_file")); } + #[test] + fn test_unknown_short_flag() { + let flags_info = [ + ArgFlag::long("output_file").short('o'), + Flag::long("verbose").short('v'), + Flag::long("help").short('h'), + ]; + + let args = gen_args("ouch a.zip -s b.tar.gz"); + let result = filter_flags(args, &flags_info).unwrap_err(); + assert!(matches!(result, OofError::UnknownShortFlag('s'))); + } + #[test] fn test_pop_subcommand() { let subcommands = &["commit", "add", "push", "remote"]; From dbb329344a43f13f3efc2530bbc27dc2f641569e Mon Sep 17 00:00:00 2001 From: Fabricio Dematte Date: Tue, 18 May 2021 20:53:35 -0300 Subject: [PATCH 27/37] add pre test setup function --- oof/src/lib.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/oof/src/lib.rs b/oof/src/lib.rs index 2158ae232..53aed704a 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -220,6 +220,25 @@ mod tests { args.map(OsString::from).collect() } + fn setup_args_scenario(arg_str: &str) -> Result<(Vec, Flags), OofError> { + let flags_info = [ + ArgFlag::long("output_file").short('o'), + Flag::long("verbose").short('v'), + Flag::long("help").short('h'), + ]; + + let args = gen_args(arg_str); + filter_flags(args, &flags_info) + } + + #[test] + fn test_unknown_short_flag() { + let result = setup_args_scenario("ouch a.zip -s b.tar.gz c.tar").unwrap_err(); + assert!(matches!(result, OofError::UnknownShortFlag('s'))); + let result = setup_args_scenario("ouch a.zip --foobar b.tar.gz c.tar").unwrap_err(); + // TODO: assert `UnknownLongFlag` error + } + // asdasdsa #[test] fn test_filter_flags() { @@ -239,19 +258,6 @@ mod tests { assert!(!flags.is_present("output_file")); } - #[test] - fn test_unknown_short_flag() { - let flags_info = [ - ArgFlag::long("output_file").short('o'), - Flag::long("verbose").short('v'), - Flag::long("help").short('h'), - ]; - - let args = gen_args("ouch a.zip -s b.tar.gz"); - let result = filter_flags(args, &flags_info).unwrap_err(); - assert!(matches!(result, OofError::UnknownShortFlag('s'))); - } - #[test] fn test_pop_subcommand() { let subcommands = &["commit", "add", "push", "remote"]; From 5e7ee4f959691e20149ab7422cfcb5404ea4e632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Wed, 19 May 2021 12:18:05 -0300 Subject: [PATCH 28/37] Removing lifetime from OofError --- oof/src/error.rs | 10 +++++----- oof/src/lib.rs | 20 +++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/oof/src/error.rs b/oof/src/error.rs index 306f17629..3da041534 100644 --- a/oof/src/error.rs +++ b/oof/src/error.rs @@ -3,7 +3,7 @@ use std::{error, ffi::OsString, fmt}; use crate::Flag; #[derive(Debug)] -pub enum OofError<'t> { +pub enum OofError { FlagValueConflict { flag: Flag, previous_value: OsString, @@ -15,17 +15,17 @@ pub enum OofError<'t> { UnknownShortFlag(char), UnknownLongFlag(String), MisplacedShortArgFlagError(char), - MissingValueToFlag(&'t Flag), - DuplicatedFlag(&'t Flag), + MissingValueToFlag(Flag), + DuplicatedFlag(Flag), } -impl<'t> error::Error for OofError<'t> { +impl error::Error for OofError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { None } } -impl<'t> fmt::Display for OofError<'t> { +impl fmt::Display for OofError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO: implement proper debug messages match self { diff --git a/oof/src/lib.rs b/oof/src/lib.rs index 53aed704a..c03fe5b88 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -131,7 +131,7 @@ pub fn filter_flags( let is_last_letter = i == letters.len() - 1; let flag_info = - short_flags_info.get(&letter).ok_or(OofError::UnknownShortFlag(letter))?; + *short_flags_info.get(&letter).ok_or(OofError::UnknownShortFlag(letter))?; if !is_last_letter && flag_info.takes_value { return Err(OofError::MisplacedShortArgFlagError(letter)); @@ -145,19 +145,20 @@ pub fn filter_flags( if flag_info.takes_value { // If it was already inserted if result_flags.argument_flags.contains_key(flag_name) { - return Err(OofError::DuplicatedFlag(flag_info)); + return Err(OofError::DuplicatedFlag(flag_info.clone())); } // pop the next one - let flag_argument = - iter.next().ok_or(OofError::MissingValueToFlag(flag_info))?; + let flag_argument = iter + .next() + .ok_or_else(|| OofError::MissingValueToFlag(flag_info.clone()))?; // Otherwise, insert it. result_flags.argument_flags.insert(flag_name, flag_argument); } else { // If it was already inserted if result_flags.boolean_flags.contains(flag_name) { - return Err(OofError::DuplicatedFlag(flag_info)); + return Err(OofError::DuplicatedFlag(flag_info.clone())); } // Otherwise, insert it result_flags.boolean_flags.insert(flag_name); @@ -168,7 +169,7 @@ pub fn filter_flags( if let FlagType::Long = flag_type { let flag = trim_double_hyphen(flag); - let flag_info = long_flags_info + let flag_info = *long_flags_info .get(flag) .ok_or_else(|| OofError::UnknownLongFlag(String::from(flag)))?; @@ -177,15 +178,16 @@ pub fn filter_flags( if flag_info.takes_value { // If it was already inserted if result_flags.argument_flags.contains_key(&flag_name) { - return Err(OofError::DuplicatedFlag(flag_info)); + return Err(OofError::DuplicatedFlag(flag_info.clone())); } - let flag_argument = iter.next().ok_or(OofError::MissingValueToFlag(flag_info))?; + let flag_argument = + iter.next().ok_or_else(|| OofError::MissingValueToFlag(flag_info.clone()))?; result_flags.argument_flags.insert(flag_name, flag_argument); } else { // If it was already inserted if result_flags.boolean_flags.contains(&flag_name) { - return Err(OofError::DuplicatedFlag(flag_info)); + return Err(OofError::DuplicatedFlag(flag_info.clone())); } // Otherwise, insert it result_flags.boolean_flags.insert(&flag_name); From f203b80eb882761f734eac04ccbd25ff3b02c147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Wed, 19 May 2021 12:28:49 -0300 Subject: [PATCH 29/37] Fixing ouch OofError convertion --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 83657e1b5..a64ff91cb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -120,7 +120,7 @@ impl From for Error { } } -impl<'t> From> for Error { +impl From for Error { fn from(err: oof::OofError) -> Self { // To avoid entering a lifetime hell, we'll just print the Oof error here // and skip saving it into a variant of Self From c057d9c682433b73bfa1a4874b3c1856036a4274 Mon Sep 17 00:00:00 2001 From: Fabricio Dematte Date: Wed, 19 May 2021 21:35:50 -0300 Subject: [PATCH 30/37] add further flag testing to oof crate --- oof/src/lib.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/oof/src/lib.rs b/oof/src/lib.rs index c03fe5b88..0c1e665cb 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -234,11 +234,29 @@ mod tests { } #[test] - fn test_unknown_short_flag() { + fn test_unknown_flags() { let result = setup_args_scenario("ouch a.zip -s b.tar.gz c.tar").unwrap_err(); assert!(matches!(result, OofError::UnknownShortFlag('s'))); let result = setup_args_scenario("ouch a.zip --foobar b.tar.gz c.tar").unwrap_err(); - // TODO: assert `UnknownLongFlag` error + // TODO: Try removing the `_` from `_foobar` + let _foobar = "foobar".to_string(); + assert!(matches!(result, OofError::UnknownLongFlag(_foobar))); + } + + #[test] + fn test_incomplete_flags() { + // TODO: Try removing the `_` from `_incomplete_flag` + let _incomplete_flag = Flag::long("output_file").short('o'); + let result = setup_args_scenario("ouch a.zip b.tar.gz c.tar -o").unwrap_err(); + assert!(matches!(result, OofError::MissingValueToFlag(_incomplete_flag))); + } + + #[test] + fn test_duplicated_flags() { + // TODO: Try removing the `_` from `_duplicated_flag` + let _duplicated_flag = Flag::long("output_file").short('o'); + let result = setup_args_scenario("ouch a.zip b.tar.gz c.tar -o -o -o").unwrap_err(); + assert!(matches!(result, OofError::DuplicatedFlag(_duplicated_flag))); } // asdasdsa From 67bd239acb3cbf2c9de99b0449e90674fe98e3bf Mon Sep 17 00:00:00 2001 From: Fabricio Dematte Date: Fri, 21 May 2021 08:06:17 -0300 Subject: [PATCH 31/37] fix test asserts to properly match error variants. add misplaced flag test --- oof/src/lib.rs | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/oof/src/lib.rs b/oof/src/lib.rs index 0c1e665cb..deafff481 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -237,26 +237,54 @@ mod tests { fn test_unknown_flags() { let result = setup_args_scenario("ouch a.zip -s b.tar.gz c.tar").unwrap_err(); assert!(matches!(result, OofError::UnknownShortFlag('s'))); + + let unknown_long_flag = "foobar".to_string(); let result = setup_args_scenario("ouch a.zip --foobar b.tar.gz c.tar").unwrap_err(); - // TODO: Try removing the `_` from `_foobar` - let _foobar = "foobar".to_string(); - assert!(matches!(result, OofError::UnknownLongFlag(_foobar))); + + assert!(match result { + OofError::UnknownLongFlag(flag) if flag == unknown_long_flag => true, + _ => false, + }) } #[test] fn test_incomplete_flags() { - // TODO: Try removing the `_` from `_incomplete_flag` - let _incomplete_flag = Flag::long("output_file").short('o'); + let incomplete_flag = ArgFlag::long("output_file").short('o'); let result = setup_args_scenario("ouch a.zip b.tar.gz c.tar -o").unwrap_err(); - assert!(matches!(result, OofError::MissingValueToFlag(_incomplete_flag))); + + assert!(match result { + OofError::MissingValueToFlag(flag) if flag == incomplete_flag => true, + _ => false, + }) } #[test] fn test_duplicated_flags() { - // TODO: Try removing the `_` from `_duplicated_flag` - let _duplicated_flag = Flag::long("output_file").short('o'); + let duplicated_flag = ArgFlag::long("output_file").short('o'); let result = setup_args_scenario("ouch a.zip b.tar.gz c.tar -o -o -o").unwrap_err(); - assert!(matches!(result, OofError::DuplicatedFlag(_duplicated_flag))); + + assert!(match result { + OofError::DuplicatedFlag(flag) if flag == duplicated_flag => true, + _ => false, + }) + } + + #[test] + fn test_misplaced_flag() { + let misplaced_flag = ArgFlag::long("output_file").short('o'); + let result = setup_args_scenario("ouch -ov a.zip b.tar.gz c.tar").unwrap_err(); + + assert!(match result { + OofError::MisplacedShortArgFlagError(flag) if flag == misplaced_flag.short.unwrap() => + true, + _ => false, + }) + } + + #[test] + fn test_invalid_unicode_flag() { + // let invalid_unicode_flag = char::from(); + // TODO: how to store invalid unicode? } // asdasdsa From 1059ba03ec3674995dba735b8140abe2fe04d53d Mon Sep 17 00:00:00 2001 From: Fabricio Dematte Date: Sun, 23 May 2021 22:04:17 -0300 Subject: [PATCH 32/37] test every OofError variant done --- oof/src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/oof/src/lib.rs b/oof/src/lib.rs index deafff481..78632f950 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -215,6 +215,8 @@ where #[cfg(test)] mod tests { + use std::os::unix::prelude::OsStringExt; + use crate::*; fn gen_args(text: &str) -> Vec { @@ -283,8 +285,14 @@ mod tests { #[test] fn test_invalid_unicode_flag() { - // let invalid_unicode_flag = char::from(); - // TODO: how to store invalid unicode? + // `invalid_unicode_flag` has to contain a leading hyphen to be considered a flag. + let invalid_unicode_flag = OsString::from_vec(vec![45, 0, 0, 0, 255, 255, 255, 255]); + let result = filter_flags(vec![invalid_unicode_flag.clone()], &[]).unwrap_err(); + + assert!(match result { + OofError::InvalidUnicode(flag) if flag == invalid_unicode_flag => true, + _ => false, + }) } // asdasdsa From 045e1d911cc2ca3bf7df74deafc56110f73aeb22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Wed, 26 May 2021 02:48:14 -0300 Subject: [PATCH 33/37] Added installation script Fixes #37 --- install.sh | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 install.sh diff --git a/install.sh b/install.sh new file mode 100644 index 000000000..a8a2ab1e3 --- /dev/null +++ b/install.sh @@ -0,0 +1,78 @@ +#! /usr/bin/sh + +# Needs to be updated each version bump +VERSION="0.1.4" + +DOWNLOAD_LOCATION="/tmp/ouch" +INSTALLATION_LOCATION="/usr/bin/ouch" +REPO_URL="https://github.com/vrmiguel/ouch" + +abort() { + echo "error occurred, aborting." ; exit 1 +} + +# Panicks script if anything fails +set -e + +echo "Ouch v$VERSION." + +printf "Detected system: " +# System detection from https://stackoverflow.com/a/27776822/9982477 +# Go there to see a full table of what `uname -s` might output +case "$(uname -s)" in + Darwin) + system_suffix="-macOS" + echo "Mac OS X." + ;; + + Linux) + echo "Linux." + system_suffix="-ubuntu-18.04-glibc" + ;; + + CYGWIN*|MINGW32*|MSYS*|MINGW*) + echo "Windows." + system_suffix=".exe" + ;; + + *) + echo "ERROR." + echo "This script only works for installing on Linux, Mac OS and Windows." + echo "We found '$(uname -s)' instead." + echo "" + echo "To install 'ouch' you can opt for other installation method" + echo "listed at $REPO_URL" + echo "" + echo "If you think this is an error, please open an issue" + exit 1 + ;; +esac + +binary_url="https://github.com/vrmiguel/ouch/releases/download/${VERSION}/ouch${system_suffix}" + +echo "" + +if [ -f "$DOWNLOAD_LOCATION" ]; then + echo "Reusing downloaded binary at '$DOWNLOAD_LOCATION'." +else + echo "Downloading binary to '$DOWNLOAD_LOCATION' with curl." + echo "From $binary_url" + curl -fSL $binary_url -o $DOWNLOAD_LOCATION +fi + +echo "" + +if [ "$USER" = "root" ]; then + echo "Detected root user, trying to copy $DOWNLOAD_LOCATION to $INSTALLATION_LOCATION." + cp $DOWNLOAD_LOCATION $INSTALLATION_LOCATION || abort +else + echo "Asking for \"sudo\" permissions to finish installation." + echo "Permission is needed to copy '$DOWNLOAD_LOCATION' to '$INSTALLATION_LOCATION'" + + sudo cp $DOWNLOAD_LOCATION $INSTALLATION_LOCATION || abort +fi + +echo "" + +echo "Successfully installed!" +echo "See $REPO_URL for usage instructions." From 22f13552f7113f3a17b9ddd622f3692c5a516e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20Bezerra?= Date: Wed, 26 May 2021 02:54:50 -0300 Subject: [PATCH 34/37] Updating installation instructions And minor markdown details. --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9a43ccf07..467fa132f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ To decompress any number of files, just supply them to `ouch`. Use the `-o, --output` flag to redirect the output of decompressions to a folder. -```bash +```sh # Decompress `a.zip` ouch a.zip @@ -39,7 +39,7 @@ You can also use the `c` alias for this subcommand. The compression format employed will be defined according to the output file's extension. -```bash +```sh # Compress four files into `archive.zip` ouch compress a b c d archive.zip @@ -80,8 +80,8 @@ Note: .tar.*¹: .tar.gz, .tar.bz, .tar.bz2, .tar.xz, .tar.lz, .tar.lzma, .tar.zi ### Getting a pre-compiled binary -```bash -curl -s https://raw.githubusercontent.com/vrmiguel/ouch/master/install.sh | bash +```sh +curl -s https://raw.githubusercontent.com/vrmiguel/ouch/master/install.sh | sh ``` ### Building @@ -90,7 +90,7 @@ A recent [Rust](rust-lang.org) toolchain is needed to build `ouch`. You can inst Once [Cargo](https://doc.rust-lang.org/cargo/) is installed, run: -```bash +```sh cargo install ouch # or git clone https://github.com/vrmiguel/ouch @@ -116,4 +116,3 @@ One must be logged into GitHub to access build artifacts. ## Limitations `ouch` does encoding and decoding in-memory, so decompressing very large files with it is not advisable. - From 5a2028b117e91cfb722ac588b5d818f66d90c0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Wed, 26 May 2021 12:58:52 -0300 Subject: [PATCH 35/37] Simplify oof testing asserts --- oof/src/lib.rs | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/oof/src/lib.rs b/oof/src/lib.rs index 78632f950..9505fe51c 100644 --- a/oof/src/lib.rs +++ b/oof/src/lib.rs @@ -238,15 +238,12 @@ mod tests { #[test] fn test_unknown_flags() { let result = setup_args_scenario("ouch a.zip -s b.tar.gz c.tar").unwrap_err(); - assert!(matches!(result, OofError::UnknownShortFlag('s'))); + assert!(matches!(result, OofError::UnknownShortFlag(flag) if flag == 's')); let unknown_long_flag = "foobar".to_string(); let result = setup_args_scenario("ouch a.zip --foobar b.tar.gz c.tar").unwrap_err(); - assert!(match result { - OofError::UnknownLongFlag(flag) if flag == unknown_long_flag => true, - _ => false, - }) + assert!(matches!(result, OofError::UnknownLongFlag(flag) if flag == unknown_long_flag)); } #[test] @@ -254,10 +251,7 @@ mod tests { let incomplete_flag = ArgFlag::long("output_file").short('o'); let result = setup_args_scenario("ouch a.zip b.tar.gz c.tar -o").unwrap_err(); - assert!(match result { - OofError::MissingValueToFlag(flag) if flag == incomplete_flag => true, - _ => false, - }) + assert!(matches!(result, OofError::MissingValueToFlag(flag) if flag == incomplete_flag)); } #[test] @@ -265,10 +259,7 @@ mod tests { let duplicated_flag = ArgFlag::long("output_file").short('o'); let result = setup_args_scenario("ouch a.zip b.tar.gz c.tar -o -o -o").unwrap_err(); - assert!(match result { - OofError::DuplicatedFlag(flag) if flag == duplicated_flag => true, - _ => false, - }) + assert!(matches!(result, OofError::DuplicatedFlag(flag) if flag == duplicated_flag)); } #[test] @@ -276,11 +267,9 @@ mod tests { let misplaced_flag = ArgFlag::long("output_file").short('o'); let result = setup_args_scenario("ouch -ov a.zip b.tar.gz c.tar").unwrap_err(); - assert!(match result { - OofError::MisplacedShortArgFlagError(flag) if flag == misplaced_flag.short.unwrap() => - true, - _ => false, - }) + assert!( + matches!(result, OofError::MisplacedShortArgFlagError(flag) if flag == misplaced_flag.short.unwrap()) + ); } #[test] @@ -289,10 +278,7 @@ mod tests { let invalid_unicode_flag = OsString::from_vec(vec![45, 0, 0, 0, 255, 255, 255, 255]); let result = filter_flags(vec![invalid_unicode_flag.clone()], &[]).unwrap_err(); - assert!(match result { - OofError::InvalidUnicode(flag) if flag == invalid_unicode_flag => true, - _ => false, - }) + assert!(matches!(result, OofError::InvalidUnicode(flag) if flag == invalid_unicode_flag)); } // asdasdsa From 57a3d231eb43a97c4461560fdf4814ffed47976b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Wed, 26 May 2021 21:54:50 -0300 Subject: [PATCH 36/37] Turning `oof` subcrate into a module --- Cargo.toml | 9 --------- oof/Cargo.toml | 9 --------- src/cli.rs | 3 ++- src/commands.rs | 2 +- src/decompressors/decompressor.rs | 2 +- src/decompressors/tar.rs | 2 +- src/decompressors/to_memory.rs | 2 +- src/decompressors/zip.rs | 2 +- src/error.rs | 2 +- src/lib.rs | 1 + {oof/src => src/oof}/error.rs | 2 +- {oof/src => src/oof}/flags.rs | 0 oof/src/lib.rs => src/oof/mod.rs | 2 +- {oof/src => src/oof}/util.rs | 0 src/utils.rs | 2 +- tests/compress_and_decompress.rs | 2 +- 16 files changed, 13 insertions(+), 29 deletions(-) delete mode 100644 oof/Cargo.toml rename {oof/src => src/oof}/error.rs (99%) rename {oof/src => src/oof}/flags.rs (100%) rename oof/src/lib.rs => src/oof/mod.rs (99%) rename {oof/src => src/oof}/util.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 2be213b2d..e3a479de9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,6 @@ tar = "0.4.33" xz2 = "0.1.6" zip = "0.5.11" -# Dependency from workspace -oof = { path = "./oof" } - [dev-dependencies] tempdir = "0.3.7" rand = { version = "0.8.3", default-features = false, features = ["small_rng", "std"] } @@ -32,9 +29,3 @@ rand = { version = "0.8.3", default-features = false, features = ["small_rng", " lto = true codegen-units = 1 opt-level = 3 - -[workspace] -members = [ - ".", - "oof", -] diff --git a/oof/Cargo.toml b/oof/Cargo.toml deleted file mode 100644 index c134bc3a9..000000000 --- a/oof/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "oof" -version = "0.1.0" -authors = ["João M. Bezerra "] -edition = "2018" -description = "Ouch's argparsing library" -repository = "https://github.com/vrmiguel/ouch" - -[dependencies] diff --git a/src/cli.rs b/src/cli.rs index b79d58866..05d8a4a69 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,9 +5,10 @@ use std::{ vec::Vec, }; -use oof::{arg_flag, flag}; use strsim::normalized_damerau_levenshtein; +use crate::{arg_flag, flag, oof}; + #[derive(PartialEq, Eq, Debug)] pub enum Command { /// Files to be compressed diff --git a/src/commands.rs b/src/commands.rs index ee36c60c6..b39ac7ca2 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -19,7 +19,7 @@ use crate::{ dialogs::Confirmation, extension::{CompressionFormat, Extension}, file::File, - utils, + oof, utils, }; pub fn run(command: Command, flags: &oof::Flags) -> crate::Result<()> { diff --git a/src/decompressors/decompressor.rs b/src/decompressors/decompressor.rs index d14bd69e0..f62bb23b1 100644 --- a/src/decompressors/decompressor.rs +++ b/src/decompressors/decompressor.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::file::File; +use crate::{file::File, oof}; pub enum DecompressionResult { FilesUnpacked(Vec), diff --git a/src/decompressors/tar.rs b/src/decompressors/tar.rs index fe2cff7f5..5298f8244 100644 --- a/src/decompressors/tar.rs +++ b/src/decompressors/tar.rs @@ -8,7 +8,7 @@ use tar::{self, Archive}; use utils::colors; use super::decompressor::{DecompressionResult, Decompressor}; -use crate::{dialogs::Confirmation, file::File, utils}; +use crate::{dialogs::Confirmation, file::File, oof, utils}; #[derive(Debug)] pub struct TarDecompressor; diff --git a/src/decompressors/to_memory.rs b/src/decompressors/to_memory.rs index 50105a5b7..9ee40838c 100644 --- a/src/decompressors/to_memory.rs +++ b/src/decompressors/to_memory.rs @@ -6,7 +6,7 @@ use std::{ use utils::colors; use super::decompressor::{DecompressionResult, Decompressor}; -use crate::{extension::CompressionFormat, file::File, utils}; +use crate::{extension::CompressionFormat, file::File, oof, utils}; struct DecompressorToMemory; pub struct GzipDecompressor; diff --git a/src/decompressors/zip.rs b/src/decompressors/zip.rs index d31115cbc..d9db14c86 100644 --- a/src/decompressors/zip.rs +++ b/src/decompressors/zip.rs @@ -8,7 +8,7 @@ use utils::colors; use zip::{self, read::ZipFile, ZipArchive}; use super::decompressor::{DecompressionResult, Decompressor}; -use crate::{dialogs::Confirmation, file::File, utils}; +use crate::{dialogs::Confirmation, file::File, oof, utils}; #[cfg(unix)] fn __unix_set_permissions(file_path: &Path, file: &ZipFile) { diff --git a/src/error.rs b/src/error.rs index a64ff91cb..ab0d97eca 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ use std::{fmt, path::PathBuf}; -use crate::utils::colors; +use crate::{oof, utils::colors}; pub enum Error { UnknownExtensionError(String), diff --git a/src/lib.rs b/src/lib.rs index c22759886..bd0f46ad6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ // Public modules pub mod cli; pub mod commands; +pub mod oof; // Private modules mod compressors; diff --git a/oof/src/error.rs b/src/oof/error.rs similarity index 99% rename from oof/src/error.rs rename to src/oof/error.rs index 3da041534..a5c1161a9 100644 --- a/oof/src/error.rs +++ b/src/oof/error.rs @@ -1,6 +1,6 @@ use std::{error, ffi::OsString, fmt}; -use crate::Flag; +use super::Flag; #[derive(Debug)] pub enum OofError { diff --git a/oof/src/flags.rs b/src/oof/flags.rs similarity index 100% rename from oof/src/flags.rs rename to src/oof/flags.rs diff --git a/oof/src/lib.rs b/src/oof/mod.rs similarity index 99% rename from oof/src/lib.rs rename to src/oof/mod.rs index 9505fe51c..cbf33469f 100644 --- a/oof/src/lib.rs +++ b/src/oof/mod.rs @@ -217,7 +217,7 @@ where mod tests { use std::os::unix::prelude::OsStringExt; - use crate::*; + use super::*; fn gen_args(text: &str) -> Vec { let args = text.split_whitespace(); diff --git a/oof/src/util.rs b/src/oof/util.rs similarity index 100% rename from oof/src/util.rs rename to src/oof/util.rs diff --git a/src/utils.rs b/src/utils.rs index d9895439b..fb9a53f8e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,7 +5,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::{dialogs::Confirmation, extension::CompressionFormat, file::File}; +use crate::{dialogs::Confirmation, extension::CompressionFormat, file::File, oof}; #[macro_export] #[cfg(debug_assertions)] diff --git a/tests/compress_and_decompress.rs b/tests/compress_and_decompress.rs index 98ab149f9..61f84a376 100644 --- a/tests/compress_and_decompress.rs +++ b/tests/compress_and_decompress.rs @@ -4,7 +4,7 @@ use std::{ path::{Path, PathBuf}, }; -use ouch::{cli::Command, commands::run}; +use ouch::{cli::Command, commands::run, oof}; use rand::{rngs::SmallRng, RngCore, SeedableRng}; use tempdir::TempDir; From 77cfb48056dc0210a060c187c59c90ad7ef0f2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Rodrigues=20Miguel?= Date: Wed, 26 May 2021 22:05:15 -0300 Subject: [PATCH 37/37] Release 0.1.5 --- Cargo.toml | 2 +- install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e3a479de9..2eaa1f16e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ouch" -version = "0.1.4" +version = "0.1.5" authors = ["Vinícius Rodrigues Miguel ", "João M. Bezerra "] edition = "2018" readme = "README.md" diff --git a/install.sh b/install.sh index a8a2ab1e3..e0bad688d 100644 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #! /usr/bin/sh # Needs to be updated each version bump -VERSION="0.1.4" +VERSION="0.1.5" DOWNLOAD_LOCATION="/tmp/ouch" INSTALLATION_LOCATION="/usr/bin/ouch"