Start replacing clap with oof

This commit is contained in:
João M. Bezerra 2021-04-04 03:17:55 -03:00
parent 535d4fcc93
commit 0c9131c307
8 changed files with 309 additions and 219 deletions

View File

@ -22,6 +22,9 @@ bzip2 = "0.4.2"
flate2 = "1.0.14"
zip = "0.5.11"
# Dependency from workspace
oof = { path = "./oof" }
[profile.release]
lto = true
codegen-units = 1

View File

@ -8,9 +8,9 @@ use std::{
/// ArgFlag::long(), is actually a Flag::long(), but sets a internal attribute.
///
/// Examples in here pls
#[derive(Debug)]
pub struct ArgFlag;
#[allow(clippy::new_ret_no_self)]
impl ArgFlag {
pub fn long(name: &'static str) -> Flag {
Flag {
@ -44,7 +44,7 @@ impl Flag {
}
}
#[derive(Default, Debug)]
#[derive(Default, PartialEq, Eq, Debug)]
pub struct Flags {
pub boolean_flags: BTreeSet<&'static str>,
pub argument_flags: BTreeMap<&'static str, OsString>,
@ -70,6 +70,7 @@ impl Flags {
}
}
#[derive(Debug)]
pub enum FlagType {
None,
Short,

View File

@ -173,9 +173,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).unwrap_or_else(|| {
panic!("User error: Unexpected/UNKNOWN flag '{}'", flag);
});
let flag_name = flag_info.long;
@ -275,26 +275,26 @@ mod tests {
assert_eq!(args[0], "a");
}
#[test]
fn test_flag_info_macros() {
let flags_info = [
arg_flag!('o', "output_file"),
arg_flag!("delay"),
flag!('v', "verbose"),
flag!('h', "help"),
flag!("version"),
];
// #[test]
// fn test_flag_info_macros() {
// let flags_info = [
// arg_flag!('o', "output_file"),
// arg_flag!("delay"),
// flag!('v', "verbose"),
// flag!('h', "help"),
// flag!("version"),
// ];
let expected = [
ArgFlag::long("output_file").short('o'),
ArgFlag::long("delay"),
Flag::long("verbose").short('v'),
Flag::long("help").short('h'),
Flag::long("version"),
];
// let expected = [
// ArgFlag::long("output_file").short('o'),
// ArgFlag::long("delay"),
// Flag::long("verbose").short('v'),
// Flag::long("help").short('h'),
// Flag::long("version"),
// ];
assert_eq!(flags_info, expected);
}
// assert_eq!(flags_info, expected);
// }
#[test]
// TODO: remove should_panic and use proper error handling inside of filter_args
@ -349,23 +349,23 @@ mod tests {
/// Create a flag with long flag (?).
#[macro_export]
macro_rules! flag {
($short:expr, $long:expr) => {
Flag::long($long).short($short)
};
($short:expr, $long:expr) => {{
oof::Flag::long($long).short($short)
}};
($long:expr) => {
Flag::long($long)
};
($long:expr) => {{
oof::Flag::long($long)
}};
}
/// Create a flag with long flag (?), receives argument (?).
#[macro_export]
macro_rules! arg_flag {
($short:expr, $long:expr) => {
ArgFlag::long($long).short($short)
};
($short:expr, $long:expr) => {{
oof::ArgFlag::long($long).short($short)
}};
($long:expr) => {
ArgFlag::long($long)
};
($long:expr) => {{
oof::ArgFlag::long($long)
}};
}

View File

@ -8,6 +8,7 @@ pub fn trim_double_hyphen(flag_text: &str) -> &str {
/// Util function to skip the single leading short flag hyphen.
pub fn trim_single_hyphen(flag_text: &str) -> &str {
let mut chars = flag_text.chars();
chars.next(); // Skipping 1 char
chars.as_str()
}

View File

@ -1,212 +1,260 @@
use std::{
convert::TryFrom,
env,
ffi::OsString,
fs,
path::{Path, PathBuf},
vec::Vec,
};
use clap::{Arg, Values};
use colored::Colorize;
use oof::{arg_flag, flag};
use crate::{extension::Extension, file::File};
// use clap::{Arg, Values};
// use colored::Colorize;
// use crate::{extension::Extension, file::File};
use crate::file::File;
#[derive(PartialEq, Eq, Debug)]
pub enum CommandKind {
Compression(
/// Files to be compressed
Vec<PathBuf>,
),
Decompression(
/// Files to be decompressed and their extensions
Vec<File>,
),
pub enum Command {
/// Files to be compressed
Compress {
files: Vec<PathBuf>,
flags: oof::Flags,
},
/// Files to be decompressed and their extensions
Decompress {
files: Vec<PathBuf>,
output_folder: Option<PathBuf>,
flags: oof::Flags,
},
ShowHelp,
ShowVersion,
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub enum Flags {
// No flags supplied
None,
// Flag -y, --yes supplied
AlwaysYes,
// Flag -n, --no supplied
AlwaysNo,
}
// #[derive(PartialEq, Eq, Debug)]
// pub struct Command {
// pub kind: CommandKind,
// pub output: Option<File>,
// }
#[derive(PartialEq, Eq, Debug)]
pub struct Command {
pub kind: CommandKind,
pub output: Option<File>,
}
// // pub fn clap_app<'a, 'b>() -> clap::App<'a, 'b> {
// // clap::App::new("ouch")
// // .version("0.1.4")
// // .about("ouch is a unified compression & decompression utility")
// // .after_help(
// // "ouch infers what to based on the extensions of the input files and output file received.
// // Examples: `ouch -i movies.tar.gz classes.zip -o Videos/` in order to decompress files into a folder.
// // `ouch -i headers/ sources/ Makefile -o my-project.tar.gz`
// // `ouch -i image{1..50}.jpeg -o images.zip`
// // Please relate any issues or contribute at https://github.com/vrmiguel/ouch")
// // .author("Vinícius R. Miguel")
// // .help_message("Displays this message and exits")
// // .settings(&[
// // clap::AppSettings::ColoredHelp,
// // clap::AppSettings::ArgRequiredElseHelp,
// // ])
// // .arg(
// // Arg::with_name("input")
// // .required(true)
// // .multiple(true)
// // .long("input")
// // .short("i")
// // .help("The input files or directories.")
// // .takes_value(true),
// // )
// // .arg(
// // Arg::with_name("output")
// // // --output/-o not required when output can be inferred from the input files
// // .required(false)
// // .multiple(false)
// // .long("output")
// // .short("o")
// // .help("The output directory or compressed file.")
// // .takes_value(true),
// // )
// // .arg(
// // Arg::with_name("yes")
// // .required(false)
// // .multiple(false)
// // .long("yes")
// // .short("y")
// // .help("Says yes to all confirmation dialogs.")
// // .conflicts_with("no")
// // .takes_value(false),
// // )
// // .arg(
// // Arg::with_name("no")
// // .required(false)
// // .multiple(false)
// // .long("no")
// // .short("n")
// // .help("Says no to all confirmation dialogs.")
// // .conflicts_with("yes")
// // .takes_value(false),
// // )
// // }
pub fn clap_app<'a, 'b>() -> clap::App<'a, 'b> {
clap::App::new("ouch")
.version("0.1.4")
.about("ouch is a unified compression & decompression utility")
.after_help(
"ouch infers what to based on the extensions of the input files and output file received.
Examples: `ouch -i movies.tar.gz classes.zip -o Videos/` in order to decompress files into a folder.
`ouch -i headers/ sources/ Makefile -o my-project.tar.gz`
`ouch -i image{1..50}.jpeg -o images.zip`
Please relate any issues or contribute at https://github.com/vrmiguel/ouch")
.author("Vinícius R. Miguel")
.help_message("Displays this message and exits")
.settings(&[
clap::AppSettings::ColoredHelp,
clap::AppSettings::ArgRequiredElseHelp,
])
.arg(
Arg::with_name("input")
.required(true)
.multiple(true)
.long("input")
.short("i")
.help("The input files or directories.")
.takes_value(true),
)
.arg(
Arg::with_name("output")
// --output/-o not required when output can be inferred from the input files
.required(false)
.multiple(false)
.long("output")
.short("o")
.help("The output directory or compressed file.")
.takes_value(true),
)
.arg(
Arg::with_name("yes")
.required(false)
.multiple(false)
.long("yes")
.short("y")
.help("Says yes to all confirmation dialogs.")
.conflicts_with("no")
.takes_value(false),
)
.arg(
Arg::with_name("no")
.required(false)
.multiple(false)
.long("no")
.short("n")
.help("Says no to all confirmation dialogs.")
.conflicts_with("yes")
.takes_value(false),
)
}
// // pub fn get_matches() -> clap::ArgMatches<'static> {
// // clap_app().get_matches()
// // }
pub fn get_matches() -> clap::ArgMatches<'static> {
clap_app().get_matches()
}
// pub fn parse_matches(matches: clap::ArgMatches<'static>) -> {
// let flag = match (matches.is_present("yes"), matches.is_present("no")) {
// (true, true) => unreachable!(),
// (true, _) => Flags::AlwaysYes,
// (_, true) => Flags::AlwaysNo,
// (_, _) => Flags::None,
// };
pub fn parse_matches(matches: clap::ArgMatches<'static>) -> crate::Result<(Command, Flags)> {
let flag = match (matches.is_present("yes"), matches.is_present("no")) {
(true, true) => unreachable!(),
(true, _) => Flags::AlwaysYes,
(_, true) => Flags::AlwaysNo,
(_, _) => Flags::None,
};
// Ok((Command::try_from(matches)?, flag))
// }
Ok((Command::try_from(matches)?, flag))
}
// impl TryFrom<clap::ArgMatches<'static>> for Command {
// type Error = crate::Error;
impl TryFrom<clap::ArgMatches<'static>> for Command {
type Error = crate::Error;
// fn try_from(matches: clap::ArgMatches<'static>) -> crate::Result<Command> {
// let process_decompressible_input = |input_files: Values| {
// let input_files =
// input_files.map(|filename| (Path::new(filename), Extension::new(filename)));
fn try_from(matches: clap::ArgMatches<'static>) -> crate::Result<Command> {
let process_decompressible_input = |input_files: Values| {
let input_files =
input_files.map(|filename| (Path::new(filename), Extension::new(filename)));
// for file in input_files.clone() {
// match file {
// (filename, Ok(_)) => {
// let path = Path::new(filename);
// if !path.exists() {
// return Err(crate::Error::FileNotFound(filename.into()));
// }
// }
// (filename, Err(_)) => {
// return Err(crate::Error::InputsMustHaveBeenDecompressible(
// filename.into(),
// ));
// }
// }
// }
for file in input_files.clone() {
match file {
(filename, Ok(_)) => {
let path = Path::new(filename);
if !path.exists() {
return Err(crate::Error::FileNotFound(filename.into()));
}
}
(filename, Err(_)) => {
return Err(crate::Error::InputsMustHaveBeenDecompressible(
filename.into(),
));
}
}
}
// Ok(input_files
// .map(|(filename, extension)| {
// (fs::canonicalize(filename).unwrap(), extension.unwrap())
// })
// .map(File::from)
// .collect::<Vec<_>>())
// };
Ok(input_files
.map(|(filename, extension)| {
(fs::canonicalize(filename).unwrap(), extension.unwrap())
})
.map(File::from)
.collect::<Vec<_>>())
};
// // Possibilities:
// // * Case 1: output not supplied, therefore try to infer output by checking if all input files are decompressible
// // * Case 2: output supplied
// Possibilities:
// * Case 1: output not supplied, therefore try to infer output by checking if all input files are decompressible
// * Case 2: output supplied
// let output_was_supplied = matches.is_present("output");
let output_was_supplied = matches.is_present("output");
// let input_files = matches.values_of("input").unwrap(); // Safe to unwrap since input is an obligatory argument
let input_files = matches.values_of("input").unwrap(); // Safe to unwrap since input is an obligatory argument
// if output_was_supplied {
// let output_file = matches.value_of("output").unwrap(); // Safe unwrap since we've established that output was supplied
if output_was_supplied {
let output_file = matches.value_of("output").unwrap(); // Safe unwrap since we've established that output was supplied
// let output_file_extension = Extension::new(output_file);
let output_file_extension = Extension::new(output_file);
// let output_is_compressible = output_file_extension.is_ok();
// if output_is_compressible {
// // The supplied output is compressible, so we'll compress our inputs to it
let output_is_compressible = output_file_extension.is_ok();
if output_is_compressible {
// The supplied output is compressible, so we'll compress our inputs to it
// let canonical_paths = input_files.clone().map(Path::new).map(fs::canonicalize);
// for (filename, canonical_path) in input_files.zip(canonical_paths.clone()) {
// if let Err(err) = canonical_path {
// let path = PathBuf::from(filename);
// if !path.exists() {
// return Err(crate::Error::FileNotFound(path));
// }
let canonical_paths = input_files.clone().map(Path::new).map(fs::canonicalize);
for (filename, canonical_path) in input_files.zip(canonical_paths.clone()) {
if let Err(err) = canonical_path {
let path = PathBuf::from(filename);
if !path.exists() {
return Err(crate::Error::FileNotFound(path));
}
// eprintln!("{} {}", "[ERROR]".red(), err);
// return Err(crate::Error::IoError);
// }
// }
eprintln!("{} {}", "[ERROR]".red(), err);
return Err(crate::Error::IoError);
}
}
// let input_files = canonical_paths.map(Result::unwrap).collect();
let input_files = canonical_paths.map(Result::unwrap).collect();
// Ok(Command {
// kind: CommandKind::Compression(input_files),
// output: Some(File {
// path: output_file.into(),
// contents_in_memory: None,
// extension: Some(output_file_extension.unwrap()),
// }),
// })
// } else {
// // Output not supplied
// // Checking if input files are decompressible
Ok(Command {
kind: CommandKind::Compression(input_files),
output: Some(File {
path: output_file.into(),
contents_in_memory: None,
extension: Some(output_file_extension.unwrap()),
}),
})
} else {
// Output not supplied
// Checking if input files are decompressible
// let input_files = process_decompressible_input(input_files)?;
let input_files = process_decompressible_input(input_files)?;
// Ok(Command {
// kind: CommandKind::Decompression(input_files),
// output: Some(File {
// path: output_file.into(),
// contents_in_memory: None,
// extension: None,
// }),
// })
// }
// } else {
// // else: output file not supplied
// // Case 1: all input files are decompressible
// // Case 2: error
// let input_files = process_decompressible_input(input_files)?;
Ok(Command {
kind: CommandKind::Decompression(input_files),
output: Some(File {
path: output_file.into(),
contents_in_memory: None,
extension: None,
}),
})
}
} else {
// else: output file not supplied
// Case 1: all input files are decompressible
// Case 2: error
let input_files = process_decompressible_input(input_files)?;
// Ok(Command {
// kind: CommandKind::Decompression(input_files),
// output: None,
// })
// }
// }
// }
Ok(Command {
kind: CommandKind::Decompression(input_files),
output: None,
pub fn parse_args_and_flags() -> crate::Result<Command> {
let args: Vec<OsString> = env::args_os().skip(1).collect();
if oof::matches_any_arg(&args, &["--help", "-h"]) {
return Ok(Command::ShowHelp);
}
if oof::matches_any_arg(&args, &["--version"]) {
return Ok(Command::ShowHelp);
}
let subcommands = &["compress"];
let mut flags_info = vec![
flag!('y', "yes"),
flag!('n', "no"),
// flag!('v', "verbose"),
];
match oof::pop_subcommand(&mut args, subcommands) {
Some(&"compress") => {
let (args, flags) = oof::filter_flags(args, &flags_info)?;
let files = args.into_iter().map(PathBuf::from).collect();
todo!("Adicionar output_file, que é o files.pop() do fim");
Ok(Command::Compress { files, flags })
}
// Defaults to decompression when there is no subcommand
None => {
// Specific flag
flags_info.push(arg_flag!('o', "output_file"));
// Parse flags
let (args, flags) = oof::filter_flags(args, &flags_info)?;
let files = args.into_iter().map(PathBuf::from).collect();
let output_folder = flags.take_arg("output_folder").map(PathBuf::from);
Ok(Command::Decompress {
files,
output_folder,
flags,
})
}
_ => unreachable!("You should match each subcommand passed."),
}
}

View File

@ -18,7 +18,7 @@ pub enum Error {
InputsMustHaveBeenDecompressible(PathBuf),
InternalError,
CompressingRootFolder,
WalkdirError
WalkdirError,
}
pub type Result<T> = std::result::Result<T, Error>;
@ -48,7 +48,7 @@ impl fmt::Display for Error {
write!(f, "{} ", "[ERROR]".red())?;
write!(f, "file '{:?}' is not decompressible", file)
}
Error::WalkdirError => {
Error::WalkdirError => {
// Already printed in the From block
write!(f, "")
}
@ -61,8 +61,17 @@ impl fmt::Display for Error {
write!(f, "{} ", "[ERROR]".red())?;
let spacing = " ";
writeln!(f, "It seems you're trying to compress the root folder.")?;
writeln!(f, "{}This is unadvisable since ouch does compressions in-memory.", spacing)?;
write!(f, "{}Use a more appropriate tool for this, such as {}.", spacing, "rsync".green())
writeln!(
f,
"{}This is unadvisable since ouch does compressions in-memory.",
spacing
)?;
write!(
f,
"{}Use a more appropriate tool for this, such as {}.",
spacing,
"rsync".green()
)
}
Error::InternalError => {
write!(f, "{} ", "[ERROR]".red())?;
@ -108,3 +117,9 @@ impl From<walkdir::Error> for Error {
Self::WalkdirError
}
}
impl From<oof::OofError> for Error {
fn from(err: oof::OofError) -> Self {
todo!("We need to implement this properly");
}
}

View File

@ -3,7 +3,7 @@ use std::{ffi::OsStr, fs, io::Write, path::PathBuf};
use colored::Colorize;
use crate::{
cli::{Command, CommandKind, Flags},
cli::Command,
compressors::{
BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor,
ZipCompressor,
@ -230,8 +230,31 @@ impl Evaluator {
Ok(())
}
pub fn evaluate(command: Command, flags: Flags) -> crate::Result<()> {
let output = command.output.clone();
pub fn evaluate(command: Command) -> crate::Result<()> {
// Compress {
// files: Vec<PathBuf>,
// flags: oof::Flags,
// },
// /// Files to be decompressed and their extensions
// Decompress {
// files: Vec<PathBuf>,
// output_folder: Option<PathBuf>,
// flags: oof::Flags,
// },
// ShowHelp,
// ShowVersion,
match command {
Command::Compress { files, flags } => {}
Command::Decompress {
files,
output_folder,
flags,
} => {
// for file in files { decompress }
}
Command::ShowHelp => todo!(),
Command::ShowVersion => todo!(),
}
match command.kind {
CommandKind::Compression(files_to_compress) => {

View File

@ -20,7 +20,6 @@ fn main() {
}
fn run() -> crate::Result<()> {
let matches = cli::get_matches();
let (command, flags) = cli::parse_matches(matches)?;
Evaluator::evaluate(command, flags)
let command = cli::parse_args_and_flags()?;
Evaluator::evaluate(command)
}