use crate::error::*;
use failure::format_err;
use libc::{self, c_int};
use std::io::{self, Write};
#[derive(Clone, Copy, Debug)]
pub enum Stream {
Stdout,
Stderr,
Stdin,
}
impl Stream {
fn to_writer(&self) -> Result<Box<dyn Write>> {
Ok(match *self {
Stream::Stdout => Box::new(io::stdout()),
Stream::Stderr => Box::new(io::stderr()),
Stream::Stdin => {
return Err(Error::InvalidArgument(format_err!(
"Cannot output interactive prompts on Stdin"
)));
}
})
}
}
pub fn isatty(stream: Stream) -> bool {
::atty::is(match stream {
Stream::Stdout => ::atty::Stream::Stdout,
Stream::Stderr => ::atty::Stream::Stderr,
Stream::Stdin => ::atty::Stream::Stdin,
})
}
fn to_io_result(ret: c_int) -> ::std::io::Result<()> {
match ret {
0 => Ok(()),
_ => Err(::std::io::Error::last_os_error()),
}
}
struct DisableEcho {
initial_attributes: libc::termios,
}
impl DisableEcho {
fn new() -> Result<Self> {
let mut initial_attributes = unsafe { ::std::mem::uninitialized() };
let mut attributes = unsafe { ::std::mem::uninitialized() };
to_io_result(unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut initial_attributes) })?;
to_io_result(unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut attributes) })?;
attributes.c_lflag &= !libc::ECHO;
attributes.c_lflag |= libc::ECHONL;
to_io_result(unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &attributes) })?;
Ok(DisableEcho {
initial_attributes: initial_attributes,
})
}
}
impl Drop for DisableEcho {
fn drop(&mut self) {
unsafe {
libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &self.initial_attributes);
}
}
}
fn remove_newline(mut s: String) -> Result<String> {
if !s.ends_with('\n') {
return Err(::std::io::Error::new(
::std::io::ErrorKind::UnexpectedEof,
"unexpected end of input",
)
.into());
}
s.pop();
if s.ends_with('\r') {
s.pop();
}
Ok(s)
}
pub fn prompt_for_string(
output_stream: Stream,
prompt: &str,
is_sensitive: bool,
) -> Result<String> {
if !isatty(output_stream) || !isatty(Stream::Stdin) {
return Err(Error::Precondition(format_err!(
"Cannot prompt for interactive user input when {:?} and Stdin are not TTYs",
output_stream
)));
}
let mut output_stream = output_stream.to_writer()?;
write!(output_stream, "{}", prompt)?;
output_stream.flush()?;
Ok({
let _disable_echo = match is_sensitive {
false => None,
true => Some(DisableEcho::new()?),
};
let mut ret = String::new();
io::stdin().read_line(&mut ret)?;
remove_newline(ret)?
})
}
pub fn continue_confirmation(output_stream: Stream, description: &str) -> Result<bool> {
let prompt = format!("{}Continue? [Yes/No] ", description);
loop {
let original_response = prompt_for_string(output_stream, prompt.as_str(), false)?;
let response = original_response.trim().to_lowercase();
if response == "y" || response == "yes" {
return Ok(true);
} else if response == "n" || response == "no" {
return Ok(false);
} else {
let mut output_stream = output_stream.to_writer()?;
write!(output_stream, "Invalid response '{}'.\n", original_response)?;
output_stream.flush()?;
}
}
}
pub fn prompt_for_string_confirm(
output_stream: Stream,
prompt: &str,
is_sensitive: bool,
) -> Result<String> {
loop {
let string = prompt_for_string(output_stream, prompt, is_sensitive)?;
if string == prompt_for_string(output_stream, "Confirm: ", is_sensitive)? {
return Ok(string);
}
}
}
pub struct MaybePromptedString {
value: String,
was_provided: bool,
}
impl MaybePromptedString {
pub fn new(
provided: Option<&str>,
output_stream: Stream,
prompt: &str,
is_sensitive: bool,
confirm: bool,
) -> Result<Self> {
let prompted: Option<String> = match provided {
None => Some(match confirm {
false => prompt_for_string(output_stream, prompt, is_sensitive)?,
true => prompt_for_string_confirm(output_stream, prompt, is_sensitive)?,
}),
Some(_) => None,
};
let was_provided = provided.is_some();
let value = provided.map_or_else(|| prompted.unwrap(), |s| s.to_owned());
Ok(MaybePromptedString {
value: value,
was_provided: was_provided,
})
}
pub fn was_provided(&self) -> bool {
self.was_provided
}
pub fn into_inner(self) -> String {
self.value
}
}