1use super::*;
2use colored::*;
3use std::io::{self, Write};
4
5const PATH_SEP: char = '.';
6
7#[derive(Debug, PartialEq)]
8enum WordResult<'a, R> {
9 Help(&'a SubClass<R>),
10 Cancel,
11 Exit,
12 Class(&'a Arc<SubClass<R>>),
13 Action(&'a Action<R>),
14 Unrecognized,
15}
16
17#[derive(Debug, PartialEq)]
19pub enum LineResult<R> {
20 Help,
22 Cancel,
24 Exit,
26 Class,
28 Action(R),
31 Unrecognized,
33}
34
35impl<R> LineResult<R> {
36 pub fn action_result(self) -> Option<R> {
39 match self {
40 LineResult::Action(r) => Some(r),
41 _ => None,
42 }
43 }
44}
45
46impl<R> Commander<R> {
47 pub fn parse_line<W: Write>(
70 &mut self,
71 line: &str,
72 colourise: bool,
73 writer: &mut W,
74 ) -> LineResult<R> {
75 let line = line.replace("\n", "").replace("\r", "");
76 let words: Vec<_> = line.trim().split(' ').collect();
77 let mut idx = 0;
78 let mut words_iter = words.iter();
79 let mut next_word = words_iter.next();
80
81 let start_class = Arc::clone(&self.current);
83 let start_path = self.path.clone();
84
85 while let Some(word) = next_word {
86 idx += 1;
87 next_word = match parse_word(&self.current, word) {
88 WordResult::Help(sc) => {
89 if colourise {
90 write_help_coloured(&sc, writer).expect("failed writing output to writer");
91 } else {
92 write_help(&sc, writer).expect("failed writing output to writer");
93 }
94 self.current = Arc::clone(&start_class);
95 self.path = start_path;
96 return LineResult::Help;
97 }
98 WordResult::Cancel => {
99 self.current = Arc::clone(&self.root);
100 self.path = self.root.name.clone();
101 return LineResult::Cancel;
102 }
103 WordResult::Exit => {
104 return LineResult::Exit;
105 }
106 WordResult::Class(sc) => {
107 self.path.push_str(&format!("{}{}", PATH_SEP, sc.name));
108 self.current = Arc::clone(&sc);
109 words_iter.next()
110 }
111 WordResult::Action(a) => {
112 let slice = &words[idx..];
113 let r = a.call(writer, slice);
114 self.current = Arc::clone(&start_class);
115 self.path = start_path;
116 return LineResult::Action(r);
117 }
118 WordResult::Unrecognized => {
119 let mut s = format!(
120 "'{}' does not match any keywords, classes, or actions",
121 word
122 )
123 .bright_red();
124
125 if !colourise {
126 s = s.white();
127 }
128
129 writeln!(writer, "{}", s).expect("failed writing output to writer");
130 self.current = Arc::clone(&start_class);
131 self.path = start_path;
132 return LineResult::Unrecognized;
133 }
134 };
135 }
136
137 LineResult::Class }
139}
140
141fn parse_word<'a, R>(subclass: &'a SubClass<R>, word: &str) -> WordResult<'a, R> {
142 let lwr = word.to_lowercase();
143 match lwr.as_str() {
144 "help" => WordResult::Help(subclass),
145 "cancel" | "c" => WordResult::Cancel,
146 "exit" => WordResult::Exit,
147 word => {
148 if let Some(c) = subclass.classes.iter().find(|c| c.name.as_str() == word) {
149 WordResult::Class(c)
150 } else if let Some(a) = subclass.actions.iter().find(|a| a.name.as_str() == word) {
151 WordResult::Action(a)
152 } else {
153 WordResult::Unrecognized
154 }
155 }
156 }
157}
158
159fn write_help_coloured<W: Write, R>(class: &SubClass<R>, writer: &mut W) -> io::Result<()> {
160 writeln!(
161 writer,
162 "{} -- prints the help messages",
163 "help".bright_yellow()
164 )?;
165 writeln!(
166 writer,
167 "{} | {} -- returns to the root class",
168 "cancel".bright_yellow(),
169 "c".bright_yellow()
170 )?;
171 writeln!(
172 writer,
173 "{} -- sends the exit signal to end the interactive loop",
174 "exit".bright_yellow()
175 )?;
176 if !class.classes.is_empty() {
177 writeln!(writer, "{}", "Classes:".bright_purple())?;
178 for class in class.classes.iter() {
179 writeln!(writer, "\t{} -- {}", class.name.bright_yellow(), class.help)?;
180 }
181 }
182
183 if !class.actions.is_empty() {
184 writeln!(writer, "{}", "Actions:".bright_purple())?;
185 for action in class.actions.iter() {
186 writeln!(
187 writer,
188 "\t{} -- {}",
189 action.name.bright_yellow(),
190 action.help
191 )?;
192 }
193 }
194
195 Ok(())
196}
197
198fn write_help<W: Write, R>(class: &SubClass<R>, writer: &mut W) -> io::Result<()> {
199 writeln!(writer, "help -- prints the help messages",)?;
200 writeln!(writer, "cancel | c -- returns to the root class",)?;
201 writeln!(
202 writer,
203 "exit -- sends the exit signal to end the interactive loop",
204 )?;
205 if !class.classes.is_empty() {
206 writeln!(writer, "Classes:")?;
207 for class in class.classes.iter() {
208 writeln!(writer, "\t{} -- {}", class.name, class.help)?;
209 }
210 }
211
212 if !class.actions.is_empty() {
213 writeln!(writer, "Actions:")?;
214 for action in class.actions.iter() {
215 writeln!(writer, "\t{} -- {}", action.name, action.help)?;
216 }
217 }
218
219 Ok(())
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn parse_line_test() {
228 let mut cmder = Builder::default_config("test")
229 .begin_class("class1", "class1 help")
230 .begin_class("class1-class1", "adsf")
231 .add_action("action1", "adf", |_, _| ())
232 .end_class()
233 .begin_class("class1-class2", "adsf")
234 .add_action("action2", "adsf", |_, _| ())
235 .end_class()
236 .end_class()
237 .begin_class("class2", "asdf")
238 .end_class()
239 .add_action("test-args", "", |_wtr, args| {
240 assert_eq!(&args, &["one", "two", "three"])
241 })
242 .into_commander()
243 .unwrap();
244
245 let w = &mut std::io::sink();
246
247 assert_eq!(cmder.parse_line("adsf", true, w), LineResult::Unrecognized); assert_eq!(cmder.current, cmder.root);
249 assert_eq!(cmder.parse_line("adsf", false, w), LineResult::Unrecognized); assert_eq!(cmder.current, cmder.root);
251
252 assert_eq!(cmder.parse_line("class1", true, w), LineResult::Class);
253 assert_ne!(cmder.current, cmder.root);
254 assert_eq!(cmder.current.name, "class1");
255
256 assert_eq!(
258 cmder.parse_line("class1-class1 action1", true, w),
259 LineResult::Action(())
260 );
261 assert_eq!(cmder.current.name, "class1");
262 assert_eq!(
263 cmder.parse_line("class1-class2 action2", true, w),
264 LineResult::Action(())
265 );
266 assert_eq!(cmder.current.name, "class1");
267
268 assert_eq!(cmder.parse_line("cancel", true, w), LineResult::Cancel);
270 assert_eq!(cmder.current.name, "test");
271
272 assert_eq!(
274 cmder.parse_line("test-args one two three", true, w),
275 LineResult::Action(())
276 );
277 assert_eq!(cmder.current.name, "test");
278
279 assert_eq!(cmder.parse_line("help", true, w), LineResult::Help);
281 assert_eq!(cmder.current.name, "test");
282 assert_eq!(cmder.parse_line("help", false, w), LineResult::Help);
283 assert_eq!(cmder.current.name, "test");
284
285 assert_eq!(cmder.parse_line("exit", true, w), LineResult::Exit);
287 }
288
289 #[test]
290 fn parse_word_test() {
291 let mut sc = SubClass::with_name("Class-Name", "help msg");
292 assert_eq!(parse_word(&sc, "HELP"), WordResult::Help(&sc));
293 assert_eq!(parse_word(&sc, "EXIT"), WordResult::Exit);
294 assert_eq!(parse_word(&sc, "CANCEL"), WordResult::Cancel);
295 assert_eq!(parse_word(&sc, "C"), WordResult::Cancel);
296 assert_eq!(parse_word(&sc, "asdf"), WordResult::Unrecognized);
297
298 sc.classes
299 .push(Arc::new(SubClass::with_name("name", "asdf")));
300 sc.actions.push(Action::blank_fn("action", "adsf"));
301 assert_eq!(parse_word(&sc, "NAME"), WordResult::Class(&sc.classes[0]));
302 assert_eq!(
303 parse_word(&sc, "aCtIoN"),
304 WordResult::Action(&sc.actions[0])
305 );
306 }
307
308 #[test]
309 fn write_help_coloured_test() {
310 let mut sc = SubClass::with_name("Class-Name", "root class");
311 sc.classes
312 .push(Arc::new(SubClass::with_name("class1", "class 1 help")));
313 sc.classes
314 .push(Arc::new(SubClass::with_name("class2", "class 2 help")));
315 sc.actions
316 .push(Action::blank_fn("action1", "action 1 help"));
317 sc.actions
318 .push(Action::blank_fn("action2", "action 2 help"));
319
320 let mut help = Vec::new();
321 write_help_coloured(&sc, &mut help).unwrap();
322 let help = String::from_utf8_lossy(&help);
323
324 assert_eq!(
325 &help,
326 &format!(
327 r#"{} -- prints the help messages
328{} | {} -- returns to the root class
329{} -- sends the exit signal to end the interactive loop
330{}
331 {} -- class 1 help
332 {} -- class 2 help
333{}
334 {} -- action 1 help
335 {} -- action 2 help
336"#,
337 "help".bright_yellow(),
338 "cancel".bright_yellow(),
339 "c".bright_yellow(),
340 "exit".bright_yellow(),
341 "Classes:".bright_purple(),
342 "class1".bright_yellow(),
343 "class2".bright_yellow(),
344 "Actions:".bright_purple(),
345 "action1".bright_yellow(),
346 "action2".bright_yellow()
347 )
348 );
349 }
350
351 #[test]
352 fn write_help_test() {
353 let mut sc = SubClass::with_name("Class-Name", "root class");
354 sc.classes
355 .push(Arc::new(SubClass::with_name("class1", "class 1 help")));
356 sc.classes
357 .push(Arc::new(SubClass::with_name("class2", "class 2 help")));
358 sc.actions
359 .push(Action::blank_fn("action1", "action 1 help"));
360 sc.actions
361 .push(Action::blank_fn("action2", "action 2 help"));
362
363 let mut help = Vec::new();
364 write_help(&sc, &mut help).unwrap();
365 let help = String::from_utf8_lossy(&help);
366
367 assert_eq!(
368 &help,
369 r#"help -- prints the help messages
370cancel | c -- returns to the root class
371exit -- sends the exit signal to end the interactive loop
372Classes:
373 class1 -- class 1 help
374 class2 -- class 2 help
375Actions:
376 action1 -- action 1 help
377 action2 -- action 2 help
378"#
379 );
380 }
381}