1use console::style;
2use indicatif::{ProgressBar, ProgressStyle};
3use std::time::Duration;
4
5pub fn spinner(msg: &str) -> ProgressBar {
6 let pb = ProgressBar::new_spinner();
7 #[allow(clippy::unwrap_used)]
8 let style = ProgressStyle::default_spinner()
9 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
10 .template("{spinner:.cyan} {msg}")
11 .unwrap();
12 pb.set_style(style);
13 pb.set_message(msg.to_string());
14 pb.enable_steady_tick(Duration::from_millis(80));
15 pb
16}
17
18pub fn success(msg: &str) {
19 println!("{} {}", style("✓").green().bold(), msg);
20}
21
22pub fn error(msg: &str) {
23 eprintln!("{} {}", style("✗").red().bold(), msg);
24}
25
26pub fn header(msg: &str) {
27 println!("\n{}", style(msg).bold().underlined());
28}
29
30pub fn confirm(msg: &str) -> bool {
31 eprint!("{} {} ", style("?").yellow().bold(), msg);
32 let mut input = String::new();
33 if std::io::stdin().read_line(&mut input).is_err() {
34 return false;
35 }
36 matches!(input.trim().to_lowercase().as_str(), "y" | "yes")
37}
38
39pub fn prompt(label: &str) -> Option<String> {
40 eprint!("{} {} ", style("?").yellow().bold(), label);
41 let mut input = String::new();
42 if std::io::stdin().read_line(&mut input).is_err() {
43 return None;
44 }
45 let trimmed = input.trim();
46 if trimmed.is_empty() {
47 None
48 } else {
49 Some(trimmed.to_string())
50 }
51}
52
53pub fn prompt_secret(label: &str) -> Option<String> {
54 eprint!("{} {} ", style("?").yellow().bold(), label);
55 let term = console::Term::stderr();
56 match term.read_secure_line() {
57 Ok(input) => {
58 let trimmed = input.trim().to_string();
59 if trimmed.is_empty() {
60 None
61 } else {
62 Some(trimmed)
63 }
64 }
65 Err(_) => None,
66 }
67}