# Smart Read
[![Crates.io](https://img.shields.io/crates/v/smart-read.svg)](https://crates.io/crates/smart-read)
[![Crates.io](https://img.shields.io/crates/d/smart-read.svg)](https://crates.io/crates/smart-read)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/What42Pizza/rust-smart-read/blob/main/LICENSE)
#### Complex but easy ways to read user input
Anything that implements [TryRead](https://docs.rs/smart-read/latest/smart_read/trait.TryRead.html) can be used with the [read](https://docs.rs/smart-read/latest/smart_read/macro.read.html) and [prompt](https://docs.rs/smart-read/latest/smart_read/macro.prompt.html) macros, and features are added by creating new implementations of this trait. For a list of default implementations, just go to the main [docs](https://docs.rs/smart-read/latest/smart_read/) page.
## Qualities
- **Extremely Customizable**, basically anything can be implemented
- **Extremely Simple**, for both using and extending
- **Extremely Ergonomic**, everything is as effortless as possible
### Basic Usage
```
use smart_read::prelude::*;
// read a line of text
let input = read!();
// prompt a line of text
let input = prompt!("Enter a string: ");
// read specific types
let input = read!(UsizeInput);
let input = read!(BoolInput);
let input = read!(NonWhitespaceInput);
let input = read!(I32Input);
// read a number within a range
let input = read!(0. ..= 100.);
// read a bool
let input = prompt!("Confirm input: "; YesNoInput);
// set a default value
let input = prompt!("Confirm input: "; [true] YesNoInput);
// choose from a list of options
let (index, input) = read!(["red", "green", "blue"]);
// some input types have special syntax
let (index, input) = read!(= "red", "green", "blue");
// give options bulletins, alternate matching strings, and extra data
let (index, input) = read!([
InputOption::new("1", "red", vec!("r", "the first color"), ()), // displayed as "1: red", and so on
InputOption::new("2", "green", vec!("g", "the second color"), ()),
InputOption::new("3", "blue", vec!("b", "the third color"), ()),
]);
// same as above, but using special syntax
let (index, input) = read!(=
["1", "red", "r", "the first color"], (),
["2", "green", "g", "the second color"], (),
["3", "blue", "b", "the third color"], (),
);
// one-time custom logic
let input = prompt!("Enter an even int: "; TransformValidate (|x: String| -> Result { // explicit types here are optional, only added for demonstration
// validate input as an integer
let Ok(x) = x.parse::() else {return Err(String::from("Could not parse input"));};
// validate input as even
if x % 2 != 0 {return Err(String::from("Input is not even."));}
Ok(x)
}));
// combine any features
let (index, input) = prompt!("Enter an int: "; [1usize] = 1, 2, 3, 4, 5); // uses prompt message, default value, and special list_constraint syntax
```
### Example stdout (from the read_lines example)
```
==== `read!(0. ..= 100.)` ====
Enter a number within the range [0.0, 100.0]: 100.0001
Invalid input, not within bounds
Enter a number within the range [0.0, 100.0]: aa
Could not parse input (error: invalid float literal)
Enter a number within the range [0.0, 100.0]: 1.
You entered: "1"
```
### Extend Existing Functionality
```
use smart_read::prelude::*;
#[derive(Clone, PartialEq)]
pub struct Car {
pub name: String,
pub color: String,
}
// choose from a list of cars
fn main() {
let (index, input) = read!(= new_car("Red", "Toyota"), new_car("Silver", "Ram"));
println!("You chose: {input} (index {index})");
}
pub fn new_car(color: impl Into, name: impl Into) -> Car {
Car {name: name.into(), color: color.into()}
}
impl std::fmt::Display for Car {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.color, self.name)
}
}
```
### Add New Functionality
```
use smart_read::*;
struct PasswordInput {
pub min_len: usize,
pub min_digits: usize,
}
// take in a password input
fn main() {
let input = read!(PasswordInput {min_len: 10, min_digits: 1});
println!("You entered: \"{input}\"");
}
impl<'a> TryRead<'a> for PasswordInput {
type Output = String;
type Default = (); // ensure no default can be given
fn try_read_line(&self, prompt: Option, default: Option) -> smart_read::BoxResult {
if default.is_some() {return DefaultNotAllowedError::new_box_result();}
let prompt = prompt.unwrap_or_else(
|| format!("Enter a password (must have {}+ characters and have {}+ digits): ", self.min_len, self.min_digits)
);
loop {
print!("{prompt}");
let password = read_stdin()?;
if password.len() < 10 {
println!("Password must have at least 10 characters");
continue;
}
if password.chars().filter(|c| c.is_digit(10)).count() < 1 {
println!("Password must have at least 1 digit");
continue;
}
return Ok(password)
}
}
}
```