io_prompt_prototype/
lib.rs

1//! A prototype for `io::prompt`, `io::promptln`, and `io::read_line`.
2//!
3//! # Examples
4//!
5//! ```no_run
6//! # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
7//! use io_prompt_prototype::{prompt, promptln};
8//!
9//! let num: u16 = prompt!("What's your favorite number? >").parse()?;
10//! println!("Oh, cool: {}!", num);
11//!
12//! let variable = "snack";
13//! let res = promptln!("What's your favorite {}? >", variable);
14//! println!("We love {} too!", res);
15//! # Ok(()) }
16//! ```
17//!
18//! # Design Considerations
19//!
20//! A survey of existing prompt functions can be found
21//! [here](https://github.com/rust-lang/rust/pull/75435#issuecomment-680278635)
22//! This library makes several tradeoffs in its design:
23//!
24//! - Just like the `std::io::println!` family of macros, the `prompt` macros
25//!     panic in the case of an error.
26//! - The prompt macros don't support parsing of values in-place. Users are
27//!     encouraged to `.parse` instead.
28//! - The prompt family of macros only support reading a single line at the time
29//!     and assigning it to a value.
30//! - The prompt family of macros doesn't support rich input types such
31//!     as passwords or dropdowns. This functionality is expected to be provided
32//!     through crates.io.
33//!
34//! This library is split into two parts: a convenient `read_line` function
35//! which is a shorthand for calling `Stdin::read_line` and reading into a new
36//! string. And the `prompt!` family of macros which support reading from
37//! writing to stdout/stderr, and reading a value from stdin.
38//!
39//! The focus for the `prompt` family of macros is on simplicity: its goal is to
40//! make it convenient to write quick prompts inside Rust programs in a way that feels similar to using `println!`. It does not
41//! try and cover parsing rules by introducing a new DSL. Such a DSL almost
42//! certainly needs to have regex-like capabilities, and would be nearly
43//! impossible to stabilize.
44
45#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
46#![deny(missing_debug_implementations, nonstandard_style)]
47#![warn(missing_docs, missing_doc_code_examples, unreachable_pub)]
48
49use std::io::{self, stdin};
50
51/// Reads a line of input from stdin.
52///
53/// This is a shorthand for calling [`Stdin::read_line`] and reading
54/// it into a new string.
55///
56/// # Examples
57///
58/// ```no_run
59/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
60/// use io_prompt_prototype::read_line;
61///
62/// print!("What's your favorite number? >");
63/// let num: u16 = read_line()?.parse()?;
64/// println!("Oh, cool: {}!", num);
65/// # Ok(()) }
66/// ```
67///
68/// [`Stdin::read_line`]: https://doc.rust-lang.org/std/io/struct.Stdin.html#method.read_line
69pub fn read_line() -> io::Result<String> {
70    let mut input = String::new();
71    stdin().read_line(&mut input)?;
72    Ok(input)
73}
74
75/// Prints to the standard output. Then reads a line of input.
76///
77/// This is a shorthand for calling [`print!`], [`read_line`], and removing
78/// any trailing newlines from the output.
79///
80/// # Examples
81///
82/// ```no_run
83/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
84/// use io_prompt_prototype::prompt;
85///
86/// let num: u16 = prompt!("What's your favorite number? >").parse()?;
87/// println!("Oh, cool: {}!", num);
88/// # Ok(()) }
89/// ```
90#[macro_export]
91macro_rules! prompt {
92    ($($arg:tt)*) => {{
93        use std::io::{stdout, Write};
94        print!($($arg)*);
95        stdout().flush().expect("failed writing to stdout");
96        let mut s = $crate::read_line().expect("failed reading from stdin");
97        if s.ends_with('\n') {
98            s.pop();
99        }
100        if s.ends_with('\r') {
101            s.pop();
102        }
103        s
104    }};
105}
106
107/// Prints to the standard output, with a newline. Then reads a line of input.
108///
109/// This is a shorthand for calling [`println!`], [`read_line`], and removing
110/// any trailing newlines from the output.
111///
112/// # Examples
113///
114/// ```no_run
115/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
116/// use io_prompt_prototype::promptln;
117///
118/// let num: u16 = promptln!("What's your favorite number? >").parse()?;
119/// println!("Oh, cool: {}!", num);
120/// # Ok(()) }
121/// ```
122#[macro_export]
123macro_rules! promptln {
124    ($($arg:tt)*) => {{
125        use std::io::{stdout, Write};
126        println!($($arg)*);
127        stdout().flush().expect("failed writing to stdout");
128        let mut s = $crate::read_line().expect("failed reading from stdin");
129        if let Some(_) = s.strip_suffix('\n') {
130            let _ = s.strip_suffix('\r');
131        }
132        s
133    }};
134}
135
136/// Prints to the standard error. Then reads a line of input.
137///
138/// This is a shorthand for calling [`eprint!`], [`read_line`], and removing
139/// any trailing newlines from the output.
140///
141/// # Examples
142///
143/// ```no_run
144/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
145/// use io_prompt_prototype::eprompt;
146///
147/// let num: u16 = eprompt!("What's your favorite number? >").parse()?;
148/// println!("Oh, cool: {}!", num);
149/// # Ok(()) }
150/// ```
151#[macro_export]
152macro_rules! eprompt {
153    ($($arg:tt)*) => {{
154        use std::io::{stdout, Write};
155        print!($($arg)*);
156        stdout().flush().expect("failed writing to stdout");
157        let mut s = $crate::read_line().expect("failed reading from stderr");
158        if s.ends_with('\n') {
159            s.pop();
160        }
161        if s.ends_with('\r') {
162            s.pop();
163        }
164        s
165    }};
166}
167
168/// Prints to the standard error, with a newline. Then reads a line of input.
169///
170/// This is a shorthand for calling [`eprintln!`], [`read_line`], and removing
171/// any trailing newlines from the output.
172///
173/// # Examples
174///
175/// ```no_run
176/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
177/// use io_prompt_prototype::epromptln;
178///
179/// let num: u16 = epromptln!("What's your favorite number? >").parse()?;
180/// println!("Oh, cool: {}!", num);
181/// # Ok(()) }
182/// ```
183#[macro_export]
184macro_rules! epromptln {
185    ($($arg:tt)*) => {{
186        use std::io::{stdout, Write};
187        println!($($arg)*);
188        stdout().flush().expect("failed writing to stdout");
189        let mut s = $crate::read_line().expect("failed reading from stderr");
190        if let Some(_) = s.strip_suffix('\n') {
191            let _ = s.strip_suffix('\r');
192        }
193        s
194    }};
195}