1use std::{borrow::Cow, fmt::Display, str::FromStr};
2
3use bstr::{BStr, BString};
4use gix_error::{ErrorExt, Message, ResultExt, validation};
5
6use crate::Color;
7
8impl Display for Color {
9 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10 let mut write_space = None;
11 if let Some(fg) = self.foreground {
12 fg.fmt(f)?;
13 write_space = Some(());
14 }
15
16 if let Some(bg) = self.background {
17 if write_space.take().is_some() {
18 write!(f, " ")?;
19 }
20 bg.fmt(f)?;
21 write_space = Some(());
22 }
23
24 if !self.attributes.is_empty() {
25 if write_space.take().is_some() {
26 write!(f, " ")?;
27 }
28 self.attributes.fmt(f)?;
29 }
30 Ok(())
31 }
32}
33
34fn color_err(input: impl Into<BString>) -> Message {
35 validation("Colors are specific color values and their attributes, like 'brightred', or 'blue'")
36 .with("input", gix_error::MetadataValue::Bytes(input.into()))
37}
38
39impl TryFrom<&BStr> for Color {
40 type Error = gix_error::Exn<gix_error::Message>;
41
42 fn try_from(s: &BStr) -> Result<Self, Self::Error> {
43 let s = std::str::from_utf8(s).or_raise(|| color_err(s))?;
44 enum ColorItem {
45 Value(Name),
46 Attr(Attribute),
47 }
48
49 let items = s.split_whitespace().filter_map(|s| {
50 if s.is_empty() {
51 return None;
52 }
53
54 Some(
55 Name::from_str(s)
56 .map(ColorItem::Value)
57 .or_else(|_| Attribute::from_str(s).map(ColorItem::Attr)),
58 )
59 });
60
61 let mut foreground = None;
62 let mut background = None;
63 let mut attributes = Attribute::empty();
64 for item in items {
65 match item {
66 Ok(item) => match item {
67 ColorItem::Value(v) => {
68 if foreground.is_none() {
69 foreground = Some(v);
70 } else if background.is_none() {
71 background = Some(v);
72 } else {
73 return Err(color_err(s).raise());
74 }
75 }
76 ColorItem::Attr(a) => attributes |= a,
77 },
78 Err(_) => return Err(color_err(s).raise()),
79 }
80 }
81
82 Ok(Color {
83 foreground,
84 background,
85 attributes,
86 })
87 }
88}
89
90impl TryFrom<&str> for Color {
91 type Error = gix_error::Exn<gix_error::Message>;
92
93 fn try_from(value: &str) -> Result<Self, Self::Error> {
94 Self::try_from(BStr::new(value))
95 }
96}
97
98impl TryFrom<Cow<'_, BStr>> for Color {
99 type Error = gix_error::Exn<gix_error::Message>;
100
101 fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> {
102 Self::try_from(c.as_ref())
103 }
104}
105
106impl TryFrom<BString> for Color {
107 type Error = gix_error::Exn<gix_error::Message>;
108
109 fn try_from(value: BString) -> Result<Self, Self::Error> {
110 Self::try_from(BStr::new(&value))
111 }
112}
113
114#[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
212fn parse_hex(hex: &[u8]) -> Option<(u8, u8, u8)> {
216 fn nibble(b: u8) -> Option<u8> {
217 char::from(b).to_digit(16).map(|d| d as u8)
218 }
219
220 match *hex {
221 [r, g, b] => Some((nibble(r)? * 0x11, nibble(g)? * 0x11, nibble(b)? * 0x11)),
222 [r1, r0, g1, g0, b1, b0] => Some((
223 nibble(r1)? << 4 | nibble(r0)?,
224 nibble(g1)? << 4 | nibble(g0)?,
225 nibble(b1)? << 4 | nibble(b0)?,
226 )),
227 _ => None,
228 }
229}
230
231impl FromStr for Name {
232 type Err = gix_error::Exn<gix_error::Message>;
233
234 fn from_str(s: &str) -> Result<Self, Self::Err> {
235 const BASIC: &[(&str, Name, Name)] = &[
236 ("black", Name::Black, Name::BrightBlack),
237 ("red", Name::Red, Name::BrightRed),
238 ("green", Name::Green, Name::BrightGreen),
239 ("yellow", Name::Yellow, Name::BrightYellow),
240 ("blue", Name::Blue, Name::BrightBlue),
241 ("magenta", Name::Magenta, Name::BrightMagenta),
242 ("cyan", Name::Cyan, Name::BrightCyan),
243 ("white", Name::White, Name::BrightWhite),
244 ];
245
246 if s.eq_ignore_ascii_case("normal") {
247 return Ok(Self::Normal);
248 }
249
250 let (name, is_bright) = match s.split_at_checked("bright".len()) {
251 Some((prefix, rest)) if prefix.eq_ignore_ascii_case("bright") => (rest, true),
252 _ => (s, false),
253 };
254
255 for &(basic, plain, brightened) in BASIC {
256 if name.eq_ignore_ascii_case(basic) {
257 return Ok(if is_bright { brightened } else { plain });
258 }
259 }
260
261 if is_bright {
262 return Err(color_err(s).raise());
263 }
264
265 if s.eq_ignore_ascii_case("normal") || s == "-1" {
266 return Ok(Self::Normal);
267 }
268
269 if s.eq_ignore_ascii_case("default") {
270 return Ok(Self::Default);
271 }
272
273 if let Ok(v) = u8::from_str(s) {
274 return Ok(Self::Ansi(v));
275 }
276
277 if let Some(hex) = s.strip_prefix('#')
278 && let Some((r, g, b)) = parse_hex(hex.as_bytes())
279 {
280 return Ok(Self::Rgb(r, g, b));
281 }
282
283 Err(color_err(s).raise())
284 }
285}
286
287impl TryFrom<&BStr> for Name {
288 type Error = gix_error::Exn<gix_error::Message>;
289
290 fn try_from(s: &BStr) -> Result<Self, Self::Error> {
291 Self::from_str(std::str::from_utf8(s).or_raise(|| color_err(s))?)
292 }
293}
294
295bitflags::bitflags! {
296 #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
302 pub struct Attribute: u32 {
303 const BOLD = 1 << 1;
305 const DIM = 1 << 2;
307 const ITALIC = 1 << 3;
309 const UL = 1 << 4;
311 const BLINK = 1 << 5;
313 const REVERSE = 1 << 6;
315 const STRIKE = 1 << 7;
317 const RESET = 1 << 8;
319
320 const NO_DIM = 1 << 21;
322 const NO_BOLD = 1 << 22;
324 const NO_ITALIC = 1 << 23;
326 const NO_UL = 1 << 24;
328 const NO_BLINK = 1 << 25;
330 const NO_REVERSE = 1 << 26;
332 const NO_STRIKE = 1 << 27;
334 }
335}
336
337impl Display for Attribute {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 let mut write_space = None;
340 for bit in 1..std::mem::size_of::<Attribute>() * 8 {
341 let attr = match Attribute::from_bits(1 << bit) {
342 Some(attr) => attr,
343 None => continue,
344 };
345 if self.contains(attr) {
346 if write_space.take().is_some() {
347 write!(f, " ")?;
348 }
349 match attr {
350 Attribute::RESET => write!(f, "reset"),
351 Attribute::BOLD => write!(f, "bold"),
352 Attribute::NO_BOLD => write!(f, "nobold"),
353 Attribute::DIM => write!(f, "dim"),
354 Attribute::NO_DIM => write!(f, "nodim"),
355 Attribute::UL => write!(f, "ul"),
356 Attribute::NO_UL => write!(f, "noul"),
357 Attribute::BLINK => write!(f, "blink"),
358 Attribute::NO_BLINK => write!(f, "noblink"),
359 Attribute::REVERSE => write!(f, "reverse"),
360 Attribute::NO_REVERSE => write!(f, "noreverse"),
361 Attribute::ITALIC => write!(f, "italic"),
362 Attribute::NO_ITALIC => write!(f, "noitalic"),
363 Attribute::STRIKE => write!(f, "strike"),
364 Attribute::NO_STRIKE => write!(f, "nostrike"),
365 _ => unreachable!("BUG: add new attribute flag"),
366 }?;
367 write_space = Some(());
368 }
369 }
370 Ok(())
371 }
372}
373
374#[cfg(feature = "serde")]
375impl serde::Serialize for Attribute {
376 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
377 where
378 S: serde::Serializer,
379 {
380 serializer.serialize_str(&self.to_string())
381 }
382}
383
384impl FromStr for Attribute {
385 type Err = gix_error::Exn<gix_error::Message>;
386
387 fn from_str(mut s: &str) -> Result<Self, Self::Err> {
388 let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) {
389 s = rest;
390 true
391 } else {
392 false
393 };
394
395 if s.eq_ignore_ascii_case("reset") {
396 return if inverted {
397 Err(color_err(s).raise())
398 } else {
399 Ok(Attribute::RESET)
400 };
401 }
402
403 match s {
404 "bold" if !inverted => Ok(Attribute::BOLD),
405 "bold" if inverted => Ok(Attribute::NO_BOLD),
406 "dim" if !inverted => Ok(Attribute::DIM),
407 "dim" if inverted => Ok(Attribute::NO_DIM),
408 "ul" if !inverted => Ok(Attribute::UL),
409 "ul" if inverted => Ok(Attribute::NO_UL),
410 "blink" if !inverted => Ok(Attribute::BLINK),
411 "blink" if inverted => Ok(Attribute::NO_BLINK),
412 "reverse" if !inverted => Ok(Attribute::REVERSE),
413 "reverse" if inverted => Ok(Attribute::NO_REVERSE),
414 "italic" if !inverted => Ok(Attribute::ITALIC),
415 "italic" if inverted => Ok(Attribute::NO_ITALIC),
416 "strike" if !inverted => Ok(Attribute::STRIKE),
417 "strike" if inverted => Ok(Attribute::NO_STRIKE),
418 _ => Err(color_err(s).raise()),
419 }
420 }
421}
422
423impl TryFrom<&BStr> for Attribute {
424 type Error = gix_error::Exn<gix_error::Message>;
425
426 fn try_from(s: &BStr) -> Result<Self, Self::Error> {
427 Self::from_str(std::str::from_utf8(s).or_raise(|| color_err(s))?)
428 }
429}