Add a sad Python script for Ouch testing
[ouch.git] / src / cli.rs
blob21a68edb13c515fbd86b144ff632ec285687e0b3
1 use std::{convert::TryFrom, fs, path::{Path, PathBuf}, vec::Vec};
3 use clap::{Arg, Values};
4 use colored::Colorize;
5 use error::Error;
7 use crate::error;
8 use crate::extension::Extension;
9 use crate::file::File;
11 #[derive(PartialEq, Eq, Debug)]
12 pub enum CommandKind {
13     Compression(
14         /// Files to be compressed
15         Vec<PathBuf>,
16     ),
17     Decompression(
18         /// Files to be decompressed and their extensions
19         Vec<File>,
20     ),
23 #[derive(PartialEq, Eq, Debug)]
24 pub struct Command {
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")
31         .version("0.1.2")
32         .about("ouch is a unified compression & decompression utility")
33         .after_help(
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")
41         .settings(&[
42             clap::AppSettings::ColoredHelp,
43             clap::AppSettings::ArgRequiredElseHelp,
44         ])
45         .arg(
46             Arg::with_name("input")
47                 .required(true)
48                 .multiple(true)
49                 .long("input")
50                 .short("i")
51                 .help("The input files or directories.")
52                 .takes_value(true),
53         )
54         .arg(
55             Arg::with_name("output")
56                 // --output/-o not required when output can be inferred from the input files
57                 .required(false)
58                 .multiple(false)
59                 .long("output")
60                 .short("o")
61                 .help("The output directory or compressed file.")
62                 .takes_value(true),
63         )
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| {
75             let input_files =
76                 input_files.map(|filename| (Path::new(filename), Extension::new(filename)));
78             for file in input_files.clone() {
79                 match file {
80                     (filename, Ok(_)) => {
81                         let path = Path::new(filename);
82                         if !path.exists() {
83                             return Err(error::Error::FileNotFound(filename.into()))
84                         }
85                     },
86                     (filename, Err(_)) => {
87                         return Err(error::Error::InputsMustHaveBeenDecompressible(filename.into()));
88                     }
89                 }
90             }
92             Ok(input_files
93                 .map(|(filename, extension)| (fs::canonicalize(filename).unwrap(), extension.unwrap()))
94                 .map(File::from)
95                 .collect::<Vec<_>>())
96         };
98         // Possibilities:
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);
119                         if !path.exists() {
120                             return Err(Error::FileNotFound(path))
121                         }
123                         eprintln!("{} {}", "[ERROR]".red(), err);
124                         return Err(Error::IOError);
125                     }
126                 }
128                 let input_files = canonical_paths.map(Result::unwrap).collect();
130                 Ok(Command {
131                     kind: CommandKind::Compression(input_files),
132                     output: Some(File {
133                         path: output_file.into(),
134                         contents_in_memory: None,
135                         // extension: output_file_extension.ok(),
136                         extension: Some(output_file_extension.unwrap())
137                     }),
138                 })
140             } else {
141                 // Output not supplied
142                 // Checking if input files are decompressible
144                 let input_files = process_decompressible_input(input_files)?;
146                 Ok(Command {
147                     kind: CommandKind::Decompression(input_files),
148                     output: Some(File {
149                         path: output_file.into(),
150                         contents_in_memory: None,
151                         extension: None
152                     })
153                 })
154             }
155         } else {
156             // else: output file not supplied
157             // Case 1: all input files are decompressible
158             // Case 2: error
159             let input_files = process_decompressible_input(input_files)?;
161             Ok(Command {
162                 kind: CommandKind::Decompression(input_files),
163                 output: None,
164             })
165         }
166     }