use std::io::{self, IsTerminal, Write};
pub fn stdout_is_tty() -> bool {
std::io::stdout().is_terminal()
}
pub fn stderr_is_tty() -> bool {
std::io::stderr().is_terminal()
}
pub fn stdin_is_tty() -> bool {
std::io::stdin().is_terminal()
}
pub fn format_severity(s: &str, use_color: bool) -> String {
if !use_color {
return s.to_string();
}
match s.to_lowercase().as_str() {
"critical" => format!("\x1b[31m{s}\x1b[0m"),
"high" => format!("\x1b[91m{s}\x1b[0m"),
"medium" => format!("\x1b[33m{s}\x1b[0m"),
"low" => format!("\x1b[36m{s}\x1b[0m"),
_ => s.to_string(),
}
}
pub fn color(text: &str, code: &str, use_color: bool) -> String {
if use_color {
format!("\x1b[{code}m{text}\x1b[0m")
} else {
text.to_string()
}
}
pub enum SelectError {
Cancelled,
JsonModeNeedsExplicit,
}
pub fn confirm(prompt: &str, default_yes: bool, skip_prompt: bool, is_json: bool) -> bool {
if skip_prompt || is_json {
return default_yes;
}
if !stdin_is_tty() {
eprintln!("Non-interactive mode detected, proceeding with default.");
return default_yes;
}
let hint = if default_yes { "[Y/n]" } else { "[y/N]" };
eprint!("{prompt} {hint} ");
io::stderr().flush().unwrap();
let mut answer = String::new();
io::stdin().read_line(&mut answer).unwrap();
let answer = answer.trim().to_lowercase();
if answer.is_empty() {
return default_yes;
}
answer == "y" || answer == "yes"
}
pub fn select_one(prompt: &str, options: &[String], is_json: bool) -> Result<usize, SelectError> {
if is_json {
return Err(SelectError::JsonModeNeedsExplicit);
}
if !stdin_is_tty() {
eprintln!("Non-interactive mode: auto-selecting first option.");
return Ok(0);
}
let selection = dialoguer::Select::with_theme(&dialoguer::theme::ColorfulTheme::default())
.with_prompt(prompt)
.items(options)
.default(0)
.interact_opt()
.map_err(|_| SelectError::Cancelled)?;
match selection {
Some(idx) => Ok(idx),
None => Err(SelectError::Cancelled),
}
}