use anyhow::{Result, bail};
use inquire::{Confirm, Password, Select, Text};
use std::fmt::Display;
use std::io::IsTerminal;
pub const NON_INTERACTIVE_ENV: &str = "TVC_NON_INTERACTIVE";
pub fn stdin_can_prompt() -> bool {
std::io::stdin().is_terminal()
}
pub fn error_required_in_non_interactive(flag_hint: &str) -> anyhow::Error {
anyhow::anyhow!(
"{flag_hint} is required in non-interactive mode \
(set {flag_hint} or run in a TTY without --non-interactive \
/ {NON_INTERACTIVE_ENV}=true)"
)
}
pub fn bail_required_in_non_interactive(flag_hint: &str) -> Result<()> {
bail!(error_required_in_non_interactive(flag_hint));
}
pub fn bail_interactive_conflicts_with_non_interactive() -> Result<()> {
bail!("--interactive conflicts with --non-interactive or {NON_INTERACTIVE_ENV}=true");
}
pub fn ensure_stdin_is_tty() -> Result<()> {
if !stdin_can_prompt() {
bail!("--interactive requires a TTY");
}
Ok(())
}
pub fn required_text(message: &str, default: Option<&str>) -> Result<String> {
let value = text(message, default)?;
if value.trim().is_empty() {
bail!("{message} cannot be empty");
}
Ok(value)
}
pub fn text(message: &str, default: Option<&str>) -> Result<String> {
let mut prompt = Text::new(message);
if let Some(d) = default {
prompt = prompt.with_default(d);
}
Ok(prompt.prompt()?)
}
pub fn confirm(message: &str, default: bool) -> Result<bool> {
Ok(Confirm::new(message).with_default(default).prompt()?)
}
pub fn confirm_or_bail(message: &str, operation: &str) -> Result<()> {
if !confirm(message, false)? {
bail!("operation cancelled by user: {operation}");
}
Ok(())
}
pub fn select<T: Display>(message: &str, options: Vec<T>) -> Result<T> {
Ok(Select::new(message, options).prompt()?)
}
pub fn password(message: &str) -> Result<String> {
Ok(Password::new(message).without_confirmation().prompt()?)
}