input_macro/
lib.rs

1/*
2 * Copyright 2022 - Oliver Lenehan (sunsetkookaburra)
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7 */
8
9//! # input-macro - No-nonsense input!(...) macro for Rust.
10//!
11//! # Example
12//!
13//! ```no_run
14//! use input_macro::input;
15//!
16//! fn main() {
17//!     let name = input!("What's your name? ");
18//!     println!("Hello, {name}!");
19//!
20//!     let age: i64 = input!("How old are you today, {name}? ").parse().unwrap();
21//!
22//!     match age {
23//!         i if i < 0 => {
24//!             println!("Whoah, negative age! Impressive! 🌌");
25//!         },
26//!         _ => {
27//!             println!("Happy Birthday! Congratulations! 🥳");
28//!         },
29//!     }
30//!
31//!     match input!("Do you like chocolate 🍫 (y/N)? ").as_str() {
32//!         "y" | "Y" => {
33//!             println!("Yay! I like chocolate too 🙂.");
34//!         },
35//!         _ => {
36//!             println!("Oh well, all the more for me 😋.");
37//!         },
38//!     }
39//! }
40//! ```
41
42use std::fmt::Arguments;
43use std::io::{self, BufRead, Write};
44
45/// Displays formatted prompt text to the standard output and
46/// then reads the next line from the standard input,
47/// returning it as a [`String`].
48///
49/// # Panics
50///
51/// Panics if writing to `std::io::stdout()` fails,
52/// or reading from `std::io::stdin()` fails.
53///
54/// # Example
55/// ```no_run
56/// use input_macro::input;
57///
58/// let name: String = input!("What's your name? ");
59/// let age: i64 = input!("How old are you today {name}? ").parse().unwrap();
60/// println!(
61///     "In hexadecimal, thats {}{:x}!",
62///     if age < 0 { "-" } else { "" }, age.abs(),
63/// );
64/// ```
65#[macro_export]
66macro_rules! input {
67    () => ($crate::read_line_expect(&mut ::std::io::stdin().lock()).unwrap());
68    ($($arg:tt)*) => ($crate::input_fmt(&mut ::std::io::stdin().lock(), &mut ::std::io::stdout(), format_args!($($arg)*)).unwrap());
69}
70
71/// Writes and flushes a formatted string as prompt text to the `dst` ([`Write`])
72/// then reads the next line from the `src` ([`io::BufRead`]),
73/// returning it as a [`io::Result<String>`].
74///
75/// # Errors
76///
77/// This function will return any I/O error reported while formatting, flushing or reading.
78/// Also returns an [`io::ErrorKind::UnexpectedEof`] error if the stream reaches EOF.
79///
80/// # Example
81/// ```
82/// use std::io::Cursor;
83/// use input_macro::input_fmt;
84///
85/// let mut source = Cursor::new("Joe Bloggs\n");
86/// let mut output = Vec::new();
87/// let name = input_fmt(&mut source, &mut output, format_args!("What's your {}? ", "name"));
88/// assert_eq!(String::from_utf8(output).unwrap(), "What's your name? ");
89/// assert_eq!(name.unwrap(), "Joe Bloggs");
90/// ```
91pub fn input_fmt<B: BufRead, W: Write>(
92    src: &mut B,
93    dst: &mut W,
94    fmt: Arguments,
95) -> io::Result<String> {
96    dst.write_fmt(fmt)?;
97    dst.flush()?;
98    read_line_expect(src)
99}
100
101/// Reads the next line from `src` ([`io::BufRead`]), mapping
102/// EOF to [`io::ErrorKind::UnexpectedEof`] and returning a [`io::Result<String>`].
103///
104/// # Errors
105///
106/// This function will return any I/O error reported while reading.
107/// Also returns an [`io::ErrorKind::UnexpectedEof`] error if `src` reaches EOF (returns `Ok(0)`).
108///
109/// # Example
110/// ```
111/// use std::io::Cursor;
112/// use input_macro::read_line_expected;
113///
114/// let mut source = Cursor::new("Insert Text Here\n");
115/// let text = read_line_expected(&mut source);
116/// assert_eq!(text.unwrap(), "Insert Text Here");
117/// ```
118pub fn read_line_expect<B: BufRead>(src: &mut B) -> io::Result<String> {
119    src.lines().next().map_or(
120        Err(io::Error::new(
121            io::ErrorKind::UnexpectedEof,
122            "Input BufRead reached EOF before".to_string(),
123        )),
124        |line| line,
125    )
126}