Skip to content

Commit

Permalink
Merge pull request #21 from vrmiguel/dev
Browse files Browse the repository at this point in the history
Add checking for typos on the compression subcommand and switch panics to errors
  • Loading branch information
vrmiguel authored Apr 8, 2021
2 parents 95ab1e7 + c94b49f commit dedf7c0
Show file tree
Hide file tree
Showing 18 changed files with 190 additions and 131 deletions.
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

0 comments on commit dedf7c0

Please sign in to comment.