use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::io::{self, Write};
use std::thread;
use std::time::Duration;
use colored::*;
#[allow(dead_code)]
pub struct ProgressTracker {
#[allow(dead_code)]
total: usize,
completed: Arc<AtomicUsize>,
#[allow(dead_code)]
current_task: Arc<Mutex<String>>,
running: Arc<Mutex<bool>>,
handle: Arc<Mutex<Option<thread::JoinHandle<()>>>>,
}
impl ProgressTracker {
#[allow(dead_code)]
pub fn new(total: usize) -> Self {
Self {
total,
completed: Arc::new(AtomicUsize::new(0)),
current_task: Arc::new(Mutex::new(String::new())),
running: Arc::new(Mutex::new(false)),
handle: Arc::new(Mutex::new(None)),
}
}
#[allow(dead_code)]
pub fn start(&self) {
let total = self.total;
*self.running.lock().unwrap() = true;
let spinner_frames = vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
let completed_for_thread = Arc::clone(&self.completed);
let current_task_for_thread = Arc::clone(&self.current_task);
let running_for_thread = Arc::clone(&self.running);
let handle = thread::spawn(move || {
let mut frame_idx = 0;
while *running_for_thread.lock().unwrap() {
frame_idx = (frame_idx + 1) % spinner_frames.len();
let completed_count = completed_for_thread.load(Ordering::Relaxed);
let current = current_task_for_thread.lock().unwrap().clone();
print!("\x1B[2K\r");
let spinner = spinner_frames[frame_idx].cyan();
let progress_text = format!("[{}/{}]", completed_count, total);
print!("{} {} ", spinner, "Analyzing projects".bright_white().bold());
print!("{} ", progress_text.bright_cyan());
if !current.is_empty() {
print!("({})", current.yellow());
}
io::stdout().flush().unwrap();
thread::sleep(Duration::from_millis(80));
}
print!("\x1B[2K\r");
println!(
"{} {} {} {}",
"✓".green().bold(),
"Analyzed".bright_white().bold(),
format!("{} projects", total).bright_cyan().bold(),
"✅"
);
io::stdout().flush().unwrap();
});
if let Ok(mut h) = self.handle.lock() {
*h = Some(handle);
}
}
#[allow(dead_code)]
pub fn set_current_task(&self, task: impl Into<String>) {
if let Ok(mut guard) = self.current_task.lock() {
*guard = task.into();
}
}
#[allow(dead_code)]
pub fn inc_completed(&self) {
self.completed.fetch_add(1, Ordering::Relaxed);
}
pub fn stop(&self) {
if let Ok(mut guard) = self.running.lock() {
*guard = false;
}
if let Ok(mut h) = self.handle.lock() {
if let Some(handle) = h.take() {
let _ = handle.join();
}
}
}
#[allow(dead_code)]
pub fn get_completed(&self) -> usize {
self.completed.load(Ordering::Relaxed)
}
}
impl Drop for ProgressTracker {
fn drop(&mut self) {
self.stop();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_tracker_creation() {
let tracker = ProgressTracker::new(10);
assert_eq!(tracker.total, 10);
assert_eq!(tracker.completed.load(Ordering::Relaxed), 0);
}
#[test]
fn test_progress_tracker_increment() {
let tracker = ProgressTracker::new(5);
tracker.inc_completed();
tracker.inc_completed();
assert_eq!(tracker.get_completed(), 2);
}
#[test]
fn test_progress_tracker_set_task() {
let tracker = ProgressTracker::new(1);
tracker.set_current_task("test task");
let task = tracker.current_task.lock().unwrap().clone();
assert_eq!(task, "test task");
}
}