input_lib/lib.rs
1use std::{fmt::Arguments, io::{self, BufRead, Write}, str::FromStr};
2
3/// A macro that:
4/// - optionally prints a prompt (with `print!`).
5/// - reads **one line** from stdin.
6/// - returns `Err(InputError::Eof)` if EOF is encountered.
7/// - returns `Err(InputError::Parse(e))` if the input cannot be parsed.
8/// - returns `Err(InputError::Io(e))` if an IO error occurs.
9///
10/// # Usage:
11/// ```no_run
12/// // No prompt
13/// let text: String = input!().unwrap();
14///
15/// // With prompt
16/// let name: String = input!("Enter your name: ").unwrap();
17///
18/// // Formatted prompt
19/// let user = "Alice";
20/// let age: String = input!("Enter {}'s age: ", user).unwrap();
21/// ```
22#[macro_export]
23macro_rules! input {
24 () => {{
25 $crate::read_input_from(
26 &mut ::std::io::stdin().lock(),
27 None,
28 $crate::PrintStyle::Continue,
29 )
30 }};
31 ($($arg:tt)*) => {{
32 $crate::read_input_from(
33 &mut ::std::io::stdin().lock(),
34 Some(format_args!($($arg)*)),
35 $crate::PrintStyle::Continue
36 )
37 }};
38}
39
40/// A macro that:
41/// - prints the prompt on its own line (with `println!`),
42/// - then reads one line,
43/// - returns `Err(InputError::Eof)` if EOF is encountered.
44/// - returns `Err(InputError::Parse(e))` if the input cannot be parsed.
45/// - returns `Err(InputError::Io(e))` if an IO error occurs.
46/// - otherwise parses into `String`.
47///
48/// # Usage:
49/// ```no_run
50/// let line: String = inputln!("What's your favorite color?").unwrap();
51/// ```
52#[macro_export]
53macro_rules! inputln {
54 () => {{
55 $crate::read_input_from(
56 &mut ::std::io::stdin().lock(),
57 None,
58 $crate::PrintStyle::NewLine
59 )
60 }};
61 ($($arg:tt)*) => {{
62 $crate::read_input_from(
63 &mut ::std::io::stdin().lock(),
64 None,
65 $crate::PrintStyle::NewLine
66 )
67 }};
68}
69
70/// A single function that:
71/// 1. Optionally prints a prompt (and flushes).
72/// 2. Reads one line from the provided `BufRead`.
73/// 3. Returns `Err(InputError::Eof)` if EOF is reached.
74/// 4. Parses into type `T`, returning `Err(InputError::Parse)` on failure.
75/// 5. Returns `Err(InputError::Io)` on I/O failure.
76pub fn read_input_from<R, T>(
77 reader: &mut R,
78 prompt: Option<Arguments<'_>>,
79 print_style: PrintStyle,
80) -> Result<T, InputError<T::Err>>
81where
82 R: BufRead,
83 T: FromStr,
84 T::Err: std::fmt::Display + std::fmt::Debug,
85{
86 if let Some(prompt_args) = prompt {
87 match print_style {
88 PrintStyle::Continue => {
89 // Use print! for no newline
90 print!("{}", prompt_args);
91 }
92 PrintStyle::NewLine => {
93 // Use println! for adding a newline
94 println!("{}", prompt_args);
95 }
96 }
97 // Always flush so the user sees the prompt immediately
98 io::stdout().flush().map_err(InputError::Io)?;
99 }
100
101 let mut input = String::new();
102 let bytes_read = reader.read_line(&mut input).map_err(InputError::Io)?;
103
104 // If 0, that's EOF — return Eof error
105 if bytes_read == 0 {
106 return Err(InputError::Eof);
107 }
108
109 let trimmed = input.trim_end_matches(['\r', '\n'].as_ref());
110 trimmed.parse::<T>().map_err(InputError::Parse)
111}
112
113/// A unified error type indicating either an I/O error, a parse error, or EOF.
114#[derive(Debug)]
115pub enum InputError<E> {
116 /// An I/O error occurred (e.g., closed stdin).
117 Io(io::Error),
118 /// Failed to parse the input into the desired type.
119 Parse(E),
120 /// EOF encountered (read_line returned 0).
121 Eof,
122}
123
124/// Defines how the prompt should be printed.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum PrintStyle {
127 /// Print the prompt without a trailing newline (uses `print!`).
128 Continue,
129 /// Print the prompt with a trailing newline (uses `println!`).
130 NewLine,
131}
132
133impl<E: std::fmt::Display + std::fmt::Debug> std::fmt::Display for InputError<E> {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 InputError::Io(e) => write!(f, "I/O error: {}", e),
137 InputError::Parse(e) => write!(f, "Parse error: {}", e),
138 InputError::Eof => write!(f, "EOF encountered"),
139 }
140 }
141}
142
143impl<E: std::fmt::Display + std::fmt::Debug> std::error::Error for InputError<E> {}