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