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//! These allow you to easily add custom logic to specific reads
37//!
38//! ```
39//! // requests a string from the user which passes the programmed validation:
40//! impl<F> TryRead for SimpleValidate<F> where F: Fn(&str) -> Result<(), String>
41//! // similar to `SimpleValidate`, but also transforms the output:
42//! impl<F, O> TryRead for TransformValidate<F, O> where F: Fn(String) -> Result<O, String>, O: Display
43//! ```
44//!
45//! <br>
46//!
47//! ### List Constraints
48//!
49//! These allow you to specify which inputs are allowed. Example: `read!(["a", "b", "c"])`
50//!
51//! NOTE: The default value for these types denotes the index of the default option
52//!
53//! ```
54//! // requests a string from the user that matches any of the names from the `InputOption`s:
55//! impl<Data> TryRead for &[InputOption<Data>]
56//! impl<Data> TryRead for &[InputOption<Data>; N]
57//! impl<Data> TryRead for [InputOption<Data>; N]
58//! // requests a string from the user that matches any value in the list:
59//! impl<T: Display> TryRead for &[T]
60//! impl<T: Display> TryRead for [T; N]
61//! impl<T: Display> TryRead for Vec<T>
62//! impl<T: Display> TryRead for VecDeque<T>
63//! impl<T: Display> TryRead for LinkedList<T>
64//! ```
65//!
66//! <br>
67//!
68//! ### Range Constraints
69//!
70//! These allow you to take a number within a specified range. Example: `read!(1. ..= 100.)`, `read!(10..)`, etc
71//!
72//! ```
73//! impl<T> TryRead for Range<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
74//! impl<T> TryRead for RangeInclusive<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
75//! impl<T> TryRead for RangeTo<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
76//! impl<T> TryRead for RangeFrom<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
77//! impl<T> TryRead for RangeToInclusive<T> where T: Display + FromStr + PartialOrd<T>, <T as FromStr>::Err: Display
78//! ```
79//!
80//! <br>
81//! <br>
82//!
83//! # Macro Syntax
84//!
85//! Prompt macros: `prompt!("message to user"; [default_value] input_type)`
86//!
87//! Read macros: `read!([default_value] input_type)`
88//!
89//! All components are optional (except the message in prompts) and all are expressions.
90//!
91//! Some examples:
92//! ```
93//! read!([2] 1..=10); // take a number from 1 to 10, with 2 as the default
94//! prompt!(messages[i]; UsizeInput); // request a positive integer for the current prompt
95//! prompt!("continue?"; [true] YesNoInput); // request a yes/no input with yes being the default
96//! ```
97//!
98//! <br>
99//!
100//! 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.
101//!
102//! Some input types have special syntax that can be substituted for the input_type component, they are:
103//!
104//! ```
105//! // this:
106//! read!()
107//! // is this:
108//! read!(())
109//!
110//! // this:
111//! read!(= 1, 2, 3)
112//! // is this:
113//! read!([1, 2, 3])
114//!
115//! // this:
116//! read!(=
117//! "1_bulletin"; "1_display_name"; ["1_alt_name_1", ...]; 1_data,
118//! "2_bulletin"; "2_display_name"; ["2_alt_name_1", ...]; 2_data,
119//! ...
120//! )
121//! // is this:
122//! read!([
123//! InputOption::new("1_bulletin", vec!("1_display_name", "1_alt_name_1", ...), 1_data),
124//! InputOption::new("2_bulletin", vec!("2_display_name", "2_alt_name_1", ...), 2_data),
125//! ...
126//! ])
127//! ```
128//!
129//! <br>
130//!
131//! And of course, you can combine this special input type syntax with everything else: `prompt!("Enter a color: "; ["red"] = "red", "green", "blue")`
132//!
133//! <br>
134//!
135//! If you have ideas for more functionality (including things you've found to be useful yourself), feel free to open an issue / pull request
136//!
137//! <br>
138//! <br>
139
140
141
142#![feature(let_chains)]
143#![allow(clippy::tabs_in_doc_comments, clippy::neg_multiply)]
144#![warn(missing_docs, clippy::todo, clippy::unwrap_used, clippy::panic, clippy::expect_used)]
145
146use std::{error::Error, fmt::{Debug, Display}, io::Write};
147
148
149
150/// Contains implementations for `()`, `UsizeInput`, `NonEmptyInput`, etc
151pub mod basics;
152/// Contains implementations for `SimpleValidate` and `TransformValidate`
153pub mod input_validation;
154/// Contains implementations for `&[T]`, `[T; N]`, `Vec<T>`, `read!(= a, b, c)`, etc
155pub mod list_constraints;
156/// Contains implementations for `Range<T>`, `RangeFrom<T>`, etc
157pub mod range_constraints;
158
159/// Easy way to use existing functionality. If you want to extend functionality instead, you can do `use smart_read::*;`
160pub mod prelude {
161 pub use super::{
162 read,
163 try_read,
164 prompt,
165 try_prompt,
166 wait_for_enter,
167 basics::*,
168 input_validation::*,
169 list_constraints::*,
170 range_constraints::*,
171 };
172}
173
174
175
176
177
178// ================================ Macros ================================ //
179
180
181
182/// ## Reads a line of text, a number, etc
183#[macro_export]
184macro_rules! read {
185 ($($args:tt)*) => {
186 smart_read::try_read!($($args)*).unwrap()
187 }
188}
189
190/// Same as `read!()`, but returns a result
191#[macro_export]
192macro_rules! try_read {
193 ($($args:tt)*) => {
194 smart_read::run_with_prompt!(None; $($args)*)
195 };
196}
197
198
199
200/// Same as `read!()`, but also prints a prompt
201#[macro_export]
202macro_rules! prompt {
203 ($($args:tt)*) => {
204 smart_read::try_prompt!($($args)*).unwrap()
205 }
206}
207
208/// Same as `prompt!()`, but returns a result
209#[macro_export]
210macro_rules! try_prompt {
211 ($prompt:expr) => {
212 smart_read::run_with_prompt!(Some($prompt.to_string());)
213 };
214 ($prompt:expr; $($args:tt)*) => {
215 smart_read::run_with_prompt!(Some($prompt.to_string()); $($args)*)
216 };
217}
218
219
220
221#[macro_export]
222#[doc(hidden)]
223macro_rules! run_with_prompt {
224 ($prompt:expr; [$default:expr] $($args:tt)*) => {
225 smart_read::run_with_prompt_and_default!($prompt; Some($default.into()); $($args)*)
226 };
227 ($prompt:expr; $($args:tt)*) => {
228 smart_read::run_with_prompt_and_default!($prompt; None; $($args)*)
229 };
230}
231
232
233
234#[macro_export]
235#[doc(hidden)]
236macro_rules! run_with_prompt_and_default {
237
238 ($prompt:expr; $default:expr;) => {{
239 use smart_read::TryRead;
240 ().try_read_line($prompt, $default)
241 }};
242
243 ($prompt:expr; $default:expr; = $($option_bulletin:expr; $option_name:expr; [$($option_alt:expr),*]; $option_data:expr,)*) => {{
244 use smart_read::TryRead;
245 [$(InputOption::new($option_bulletin, &[$option_name.to_string() $(,$option_alt.to_string())*], $option_data)),*].try_read_line($prompt, $default)
246 }};
247
248 ($prompt:expr; $default:expr; = $($option:expr),*) => {{
249 use smart_read::TryRead;
250 [$($option),*].try_read_line($prompt, $default)
251 }};
252
253 ($prompt:expr; $default:expr; $tryread_type:expr) => {{
254 use smart_read::TryRead;
255 ($tryread_type).try_read_line($prompt, $default)
256 }};
257
258}
259
260
261
262
263
264// ================================ TYPES ================================ //
265
266
267
268/// Just `Result<T, Box<dyn Error>>`, mostly for internal use
269pub type BoxResult<T> = Result<T, Box<dyn Error>>;
270
271
272
273/// This is what powers the whole crate. Any type that implements this can be used with the macros
274pub trait TryRead {
275 /// Defines the output type of `read` and `prompt` macros
276 type Output;
277 /// Defines the type of the default input
278 type Default;
279 /// This is what's called by the `read` and `prompt` macros
280 fn try_read_line(self, prompt: Option<String>, default: Option<Self::Default>) -> BoxResult<Self::Output>;
281}
282
283
284
285
286
287// ================================ FUNCTIONS ================================ //
288
289
290
291/// Utility function, mostly for internal use
292pub fn read_stdin() -> Result<String, std::io::Error> {
293 std::io::stdout().flush()?;
294 let mut output = String::new();
295 std::io::stdin().read_line(&mut output)?;
296 if output.ends_with('\n') {output.pop();}
297 if output.ends_with('\r') {output.pop();}
298 Ok(output)
299}
300
301
302
303/// Tiny utility function, clears the terminal output, but you should probably use the [ClearScreen](https://crates.io/crates/clearscreen) crate instead
304pub fn clear_term() {
305 print!("{esc}c", esc = 27 as char);
306}
307
308
309
310/// Waits for the user to press enter, prints "Press enter to continue "
311///
312/// This is basically a wrapper for `prompt!("Press enter to continue ")`
313pub fn wait_for_enter() {
314 // this would be `prompt!("Press...")`, but that causes an error because of scopes
315 print!("Press enter to continue ");
316 let _ = read_stdin();
317}