1#![cfg_attr(feature = "strict", feature(plugin))]
15#![cfg_attr(feature = "strict", plugin(clippy))]
16#![cfg_attr(feature = "strict", deny(warnings))]
17
18use std::collections::HashMap;
19use std::io::{BufRead, BufReader, Read, Write};
20
21#[derive(Clone)]
45pub struct Question<R, W>
46where
47 R: Read,
48 W: Write,
49{
50 question: String,
51 prompt: String,
52 default: Option<Answer>,
53 clarification: Option<String>,
54 acceptable: Option<Vec<String>>,
55 valid_responses: Option<HashMap<String, Answer>>,
56 tries: Option<u64>,
57 until_acceptable: bool,
58 show_defaults: bool,
59 yes_no: bool,
60 reader: R,
61 writer: W,
62}
63
64impl Question<std::io::Stdin, std::io::Stdout> {
65 pub fn new(question: &str) -> Question<std::io::Stdin, std::io::Stdout> {
74 let question = question.to_string();
75 Question {
76 question: question.clone(),
77 prompt: question,
78 default: None,
79 acceptable: None,
80 valid_responses: None,
81 clarification: None,
82 tries: None,
83 until_acceptable: false,
84 show_defaults: false,
85 yes_no: false,
86 reader: std::io::stdin(),
87 writer: std::io::stdout(),
88 }
89 }
90}
91
92impl<R, W> Question<R, W>
93where
94 R: Read,
95 W: Write,
96{
97 #[cfg(test)]
98 pub fn with_cursor(question: &str, input: R, output: W) -> Question<R, W> {
99 let question = question.to_string();
100 Question {
101 question: question.clone(),
102 prompt: question,
103 default: None,
104 acceptable: None,
105 valid_responses: None,
106 clarification: None,
107 tries: None,
108 until_acceptable: false,
109 show_defaults: false,
110 yes_no: false,
111 reader: input,
112 writer: output,
113 }
114 }
115
116 pub fn accept(&mut self, accepted: &str) -> &mut Question<R, W> {
132 let accepted = accepted.to_string();
133 match self.acceptable {
134 Some(ref mut vec) => vec.push(accepted),
135 None => {
136 let mut vec = Vec::new();
137 vec.push(accepted);
138 self.acceptable = Some(vec);
139 }
140 }
141 self
142 }
143
144 pub fn acceptable(&mut self, accepted: Vec<&str>) -> &mut Question<R, W> {
159 let mut accepted = accepted.into_iter().map(|x| x.into()).collect();
160 match self.acceptable {
161 Some(ref mut vec) => vec.append(&mut accepted),
162 None => self.acceptable = Some(accepted),
163 }
164 self
165 }
166
167 pub fn yes_no(&mut self) -> &mut Question<R, W> {
183 self.yes_no = true;
184 let response_keys = vec![
185 String::from("yes"),
186 String::from("y"),
187 String::from("no"),
188 String::from("n"),
189 ];
190
191 let response_values = vec![Answer::YES, Answer::YES, Answer::NO, Answer::NO];
192 let mut valid_responses: HashMap<String, Answer> = response_keys
193 .into_iter()
194 .zip(response_values.into_iter())
195 .collect();
196
197 match self.valid_responses {
198 Some(ref mut hashmap) => for (k, v) in valid_responses.drain() {
199 hashmap.insert(k, v);
200 },
201 None => self.valid_responses = Some(valid_responses),
202 }
203 self
204 }
205
206 pub fn tries(&mut self, tries: u64) -> &mut Question<R, W> {
224 match tries {
225 0 => self.until_acceptable = true,
226 1 => return self,
227 _ => self.tries = Some(tries),
228 }
229 self
230 }
231
232 pub fn until_acceptable(&mut self) -> &mut Question<R, W> {
249 self.until_acceptable = true;
250 self
251 }
252
253 pub fn show_defaults(&mut self) -> &mut Question<R, W> {
278 self.show_defaults = true;
279 self
280 }
281
282 pub fn default(&mut self, answer: Answer) -> &mut Question<R, W> {
301 self.default = Some(answer);
302 self
303 }
304
305 pub fn clarification(&mut self, c: &str) -> &mut Question<R, W> {
332 self.clarification = Some(c.into());
333 self
334 }
335
336 pub fn ask(&mut self) -> Option<Answer> {
348 self.build_prompt();
349 if self.until_acceptable {
350 return Some(self.until_valid());
351 }
352 if self.tries.is_some() {
353 return self.max_tries();
354 }
355 match self.get_response() {
356 Ok(answer) => Some(answer),
357 Err(_) => None,
358 }
359 }
360
361 pub fn confirm(&mut self) -> Answer {
371 self.yes_no();
372 self.build_prompt();
373 self.until_valid()
374 }
375
376 fn get_response(&mut self) -> Result<Answer, std::io::Error> {
377 let prompt = self.prompt.clone();
378 match self.prompt_user(&prompt) {
379 Ok(ref answer) if (self.default != None) && answer == "" => {
380 Ok(self.default.clone().unwrap())
381 }
382 Ok(answer) => Ok(Answer::RESPONSE(answer)),
383 Err(e) => Err(e),
384 }
385 }
386
387 fn get_valid_response(&mut self) -> Option<Answer> {
388 let prompt = self.prompt.clone();
389 let valid_responses = match self.valid_responses.clone() {
390 Some(thing) => thing,
391 None => panic!(),
392 };
393 if let Ok(response) = self.prompt_user(&prompt) {
394 for key in valid_responses.keys() {
395 if *response.trim().to_lowercase() == *key {
396 return Some(valid_responses[key].clone());
397 }
398 if let Some(default) = self.default.clone() {
399 if response == "" {
400 return Some(default);
401 }
402 }
403 }
404 }
405 None
406 }
407
408 fn get_acceptable_response(&mut self) -> Option<Answer> {
409 let prompt = self.prompt.clone();
410 let acceptable_responses = match self.acceptable.clone() {
411 Some(thing) => thing,
412 None => panic!(),
413 };
414 if let Ok(response) = self.prompt_user(&prompt) {
415 for acceptable_response in acceptable_responses {
416 if *response.trim().to_lowercase() == acceptable_response {
417 return Some(Answer::RESPONSE(acceptable_response.clone()));
418 }
419 if let Some(default) = self.default.clone() {
420 if response == "" {
421 return Some(default);
422 }
423 }
424 }
425 }
426 None
427 }
428
429 fn max_tries(&mut self) -> Option<Answer> {
430 let mut attempts = 0;
431 while attempts < self.tries.unwrap() {
432 match self.get_valid_response() {
433 Some(answer) => return Some(answer),
434 None => {
435 self.build_clarification();
436 attempts += 1;
437 continue;
438 }
439 }
440 }
441 None
442 }
443
444 fn until_valid(&mut self) -> Answer {
445 if self.valid_responses.is_some() {
446 loop {
447 match self.get_valid_response() {
448 Some(answer) => return answer,
449 None => {
450 self.build_clarification();
451 continue;
452 }
453 }
454 }
455 }
456 if self.acceptable.is_some() {
457 loop {
458 match self.get_acceptable_response() {
459 Some(answer) => return answer,
460 None => {
461 self.build_clarification();
462 continue;
463 }
464 }
465 }
466 }
467 panic!("Valid responses must be defined for `until_acceptable()`")
468 }
469
470 fn build_prompt(&mut self) {
471 if self.show_defaults {
472 match self.default {
473 Some(Answer::YES) => self.prompt += " (Y/n)",
474 Some(Answer::NO) => self.prompt += " (y/N)",
475 Some(Answer::RESPONSE(ref s)) => {
476 self.prompt += " (";
477 self.prompt += s;
478 self.prompt += ")";
479 }
480 None => self.prompt += " (y/n)",
481 }
482 }
483 self.prompt += " ";
484 }
485
486 fn build_clarification(&mut self) {
487 if let Some(clarification) = self.clarification.clone() {
488 self.prompt = clarification;
489 self.prompt += "\n";
490 self.prompt += &self.question;
491 self.build_prompt();
492 }
493 }
494
495 fn prompt_user(&mut self, question: &str) -> Result<String, std::io::Error> {
496 let mut input = BufReader::new(&mut self.reader);
497 write!(&mut self.writer, "{}", question)?;
498 std::io::stdout().flush()?;
499 let mut s = String::new();
500 input.read_line(&mut s)?;
501 Ok(String::from(s.trim()))
502 }
503}
504
505#[derive(Eq, PartialEq, Hash, Clone, Debug)]
507pub enum Answer {
508 RESPONSE(String),
512
513 YES,
518
519 NO,
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529 use std::io::Cursor;
530
531 #[test]
532 fn default_constructor() {
533 let question = "Continue?";
534 let q = Question::new(question);
535 assert_eq!(question, q.question);
536 assert_eq!(question, q.prompt);
537 assert_eq!(None, q.default);
538 assert_eq!(None, q.acceptable);
539 assert_eq!(None, q.valid_responses);
540 assert_eq!(None, q.clarification);
541 assert_eq!(None, q.tries);
542 assert_eq!(false, q.until_acceptable);
543 assert_eq!(false, q.show_defaults);
544 assert_eq!(false, q.yes_no);
545 }
546
547 #[test]
548 fn set_default() {
549 macro_rules! default {
550 ($question:expr, $set:expr, $expected:expr) => {
551 let mut q = Question::new($question);
552 q.default($set);
553 assert_eq!($expected, q.default.unwrap());
554 };
555 }
556 let set = String::from("Yes Please!");
557 let response = String::from("Yes Please!");
558 default!("Continue?", Answer::NO, Answer::NO);
559 default!("Continue?", Answer::YES, Answer::YES);
560 default!(
561 "Continue?",
562 Answer::RESPONSE(set),
563 Answer::RESPONSE(response)
564 );
565 }
566
567 #[test]
568 fn accept() {
569 let mut q = Question::new("Continue?");
570
571 q.accept("y");
572 assert_eq!(vec!["y"], q.acceptable.unwrap());
573
574 let mut q = Question::new("Continue?");
575 q.accept("y");
576 q.accept("yes");
577 assert_eq!(vec!["y", "yes"], q.acceptable.unwrap());
578 }
579
580 #[test]
581 fn accept_ask() {
582 let response = String::from("y");
583 let input = Cursor::new(response.into_bytes());
584 let output = Cursor::new(Vec::new());
585 let actual = Question::with_cursor("y", input, output)
586 .accept("y")
587 .until_acceptable()
588 .ask();
589 assert_eq!(Some(Answer::RESPONSE(String::from("y"))), actual);
590 }
591
592 #[test]
593 fn acceptable() {
594 let mut q = Question::new("Continue?");
595
596 q.acceptable(vec!["y"]);
597 assert_eq!(vec!["y"], q.acceptable.unwrap());
598
599 let mut q = Question::new("Continue?");
600 q.accept("y");
601 q.acceptable(vec!["yes", "n", "no"]);
602 assert_eq!(vec!["y", "yes", "n", "no"], q.acceptable.unwrap());
603 }
604
605 #[test]
606 fn prompt() {
607 macro_rules! prompt {
608 ($question:expr, $user_input:expr) => {
609 let response = String::from($user_input);
610 let input = Cursor::new(response.clone().into_bytes());
611 let mut displayed_output = Cursor::new(Vec::new());
612 let result;
613
614 {
615 let mut q = Question::with_cursor($question, input, &mut displayed_output);
616 result = q.prompt_user($question).unwrap();
617 } let output =
620 String::from_utf8(displayed_output.into_inner()).expect("Not UTF-8");
621 assert_eq!($question, output);
622 assert_eq!(response, result);
623 };
624 }
625 prompt!("what is the meaning to life", "42");
626 prompt!("the universe", "42");
627 prompt!("everything", "42");
628 prompt!("Continue", "yes");
629 prompt!(
630 "What is the only manmade object visable from the moon?",
631 "The Great Wall of China"
632 );
633 }
634
635 #[test]
636 fn basic_confirm() {
637 macro_rules! confirm {
638 ($i:expr, $q:expr, $expected:expr) => {
639 let response = String::from($i);
640 let input = Cursor::new(response.into_bytes());
641 let output = Cursor::new(Vec::new());
642 let actual = Question::with_cursor($q, input, output).confirm();
643 assert_eq!($expected, actual);
644 };
645 }
646 confirm!("y", "Continue?", Answer::YES);
647 confirm!("yes", "Continue?", Answer::YES);
648 confirm!("n", "Continue?", Answer::NO);
649 confirm!("no", "Continue?", Answer::NO);
650 }
651
652 #[test]
653 fn basic_ask() {
654 macro_rules! ask {
655 ($i:expr, $q:expr, $expected:expr) => {
656 let response = String::from($i);
657 let input = Cursor::new(response.into_bytes());
658 let output = Cursor::new(Vec::new());
659 let actual = Question::with_cursor($q, input, output).ask();
660 assert_eq!(Some(Answer::RESPONSE(String::from($expected))), actual);
661 };
662 }
663
664 ask!("y\n", "Continue?", "y");
665 ask!("yes\n", "Continue?", "yes");
666 ask!("n\n", "Continue?", "n");
667 ask!("no\n", "Continue?", "no");
668 ask!("the universe,\n", "42", "the universe,");
669 ask!("and everything\n", "42", "and everything");
670 ask!(
671 "what is the meaning to life,\n",
672 "42",
673 "what is the meaning to life,"
674 );
675
676 ask!("y", "Continue?", "y");
677 ask!("yes", "Continue?", "yes");
678 ask!("n", "Continue?", "n");
679 ask!("no", "Continue?", "no");
680 ask!("the universe,", "42", "the universe,");
681 ask!("and everything", "42", "and everything");
682 ask!(
683 "what is the meaning to life,",
684 "42",
685 "what is the meaning to life,"
686 );
687 }
688
689 #[test]
690 fn set_clarification() {
691 macro_rules! confirm_clarification {
692 ($i:expr, $q:expr, $clarification:expr) => {
693 let response = String::from($i);
694 let input = Cursor::new(response.into_bytes());
695 let output = Cursor::new(Vec::new());
696 let mut q = Question::with_cursor($q, input, output);
697 q.clarification($clarification);
698 assert_eq!($clarification, q.clarification.unwrap());
699 };
700 }
701 confirm_clarification!("what is the meaning to life", "42", "14*3");
702 confirm_clarification!("Continue?", "wat", "Please respond with yes/no");
703 }
704
705 #[test]
706 fn set_max_tries() {
707 macro_rules! confirm_max_tries {
708 ($i:expr, $q:expr, $max_tries:expr) => {
709 let response = String::from($i);
710 let input = Cursor::new(response.into_bytes());
711 let output = Cursor::new(Vec::new());
712 let mut q = Question::with_cursor($q, input, output);
713 q.tries($max_tries);
714 assert_eq!($max_tries, q.tries.unwrap());
715 };
716 }
717 confirm_max_tries!("what is the meaning to life", "42", 42);
718 confirm_max_tries!("Continue?", "wat", 0x79);
719 }
720
721 #[test]
722 fn set_until_acceptable() {
723 macro_rules! confirm_until_acceptable {
724 ($i:expr, $q:expr, $until_acceptable:expr) => {
725 let response = String::from($i);
726 let input = Cursor::new(response.into_bytes());
727 let output = Cursor::new(Vec::new());
728 let mut q = Question::with_cursor($q, input, output);
729 q.until_acceptable();
730 assert_eq!($until_acceptable, q.until_acceptable);
731 };
732 }
733 confirm_until_acceptable!("what is the meaning to life", "42", true);
734 }
735
736 #[test]
737 fn set_show_defaults() {
738 macro_rules! confirm_show_defaults {
739 ($i:expr, $q:expr, $show_defaults:expr) => {
740 let response = String::from($i);
741 let input = Cursor::new(response.into_bytes());
742 let output = Cursor::new(Vec::new());
743 let mut q = Question::with_cursor($q, input, output);
744 q.show_defaults();
745 assert_eq!($show_defaults, q.show_defaults);
746 };
747 }
748 confirm_show_defaults!("what is the meaning to life", "42", true);
749 }
750
751 #[test]
752 fn set_yes_no() {
753 macro_rules! confirm_yes_no {
754 ($i:expr, $q:expr, $yes_no:expr) => {
755 let response = String::from($i);
756 let input = Cursor::new(response.into_bytes());
757 let output = Cursor::new(Vec::new());
758 let mut q = Question::with_cursor($q, input, output);
759 q.yes_no();
760 assert_eq!($yes_no, q.yes_no);
761 };
762 }
763 confirm_yes_no!("what is the meaning to life", "42", true);
764 }
765}