input_validation/lib.rs
1//! Prompts the user for input and parses the input as a value of type 'T'.
2//!
3//! If the input cannot be parsed as the request 'T', the function will prompt the user again
4//! until a valid input is entered.
5//!
6//! # Arguments
7//!
8//! * 'prompt' - A string that will be printed to the console to prompt the user for input.
9//!
10//! # Returns
11//!
12//! A value of type 'T' that was parsed from the user's input.
13//!
14//! # Panics
15//! This function will panic if 'T' does not implement the 'FromStr' trait.
16use std::io::{self, Write};
17use regex::Regex;
18
19/// Returns the decimal separator
20fn get_separator() -> char {
21 let integer = 1234;
22 let float = integer as f32 / 100 as f32;
23 // Parse the decimal separator from the float
24 let separator = float.to_string().chars().nth(2).unwrap();
25 separator
26}
27
28/// Prompts the user for input and parses the user's response as a specified type.
29///
30/// This function repeatedly prompts the user for input until the user provides input that can be
31/// successfully parsed as the specified type `T`. If the user's input cannot be parsed as `T`,
32/// the function will continue to prompt the user for input.
33///
34/// # Examples
35///
36/// ```
37/// use input_validation::get_input;
38///
39/// let name: String = get_input("What is your name? ");
40/// println!("Hello, {}!", name);
41/// ```
42///
43/// ```
44/// use input_validation::get_input;
45///
46/// let age: u32 = get_input("How old are you? ");
47/// println!("You are {} years old.", age);
48/// ```
49///
50/// # Panics
51///
52/// This function will panic if it is unable to write to the standard output stream.
53///
54/// # Errors
55///
56/// This function will return an error if it is unable to read from the standard input stream or
57/// if the user's input cannot be parsed as the specified type `T`.
58pub fn get_input<T: std::str::FromStr>(prompt: &str) -> T
59where
60 <T as std::str::FromStr>::Err: std::fmt::Debug,
61{
62 // First letter of T type
63 let first_letter = std::any::type_name::<T>().chars().next().unwrap();
64
65 loop {
66 print!("{}", prompt);
67 io::stdout().flush().unwrap();
68
69 let mut input = String::new();
70
71 // If first_letter is i, u or f, remove any commas from input
72 if first_letter == 'i' || first_letter == 'u' || first_letter == 'f' {
73 // If the decimal separator is '.' set thousands separator to ',', else set it to '.'
74 let locale = get_separator();
75 let thousands_separator = if locale == '.' { ',' } else { '.' };
76
77 match io::stdin().read_line(&mut input) {
78 Ok(_) => match input.trim().replace(thousands_separator, "").parse::<T>() {
79 Ok(val) => return val,
80 Err(_) => continue,
81 },
82 Err(_) => continue,
83 }
84 }
85
86 match io::stdin().read_line(&mut input) {
87 Ok(_) => match input.trim().parse::<T>() {
88 Ok(val) => return val,
89 Err(_) => continue,
90 },
91 Err(_) => continue,
92 }
93 }
94}
95
96/// Reads input from the user and returns a vector of elements of type T.
97///
98/// This function prompts the user for input using the specified `prompt` and then splits
99/// the input string into individual elements using the specified `separator`. If all of the
100/// elements can be parsed into type `T`, then they are returned as a `Vec<T>`. If any of the
101/// elements fail to parse, the function will loop and prompt the user for input again.
102///
103/// # Examples
104///
105/// ```rust
106/// let numbers: Vec<i32> = get_list("Enter some numbers, separated by commas: ", ",");
107/// ```
108///
109/// # Panics
110///
111/// This function will panic if `separator` is an empty string.
112///
113/// # Errors
114///
115/// This function will keep looping and prompting for input if any of the elements fail to parse
116/// into type `T`. The error message from the parsing failure is printed to stderr.
117///
118pub fn get_list<T: std::str::FromStr>(prompt: &str, separator: &str) -> Vec<T>
119where
120 <T as std::str::FromStr>::Err: std::fmt::Debug,
121{
122 loop {
123 let input = get_input::<String>(prompt);
124 let values: Vec<_> = input.split(separator).map(str::trim).collect();
125 if values.iter().all(|s| s.parse::<T>().is_ok()) {
126 return values.iter().map(|s| s.parse::<T>().unwrap()).collect();
127 }
128 }
129}
130
131/// Prompts the user for a boolean input, returning `true` if the input is
132/// "y" or "yes" (case insensitive), and `false` if the input is "n" or "no"
133/// (case insensitive). If the input does not match either "y", "yes", "n", or
134/// "no", the function will loop and prompt the user again.
135///
136/// # Arguments
137///
138/// * `prompt` - A string slice that will be displayed to the user as the prompt
139/// for their input.
140///
141/// # Example
142///
143/// ```
144/// use user_input::{get_bool};
145///
146/// let confirm = get_bool("Are you sure you want to proceed? (y/n) ");
147/// if confirm {
148/// println!("User confirmed.");
149/// } else {
150/// println!("User declined.");
151/// }
152/// ```
153pub fn get_bool(prompt: &str) -> bool {
154 loop {
155 let input = get_input::<String>(prompt).to_lowercase();
156 match input.as_str() {
157 "y" | "yes" => return true,
158 "n" | "no" => return false,
159 _ => continue,
160 }
161 }
162}
163
164/// Prompts the user to select a choice from a list of options and returns the index of the selected choice.
165///
166/// # Arguments
167///
168/// * `prompt` - A string slice containing the prompt to display to the user.
169/// * `choices` - A slice containing the available choices.
170///
171/// # Examples
172///
173/// ```
174/// let choices = vec!["Option 1", "Option 2", "Option 3"];
175/// let index = get_choice("Please select an option", &choices);
176/// println!("You selected: {}", choices[index]);
177/// ```
178 pub fn get_choice(prompt: &str, choices: &[&str]) -> usize {
179 loop {
180 let choice = get_input::<String>(&format!("{} ({}) ", prompt, choices.join("/")));
181 match choices
182 .iter()
183 .position(|&c| c.to_lowercase() == choice.to_lowercase().trim())
184 {
185 Some(index) => return index,
186 None => continue,
187 }
188 }
189}
190
191
192/// Prompts the user to enter an email address and returns it as a string.
193///
194/// The function ensures that the email address is valid and has the correct format.
195///
196/// # Examples
197///
198/// ```
199/// use rust_input_lib::get_email;
200///
201/// let email = get_email("Enter your email address: ");
202/// println!("Your email address is: {}", email);
203/// ```
204pub fn get_email(prompt: &str) -> String {
205 let re = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
206
207 loop {
208 let email = get_input::<String>(prompt);
209 if re.is_match(&email) {
210 return email;
211 } else {
212 println!("Invalid email address. Please enter a valid email address.");
213 continue;
214 }
215 }
216}
217
218#[cfg(test)]
219mod integration_tests;