use crate::Severity;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorMode {
Ansi,
Plain,
}
#[must_use]
pub fn color_of(no_color_flag: bool, no_color_env: bool, is_terminal: bool) -> ColorMode {
if no_color_flag || no_color_env {
return ColorMode::Plain;
}
match is_terminal {
true => ColorMode::Ansi,
false => ColorMode::Plain,
}
}
#[derive(Debug, Clone, Copy)]
pub enum Role {
Error,
Warn,
Info,
Path,
Verb,
Obligation,
Heading,
}
#[must_use]
pub fn severity_role(severity: Severity) -> Role {
match severity {
Severity::Error => Role::Error,
Severity::Warn => Role::Warn,
Severity::Info => Role::Info,
}
}
#[must_use]
pub fn paint(role: Role, text: &str, mode: ColorMode) -> String {
let (open, close) = match (role, mode) {
(Role::Error, ColorMode::Ansi) => ("\x1b[1;31m", "\x1b[0m"),
(Role::Warn, ColorMode::Ansi) => ("\x1b[1;33m", "\x1b[0m"),
(Role::Info, ColorMode::Ansi) => ("\x1b[34m", "\x1b[0m"),
(Role::Path, ColorMode::Ansi) => ("\x1b[36m", "\x1b[0m"),
(Role::Verb, ColorMode::Ansi) => ("\x1b[1;32m", "\x1b[0m"),
(Role::Obligation, ColorMode::Ansi) => ("\x1b[35m", "\x1b[0m"),
(Role::Heading, ColorMode::Ansi) => ("\x1b[1m", "\x1b[0m"),
(_, ColorMode::Plain) => ("", ""),
};
format!("{open}{text}{close}")
}
#[must_use]
pub fn dim(text: &str, mode: ColorMode) -> String {
match mode {
ColorMode::Ansi => format!("\x1b[2m{text}\x1b[0m"),
ColorMode::Plain => text.to_string(),
}
}
#[must_use]
pub fn glyph(severity: Severity) -> &'static str {
match severity {
Severity::Error => "✗",
Severity::Warn => "▲",
Severity::Info => "·",
}
}
#[must_use]
pub fn severity_word(severity: Severity, mode: ColorMode) -> String {
let word = severity.to_string();
match mode {
ColorMode::Ansi => paint(severity_role(severity), &word, mode),
ColorMode::Plain => format!("{} {word}", glyph(severity)),
}
}
#[cfg(test)]
mod tests {
use super::{color_of, dim, glyph, paint, severity_word, ColorMode, Role};
use crate::Severity;
#[test]
fn color_is_plain_off_a_terminal_and_ansi_on_one_unless_overridden() {
assert_eq!(color_of(false, false, false), ColorMode::Plain);
assert_eq!(color_of(false, false, true), ColorMode::Ansi);
assert_eq!(
color_of(true, false, true),
ColorMode::Plain,
"--no-color wins"
);
assert_eq!(
color_of(false, true, true),
ColorMode::Plain,
"NO_COLOR wins"
);
assert_eq!(color_of(true, true, false), ColorMode::Plain);
}
#[test]
fn plain_writes_no_escape_sequence_for_any_role() {
for role in [
Role::Error,
Role::Warn,
Role::Info,
Role::Path,
Role::Verb,
Role::Obligation,
Role::Heading,
] {
assert_eq!(paint(role, "text", ColorMode::Plain), "text");
}
assert_eq!(dim("text", ColorMode::Plain), "text");
}
#[test]
fn ansi_wraps_every_role_in_an_opening_and_a_reset() {
for role in [
Role::Error,
Role::Warn,
Role::Info,
Role::Path,
Role::Verb,
Role::Obligation,
Role::Heading,
] {
let written = paint(role, "text", ColorMode::Ansi);
assert!(written.starts_with("\x1b["), "{written:?}");
assert!(written.ends_with("\x1b[0m"), "{written:?}");
assert!(written.contains("text"), "{written:?}");
}
let dimmed = dim("text", ColorMode::Ansi);
assert_eq!(dimmed, "\x1b[2mtext\x1b[0m");
}
#[test]
fn every_severity_has_its_own_glyph() {
let glyphs = [
glyph(Severity::Error),
glyph(Severity::Warn),
glyph(Severity::Info),
];
assert_eq!(glyphs, ["✗", "▲", "·"]);
}
#[test]
fn a_severity_word_carries_a_glyph_in_plain_and_color_in_ansi() {
let plain = severity_word(Severity::Error, ColorMode::Plain);
assert_eq!(plain, "✗ error");
assert!(!plain.contains('\x1b'));
let ansi = severity_word(Severity::Error, ColorMode::Ansi);
assert!(ansi.contains("error"), "{ansi:?}");
assert!(ansi.starts_with("\x1b["), "{ansi:?}");
assert!(!ansi.contains('✗'), "{ansi:?}");
}
}