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 (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: `prompt!("Enter any string: ")`, `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: `read!([1] 0..10)`, `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: `read!()`, `prompt!("Enter a color: "; ["red"] &["red", "green", "blue"])`, `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: `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}