1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6pub mod prelude {
8 pub use crate::{
9 ConfirmationParseError, PromptText, PromptTextError, YesNoAnswer, is_no, is_yes,
10 parse_confirmation,
11 };
12}
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum PromptTextError {
17 Empty,
19}
20
21impl fmt::Display for PromptTextError {
22 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 Self::Empty => formatter.write_str("prompt text cannot be empty"),
25 }
26 }
27}
28
29impl std::error::Error for PromptTextError {}
30
31#[derive(Clone, Debug, PartialEq, Eq, Hash)]
33pub struct PromptText {
34 text: String,
35}
36
37impl PromptText {
38 pub fn new(text: impl Into<String>) -> Result<Self, PromptTextError> {
44 let text = text.into();
45 if text.trim().is_empty() {
46 Err(PromptTextError::Empty)
47 } else {
48 Ok(Self { text })
49 }
50 }
51
52 #[must_use]
54 pub fn as_str(&self) -> &str {
55 &self.text
56 }
57
58 #[must_use]
60 pub fn into_string(self) -> String {
61 self.text
62 }
63}
64
65impl AsRef<str> for PromptText {
66 fn as_ref(&self) -> &str {
67 self.as_str()
68 }
69}
70
71impl fmt::Display for PromptText {
72 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73 formatter.write_str(&self.text)
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
79pub enum YesNoAnswer {
80 Yes,
82 No,
84}
85
86impl YesNoAnswer {
87 #[must_use]
89 pub const fn is_yes(self) -> bool {
90 matches!(self, Self::Yes)
91 }
92
93 #[must_use]
95 pub const fn is_no(self) -> bool {
96 matches!(self, Self::No)
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum ConfirmationParseError {
103 Empty,
105 Unrecognized,
107}
108
109impl fmt::Display for ConfirmationParseError {
110 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 Self::Empty => formatter.write_str("confirmation answer cannot be empty"),
113 Self::Unrecognized => formatter.write_str("confirmation answer must be yes or no"),
114 }
115 }
116}
117
118impl std::error::Error for ConfirmationParseError {}
119
120pub fn parse_confirmation(input: &str) -> Result<YesNoAnswer, ConfirmationParseError> {
126 let trimmed = input.trim();
127 if trimmed.is_empty() {
128 return Err(ConfirmationParseError::Empty);
129 }
130
131 match trimmed.to_ascii_lowercase().as_str() {
132 "y" | "yes" => Ok(YesNoAnswer::Yes),
133 "n" | "no" => Ok(YesNoAnswer::No),
134 _ => Err(ConfirmationParseError::Unrecognized),
135 }
136}
137
138#[must_use]
140pub fn is_yes(input: &str) -> bool {
141 matches!(parse_confirmation(input), Ok(YesNoAnswer::Yes))
142}
143
144#[must_use]
146pub fn is_no(input: &str) -> bool {
147 matches!(parse_confirmation(input), Ok(YesNoAnswer::No))
148}
149
150#[cfg(test)]
151mod tests {
152 use super::{
153 ConfirmationParseError, PromptText, PromptTextError, YesNoAnswer, is_no, is_yes,
154 parse_confirmation,
155 };
156
157 #[test]
158 fn validates_prompt_text() -> Result<(), PromptTextError> {
159 let prompt = PromptText::new("Continue?")?;
160
161 assert_eq!(prompt.as_str(), "Continue?");
162 assert_eq!(PromptText::new(" "), Err(PromptTextError::Empty));
163 Ok(())
164 }
165
166 #[test]
167 fn parses_confirmation_answers() -> Result<(), ConfirmationParseError> {
168 assert_eq!(parse_confirmation("yes")?, YesNoAnswer::Yes);
169 assert_eq!(parse_confirmation(" N ")?, YesNoAnswer::No);
170 assert!(is_yes("y"));
171 assert!(is_no("no"));
172 assert_eq!(
173 parse_confirmation("maybe"),
174 Err(ConfirmationParseError::Unrecognized)
175 );
176 Ok(())
177 }
178}