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