Skip to main content

runi_cli/
tint.rs

1use nu_ansi_term::{Color as AnsiColor, Style};
2use std::fmt;
3
4#[derive(Clone, Debug)]
5pub struct Tint {
6    style: Style,
7}
8
9impl Tint {
10    fn new(style: Style) -> Self {
11        Self { style }
12    }
13
14    // Foreground colors
15    pub fn red() -> Self {
16        Self::new(AnsiColor::Red.normal())
17    }
18    pub fn green() -> Self {
19        Self::new(AnsiColor::Green.normal())
20    }
21    pub fn yellow() -> Self {
22        Self::new(AnsiColor::Yellow.normal())
23    }
24    pub fn blue() -> Self {
25        Self::new(AnsiColor::Blue.normal())
26    }
27    pub fn purple() -> Self {
28        Self::new(AnsiColor::Purple.normal())
29    }
30    pub fn cyan() -> Self {
31        Self::new(AnsiColor::Cyan.normal())
32    }
33    pub fn white() -> Self {
34        Self::new(AnsiColor::White.normal())
35    }
36    pub fn black() -> Self {
37        Self::new(AnsiColor::Black.normal())
38    }
39
40    // Bright foreground colors
41    pub fn bright_red() -> Self {
42        Self::new(AnsiColor::LightRed.normal())
43    }
44    pub fn bright_green() -> Self {
45        Self::new(AnsiColor::LightGreen.normal())
46    }
47    pub fn bright_yellow() -> Self {
48        Self::new(AnsiColor::LightYellow.normal())
49    }
50    pub fn bright_blue() -> Self {
51        Self::new(AnsiColor::LightBlue.normal())
52    }
53    pub fn bright_purple() -> Self {
54        Self::new(AnsiColor::LightPurple.normal())
55    }
56    pub fn bright_cyan() -> Self {
57        Self::new(AnsiColor::LightCyan.normal())
58    }
59
60    // Style modifiers (chainable)
61    pub fn bold(mut self) -> Self {
62        self.style = self.style.bold();
63        self
64    }
65    pub fn dimmed(mut self) -> Self {
66        self.style = self.style.dimmed();
67        self
68    }
69    pub fn italic(mut self) -> Self {
70        self.style = self.style.italic();
71        self
72    }
73    pub fn underline(mut self) -> Self {
74        self.style = self.style.underline();
75        self
76    }
77    pub fn strikethrough(mut self) -> Self {
78        self.style = self.style.strikethrough();
79        self
80    }
81
82    // Background colors (chainable)
83    pub fn bg_red(mut self) -> Self {
84        self.style = self.style.on(AnsiColor::Red);
85        self
86    }
87    pub fn bg_green(mut self) -> Self {
88        self.style = self.style.on(AnsiColor::Green);
89        self
90    }
91    pub fn bg_yellow(mut self) -> Self {
92        self.style = self.style.on(AnsiColor::Yellow);
93        self
94    }
95    pub fn bg_blue(mut self) -> Self {
96        self.style = self.style.on(AnsiColor::Blue);
97        self
98    }
99    pub fn bg_purple(mut self) -> Self {
100        self.style = self.style.on(AnsiColor::Purple);
101        self
102    }
103    pub fn bg_cyan(mut self) -> Self {
104        self.style = self.style.on(AnsiColor::Cyan);
105        self
106    }
107    pub fn bg_white(mut self) -> Self {
108        self.style = self.style.on(AnsiColor::White);
109        self
110    }
111    pub fn bg_black(mut self) -> Self {
112        self.style = self.style.on(AnsiColor::Black);
113        self
114    }
115
116    // ANSI 256 color
117    pub fn color(n: u8) -> Self {
118        Self::new(AnsiColor::Fixed(n).normal())
119    }
120    pub fn bg_color(mut self, n: u8) -> Self {
121        self.style = self.style.on(AnsiColor::Fixed(n));
122        self
123    }
124
125    // RGB color
126    pub fn rgb(r: u8, g: u8, b: u8) -> Self {
127        Self::new(AnsiColor::Rgb(r, g, b).normal())
128    }
129    pub fn bg_rgb(mut self, r: u8, g: u8, b: u8) -> Self {
130        self.style = self.style.on(AnsiColor::Rgb(r, g, b));
131        self
132    }
133
134    /// Apply this style to a string and return the styled output.
135    pub fn paint(&self, text: &str) -> String {
136        self.style.paint(text).to_string()
137    }
138}
139
140impl fmt::Display for Tint {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        write!(f, "{:?}", self.style)
143    }
144}
145
146/// Check if stderr supports color output.
147pub fn supports_color() -> bool {
148    std::io::IsTerminal::is_terminal(&std::io::stderr())
149}
150
151/// Check if stdout supports color output. Use this when writing to stdout so
152/// color isn't emitted when the output is being redirected.
153pub fn supports_color_stdout() -> bool {
154    std::io::IsTerminal::is_terminal(&std::io::stdout())
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn basic_colors() {
163        let s = Tint::red().paint("error");
164        assert!(s.contains("error"));
165
166        let s = Tint::green().paint("success");
167        assert!(s.contains("success"));
168    }
169
170    #[test]
171    fn chained_styles() {
172        let s = Tint::red().bold().underline().paint("important");
173        assert!(s.contains("important"));
174    }
175
176    #[test]
177    fn background_colors() {
178        let s = Tint::white().bg_red().paint("alert");
179        assert!(s.contains("alert"));
180    }
181
182    #[test]
183    fn ansi256_color() {
184        let s = Tint::color(208).paint("orange");
185        assert!(s.contains("orange"));
186    }
187
188    #[test]
189    fn rgb_color() {
190        let s = Tint::rgb(255, 128, 0).paint("custom");
191        assert!(s.contains("custom"));
192    }
193
194    #[test]
195    fn style_reuse() {
196        let header = Tint::cyan().bold();
197        let a = header.paint("Title A");
198        let b = header.paint("Title B");
199        assert!(a.contains("Title A"));
200        assert!(b.contains("Title B"));
201    }
202}