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}