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