1use std::{borrow::Cow, fmt::Display, str::FromStr};
2
3use bstr::{BStr, BString};
4
5use crate::{Color, Error};
6
7impl Display for Color {
8 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9 let mut write_space = None;
10 if let Some(fg) = self.foreground {
11 fg.fmt(f)?;
12 write_space = Some(());
13 }
14
15 if let Some(bg) = self.background {
16 if write_space.take().is_some() {
17 write!(f, " ")?;
18 }
19 bg.fmt(f)?;
20 write_space = Some(());
21 }
22
23 if !self.attributes.is_empty() {
24 if write_space.take().is_some() {
25 write!(f, " ")?;
26 }
27 self.attributes.fmt(f)?;
28 }
29 Ok(())
30 }
31}
32
33fn color_err(input: impl Into<BString>) -> Error {
34 Error::new(
35 "Colors are specific color values and their attributes, like 'brightred', or 'blue'",
36 input,
37 )
38}
39
40impl TryFrom<&BStr> for Color {
41 type Error = Error;
42
43 fn try_from(s: &BStr) -> Result<Self, Self::Error> {
44 let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?;
45 enum ColorItem {
46 Value(Name),
47 Attr(Attribute),
48 }
49
50 let items = s.split_whitespace().filter_map(|s| {
51 if s.is_empty() {
52 return None;
53 }
54
55 Some(
56 Name::from_str(s)
57 .map(ColorItem::Value)
58 .or_else(|_| Attribute::from_str(s).map(ColorItem::Attr)),
59 )
60 });
61
62 let mut foreground = None;
63 let mut background = None;
64 let mut attributes = Attribute::empty();
65 for item in items {
66 match item {
67 Ok(item) => match item {
68 ColorItem::Value(v) => {
69 if foreground.is_none() {
70 foreground = Some(v);
71 } else if background.is_none() {
72 background = Some(v);
73 } else {
74 return Err(color_err(s));
75 }
76 }
77 ColorItem::Attr(a) => attributes |= a,
78 },
79 Err(_) => return Err(color_err(s)),
80 }
81 }
82
83 Ok(Color {
84 foreground,
85 background,
86 attributes,
87 })
88 }
89}
90
91impl TryFrom<&str> for Color {
92 type Error = Error;
93
94 fn try_from(value: &str) -> Result<Self, Self::Error> {
95 Self::try_from(BStr::new(value))
96 }
97}
98
99impl TryFrom<Cow<'_, BStr>> for Color {
100 type Error = Error;
101
102 fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> {
103 Self::try_from(c.as_ref())
104 }
105}
106
107impl TryFrom<BString> for Color {
108 type Error = Error;
109
110 fn try_from(value: BString) -> Result<Self, Self::Error> {
111 Self::try_from(BStr::new(&value))
112 }
113}
114
115#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
122pub enum Name {
123 Normal,
125 Default,
127 Black,
129 BrightBlack,
131 Red,
133 BrightRed,
135 Green,
137 BrightGreen,
139 Yellow,
141 BrightYellow,
143 Blue,
145 BrightBlue,
147 Magenta,
149 BrightMagenta,
151 Cyan,
153 BrightCyan,
155 White,
157 BrightWhite,
159 Ansi(
161 u8,
163 ),
164 Rgb(
166 u8,
168 u8,
170 u8,
172 ),
173}
174
175impl Display for Name {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 match self {
178 Self::Normal => write!(f, "normal"),
179 Self::Default => write!(f, "default"),
180 Self::Black => write!(f, "black"),
181 Self::BrightBlack => write!(f, "brightblack"),
182 Self::Red => write!(f, "red"),
183 Self::BrightRed => write!(f, "brightred"),
184 Self::Green => write!(f, "green"),
185 Self::BrightGreen => write!(f, "brightgreen"),
186 Self::Yellow => write!(f, "yellow"),
187 Self::BrightYellow => write!(f, "brightyellow"),
188 Self::Blue => write!(f, "blue"),
189 Self::BrightBlue => write!(f, "brightblue"),
190 Self::Magenta => write!(f, "magenta"),
191 Self::BrightMagenta => write!(f, "brightmagenta"),
192 Self::Cyan => write!(f, "cyan"),
193 Self::BrightCyan => write!(f, "brightcyan"),
194 Self::White => write!(f, "white"),
195 Self::BrightWhite => write!(f, "brightwhite"),
196 Self::Ansi(num) => num.fmt(f),
197 Self::Rgb(r, g, b) => write!(f, "#{r:02x}{g:02x}{b:02x}"),
198 }
199 }
200}
201
202#[cfg(feature = "serde")]
203impl serde::Serialize for Name {
204 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
205 where
206 S: serde::Serializer,
207 {
208 serializer.serialize_str(&self.to_string())
209 }
210}
211
212impl FromStr for Name {
213 type Err = Error;
214
215 fn from_str(s: &str) -> Result<Self, Self::Err> {
216 const BASIC: &[(&str, Name, Name)] = &[
217 ("black", Name::Black, Name::BrightBlack),
218 ("red", Name::Red, Name::BrightRed),
219 ("green", Name::Green, Name::BrightGreen),
220 ("yellow", Name::Yellow, Name::BrightYellow),
221 ("blue", Name::Blue, Name::BrightBlue),
222 ("magenta", Name::Magenta, Name::BrightMagenta),
223 ("cyan", Name::Cyan, Name::BrightCyan),
224 ("white", Name::White, Name::BrightWhite),
225 ];
226
227 if s.eq_ignore_ascii_case("normal") {
228 return Ok(Self::Normal);
229 }
230
231 let (name, is_bright) = match s.split_at_checked("bright".len()) {
232 Some((prefix, rest)) if prefix.eq_ignore_ascii_case("bright") => (rest, true),
233 _ => (s, false),
234 };
235
236 for &(basic, plain, brightened) in BASIC {
237 if name.eq_ignore_ascii_case(basic) {
238 return Ok(if is_bright { brightened } else { plain });
239 }
240 }
241
242 if is_bright {
243 return Err(color_err(s));
244 }
245
246 if s.eq_ignore_ascii_case("normal") || s == "-1" {
247 return Ok(Self::Normal);
248 }
249
250 if s.eq_ignore_ascii_case("default") {
251 return Ok(Self::Default);
252 }
253
254 if let Ok(v) = u8::from_str(s) {
255 return Ok(Self::Ansi(v));
256 }
257
258 if let Some(s) = s.strip_prefix('#') {
259 if s.len() == 6 && s.is_char_boundary(2) && s.is_char_boundary(4) && s.is_char_boundary(6) {
260 let rgb = (
261 u8::from_str_radix(&s[..2], 16),
262 u8::from_str_radix(&s[2..4], 16),
263 u8::from_str_radix(&s[4..], 16),
264 );
265
266 if let (Ok(r), Ok(g), Ok(b)) = rgb {
267 return Ok(Self::Rgb(r, g, b));
268 }
269 }
270 }
271
272 Err(color_err(s))
273 }
274}
275
276impl TryFrom<&BStr> for Name {
277 type Error = Error;
278
279 fn try_from(s: &BStr) -> Result<Self, Self::Error> {
280 Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?)
281 }
282}
283
284bitflags::bitflags! {
285 #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
291 pub struct Attribute: u32 {
292 const BOLD = 1 << 1;
294 const DIM = 1 << 2;
296 const ITALIC = 1 << 3;
298 const UL = 1 << 4;
300 const BLINK = 1 << 5;
302 const REVERSE = 1 << 6;
304 const STRIKE = 1 << 7;
306 const RESET = 1 << 8;
308
309 const NO_DIM = 1 << 21;
311 const NO_BOLD = 1 << 22;
313 const NO_ITALIC = 1 << 23;
315 const NO_UL = 1 << 24;
317 const NO_BLINK = 1 << 25;
319 const NO_REVERSE = 1 << 26;
321 const NO_STRIKE = 1 << 27;
323 }
324}
325
326impl Display for Attribute {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 let mut write_space = None;
329 for bit in 1..std::mem::size_of::<Attribute>() * 8 {
330 let attr = match Attribute::from_bits(1 << bit) {
331 Some(attr) => attr,
332 None => continue,
333 };
334 if self.contains(attr) {
335 if write_space.take().is_some() {
336 write!(f, " ")?;
337 }
338 match attr {
339 Attribute::RESET => write!(f, "reset"),
340 Attribute::BOLD => write!(f, "bold"),
341 Attribute::NO_BOLD => write!(f, "nobold"),
342 Attribute::DIM => write!(f, "dim"),
343 Attribute::NO_DIM => write!(f, "nodim"),
344 Attribute::UL => write!(f, "ul"),
345 Attribute::NO_UL => write!(f, "noul"),
346 Attribute::BLINK => write!(f, "blink"),
347 Attribute::NO_BLINK => write!(f, "noblink"),
348 Attribute::REVERSE => write!(f, "reverse"),
349 Attribute::NO_REVERSE => write!(f, "noreverse"),
350 Attribute::ITALIC => write!(f, "italic"),
351 Attribute::NO_ITALIC => write!(f, "noitalic"),
352 Attribute::STRIKE => write!(f, "strike"),
353 Attribute::NO_STRIKE => write!(f, "nostrike"),
354 _ => unreachable!("BUG: add new attribute flag"),
355 }?;
356 write_space = Some(());
357 }
358 }
359 Ok(())
360 }
361}
362
363#[cfg(feature = "serde")]
364impl serde::Serialize for Attribute {
365 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
366 where
367 S: serde::Serializer,
368 {
369 serializer.serialize_str(&self.to_string())
370 }
371}
372
373impl FromStr for Attribute {
374 type Err = Error;
375
376 fn from_str(mut s: &str) -> Result<Self, Self::Err> {
377 let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) {
378 s = rest;
379 true
380 } else {
381 false
382 };
383
384 if s.eq_ignore_ascii_case("reset") {
385 return if inverted {
386 Err(color_err(s))
387 } else {
388 Ok(Attribute::RESET)
389 };
390 }
391
392 match s {
393 "bold" if !inverted => Ok(Attribute::BOLD),
394 "bold" if inverted => Ok(Attribute::NO_BOLD),
395 "dim" if !inverted => Ok(Attribute::DIM),
396 "dim" if inverted => Ok(Attribute::NO_DIM),
397 "ul" if !inverted => Ok(Attribute::UL),
398 "ul" if inverted => Ok(Attribute::NO_UL),
399 "blink" if !inverted => Ok(Attribute::BLINK),
400 "blink" if inverted => Ok(Attribute::NO_BLINK),
401 "reverse" if !inverted => Ok(Attribute::REVERSE),
402 "reverse" if inverted => Ok(Attribute::NO_REVERSE),
403 "italic" if !inverted => Ok(Attribute::ITALIC),
404 "italic" if inverted => Ok(Attribute::NO_ITALIC),
405 "strike" if !inverted => Ok(Attribute::STRIKE),
406 "strike" if inverted => Ok(Attribute::NO_STRIKE),
407 _ => Err(color_err(s)),
408 }
409 }
410}
411
412impl TryFrom<&BStr> for Attribute {
413 type Error = Error;
414
415 fn try_from(s: &BStr) -> Result<Self, Self::Error> {
416 Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?)
417 }
418}