fob_cli/ui/spinner.rs
1//! Simple spinner for tasks without known duration.
2
3use indicatif::{ProgressBar, ProgressStyle};
4use owo_colors::OwoColorize;
5use std::time::Duration;
6
7/// Simple spinner for tasks without known duration.
8///
9/// Useful for quick operations like loading config or checking files.
10///
11/// # Examples
12///
13/// ```no_run
14/// use fob_cli::ui::Spinner;
15///
16/// let spinner = Spinner::new("Loading config...");
17/// // Do work...
18/// spinner.finish("Config loaded!");
19/// ```
20pub struct Spinner {
21 pb: ProgressBar,
22}
23
24impl Spinner {
25 /// Create and start a new spinner.
26 ///
27 /// # Arguments
28 ///
29 /// * `message` - Initial message to display
30 ///
31 /// # Examples
32 ///
33 /// ```no_run
34 /// use fob_cli::ui::Spinner;
35 ///
36 /// let spinner = Spinner::new("Loading...");
37 /// // Do work...
38 /// spinner.finish("Done!");
39 /// ```
40 pub fn new(message: &str) -> Self {
41 let pb = ProgressBar::new_spinner();
42 pb.set_style(
43 ProgressStyle::default_spinner()
44 .template("{spinner:.cyan} {msg}")
45 .expect("valid template")
46 .tick_strings(&["◐", "◓", "◑", "◒"]),
47 );
48 pb.set_message(message.to_string());
49 pb.enable_steady_tick(Duration::from_millis(100));
50
51 Self { pb }
52 }
53
54 /// Update spinner message while it's running.
55 ///
56 /// # Arguments
57 ///
58 /// * `message` - New message to display
59 pub fn set_message(&self, message: &str) {
60 self.pb.set_message(message.to_string());
61 }
62
63 /// Finish spinner with success message.
64 ///
65 /// Displays a green checkmark.
66 ///
67 /// # Arguments
68 ///
69 /// * `message` - Success message to display
70 pub fn finish(&self, message: &str) {
71 self.pb
72 .finish_with_message(format!("{} {}", "✓".green(), message));
73 }
74
75 /// Finish spinner with error message.
76 ///
77 /// Displays a red X.
78 ///
79 /// # Arguments
80 ///
81 /// * `message` - Error message to display
82 pub fn fail(&self, message: &str) {
83 self.pb
84 .finish_with_message(format!("{} {}", "✗".red(), message));
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_spinner_creation() {
94 // Should not panic
95 let spinner = Spinner::new("Loading...");
96 spinner.set_message("Updated");
97 spinner.finish("Done");
98 }
99
100 #[test]
101 fn test_spinner_fail() {
102 let spinner = Spinner::new("Processing");
103 spinner.fail("Failed");
104 }
105}