1use std::io::{self, BufRead};
29
30use crate::Console;
31use crate::text::Text;
32
33#[derive(Debug, Clone)]
39pub enum PromptError {
40 Io(String),
42 InvalidResponse(InvalidResponse),
44 Interrupted,
46}
47
48impl std::fmt::Display for PromptError {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 PromptError::Io(msg) => write!(f, "I/O error: {}", msg),
52 PromptError::InvalidResponse(err) => write!(f, "{}", err.message),
53 PromptError::Interrupted => write!(f, "Input interrupted"),
54 }
55 }
56}
57
58impl std::error::Error for PromptError {}
59
60impl From<io::Error> for PromptError {
61 fn from(err: io::Error) -> Self {
62 match err.kind() {
63 io::ErrorKind::UnexpectedEof | io::ErrorKind::Interrupted => PromptError::Interrupted,
64 _ => PromptError::Io(err.to_string()),
65 }
66 }
67}
68
69impl From<InvalidResponse> for PromptError {
70 fn from(err: InvalidResponse) -> Self {
71 PromptError::InvalidResponse(err)
72 }
73}
74
75#[derive(Debug, Clone)]
79pub struct InvalidResponse {
80 pub message: String,
82}
83
84impl InvalidResponse {
85 pub fn new(message: impl Into<String>) -> Self {
87 InvalidResponse {
88 message: message.into(),
89 }
90 }
91}
92
93impl std::fmt::Display for InvalidResponse {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(f, "{}", self.message)
96 }
97}
98
99impl std::error::Error for InvalidResponse {}
100
101pub type Result<T> = std::result::Result<T, PromptError>;
103
104pub trait PromptBase<T> {
110 const VALIDATE_ERROR_MESSAGE: &'static str = "[prompt.invalid]Please enter a valid value";
112
113 const ILLEGAL_CHOICE_MESSAGE: &'static str =
115 "[prompt.invalid.choice]Please select one of the available options";
116
117 const PROMPT_SUFFIX: &'static str = ": ";
119
120 fn process_response(&self, value: &str) -> std::result::Result<T, InvalidResponse>;
122
123 fn render_default(&self, default: &T) -> String;
125}
126
127pub struct Prompt {
148 prompt: String,
150 default: Option<String>,
152 choices: Option<Vec<String>>,
154 case_sensitive: bool,
156 show_default: bool,
158 show_choices: bool,
160 password: bool,
162 stream: Option<Box<dyn BufRead + Send>>,
164 pre_prompt: Option<Box<dyn Fn() + Send + Sync>>,
166}
167
168impl Default for Prompt {
169 fn default() -> Self {
170 Self::new("")
171 }
172}
173
174impl Prompt {
175 pub fn new(prompt: impl Into<String>) -> Self {
177 Prompt {
178 prompt: prompt.into(),
179 default: None,
180 choices: None,
181 case_sensitive: true,
182 show_default: true,
183 show_choices: true,
184 password: false,
185 stream: None,
186 pre_prompt: None,
187 }
188 }
189
190 pub fn with_default(mut self, default: impl Into<String>) -> Self {
192 self.default = Some(default.into());
193 self
194 }
195
196 pub fn with_choices(mut self, choices: &[&str]) -> Self {
198 self.choices = Some(choices.iter().map(|s| s.to_string()).collect());
199 self
200 }
201
202 pub fn case_sensitive(mut self, sensitive: bool) -> Self {
204 self.case_sensitive = sensitive;
205 self
206 }
207
208 pub fn show_default(mut self, show: bool) -> Self {
210 self.show_default = show;
211 self
212 }
213
214 pub fn show_choices(mut self, show: bool) -> Self {
216 self.show_choices = show;
217 self
218 }
219
220 pub fn password(mut self, is_password: bool) -> Self {
222 self.password = is_password;
223 self
224 }
225
226 pub fn with_stream(mut self, stream: impl BufRead + Send + 'static) -> Self {
230 self.stream = Some(Box::new(stream));
231 self
232 }
233
234 pub fn with_pre_prompt(mut self, f: impl Fn() + Send + Sync + 'static) -> Self {
236 self.pre_prompt = Some(Box::new(f));
237 self
238 }
239
240 pub fn ask(prompt: impl Into<String>) -> Result<String> {
248 Prompt::new(prompt).run()
249 }
250
251 pub fn has_stream(&self) -> bool {
253 self.stream.is_some()
254 }
255
256 pub fn run(&mut self) -> Result<String> {
258 let mut console = Console::new();
259 self.run_with_console(&mut console)
260 }
261
262 pub fn run_with_console(&mut self, console: &mut Console) -> Result<String> {
264 loop {
265 if let Some(ref pre_prompt) = self.pre_prompt {
266 pre_prompt();
267 }
268
269 let prompt_text = self.make_prompt();
270
271 let value = if let Some(ref mut stream) = self.stream {
272 let _ = console.print(&prompt_text, None, None, None, false, "");
274 let mut line = String::new();
275 stream.read_line(&mut line).map_err(PromptError::from)?;
276 if line.is_empty() {
277 return Err(PromptError::Interrupted);
278 }
279 line.trim_end_matches('\n')
280 .trim_end_matches('\r')
281 .to_string()
282 } else {
283 console.input(&prompt_text, self.password)?
284 };
285
286 if value.is_empty() {
288 if let Some(ref default) = self.default {
289 return Ok(default.clone());
290 }
291 }
292
293 match self.process_response(&value) {
295 Ok(result) => return Ok(result),
296 Err(err) => {
297 let error_text = Text::from_markup(&err.message, false)
299 .unwrap_or_else(|_| Text::plain(&err.message));
300 let _ = console.print(&error_text, None, None, None, false, "\n");
301 }
302 }
303 }
304 }
305
306 fn make_prompt(&self) -> Text {
308 let mut parts = vec![self.prompt.clone()];
309
310 if self.show_choices {
312 if let Some(ref choices) = self.choices {
313 let choices_str = choices.join("/");
314 parts.push(format!(" [prompt.choices]\\[{}][/]", choices_str));
315 }
316 }
317
318 if self.show_default {
320 if let Some(ref default) = self.default {
321 parts.push(format!(" [prompt.default]({})[/]", default));
322 }
323 }
324
325 parts.push(": ".to_string());
327
328 let markup = parts.join("");
329 Text::from_markup(&markup, false).unwrap_or_else(|_| Text::plain(&markup))
330 }
331
332 fn check_choice(&self, value: &str) -> bool {
334 if let Some(ref choices) = self.choices {
335 if self.case_sensitive {
336 choices.iter().any(|c| c == value)
337 } else {
338 let value_lower = value.to_lowercase();
339 choices.iter().any(|c| c.to_lowercase() == value_lower)
340 }
341 } else {
342 true
343 }
344 }
345
346 fn get_original_choice(&self, value: &str) -> String {
348 if let Some(ref choices) = self.choices {
349 if !self.case_sensitive {
350 let value_lower = value.to_lowercase();
351 for choice in choices {
352 if choice.to_lowercase() == value_lower {
353 return choice.clone();
354 }
355 }
356 }
357 }
358 value.to_string()
359 }
360
361 fn process_response(&self, value: &str) -> std::result::Result<String, InvalidResponse> {
363 let value = value.trim();
364
365 if self.choices.is_some() && !self.check_choice(value) {
367 return Err(InvalidResponse::new(
368 "[prompt.invalid.choice]Please select one of the available options",
369 ));
370 }
371
372 if !self.case_sensitive && self.choices.is_some() {
374 Ok(self.get_original_choice(value))
375 } else {
376 Ok(value.to_string())
377 }
378 }
379}
380
381pub struct IntPrompt {
400 prompt: String,
402 default: Option<i64>,
404 show_default: bool,
406 choices: Option<Vec<i64>>,
408 stream: Option<Box<dyn BufRead + Send>>,
410 pre_prompt: Option<Box<dyn Fn() + Send + Sync>>,
412}
413
414impl Default for IntPrompt {
415 fn default() -> Self {
416 Self::new("")
417 }
418}
419
420impl IntPrompt {
421 pub fn new(prompt: impl Into<String>) -> Self {
423 IntPrompt {
424 prompt: prompt.into(),
425 default: None,
426 show_default: true,
427 choices: None,
428 stream: None,
429 pre_prompt: None,
430 }
431 }
432
433 pub fn with_default(mut self, default: i64) -> Self {
435 self.default = Some(default);
436 self
437 }
438
439 pub fn show_default(mut self, show: bool) -> Self {
441 self.show_default = show;
442 self
443 }
444
445 pub fn with_choices(mut self, choices: Vec<i64>) -> Self {
447 self.choices = Some(choices);
448 self
449 }
450
451 pub fn with_stream(mut self, stream: impl BufRead + Send + 'static) -> Self {
453 self.stream = Some(Box::new(stream));
454 self
455 }
456
457 pub fn with_pre_prompt(mut self, f: impl Fn() + Send + Sync + 'static) -> Self {
459 self.pre_prompt = Some(Box::new(f));
460 self
461 }
462
463 pub fn ask(prompt: impl Into<String>) -> Result<i64> {
471 IntPrompt::new(prompt).run()
472 }
473
474 pub fn run(&mut self) -> Result<i64> {
476 let mut console = Console::new();
477 self.run_with_console(&mut console)
478 }
479
480 pub fn run_with_console(&mut self, console: &mut Console) -> Result<i64> {
482 loop {
483 if let Some(ref pre_prompt) = self.pre_prompt {
484 pre_prompt();
485 }
486
487 let prompt_text = self.make_prompt();
488
489 let value = if let Some(ref mut stream) = self.stream {
490 let _ = console.print(&prompt_text, None, None, None, false, "");
491 let mut line = String::new();
492 stream.read_line(&mut line).map_err(PromptError::from)?;
493 if line.is_empty() {
494 return Err(PromptError::Interrupted);
495 }
496 line.trim_end_matches('\n')
497 .trim_end_matches('\r')
498 .to_string()
499 } else {
500 console.input(&prompt_text, false)?
501 };
502
503 if value.is_empty() {
505 if let Some(default) = self.default {
506 return Ok(default);
507 }
508 }
509
510 match self.process_response(&value) {
512 Ok(result) => return Ok(result),
513 Err(err) => {
514 let error_text = Text::from_markup(&err.message, false)
516 .unwrap_or_else(|_| Text::plain(&err.message));
517 let _ = console.print(&error_text, None, None, None, false, "\n");
518 }
519 }
520 }
521 }
522
523 fn make_prompt(&self) -> Text {
525 let mut parts = vec![self.prompt.clone()];
526
527 if let Some(ref choices) = self.choices {
529 let choices_str: Vec<String> = choices.iter().map(|c| c.to_string()).collect();
530 parts.push(format!(" [prompt.choices]\\[{}][/]", choices_str.join("/")));
531 }
532
533 if self.show_default {
535 if let Some(default) = self.default {
536 parts.push(format!(" [prompt.default]({})[/]", default));
537 }
538 }
539
540 parts.push(": ".to_string());
542
543 let markup = parts.join("");
544 Text::from_markup(&markup, false).unwrap_or_else(|_| Text::plain(&markup))
545 }
546
547 fn process_response(&self, value: &str) -> std::result::Result<i64, InvalidResponse> {
549 let value = value.trim();
550 let parsed = value.parse::<i64>().map_err(|_| {
551 InvalidResponse::new("[prompt.invalid]Please enter a valid integer number")
552 })?;
553 if let Some(ref choices) = self.choices {
554 if !choices.contains(&parsed) {
555 return Err(InvalidResponse::new(
556 "[prompt.invalid.choice]Please select one of the available options",
557 ));
558 }
559 }
560 Ok(parsed)
561 }
562}
563
564pub struct FloatPrompt {
583 prompt: String,
585 default: Option<f64>,
587 show_default: bool,
589 choices: Option<Vec<f64>>,
591 stream: Option<Box<dyn BufRead + Send>>,
593 pre_prompt: Option<Box<dyn Fn() + Send + Sync>>,
595}
596
597impl Default for FloatPrompt {
598 fn default() -> Self {
599 Self::new("")
600 }
601}
602
603impl FloatPrompt {
604 pub fn new(prompt: impl Into<String>) -> Self {
606 FloatPrompt {
607 prompt: prompt.into(),
608 default: None,
609 show_default: true,
610 choices: None,
611 stream: None,
612 pre_prompt: None,
613 }
614 }
615
616 pub fn with_default(mut self, default: f64) -> Self {
618 self.default = Some(default);
619 self
620 }
621
622 pub fn show_default(mut self, show: bool) -> Self {
624 self.show_default = show;
625 self
626 }
627
628 pub fn with_choices(mut self, choices: Vec<f64>) -> Self {
630 self.choices = Some(choices);
631 self
632 }
633
634 pub fn with_stream(mut self, stream: impl BufRead + Send + 'static) -> Self {
636 self.stream = Some(Box::new(stream));
637 self
638 }
639
640 pub fn with_pre_prompt(mut self, f: impl Fn() + Send + Sync + 'static) -> Self {
642 self.pre_prompt = Some(Box::new(f));
643 self
644 }
645
646 pub fn ask(prompt: impl Into<String>) -> Result<f64> {
654 FloatPrompt::new(prompt).run()
655 }
656
657 pub fn run(&mut self) -> Result<f64> {
659 let mut console = Console::new();
660 self.run_with_console(&mut console)
661 }
662
663 pub fn run_with_console(&mut self, console: &mut Console) -> Result<f64> {
665 loop {
666 if let Some(ref pre_prompt) = self.pre_prompt {
667 pre_prompt();
668 }
669
670 let prompt_text = self.make_prompt();
671
672 let value = if let Some(ref mut stream) = self.stream {
673 let _ = console.print(&prompt_text, None, None, None, false, "");
674 let mut line = String::new();
675 stream.read_line(&mut line).map_err(PromptError::from)?;
676 if line.is_empty() {
677 return Err(PromptError::Interrupted);
678 }
679 line.trim_end_matches('\n')
680 .trim_end_matches('\r')
681 .to_string()
682 } else {
683 console.input(&prompt_text, false)?
684 };
685
686 if value.is_empty() {
688 if let Some(default) = self.default {
689 return Ok(default);
690 }
691 }
692
693 match self.process_response(&value) {
695 Ok(result) => return Ok(result),
696 Err(err) => {
697 let error_text = Text::from_markup(&err.message, false)
699 .unwrap_or_else(|_| Text::plain(&err.message));
700 let _ = console.print(&error_text, None, None, None, false, "\n");
701 }
702 }
703 }
704 }
705
706 fn make_prompt(&self) -> Text {
708 let mut parts = vec![self.prompt.clone()];
709
710 if let Some(ref choices) = self.choices {
712 let choices_str: Vec<String> = choices.iter().map(|c| c.to_string()).collect();
713 parts.push(format!(" [prompt.choices]\\[{}][/]", choices_str.join("/")));
714 }
715
716 if self.show_default {
718 if let Some(default) = self.default {
719 parts.push(format!(" [prompt.default]({})[/]", default));
720 }
721 }
722
723 parts.push(": ".to_string());
725
726 let markup = parts.join("");
727 Text::from_markup(&markup, false).unwrap_or_else(|_| Text::plain(&markup))
728 }
729
730 fn process_response(&self, value: &str) -> std::result::Result<f64, InvalidResponse> {
732 let value = value.trim();
733 let parsed = value
734 .parse::<f64>()
735 .map_err(|_| InvalidResponse::new("[prompt.invalid]Please enter a number"))?;
736 if let Some(ref choices) = self.choices {
737 if !choices.iter().any(|c| (*c - parsed).abs() < f64::EPSILON) {
738 return Err(InvalidResponse::new(
739 "[prompt.invalid.choice]Please select one of the available options",
740 ));
741 }
742 }
743 Ok(parsed)
744 }
745}
746
747pub struct Confirm {
768 prompt: String,
770 default: Option<bool>,
772 show_default: bool,
774 yes_choice: String,
776 no_choice: String,
778 stream: Option<Box<dyn BufRead + Send>>,
780 pre_prompt: Option<Box<dyn Fn() + Send + Sync>>,
782}
783
784impl Default for Confirm {
785 fn default() -> Self {
786 Self::new("")
787 }
788}
789
790impl Confirm {
791 pub fn new(prompt: impl Into<String>) -> Self {
793 Confirm {
794 prompt: prompt.into(),
795 default: None,
796 show_default: true,
797 yes_choice: "y".to_string(),
798 no_choice: "n".to_string(),
799 stream: None,
800 pre_prompt: None,
801 }
802 }
803
804 pub fn with_default(mut self, default: bool) -> Self {
806 self.default = Some(default);
807 self
808 }
809
810 pub fn show_default(mut self, show: bool) -> Self {
812 self.show_default = show;
813 self
814 }
815
816 pub fn with_choices(mut self, yes: impl Into<String>, no: impl Into<String>) -> Self {
818 self.yes_choice = yes.into();
819 self.no_choice = no.into();
820 self
821 }
822
823 pub fn with_stream(mut self, stream: impl BufRead + Send + 'static) -> Self {
825 self.stream = Some(Box::new(stream));
826 self
827 }
828
829 pub fn with_pre_prompt(mut self, f: impl Fn() + Send + Sync + 'static) -> Self {
831 self.pre_prompt = Some(Box::new(f));
832 self
833 }
834
835 pub fn ask(prompt: impl Into<String>) -> Result<bool> {
845 Confirm::new(prompt).run()
846 }
847
848 pub fn run(&mut self) -> Result<bool> {
850 let mut console = Console::new();
851 self.run_with_console(&mut console)
852 }
853
854 pub fn run_with_console(&mut self, console: &mut Console) -> Result<bool> {
856 loop {
857 if let Some(ref pre_prompt) = self.pre_prompt {
858 pre_prompt();
859 }
860
861 let prompt_text = self.make_prompt();
862
863 let value = if let Some(ref mut stream) = self.stream {
864 let _ = console.print(&prompt_text, None, None, None, false, "");
865 let mut line = String::new();
866 stream.read_line(&mut line).map_err(PromptError::from)?;
867 if line.is_empty() {
868 return Err(PromptError::Interrupted);
869 }
870 line.trim_end_matches('\n')
871 .trim_end_matches('\r')
872 .to_string()
873 } else {
874 console.input(&prompt_text, false)?
875 };
876
877 if value.is_empty() {
879 if let Some(default) = self.default {
880 return Ok(default);
881 }
882 }
883
884 match self.process_response(&value) {
886 Ok(result) => return Ok(result),
887 Err(err) => {
888 let error_text = Text::from_markup(&err.message, false)
890 .unwrap_or_else(|_| Text::plain(&err.message));
891 let _ = console.print(&error_text, None, None, None, false, "\n");
892 }
893 }
894 }
895 }
896
897 fn make_prompt(&self) -> Text {
899 let mut parts = vec![self.prompt.clone()];
900
901 let choices_str = format!("{}/{}", self.yes_choice, self.no_choice);
903 parts.push(format!(" [prompt.choices]\\[{}][/]", choices_str));
904
905 if self.show_default {
907 if let Some(default) = self.default {
908 let default_str = if default {
909 &self.yes_choice
910 } else {
911 &self.no_choice
912 };
913 parts.push(format!(" [prompt.default]({})[/]", default_str));
914 }
915 }
916
917 parts.push(": ".to_string());
919
920 let markup = parts.join("");
921 Text::from_markup(&markup, false).unwrap_or_else(|_| Text::plain(&markup))
922 }
923
924 fn process_response(&self, value: &str) -> std::result::Result<bool, InvalidResponse> {
926 let value = value.trim().to_lowercase();
927 let yes_lower = self.yes_choice.to_lowercase();
928 let no_lower = self.no_choice.to_lowercase();
929
930 if value == yes_lower {
931 Ok(true)
932 } else if value == no_lower {
933 Ok(false)
934 } else {
935 Err(InvalidResponse::new("[prompt.invalid]Please enter Y or N"))
936 }
937 }
938}
939
940#[cfg(test)]
945mod tests {
946 use super::*;
947
948 #[test]
949 fn test_prompt_new() {
950 let prompt = Prompt::new("Enter name");
951 assert_eq!(prompt.prompt, "Enter name");
952 assert!(prompt.default.is_none());
953 assert!(prompt.choices.is_none());
954 assert!(prompt.case_sensitive);
955 assert!(prompt.show_default);
956 assert!(prompt.show_choices);
957 assert!(!prompt.password);
958 assert!(!prompt.has_stream());
959 }
960
961 #[test]
962 fn test_prompt_with_options() {
963 let prompt = Prompt::new("Choose")
964 .with_default("Alice")
965 .with_choices(&["Alice", "Bob", "Charlie"])
966 .case_sensitive(false)
967 .password(true);
968
969 assert_eq!(prompt.default, Some("Alice".to_string()));
970 assert_eq!(
971 prompt.choices,
972 Some(vec![
973 "Alice".to_string(),
974 "Bob".to_string(),
975 "Charlie".to_string()
976 ])
977 );
978 assert!(!prompt.case_sensitive);
979 assert!(prompt.password);
980 }
981
982 #[test]
983 fn test_prompt_check_choice_case_sensitive() {
984 let prompt = Prompt::new("Choose")
985 .with_choices(&["Alice", "Bob"])
986 .case_sensitive(true);
987
988 assert!(prompt.check_choice("Alice"));
989 assert!(prompt.check_choice("Bob"));
990 assert!(!prompt.check_choice("alice"));
991 assert!(!prompt.check_choice("Charlie"));
992 }
993
994 #[test]
995 fn test_prompt_check_choice_case_insensitive() {
996 let prompt = Prompt::new("Choose")
997 .with_choices(&["Alice", "Bob"])
998 .case_sensitive(false);
999
1000 assert!(prompt.check_choice("Alice"));
1001 assert!(prompt.check_choice("alice"));
1002 assert!(prompt.check_choice("ALICE"));
1003 assert!(!prompt.check_choice("Charlie"));
1004 }
1005
1006 #[test]
1007 fn test_prompt_get_original_choice() {
1008 let prompt = Prompt::new("Choose")
1009 .with_choices(&["Alice", "Bob"])
1010 .case_sensitive(false);
1011
1012 assert_eq!(prompt.get_original_choice("alice"), "Alice");
1013 assert_eq!(prompt.get_original_choice("ALICE"), "Alice");
1014 assert_eq!(prompt.get_original_choice("bob"), "Bob");
1015 }
1016
1017 #[test]
1018 fn test_prompt_process_response_valid() {
1019 let prompt = Prompt::new("Enter name");
1020 let result = prompt.process_response(" John ");
1021 assert_eq!(result.unwrap(), "John");
1022 }
1023
1024 #[test]
1025 fn test_prompt_process_response_invalid_choice() {
1026 let prompt = Prompt::new("Choose").with_choices(&["Alice", "Bob"]);
1027 let result = prompt.process_response("Charlie");
1028 assert!(result.is_err());
1029 }
1030
1031 #[test]
1032 fn test_int_prompt_new() {
1033 let prompt = IntPrompt::new("Enter number");
1034 assert_eq!(prompt.prompt, "Enter number");
1035 assert!(prompt.default.is_none());
1036 assert!(prompt.show_default);
1037 assert!(prompt.choices.is_none());
1038 }
1039
1040 #[test]
1041 fn test_int_prompt_with_default() {
1042 let prompt = IntPrompt::new("Enter number").with_default(42);
1043 assert_eq!(prompt.default, Some(42));
1044 }
1045
1046 #[test]
1047 fn test_int_prompt_with_choices() {
1048 let prompt = IntPrompt::new("Pick").with_choices(vec![1, 2, 3]);
1049 assert_eq!(prompt.choices, Some(vec![1, 2, 3]));
1050 assert!(prompt.process_response("2").is_ok());
1052 assert!(prompt.process_response("5").is_err());
1054 }
1055
1056 #[test]
1057 fn test_int_prompt_process_response_valid() {
1058 let prompt = IntPrompt::new("Enter number");
1059 assert_eq!(prompt.process_response("42").unwrap(), 42);
1060 assert_eq!(prompt.process_response(" -10 ").unwrap(), -10);
1061 }
1062
1063 #[test]
1064 fn test_int_prompt_process_response_invalid() {
1065 let prompt = IntPrompt::new("Enter number");
1066 assert!(prompt.process_response("abc").is_err());
1067 assert!(prompt.process_response("3.14").is_err());
1068 }
1069
1070 #[test]
1071 fn test_float_prompt_new() {
1072 let prompt = FloatPrompt::new("Enter number");
1073 assert_eq!(prompt.prompt, "Enter number");
1074 assert!(prompt.default.is_none());
1075 assert!(prompt.choices.is_none());
1076 }
1077
1078 #[test]
1079 fn test_float_prompt_with_default() {
1080 let prompt = FloatPrompt::new("Enter number").with_default(3.125);
1081 assert_eq!(prompt.default, Some(3.125));
1082 }
1083
1084 #[test]
1085 fn test_float_prompt_with_choices() {
1086 let prompt = FloatPrompt::new("Pick").with_choices(vec![1.0, 2.5, 3.125]);
1087 assert!(prompt.process_response("2.5").is_ok());
1089 assert!(prompt.process_response("9.9").is_err());
1091 }
1092
1093 #[test]
1094 fn test_float_prompt_process_response_valid() {
1095 let prompt = FloatPrompt::new("Enter number");
1096 assert!((prompt.process_response("3.125").unwrap() - 3.125).abs() < f64::EPSILON);
1097 assert!((prompt.process_response(" -2.5 ").unwrap() - (-2.5)).abs() < f64::EPSILON);
1098 assert!((prompt.process_response("42").unwrap() - 42.0).abs() < f64::EPSILON);
1099 }
1100
1101 #[test]
1102 fn test_float_prompt_process_response_invalid() {
1103 let prompt = FloatPrompt::new("Enter number");
1104 assert!(prompt.process_response("abc").is_err());
1105 }
1106
1107 #[test]
1108 fn test_confirm_new() {
1109 let confirm = Confirm::new("Continue?");
1110 assert_eq!(confirm.prompt, "Continue?");
1111 assert!(confirm.default.is_none());
1112 assert_eq!(confirm.yes_choice, "y");
1113 assert_eq!(confirm.no_choice, "n");
1114 assert!(confirm.stream.is_none());
1115 }
1116
1117 #[test]
1118 fn test_confirm_with_default() {
1119 let confirm = Confirm::new("Continue?").with_default(true);
1120 assert_eq!(confirm.default, Some(true));
1121 }
1122
1123 #[test]
1124 fn test_confirm_with_choices() {
1125 let confirm = Confirm::new("Continue?").with_choices("yes", "no");
1126 assert_eq!(confirm.yes_choice, "yes");
1127 assert_eq!(confirm.no_choice, "no");
1128 }
1129
1130 #[test]
1131 fn test_confirm_process_response_yes() {
1132 let confirm = Confirm::new("Continue?");
1133 assert!(confirm.process_response("y").unwrap());
1134 assert!(confirm.process_response("Y").unwrap());
1135 }
1136
1137 #[test]
1138 fn test_confirm_process_response_no() {
1139 let confirm = Confirm::new("Continue?");
1140 assert!(!confirm.process_response("n").unwrap());
1141 assert!(!confirm.process_response("N").unwrap());
1142 }
1143
1144 #[test]
1145 fn test_confirm_process_response_invalid() {
1146 let confirm = Confirm::new("Continue?");
1147 assert!(confirm.process_response("x").is_err());
1148 assert!(confirm.process_response("yes").is_err()); }
1150
1151 #[test]
1152 fn test_confirm_custom_choices() {
1153 let confirm = Confirm::new("Continue?").with_choices("yes", "no");
1154 assert!(confirm.process_response("yes").unwrap());
1155 assert!(!confirm.process_response("no").unwrap());
1156 assert!(confirm.process_response("y").is_err()); }
1158
1159 #[test]
1160 fn test_invalid_response() {
1161 let err = InvalidResponse::new("Test error");
1162 assert_eq!(err.message, "Test error");
1163 assert_eq!(format!("{}", err), "Test error");
1164 }
1165
1166 #[test]
1167 fn test_prompt_error_display() {
1168 let io_err = PromptError::Io("test error".to_string());
1169 assert!(format!("{}", io_err).contains("test error"));
1170
1171 let invalid_err = PromptError::InvalidResponse(InvalidResponse::new("invalid"));
1172 assert!(format!("{}", invalid_err).contains("invalid"));
1173
1174 let interrupted = PromptError::Interrupted;
1175 assert!(format!("{}", interrupted).contains("interrupted"));
1176 }
1177
1178 #[test]
1179 fn test_prompt_with_stream() {
1180 let input = std::io::Cursor::new(b"Alice\n");
1181 let mut prompt = Prompt::new("Name")
1182 .with_stream(input)
1183 .with_choices(&["Alice", "Bob"]);
1184 let mut console = Console::new();
1185 let result = prompt.run_with_console(&mut console).unwrap();
1186 assert_eq!(result, "Alice");
1187 }
1188
1189 #[test]
1190 fn test_prompt_make_prompt() {
1191 let prompt = Prompt::new("Enter name")
1192 .with_default("John")
1193 .with_choices(&["John", "Jane"]);
1194
1195 let text = prompt.make_prompt();
1196 let plain = text.plain_text();
1197 assert!(plain.contains("Enter name"));
1198 assert!(plain.contains(":")); }
1200}