Skip to main content

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> {}