Skip to main content

gix_config_value/
color.rs

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/// Discriminating enum for names of [`Color`] values.
116///
117/// `git-config` supports the eight standard colors, their bright variants, an
118/// ANSI color code, or a 24-bit hex value prefixed with an octothorpe/hash.
119#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
120pub enum Name {
121    /// The `normal` color name.
122    Normal,
123    /// The terminal's default color.
124    Default,
125    /// Black.
126    Black,
127    /// Bright black.
128    BrightBlack,
129    /// Red.
130    Red,
131    /// Bright red.
132    BrightRed,
133    /// Green.
134    Green,
135    /// Bright green.
136    BrightGreen,
137    /// Yellow.
138    Yellow,
139    /// Bright yellow.
140    BrightYellow,
141    /// Blue.
142    Blue,
143    /// Bright blue.
144    BrightBlue,
145    /// Magenta.
146    Magenta,
147    /// Bright magenta.
148    BrightMagenta,
149    /// Cyan.
150    Cyan,
151    /// Bright cyan.
152    BrightCyan,
153    /// White.
154    White,
155    /// Bright white.
156    BrightWhite,
157    /// A color from the ANSI 256-color palette.
158    Ansi(
159        /// The palette index.
160        u8,
161    ),
162    /// A 24-bit RGB color.
163    Rgb(
164        /// The red component.
165        u8,
166        /// The green component.
167        u8,
168        /// The blue component.
169        u8,
170    ),
171}
172
173impl Display for Name {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        match self {
176            Self::Normal => write!(f, "normal"),
177            Self::Default => write!(f, "default"),
178            Self::Black => write!(f, "black"),
179            Self::BrightBlack => write!(f, "brightblack"),
180            Self::Red => write!(f, "red"),
181            Self::BrightRed => write!(f, "brightred"),
182            Self::Green => write!(f, "green"),
183            Self::BrightGreen => write!(f, "brightgreen"),
184            Self::Yellow => write!(f, "yellow"),
185            Self::BrightYellow => write!(f, "brightyellow"),
186            Self::Blue => write!(f, "blue"),
187            Self::BrightBlue => write!(f, "brightblue"),
188            Self::Magenta => write!(f, "magenta"),
189            Self::BrightMagenta => write!(f, "brightmagenta"),
190            Self::Cyan => write!(f, "cyan"),
191            Self::BrightCyan => write!(f, "brightcyan"),
192            Self::White => write!(f, "white"),
193            Self::BrightWhite => write!(f, "brightwhite"),
194            Self::Ansi(num) => num.fmt(f),
195            Self::Rgb(r, g, b) => write!(f, "#{r:02x}{g:02x}{b:02x}"),
196        }
197    }
198}
199
200#[cfg(feature = "serde")]
201impl serde::Serialize for Name {
202    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
203    where
204        S: serde::Serializer,
205    {
206        serializer.serialize_str(&self.to_string())
207    }
208}
209
210impl FromStr for Name {
211    type Err = Error;
212
213    fn from_str(mut s: &str) -> Result<Self, Self::Err> {
214        let bright = if let Some(rest) = s.strip_prefix("bright") {
215            s = rest;
216            true
217        } else {
218            false
219        };
220
221        match s {
222            "normal" if !bright => return Ok(Self::Normal),
223            "-1" if !bright => return Ok(Self::Normal),
224            "normal" if bright => return Err(color_err(s)),
225            "default" if !bright => return Ok(Self::Default),
226            "default" if bright => return Err(color_err(s)),
227            "black" if !bright => return Ok(Self::Black),
228            "black" if bright => return Ok(Self::BrightBlack),
229            "red" if !bright => return Ok(Self::Red),
230            "red" if bright => return Ok(Self::BrightRed),
231            "green" if !bright => return Ok(Self::Green),
232            "green" if bright => return Ok(Self::BrightGreen),
233            "yellow" if !bright => return Ok(Self::Yellow),
234            "yellow" if bright => return Ok(Self::BrightYellow),
235            "blue" if !bright => return Ok(Self::Blue),
236            "blue" if bright => return Ok(Self::BrightBlue),
237            "magenta" if !bright => return Ok(Self::Magenta),
238            "magenta" if bright => return Ok(Self::BrightMagenta),
239            "cyan" if !bright => return Ok(Self::Cyan),
240            "cyan" if bright => return Ok(Self::BrightCyan),
241            "white" if !bright => return Ok(Self::White),
242            "white" if bright => return Ok(Self::BrightWhite),
243            _ => (),
244        }
245
246        if let Ok(v) = u8::from_str(s) {
247            return Ok(Self::Ansi(v));
248        }
249
250        if let Some(s) = s.strip_prefix('#') {
251            if s.len() == 6 && s.is_char_boundary(2) && s.is_char_boundary(4) && s.is_char_boundary(6) {
252                let rgb = (
253                    u8::from_str_radix(&s[..2], 16),
254                    u8::from_str_radix(&s[2..4], 16),
255                    u8::from_str_radix(&s[4..], 16),
256                );
257
258                if let (Ok(r), Ok(g), Ok(b)) = rgb {
259                    return Ok(Self::Rgb(r, g, b));
260                }
261            }
262        }
263
264        Err(color_err(s))
265    }
266}
267
268impl TryFrom<&BStr> for Name {
269    type Error = Error;
270
271    fn try_from(s: &BStr) -> Result<Self, Self::Error> {
272        Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?)
273    }
274}
275
276bitflags::bitflags! {
277    /// Discriminating enum for [`Color`] attributes.
278    ///
279    /// `git-config` supports modifiers and their negators. The negating color
280    /// attributes are equivalent to having a `no` or `no-` prefix to the normal
281    /// variant.
282    #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
283    pub struct Attribute: u32 {
284        /// Use bold or increased-intensity text.
285        const BOLD = 1 << 1;
286        /// Use dim or decreased-intensity text.
287        const DIM = 1 << 2;
288        /// Use italic text.
289        const ITALIC = 1 << 3;
290        /// Underline text.
291        const UL = 1 << 4;
292        /// Blink text.
293        const BLINK = 1 << 5;
294        /// Reverse the foreground and background colors.
295        const REVERSE = 1 << 6;
296        /// Strike through text.
297        const STRIKE = 1 << 7;
298        /// Parse the `reset` attribute, which Git otherwise leaves without an effect here.
299        const RESET = 1 << 8;
300
301        /// Disable dim text.
302        const NO_DIM = 1 << 21;
303        /// Disable bold text.
304        const NO_BOLD = 1 << 22;
305        /// Disable italic text.
306        const NO_ITALIC = 1 << 23;
307        /// Disable underlining.
308        const NO_UL = 1 << 24;
309        /// Disable blinking.
310        const NO_BLINK = 1 << 25;
311        /// Disable reversed colors.
312        const NO_REVERSE = 1 << 26;
313        /// Disable strikethrough.
314        const NO_STRIKE = 1 << 27;
315    }
316}
317
318impl Display for Attribute {
319    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320        let mut write_space = None;
321        for bit in 1..std::mem::size_of::<Attribute>() * 8 {
322            let attr = match Attribute::from_bits(1 << bit) {
323                Some(attr) => attr,
324                None => continue,
325            };
326            if self.contains(attr) {
327                if write_space.take().is_some() {
328                    write!(f, " ")?;
329                }
330                match attr {
331                    Attribute::RESET => write!(f, "reset"),
332                    Attribute::BOLD => write!(f, "bold"),
333                    Attribute::NO_BOLD => write!(f, "nobold"),
334                    Attribute::DIM => write!(f, "dim"),
335                    Attribute::NO_DIM => write!(f, "nodim"),
336                    Attribute::UL => write!(f, "ul"),
337                    Attribute::NO_UL => write!(f, "noul"),
338                    Attribute::BLINK => write!(f, "blink"),
339                    Attribute::NO_BLINK => write!(f, "noblink"),
340                    Attribute::REVERSE => write!(f, "reverse"),
341                    Attribute::NO_REVERSE => write!(f, "noreverse"),
342                    Attribute::ITALIC => write!(f, "italic"),
343                    Attribute::NO_ITALIC => write!(f, "noitalic"),
344                    Attribute::STRIKE => write!(f, "strike"),
345                    Attribute::NO_STRIKE => write!(f, "nostrike"),
346                    _ => unreachable!("BUG: add new attribute flag"),
347                }?;
348                write_space = Some(());
349            }
350        }
351        Ok(())
352    }
353}
354
355#[cfg(feature = "serde")]
356impl serde::Serialize for Attribute {
357    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
358    where
359        S: serde::Serializer,
360    {
361        serializer.serialize_str(&self.to_string())
362    }
363}
364
365impl FromStr for Attribute {
366    type Err = Error;
367
368    fn from_str(mut s: &str) -> Result<Self, Self::Err> {
369        let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) {
370            s = rest;
371            true
372        } else {
373            false
374        };
375
376        match s {
377            "reset" if !inverted => Ok(Attribute::RESET),
378            "reset" if inverted => Err(color_err(s)),
379            "bold" if !inverted => Ok(Attribute::BOLD),
380            "bold" if inverted => Ok(Attribute::NO_BOLD),
381            "dim" if !inverted => Ok(Attribute::DIM),
382            "dim" if inverted => Ok(Attribute::NO_DIM),
383            "ul" if !inverted => Ok(Attribute::UL),
384            "ul" if inverted => Ok(Attribute::NO_UL),
385            "blink" if !inverted => Ok(Attribute::BLINK),
386            "blink" if inverted => Ok(Attribute::NO_BLINK),
387            "reverse" if !inverted => Ok(Attribute::REVERSE),
388            "reverse" if inverted => Ok(Attribute::NO_REVERSE),
389            "italic" if !inverted => Ok(Attribute::ITALIC),
390            "italic" if inverted => Ok(Attribute::NO_ITALIC),
391            "strike" if !inverted => Ok(Attribute::STRIKE),
392            "strike" if inverted => Ok(Attribute::NO_STRIKE),
393            _ => Err(color_err(s)),
394        }
395    }
396}
397
398impl TryFrom<&BStr> for Attribute {
399    type Error = Error;
400
401    fn try_from(s: &BStr) -> Result<Self, Self::Error> {
402        Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?)
403    }
404}