8 use strsim::normalized_damerau_levenshtein;
9 use oof::{arg_flag, flag};
12 #[derive(PartialEq, Eq, Debug)]
14 /// Files to be compressed
17 compressed_output_path: PathBuf,
19 /// Files to be decompressed and their extensions
22 output_folder: Option<PathBuf>,
28 /// Calls parse_args_and_flags_from using std::env::args_os ( argv )
29 pub fn parse_args() -> crate::Result<ParsedArgs> {
30 let args = env::args_os().skip(1).collect();
34 pub struct ParsedArgs {
36 pub flags: oof::Flags,
40 /// check_for_typo checks if the first argument is
41 /// a typo for the compress subcommand.
42 /// Returns true if the arg is probably a typo or false otherwise.
43 fn is_typo<'a, P>(path: P) -> bool
47 if path.as_ref().exists() {
48 // If the file exists then we won't check for a typo
52 let path = path.as_ref().to_string_lossy();
53 // We'll consider it a typo if the word is somewhat 'close' to "compress"
54 normalized_damerau_levenshtein("compress", &path) > 0.625
57 fn canonicalize<'a, P>(path: P) -> crate::Result<PathBuf>
61 match std::fs::canonicalize(&path.as_ref()) {
62 Ok(abs_path) => Ok(abs_path),
64 if !path.as_ref().exists() {
65 Err(crate::Error::FileNotFound(PathBuf::from(path.as_ref())))
67 Err(crate::Error::IoError(io_err))
75 fn canonicalize_files<'a, P>(files: Vec<P>) -> crate::Result<Vec<PathBuf>>
79 files.into_iter().map(canonicalize).collect()
82 pub fn parse_args_from(mut args: Vec<OsString>) -> crate::Result<ParsedArgs> {
83 if oof::matches_any_arg(&args, &["--help", "-h"]) || args.is_empty() {
84 return Ok(ParsedArgs {
85 command: Command::ShowHelp,
86 flags: oof::Flags::default(),
90 if oof::matches_any_arg(&args, &["--version"]) {
91 return Ok(ParsedArgs {
92 command: Command::ShowVersion,
93 flags: oof::Flags::default(),
97 let subcommands = &["compress"];
99 let mut flags_info = vec![flag!('y', "yes"), flag!('n', "no")];
101 let parsed_args = match oof::pop_subcommand(&mut args, subcommands) {
102 Some(&"compress") => {
103 // `ouch compress` subcommand
104 let (args, flags) = oof::filter_flags(args, &flags_info)?;
105 let mut files: Vec<PathBuf> = args.into_iter().map(PathBuf::from).collect();
108 return Err(crate::Error::MissingArgumentsForCompression);
111 // Safety: we checked that args.len() >= 2
112 let compressed_output_path = files.pop().unwrap();
114 let files = canonicalize_files(files)?;
116 let command = Command::Compress {
118 compressed_output_path,
120 ParsedArgs { command, flags }
122 // Defaults to decompression when there is no subcommand
124 flags_info.push(arg_flag!('o', "output"));
126 let first_arg = args.first().unwrap();
127 if is_typo(first_arg) {
128 return Err(crate::Error::CompressionTypo);
134 let (args, mut flags) = oof::filter_flags(args, &flags_info)?;
136 let files = args.into_iter().map(canonicalize);
137 for file in files.clone() {
138 if let Err(err) = file {
142 let files = files.map(Result::unwrap).collect();
144 let output_folder = flags.take_arg("output").map(PathBuf::from);
146 // TODO: ensure all files are decompressible
148 let command = Command::Decompress {
152 ParsedArgs { command, flags }
154 _ => unreachable!("You should match each subcommand passed."),