chant 0.1.1

Shell glamour - beautiful prompts and output for scripts 🪄
Documentation
//! Spinner command.

use glyphs::{style, Color};
use scoria::{Spinner, SpinnerStyle};
use std::io::{stdout, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

/// Create a spinner.
pub fn spin(title: impl Into<String>) -> Spin {
    Spin::new(title)
}

/// Spinner builder.
pub struct Spin {
    title: String,
    spinner_style: SpinnerStyle,
    color: Option<Color>,
}

impl Spin {
    /// Create a new spinner.
    pub fn new(title: impl Into<String>) -> Self {
        Self {
            title: title.into(),
            spinner_style: SpinnerStyle::Dots,
            color: Some(Color::Cyan),
        }
    }

    /// Set spinner style.
    #[must_use]
    pub fn style(mut self, style: SpinnerStyle) -> Self {
        self.spinner_style = style;
        self
    }

    /// Set spinner color.
    #[must_use]
    pub fn color(mut self, color: Color) -> Self {
        self.color = Some(color);
        self
    }

    /// Run with a closure and show spinner until complete.
    pub fn run<F, T>(self, f: F) -> T
    where
        F: FnOnce() -> T,
    {
        let running = Arc::new(AtomicBool::new(true));
        let running_clone = running.clone();

        let title = self.title.clone();
        let spinner_style = self.spinner_style;
        let color = self.color;

        // Spawn spinner thread
        let spinner_thread = thread::spawn(move || {
            let mut spinner = Spinner::new(spinner_style);
            if let Some(c) = color {
                spinner = spinner.color(c);
            }
            spinner = spinner.title(&title);

            let mut stdout = stdout();

            while running_clone.load(Ordering::Relaxed) {
                print!("\r{}", spinner.view());
                stdout.flush().ok();
                spinner.tick();
                thread::sleep(Duration::from_millis(spinner.interval_ms()));
            }

            // Clear spinner line
            print!("\r{}\r", " ".repeat(title.len() + 10));
            stdout.flush().ok();
        });

        // Run the actual work
        let result = f();

        // Stop spinner
        running.store(false, Ordering::Relaxed);
        spinner_thread.join().ok();

        // Print completion
        println!(
            "{} {}",
            style("✓").fg(Color::Green),
            style(&self.title).dim()
        );

        result
    }

    /// Run with a result and show success/failure.
    pub fn run_result<F, T, E>(self, f: F) -> Result<T, E>
    where
        F: FnOnce() -> Result<T, E>,
    {
        let running = Arc::new(AtomicBool::new(true));
        let running_clone = running.clone();

        let title = self.title.clone();
        let spinner_style = self.spinner_style;
        let color = self.color;

        // Spawn spinner thread
        let spinner_thread = thread::spawn(move || {
            let mut spinner = Spinner::new(spinner_style);
            if let Some(c) = color {
                spinner = spinner.color(c);
            }
            spinner = spinner.title(&title);

            let mut stdout = stdout();

            while running_clone.load(Ordering::Relaxed) {
                print!("\r{}", spinner.view());
                stdout.flush().ok();
                spinner.tick();
                thread::sleep(Duration::from_millis(spinner.interval_ms()));
            }

            // Clear spinner line
            print!("\r{}\r", " ".repeat(title.len() + 10));
            stdout.flush().ok();
        });

        // Run the actual work
        let result = f();

        // Stop spinner
        running.store(false, Ordering::Relaxed);
        spinner_thread.join().ok();

        // Print completion
        match &result {
            Ok(_) => {
                println!(
                    "{} {}",
                    style("✓").fg(Color::Green),
                    style(&self.title).dim()
                );
            }
            Err(_) => {
                println!(
                    "{} {}",
                    style("✗").fg(Color::Red),
                    style(&self.title).dim()
                );
            }
        }

        result
    }
}