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