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: &nbsp; `prompt!("message to user"; [default_value] input_type)`
86//! 
87//! Read macros: &nbsp; `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: &nbsp; `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}