openlatch-client 0.0.1

The open-source security layer for AI agents — client forwarder
Documentation
//! Terminal color and TTY detection for the CLI.
//!
//! Priority order: `--no-color` flag > `NO_COLOR` env var > TTY detection.
//!
//! Per D-09: green checkmarks, red errors, dim secondary info on interactive
//! terminals. Plain text markers (OK/ERR) when piped/redirected/NO_COLOR.

use std::io::IsTerminal;

/// Determine if color output should be enabled.
///
/// Priority order:
/// 1. `--no-color` flag → always disable
/// 2. `NO_COLOR` env var (any value) → disable (standard convention)
/// 3. TTY detection — enable only if stdout is a terminal
pub fn is_color_enabled(no_color_flag: bool) -> bool {
    if no_color_flag {
        return false;
    }
    if std::env::var("NO_COLOR").is_ok() {
        return false;
    }
    std::io::stdout().is_terminal()
}

/// Wrap text in ANSI green if color is enabled.
pub fn green(text: &str, enabled: bool) -> String {
    if enabled {
        format!("\x1b[32m{text}\x1b[0m")
    } else {
        text.to_string()
    }
}

/// Wrap text in ANSI red if color is enabled.
pub fn red(text: &str, enabled: bool) -> String {
    if enabled {
        format!("\x1b[31m{text}\x1b[0m")
    } else {
        text.to_string()
    }
}

/// Wrap text in dim (gray) ANSI if color is enabled.
pub fn dim(text: &str, enabled: bool) -> String {
    if enabled {
        format!("\x1b[2m{text}\x1b[0m")
    } else {
        text.to_string()
    }
}

/// Wrap text in bold ANSI if color is enabled.
pub fn bold(text: &str, enabled: bool) -> String {
    if enabled {
        format!("\x1b[1m{text}\x1b[0m")
    } else {
        text.to_string()
    }
}

/// Return a checkmark symbol.
///
/// Returns a green Unicode checkmark on color-enabled terminals,
/// or the plain text "OK" when color is disabled.
pub fn checkmark(color_enabled: bool) -> &'static str {
    if color_enabled {
        "\x1b[32m\u{2713}\x1b[0m"
    } else {
        "OK"
    }
}

/// Return a cross/error symbol.
///
/// Returns a red Unicode cross on color-enabled terminals,
/// or the plain text "ERR" when color is disabled.
pub fn cross(color_enabled: bool) -> &'static str {
    if color_enabled {
        "\x1b[31m\u{2717}\x1b[0m"
    } else {
        "ERR"
    }
}

/// Return an indented bullet symbol.
///
/// Returns a dim middle dot on color-enabled terminals,
/// or a plain hyphen when color is disabled.
pub fn bullet(color_enabled: bool) -> &'static str {
    if color_enabled {
        "  \u{00b7}"
    } else {
        "  -"
    }
}

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

    #[test]
    fn test_is_color_enabled_no_color_flag_overrides_tty() {
        // --no-color flag must disable color regardless of terminal state
        assert!(!is_color_enabled(true));
    }

    #[test]
    fn test_checkmark_returns_ok_when_color_disabled() {
        assert_eq!(checkmark(false), "OK");
    }

    #[test]
    fn test_checkmark_returns_ansi_when_color_enabled() {
        let mark = checkmark(true);
        assert!(
            mark.contains('\u{2713}'),
            "should contain checkmark unicode char"
        );
        assert!(mark.contains("\x1b[32m"), "should contain green ANSI code");
    }

    #[test]
    fn test_cross_returns_err_when_color_disabled() {
        assert_eq!(cross(false), "ERR");
    }

    #[test]
    fn test_bullet_returns_hyphen_when_color_disabled() {
        assert_eq!(bullet(false), "  -");
    }

    #[test]
    fn test_green_passthrough_when_disabled() {
        assert_eq!(green("hello", false), "hello");
    }

    #[test]
    fn test_green_wraps_ansi_when_enabled() {
        let result = green("hello", true);
        assert!(result.contains("\x1b[32m"));
        assert!(result.contains("hello"));
        assert!(result.contains("\x1b[0m"));
    }

    #[test]
    fn test_red_passthrough_when_disabled() {
        assert_eq!(red("error", false), "error");
    }

    #[test]
    fn test_dim_passthrough_when_disabled() {
        assert_eq!(dim("info", false), "info");
    }

    #[test]
    fn test_bold_passthrough_when_disabled() {
        assert_eq!(bold("title", false), "title");
    }
}