1 use man;
2 use std::env::args;
3 use std::fs;
4 use std::io::{BufRead, BufReader, StdoutLock, Write};
5 use std::process::exit;
6 
7 #[derive(Debug)]
8 pub enum InputError {
9     FileError(String, String),
10     NoInputsProvided,
11     NotEnoughInputs
12 }
13 
14 
15 /// Scans input arguments for flags that control the behaviour of the program.
parse_options(stdout: &mut StdoutLock) -> (Vec<String>, bool, bool, bool)16 pub fn parse_options(stdout: &mut StdoutLock) -> (Vec<String>, bool, bool, bool) {
17     let mut input = Vec::new();
18     let (mut benchmark, mut interpret_files, mut no_delimiters) = (false, false, false);
19     for argument in args().skip(1) {
20         match argument.as_str() {
21             "-b" | "--benchmark" => benchmark = true,
22             "-f" | "--files" => interpret_files = true,
23             "-h" | "--help" => {
24                 let _ = stdout.write(man::MANPAGE.as_bytes());
25                 exit(0);
26             },
27             "-n" | "--no-delimiters" => no_delimiters = true,
28             _ => input.push(argument)
29         }
30     }
31     (input, benchmark, interpret_files, no_delimiters)
32 }
33 
34 /// This is effectively a command-line interpreter designed specifically for this program.
parse_arguments(list_collection: &mut Vec<Vec<String>>, input: &str, interpret_files: bool) -> Result<(), InputError>35 pub fn parse_arguments(list_collection: &mut Vec<Vec<String>>, input: &str, interpret_files: bool)
36     -> Result<(), InputError>
37 {
38     let mut add_to_previous_list = false;
39     let mut backslash            = false;
40     let mut double_quote         = false;
41     let mut single_quote         = false;
42     let mut match_set            = false;
43     let mut interpret_files      = interpret_files;
44     let mut matches              = 0;
45     let mut current_list         = Vec::new();
46     let mut current_argument     = String::new();
47 
48     for character in input.chars() {
49         if match_set {
50             match character {
51                 '+' => add_to_previous_list = true,
52                 ' ' => {
53                     if matches == 3 {
54                         if add_to_previous_list {
55                             add_to_previous_list = false;
56                         } else {
57                             if current_list.is_empty() {
58                                 return Err(InputError::NoInputsProvided);
59                             } else {
60                                 list_collection.push(current_list.clone());
61                                 current_list.clear();
62                             }
63                         }
64                         interpret_files = false;
65                     } else if matches == 4 {
66                         if add_to_previous_list {
67                             add_to_previous_list = false;
68                         } else {
69                             if current_list.is_empty() {
70                                 return Err(InputError::NoInputsProvided);
71                             } else {
72                                 list_collection.push(current_list.clone());
73                                 current_list.clear();
74                             }
75                         }
76                         interpret_files = true;
77                     } else {
78                         for _ in 0..matches { current_argument.push(':'); }
79                         current_list.push(current_argument.clone());
80                         current_argument.clear();
81                     }
82                     match_set = false;
83                     matches = 0;
84                 } ,
85                 ':' if !add_to_previous_list => matches += 1,
86                 _ => {
87                     for _ in 0..matches { current_argument.push(':'); }
88                     current_argument.push(character);
89                     match_set = false;
90                     matches = 0;
91                 },
92             }
93         } else if backslash {
94             match character {
95                 '\\' | '\'' | ' ' | '\"' => current_argument.push(character),
96                 _    => {
97                     current_argument.push('\\');
98                     current_argument.push(' ');
99                 },
100             }
101             backslash = false;
102         } else if single_quote {
103             match character {
104                 '\\' => backslash = true,
105                 '\'' => single_quote = false,
106                 _    => current_argument.push(character)
107             }
108         } else if double_quote {
109             match character {
110                 '\\' => backslash = true,
111                 '\"' => double_quote = false,
112                 _    => current_argument.push(character)
113             }
114         } else {
115             match character {
116                 ' ' => {
117                     if !current_argument.is_empty() {
118                         if interpret_files {
119                             for argument in try!(file_parse(&current_argument)) {
120                                 current_list.push(argument);
121                             }
122                         } else {
123                             current_list.push(current_argument.clone());
124                         }
125                         current_argument.clear();
126                     }
127                 },
128                 '\\' => backslash = true,
129                 '\'' => single_quote = true,
130                 '\"' => double_quote = true,
131                 ':' => {
132                     match_set = true;
133                     matches = 1;
134                 },
135                 _ => current_argument.push(character)
136             }
137         }
138     }
139 
140     if !current_argument.is_empty() {
141         if interpret_files {
142             for argument in try!(file_parse(&current_argument)) {
143                 current_list.push(argument);
144             }
145         } else {
146             current_list.push(current_argument);
147         }
148     }
149 
150     if !current_list.is_empty() {
151         list_collection.push(current_list);
152     }
153 
154     if list_collection.len() == 0 || (list_collection.len() == 1 && list_collection[0].len() == 1) {
155         return Err(InputError::NotEnoughInputs)
156     } else {
157         Ok(())
158     }
159 }
160 
161 /// Attempts to open an input argument and adds each line to the `inputs` list.
file_parse(path: &str) -> Result<Vec<String>, InputError>162 fn file_parse(path: &str) -> Result<Vec<String>, InputError> {
163     let mut inputs = Vec::new();
164     let file = try!(fs::File::open(path)
165         .map_err(|err| InputError::FileError(path.to_owned(), err.to_string())));
166     for line in BufReader::new(file).lines() {
167         if let Ok(line) = line { inputs.push(line); }
168     }
169     Ok(inputs)
170 }
171 
172 #[cfg(test)]
173 mod test {
174     use super::parse_arguments;
175 
176     #[test]
test_parse_arguments()177     fn test_parse_arguments() {
178         let mut output = Vec::new();
179         let inputs = "A B ::: \"C D\" \\\"EF\\\" ::: five:six seven\\ eight";
180         let expected = vec![
181             vec!["A".to_owned(), "B".to_owned()],
182             vec!["C D".to_owned(), "\"EF\"".to_owned()],
183             vec!["five:six".to_owned(), "seven eight".to_owned()]
184         ];
185         let _ = parse_arguments(&mut output, inputs, false);
186         assert_eq!(output, expected);
187     }
188 }
189