use std::io::IsTerminal;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::ValueEnum)]
pub enum ColorChoice {
#[default]
Auto,
Always,
Never,
}
#[derive(Debug, Clone, Copy)]
pub struct Ui {
color: bool,
quiet: bool,
}
impl Ui {
pub fn new(choice: ColorChoice, quiet: bool) -> Self {
let color = match choice {
ColorChoice::Always => true,
ColorChoice::Never => false,
ColorChoice::Auto => {
std::env::var_os("NO_COLOR").is_none() && std::io::stdout().is_terminal()
}
};
Self { color, quiet }
}
pub fn say(self, line: impl AsRef<str>) {
if !self.quiet {
println!("{}", line.as_ref());
}
}
fn paint(self, code: &str, text: &str) -> String {
if self.color {
format!("\u{1b}[{code}m{text}\u{1b}[0m")
} else {
text.to_string()
}
}
pub fn bold(self, text: &str) -> String {
self.paint("1", text)
}
pub fn dim(self, text: &str) -> String {
self.paint("2", text)
}
pub fn green(self, text: &str) -> String {
self.paint("32", text)
}
pub fn yellow(self, text: &str) -> String {
self.paint("33", text)
}
pub fn red(self, text: &str) -> String {
self.paint("31", text)
}
pub fn cyan(self, text: &str) -> String {
self.paint("36", text)
}
}
pub fn pad(text: &str, width: usize) -> String {
let mut out = String::with_capacity(width.max(text.len()));
out.push_str(text);
for _ in text.len()..width {
out.push(' ');
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn never_choice_emits_no_escape_sequences() {
let ui = Ui::new(ColorChoice::Never, false);
assert_eq!(ui.green("ok"), "ok");
assert_eq!(ui.bold("ok"), "ok");
}
#[test]
fn always_choice_wraps_in_ansi() {
let ui = Ui::new(ColorChoice::Always, false);
assert_eq!(ui.green("ok"), "\u{1b}[32mok\u{1b}[0m");
}
#[test]
fn pad_never_truncates() {
assert_eq!(pad("abc", 5), "abc ");
assert_eq!(pad("abcdef", 3), "abcdef");
}
}