1 use std::{convert::TryFrom, fs, path::{Path, PathBuf}, vec::Vec};
3 use clap::{Arg, Values};
8 use crate::extension::Extension;
11 #[derive(PartialEq, Eq, Debug)]
12 pub enum CommandKind {
14 /// Files to be compressed
18 /// Files to be decompressed and their extensions
23 #[derive(PartialEq, Eq, Debug)]
25 pub kind: CommandKind,
26 pub output: Option<File>,
29 pub fn clap_app<'a, 'b>() -> clap::App<'a, 'b> {
30 clap::App::new("ouch")
32 .about("ouch is a unified compression & decompression utility")
34 "ouch infers what to based on the extensions of the input files and output file received.
35 Examples: `ouch -i movies.tar.gz classes.zip -o Videos/` in order to decompress files into a folder.
36 `ouch -i headers/ sources/ Makefile -o my-project.tar.gz`
37 `ouch -i image{1..50}.jpeg -o images.zip`
38 Please relate any issues or contribute at https://github.com/vrmiguel/ouch")
39 .author("VinÃcius R. Miguel")
40 .help_message("Displays this message and exits")
42 clap::AppSettings::ColoredHelp,
43 clap::AppSettings::ArgRequiredElseHelp,
46 Arg::with_name("input")
51 .help("The input files or directories.")
55 Arg::with_name("output")
56 // --output/-o not required when output can be inferred from the input files
61 .help("The output directory or compressed file.")
66 pub fn get_matches() -> clap::ArgMatches<'static> {
67 clap_app().get_matches()
70 impl TryFrom<clap::ArgMatches<'static>> for Command {
71 type Error = error::Error;
73 fn try_from(matches: clap::ArgMatches<'static>) -> error::OuchResult<Command> {
74 let process_decompressible_input = |input_files: Values| {
76 input_files.map(|filename| (Path::new(filename), Extension::new(filename)));
78 for file in input_files.clone() {
80 (filename, Ok(_)) => {
81 let path = Path::new(filename);
83 return Err(error::Error::FileNotFound(filename.into()))
86 (filename, Err(_)) => {
87 return Err(error::Error::InputsMustHaveBeenDecompressible(filename.into()));
93 .map(|(filename, extension)| (fs::canonicalize(filename).unwrap(), extension.unwrap()))
99 // * Case 1: output not supplied, therefore try to infer output by checking if all input files are decompressible
100 // * Case 2: output supplied
102 let output_was_supplied = matches.is_present("output");
104 let input_files = matches.values_of("input").unwrap(); // Safe to unwrap since input is an obligatory argument
106 if output_was_supplied {
107 let output_file = matches.value_of("output").unwrap(); // Safe unwrap since we've established that output was supplied
109 let output_file_extension = Extension::new(output_file);
111 let output_is_compressible = output_file_extension.is_ok();
112 if output_is_compressible {
113 // The supplied output is compressible, so we'll compress our inputs to it
115 let canonical_paths = input_files.clone().map(Path::new).map(fs::canonicalize);
116 for (filename, canonical_path) in input_files.zip(canonical_paths.clone()) {
117 if let Err(err) = canonical_path {
118 let path = PathBuf::from(filename);
120 return Err(Error::FileNotFound(path))
123 eprintln!("{} {}", "[ERROR]".red(), err);
124 return Err(Error::IOError);
128 let input_files = canonical_paths.map(Result::unwrap).collect();
131 kind: CommandKind::Compression(input_files),
133 path: output_file.into(),
134 contents_in_memory: None,
135 // extension: output_file_extension.ok(),
136 extension: Some(output_file_extension.unwrap())
141 // Output not supplied
142 // Checking if input files are decompressible
144 let input_files = process_decompressible_input(input_files)?;
147 kind: CommandKind::Decompression(input_files),
149 path: output_file.into(),
150 contents_in_memory: None,
156 // else: output file not supplied
157 // Case 1: all input files are decompressible
159 let input_files = process_decompressible_input(input_files)?;
162 kind: CommandKind::Decompression(input_files),