tiny_input/
lib.rs

1//! Tiny input macros.
2//!
3//! This crate provides three macros for receiving user input:
4//! [`tiny_input!`], [`input!`] and [`raw_input!`].
5//!
6//! [`raw_input!`] is used for when you just need the string (while handling I/O errors):
7//!
8//! ```no_run
9//! use tiny_input::raw_input;
10//!
11//! let name = raw_input!("What is your name? ").unwrap();
12//!
13//! println!("Hello, {name}!");
14//! ```
15//!
16//! [`tiny_input!`] is useful for when panicking on I/O errors is fine,
17//! and you only need to parse the input:
18//!
19//! ```no_run
20//! use tiny_input::tiny_input;
21//!
22//! let value: u64 = tiny_input!("the square of ").unwrap();
23//!
24//! println!("is {}", value * value);
25//! ```
26//!
27//! [`input!`] is when you need to handle both I/O and parsing errors:
28//!
29//! ```no_run
30//! use tiny_input::{input, Error};
31//!
32//! match input!(as u64, "the square of ") {
33//!     Ok(value) => println!("is {}", value * value),
34//!     Err(error) => match error {
35//!         Error::Fetch(fetch_error) => eprintln!("failed to fetch: {fetch_error}"),
36//!         Error::Parse(parse_error) => eprintln!("failed to parse: {parse_error}"),
37//!     },
38//! }
39//! ```
40//!
41//! As one might have noticed, there are two kinds of [`tiny_input!`] and [`input!`],
42//! one that attempts to infer the type, and one where you can provide the type explicitly.
43
44#![forbid(unsafe_code)]
45#![forbid(missing_docs)]
46
47use thiserror::Error;
48
49/// Represents errors that can occur when processing inputs.
50#[derive(Debug, Error)]
51#[error(transparent)]
52pub enum Error<E> {
53    /// Fetch error. Returned when any I/O errors occur,
54    /// such as when writing to [`stdout`] and flushing it,
55    /// as well as when reading from [`stdin`].
56    ///
57    /// [`stdin`]: std::io::stdin
58    /// [`stdout`]: std::io::stdout
59    Fetch(std::io::Error),
60    /// Parse error, which is contrained to implement the [`Error`] trait.
61    /// Returned when parsing into `T` fails; the [`T::Err`] is wrapped into this variant.
62    ///
63    /// [`T::Err`]: std::str::FromStr::Err
64    /// [`Error`]: std::error::Error
65    Parse(E),
66}
67
68/// The specialized result type to be used in this library.
69pub type Result<T, E> = std::result::Result<T, Error<E>>;
70
71/// The message used for expecting values.
72pub const FETCH_ERROR: &str = "I/O error occured while fetching input";
73
74/// Invokes [`raw_input!`], panicking on I/O errors before parsing the string.
75#[macro_export]
76macro_rules! tiny_input {
77    (as $type: ty $(, $($token: tt)+)?) => {
78        $crate::raw_input!($($($token)+)?).expect($crate::FETCH_ERROR).parse::<$type>()
79    };
80    ($($token: tt)*) => {
81        $crate::raw_input!($($token)*).expect($crate::FETCH_ERROR).parse()
82    };
83}
84
85/// Similar to [`tiny_input!`], except I/O and parse errors are wrapped into [`enum@Error<E>`].
86#[macro_export]
87macro_rules! input {
88    (as $type: ty $(, $($token: tt)+)?) => {
89        $crate::raw_input!($($($token)+)?)
90            .map_err($crate::Error::Fetch)
91            .and_then(|string| string.parse::<$type>().map_err($crate::Error::Parse))
92    };
93    ($($token: tt)*) => {
94        $crate::raw_input!($($token)*)
95            .map_err($crate::Error::Fetch)
96            .and_then(|string| string.parse().map_err($crate::Error::Parse))
97    };
98}
99
100/// Fetches raw inputs, returning the resulting [`String`] and propagating I/O errors.
101#[macro_export]
102macro_rules! raw_input {
103    ($($token: tt)+) => {{
104        use ::std::io::Write;
105
106        let mut stdout = ::std::io::stdout().lock();
107
108        // avoid using `?` operator here
109
110        match write!(stdout, $($token)+) {
111            // we do not really need to know the byte count
112            Ok(_) => match stdout.flush() {
113                Ok(_) => $crate::raw_input!(),
114                Err(error) => Err(error),
115            },
116            Err(error) => Err(error),
117        }
118    }};
119    () => {{
120        use ::std::io::BufRead;
121
122        let mut string = ::std::string::String::new();
123
124        match ::std::io::stdin().lock().read_line(&mut string) {
125            // we do not need the byte count here
126            Ok(_) => {
127                string.pop();  // remove the newline character, if there is one
128
129                Ok(string)
130            },
131            Err(error) => Err(error),
132        }
133    }};
134}