use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use std::io::{self, Write};
use std::time::Duration;
pub struct ProgressIndicator {
spinner: Option<ProgressBar>,
enabled: bool,
}
impl ProgressIndicator {
pub fn new(enabled: bool) -> Self {
Self {
spinner: None,
enabled,
}
}
pub fn start_spinner(&mut self, message: &str) {
if !self.enabled {
return;
}
let spinner = ProgressBar::new_spinner();
spinner.set_style(
ProgressStyle::default_spinner()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ")
.template("{spinner:.cyan} {msg}")
.unwrap_or_else(|_| ProgressStyle::default_spinner()),
);
spinner.set_message(message.to_string());
spinner.enable_steady_tick(Duration::from_millis(80));
self.spinner = Some(spinner);
}
pub fn update_message(&self, message: &str) {
if let Some(ref spinner) = self.spinner {
spinner.set_message(message.to_string());
}
}
pub fn finish_with_message(&mut self, message: &str) {
if let Some(spinner) = self.spinner.take() {
spinner.finish_with_message(format!("{} {}", "✓".green(), message));
}
}
pub fn finish_with_error(&mut self, message: &str) {
if let Some(spinner) = self.spinner.take() {
spinner.finish_with_message(format!("{} {}", "✗".red(), message));
}
}
pub fn clear(&mut self) {
if let Some(spinner) = self.spinner.take() {
spinner.finish_and_clear();
}
}
}
pub struct FileProgressBar {
bar: Option<ProgressBar>,
#[allow(dead_code)]
enabled: bool,
}
impl FileProgressBar {
pub fn new(total: usize, enabled: bool) -> Self {
if !enabled || total == 0 {
return Self {
bar: None,
enabled: false,
};
}
let bar = ProgressBar::new(total as u64);
bar.set_style(
ProgressStyle::default_bar()
.template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
.unwrap_or_else(|_| ProgressStyle::default_bar())
.progress_chars("#>-"),
);
Self {
bar: Some(bar),
enabled: true,
}
}
pub fn inc(&self, delta: u64) {
if let Some(ref bar) = self.bar {
bar.inc(delta);
}
}
pub fn set_message(&self, message: &str) {
if let Some(ref bar) = self.bar {
bar.set_message(message.to_string());
}
}
pub fn finish(&mut self) {
if let Some(bar) = self.bar.take() {
bar.finish_and_clear();
}
}
}
pub fn success_message(message: &str) -> String {
format!("{} {}", "✓".green().bold(), message)
}
pub fn error_message(message: &str) -> String {
format!("{} {}", "✗".red().bold(), message)
}
pub fn warning_message(message: &str) -> String {
format!("{} {}", "⚠".yellow().bold(), message)
}
pub fn info_message(message: &str) -> String {
format!("{} {}", "ℹ".blue().bold(), message)
}
pub fn confirm_prompt(message: &str, default: bool) -> io::Result<bool> {
let default_text = if default { "[Y/n]" } else { "[y/N]" };
print!("{} {} {} ", "?".yellow().bold(), message, default_text);
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim().to_lowercase();
Ok(match input.as_str() {
"" => default,
"y" | "yes" => true,
"n" | "no" => false,
_ => default,
})
}
pub fn print_section(title: &str) {
eprintln!();
eprintln!("{}", title.cyan().bold());
eprintln!("{}", "─".repeat(title.len()).cyan());
}
pub fn print_summary(title: &str, items: &[(&str, String)]) {
eprintln!();
eprintln!("{}", title.green().bold());
for (label, value) in items {
eprintln!(" {}: {}", label.bold(), value);
}
}
pub fn format_duration(duration_ms: u64) -> String {
let seconds = duration_ms as f64 / 1000.0;
if seconds < 1.0 {
format!("{}ms", duration_ms)
} else if seconds < 60.0 {
format!("{:.2}s", seconds)
} else {
let minutes = (seconds / 60.0).floor() as u64;
let secs = (seconds % 60.0).floor() as u64;
format!("{}m {}s", minutes, secs)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_duration() {
assert_eq!(format_duration(500), "500ms");
assert_eq!(format_duration(1500), "1.50s");
assert_eq!(format_duration(65000), "1m 5s");
}
#[test]
fn test_message_formatting() {
let msg = success_message("Test");
assert!(msg.contains("Test"));
let msg = error_message("Error");
assert!(msg.contains("Error"));
let msg = warning_message("Warning");
assert!(msg.contains("Warning"));
let msg = info_message("Info");
assert!(msg.contains("Info"));
}
}