use console::{StyledObject, style};
use dialoguer::Input;
use indicatif::{ProgressBar, ProgressStyle};
pub const ICON_SUCCESS: &str = "✓";
pub const ICON_ERROR: &str = "✗";
pub const ICON_INFO: &str = "𝙞";
pub const ICON_ARROW: &str = "→";
pub const ICON_WARN: &str = "!";
pub const ICON_BULLET: &str = "•";
pub fn success_prefix() -> StyledObject<&'static str> {
style(ICON_SUCCESS).green().bold()
}
pub fn error_prefix() -> StyledObject<&'static str> {
style(ICON_ERROR).red().bold()
}
pub fn info_prefix() -> StyledObject<&'static str> {
style(ICON_INFO).cyan()
}
pub fn warn_prefix() -> StyledObject<&'static str> {
style(ICON_WARN).yellow().bold()
}
pub fn arrow_prefix() -> StyledObject<&'static str> {
style(ICON_ARROW).cyan().bold()
}
pub fn bullet_prefix() -> StyledObject<&'static str> {
style(ICON_BULLET).dim()
}
pub fn print_success(message: &str) {
println!("{} {}", success_prefix(), style(message).green());
}
pub fn print_error(message: &str) {
eprintln!("{} {}", error_prefix(), style(message).red());
}
pub fn print_info(message: &str) {
println!("{} {}", info_prefix(), style(message).dim());
}
pub fn print_warn(message: &str) {
println!("{} {}", warn_prefix(), style(message).yellow());
}
pub fn print_header(title: &str) {
println!("\n{} {}", arrow_prefix(), style(title).bold());
println!();
}
pub fn print_kv(key: &str, value: &str) {
println!(" {} {}", style(key).dim(), value);
}
pub fn print_kv_highlight(key: &str, value: &str) {
println!(" {} {}", style(key).dim(), style(value).cyan());
}
pub fn print_kv_warn(key: &str, value: &str) {
println!(" {} {}", style(key).dim(), style(value).yellow());
}
pub fn print_item(text: &str) {
println!(" {} {}", bullet_prefix(), text);
}
pub fn create_spinner(message: &str) -> ProgressBar {
let spinner = ProgressBar::new_spinner();
spinner.set_style(
ProgressStyle::default_spinner()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
.template("{spinner:.cyan} {msg}")
.unwrap(),
);
spinner.enable_steady_tick(std::time::Duration::from_millis(80));
spinner.set_message(message.to_string());
spinner
}
pub fn create_progress_bar(len: u64) -> ProgressBar {
let pb = ProgressBar::new(len);
pb.set_style(
ProgressStyle::default_bar()
.template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
.unwrap()
.progress_chars("━╸─"),
);
pb
}
pub fn prompt_input(prompt: &str) -> anyhow::Result<String> {
use dialoguer::theme::ColorfulTheme;
let theme = ColorfulTheme::default();
let result: String = Input::with_theme(&theme)
.with_prompt(prompt)
.interact_text()?;
Ok(result)
}
pub fn prompt_passphrase(prompt: &str, min_length: usize) -> anyhow::Result<String> {
use dialoguer::theme::ColorfulTheme;
let theme = ColorfulTheme::default();
let result: String = Input::with_theme(&theme)
.with_prompt(prompt)
.validate_with(|input: &String| -> Result<(), String> {
if input.len() < min_length {
Err(format!(
"Passphrase must be at least {} characters long",
min_length
))
} else {
Ok(())
}
})
.interact_text()?;
Ok(result)
}
pub type InputValidator = fn(&String) -> Result<(), String>;
pub fn prompt_input_with_default(
prompt: &str,
default: &str,
validator: Option<InputValidator>,
) -> anyhow::Result<String> {
use console::style;
use dialoguer::theme::ColorfulTheme;
let theme = ColorfulTheme::default();
let default_plain = default.to_string();
let styled_default = style(&default_plain).dim().to_string();
let result: String = if let Some(validate_fn) = validator {
Input::with_theme(&theme)
.with_prompt(prompt)
.default(styled_default.clone())
.validate_with(validate_fn)
.interact_text()?
} else {
Input::with_theme(&theme)
.with_prompt(prompt)
.default(styled_default.clone())
.interact_text()?
};
if result == styled_default || result.contains("\x1b[") || result.trim().is_empty() {
Ok(default_plain)
} else {
Ok(result)
}
}
pub fn generate_secure_passphrase(length: usize) -> String {
use rand::Rng;
const CHARSET: &[u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*-_=+";
let mut rng = rand::rng();
(0..length)
.map(|_| {
let idx = rng.random_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
}