smart_read/
range_constraints.rs

1use crate::*;
2use std::{ops::{Range, RangeBounds, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}, str::FromStr};
3
4
5
6/// Internal utility function
7pub fn read_range<T, R>(range: R, prompt: Option<String>, default: Option<T>, format: fn(&R) -> String) -> BoxResult<T>
8where
9	T: Display + FromStr + PartialOrd<T>,
10	R: RangeBounds<T>,
11	<T as FromStr>::Err: Display,
12{
13	let mut prompt = match prompt {
14		Some(v) => v,
15		None => format!("Enter a number within the range {}: ", format(&range)),
16	};
17	if let Some(default) = default.as_ref() {
18		prompt += &format!(" (default: {default})");
19	}
20	loop {
21		
22		print!("{prompt}");
23		let output_string = read_stdin()?;
24		if output_string.is_empty() && let Some(default) = default {
25			return Ok(default);
26		}
27		
28		let output = match output_string.parse::<T>() {
29			Ok(v) => v,
30			Err(err) => {
31				println!();
32				println!("Could not parse input (error: {err})");
33				continue;
34			}
35		};
36		if range.contains(&output) {
37			return Ok(output);
38		}
39		
40		println!();
41		println!("Invalid input, not within bounds");
42	}
43}
44
45
46
47impl<T> TryRead for Range<T>
48where
49	T: Display + FromStr + PartialOrd<T>,
50	<T as FromStr>::Err: Display,
51{
52	type Output = T;
53	type Default = T;
54	fn try_read_line(self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
55		fn format(range: &Range<impl Display>) -> String {
56			format!("[{:.1}, {:.1})", range.start, range.end)
57		}
58		read_range(self, prompt, default, format)
59	}
60}
61
62impl<T> TryRead for RangeInclusive<T>
63where
64	T: Display + FromStr + PartialOrd<T>,
65	<T as FromStr>::Err: Display,
66{
67	type Output = T;
68	type Default = T;
69	fn try_read_line(self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
70		fn format(range: &RangeInclusive<impl Display>) -> String {
71			format!("[{:.1}, {:.1}]", range.start(), range.end())
72		}
73		read_range(self, prompt, default, format)
74	}
75}
76
77impl<T> TryRead for RangeTo<T>
78where
79	T: Display + FromStr + PartialOrd<T>,
80	<T as FromStr>::Err: Display,
81{
82	type Output = T;
83	type Default = T;
84	fn try_read_line(self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
85		fn format(range: &RangeTo<impl Display>) -> String {
86			format!(".., {:.1})", range.end)
87		}
88		read_range(self, prompt, default, format)
89	}
90}
91
92impl<T> TryRead for RangeFrom<T>
93where
94	T: Display + FromStr + PartialOrd<T>,
95	<T as FromStr>::Err: Display,
96{
97	type Output = T;
98	type Default = T;
99	fn try_read_line(self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
100		fn format(range: &RangeFrom<impl Display>) -> String {
101			format!("[{:.1}, ..", range.start)
102		}
103		read_range(self, prompt, default, format)
104	}
105}
106
107impl<T> TryRead for RangeToInclusive<T>
108where
109	T: Display + FromStr + PartialOrd<T>,
110	<T as FromStr>::Err: Display,
111{
112	type Output = T;
113	type Default = T;
114	fn try_read_line(self, prompt: Option<String>, default: Option<Self::Output>) -> BoxResult<Self::Output> {
115		fn format(range: &RangeToInclusive<impl Display>) -> String {
116			format!(".., {:.1}]", range.end)
117		}
118		read_range(self, prompt, default, format)
119	}
120}