text_io/
lib.rs

1//! This crate allows one-liners to read from a terminal
2//! A minimal example to get an i32 from the command line is
3//!
4//! ```rust,no_run
5//! use text_io::read;
6//!
7//! let i: i32 = read!();
8//! ```
9//!
10//! The `read!()` macro will always read until the next ascii whitespace character
11//! (`\n`, `\r`, `\t` or space).
12//!
13//! Any type that implements the `FromStr` trait can be read with the `read!` macro.
14//!
15//! # Advanced
16//! Text parsing can be done similar to `println!` by adding a format string
17//! to the macro:
18//!
19//! ```rust,no_run
20//! use text_io::read;
21//!
22//! let i: i32 = read!("The answer: {}!");
23//! ```
24//!
25//! This will read `"The answer: "`, then an integer, then an exclamation mark. Any deviation from
26//! the format string will result in a panic.
27//!
28//! Note: only a single value can be read per `read!` invocation.
29
30use std::error;
31use std::fmt;
32use std::str::FromStr;
33
34#[non_exhaustive]
35#[derive(Debug, PartialEq)]
36pub enum Error {
37    MissingMatch,
38    MissingClosingBrace,
39    UnexpectedValue(u8, Option<u8>),
40    InvalidUtf8(Vec<u8>),
41    PartialUtf8(usize, Vec<u8>),
42    Parse(String, &'static str),
43}
44
45impl error::Error for Error {}
46
47impl fmt::Display for Error {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        use crate::Error::*;
50        use std::str::from_utf8;
51
52        match *self {
53            InvalidUtf8(ref raw) => write!(f, "input was not valid utf8: {:?}", raw),
54            Parse(ref s, arg) => write!(f, "could not parse {} as target type of {}", s, arg),
55            UnexpectedValue(exp, act) => write!(
56                f,
57                "found value {:?} not matching the pattern value {}",
58                act.map(|b| b as char),
59                exp as char
60            ),
61            PartialUtf8(n, ref raw) => write!(
62                f,
63                "input was only partially valid utf8: \"{}\" followed by {:?}",
64                from_utf8(&raw[..n]).unwrap(),
65                &raw[n..]
66            ),
67            MissingMatch => write!(f, "Bad read! format string: did not contain {{}}"),
68            MissingClosingBrace => write!(
69                f,
70                "found single open curly brace at the end of the format string"
71            ),
72        }
73    }
74}
75
76pub fn match_next(expected: u8, iter: &mut dyn Iterator<Item = u8>) -> Result<(), Error> {
77    let next = iter.next();
78    if next != Some(expected) {
79        return Err(Error::UnexpectedValue(expected, next));
80    }
81    Ok(())
82}
83
84pub fn parse_capture<T>(
85    name: &'static str,
86    next: Option<u8>,
87    iter: &mut dyn Iterator<Item = u8>,
88) -> Result<T, Error>
89where
90    T: FromStr,
91    <T as FromStr>::Err: ::std::fmt::Debug,
92{
93    static WHITESPACES: &[u8] = b"\t\r\n ";
94    let raw: Vec<u8> = match next {
95        Some(c) => iter.take_while(|&ch| ch != c).collect(),
96        None => iter
97            .skip_while(|ch| WHITESPACES.contains(ch))
98            .take_while(|ch| !WHITESPACES.contains(ch))
99            .collect(),
100    };
101    match String::from_utf8(raw) {
102        Ok(s) => FromStr::from_str(&s).map_err(|_| Error::Parse(s, name)),
103        Err(e) => {
104            let n = e.utf8_error().valid_up_to();
105            let raw = e.into_bytes();
106            if n == 0 {
107                Err(Error::InvalidUtf8(raw))
108            } else {
109                Err(Error::PartialUtf8(n, raw))
110            }
111        }
112    }
113}
114
115/// ```rust,no_run
116/// use text_io::try_read;
117///
118/// let i: i32 = try_read!("The answer: {}!").unwrap();
119/// let i: Result<i32, _> = try_read!("The {}{{}}!", "The answer is 42!".bytes());
120/// assert!(i.is_err());
121/// ```
122///
123/// ```rust
124/// use text_io::try_read;
125///
126/// let i: Result<i32, _> = try_read!("The answer is {}!", "The answer is 42!".bytes());
127/// assert!(i.is_ok());
128///
129/// let i: Result<i32, _> = try_read!("The {}{{}}!", "The answer is 42!".bytes());
130/// assert!(i.is_err());
131/// ```
132#[macro_export]
133macro_rules! try_read(
134    () => { $crate::try_read!("{}") };
135    ($text:expr) => {{
136        (|| -> std::result::Result<_, $crate::Error> {
137            use std::io::Write;
138            std::io::stdout().flush().unwrap();
139            let __try_read_var__;
140            $crate::try_scan!($text, __try_read_var__);
141            Ok(__try_read_var__)
142        })()
143    }};
144    ($text:expr, $input:expr) => {{
145        (|| -> std::result::Result<_, $crate::Error> {
146            use std::io::Write;
147            std::io::stdout().flush().unwrap();
148            let __try_read_var__;
149            $crate::try_scan!($input => $text, __try_read_var__);
150            Ok(__try_read_var__)
151        })()
152    }};
153);
154
155/// ```rust,no_run
156/// use text_io::try_scan;
157///
158/// fn parser() -> Result<i32, Box<dyn std::error::Error>> {
159///     let i: i32;
160///     let text = "The answer is 42!";
161///
162///     try_scan!(text.bytes() => "The answer is {}!", i);
163///
164///     assert_eq!(i, 1);
165///     Ok(i)
166/// }
167/// ```
168#[macro_export]
169macro_rules! try_scan(
170    ($pattern:expr, $($arg:expr),*) => {
171        use ::std::io::Read;
172        $crate::try_scan!(::std::io::stdin().bytes().map(std::result::Result::unwrap) => $pattern, $($arg),*);
173    };
174    ($input:expr => $pattern:expr, $($arg:expr),*) => {{
175        $crate::try_scan!(@impl question_mark; $input => $pattern, $($arg),*)
176    }};
177    // implementation detail.
178    (@question_mark: $($e:tt)+) => {{
179        ($($e)+)?
180    }};
181    // implementation detail.
182    (@unwrap: $($e:tt)+) => {{
183        ($($e)+).unwrap()
184    }};
185    (@impl $action:tt; $input:expr => $pattern:expr, $($arg:expr),*) => {{
186        #![allow(clippy::try_err)]
187        use $crate::{Error, match_next, parse_capture};
188
189        // typesafe macros :)
190        let pattern: &'static str = $pattern;
191        let stdin: &mut dyn Iterator<Item = u8> = &mut ($input);
192
193        let mut pattern = pattern.bytes();
194
195        $(
196            $arg = loop {
197                match $crate::try_scan!(@$action: pattern.next().ok_or(Error::MissingMatch)) {
198                    b'{' => match $crate::try_scan!(@$action: pattern.next().ok_or(Error::MissingClosingBrace)) {
199                        b'{' => $crate::try_scan!(@$action: match_next(b'{', stdin)),
200                        b'}' => break $crate::try_scan!(@$action: parse_capture(stringify!($arg), pattern.next(), stdin)),
201                        _ => return $crate::try_scan!(@$action: Err(Error::MissingClosingBrace)),
202                    },
203                    c => $crate::try_scan!(@$action: match_next(c, stdin)),
204                }
205            };
206        )*
207
208        for c in pattern {
209            $crate::try_scan!(@$action: match_next(c, stdin))
210        }
211
212        format_args!($pattern, $({let _ = &$arg; ""}),*);
213    }};
214);
215
216/// All text input is handled through this macro
217#[macro_export]
218macro_rules! read(
219    ($($arg:tt)*) => {
220        $crate::try_read!($($arg)*).unwrap()
221    };
222);
223
224/// This macro allows to pass several variables so multiple values can be read
225#[macro_export]
226macro_rules! scan(
227    ($text:expr, $($arg:expr),*) => {
228        {
229            use ::std::io::Read;
230            $crate::scan!(::std::io::stdin().bytes().map(std::result::Result::unwrap) => $text, $($arg),*);
231        }
232    };
233    ($input:expr => $pattern:expr, $($arg:expr),*) => {{
234        $crate::try_scan!(@impl unwrap; $input => $pattern, $($arg),*)
235    }};
236);
237
238#[cfg(test)]
239mod tests {
240    use std::convert::Infallible;
241
242    use super::*;
243
244    /// dummy struct that does not implement Display
245    #[derive(Debug)]
246    struct NoDisplay;
247    impl FromStr for NoDisplay {
248        type Err = Infallible;
249        fn from_str(_: &str) -> Result<Self, Self::Err> {
250            Ok(Self)
251        }
252    }
253    #[test]
254    fn test_no_display() {
255        let _: NoDisplay = read!("{}", "".bytes());
256    }
257}