1#![feature(int_roundings)]
2
3use std::{any::type_name, fmt::Display, io, str::FromStr};
4
5fn print_prompt(prompt: Option<&str>) -> bool {
6 match prompt {
7 Some(input_prompt) => {
8 println!("{}", input_prompt);
9 return true;
10 }
11 None => {
12 return false;
13 }
14 }
15}
16
17fn check_length(length: &usize, max_length: Option<i32>) -> bool {
18 match max_length {
19 Some(max) => {
20 let input_length = *length as i32;
21 if input_length > max {
22 println!(
23 "Your input is {} characters higher than the {} character limit. Please try again.",
24 input_length - max,
25 length
26 );
27 return false;
28 } else {
29 return true;
30 }
31 }
32 None => return true,
33 }
34}
35
36fn check_empty(length: &usize, can_be_empty: bool) -> bool {
37 let input_length = *length as i32;
38 if input_length <= 0 && !can_be_empty {
39 println!("Your input cannot be empty.");
40 return false;
41 } else {
42 return true;
43 }
44}
45
46fn check_min_max<T: PartialOrd + Display>(
47 number: T,
48 min_value: Option<T>,
49 max_value: Option<T>,
50) -> bool {
51 match min_value {
52 Some(min) => {
53 if number < min {
54 println!(
55 "Your input ({}) is lower than the minimum allowed value of {}.",
56 number, min
57 );
58 return false;
59 }
60 }
61 None => {}
62 }
63 match max_value {
64 Some(max) => {
65 if number > max {
66 println!(
67 "Your input ({}) is larger than the maximum allowed value of {}.",
68 number, max
69 );
70 return false;
71 }
72 }
73 None => {}
74 }
75 return true;
76}
77
78fn check_number_is_a_choice<T: PartialOrd + Display>(
79 number: &T,
80 choices: &Vec<T>,
81 show_choices_on_failure: bool,
82) -> bool {
83 for choice in choices.iter() {
84 if number == choice {
85 return true;
86 }
87 }
88 if show_choices_on_failure {
89 print!("Your input ({}) is not an option of the choices: ", number);
90 for choice in choices.iter() {
91 print!("{}, ", choice);
92 }
93 print!("\n");
94 } else {
95 println!("Your input ({}) is not a valid choice.", number);
96 }
97
98 return false;
99}
100
101fn check_string_is_a_choice(
102 input: &String,
103 choices: &Vec<&str>,
104 case_sensitive: bool,
105 show_choices_on_failure: bool,
106) -> bool {
107 for choice in choices.iter() {
108 if input == choice {
109 return true;
110 } else if input.to_lowercase() == choice.to_lowercase() && !case_sensitive {
111 return true;
112 }
113 }
114 if show_choices_on_failure {
115 print!("Your input ({}) is not an option of the choices: ", input);
116 for choice in choices.iter() {
117 print!("{}, ", choice);
118 }
119 print!("\n");
120 } else {
121 print!("Your input ({}) is not a valid choice. ", input);
122 }
123 print!("(Case Sensitive: {})\n", case_sensitive);
124 return false;
125}
126
127pub fn print_list<T: Display>(header_message: Option<&str>, items: &[T]) {
143 print_prompt(header_message);
144 for i in 0..items.len() {
145 println!("{}", items[i])
146 }
147}
148
149pub fn clear_terminal() {
158 print!("{esc}c", esc = 27 as char);
159}
160
161pub fn paginated_list<T: Display>(
182 header_message: Option<&str>,
183 items: &[T],
184 items_per_page: i32,
185 clear_on_update: bool,
186) {
187 if items_per_page <= 0 {
188 panic!("Items per page must be greater than zero.");
189 }
190 let mut quit = false;
191 let number_of_items = items.len() as i32;
192 let mut current_page: i32 = 1;
193 let mut number_of_pages: i32 = number_of_items.div_ceil(items_per_page);
194 if number_of_pages == 0 {
195 number_of_pages = 1;
196 }
197 while !quit {
198 print_prompt(header_message);
199 let end_index: i32;
200 if current_page == number_of_pages {
201 end_index = number_of_items;
202 } else {
203 end_index = current_page * items_per_page;
204 }
205 if number_of_items > 0 {
206 for i in ((current_page - 1) * items_per_page)..end_index {
207 println!("{}", items[i as usize]);
208 }
209 }
210 println!("(Page {} of {})", current_page, number_of_pages);
211 let user_input = select_string_from_choices(
212 Some("Press N to view the next page, P for previous, S for a specific page, or E to Exit."),
213 Some("Press N to view the next page, P for previous, S for a specific page, or E to Exit."),
214 vec!["N", "P", "S", "E"],
215 false,
216 true
217 );
218 match user_input.to_lowercase().as_str() {
219 "n" => {
220 if current_page < number_of_pages {
221 current_page += 1;
222 }
223 }
224 "p" => {
225 if current_page > 1 {
226 current_page -= 1;
227 }
228 }
229 "s" => {
230 current_page = select_number_from_choices(
231 Some("Enter the page you would like to view."),
232 Some("Enter the page you would like to view."),
233 (1..(number_of_pages + 1)).collect(),
234 false,
235 );
236 }
237 "e" => {
238 quit = true;
239 }
240 _ => {}
241 }
242 if clear_on_update {
243 clear_terminal();
244 }
245 }
246}
247
248pub fn get_string(
264 prompt: Option<&str>,
265 repeat_message: Option<&str>,
266 max_length: Option<i32>,
267 can_be_empty: bool,
268) -> String {
269 print_prompt(prompt);
270 let mut input = String::new();
271 loop {
272 match io::stdin().read_line(&mut input) {
273 Ok(_n) => {
274 let trimmed_input = input.trim();
275 let length = trimmed_input.len();
276 if check_length(&length, max_length) && check_empty(&length, can_be_empty) {
277 return trimmed_input.to_string();
278 }
279 }
280 Err(error) => panic!("Unexpected stdin error while reading input: {}", error),
281 }
282 input.clear();
283 print_prompt(repeat_message);
284 }
285}
286
287pub fn get_number<T: PartialOrd + Display + FromStr + Copy>(
305 prompt: Option<&str>,
306 repeat_message: Option<&str>,
307 min_value: Option<T>,
308 max_value: Option<T>,
309) -> T {
310 print_prompt(prompt);
311 let mut input = String::new();
312 loop {
313 match io::stdin().read_line(&mut input) {
314 Ok(_n) => match input.trim().parse::<T>() {
315 Ok(number) => {
316 if check_min_max(number, min_value, max_value) {
317 return number;
318 }
319 }
320 Err(_e) => {
321 println!("Please enter a valid {} value.", type_name::<T>());
322 }
323 },
324 Err(error) => panic!("Unexpected stdin error while reading input: {}", error),
325 }
326 input.clear();
327 print_prompt(repeat_message);
328 }
329}
330
331pub fn select_number_from_choices<T: PartialOrd + Display + FromStr + Copy>(
349 prompt: Option<&str>,
350 repeat_message: Option<&str>,
351 choices: Vec<T>,
352 show_choices_on_failure: bool,
353) -> T {
354 if choices.len() == 0 {
355 panic!("You have not supplied a vector of at least one integer choices.")
356 }
357
358 print_prompt(prompt);
359 let mut input = String::new();
360 loop {
361 match io::stdin().read_line(&mut input) {
362 Ok(_n) => match input.trim().parse::<T>() {
363 Ok(number) => {
364 if check_number_is_a_choice(&number, &choices, show_choices_on_failure) {
365 return number;
366 }
367 }
368 Err(_e) => {
369 println!("Please enter a valid {} value.", type_name::<T>());
370 }
371 },
372 Err(error) => panic!("Unexpected stdin error while reading input: {}", error),
373 }
374 input.clear();
375 print_prompt(repeat_message);
376 }
377}
378
379pub fn select_string_from_choices(
398 prompt: Option<&str>,
399 repeat_message: Option<&str>,
400 choices: Vec<&str>,
401 case_sensitive: bool,
402 show_choices_on_failure: bool,
403) -> String {
404 if choices.len() == 0 {
405 panic!("You have not supplied a vector of at least one string choices.")
406 }
407 print_prompt(prompt);
408 let mut input = String::new();
409 loop {
410 match io::stdin().read_line(&mut input) {
411 Ok(_n) => {
412 let trimmed = input.trim().to_string();
413 if check_string_is_a_choice(
414 &trimmed,
415 &choices,
416 case_sensitive,
417 show_choices_on_failure,
418 ) {
419 return trimmed;
420 }
421 }
422 Err(error) => panic!("Unexpected stdin error while reading input: {}", error),
423 }
424 input.clear();
425 print_prompt(repeat_message);
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 #[test]
434 fn test_print_prompt() {
435 let no_prompt: Option<&str> = None;
436 let some_prompt: Option<&str> = Some("Test Message.");
437 assert_eq!(print_prompt(no_prompt), false);
438 assert_eq!(print_prompt(some_prompt), true);
439 }
440
441 #[test]
442 fn test_check_length() {
443 let no_max_length: Option<i32> = None;
444 let yes_max_length: Option<i32> = Some(10);
445 let small_string = "hi";
446 let big_string = "abcuiwehfuewnfiuewnf";
447 assert_eq!(check_length(&small_string.len(), no_max_length), true);
448 assert_eq!(check_length(&small_string.len(), yes_max_length), true);
449 assert_eq!(check_length(&big_string.len(), yes_max_length), false);
450 }
451
452 #[test]
453 fn test_check_empty() {
454 let empty_string = "";
455 let non_empty_string = "Hello!";
456 assert_eq!(check_empty(&empty_string.len(), true), true);
457 assert_eq!(check_empty(&empty_string.len(), false), false);
458 assert_eq!(check_empty(&non_empty_string.len(), false), true);
459 assert_eq!(check_empty(&non_empty_string.len(), true), true);
460 }
461
462 #[test]
463 fn test_min_max() {
464 let no_min: Option<i32> = None;
465 let min: Option<i32> = Some(1);
466 let no_max: Option<i32> = None;
467 let max: Option<i32> = Some(3);
468 let no_min_2: Option<f32> = None;
469 let min_2: Option<f32> = Some(1.5);
470 let no_max_2: Option<f32> = None;
471 let max_2: Option<f32> = Some(3.5);
472 assert_eq!(check_min_max(5, no_min, no_max), true);
473 assert_eq!(check_min_max(-5, min, no_max), false);
474 assert_eq!(check_min_max(-5, no_min, max), true);
475 assert_eq!(check_min_max(5, no_min, max), false);
476 assert_eq!(check_min_max(5, min, max), false);
477 assert_eq!(check_min_max(2, min, max), true);
478 assert_eq!(check_min_max(5.0, no_min_2, no_max_2), true);
479 assert_eq!(check_min_max(-5.0, min_2, no_max_2), false);
480 assert_eq!(check_min_max(-5.0, no_min_2, max_2), true);
481 assert_eq!(check_min_max(5.0, no_min_2, max_2), false);
482 assert_eq!(check_min_max(5.0, min_2, max_2), false);
483 assert_eq!(check_min_max(2.0, min_2, max_2), true);
484 }
485
486 #[test]
487 fn test_check_string_is_choice() {
488 let choices = vec!["Earl", "Roger", "Mark"];
489 let bob = String::from("Bob");
490 let earl_uppercase = String::from("EARL");
491 let mark = String::from("Mark");
492 assert_eq!(check_string_is_a_choice(&bob, &choices, false, true), false);
493 assert_eq!(check_string_is_a_choice(&bob, &choices, true, true), false);
494 assert_eq!(
495 check_string_is_a_choice(&earl_uppercase, &choices, false, true),
496 true
497 );
498 assert_eq!(
499 check_string_is_a_choice(&earl_uppercase, &choices, true, false),
500 false
501 );
502 assert_eq!(check_string_is_a_choice(&mark, &choices, true, false), true);
503 }
504
505 #[test]
506 fn test_check_num_is_choice() {
507 let choices = vec![1, 5, 10, 15];
508 let choices_float = vec![0.5, 1.5, 2.0, 3.35];
509 assert_eq!(check_number_is_a_choice(&1, &choices, true), true);
510 assert_eq!(check_number_is_a_choice(&5, &choices, false), true);
511 assert_eq!(check_number_is_a_choice(&10, &choices, true), true);
512 assert_eq!(check_number_is_a_choice(&15, &choices, false), true);
513 assert_eq!(check_number_is_a_choice(&-50, &choices, true), false);
514 assert_eq!(check_number_is_a_choice(&0.5, &choices_float, false), true);
515 assert_eq!(check_number_is_a_choice(&1.5, &choices_float, true), true);
516 assert_eq!(check_number_is_a_choice(&2.0, &choices_float, false), true);
517 assert_eq!(check_number_is_a_choice(&3.35, &choices_float, true), true);
518 assert_eq!(
519 check_number_is_a_choice(&-5.5, &choices_float, false),
520 false
521 );
522 }
523}