use std::io::{BufRead, IsTerminal};
use dialoguer::{Confirm, Password, theme::ColorfulTheme};
pub(crate) trait WizardInput {
type Value;
fn flag_value(&self) -> Option<Result<Self::Value, String>>;
fn prompt(&self) -> Result<Self::Value, String>;
fn non_interactive_fallback(&self) -> Result<Self::Value, String>;
fn resolve(&self) -> Result<Self::Value, String> {
if let Some(result) = self.flag_value() {
return result;
}
if std::io::stdin().is_terminal() {
self.prompt()
} else {
self.non_interactive_fallback()
}
}
}
pub fn read_secret(prompt: &str) -> Result<String, String> {
if std::io::stdin().is_terminal() {
Password::new()
.with_prompt(prompt)
.interact()
.map_err(|err| format!("failed to read secret: {err}"))
} else {
let mut line = String::new();
std::io::stdin()
.lock()
.read_line(&mut line)
.map_err(|err| format!("failed to read secret from stdin: {err}"))?;
Ok(line.trim_end_matches(['\n', '\r']).to_string())
}
}
pub fn confirm(prompt: &str, default: bool) -> Result<bool, String> {
if std::io::stdin().is_terminal() {
Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(prompt)
.default(default)
.interact()
.map_err(|err| format!("failed to read confirmation: {err}"))
} else {
let mut line = String::new();
std::io::stdin()
.lock()
.read_line(&mut line)
.map_err(|err| format!("failed to read confirmation from stdin: {err}"))?;
Ok(match line.trim().to_ascii_lowercase().as_str() {
"y" | "yes" => true,
"n" | "no" => false,
_ => default,
})
}
}