archetect_core/vendor/read_input/
mod.rs

1//! Go the the [readme](https://crates.io/crates/read_input) file for extra documentation and tutorial.
2
3#![deny(clippy::pedantic, missing_docs)]
4#![allow(clippy::must_use_candidate)]
5// `impl ToString` is better than `&impl ToString`. Clippy is not ready for impl trait.
6#![allow(clippy::needless_pass_by_value)]
7
8use std::{cmp::PartialOrd, io, rc::Rc, str::FromStr, string::ToString};
9use std::cell::RefCell;
10use std::io::Write;
11
12use crate::vendor::read_input::core::read_input;
13use crate::vendor::read_input::test_generators::InsideFunc;
14
15mod core;
16pub mod prelude;
17pub mod shortcut;
18mod test_generators;
19#[cfg(test)]
20mod tests;
21
22const DEFAULT_ERR: &str = "That value does not pass. Please try again";
23
24/// Trait for common types that store input settings.
25pub trait InputBuild<T: FromStr> {
26    /// Changes or adds a prompt message that gets printed once when input if fetched.
27    fn msg(self, msg: impl ToString) -> Self;
28    /// Changes or adds a prompt message and that is repeated each time input is requested.
29    fn repeat_msg(self, msg: impl ToString) -> Self;
30    /// Changes fallback error message.
31    fn err(self, err: impl ToString) -> Self;
32    /// Adds a validation check on input.
33    fn add_test<F: Fn(&T) -> bool + 'static>(self, test: F) -> Self;
34    /// Adds a validation check on input with a custom error message printed when the test
35    /// fails.
36    fn add_err_test<F>(self, test: F, err: impl ToString) -> Self
37    where
38        F: Fn(&T) -> bool + 'static;
39    /// Removes all validation checks made by `.add_test()`, `.add_err_test()`,
40    /// `.inside()` and `.inside_err()`.
41    fn clear_tests(self) -> Self;
42    /// Used specify custom error messages that depend on the errors produced by `from_str()`.
43    fn err_match<F>(self, err_match: F) -> Self
44    where
45        F: Fn(&T::Err) -> Option<String> + 'static;
46    /// Ensures that input is within a range, array or vector.
47    fn inside<U: InsideFunc<T>>(self, constraint: U) -> Self;
48    /// Ensures that input is within a range, array or vector with a custom error message
49    /// printed when input fails.
50    fn inside_err<U: InsideFunc<T>>(self, constraint: U, err: impl ToString) -> Self;
51    /// Toggles whether a prompt message gets printed once or each time input is requested.
52    fn toggle_msg_repeat(self) -> Self;
53    /// Send prompts to custom writer instead of stdout
54    fn prompting_on(self, prompt_output: RefCell<Box<dyn Write>>) -> Self;
55    /// Send prompts to stderr instead of stdout
56    fn prompting_on_stderr(self) -> Self;
57}
58
59/// Trait for changing input settings by adding constraints that require `PartialOrd`
60/// on the input type.
61pub trait InputConstraints<T>: InputBuild<T>
62where
63    T: FromStr + PartialOrd + 'static,
64    Self: Sized,
65{
66    /// Sets a minimum input value.
67    fn min(self, min: T) -> Self {
68        self.inside(min..)
69    }
70    /// Sets a minimum input value with custom error message.
71    fn min_err(self, min: T, err: impl ToString) -> Self {
72        self.inside_err(min.., err)
73    }
74    /// Sets a maximum input value.
75    fn max(self, max: T) -> Self {
76        self.inside(..=max)
77    }
78    /// Sets a maximum input value with custom error message.
79    fn max_err(self, max: T, err: impl ToString) -> Self {
80        self.inside_err(..=max, err)
81    }
82    /// Sets a minimum and maximum input value.
83    fn min_max(self, min: T, max: T) -> Self {
84        self.inside(min..=max)
85    }
86    /// Sets a minimum and maximum input value with custom error message.
87    fn min_max_err(self, min: T, max: T, err: impl ToString) -> Self {
88        self.inside_err(min..=max, err)
89    }
90    /// Sets a restricted input value.
91    fn not(self, this: T) -> Self {
92        self.add_test(move |x: &T| *x != this)
93    }
94    /// Sets a restricted input value with custom error message.
95    fn not_err(self, this: T, err: impl ToString) -> Self {
96        self.add_err_test(move |x: &T| *x != this, err)
97    }
98}
99
100#[derive(Clone)]
101pub(crate) struct Prompt {
102    pub msg: String,
103    pub repeat: bool,
104}
105
106#[derive(Clone)]
107pub(crate) struct Test<T> {
108    pub func: Rc<dyn Fn(&T) -> bool>,
109    pub err: Option<String>,
110}
111
112/// 'builder' used to store the settings that are used to fetch input.
113///
114/// `.get()` method only takes these settings by reference so can be called multiple times.
115///
116/// This type does not have support for default input value.
117pub struct InputBuilder<T: FromStr> {
118    msg: Prompt,
119    err: String,
120    tests: Vec<Test<T>>,
121    err_match: Rc<dyn Fn(&T::Err) -> Option<String>>,
122    prompt_output: RefCell<Box<dyn Write>>,
123}
124
125impl<T: FromStr> InputBuilder<T> {
126    /// Creates a new instance of `InputBuilder` with default settings.
127    pub fn new() -> Self {
128        Self {
129            msg: Prompt {
130                msg: String::new(),
131                repeat: false,
132            },
133            err: DEFAULT_ERR.to_string(),
134            tests: Vec::new(),
135            err_match: Rc::new(|_| None),
136            prompt_output: RefCell::new(Box::new(std::io::stdout())),
137        }
138    }
139    /// 'gets' the input form the user.
140    ///
141    /// Panics if unable to read input line.
142    pub fn get(&self) -> T {
143        self.try_get().expect("Failed to read line")
144    }
145    /// 'gets' the input form the user.
146    ///
147    /// # Errors
148    ///
149    /// Returns `Err` if unable to read input line.
150    pub fn try_get(&self) -> io::Result<T> {
151        read_input::<T>(
152            &self.msg,
153            &self.err,
154            None,
155            &self.tests,
156            &*self.err_match,
157            &mut (*self.prompt_output.borrow_mut()),
158        )
159    }
160    /// Changes or adds a default input value.
161    pub fn default(self, default: T) -> InputBuilderOnce<T> {
162        InputBuilderOnce {
163            builder: self,
164            default: Some(default),
165        }
166    }
167    // Internal function for adding tests and constraints.
168    fn test_err_opt(mut self, func: Rc<dyn Fn(&T) -> bool>, err: Option<String>) -> Self {
169        self.tests.push(Test { func, err });
170        self
171    }
172}
173
174impl<T: FromStr> InputBuild<T> for InputBuilder<T> {
175    fn msg(mut self, msg: impl ToString) -> Self {
176        self.msg = Prompt {
177            msg: msg.to_string(),
178            repeat: false,
179        };
180        self
181    }
182    fn repeat_msg(mut self, msg: impl ToString) -> Self {
183        self.msg = Prompt {
184            msg: msg.to_string(),
185            repeat: true,
186        };
187        self
188    }
189    fn err(mut self, err: impl ToString) -> Self {
190        self.err = err.to_string();
191        self
192    }
193
194    fn add_test<F: Fn(&T) -> bool + 'static>(self, test: F) -> Self {
195        self.test_err_opt(Rc::new(test), None)
196    }
197    fn add_err_test<F>(self, test: F, err: impl ToString) -> Self
198    where
199        F: Fn(&T) -> bool + 'static,
200    {
201        self.test_err_opt(Rc::new(test), Some(err.to_string()))
202    }
203    fn clear_tests(mut self) -> Self {
204        self.tests = Vec::new();
205        self
206    }
207    fn err_match<F>(mut self, err_match: F) -> Self
208    where
209        F: Fn(&T::Err) -> Option<String> + 'static,
210    {
211        self.err_match = Rc::new(err_match);
212        self
213    }
214    fn inside<U: InsideFunc<T>>(self, constraint: U) -> Self {
215        self.test_err_opt(constraint.contains_func(), None)
216    }
217    fn inside_err<U: InsideFunc<T>>(self, constraint: U, err: impl ToString) -> Self {
218        self.test_err_opt(constraint.contains_func(), Some(err.to_string()))
219    }
220    fn toggle_msg_repeat(mut self) -> Self {
221        self.msg.repeat = !self.msg.repeat;
222        self
223    }
224
225    fn prompting_on(mut self, prompt_output: RefCell<Box<dyn Write>>) -> Self {
226        self.prompt_output = prompt_output;
227        self
228    }
229
230    fn prompting_on_stderr(self) -> Self {
231        self.prompting_on(RefCell::new(Box::new(std::io::stderr())))
232    }
233}
234
235impl<T: FromStr + PartialOrd + 'static> InputConstraints<T> for InputBuilder<T> {}
236
237impl<T: FromStr> Default for InputBuilder<T> {
238    fn default() -> Self {
239        Self::new()
240    }
241}
242
243impl<T: FromStr + Clone> Clone for InputBuilder<T> {
244    fn clone(&self) -> Self {
245        Self {
246            msg: self.msg.clone(),
247            err: self.err.clone(),
248            tests: self.tests.clone(),
249            err_match: self.err_match.clone(),
250            prompt_output: RefCell::new(Box::new(std::io::stdout())),
251        }
252    }
253}
254
255/// 'builder' used to store the settings that are used to fetch input.
256///
257/// `.get()` method takes ownership of the settings so can be called only once without cloning.
258///
259/// This type has support for default input value.
260pub struct InputBuilderOnce<T: FromStr> {
261    builder: InputBuilder<T>,
262    default: Option<T>,
263}
264
265impl<T: FromStr> InputBuilderOnce<T> {
266    /// 'gets' the input form the user.
267    ///
268    /// Panics if unable to read input line.
269    pub fn get(self) -> T {
270        self.try_get().expect("Failed to read line")
271    }
272    /// 'gets' the input form the user.
273    ///
274    /// # Errors
275    ///
276    /// Returns `Err` if unable to read input line.
277    pub fn try_get(self) -> io::Result<T> {
278        read_input::<T>(
279            &self.builder.msg,
280            &self.builder.err,
281            self.default,
282            &self.builder.tests,
283            &*self.builder.err_match,
284            &mut (*self.builder.prompt_output.borrow_mut()),
285        )
286    }
287    // Function that makes it less verbose to change settings of internal `InputBuilder`.
288    fn internal<F>(self, with: F) -> Self
289    where
290        F: FnOnce(InputBuilder<T>) -> InputBuilder<T>,
291    {
292        Self {
293            builder: with(self.builder),
294            ..self
295        }
296    }
297}
298
299impl<T: FromStr> InputBuild<T> for InputBuilderOnce<T> {
300    fn msg(self, msg: impl ToString) -> Self {
301        self.internal(|x| x.msg(msg))
302    }
303    fn repeat_msg(self, msg: impl ToString) -> Self {
304        self.internal(|x| x.repeat_msg(msg))
305    }
306    fn err(self, err: impl ToString) -> Self {
307        self.internal(|x| x.err(err))
308    }
309    fn add_test<F: Fn(&T) -> bool + 'static>(self, test: F) -> Self {
310        self.internal(|x| x.add_test(test))
311    }
312    fn add_err_test<F>(self, test: F, err: impl ToString) -> Self
313    where
314        F: Fn(&T) -> bool + 'static,
315    {
316        self.internal(|x| x.add_err_test(test, err))
317    }
318    fn clear_tests(self) -> Self {
319        self.internal(InputBuild::clear_tests)
320    }
321    fn err_match<F>(self, err_match: F) -> Self
322    where
323        F: Fn(&T::Err) -> Option<String> + 'static,
324    {
325        self.internal(|x| x.err_match(err_match))
326    }
327    fn inside<U: InsideFunc<T>>(self, constraint: U) -> Self {
328        self.internal(|x| x.inside(constraint))
329    }
330    fn inside_err<U: InsideFunc<T>>(self, constraint: U, err: impl ToString) -> Self {
331        self.internal(|x| x.inside_err(constraint, err))
332    }
333    fn toggle_msg_repeat(self) -> Self {
334        self.internal(InputBuild::toggle_msg_repeat)
335    }
336
337    fn prompting_on(self, prompt_output: RefCell<Box<dyn Write>>) -> Self {
338        self.internal(|x| x.prompting_on(prompt_output))
339    }
340
341    fn prompting_on_stderr(self) -> Self {
342        self.internal(|x| x.prompting_on(RefCell::new(Box::new(std::io::stderr()))))
343    }
344}
345
346impl<T: FromStr + PartialOrd + 'static> InputConstraints<T> for InputBuilderOnce<T> {}
347
348impl<T> Clone for InputBuilderOnce<T>
349where
350    T: Clone + FromStr,
351{
352    fn clone(&self) -> Self {
353        Self {
354            default: self.default.clone(),
355            builder: self.builder.clone(),
356        }
357    }
358}