colors.rs (4520B)
1 use irc::proto::Message; 2 use std::fmt::Display; 3 use std::str::FromStr; 4 5 #[derive(Clone,Copy)] 6 pub enum ColorKind { 7 White, 8 Black, 9 Blue, 10 Green, 11 Red, 12 Brown, 13 Magenta, 14 Orange, 15 Yellow, 16 LightGreen, 17 Cyan, 18 LightCyan, 19 LightBlue, 20 Pink, 21 Grey, 22 LightGrey, 23 } 24 25 impl ColorKind { 26 fn as_str(&self) -> &'static str { 27 match self { 28 Self::White => "00", 29 Self::Black => "01", 30 Self::Blue => "02", 31 Self::Green => "03", 32 Self::Red => "04", 33 Self::Brown => "05", 34 Self::Magenta => "06", 35 Self::Orange => "07", 36 Self::Yellow => "08", 37 Self::LightGreen => "09", 38 Self::Cyan => "10", 39 Self::LightCyan => "11", 40 Self::LightBlue => "12", 41 Self::Pink => "13", 42 Self::Grey => "14", 43 Self::LightGrey => "15", 44 } 45 } 46 } 47 48 impl Display for ColorKind { 49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 write!(f, "{}", self.as_str()) 51 } 52 } 53 54 pub enum FormattingKind { 55 Bold, 56 Italics, 57 Underline, 58 Strikethrough, 59 Monospace, 60 Color { 61 fg: ColorKind, 62 bg: Option<ColorKind>, 63 }, 64 ReverseColor, 65 None, 66 } 67 68 impl FormattingKind { 69 pub fn is_none(&self) -> bool { 70 match self { 71 Self::None => true, 72 _ => false 73 } 74 } 75 } 76 77 impl Display for FormattingKind { 78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 79 if let Self::Color { fg, bg } = self { 80 write!(f, "\u{0003}{}", fg)?; 81 if let Some(b) = bg { 82 write!(f, ",{}", b) 83 } else { 84 Ok(()) 85 } 86 } else { 87 write!( 88 f, 89 "{}", 90 match self { 91 Self::Bold => "\u{0002}", 92 Self::Italics => "\u{001D}", 93 Self::Underline => "\u{001F}", 94 Self::Strikethrough => "\u{001E}", 95 Self::Monospace => "\u{0011}", 96 Self::ReverseColor => "\u{0016}", 97 Self::None => "\u{000F}", 98 Self::Color { .. } => unreachable!(), 99 } 100 ) 101 } 102 } 103 } 104 105 pub struct Formatted<T: Display> { 106 kinds: Vec<FormattingKind>, 107 content: T, 108 } 109 110 impl<T: Display> Display for Formatted<T> { 111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 112 if self.kinds.len() == 1 && self.kinds.get(0).unwrap().is_none() { 113 return self.content.fmt(f); 114 } 115 116 for (i, k) in self.kinds.iter().enumerate() { 117 write!(f, "{}", k)?; 118 if let FormattingKind::Color { bg: None, .. } = k { 119 // Avoid eating , here 120 if self.content.to_string().starts_with(',') && i == self.kinds.len() - 1 { 121 write!( 122 f, 123 "{}{}", 124 FormattingKind::ReverseColor, 125 FormattingKind::ReverseColor 126 )?; 127 } 128 } 129 } 130 write!(f, "{}{}", self.content, FormattingKind::None) 131 } 132 } 133 134 impl<T: Display> Formatted<T> { 135 pub fn new(kinds: Vec<FormattingKind>, content: T) -> Self { 136 Formatted { kinds, content } 137 } 138 139 pub fn simple(kind: FormattingKind, content: T) -> Self { 140 Formatted { kinds: vec![kind], content} 141 } 142 } 143 144 impl<'a> Formatted<&'a str> { 145 pub fn from_str<'b: 'a>(s: &'b str) -> Self { 146 Self::new(vec![FormattingKind::None], s) 147 } 148 } 149 150 macro_rules! irc_fmt { 151 ($fmt:expr => $inner:expr) => { 152 Formatted::simple($fmt, $inner) 153 }; 154 ($($fmts:expr),+ => $inner:expr) => { 155 Formatted::new(vec![$($fmts),+], $inner) 156 }; 157 } 158 159 #[cfg(test)] 160 mod test { 161 use super::{ColorKind, Formatted, FormattingKind}; 162 163 #[test] 164 fn basic() { 165 let f = Formatted::from_str("abc"); 166 assert_eq!(f.to_string(), String::from("abc")) 167 } 168 169 #[test] 170 fn does_not_eat() { 171 let f = irc_fmt!(FormattingKind::Color { fg: ColorKind::White, bg: None } => ",02"); 172 assert_ne!(f.to_string(), String::from("\u{0003}00,02\u{000F}")) 173 } 174 175 #[test] 176 fn nested() { 177 let f = irc_fmt!(FormattingKind::Bold, FormattingKind::ReverseColor => "abc"); 178 assert_eq!(f.to_string(), String::from("\u{0002}\u{0016}abc\u{000F}")) 179 } 180 }