1use std::fmt::Arguments;
2use std::io::{self, BufRead, Write};
3use std::str::FromStr;
4
5#[derive(Debug)]
7pub enum InputError<E> {
8 Io(io::Error),
10 Parse(E),
12 Eof,
14}
15
16impl<E: std::fmt::Display + std::fmt::Debug> std::fmt::Display for InputError<E> {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 match self {
19 InputError::Io(e) => write!(f, "I/O error: {}", e),
20 InputError::Parse(e) => write!(f, "Parse error: {}", e),
21 InputError::Eof => write!(f, "EOF encountered"),
22 }
23 }
24}
25
26impl<E: std::fmt::Display + std::fmt::Debug> std::error::Error for InputError<E> {}
27
28pub fn read_input_from<R, T>(
35 reader: &mut R,
36 prompt: Option<Arguments<'_>>,
37) -> Result<T, InputError<T::Err>>
38where
39 R: BufRead,
40 T: FromStr,
41 T::Err: std::fmt::Display + std::fmt::Debug,
42{
43 if let Some(prompt_args) = prompt {
44 print!("{}", prompt_args);
45 io::stdout().flush().map_err(InputError::Io)?;
47 }
48
49 let mut input = String::new();
50 let bytes_read = reader.read_line(&mut input).map_err(InputError::Io)?;
51
52 if bytes_read == 0 {
54 return Err(InputError::Eof);
55 }
56
57 let trimmed = input.trim_end_matches(['\r', '\n'].as_ref());
58 trimmed.parse::<T>().map_err(InputError::Parse)
59}
60
61pub fn read_input<T>() -> Result<T, InputError<T::Err>>
63where
64 T: FromStr,
65 T::Err: std::fmt::Display + std::fmt::Debug,
66{
67 let stdin = io::stdin();
68 let mut locked = stdin.lock();
69 read_input_from(&mut locked, None)
70}
71
72pub fn read_input_with_prompt<T>(prompt: Arguments<'_>) -> Result<T, InputError<T::Err>>
74where
75 T: FromStr,
76 T::Err: std::fmt::Display + std::fmt::Debug,
77{
78 let stdin = io::stdin();
79 let mut locked = stdin.lock();
80 read_input_from(&mut locked, Some(prompt))
81}
82
83#[macro_export]
100macro_rules! input {
101 () => {{
102 match $crate::read_input_from(&mut ::std::io::stdin().lock(), None) {
104 Ok(val) => Ok(Some(val)),
105 Err($crate::InputError::Eof) => Ok(None),
106 Err(err) => Err(err),
107 }
108 }};
109 ($($arg:tt)*) => {{
110 match $crate::read_input_from(
111 &mut ::std::io::stdin().lock(),
112 Some(format_args!($($arg)*))
113 ) {
114 Ok(val) => Ok(Some(val)),
115 Err($crate::InputError::Eof) => Ok(None),
116 Err(err) => Err(err),
117 }
118 }};
119}
120
121#[macro_export]
132macro_rules! inputln {
133 () => {{
134 match $crate::read_input_from(&mut ::std::io::stdin().lock(), None) {
135 Ok(val) => Ok(Some(val)),
136 Err($crate::InputError::Eof) => Ok(None),
137 Err(err) => Err(err),
138 }
139 }};
140 ($($arg:tt)*) => {{
141 println!("{}", format_args!($($arg)*));
142 ::std::io::Write::flush(&mut ::std::io::stdout()).unwrap();
143 match $crate::read_input_from(&mut ::std::io::stdin().lock(), None) {
144 Ok(val) => Ok(Some(val)),
145 Err($crate::InputError::Eof) => Ok(None),
146 Err(err) => Err(err),
147 }
148 }};
149}
150
151#[macro_export]
165macro_rules! input_no_eof {
166 () => {{
167 $crate::read_input_from(&mut ::std::io::stdin().lock(), None)
169 }};
170 ($($arg:tt)*) => {{
171 $crate::read_input_from(
172 &mut ::std::io::stdin().lock(),
173 Some(format_args!($($arg)*))
174 )
175 }};
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use std::io::{Cursor, Error, ErrorKind};
182
183 #[test]
185 fn test_read_input_integer() {
186 let mut reader = Cursor::new("42\n");
187 let res: Result<i32, _> = read_input_from(&mut reader, None);
188 assert_eq!(res.unwrap(), 42);
189 }
190
191 #[test]
193 fn test_read_input_float() {
194 let mut reader = Cursor::new("3.14159\n");
195 let res: Result<f64, _> = read_input_from(&mut reader, None);
196 assert!((res.unwrap() - 3.14159).abs() < f64::EPSILON);
197 }
198
199 #[test]
201 fn test_read_input_unsigned() {
202 let mut reader = Cursor::new("255\n");
203 let res: Result<u32, _> = read_input_from(&mut reader, None);
204 assert_eq!(res.unwrap(), 255);
205 }
206
207 #[test]
209 fn test_read_input_eof() {
210 let mut reader = Cursor::new("");
211 let res: Result<i32, _> = read_input_from(&mut reader, None);
212 assert!(matches!(res, Err(InputError::Eof)));
213 }
214
215 #[test]
217 fn test_read_input_parse_error() {
218 let mut reader = Cursor::new("not an int\n");
219 let res: Result<i32, _> = read_input_from(&mut reader, None);
220 assert!(matches!(res, Err(InputError::Parse(_))));
221 }
222
223 #[test]
225 fn test_read_input_string() {
226 let mut reader = Cursor::new("hello world\r\n");
227 let res: Result<String, _> = read_input_from(&mut reader, None);
228 assert_eq!(res.unwrap(), "hello world");
229 }
230
231 #[test]
233 fn test_with_prompt() {
234 let mut reader = Cursor::new("100\n");
235 let prompt = format_args!("Enter: ");
237 let res: Result<i32, _> = read_input_from(&mut reader, Some(prompt));
238 assert_eq!(res.unwrap(), 100);
239 }
240
241 #[test]
243 fn test_multiple_lines_valid() {
244 let mut reader = Cursor::new("123\n456\n");
245 let first: i32 = read_input_from(&mut reader, None).unwrap();
247 assert_eq!(first, 123);
248
249 let second: i32 = read_input_from(&mut reader, None).unwrap();
251 assert_eq!(second, 456);
252 }
253
254 #[test]
256 fn test_multiple_lines_parse_error_then_eof() {
257 let mut reader = Cursor::new("42\nnotanint\n");
258 let first: i32 = read_input_from(&mut reader, None).unwrap();
260 assert_eq!(first, 42);
261
262 let second = read_input_from::<_, i32>(&mut reader, None);
264 assert!(matches!(second, Err(InputError::Parse(_))));
265
266 let third = read_input_from::<_, i32>(&mut reader, None);
268 assert!(matches!(third, Err(InputError::Eof)));
269 }
270
271 #[test]
274 fn test_empty_line_behavior() {
275 let mut reader = Cursor::new("\n");
276 let res: Result<i32, _> = read_input_from(&mut reader, None);
277 assert!(matches!(res, Err(InputError::Parse(_))));
279 }
280
281 #[test]
284 fn test_input_macro() {
285 let mut reader = Cursor::new("HelloFromMacro\n");
286 let result: Result<String, _> = read_input_from(&mut reader, None);
292 assert_eq!(result.unwrap(), "HelloFromMacro");
293 }
294
295 #[test]
297 fn test_io_error() {
298 struct ErrorReader;
299
300 impl BufRead for ErrorReader {
301 fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
302 Err(Error::new(ErrorKind::Other, "Simulated I/O failure"))
304 }
305 fn consume(&mut self, _amt: usize) {}
306 }
307
308 impl std::io::Read for ErrorReader {
310 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
311 Err(Error::new(ErrorKind::Other, "Simulated I/O failure"))
312 }
313 }
314
315 let mut reader = ErrorReader;
316 let res: Result<String, _> = read_input_from(&mut reader, None);
317 assert!(matches!(res, Err(InputError::Io(_))));
318 }
319}