aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoronielfa <[email protected]>2020-07-31 16:46:24 +0100
committeronielfa <[email protected]>2020-07-31 16:46:24 +0100
commit76bdf45721d597922962e003568dcfdbcb548e63 (patch)
tree5c1e13a2bf3ae9795dafc22fec979c11bca4cf71
parent4591912ad315700b198238b8a3d3ba3c68d3bccf (diff)
Refactor parsers
-rw-r--r--src/command.rs500
1 files changed, 378 insertions, 122 deletions
diff --git a/src/command.rs b/src/command.rs
index d474e35..9809718 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -118,9 +118,25 @@ pub enum Command {
118 Blank, 118 Blank,
119} 119}
120 120
121#[derive(Debug)] 121#[derive(PartialEq, Debug)]
122enum CommandName {
123 Add,
124 AddAuto,
125 MonthPrev,
126 MonthNext,
127 Delete,
128 TrackUp,
129 TrackDown,
130 Help,
131 Write,
132 Quit,
133 Blank,
134}
135
136#[derive(PartialEq, Debug)]
122pub enum CommandLineError { 137pub enum CommandLineError {
123 InvalidCommand(String), // command name 138 InvalidCommand(String), // command name
139 InvalidArg(u32), // position
124 NotEnoughArgs(String, u32), // command name, required no. of args 140 NotEnoughArgs(String, u32), // command name, required no. of args
125} 141}
126 142
@@ -130,6 +146,7 @@ impl fmt::Display for CommandLineError {
130 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 146 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131 match self { 147 match self {
132 CommandLineError::InvalidCommand(s) => write!(f, "Invalid command: `{}`", s), 148 CommandLineError::InvalidCommand(s) => write!(f, "Invalid command: `{}`", s),
149 CommandLineError::InvalidArg(p) => write!(f, "Invalid argument at position {}", p),
133 CommandLineError::NotEnoughArgs(s, n) => { 150 CommandLineError::NotEnoughArgs(s, n) => {
134 write!(f, "Command `{}` requires atleast {} argument(s)!", s, n) 151 write!(f, "Command `{}` requires atleast {} argument(s)!", s, n)
135 } 152 }
@@ -141,77 +158,191 @@ type Result<T> = std::result::Result<T, CommandLineError>;
141 158
142impl Command { 159impl Command {
143 pub fn from_string<P: AsRef<str>>(input: P) -> Result<Command> { 160 pub fn from_string<P: AsRef<str>>(input: P) -> Result<Command> {
144 let mut strings: Vec<&str> = input.as_ref().trim().split(' ').collect(); 161 let input_str = input.as_ref().trim();
145 if strings.is_empty() { 162 let parsed = parse_command_name(input_str);
146 return Ok(Command::Blank); 163 if let Ok((command_name, rest)) = parsed {
147 } 164 match command_name {
148 165 CommandName::Add => return parse_add(rest),
149 let first = strings.first().unwrap().to_string(); 166 CommandName::AddAuto => return parse_add_auto(rest),
150 let args: Vec<String> = strings.iter_mut().skip(1).map(|s| s.to_string()).collect(); 167 CommandName::Delete => return parse_delete(rest),
151 let mut _add = |auto: bool, first: String| { 168 CommandName::TrackUp => return parse_track_up(rest),
152 return parse_add(first, args.clone(), auto); 169 CommandName::TrackDown => return parse_track_down(rest),
153 }; 170 CommandName::Help => return parse_help(rest),
154 171 CommandName::MonthPrev => return Ok(Command::MonthPrev),
155 match first.as_ref() { 172 CommandName::MonthNext => return Ok(Command::MonthNext),
156 "add" | "a" => _add(false, first), 173 CommandName::Quit => return Ok(Command::Quit),
157 "add-auto" | "aa" => _add(true, first), 174 CommandName::Write => return Ok(Command::Write),
158 "delete" | "d" => { 175 CommandName::Blank => return Ok(Command::Blank),
159 if args.is_empty() {
160 return Err(CommandLineError::NotEnoughArgs(first, 1));
161 }
162 return Ok(Command::Delete(args[0].to_string()));
163 }
164 "track-up" | "tup" => {
165 if args.is_empty() {
166 return Err(CommandLineError::NotEnoughArgs(first, 1));
167 }
168 return Ok(Command::TrackUp(args[0].to_string()));
169 }
170 "track-down" | "tdown" => {
171 if args.is_empty() {
172 return Err(CommandLineError::NotEnoughArgs(first, 1));
173 }
174 return Ok(Command::TrackDown(args[0].to_string()));
175 }
176 "h" | "?" | "help" => {
177 if args.is_empty() {
178 return Ok(Command::Help(None));
179 }
180 return Ok(Command::Help(Some(args[0].to_string())));
181 } 176 }
182 "mprev" | "month-prev" => return Ok(Command::MonthPrev), 177 } else {
183 "mnext" | "month-next" => return Ok(Command::MonthNext), 178 return Err(parsed.err().unwrap());
184 "q" | "quit" => return Ok(Command::Quit),
185 "w" | "write" => return Ok(Command::Write),
186 "" => return Ok(Command::Blank),
187 s => return Err(CommandLineError::InvalidCommand(s.into())),
188 } 179 }
189 } 180 }
190} 181}
191 182
192fn parse_add(verb: String, args: Vec<String>, auto: bool) -> Result<Command> { 183fn parse_command_name(input: &str) -> Result<(CommandName, &str)> {
193 if args.is_empty() { 184 let chars = input.trim().chars();
194 return Err(CommandLineError::NotEnoughArgs(verb, 1)); 185 let mut parsed_name = "".to_owned();
186 let mut pos = 0;
187
188 for c in chars {
189 pos = pos + 1;
190 if c == ' ' {
191 break;
192 }
193
194 parsed_name.push(c);
195 } 195 }
196 196
197 let mut pos = 1; 197 match parsed_name.as_ref() {
198 let mut acc = "".to_owned(); 198 "add" | "a" => Ok((CommandName::Add, &input[pos..])),
199 let mut new_goal: Option<u32> = None; 199 "add-auto" | "aa" => Ok((CommandName::AddAuto, &input[pos..])),
200 for s1 in args { 200 "delete" | "d" => Ok((CommandName::Delete, &input[pos..])),
201 if pos == 1 { 201 "track-up" | "tup" => Ok((CommandName::TrackUp, &input[pos..])),
202 acc.push_str(&s1); 202 "track-down" | "tdown" => Ok((CommandName::TrackDown, &input[pos..])),
203 } else { 203 "h" | "?" | "help" => Ok((CommandName::Help, &input[pos..])),
204 if let Ok(n) = s1.parse::<u32>() { 204 "mprev" => Ok((CommandName::MonthPrev, &input[pos..])),
205 new_goal = Some(n); 205 "mnext" => Ok((CommandName::MonthNext, &input[pos..])),
206 "quit" | "q" => Ok((CommandName::Quit, &input[pos..])),
207 "write" | "w" => Ok((CommandName::Write, &input[pos..])),
208 "" => Ok((CommandName::Blank, &input[pos..])),
209 _ => Err(CommandLineError::InvalidCommand(parsed_name)),
210 }
211}
212
213fn parse_name(input: &str) -> (String, &str) {
214 let chars = input.trim().chars();
215 let mut name = "".to_owned();
216 let mut pos = 0;
217 let mut parenthesis = false;
218
219 for c in chars {
220 pos = pos + 1;
221 if c == '"' || c == '\"' {
222 if parenthesis {
223 return (name, &input[pos..]);
206 } else { 224 } else {
207 acc.push(' '); 225 parenthesis = true;
208 acc.push_str(&s1); 226 continue;
209 } 227 }
210 } 228 }
229
230 if parenthesis {
231 name.push(c);
232 continue;
233 }
234
235 if c == ' ' {
236 break;
237 }
238
239 name.push(c);
240 }
241
242 (name, &input[pos..])
243}
244
245fn parse_goal(input: &str) -> Option<(Option<u32>, &str)> {
246 let chars = input.trim().chars();
247 let mut goal_string = "".to_owned();
248 let mut pos = 0;
249
250 if input.is_empty() {
251 return Some((None, input));
252 }
253
254 for c in chars {
211 pos = pos + 1; 255 pos = pos + 1;
256 if c == ' ' {
257 break;
258 }
259
260 goal_string.push(c);
261 }
262
263 let parsed = goal_string.parse::<u32>();
264
265 if parsed.is_err() {
266 return None;
267 }
268
269 if pos + 1 > input.len() {
270 return Some((parsed.ok(), ""));
271 }
272
273 Some((parsed.ok(), &input[pos..]))
274}
275
276fn parse_add(input: &str) -> Result<Command> {
277 let (name, rest) = parse_name(input);
278
279 if name.is_empty() {
280 return Err(CommandLineError::NotEnoughArgs("add".to_owned(), 2));
281 }
282
283 let parsed_goal = parse_goal(rest);
284
285 if parsed_goal.is_none() {
286 return Err(CommandLineError::InvalidArg(2));
287 }
288
289 Ok(Command::Add(name, parsed_goal.unwrap().0, false))
290}
291
292fn parse_add_auto(input: &str) -> Result<Command> {
293 let (name, rest) = parse_name(input);
294
295 if name.is_empty() {
296 return Err(CommandLineError::NotEnoughArgs("add-auto".to_owned(), 2));
297 }
298
299 let parsed_goal = parse_goal(rest);
300
301 if parsed_goal.is_none() {
302 return Err(CommandLineError::InvalidArg(2));
303 }
304
305 Ok(Command::Add(name, parsed_goal.unwrap().0, true))
306}
307
308fn parse_delete(input: &str) -> Result<Command> {
309 let (name, _) = parse_name(input);
310
311 if name.is_empty() {
312 return Err(CommandLineError::NotEnoughArgs("delete".to_owned(), 1));
313 }
314
315 Ok(Command::Delete(name))
316}
317
318fn parse_track_up(input: &str) -> Result<Command> {
319 let (name, _) = parse_name(input);
320
321 if name.is_empty() {
322 return Err(CommandLineError::NotEnoughArgs("track-up".to_owned(), 1));
323 }
324
325 Ok(Command::TrackUp(name))
326}
327
328fn parse_track_down(input: &str) -> Result<Command> {
329 let (name, _) = parse_name(input);
330
331 if name.is_empty() {
332 return Err(CommandLineError::NotEnoughArgs("track-down".to_owned(), 1));
333 }
334
335 Ok(Command::TrackDown(name))
336}
337
338fn parse_help(input: &str) -> Result<Command> {
339 let (name, _) = parse_name(input);
340
341 if name.is_empty() {
342 return Ok(Command::Help(None));
212 } 343 }
213 344
214 return Ok(Command::Add(acc, new_goal, auto)); 345 Ok(Command::Help(Some(name)))
215} 346}
216 347
217#[cfg(test)] 348#[cfg(test)]
@@ -220,113 +351,179 @@ mod tests {
220 351
221 #[test] 352 #[test]
222 fn parse_add_command() { 353 fn parse_add_command() {
223 let result = Command::from_string("add eat 2"); 354 let inputs = ["add eat 2", "a eat 2"];
224 355
225 assert!(result.is_ok()); 356 for input in inputs.iter() {
226 match result.unwrap() { 357 let result = Command::from_string(input);
227 Command::Add(name, goal, auto) => { 358
228 assert_eq!(name, "eat"); 359 assert!(result.is_ok());
229 assert_eq!(goal.unwrap(), 2); 360 match result.unwrap() {
230 assert_eq!(auto, false); 361 Command::Add(name, goal, auto) => {
362 assert_eq!(name, "eat");
363 assert_eq!(goal.unwrap(), 2);
364 assert_eq!(auto, false);
365 }
366 _ => panic!(),
231 } 367 }
232 _ => panic!(),
233 } 368 }
234 } 369 }
235 370
236 #[test] 371 #[test]
237 fn parse_add_command_without_goal() { 372 fn parse_add_command_without_goal() {
238 let result = Command::from_string("add eat"); 373 let inputs = ["add eat", "a eat"];
239 374
240 assert!(result.is_ok()); 375 for input in inputs.iter() {
241 match result.unwrap() { 376 let result = Command::from_string(input);
242 Command::Add(name, goal, auto) => { 377
243 assert_eq!(name, "eat"); 378 assert!(result.is_ok());
244 assert!(goal.is_none()); 379 match result.unwrap() {
245 assert_eq!(auto, false); 380 Command::Add(name, goal, auto) => {
381 assert_eq!(name, "eat");
382 assert!(goal.is_none());
383 assert_eq!(auto, false);
384 }
385 _ => panic!(),
246 } 386 }
247 _ => panic!(),
248 } 387 }
249 } 388 }
250 389
251 // #[test] 390 #[test]
252 fn parse_add_command_with_long_name() { 391 fn parse_add_command_with_long_name() {
253 let result = Command::from_string("add \"eat healthy\" 5"); 392 let inputs = ["add \"eat healthy\" 5", "a \"eat healthy\" 5"];
254 393
255 assert!(result.is_ok()); 394 for input in inputs.iter() {
256 match result.unwrap() { 395 let result = Command::from_string(input);
257 Command::Add(name, goal, auto) => { 396
258 assert_eq!(name, "eat healthy"); 397 assert!(result.is_ok());
259 assert_eq!(goal.unwrap(), 5); 398 match result.unwrap() {
260 assert_eq!(auto, false); 399 Command::Add(name, goal, auto) => {
400 assert_eq!(name, "eat healthy");
401 assert_eq!(goal.unwrap(), 5);
402 assert_eq!(auto, false);
403 }
404 _ => panic!(),
261 } 405 }
262 _ => panic!(),
263 } 406 }
264 } 407 }
265 408
266 #[test] 409 #[test]
267 fn parse_add_auto_command() { 410 fn parse_add_auto_command() {
268 let result = Command::from_string("add-auto eat 2"); 411 let inputs = ["add-auto eat 2", "aa eat 2"];
269 412
270 assert!(result.is_ok()); 413 for input in inputs.iter() {
271 match result.unwrap() { 414 let result = Command::from_string(input);
272 Command::Add(name, goal, auto) => { 415
273 assert_eq!(name, "eat"); 416 assert!(result.is_ok());
274 assert_eq!(goal.unwrap(), 2); 417 match result.unwrap() {
275 assert_eq!(auto, true); 418 Command::Add(name, goal, auto) => {
419 assert_eq!(name, "eat");
420 assert_eq!(goal.unwrap(), 2);
421 assert_eq!(auto, true);
422 }
423 _ => panic!(),
276 } 424 }
277 _ => panic!(),
278 } 425 }
279 } 426 }
280 427
281 #[test] 428 #[test]
282 fn parse_delete_command() { 429 fn parse_delete_command() {
283 let result = Command::from_string("delete eat"); 430 let inputs = ["delete eat", "d eat"];
284 431
285 assert!(result.is_ok()); 432 for input in inputs.iter() {
286 match result.unwrap() { 433 let result = Command::from_string(input);
287 Command::Delete(name) => { 434
288 assert_eq!(name, "eat"); 435 assert!(result.is_ok());
436 match result.unwrap() {
437 Command::Delete(name) => {
438 assert_eq!(name, "eat");
439 }
440 _ => panic!(),
441 }
442 }
443 }
444
445 #[test]
446 fn parse_delete_long_name_command() {
447 let inputs = ["delete \"eat healthy\"", "d \"eat healthy\""];
448
449 for input in inputs.iter() {
450 let result = Command::from_string(input);
451
452 assert!(result.is_ok());
453 match result.unwrap() {
454 Command::Delete(name) => {
455 assert_eq!(name, "eat healthy");
456 }
457 _ => panic!(),
289 } 458 }
290 _ => panic!(),
291 } 459 }
292 } 460 }
293 461
294 #[test] 462 #[test]
295 fn parse_track_up_command() { 463 fn parse_track_up_command() {
296 let result = Command::from_string("track-up eat"); 464 let inputs = ["track-up eat", "tup eat"];
297 465
298 assert!(result.is_ok()); 466 for input in inputs.iter() {
299 match result.unwrap() { 467 let result = Command::from_string(input);
300 Command::TrackUp(name) => { 468
301 assert_eq!(name, "eat"); 469 assert!(result.is_ok());
470 match result.unwrap() {
471 Command::TrackUp(name) => {
472 assert_eq!(name, "eat");
473 }
474 _ => panic!(),
302 } 475 }
303 _ => panic!(),
304 } 476 }
305 } 477 }
306 478
307 #[test] 479 #[test]
308 fn parse_track_down_command() { 480 fn parse_track_down_command() {
309 let result = Command::from_string("track-down eat"); 481 let inputs = ["track-down eat", "tdown eat"];
310 482
311 assert!(result.is_ok()); 483 for input in inputs.iter() {
312 match result.unwrap() { 484 let result = Command::from_string(input);
313 Command::TrackDown(name) => { 485
314 assert_eq!(name, "eat"); 486 assert!(result.is_ok());
487 match result.unwrap() {
488 Command::TrackDown(name) => {
489 assert_eq!(name, "eat");
490 }
491 _ => panic!(),
315 } 492 }
316 _ => panic!(),
317 } 493 }
318 } 494 }
319 495
320 #[test] 496 #[test]
321 fn parse_help_command() { 497 fn parse_help_command() {
322 let result = Command::from_string("help add"); 498 let inputs = ["help add", "? add", "h add"];
323 499
324 assert!(result.is_ok()); 500 for input in inputs.iter() {
325 match result.unwrap() { 501 let result = Command::from_string(input);
326 Command::Help(name) => { 502
327 assert_eq!(name.unwrap(), "add"); 503 assert!(result.is_ok());
504 match result.unwrap() {
505 Command::Help(name) => {
506 assert_eq!(name.unwrap(), "add");
507 }
508 _ => panic!(),
509 }
510 }
511 }
512
513 #[test]
514 fn parse_help_global_command() {
515 let inputs = ["help", "?", "h"];
516
517 for input in inputs.iter() {
518 let result = Command::from_string(input);
519
520 assert!(result.is_ok());
521 match result.unwrap() {
522 Command::Help(name) => {
523 assert!(name.is_none());
524 }
525 _ => panic!(),
328 } 526 }
329 _ => panic!(),
330 } 527 }
331 } 528 }
332 529
@@ -348,18 +545,26 @@ mod tests {
348 545
349 #[test] 546 #[test]
350 fn parse_quit_command() { 547 fn parse_quit_command() {
351 let result = Command::from_string("q"); 548 let inputs = ["q", "quit"];
352 549
353 assert!(result.is_ok()); 550 for input in inputs.iter() {
354 assert_eq!(result.unwrap(), Command::Quit); 551 let result = Command::from_string(input);
552
553 assert!(result.is_ok());
554 assert_eq!(result.unwrap(), Command::Quit);
555 }
355 } 556 }
356 557
357 #[test] 558 #[test]
358 fn parse_write_command() { 559 fn parse_write_command() {
359 let result = Command::from_string("w"); 560 let inputs = ["w", "write"];
360 561
361 assert!(result.is_ok()); 562 for input in inputs.iter() {
362 assert_eq!(result.unwrap(), Command::Write); 563 let result = Command::from_string(input);
564
565 assert!(result.is_ok());
566 assert_eq!(result.unwrap(), Command::Write);
567 }
363 } 568 }
364 569
365 #[test] 570 #[test]
@@ -369,4 +574,55 @@ mod tests {
369 assert!(result.is_ok()); 574 assert!(result.is_ok());
370 assert_eq!(result.unwrap(), Command::Blank); 575 assert_eq!(result.unwrap(), Command::Blank);
371 } 576 }
577
578 #[test]
579 fn parse_error_invalid_command() {
580 let input = "non-existing-command".to_owned();
581 let result = Command::from_string(&input);
582
583 assert!(result.is_err());
584 assert_eq!(
585 result.err().unwrap(),
586 CommandLineError::InvalidCommand(input)
587 );
588 }
589
590 #[test]
591 fn parse_error_not_enough_args() {
592 let test_cases = [
593 ("add".to_owned(), "add".to_owned(), 2),
594 ("add-auto".to_owned(), "add-auto".to_owned(), 2),
595 ("delete".to_owned(), "delete".to_owned(), 1),
596 ("track-up".to_owned(), "track-up".to_owned(), 1),
597 ("track-down".to_owned(), "track-down".to_owned(), 1),
598 ];
599
600 for test_case in test_cases.iter() {
601 let result = Command::from_string(&test_case.0);
602
603 assert!(result.is_err());
604 assert_eq!(
605 result.err().unwrap(),
606 CommandLineError::NotEnoughArgs(test_case.1.clone(), test_case.2)
607 );
608 }
609 }
610
611 #[test]
612 fn parse_error_invalid_arg() {
613 let test_cases = [
614 ("add habit n".to_owned(), 2),
615 ("add-auto habit n".to_owned(), 2),
616 ];
617
618 for test_case in test_cases.iter() {
619 let result = Command::from_string(&test_case.0);
620
621 assert!(result.is_err());
622 assert_eq!(
623 result.err().unwrap(),
624 CommandLineError::InvalidArg(test_case.1)
625 );
626 }
627 }
372} 628}