chant 0.1.1

Shell glamour - beautiful prompts and output for scripts 🪄
Documentation
//! Confirmation prompt.

use crossterm::{
    event::{self, Event, KeyCode, KeyModifiers},
    terminal,
};
use glyphs::{style, Color};
use std::io::{stdout, Write};

/// Create a confirmation prompt.
pub fn confirm(prompt: impl Into<String>) -> Confirm {
    Confirm::new(prompt)
}

/// Confirmation prompt builder.
pub struct Confirm {
    prompt: String,
    default: Option<bool>,
    affirmative: String,
    negative: String,
}

impl Confirm {
    /// Create a new confirmation.
    pub fn new(prompt: impl Into<String>) -> Self {
        Self {
            prompt: prompt.into(),
            default: None,
            affirmative: "Yes".to_string(),
            negative: "No".to_string(),
        }
    }

    /// Set default value.
    #[must_use]
    pub fn default(mut self, default: bool) -> Self {
        self.default = Some(default);
        self
    }

    /// Set affirmative text.
    #[must_use]
    pub fn affirmative(mut self, text: impl Into<String>) -> Self {
        self.affirmative = text.into();
        self
    }

    /// Set negative text.
    #[must_use]
    pub fn negative(mut self, text: impl Into<String>) -> Self {
        self.negative = text.into();
        self
    }

    /// Run the confirmation.
    pub fn run(self) -> bool {
        let mut stdout = stdout();

        terminal::enable_raw_mode().expect("Failed to enable raw mode");

        let hint = match self.default {
            Some(true) => format!("[{}/n]", self.affirmative.chars().next().unwrap_or('Y')),
            Some(false) => format!("[y/{}]", self.negative.chars().next().unwrap_or('N')),
            None => "[y/n]".to_string(),
        };

        print!(
            "{} {} ",
            style(&self.prompt).fg(Color::Cyan),
            style(&hint).dim()
        );
        stdout.flush().ok();

        let result = loop {
            if let Ok(Event::Key(key)) = event::read() {
                match key.code {
                    KeyCode::Char('y') | KeyCode::Char('Y') => break true,
                    KeyCode::Char('n') | KeyCode::Char('N') => break false,
                    KeyCode::Enter => {
                        if let Some(default) = self.default {
                            break default;
                        }
                    }
                    KeyCode::Esc => break false,
                    KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        break false
                    }
                    _ => {}
                }
            }
        };

        terminal::disable_raw_mode().expect("Failed to disable raw mode");

        let answer = if result {
            style(&self.affirmative).fg(Color::Green)
        } else {
            style(&self.negative).fg(Color::Red)
        };
        println!("{}", answer);

        result
    }
}