bashers 0.8.8

Installable cli helpers
Documentation
use std::io::{self, Write};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

pub const ANSI_CYAN_BOLD: &str = "\x1b[36m\x1b[1m";
pub const ANSI_GREEN: &str = "\x1b[32m";
pub const ANSI_RED: &str = "\x1b[31m";
pub const ANSI_YELLOW: &str = "\x1b[33m";
pub const ANSI_DIM: &str = "\x1b[2m";
pub const ANSI_RESET: &str = "\x1b[0m";

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VersionChange {
    Upgraded,
    Unchanged,
    Downgraded,
}

pub fn format_bumped_message_colored(before: &str, after: &str, change: VersionChange) -> String {
    if atty::is(atty::Stream::Stderr) {
        let after_color = match change {
            VersionChange::Upgraded => ANSI_GREEN,
            VersionChange::Unchanged => ANSI_DIM,
            VersionChange::Downgraded => ANSI_RED,
        };
        format!(
            "bumped from {}{}{} -> {}{}{}",
            ANSI_YELLOW, before, ANSI_RESET, after_color, after, ANSI_RESET
        )
    } else {
        format!("bumped from {} -> {}", before, after)
    }
}

pub struct Colors {
    stdout: StandardStream,
}

impl Default for Colors {
    fn default() -> Self {
        Self::new()
    }
}

impl Colors {
    pub fn new() -> Self {
        let choice = if atty::is(atty::Stream::Stdout) {
            ColorChoice::Auto
        } else {
            ColorChoice::Never
        };
        Self {
            stdout: StandardStream::stdout(choice),
        }
    }

    pub fn green(&mut self) -> io::Result<()> {
        self.stdout
            .set_color(ColorSpec::new().set_fg(Some(Color::Green)))
    }

    pub fn cyan(&mut self) -> io::Result<()> {
        self.stdout
            .set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))
    }

    pub fn bold(&mut self) -> io::Result<()> {
        self.stdout.set_color(ColorSpec::new().set_bold(true))
    }

    pub fn reset(&mut self) -> io::Result<()> {
        self.stdout.reset()
    }

    pub fn red(&mut self) -> io::Result<()> {
        self.stdout
            .set_color(ColorSpec::new().set_fg(Some(Color::Red)))
    }

    pub fn yellow(&mut self) -> io::Result<()> {
        self.stdout
            .set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))
    }

    pub fn print(&mut self, text: &str) -> io::Result<()> {
        write!(&mut self.stdout, "{}", text)
    }

    pub fn println(&mut self, text: &str) -> io::Result<()> {
        writeln!(&mut self.stdout, "{}", text)
    }

    pub fn flush(&mut self) -> io::Result<()> {
        self.stdout.flush()
    }
}

pub fn print_update(package: &str) {
    let mut colors = Colors::new();
    let _ = colors.green();
    let _ = colors.print("[update]");
    let _ = colors.reset();
    let _ = colors.print(": updating ");
    let _ = colors.green();
    let _ = colors.print(package);
    let _ = colors.reset();
    let _ = colors.println("");
}

pub fn print_updated_version(package: &str, version: &str) {
    let v = if version.starts_with('v') {
        version.to_string()
    } else {
        format!("v{}", version)
    };
    let mut colors = Colors::new();
    let _ = colors.green();
    let _ = colors.print("[update]");
    let _ = colors.reset();
    let _ = colors.print(": ");
    let _ = colors.green();
    let _ = colors.print(package);
    let _ = colors.reset();
    let _ = colors.println(&format!(" is now {}", v));
}

pub fn print_bumped_version(package: &str, before: &str, after: &str) {
    let mut colors = Colors::new();
    let _ = colors.green();
    let _ = colors.print("[update]");
    let _ = colors.reset();
    let _ = colors.print(": ");
    let _ = colors.green();
    let _ = colors.print(package);
    let _ = colors.reset();
    let _ = colors.print(" bumped from ");
    let _ = colors.yellow();
    let _ = colors.print(before);
    let _ = colors.reset();
    let _ = colors.print(" -> ");
    let _ = colors.green();
    let _ = colors.print(after);
    let _ = colors.reset();
    let _ = colors.println("");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_colors_new() {
        let _colors = Colors::new();
    }

    #[test]
    fn test_colors_methods() {
        let mut colors = Colors::new();

        // Test all color methods don't panic
        assert!(colors.green().is_ok());
        assert!(colors.cyan().is_ok());
        assert!(colors.bold().is_ok());
        assert!(colors.red().is_ok());
        assert!(colors.yellow().is_ok());
        assert!(colors.reset().is_ok());
    }

    #[test]
    fn test_colors_print() {
        let mut colors = Colors::new();
        assert!(colors.print("test").is_ok());
        assert!(colors.println("test").is_ok());
    }

    #[test]
    fn test_print_update() {
        print_update("test-package");
    }

    #[test]
    fn test_colors_sequence() {
        let mut colors = Colors::new();
        assert!(colors.green().is_ok());
        assert!(colors.print("green text").is_ok());
        assert!(colors.reset().is_ok());
        assert!(colors.cyan().is_ok());
        assert!(colors.bold().is_ok());
        assert!(colors.println("bold cyan").is_ok());
        assert!(colors.reset().is_ok());
    }

    #[test]
    fn test_format_bumped_message_colored_contains_versions() {
        for change in [
            VersionChange::Upgraded,
            VersionChange::Unchanged,
            VersionChange::Downgraded,
        ] {
            let s = format_bumped_message_colored("v1.0.0", "v1.0.102", change);
            assert!(s.contains("bumped from"));
            assert!(s.contains("1.0.0"));
            assert!(s.contains("1.0.102"));
            assert!(s.contains(" -> "));
        }
    }

    #[test]
    fn test_version_change_equality() {
        assert_eq!(VersionChange::Upgraded, VersionChange::Upgraded);
        assert_ne!(VersionChange::Upgraded, VersionChange::Downgraded);
    }
}