use std::io::IsTerminal;
use dialoguer::{Confirm, Input, Password};
use zeroize::Zeroizing;
use crate::error::Error;
pub fn interactive() -> bool {
std::io::stdin().is_terminal() && std::io::stderr().is_terminal()
}
pub fn required<T>(
flag: Option<String>,
flag_name: &str,
question: &str,
parse: impl Fn(&str) -> Result<T, String>,
) -> Result<T, Error> {
if let Some(value) = flag {
return parse(&value).map_err(|e| Error::msg(format!("{flag_name}: {e}")));
}
if !interactive() {
return Err(Error::msg(format!(
"{flag_name} is required when not running in a terminal"
)));
}
let answer: String = Input::new()
.with_prompt(question)
.validate_with(|input: &String| parse(input).map(|_| ()))
.interact_text()
.map_err(prompt_error)?;
parse(&answer).map_err(Error::msg)
}
pub fn optional<T>(
flag: Option<String>,
flag_name: &str,
question: &str,
parse: impl Fn(&str) -> Result<T, String>,
) -> Result<Option<T>, Error> {
if let Some(value) = flag {
return parse(&value)
.map(Some)
.map_err(|e| Error::msg(format!("{flag_name}: {e}")));
}
if !interactive() {
return Ok(None);
}
let answer: String = Input::new()
.with_prompt(format!("{question} (blank to skip)"))
.allow_empty(true)
.validate_with(|input: &String| {
if input.trim().is_empty() {
Ok(())
} else {
parse(input).map(|_| ())
}
})
.interact_text()
.map_err(prompt_error)?;
if answer.trim().is_empty() {
Ok(None)
} else {
parse(&answer).map(Some).map_err(Error::msg)
}
}
pub fn with_default<T>(
flag: Option<String>,
flag_name: &str,
question: &str,
default: &str,
parse: impl Fn(&str) -> Result<T, String>,
) -> Result<T, Error> {
if let Some(value) = flag {
return parse(&value).map_err(|e| Error::msg(format!("{flag_name}: {e}")));
}
if !interactive() {
return parse(default).map_err(|e| Error::msg(format!("{flag_name}: {e}")));
}
let answer: String = Input::new()
.with_prompt(question)
.default(default.to_owned())
.validate_with(|input: &String| parse(input).map(|_| ()))
.interact_text()
.map_err(prompt_error)?;
parse(&answer).map_err(Error::msg)
}
pub fn confirm(question: &str) -> Result<bool, Error> {
if !interactive() {
return Ok(false);
}
Confirm::new()
.with_prompt(question)
.default(false)
.interact()
.map_err(prompt_error)
}
pub fn secret(question: &str) -> Result<Zeroizing<String>, Error> {
Password::new()
.with_prompt(question)
.interact()
.map(Zeroizing::new)
.map_err(prompt_error)
}
fn prompt_error(e: dialoguer::Error) -> Error {
Error::msg(format!("couldn't read your answer: {e}"))
}