use anstyle::{Ansi256Color, AnsiColor, Color, Style};
use crate::vocabulary::Role;
#[must_use]
pub(crate) fn style_for(role: Role, deep: bool) -> Style {
let colour = match (role, deep) {
(Role::Meadow, true) => Color::Ansi256(Ansi256Color(29)),
(Role::Bark, true) => Color::Ansi256(Ansi256Color(166)),
(Role::Butter, true) => Color::Ansi256(Ansi256Color(221)),
(Role::Ink3, true) => Color::Ansi256(Ansi256Color(245)),
(Role::Meadow, false) => Color::Ansi(AnsiColor::Green),
(Role::Bark, false) => Color::Ansi(AnsiColor::Red),
(Role::Butter, false) => Color::Ansi(AnsiColor::Yellow),
(Role::Ink3, false) => Color::Ansi(AnsiColor::BrightBlack),
(Role::Sky, true) => Color::Ansi256(Ansi256Color(74)),
(Role::Sky, false) => Color::Ansi(AnsiColor::Blue),
};
Style::new().fg_color(Some(colour))
}
#[must_use]
pub(crate) fn help_styles(deep: bool) -> clap::builder::Styles {
let heading = style_for(Role::Butter, deep).bold();
clap::builder::Styles::plain()
.header(heading)
.usage(heading)
.literal(style_for(Role::Meadow, deep))
.placeholder(style_for(Role::Sky, deep))
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory as _;
use crate::cli::Cli;
#[test]
fn coloured_help_paints_headings_flags_and_placeholders_in_the_palette() {
let coloured = Cli::command().styles(help_styles(true)).render_help();
let ansi = coloured.ansi().to_string();
assert!(
ansi.contains("\x1b[1m\x1b[38;5;221mUsage:\x1b[0m"),
"bold butter heading, got {ansi:?}"
);
assert!(
ansi.contains("\x1b[38;5;29m--format\x1b[0m"),
"meadow flag, got {ansi:?}"
);
assert!(
ansi.contains("\x1b[38;5;74m<FORMAT>\x1b[0m"),
"sky placeholder, got {ansi:?}"
);
}
#[test]
fn coloured_help_has_the_same_text_as_the_help_it_replaces() {
let today = Cli::command().render_help().to_string();
let coloured = Cli::command().styles(help_styles(true)).render_help();
assert!(coloured.ansi().to_string().contains('\x1b'));
assert_eq!(coloured.to_string(), today);
}
#[test]
fn the_sixteen_colour_tier_uses_named_colours() {
let ansi = Cli::command()
.styles(help_styles(false))
.render_help()
.ansi()
.to_string();
assert!(ansi.contains("\x1b[32m--format\x1b[0m"), "got {ansi:?}");
}
#[test]
fn every_role_resolves_a_foreground_at_both_tiers() {
for role in [Role::Meadow, Role::Bark, Role::Butter, Role::Ink3] {
for deep in [true, false] {
assert!(
style_for(role, deep).get_fg_color().is_some(),
"{role:?} at deep={deep} must set a foreground"
);
}
}
}
#[test]
fn bark_is_the_only_role_painted_bark() {
for deep in [true, false] {
let bark = style_for(Role::Bark, deep);
for other in [Role::Meadow, Role::Butter, Role::Ink3] {
assert_ne!(
style_for(other, deep),
bark,
"{other:?} at deep={deep} must not share bark's colour"
);
}
}
}
#[test]
fn the_four_roles_are_pairwise_distinct_at_each_tier() {
for deep in [true, false] {
let styles = [
style_for(Role::Meadow, deep),
style_for(Role::Bark, deep),
style_for(Role::Butter, deep),
style_for(Role::Ink3, deep),
];
for (i, a) in styles.iter().enumerate() {
for (j, b) in styles.iter().enumerate() {
if i != j {
assert_ne!(
a, b,
"roles at index {i} and {j} share a colour at deep={deep}"
);
}
}
}
}
}
}