Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checking for typos on the compression subcommand and switch panics to errors #21

Merged
merged 13 commits into from
Apr 8, 2021
Merged
9 changes: 4 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,18 @@ 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"
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" }

[target.'cfg(unix)'.dependencies]
termion = "1.5.6"
[profile.release]
lto = true
codegen-units = 1
Expand Down
20 changes: 17 additions & 3 deletions oof/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,29 @@ use std::{error, ffi::OsString, fmt};
use crate::Flag;

#[derive(Debug)]
pub enum OofError {
pub enum OofError<'t> {
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),
UnknownLongFlag(String),
MisplacedShortArgFlagError(char),
MissingValueToFlag(&'t Flag),
DuplicatedFlag(&'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 {
Expand All @@ -30,6 +38,12 @@ 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),
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),
}
}
}
9 changes: 9 additions & 0 deletions oof/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
56 changes: 24 additions & 32 deletions oof/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand All @@ -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(
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
}

Expand All @@ -145,23 +143,20 @@ 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
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(
OofError::MissingValueToFlag(flag_info)
)?;

// 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) {
panic!("User error: duplicated, found this flag TWICE!");
return Err(OofError::DuplicatedFlag(flag_info));
}
// Otherwise, insert it
result_flags.boolean_flags.insert(flag_name);
Expand All @@ -172,29 +167,26 @@ 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;

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(|| {
panic!(
"USer errror: argument flag `argument_flag` came at last, but it requires \
an argument"
)
});
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
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);
Expand Down
39 changes: 31 additions & 8 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ use std::{
vec::Vec,
};

use strsim::normalized_damerau_levenshtein;
use oof::{arg_flag, flag};


#[derive(PartialEq, Eq, Debug)]
pub enum Command {
/// Files to be compressed
Expand All @@ -23,12 +25,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<ParsedArgs> {
let args = env::args_os().skip(1).collect();
Expand All @@ -40,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<Path> + '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<PathBuf>
where
P: AsRef<Path> + 'a,
Expand All @@ -50,13 +64,14 @@ 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))
}
}
}
}



fn canonicalize_files<'a, P>(files: Vec<P>) -> crate::Result<Vec<PathBuf>>
where
P: AsRef<Path> + 'a,
Expand Down Expand Up @@ -85,6 +100,7 @@ pub fn parse_args_from(mut args: Vec<OsString>) -> crate::Result<ParsedArgs> {

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<PathBuf> = args.into_iter().map(PathBuf::from).collect();

Expand All @@ -106,6 +122,13 @@ pub fn parse_args_from(mut args: Vec<OsString>) -> crate::Result<ParsedArgs> {
// 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) {
return Err(crate::Error::CompressionTypo);
}
}


// Parse flags
let (args, mut flags) = oof::filter_flags(args, &flags_info)?;
Expand Down
14 changes: 8 additions & 6 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
path::{Path, PathBuf},
};

use colored::Colorize;
use utils::colors;

use crate::{
cli::Command,
Expand Down Expand Up @@ -88,8 +88,9 @@ fn get_decompressor(file: &File) -> crate::Result<(Option<BoxedDecompressor>, 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);
}
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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)
);
Expand Down
7 changes: 4 additions & 3 deletions src/compressors/bzip.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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)
);
Expand Down
7 changes: 4 additions & 3 deletions src/compressors/gzip.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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)
);
Expand Down
7 changes: 4 additions & 3 deletions src/compressors/lzma.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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)
);
Expand Down
Loading