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(¤t_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(¤t_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