1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
//! This library makes it easy to prompt for input in a console application on all platforms, Unix
//! and Windows alike.
//!
//! Here's how you can prompt for a reply:
//! ```no_run
//! let name = rprompt::prompt_reply("What's your name? ").unwrap();
//! println!("Your name is {}", name);
//! ```
//!
//! Alternatively, you can read the reply without prompting:
//! ```no_run
//! let name = rprompt::read_reply().unwrap();
//! println!("Your name is {}", name);
//! ```
//!
//! If you need more control over the source of the input, which can be useful if you want to unit
//! test your CLI or handle pipes gracefully, you can use `from_bufread` versions of the functions
//! and pass any reader you want:
//! ```no_run
//! let stdin = std::io::stdin();
//! let stdout = std::io::stdout();
//! let name = rprompt::prompt_reply_from_bufread(&mut stdin.lock(), &mut stdout.lock(), "What's your name? ").unwrap();
//! println!("Your name is {}", name);
//! ```
use rtoolbox::fix_line_issues::fix_line_issues;
use rtoolbox::print_tty::{print_tty, print_writer};
use std::io::{BufRead, BufReader, Write};
/// Reads user input from stdin
pub fn read_reply() -> std::io::Result<String> {
read_reply_from_bufread(&mut get_tty_reader()?)
}
/// Reads user input from anything that implements BufRead
pub fn read_reply_from_bufread(reader: &mut impl BufRead) -> std::io::Result<String> {
let mut reply = String::new();
reader.read_line(&mut reply)?;
fix_line_issues(reply)
}
/// Displays a message on the TTY, then reads user input from stdin
pub fn prompt_reply(prompt: impl ToString) -> std::io::Result<String> {
print_tty(prompt).and_then(|_| read_reply_from_bufread(&mut get_tty_reader()?))
}
/// Displays a message on the TTY, then reads user input from anything that implements BufRead
pub fn prompt_reply_from_bufread(
reader: &mut impl BufRead,
writer: &mut impl Write,
prompt: impl ToString,
) -> std::io::Result<String> {
print_writer(writer, prompt.to_string().as_str()).and_then(|_| read_reply_from_bufread(reader))
}
#[cfg(unix)]
fn get_tty_reader() -> std::io::Result<impl BufRead> {
Ok(BufReader::new(
std::fs::OpenOptions::new().read(true).open("/dev/tty")?,
))
}
#[cfg(windows)]
fn get_tty_reader() -> std::io::Result<impl BufRead> {
use std::os::windows::io::FromRawHandle;
use windows_sys::core::PCSTR;
use windows_sys::Win32::Foundation::{INVALID_HANDLE_VALUE, GENERIC_READ, GENERIC_WRITE};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileA, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
let handle = unsafe {
CreateFileA(
b"CONIN$\x00".as_ptr() as PCSTR,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
std::ptr::null(),
OPEN_EXISTING,
0,
INVALID_HANDLE_VALUE,
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(std::io::Error::last_os_error());
}
Ok(BufReader::new(unsafe {
std::fs::File::from_raw_handle(handle as _)
}))
}