1pub mod ansi {
5 pub const RESET: &str = "\x1b[0m";
7 pub const RED: &str = "\x1b[31m";
9 pub const GREEN: &str = "\x1b[32m";
11 pub const YELLOW: &str = "\x1b[33m";
13 pub const CYAN: &str = "\x1b[36m";
15 pub const MAGENTA: &str = "\x1b[35m";
17 pub const DIM: &str = "\x1b[2m";
19}
20
21pub fn paint(text: &str, code: &str, enabled: bool) -> String {
25 if enabled && !text.trim().is_empty() {
26 format!("{code}{text}{}", ansi::RESET)
27 } else {
28 text.to_string()
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
34pub enum ColorChoice {
35 Auto,
37 Always,
39 Never,
41}
42
43impl ColorChoice {
44 pub fn parse(value: &str) -> Option<ColorChoice> {
46 match value {
47 "auto" => Some(ColorChoice::Auto),
48 "always" => Some(ColorChoice::Always),
49 "never" => Some(ColorChoice::Never),
50 _ => None,
51 }
52 }
53}
54
55pub fn resolve_color(
65 flag: Option<ColorChoice>,
66 no_color: bool,
67 config: Option<ColorChoice>,
68 stdout_is_tty: bool,
69) -> bool {
70 match flag {
71 Some(ColorChoice::Always) => return true,
72 Some(ColorChoice::Never) => return false,
73 Some(ColorChoice::Auto) | None => {}
74 }
75 if no_color {
76 return false;
77 }
78 match config {
79 Some(ColorChoice::Always) => return true,
80 Some(ColorChoice::Never) => return false,
81 Some(ColorChoice::Auto) | None => {}
82 }
83 stdout_is_tty
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn parse_known_and_unknown() {
92 assert_eq!(ColorChoice::parse("auto"), Some(ColorChoice::Auto));
93 assert_eq!(ColorChoice::parse("always"), Some(ColorChoice::Always));
94 assert_eq!(ColorChoice::parse("never"), Some(ColorChoice::Never));
95 assert_eq!(ColorChoice::parse("bogus"), None);
96 }
97
98 #[test]
99 fn flag_always_wins_over_no_color() {
100 assert!(resolve_color(
101 Some(ColorChoice::Always),
102 true,
103 Some(ColorChoice::Never),
104 false
105 ));
106 }
107
108 #[test]
109 fn flag_never_wins() {
110 assert!(!resolve_color(
111 Some(ColorChoice::Never),
112 false,
113 Some(ColorChoice::Always),
114 true
115 ));
116 }
117
118 #[test]
119 fn no_color_env_beats_config_and_auto() {
120 assert!(!resolve_color(None, true, Some(ColorChoice::Always), true));
121 assert!(!resolve_color(Some(ColorChoice::Auto), true, None, true));
122 }
123
124 #[test]
125 fn config_used_when_no_flag_or_no_color() {
126 assert!(resolve_color(None, false, Some(ColorChoice::Always), false));
127 assert!(!resolve_color(None, false, Some(ColorChoice::Never), true));
128 }
129
130 #[test]
131 fn auto_falls_back_to_tty() {
132 assert!(resolve_color(None, false, None, true));
133 assert!(!resolve_color(None, false, None, false));
134 assert!(resolve_color(
135 Some(ColorChoice::Auto),
136 false,
137 Some(ColorChoice::Auto),
138 true
139 ));
140 }
141}