smart_read/lib.rs
1//! Complex but easy ways to read user input
2//!
3//! <br>
4//!
5//! ### Functionality in this crate is defined by types that implement `TryRead`.
6//!
7//! <br>
8//! <br>
9//!
10//! # Types that implement `TryRead`:
11//!
12//! This is basically a list of all default functionality, if you want to know more about one of these types, the header name is the same as the module which contains the type
13//!
14//! <br>
15//!
16//! ### Basics
17//!
18//! ```
19//! impl TryRead for () // requests any string from the user
20//! impl TryRead for NonEmptyInput // requests a non-empty string from the user
21//! impl TryRead for NonWhitespaceInput // requests a non-whitespace string from the user
22//! impl TryRead for BoolInput // requests a true/false/t/f string from the user
23//! impl TryRead for YesNoInput // requests a yes/no/y/n string from the user
24//! impl TryRead for CharInput // requests a single-char string from the user
25//! // requests a number from the user that can be converted to a specific type:
26//! impl TryRead for U8Input, U16Input, U32Input, U64Input, U128Input, USizeInput
27//! impl TryRead for I8Input, I16Input, I32Input, I64Input, I128Input, ISizeInput
28//! impl TryRead for F32Input
29//! impl TryRead for F64Input
30//! ```
31//!
32//! <br>
33//!
34//! ### Input Validations
35//!
36//! ```
37//! // requests a string from the user which passes the programmed validation:
38//! impl<F: Fn(&str) -> Result<(), String>> TryRead for SimpleValidate<F>
39//! // similar to `SimpleValidate`, but also transforms the output:
40//! impl<F: Fn(String) -> Result<O, String>, O: Display> TryRead for TransformValidate<F, O>
41//! ```
42//!
43//! <br>
44//!
45//! ### List Constraints
46//!
47//! These allow you to specify which inputs are allowed. Example: `read!(["a", "b", "c"])`
48//!
49//! Implemented types:
50//! ```
51//! // requests a string from the user that matches any of the names from the `InputOption`s:
52//! impl<Data> TryRead for &[InputOption<Data>]
53//! impl<Data> TryRead for &[InputOption<Data>; _]
54//! impl<Data> TryRead for [InputOption<Data>; _]
55//! // requests a string from the user that matches any value in the list:
56//! impl<T: Display> TryRead for &[T]
57//! impl<T: Display> TryRead for [T; _]
58//! impl<T: Display> TryRead for Vec<T>
59//! impl<T: Display> TryRead for VecDeque<T>
60//! impl<T: Display> TryRead for LinkedList<T>
61//! ```
62//!
63//! <br>
64//!
65//! ### Range Constraints
66//!
67//! These allow you to take a number within a specified range. Example: `read!(1. .. 100.)`, `read!(10..)`, etc
68//!
69//! Implemented types:
70//! ```
71//! impl<T> TryRead for Range<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
72//! impl<T> TryRead for RangeInclusive<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
73//! impl<T> TryRead for RangeTo<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
74//! impl<T> TryRead for RangeFrom<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
75//! impl<T> TryRead for RangeToInclusive<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
76//! ```
77//!
78//! <br>
79//! <br>
80//!
81//! # Macro Syntax
82//!
83//! Prompt macros: `prompt!("message to user"; [default_value] input_type)`
84//!
85//! Read macros: `read!([default_value] input_type)`
86//!
87//! All components (prompt message, default value, and input type) are optional (except the message in prompts) and all are expressions.
88//!
89//! Some examples:
90//! ```
91//! read!([2] 1..=10); // take a number from 1 to 10, with 2 as the default
92//! prompt!(messages[i]; UsizeInput); // request a positive integer for the current prompt
93//! prompt!("continue?"; [true] YesNoInput); // request a yes/no input with yes being the default
94//! ```
95//!
96//! <br>
97//!
98//! The input type is what determines the functionality of the input. It is another expression, and the type of the resulting value is what determines which impl of `TryRead` is used. For example, if you have `read!(1..10)` then the impl for `Range<i32>` is used. Also, when you have something like `read!(UsizeInput)`, you are creating a new `UsizeInput` value and passing it to the macro.
99//!
100//! Some types have special syntax that can be substituted for the input_type component, they are:
101//!
102//! ```
103//! // this:
104//! read!()
105//! // is this:
106//! read!(())
107//!
108//! // this:
109//! read!(= 1, 2, 3)
110//! // is this:
111//! read!([1, 2, 3])
112//!
113//! // this:
114//! read!(=
115//! ["1_bulletin", "1_display_name", "1_alt_name_1", ...], 1_data,
116//! ["2_bulletin", "2_display_name", "2_alt_name_1", ...], 2_data
117//! ...
118//! )
119//! // is this:
120//! read!([
121//! InputOption::new("1_bulletin", vec!("1_display_name", "1_alt_name_1", ...), 1_data),
122//! InputOption::new("2_bulletin", vec!("2_display_name", "2_alt_name_1", ...), 2_data),
123//! ...
124//! ])
125//! ```
126//!
127//! <br>
128//!
129//! And of course, you can combine this with any other piece of syntax: `prompt!("Enter a color: "; ["red"] = "red", "green", "blue")`
130//!
131//! <br>
132//! <br>
133//!
134//! If you have ideas for more functionality (including things you've found to be useful yourself), feel free to open an issue / pull request
135//!
136//! <br>
137//! <br>
138
139
140
141#![feature(let_chains)]
142#![allow(clippy::tabs_in_doc_comments, clippy::neg_multiply)]
143#![warn(missing_docs, clippy::todo, clippy::unwrap_used, clippy::panic, clippy::expect_used)]
144
145use std::{error::Error, fmt::{Debug, Display}, io::Write};
146
147
148
149/// Contains implementations for `()`, `UsizeInput`, `NonEmptyInput`, etc
150pub mod basics;
151/// Contains implementations for `SimpleValidate` and `TransformValidate`
152pub mod input_validation;
153/// Contains implementations for `Vec<T>`, `read!(= a, b, c)`, etc
154pub mod list_constraints;
155/// Contains implementations for `Range<T>`, `RangeFrom<T>`, etc
156pub mod range_constraints;
157
158/// Easy way to use existing functionality. If you want to extend functionality instead, you can do `use smart_read::*;`
159pub mod prelude {
160 pub use super::{
161 read,
162 try_read,
163 prompt,
164 try_prompt,
165 basics::*,
166 input_validation::*,
167 list_constraints::*,
168 range_constraints::*,
169 };
170}
171
172
173
174
175
176// ================================ Macros ================================ //
177
178
179
180/// ## Reads a line of text, a number, etc
181#[macro_export]
182macro_rules! read {
183 ($($args:tt)*) => {
184 smart_read::try_read!($($args)*).unwrap()
185 }
186}
187
188/// Same as read!(), but returns a result
189#[macro_export]
190macro_rules! try_read {
191 ($($args:tt)*) => {
192 smart_read::run_with_prompt!(None; $($args)*)
193 };
194}
195
196
197
198/// Same as read!(), but also prints a prompt
199#[macro_export]
200macro_rules! prompt {
201 ($($args:tt)*) => {
202 smart_read::try_prompt!($($args)*).unwrap()
203 }
204}
205
206/// Same as prompt!(), but returns a result
207#[macro_export]
208macro_rules! try_prompt {
209 ($prompt:expr) => {
210 smart_read::run_with_prompt!(Some($prompt.to_string());)
211 };
212 ($prompt:expr; $($args:tt)*) => {
213 smart_read::run_with_prompt!(Some($prompt.to_string()); $($args)*)
214 };
215}
216
217
218
219#[macro_export]
220#[doc(hidden)]
221macro_rules! run_with_prompt {
222 ($prompt:expr; [$default:expr] $($args:tt)*) => {
223 smart_read::run_with_prompt_and_default!($prompt; Some($default.into()); $($args)*)
224 };
225 ($prompt:expr; $($args:tt)*) => {
226 smart_read::run_with_prompt_and_default!($prompt; None; $($args)*)
227 };
228}
229
230
231
232#[macro_export]
233#[doc(hidden)]
234macro_rules! run_with_prompt_and_default {
235
236 ($prompt:expr; $default:expr;) => {{
237 use smart_read::TryRead;
238 ().try_read_line($prompt, $default)
239 }};
240
241 ($prompt:expr; $default:expr; = $([$option_bulletin:expr, $option_name:expr, $($option_alt:expr),*], $option_data:expr,)*) => {{
242 use smart_read::TryRead;
243 [$(InputOption::new($option_bulletin, vec!($option_name.to_string() $(,$option_alt.to_string())*), $option_data)),*].try_read_line($prompt, $default)
244 }};
245
246 ($prompt:expr; $default:expr; = $($option:expr),*) => {{
247 use smart_read::TryRead;
248 [$($option),*].try_read_line($prompt, $default)
249 }};
250
251 ($prompt:expr; $default:expr; $tryread_struct:expr) => {{
252 use smart_read::TryRead;
253 ($tryread_struct).try_read_line($prompt, $default)
254 }};
255
256}
257
258
259
260
261
262// ================================ TYPES ================================ //
263
264
265
266/// Just `Result<T, Box<dyn Error>>`, mostly for internal use
267pub type BoxResult<T> = Result<T, Box<dyn Error>>;
268
269
270
271/// This is what powers the whole crate. Any struct that implements this can be used with the macros
272pub trait TryRead {
273 /// Defines the output type of `read` and `prompt` macros
274 type Output;
275 /// Defines the output type of the default input
276 type Default;
277 /// This is what's called by the `read` and `prompt` macros
278 fn try_read_line(self, prompt: Option<String>, default: Option<Self::Default>) -> BoxResult<Self::Output>;
279}
280
281
282
283/// Useful pre-made error
284#[derive(Debug)]
285pub struct DefaultNotAllowedError;
286
287impl Error for DefaultNotAllowedError {}
288
289impl Display for DefaultNotAllowedError {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 write!(f, "Default value is not allowed for input type.")
292 }
293}
294
295impl DefaultNotAllowedError {
296 /// Easily get a return value
297 pub fn new_box_result<T>() -> BoxResult<T> {
298 Err(Box::new(Self))
299 }
300}
301
302
303
304/// Useful pre-made error
305#[derive(Debug)]
306pub struct PromptNotAllowedError;
307
308impl Error for PromptNotAllowedError {}
309
310impl Display for PromptNotAllowedError {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 write!(f, "Prompt value is not allowed for input type.")
313 }
314}
315
316impl PromptNotAllowedError {
317 /// Easily get a return value
318 pub fn new_box_result<T>() -> BoxResult<T> {
319 Err(Box::new(Self))
320 }
321}
322
323
324
325
326
327// ================================ FUNCTIONS ================================ //
328
329
330
331/// Utility function, mostly for internal use
332pub fn read_stdin() -> BoxResult<String> {
333 std::io::stdout().flush()?;
334 let mut output = String::new();
335 std::io::stdin().read_line(&mut output)?;
336 if output.ends_with('\n') {output.pop();}
337 if output.ends_with('\r') {output.pop();}
338 Ok(output)
339}
340
341
342
343/// Tiny utility function, clears the terminal output, but you should probably use the [ClearScreen](https://crates.io/crates/clearscreen) crate instead
344pub fn clear_term() {
345 print!("{esc}c", esc = 27 as char);
346}