upgate-presentation 0.1.0

Keep globally installed developer tools up to date
Documentation
use std::fmt::{self, Display};
use std::io::IsTerminal;
use std::time::Duration;

use indicatif::{ProgressBar, ProgressStyle};

use crate::OutputTheme;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BatchTerminalAction {
    Scan,
    Plan,
    Apply,
}

impl BatchTerminalAction {
    const fn spinner_label(self) -> &'static str {
        match self {
            Self::Scan => "Scanning",
            Self::Plan => "Planning",
            Self::Apply => "Applying",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MutationNotice {
    Skip,
    Real,
}

impl Display for MutationNotice {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(match self {
            Self::Skip => {
                "note: apply runs in safe mode: mutating commands are skipped (safe mode)"
            }
            Self::Real => "warning: apply runs with real mutating commands are ENABLED",
        })
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BatchTerminal {
    theme: OutputTheme,
    stderr_is_tty: bool,
    spinner_suppressed: bool,
}

impl BatchTerminal {
    pub fn from_environment(theme: OutputTheme) -> Self {
        Self {
            theme,
            stderr_is_tty: std::io::stderr().is_terminal(),
            spinner_suppressed: false,
        }
    }
    pub const fn suppress_spinner(self) -> Self {
        Self {
            spinner_suppressed: true,
            ..self
        }
    }
    pub const fn spinner_enabled(self) -> bool {
        !self.spinner_suppressed && !self.theme.is_plain() && self.stderr_is_tty
    }
    pub const fn notice_enabled(self) -> bool {
        self.stderr_is_tty
    }
    pub fn start_action_spinner(self, action: BatchTerminalAction) -> ManagerSpinner {
        if !self.spinner_enabled() {
            return ManagerSpinner(None);
        }

        let progress = ProgressBar::new_spinner();
        progress.set_style(spinner_style(self.theme.color()));
        progress.set_message(format!("{}...", action.spinner_label()));
        progress.enable_steady_tick(Duration::from_millis(90));

        ManagerSpinner(Some(progress))
    }
}

#[derive(Debug)]
pub struct ManagerSpinner(Option<ProgressBar>);

impl Drop for ManagerSpinner {
    fn drop(&mut self) {
        if let Some(progress) = self.0.take() {
            progress.finish_and_clear();
        }
    }
}

fn spinner_style(color: bool) -> ProgressStyle {
    let (template, ticks): (&str, &[&str]) = if color {
        (
            "{spinner:.cyan} {msg}",
            &["", "", "", "", "", "", "", "", "", ""],
        )
    } else {
        ("{spinner} {msg}", &["-", "\\", "|", "/"])
    };

    ProgressStyle::with_template(template)
        .unwrap_or_else(|_| ProgressStyle::default_spinner())
        .tick_strings(ticks)
}