read_input/
lib.rs

1//! ## How to use
2//!
3//! Add
4//! ```toml
5//! read_input = "0.8"
6//! ```
7//! to your `cargo.toml` under `[dependencies]` and add
8//! ```rust
9//! use read_input::prelude::*;
10//! ```
11//! to your main file.
12//!
13//! ---
14//!
15//! You can get input with.
16//!
17//! ```no_run
18//! # use read_input::prelude::*;
19//! # type Type = String;
20//! input::<Type>().get();
21//! ```
22//!
23//! Where `Type` is the type you want.
24//! You can use all types that implement [`std::str::FromStr`].
25//! This currently includes the standard library types [`isize`], [`usize`], [`i8`], [`u8`], [`i16`], [`u16`], [`f32`], [`i32`], [`u32`], [`f64`], [`i64`], [`u64`], [`i128`], [`u128`], [`char`], [`Ipv4Addr`], [`Ipv6Addr`], [`SocketAddrV4`], [`SocketAddrV6`] and [`String`].
26//! Many crates also implement [`std::str::FromStr`] for their types.
27//!
28//! [`Ipv4Addr`]: std::net::Ipv4Addr
29//! [`Ipv6Addr`]: std::net::Ipv6Addr
30//! [`SocketAddrV4`]: std::net::SocketAddrV4
31//! [`SocketAddrV6`]: std::net::SocketAddrV6
32//!
33//! For example, if you want to assign a valid unsigned 32bit value to a variable called `input`, you could write.
34//!
35//! ```no_run
36//! # use read_input::prelude::*;
37//! let input = input::<u32>().get();
38//! ```
39//!
40//! Rust can often work out the type. When this is the case you can skip explicitly stating the type.
41//!
42//! ```no_run
43//! # fn foo() -> String {
44//! # use read_input::prelude::*;
45//! input().get()
46//! # }
47//! ```
48//!
49//! The [`input()`] function uses a common pattern called the builder pattern.
50//! Many settings can be use by adding methods between [`input()`] and [`get()`].
51//! Available methods can be found on the [InputBuild] Trait;
52//!
53//! [`input()`]: shortcut::input
54//! [`get()`]: InputBuilder::get
55//!
56//! ## How to use with custom type
57//!
58//! To use `read_input` with a custom type you need to implement [`std::str::FromStr`] for that type.
59//!
60//! [Working example](https://github.com/eopb/read_input/blob/master/examples/point_input.rs)
61
62#![deny(missing_docs)]
63#![allow(clippy::must_use_candidate)]
64// `impl ToString` is better than `&impl ToString`. Clippy is not ready for impl trait.
65#![allow(clippy::needless_pass_by_value)]
66
67mod core;
68pub mod prelude;
69pub mod shortcut;
70mod test_generators;
71#[cfg(test)]
72mod tests;
73
74use crate::{core::read_input, test_generators::InsideFunc};
75use std::cell::RefCell;
76use std::io::Write;
77use std::{cmp::PartialOrd, io, rc::Rc, str::FromStr, string::ToString};
78
79const DEFAULT_ERR: &str = "That value does not pass. Please try again";
80
81/// Trait implemented by [InputBuilder] and [InputBuilderOnce] to standardize input settings.
82pub trait InputBuild<T: FromStr> {
83    /// Changes or adds a prompt message that gets printed once when input if fetched.
84    ///
85    /// Custom messages are written on the same line as the input cursor.
86    ///
87    /// ```no_run
88    /// # use read_input::prelude::*; 
89    /// let username: String = input().msg("Please input your name: ").get();
90    /// ```
91    ///
92    /// If you wish to fetch input from the next line append a `\n`.
93    ///
94    /// ```no_run
95    /// # use read_input::prelude::*; 
96    /// let username: String = input().msg("Please input your name:\n").get();
97    /// ```
98    fn msg(self, msg: impl ToString) -> Self;
99    /// Changes or adds a prompt message and that is repeated each time input is requested.
100    ///
101    /// ```no_run
102    /// # use read_input::prelude::*; 
103    /// let username: String = input().repeat_msg("Please input your name: ").get();
104    /// ```
105    fn repeat_msg(self, msg: impl ToString) -> Self;
106    /// Changes fallback error message.
107    ///
108    /// The default error message is "That value does not pass. Please try again".
109    ///
110    /// ```no_run
111    /// # use read_input::prelude::*; 
112    /// let input = input::<u32>()
113    ///     .msg("Please input a positive number: ")
114    ///     .err("That does not look like a positive number. Please try again")
115    ///     .get();
116    /// ```
117    fn err(self, err: impl ToString) -> Self;
118    /// Adds a validation check on input to ensure the value meets your criteria.
119    ///
120    /// If you want an integer that is not 6 you could write.
121    ///
122    /// ```no_run
123    /// # use read_input::prelude::*; 
124    /// let input = input().add_test(|x: &u8| *x != 6).get();
125    /// ```
126    /// However for this example it would be better to use [InputConstraints::not]
127    fn add_test<F: Fn(&T) -> bool + 'static>(self, test: F) -> Self;
128    /// Does the same thing as [InputBuild::err], but with a custom error message printed when the test
129    /// fails.
130    ///
131    ///
132    /// If you want a value from 4 to 9 that is not 6 you could write.
133    ///
134    /// ```no_run
135    /// # use read_input::prelude::*; 
136    /// let input = input()
137    ///     .msg("Please input a number from 4 to 9 that is not 6: ")
138    ///     .inside_err(
139    ///         4..=9,
140    ///         "That does not look like a number from 4 to 9. Please try again"
141    ///     )
142    ///     .add_err_test(
143    ///         |x| *x != 6,
144    ///         "That value is 6! I don't want 6. Please try again"
145    ///     )
146    ///     .err("That does not look like a number. Please try again")
147    ///     .get();
148    /// ```
149    fn add_err_test<F>(self, test: F, err: impl ToString) -> Self
150    where
151        F: Fn(&T) -> bool + 'static;
152    /// Removes all validation checks made by [`InputBuild::add_test`], [`InputBuild::add_err_test`],
153    /// [`InputBuild::inside`] and [`InputBuild::inside_err`].
154    fn clear_tests(self) -> Self;
155    /// Used specify custom error messages that depend on the errors produced by [`FromStr`].
156    ///
157    /// You can specify custom error messages that depend on the errors produced by [`FromStr`] with [`InputBuild::err_match()`].
158    ///
159    /// Here is an extract from the [`point_input`](https://github.com/eopb/read_input/blob/master/examples/point_input.rs) example showing this in practice.
160    ///
161    /// ```ignore
162    /// # use read_input::prelude::*; 
163    /// let point = input::<Point>()
164    ///     .repeat_msg("Please input a point in 2D space in the format (x, y): ")
165    ///     .err_match(|e| {
166    ///         Some(match e {
167    ///             ParsePointError::FailedParse(s) => format!(
168    ///                 "Failed to parse \"{}\" it is not a number that can be parsed.",
169    ///                 s
170    ///             ),
171    ///             ParsePointError::Not2Dimensional(num) => {
172    ///                 format!("What you inputted was {} dimensional.", num)
173    ///             }
174    ///             ParsePointError::NonNumeric => "That contains a invalid character.".to_string(),
175    ///         })
176    ///     })
177    ///     .get();
178    /// ```
179    ///
180    /// In nightly rust this can also be done with integers with the feature flag `#![feature(int_error_matching)]` shown in the example [`match_num_err`](https://github.com/eopb/read_input/blob/master/examples/match_num_err.rs).
181    ///
182    /// ```ignore
183    /// # use read_input::prelude::*; 
184    /// use core::num::IntErrorKind::*;
185    /// let input = input::<i16>()
186    ///     .err_match(|x| {
187    ///         Some(
188    ///             match x.kind() {
189    ///                 Empty => "You did not input any value. Try again.",
190    ///                 InvalidDigit => "You typed an invalid digit. Try again using only numbers.",
191    ///                 Overflow => "Integer is too large to store. Try again with a smaller number.",
192    ///                 Underflow => "Integer is too small to store. Try again with a smaller number.",
193    ///                 _ => "That value did not pass for an unexpected reason.",
194    ///             }
195    ///             .to_string(),
196    ///         )
197    ///     })
198    ///     .repeat_msg("Please input a number: ")
199    ///     .get();
200    /// ```
201    fn err_match<F>(self, err_match: F) -> Self
202    where
203        F: Fn(&T::Err) -> Option<String> + 'static;
204    /// Ensures that input is within a range, array or vector.
205    ///
206    /// If you want an integer from 4 to 9 you could write.
207    ///
208    /// ```no_run
209    /// # use read_input::prelude::*; 
210    /// let input = input().inside([4, 5, 6, 7, 8, 9]).get();
211    /// ```
212    ///
213    /// or alternatively
214    ///
215    /// ```no_run
216    /// # use read_input::prelude::*; 
217    /// let input = input().inside(4..=9).get();
218    /// ```
219    fn inside<U: InsideFunc<T>>(self, constraint: U) -> Self;
220    /// Does the same thing as [`InputBuild::inside`], but with a custom error message
221    /// printed when input fails.
222    fn inside_err<U: InsideFunc<T>>(self, constraint: U, err: impl ToString) -> Self;
223    /// Toggles whether a prompt message gets printed once or each time input is requested.
224    fn toggle_msg_repeat(self) -> Self;
225    /// Send prompts to custom writer instead of stdout
226    fn prompting_on(self, prompt_output: RefCell<Box<dyn Write>>) -> Self;
227    /// Send prompts to stderr instead of stdout
228    fn prompting_on_stderr(self) -> Self;
229}
230
231/// A set of validation tests that use `InputBuild::test` under the hood.
232pub trait InputConstraints<T>: InputBuild<T>
233where
234    T: FromStr + PartialOrd + 'static,
235    Self: Sized,
236{
237    /// Sets a minimum input value.
238    fn min(self, min: T) -> Self {
239        self.inside(min..)
240    }
241    /// Sets a minimum input value with custom error message.
242    fn min_err(self, min: T, err: impl ToString) -> Self {
243        self.inside_err(min.., err)
244    }
245    /// Sets a maximum input value.
246    fn max(self, max: T) -> Self {
247        self.inside(..=max)
248    }
249    /// Sets a maximum input value with custom error message.
250    fn max_err(self, max: T, err: impl ToString) -> Self {
251        self.inside_err(..=max, err)
252    }
253    /// Sets a minimum and maximum input value.
254    fn min_max(self, min: T, max: T) -> Self {
255        self.inside(min..=max)
256    }
257    /// Sets a minimum and maximum input value with custom error message.
258    fn min_max_err(self, min: T, max: T, err: impl ToString) -> Self {
259        self.inside_err(min..=max, err)
260    }
261    /// Sets a restricted input value.
262    fn not(self, this: T) -> Self {
263        self.add_test(move |x: &T| *x != this)
264    }
265    /// Sets a restricted input value with custom error message.
266    fn not_err(self, this: T, err: impl ToString) -> Self {
267        self.add_err_test(move |x: &T| *x != this, err)
268    }
269}
270
271#[derive(Clone)]
272pub(crate) struct Prompt {
273    pub msg: String,
274    pub repeat: bool,
275}
276
277#[derive(Clone)]
278pub(crate) struct Test<T> {
279    pub func: Rc<dyn Fn(&T) -> bool>,
280    pub err: Option<String>,
281}
282
283/// 'builder' used to store the settings that are used to fetch input.
284///
285/// `.get()` method only takes these settings by reference so can be called multiple times.
286///
287/// This type does not have support for default input value.
288pub struct InputBuilder<T: FromStr> {
289    msg: Prompt,
290    err: String,
291    tests: Vec<Test<T>>,
292    err_match: Rc<dyn Fn(&T::Err) -> Option<String>>,
293    prompt_output: RefCell<Box<dyn Write>>,
294}
295
296impl<T: FromStr> InputBuilder<T> {
297    /// Creates a new instance of `InputBuilder` with default settings.
298    pub fn new() -> Self {
299        Self {
300            msg: Prompt {
301                msg: String::new(),
302                repeat: false,
303            },
304            err: DEFAULT_ERR.to_string(),
305            tests: Vec::new(),
306            err_match: Rc::new(|_| None),
307            prompt_output: RefCell::new(Box::new(std::io::stdout())),
308        }
309    }
310    /// 'gets' the input form the user.
311    ///
312    /// Panics if unable to read input line.
313    pub fn get(&self) -> T {
314        self.try_get().expect("Failed to read line")
315    }
316    /// 'gets' the input form the user.
317    ///
318    /// # Errors
319    ///
320    /// Returns `Err` if unable to read input line.
321    pub fn try_get(&self) -> io::Result<T> {
322        read_input::<T>(
323            &self.msg,
324            &self.err,
325            None,
326            &self.tests,
327            &*self.err_match,
328            &mut (*self.prompt_output.borrow_mut()),
329        )
330    }
331    /// Changes or adds a default input value.
332    ///
333    /// If the user presses enter before typing anything `.get()` will return a default value when [InputBuilder::default] is used.
334    ///
335    /// ```rust
336    /// # use read_input::prelude::*; 
337    /// let input = input().msg("Please input pi: ").default(3.141).get();
338    /// ```
339    pub fn default(self, default: T) -> InputBuilderOnce<T> {
340        InputBuilderOnce {
341            builder: self,
342            default: Some(default),
343        }
344    }
345    // Internal function for adding tests and constraints.
346    fn test_err_opt(mut self, func: Rc<dyn Fn(&T) -> bool>, err: Option<String>) -> Self {
347        self.tests.push(Test { func, err });
348        self
349    }
350}
351
352impl<T: FromStr> InputBuild<T> for InputBuilder<T> {
353    fn msg(mut self, msg: impl ToString) -> Self {
354        self.msg = Prompt {
355            msg: msg.to_string(),
356            repeat: false,
357        };
358        self
359    }
360    fn repeat_msg(mut self, msg: impl ToString) -> Self {
361        self.msg = Prompt {
362            msg: msg.to_string(),
363            repeat: true,
364        };
365        self
366    }
367    fn err(mut self, err: impl ToString) -> Self {
368        self.err = err.to_string();
369        self
370    }
371
372    fn add_test<F: Fn(&T) -> bool + 'static>(self, test: F) -> Self {
373        self.test_err_opt(Rc::new(test), None)
374    }
375    fn add_err_test<F>(self, test: F, err: impl ToString) -> Self
376    where
377        F: Fn(&T) -> bool + 'static,
378    {
379        self.test_err_opt(Rc::new(test), Some(err.to_string()))
380    }
381    fn clear_tests(mut self) -> Self {
382        self.tests = Vec::new();
383        self
384    }
385    fn err_match<F>(mut self, err_match: F) -> Self
386    where
387        F: Fn(&T::Err) -> Option<String> + 'static,
388    {
389        self.err_match = Rc::new(err_match);
390        self
391    }
392    fn inside<U: InsideFunc<T>>(self, constraint: U) -> Self {
393        self.test_err_opt(constraint.contains_func(), None)
394    }
395    fn inside_err<U: InsideFunc<T>>(self, constraint: U, err: impl ToString) -> Self {
396        self.test_err_opt(constraint.contains_func(), Some(err.to_string()))
397    }
398    fn toggle_msg_repeat(mut self) -> Self {
399        self.msg.repeat = !self.msg.repeat;
400        self
401    }
402
403    fn prompting_on(mut self, prompt_output: RefCell<Box<dyn Write>>) -> Self {
404        self.prompt_output = prompt_output;
405        self
406    }
407
408    fn prompting_on_stderr(self) -> Self {
409        self.prompting_on(RefCell::new(Box::new(std::io::stderr())))
410    }
411}
412
413impl<T: FromStr + PartialOrd + 'static> InputConstraints<T> for InputBuilder<T> {}
414
415impl<T: FromStr> Default for InputBuilder<T> {
416    fn default() -> Self {
417        Self::new()
418    }
419}
420
421impl<T: FromStr + Clone> Clone for InputBuilder<T> {
422    fn clone(&self) -> Self {
423        Self {
424            msg: self.msg.clone(),
425            err: self.err.clone(),
426            tests: self.tests.clone(),
427            err_match: self.err_match.clone(),
428            prompt_output: RefCell::new(Box::new(std::io::stdout())),
429        }
430    }
431}
432
433/// 'builder' used to store the settings that are used to fetch input.
434///
435/// `.get()` method takes ownership of the settings so can be called only once without cloning.
436///
437/// This type has support for default input value.
438pub struct InputBuilderOnce<T: FromStr> {
439    builder: InputBuilder<T>,
440    default: Option<T>,
441}
442
443impl<T: FromStr> InputBuilderOnce<T> {
444    /// 'gets' the input form the user.
445    ///
446    /// Panics if unable to read input line.
447    pub fn get(self) -> T {
448        self.try_get().expect("Failed to read line")
449    }
450    /// 'gets' the input form the user.
451    ///
452    /// # Errors
453    ///
454    /// Returns `Err` if unable to read input line.
455    pub fn try_get(self) -> io::Result<T> {
456        read_input::<T>(
457            &self.builder.msg,
458            &self.builder.err,
459            self.default,
460            &self.builder.tests,
461            &*self.builder.err_match,
462            &mut (*self.builder.prompt_output.borrow_mut()),
463        )
464    }
465    // Function that makes it less verbose to change settings of internal `InputBuilder`.
466    fn internal<F>(self, with: F) -> Self
467    where
468        F: FnOnce(InputBuilder<T>) -> InputBuilder<T>,
469    {
470        Self {
471            builder: with(self.builder),
472            ..self
473        }
474    }
475}
476
477impl<T: FromStr> InputBuild<T> for InputBuilderOnce<T> {
478    fn msg(self, msg: impl ToString) -> Self {
479        self.internal(|x| x.msg(msg))
480    }
481    fn repeat_msg(self, msg: impl ToString) -> Self {
482        self.internal(|x| x.repeat_msg(msg))
483    }
484    fn err(self, err: impl ToString) -> Self {
485        self.internal(|x| x.err(err))
486    }
487    fn add_test<F: Fn(&T) -> bool + 'static>(self, test: F) -> Self {
488        self.internal(|x| x.add_test(test))
489    }
490    fn add_err_test<F>(self, test: F, err: impl ToString) -> Self
491    where
492        F: Fn(&T) -> bool + 'static,
493    {
494        self.internal(|x| x.add_err_test(test, err))
495    }
496    fn clear_tests(self) -> Self {
497        self.internal(InputBuild::clear_tests)
498    }
499    fn err_match<F>(self, err_match: F) -> Self
500    where
501        F: Fn(&T::Err) -> Option<String> + 'static,
502    {
503        self.internal(|x| x.err_match(err_match))
504    }
505    fn inside<U: InsideFunc<T>>(self, constraint: U) -> Self {
506        self.internal(|x| x.inside(constraint))
507    }
508    fn inside_err<U: InsideFunc<T>>(self, constraint: U, err: impl ToString) -> Self {
509        self.internal(|x| x.inside_err(constraint, err))
510    }
511    fn toggle_msg_repeat(self) -> Self {
512        self.internal(InputBuild::toggle_msg_repeat)
513    }
514
515    fn prompting_on(self, prompt_output: RefCell<Box<dyn Write>>) -> Self {
516        self.internal(|x| x.prompting_on(prompt_output))
517    }
518
519    fn prompting_on_stderr(self) -> Self {
520        self.internal(|x| x.prompting_on(RefCell::new(Box::new(std::io::stderr()))))
521    }
522}
523
524impl<T: FromStr + PartialOrd + 'static> InputConstraints<T> for InputBuilderOnce<T> {}
525
526impl<T> Clone for InputBuilderOnce<T>
527where
528    T: Clone + FromStr,
529{
530    fn clone(&self) -> Self {
531        Self {
532            default: self.default.clone(),
533            builder: self.builder.clone(),
534        }
535    }
536}