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}