casual/
lib.rs

1//! Simple crate for parsing user input.
2//!
3//! # Examples
4//!
5//! Rust type inference is used to know what to return.
6//!
7//! ```no_run
8//! let username: String = casual::prompt("Please enter your name: ").get();
9//! ```
10//!
11//! [`FromStr`] is used to parse the input, so you can read any type that
12//! implements [`FromStr`].
13//!
14//! ```no_run
15//! let age: u32 = casual::prompt("Please enter your age: ").get();
16//! ```
17//!
18//! [`.matches()`] can be used to validate the input data.
19//!
20//! ```no_run
21//! let age: u32 = casual::prompt("Please enter your age again: ")
22//!     .matches(|x| *x < 120)
23//!     .get();
24//! ```
25//!
26//! A convenience function [`confirm`] is provided for getting a yes or no
27//! answer.
28//!
29//! ```no_run
30//! if casual::confirm("Are you sure you want to continue?") {
31//!     // continue
32//! } else {
33//!     panic!("Aborted!");
34//! }
35//! ```
36//!
37//! [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
38//! [`.matches()`]: struct.Input.html#method.matches
39//! [`confirm`]: fn.confirm.html
40
41use std::fmt::{self, Debug, Display};
42use std::io::{self, Write};
43use std::str::FromStr;
44
45/////////////////////////////////////////////////////////////////////////
46// Definitions
47/////////////////////////////////////////////////////////////////////////
48
49/// A validator for user input.
50struct Validator<T> {
51    raw: Box<dyn Fn(&T) -> bool + 'static>,
52}
53
54/// An input builder.
55pub struct Input<T> {
56    prompt: Option<String>,
57    prefix: Option<String>,
58    suffix: Option<String>,
59    default: Option<T>,
60    validator: Option<Validator<T>>,
61}
62
63/////////////////////////////////////////////////////////////////////////
64// Implementations
65/////////////////////////////////////////////////////////////////////////
66
67impl<T> Validator<T> {
68    /// Construct a new `Validator`.
69    fn new<F>(raw: F) -> Self
70    where
71        F: Fn(&T) -> bool + 'static,
72    {
73        Self { raw: Box::new(raw) }
74    }
75
76    /// Run the validator on the given input.
77    fn run(&self, input: &T) -> bool {
78        (self.raw)(input)
79    }
80}
81
82impl<T: Debug> Debug for Input<T> {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        f.debug_struct("Input")
85            .field("prefix", &self.prefix)
86            .field("prompt", &self.prompt)
87            .field("suffix", &self.suffix)
88            .field("default", &self.default)
89            .finish() // FIXME rust-lang/rust#67364:
90                      // use .finish_non_exhaustive() when it's stabilized
91    }
92}
93
94impl<T> Default for Input<T> {
95    /// Construct a new empty `Input`.
96    ///
97    /// Identical to [`Input::new()`](struct.Input.html#method.new).
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103impl<T> Input<T> {
104    /// Construct a new empty `Input`.
105    ///
106    /// Identical to [`Input::default()`](struct.Input.html#impl-Default).
107    pub fn new() -> Self {
108        Self {
109            prefix: None,
110            prompt: None,
111            suffix: None,
112            default: None,
113            validator: None,
114        }
115    }
116
117    /// Set the prompt to display before waiting for user input.
118    pub fn prompt<S: Into<String>>(mut self, prompt: S) -> Self {
119        self.prompt = Some(prompt.into());
120        self
121    }
122
123    /// Set the prompt prefix.
124    pub fn prefix<S: Into<String>>(mut self, prefix: S) -> Self {
125        self.prefix = Some(prefix.into());
126        self
127    }
128
129    /// Set the prompt suffix.
130    pub fn suffix<S: Into<String>>(mut self, suffix: S) -> Self {
131        self.suffix = Some(suffix.into());
132        self
133    }
134
135    /// Set the default value.
136    ///
137    /// If set, this will be returned in the event the user enters an empty
138    /// input.
139    pub fn default(mut self, default: T) -> Self {
140        self.default = Some(default);
141        self
142    }
143
144    /// Check input values.
145    ///
146    /// If set, this function will be called on the parsed user input and only
147    /// if it passes will we return the value.
148    ///
149    /// # Examples
150    ///
151    /// ```no_run
152    /// # use casual::Input;
153    /// let num: u32 = Input::new().matches(|x| *x != 10).get();
154    /// ```
155    pub fn matches<F>(mut self, matches: F) -> Self
156    where
157        F: Fn(&T) -> bool + 'static,
158    {
159        self.validator = Some(Validator::new(matches));
160        self
161    }
162}
163
164fn read_line(prompt: &Option<String>) -> io::Result<String> {
165    if let Some(prompt) = prompt {
166        let mut stdout = io::stdout();
167        stdout.write_all(prompt.as_bytes())?;
168        stdout.flush()?;
169    }
170    let mut result = String::new();
171    io::stdin().read_line(&mut result)?;
172    Ok(result)
173}
174
175impl<T> Input<T>
176where
177    T: FromStr,
178    <T as FromStr>::Err: Display,
179{
180    fn try_get_with<F>(self, read_line: F) -> io::Result<T>
181    where
182        F: Fn(&Option<String>) -> io::Result<String>,
183    {
184        let Self {
185            prompt,
186            prefix,
187            suffix,
188            default,
189            validator,
190        } = self;
191
192        let prompt = prompt.map(move |prompt| {
193            let mut p = String::new();
194            if let Some(prefix) = prefix {
195                p.push_str(&prefix);
196            }
197            p.push_str(&prompt);
198            if let Some(suffix) = suffix {
199                p.push_str(&suffix);
200            }
201            p
202        });
203
204        Ok(loop {
205            match read_line(&prompt)?.trim() {
206                "" => {
207                    if let Some(default) = default {
208                        break default;
209                    } else {
210                        continue;
211                    }
212                }
213                raw => match raw.parse() {
214                    Ok(result) => {
215                        if let Some(validator) = &validator {
216                            if !validator.run(&result) {
217                                println!("Error: invalid input");
218                                continue;
219                            }
220                        }
221                        break result;
222                    }
223                    Err(err) => {
224                        println!("Error: {}", err);
225                        continue;
226                    }
227                },
228            }
229        })
230    }
231
232    #[inline]
233    fn try_get(self) -> io::Result<T> {
234        self.try_get_with(read_line)
235    }
236
237    /// Consumes the `Input` and reads the input from the user.
238    ///
239    /// This function uses [`FromStr`] to parse the input data.
240    ///
241    /// ```no_run
242    /// # use casual::Input;
243    /// let num: u32 = Input::new().prompt("Enter a number: ").get();
244    /// ```
245    ///
246    /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
247    pub fn get(self) -> T {
248        self.try_get().unwrap()
249    }
250
251    /// Consumes the `Input` and applies the given function to it.
252    ///
253    /// This function uses [`FromStr`] to parse the input data. The result is
254    /// then fed to the given closure.
255    ///
256    /// ```no_run
257    /// # use casual::Input;
258    /// let value = Input::new().map(|s: String| &s.to_lowercase() == "yes");
259    /// ```
260    ///
261    /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
262    pub fn map<F, U>(self, map: F) -> U
263    where
264        F: Fn(T) -> U,
265    {
266        map(self.get())
267    }
268}
269
270/////////////////////////////////////////////////////////////////////////
271// Shortcut functions
272/////////////////////////////////////////////////////////////////////////
273
274/// Returns a new empty `Input`.
275///
276/// # Examples
277///
278/// Read in something without any prompt.
279///
280/// ```no_run
281/// # use casual::input;
282/// let data: String = input().get();
283/// ```
284pub fn input<T>() -> Input<T> {
285    Input::new()
286}
287
288/// Returns an `Input` that prompts the user for input.
289///
290/// # Examples
291///
292/// Read in a simple string:
293///
294/// ```no_run
295/// # use casual::prompt;
296/// let username: String = prompt("Please enter your name: ").get();
297/// ```
298///
299/// Types that implement [`FromStr`] will be automatically parsed.
300///
301/// ```no_run
302/// # use casual::prompt;
303/// let years = prompt("How many years have you been coding Rust: ")
304///     .default(0)
305///     .get();
306/// ```
307///
308/// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
309pub fn prompt<S, T>(text: S) -> Input<T>
310where
311    S: Into<String>,
312{
313    Input::new().prompt(text)
314}
315
316/// Prompts the user for confirmation (yes/no).
317///
318/// # Examples
319///
320/// ```no_run
321/// # use casual::confirm;
322/// if confirm("Are you sure you want to continue?") {
323///     // continue
324/// } else {
325///     panic!("Aborted!");
326/// }
327/// ```
328pub fn confirm<S: Into<String>>(text: S) -> bool {
329    prompt(text)
330        .suffix(" [y/N] ")
331        .default("n".to_string())
332        .matches(|s| matches!(&*s.trim().to_lowercase(), "n" | "no" | "y" | "yes"))
333        .map(|s| matches!(&*s.to_lowercase(), "y" | "yes"))
334}