smart_read/
lib.rs

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