| 1 | use std::fmt::{self, Display, Write}; |
| 2 | |
| 3 | use crate::{Colour, Style}; |
| 4 | |
| 5 | impl Style { |
| 6 | /// Write any bytes that go *before* a piece of text to the given writer. |
| 7 | pub fn write_prefix(&self, f: &mut fmt::Formatter) -> Result<bool, fmt::Error> { |
| 8 | let mut written_anything = false; |
| 9 | macro_rules! write_anything { |
| 10 | () => { |
| 11 | if written_anything { |
| 12 | f.write_char(';' )?; |
| 13 | } else { |
| 14 | // Write the codes’ prefix, then write numbers, separated by |
| 15 | // semicolons, for each text style we want to apply. |
| 16 | f.write_str(" \x1B[" )?; |
| 17 | written_anything = true; |
| 18 | } |
| 19 | }; |
| 20 | } |
| 21 | macro_rules! write_char { |
| 22 | ($cond:ident, $c:expr) => { |
| 23 | if self.$cond { |
| 24 | write_anything!(); |
| 25 | f.write_char($c)?; |
| 26 | } |
| 27 | }; |
| 28 | } |
| 29 | macro_rules! write_chars { |
| 30 | ($cond:ident => $c:expr) => { write_char!($cond, $c); }; |
| 31 | ($cond:ident => $c:expr, $($t:tt)+) => { |
| 32 | write_char!($cond, $c); |
| 33 | write_chars!($($t)+); |
| 34 | }; |
| 35 | } |
| 36 | |
| 37 | write_chars!( |
| 38 | is_bold => '1' , |
| 39 | is_dimmed => '2' , |
| 40 | is_italic => '3' , |
| 41 | is_underline => '4' , |
| 42 | is_blink => '5' , |
| 43 | is_reverse => '7' , |
| 44 | is_hidden => '8' , |
| 45 | is_strikethrough => '9' |
| 46 | ); |
| 47 | |
| 48 | // The foreground and background colours, if specified, need to be |
| 49 | // handled specially because the number codes are more complicated. |
| 50 | // (see `write_background_code` and `write_foreground_code`) |
| 51 | if let Some(bg) = self.background { |
| 52 | write_anything!(); |
| 53 | bg.write_background_code(f)?; |
| 54 | } |
| 55 | |
| 56 | if let Some(fg) = self.foreground { |
| 57 | write_anything!(); |
| 58 | fg.write_foreground_code(f)?; |
| 59 | } |
| 60 | |
| 61 | if written_anything { |
| 62 | // All the codes end with an `m`, because reasons. |
| 63 | f.write_char('m' )?; |
| 64 | } |
| 65 | |
| 66 | Ok(written_anything) |
| 67 | } |
| 68 | |
| 69 | /// Write any bytes that go *after* a piece of text to the given writer. |
| 70 | #[inline ] |
| 71 | pub fn write_reset(f: &mut fmt::Formatter) -> fmt::Result { |
| 72 | f.write_str(RESET) |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | impl Colour { |
| 77 | /// Write any bytes that go *before* a piece of text to the given writer. |
| 78 | #[inline ] |
| 79 | pub fn write_prefix(self, f: &mut fmt::Formatter) -> Result<bool, fmt::Error> { |
| 80 | self.normal().write_prefix(f) |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | /// The code to send to reset all styles and return to `Style::default()`. |
| 85 | pub static RESET: &str = " \x1B[0m" ; |
| 86 | |
| 87 | macro_rules! write_color { |
| 88 | ($_self:ident, $f:ident => |
| 89 | $black:expr, $red:expr, $green:expr, $yellow:expr, $blue:expr, |
| 90 | $purple:expr, $cyan:expr, $white:expr, $fixed:expr, $rgb:expr) => {{ |
| 91 | use Colour::*; |
| 92 | match $_self { |
| 93 | Black => $f.write_str($black), |
| 94 | Red => $f.write_str($red), |
| 95 | Green => $f.write_str($green), |
| 96 | Yellow => $f.write_str($yellow), |
| 97 | Blue => $f.write_str($blue), |
| 98 | Purple => $f.write_str($purple), |
| 99 | Cyan => $f.write_str($cyan), |
| 100 | White => $f.write_str($white), |
| 101 | Fixed(num) => { |
| 102 | $f.write_str($fixed)?; |
| 103 | num.fmt($f) |
| 104 | } |
| 105 | RGB(r, g, b) => { |
| 106 | $f.write_str($rgb)?; |
| 107 | r.fmt($f)?; |
| 108 | $f.write_char(';' )?; |
| 109 | g.fmt($f)?; |
| 110 | $f.write_char(';' )?; |
| 111 | b.fmt($f) |
| 112 | } |
| 113 | } |
| 114 | }}; |
| 115 | } |
| 116 | |
| 117 | impl Colour { |
| 118 | #[inline ] |
| 119 | fn write_foreground_code(self, f: &mut fmt::Formatter) -> fmt::Result { |
| 120 | write_color!(self, f => "30" , "31" , "32" , "33" , "34" , "35" , "36" , "37" , "38;5;" , "38;2;" ) |
| 121 | } |
| 122 | |
| 123 | #[inline ] |
| 124 | fn write_background_code(self, f: &mut fmt::Formatter) -> fmt::Result { |
| 125 | write_color!(self, f => "40" , "41" , "42" , "43" , "44" , "45" , "46" , "47" , "48;5;" , "48;2;" ) |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | #[cfg (test)] |
| 130 | mod test { |
| 131 | use crate::{Colour::*, Style}; |
| 132 | |
| 133 | macro_rules! test { |
| 134 | ($name: ident: $style: expr; $input: expr => $result: expr) => { |
| 135 | #[test] |
| 136 | fn $name() { |
| 137 | assert_eq!($style.paint($input).to_string(), $result.to_string()); |
| 138 | } |
| 139 | }; |
| 140 | } |
| 141 | |
| 142 | test !(plain: Style::default(); "text/plain" => "text/plain" ); |
| 143 | test !(red: Red; "hi" => " \x1B[31mhi \x1B[0m" ); |
| 144 | test !(black: Black.normal(); "hi" => " \x1B[30mhi \x1B[0m" ); |
| 145 | test !(yellow_bold: Yellow.bold(); "hi" => " \x1B[1;33mhi \x1B[0m" ); |
| 146 | test !(yellow_bold_2: Yellow.normal().bold(); "hi" => " \x1B[1;33mhi \x1B[0m" ); |
| 147 | test !(blue_underline: Blue.underline(); "hi" => " \x1B[4;34mhi \x1B[0m" ); |
| 148 | test !(green_bold_ul: Green.bold().underline(); "hi" => " \x1B[1;4;32mhi \x1B[0m" ); |
| 149 | test !(green_bold_ul_2: Green.underline().bold(); "hi" => " \x1B[1;4;32mhi \x1B[0m" ); |
| 150 | test !(purple_on_white: Purple.on(White); "hi" => " \x1B[47;35mhi \x1B[0m" ); |
| 151 | test !(purple_on_white_2: Purple.normal().on(White); "hi" => " \x1B[47;35mhi \x1B[0m" ); |
| 152 | test !(yellow_on_blue: Style::new().on(Blue).fg(Yellow); "hi" => " \x1B[44;33mhi \x1B[0m" ); |
| 153 | test !(yellow_on_blue_2: Cyan.on(Blue).fg(Yellow); "hi" => " \x1B[44;33mhi \x1B[0m" ); |
| 154 | test !(cyan_bold_on_white: Cyan.bold().on(White); "hi" => " \x1B[1;47;36mhi \x1B[0m" ); |
| 155 | test !(cyan_ul_on_white: Cyan.underline().on(White); "hi" => " \x1B[4;47;36mhi \x1B[0m" ); |
| 156 | test !(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => " \x1B[1;4;47;36mhi \x1B[0m" ); |
| 157 | test !(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => " \x1B[1;4;47;36mhi \x1B[0m" ); |
| 158 | test !(fixed: Fixed(100); "hi" => " \x1B[38;5;100mhi \x1B[0m" ); |
| 159 | test !(fixed_on_purple: Fixed(100).on(Purple); "hi" => " \x1B[45;38;5;100mhi \x1B[0m" ); |
| 160 | test !(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => " \x1B[48;5;200;38;5;100mhi \x1B[0m" ); |
| 161 | test !(rgb: RGB(70,130,180); "hi" => " \x1B[38;2;70;130;180mhi \x1B[0m" ); |
| 162 | test !(rgb_on_blue: RGB(70,130,180).on(Blue); "hi" => " \x1B[44;38;2;70;130;180mhi \x1B[0m" ); |
| 163 | test !(blue_on_rgb: Blue.on(RGB(70,130,180)); "hi" => " \x1B[48;2;70;130;180;34mhi \x1B[0m" ); |
| 164 | test !(rgb_on_rgb: RGB(70,130,180).on(RGB(5,10,15)); "hi" => " \x1B[48;2;5;10;15;38;2;70;130;180mhi \x1B[0m" ); |
| 165 | test !(bold: Style::new().bold(); "hi" => " \x1B[1mhi \x1B[0m" ); |
| 166 | test !(underline: Style::new().underline(); "hi" => " \x1B[4mhi \x1B[0m" ); |
| 167 | test !(bunderline: Style::new().bold().underline(); "hi" => " \x1B[1;4mhi \x1B[0m" ); |
| 168 | test !(dimmed: Style::new().dimmed(); "hi" => " \x1B[2mhi \x1B[0m" ); |
| 169 | test !(italic: Style::new().italic(); "hi" => " \x1B[3mhi \x1B[0m" ); |
| 170 | test !(blink: Style::new().blink(); "hi" => " \x1B[5mhi \x1B[0m" ); |
| 171 | test !(reverse: Style::new().reverse(); "hi" => " \x1B[7mhi \x1B[0m" ); |
| 172 | test !(hidden: Style::new().hidden(); "hi" => " \x1B[8mhi \x1B[0m" ); |
| 173 | test !(stricken: Style::new().strikethrough(); "hi" => " \x1B[9mhi \x1B[0m" ); |
| 174 | |
| 175 | macro_rules! test_fn { |
| 176 | ($name:ident: $style:expr; $result:expr) => { |
| 177 | #[test] |
| 178 | fn $name() { |
| 179 | let string = String::from("hi" ); |
| 180 | let string: &str = &string; |
| 181 | assert_eq!( |
| 182 | $style.paint_fn(|f| f.write_str(string)).to_string(), |
| 183 | $result.to_string() |
| 184 | ); |
| 185 | } |
| 186 | }; |
| 187 | } |
| 188 | |
| 189 | test_fn!(plain_fn: Style::default(); "hi" ); |
| 190 | test_fn!(red_fn: Red; " \x1B[31mhi \x1B[0m" ); |
| 191 | test_fn!(black_fn: Black.normal(); " \x1B[30mhi \x1B[0m" ); |
| 192 | test_fn!(yellow_bold_fn: Yellow.bold(); " \x1B[1;33mhi \x1B[0m" ); |
| 193 | test_fn!(yellow_bold_2_fn: Yellow.normal().bold(); " \x1B[1;33mhi \x1B[0m" ); |
| 194 | test_fn!(blue_underline_fn: Blue.underline(); " \x1B[4;34mhi \x1B[0m" ); |
| 195 | test_fn!(green_bold_ul_fn: Green.bold().underline(); " \x1B[1;4;32mhi \x1B[0m" ); |
| 196 | test_fn!(green_bold_ul_2_fn: Green.underline().bold(); " \x1B[1;4;32mhi \x1B[0m" ); |
| 197 | test_fn!(purple_on_white_fn: Purple.on(White); " \x1B[47;35mhi \x1B[0m" ); |
| 198 | test_fn!(purple_on_white_2_fn: Purple.normal().on(White); " \x1B[47;35mhi \x1B[0m" ); |
| 199 | test_fn!(yellow_on_blue_fn: Style::new().on(Blue).fg(Yellow); " \x1B[44;33mhi \x1B[0m" ); |
| 200 | test_fn!(yellow_on_blue_2_fn: Cyan.on(Blue).fg(Yellow); " \x1B[44;33mhi \x1B[0m" ); |
| 201 | test_fn!(cyan_bold_on_white_fn: Cyan.bold().on(White); " \x1B[1;47;36mhi \x1B[0m" ); |
| 202 | test_fn!(cyan_ul_on_white_fn: Cyan.underline().on(White); " \x1B[4;47;36mhi \x1B[0m" ); |
| 203 | test_fn!(cyan_bold_ul_on_white_fn: Cyan.bold().underline().on(White); " \x1B[1;4;47;36mhi \x1B[0m" ); |
| 204 | test_fn!(cyan_ul_bold_on_white_fn: Cyan.underline().bold().on(White); " \x1B[1;4;47;36mhi \x1B[0m" ); |
| 205 | test_fn!(fixed_fn: Fixed(100); " \x1B[38;5;100mhi \x1B[0m" ); |
| 206 | test_fn!(fixed_on_purple_fn: Fixed(100).on(Purple); " \x1B[45;38;5;100mhi \x1B[0m" ); |
| 207 | test_fn!(fixed_on_fixed_fn: Fixed(100).on(Fixed(200)); " \x1B[48;5;200;38;5;100mhi \x1B[0m" ); |
| 208 | test_fn!(rgb_fn: RGB(70,130,180); " \x1B[38;2;70;130;180mhi \x1B[0m" ); |
| 209 | test_fn!(rgb_on_blue_fn: RGB(70,130,180).on(Blue); " \x1B[44;38;2;70;130;180mhi \x1B[0m" ); |
| 210 | test_fn!(blue_on_rgb_fn: Blue.on(RGB(70,130,180)); " \x1B[48;2;70;130;180;34mhi \x1B[0m" ); |
| 211 | test_fn!(rgb_on_rgb_fn: RGB(70,130,180).on(RGB(5,10,15)); " \x1B[48;2;5;10;15;38;2;70;130;180mhi \x1B[0m" ); |
| 212 | test_fn!(bold_fn: Style::new().bold(); " \x1B[1mhi \x1B[0m" ); |
| 213 | test_fn!(underline_fn: Style::new().underline(); " \x1B[4mhi \x1B[0m" ); |
| 214 | test_fn!(bunderline_fn: Style::new().bold().underline(); " \x1B[1;4mhi \x1B[0m" ); |
| 215 | test_fn!(dimmed_fn: Style::new().dimmed(); " \x1B[2mhi \x1B[0m" ); |
| 216 | test_fn!(italic_fn: Style::new().italic(); " \x1B[3mhi \x1B[0m" ); |
| 217 | test_fn!(blink_fn: Style::new().blink(); " \x1B[5mhi \x1B[0m" ); |
| 218 | test_fn!(reverse_fn: Style::new().reverse(); " \x1B[7mhi \x1B[0m" ); |
| 219 | test_fn!(hidden_fn: Style::new().hidden(); " \x1B[8mhi \x1B[0m" ); |
| 220 | test_fn!(stricken_fn: Style::new().strikethrough(); " \x1B[9mhi \x1B[0m" ); |
| 221 | |
| 222 | #[test ] |
| 223 | fn test_move() { |
| 224 | let string = String::from("hi" ); |
| 225 | assert_eq!( |
| 226 | Style::default() |
| 227 | .paint_fn(|f| f.write_str(&string)) |
| 228 | .to_string(), |
| 229 | "hi" |
| 230 | ); |
| 231 | } |
| 232 | |
| 233 | #[test ] |
| 234 | fn test_ref() { |
| 235 | let string = &String::from("hi" ); |
| 236 | assert_eq!( |
| 237 | Style::default() |
| 238 | .paint_fn(|f| f.write_str(string)) |
| 239 | .to_string(), |
| 240 | "hi" |
| 241 | ); |
| 242 | } |
| 243 | |
| 244 | #[test ] |
| 245 | fn test_debug() { |
| 246 | let a = vec![1, 2, 3]; |
| 247 | assert_eq!( |
| 248 | Style::default() |
| 249 | .paint_fn(|f| std::fmt::Debug::fmt(&a, f)) |
| 250 | .to_string(), |
| 251 | "[1, 2, 3]" |
| 252 | ); |
| 253 | assert_eq!( |
| 254 | Style::default() |
| 255 | .bold() |
| 256 | .paint_fn(|f| std::fmt::Debug::fmt(&a, f)) |
| 257 | .to_string(), |
| 258 | " \x1B[1m[1, 2, 3] \x1B[0m" |
| 259 | ); |
| 260 | } |
| 261 | |
| 262 | #[test ] |
| 263 | fn test_write() { |
| 264 | assert_eq!( |
| 265 | Style::default() |
| 266 | .paint_fn(|f| write!(f, "{:.5}" , 1.0)) |
| 267 | .to_string(), |
| 268 | "1.00000" |
| 269 | ); |
| 270 | assert_eq!( |
| 271 | Style::default() |
| 272 | .bold() |
| 273 | .paint_fn(|f| write!(f, "{:.5}" , 1.0)) |
| 274 | .to_string(), |
| 275 | " \x1B[1m1.00000 \x1B[0m" |
| 276 | ); |
| 277 | } |
| 278 | |
| 279 | /// Can not write the same `impl Display` two or more times |
| 280 | /// else return error |
| 281 | #[test ] |
| 282 | fn test_error() { |
| 283 | use std::fmt::Write; |
| 284 | let a = Style::default().paint("foo" ); |
| 285 | let _ = a.to_string(); |
| 286 | let mut b = String::new(); |
| 287 | |
| 288 | assert!(write!(b, "{}" , a).is_err()); |
| 289 | } |
| 290 | } |
| 291 | |