#user-input #input #read #prompt #stdin #cli

nightly smart-read

Complex but easy ways to read user input

14 releases (4 breaking)

new 0.5.3 Apr 22, 2024
0.5.2 Apr 22, 2024
0.4.1 Mar 29, 2024
0.3.2 Mar 21, 2024
0.1.1 Mar 6, 2024

#218 in Command-line interface

Download history 430/week @ 2024-03-04 26/week @ 2024-03-11 670/week @ 2024-03-18 137/week @ 2024-03-25 267/week @ 2024-04-01 31/week @ 2024-04-08

1,107 downloads per month

MIT license

35KB
623 lines

Smart Read

Crates.io Crates.io license

Complex but easy ways to read user input

Anything that implements TryRead can be used with the read and prompt macros, and features are added by creating new implementations of this trait. For a list of default implementations, just go to the main docs 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

// read a line of text
let _ = read!();

// prompt a line of text
let _ = prompt!("Enter a string: ");

// read specific types
let _ = read!(UsizeInput);
let _ = read!(BoolInput);
let _ = read!(NonWhitespaceInput);
let _ = read!(I32Input);

// read a number within a range
let _ = read!(0. ..= 100.);


// read a bool
let _ = prompt!("Confirm input: "; YesNoInput);

// set a default value
let _ = prompt!("Confirm input: "; [true] YesNoInput);


// choose from a list of options
let _ = read!(&["red", "green", "blue"]);

// some input type have special syntax
let _ = read!(= "red", "green", "blue");


// one-time custom logic
let _ = prompt!("Enter an even int: "; TransformValidate (|x: String| -> Result<isize, String> { // explicit types here are optional, only added for demonstration
	let Ok(x) = x.parse::<isize>() else {return Err(String::from("Could not parse input"));};
	if x % 2 != 0 {return Err(String::from("Input is not even"));}
	Ok(x)
}));


// combine any features
let _ = prompt!("Enter an int: "; [1] = 1, 2, 3, 4, 5);

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 input = read!(= new_car("Red", "Toyota"), new_car("Silver", "Ram"));
	println!("You chose: {input}");
}

pub fn new_car(color: impl Into<String>, name: impl Into<String>) -> 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 TryRead for PasswordInput {
	type Output = String;
	fn try_read_line(&self, read_data: TryReadArgs<Self::Output>) -> smart_read::BoxResult<Self::Output> {
		if read_data.default.is_some() {return DefaultNotAllowedError::new_box_result();}
		let prompt = read_data.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)
			
		}
	}
}

No runtime deps