1use std::fmt;
6use std::ops::Add;
7
8use termion::color::{AnsiValue, Bg, Fg};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct Style {
13 pub foreground: Option<u8>,
15 pub background: Option<u8>,
17 pub bold: Option<bool>,
19 pub italic: Option<bool>,
21 pub invert: Option<bool>,
23}
24
25impl Style {
26 pub const EMPTY: Style = Style {
28 foreground: None,
29 background: None,
30 bold: None,
31 italic: None,
32 invert: None,
33 };
34
35 pub const BOLD: Style = Style {
37 bold: Some(true),
38 ..Style::EMPTY
39 };
40
41 pub const ITALIC: Style = Style {
43 italic: Some(true),
44 ..Style::EMPTY
45 };
46
47 pub const INVERT: Style = Style {
49 invert: Some(true),
50 ..Style::EMPTY
51 };
52
53 pub fn fg(value: u8) -> Style {
55 Style {
56 foreground: Some(value),
57 ..Style::EMPTY
58 }
59 }
60
61 pub fn bg(value: u8) -> Style {
63 Style {
64 background: Some(value),
65 ..Style::EMPTY
66 }
67 }
68
69 pub fn with(self, other: Style) -> Style {
71 other.or(self)
72 }
73
74 pub fn or(self, other: Style) -> Style {
76 Style {
77 foreground: self.foreground.or(other.foreground),
78 background: self.background.or(other.background),
79 bold: self.bold.or(other.bold),
80 italic: self.italic.or(other.italic),
81 invert: self.invert.or(other.invert),
82 }
83 }
84}
85
86impl Default for Style {
87 fn default() -> Self {
88 Self::EMPTY
89 }
90}
91
92impl Add for Style {
93 type Output = Style;
94
95 fn add(self, rhs: Self) -> Self::Output {
96 self.with(rhs)
97 }
98}
99
100impl fmt::Display for Style {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 if let Some(foreground) = self.foreground {
103 Fg(AnsiValue(foreground)).fmt(f)?;
104 }
105 if let Some(background) = self.background {
106 Bg(AnsiValue(background)).fmt(f)?;
107 }
108 if self.bold == Some(true) {
109 termion::style::Bold.fmt(f)?;
110 }
111 if self.italic == Some(true) {
112 termion::style::Italic.fmt(f)?;
113 }
114 if self.invert == Some(true) {
115 termion::style::Invert.fmt(f)?;
116 }
117 Ok(())
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use std::io::Write;
124
125 use super::*;
126
127 const STYLE_1: Style = Style {
128 foreground: Some(1),
129 bold: Some(true),
130 ..Style::EMPTY
131 };
132
133 const STYLE_2: Style = Style {
134 foreground: Some(2),
135 italic: Some(true),
136 ..Style::EMPTY
137 };
138
139 #[test]
140 fn with() {
141 let style = STYLE_1.with(STYLE_2);
142 assert_eq!(
143 style,
144 Style {
145 foreground: Some(2),
146 background: None,
147 bold: Some(true),
148 italic: Some(true),
149 invert: None
150 },
151 );
152 }
153
154 #[test]
155 fn or() {
156 let style = STYLE_1.or(STYLE_2);
157 assert_eq!(
158 style,
159 Style {
160 foreground: Some(1),
161 background: None,
162 bold: Some(true),
163 italic: Some(true),
164 invert: None
165 },
166 );
167 }
168
169 #[test]
170 fn plus() {
171 assert_eq!(STYLE_1.with(STYLE_2), STYLE_1 + STYLE_2);
172 }
173
174 #[test]
175 fn print_empty() {
176 let mut output = vec![];
177 write!(&mut output, "{}", Style::EMPTY).unwrap();
178 assert_eq!(output, b"");
179 }
180
181 #[test]
182 fn print_full() {
183 let mut output = vec![];
184 write!(
185 &mut output,
186 "{}",
187 Style {
188 foreground: Some(1),
189 background: Some(2),
190 bold: Some(true),
191 italic: Some(true),
192 invert: Some(true),
193 },
194 )
195 .unwrap();
196 assert_eq!(output, b"\x1b[38;5;1m\x1b[48;5;2m\x1b[1m\x1b[3m\x1b[7m");
197 }
198}