Add support for Bzip compression (includes .tar.bz2 and .zip.bz2 and etc)
[ouch.git] / src / cli.rs
blobfbde8650998d7a889b57a57c9162aed3e6bdc2a6
1 use std::{convert::TryFrom, path::PathBuf, vec::Vec};
3 use clap::{Arg, Values};
4 // use colored::Colorize;
6 use crate::error;
7 use crate::extension::Extension;
8 use crate::file::File;
10 #[derive(PartialEq, Eq, Debug)]
11 pub enum CommandKind {
12     Compression(
13         // Files to be compressed
14         Vec<PathBuf>,
15     ),
16     Decompression(
17         // Files to be decompressed and their extensions
18         Vec<File>,
19     ),
22 #[derive(PartialEq, Eq, Debug)]
23 pub struct Command {
24     pub kind: CommandKind,
25     pub output: Option<File>,
28 pub fn clap_app<'a, 'b>() -> clap::App<'a, 'b> {
29     clap::App::new("ouch")
30         .version("0.1.0")
31         .about("ouch is a unified compression & decompression utility")
32         .after_help(
33 "ouch infers what to based on the extensions of the input files and output file received.
34 Examples: `ouch -i movies.tar.gz classes.zip -o Videos/` in order to decompress files into a folder.
35           `ouch -i headers/ sources/ Makefile -o my-project.tar.gz` 
36           `ouch -i image{1..50}.jpeg -o images.zip`
37 Please relate any issues or contribute at https://github.com/vrmiguel/ouch")
38         .author("Vinícius R. Miguel")
39         .help_message("Displays this message and exits")
40         .settings(&[
41             clap::AppSettings::ColoredHelp,
42             clap::AppSettings::ArgRequiredElseHelp,
43         ])
44         .arg(
45             Arg::with_name("input")
46                 .required(true)
47                 .multiple(true)
48                 .long("input")
49                 .short("i")
50                 .help("The input files or directories.")
51                 .takes_value(true),
52         )
53         .arg(
54             Arg::with_name("output")
55                 // --output/-o not required when output can be inferred from the input files
56                 .required(false)
57                 .multiple(false)
58                 .long("output")
59                 .short("o")
60                 .help("The output directory or compressed file.")
61                 .takes_value(true),
62         )
65 pub fn get_matches() -> clap::ArgMatches<'static> {
66     clap_app().get_matches()
69 // holy spaghetti code
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| (filename, Extension::new(filename)));
78             for file in input_files.clone() {
79                 if let (file, Err(_)) = file {
80                     return Err(error::Error::InputsMustHaveBeenDecompressible(file.into()));
81                 }
82             }
84             Ok(input_files
85                 .map(|(filename, extension)| (PathBuf::from(filename), extension.unwrap()))
86                 .map(File::from)
87                 .collect::<Vec<_>>())
88         };
90         // Possibilities:
91         //   * Case 1: output not supplied, therefore try to infer output by checking if all input files are decompressible
92         //   * Case 2: output supplied
94         let output_was_supplied = matches.is_present("output");
96         let input_files = matches.values_of("input").unwrap(); // Safe to unwrap since input is an obligatory argument
98         if output_was_supplied {
99             let output_file = matches.value_of("output").unwrap(); // Safe unwrap since we've established that output was supplied
101             let output_file_extension = Extension::new(output_file);
102             
103             let output_is_compressible = output_file_extension.is_ok();
104             if output_is_compressible {
105                 // The supplied output is compressible, so we'll compress our inputs to it
107                 // println!(
108                 //     "{}: trying to compress input files into '{}'",
109                 //     "info".yellow(),
110                 //     output_file
111                 // );
113                 let input_files = input_files.map(PathBuf::from).collect();
115                 return Ok(Command {
116                     kind: CommandKind::Compression(input_files),
117                     output: Some(File {
118                         path: output_file.into(),
119                         contents_in_memory: None,
120                         extension: Some(output_file_extension.unwrap())
121                     }),
122                 });
124             } else {
125                 // Output not supplied
126                 // Checking if input files are decompressible
128                 let input_files = process_decompressible_input(input_files)?;
130                 return Ok(Command {
131                     kind: CommandKind::Decompression(input_files),
132                     output: Some(File {
133                         path: output_file.into(),
134                         contents_in_memory: None,
135                         extension: None
136                     })
137                 });
138             }
139         } else {
140             // else: output file not supplied
141             // Case 1: all input files are decompressible
142             // Case 2: error
143             let input_files = process_decompressible_input(input_files)?;
145             return Ok(Command {
146                 kind: CommandKind::Decompression(input_files),
147                 output: None,
148             });
149         }
150     }