git_config_value/
color.rs

1#![allow(missing_docs)]
2use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr};
3
4use bstr::{BStr, BString};
5
6use crate::{Color, Error};
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>) -> Error {
35    Error::new(
36        "Colors are specific color values and their attributes, like 'brightred', or 'blue'",
37        input,
38    )
39}
40
41impl TryFrom<&BStr> for Color {
42    type Error = Error;
43
44    fn try_from(s: &BStr) -> Result<Self, Self::Error> {
45        let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?;
46        enum ColorItem {
47            Value(Name),
48            Attr(Attribute),
49        }
50
51        let items = s.split_whitespace().filter_map(|s| {
52            if s.is_empty() {
53                return None;
54            }
55
56            Some(
57                Name::from_str(s)
58                    .map(ColorItem::Value)
59                    .or_else(|_| Attribute::from_str(s).map(ColorItem::Attr)),
60            )
61        });
62
63        let mut foreground = None;
64        let mut background = None;
65        let mut attributes = Attribute::empty();
66        for item in items {
67            match item {
68                Ok(item) => match item {
69                    ColorItem::Value(v) => {
70                        if foreground.is_none() {
71                            foreground = Some(v);
72                        } else if background.is_none() {
73                            background = Some(v);
74                        } else {
75                            return Err(color_err(s));
76                        }
77                    }
78                    ColorItem::Attr(a) => attributes |= a,
79                },
80                Err(_) => return Err(color_err(s)),
81            }
82        }
83
84        Ok(Color {
85            foreground,
86            background,
87            attributes,
88        })
89    }
90}
91
92impl TryFrom<Cow<'_, BStr>> for Color {
93    type Error = Error;
94
95    fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> {
96        Self::try_from(c.as_ref())
97    }
98}
99
100/// Discriminating enum for names of [`Color`] values.
101///
102/// `git-config` supports the eight standard colors, their bright variants, an
103/// ANSI color code, or a 24-bit hex value prefixed with an octothorpe/hash.
104#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
105#[allow(missing_docs)]
106pub enum Name {
107    Normal,
108    Default,
109    Black,
110    BrightBlack,
111    Red,
112    BrightRed,
113    Green,
114    BrightGreen,
115    Yellow,
116    BrightYellow,
117    Blue,
118    BrightBlue,
119    Magenta,
120    BrightMagenta,
121    Cyan,
122    BrightCyan,
123    White,
124    BrightWhite,
125    Ansi(u8),
126    Rgb(u8, u8, u8),
127}
128
129impl Display for Name {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        match self {
132            Self::Normal => write!(f, "normal"),
133            Self::Default => write!(f, "default"),
134            Self::Black => write!(f, "black"),
135            Self::BrightBlack => write!(f, "brightblack"),
136            Self::Red => write!(f, "red"),
137            Self::BrightRed => write!(f, "brightred"),
138            Self::Green => write!(f, "green"),
139            Self::BrightGreen => write!(f, "brightgreen"),
140            Self::Yellow => write!(f, "yellow"),
141            Self::BrightYellow => write!(f, "brightyellow"),
142            Self::Blue => write!(f, "blue"),
143            Self::BrightBlue => write!(f, "brightblue"),
144            Self::Magenta => write!(f, "magenta"),
145            Self::BrightMagenta => write!(f, "brightmagenta"),
146            Self::Cyan => write!(f, "cyan"),
147            Self::BrightCyan => write!(f, "brightcyan"),
148            Self::White => write!(f, "white"),
149            Self::BrightWhite => write!(f, "brightwhite"),
150            Self::Ansi(num) => num.fmt(f),
151            Self::Rgb(r, g, b) => write!(f, "#{r:02x}{g:02x}{b:02x}"),
152        }
153    }
154}
155
156#[cfg(feature = "serde")]
157impl serde::Serialize for Name {
158    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
159    where
160        S: serde::Serializer,
161    {
162        serializer.serialize_str(&self.to_string())
163    }
164}
165
166impl FromStr for Name {
167    type Err = Error;
168
169    fn from_str(mut s: &str) -> Result<Self, Self::Err> {
170        let bright = if let Some(rest) = s.strip_prefix("bright") {
171            s = rest;
172            true
173        } else {
174            false
175        };
176
177        match s {
178            "normal" if !bright => return Ok(Self::Normal),
179            "-1" if !bright => return Ok(Self::Normal),
180            "normal" if bright => return Err(color_err(s)),
181            "default" if !bright => return Ok(Self::Default),
182            "default" if bright => return Err(color_err(s)),
183            "black" if !bright => return Ok(Self::Black),
184            "black" if bright => return Ok(Self::BrightBlack),
185            "red" if !bright => return Ok(Self::Red),
186            "red" if bright => return Ok(Self::BrightRed),
187            "green" if !bright => return Ok(Self::Green),
188            "green" if bright => return Ok(Self::BrightGreen),
189            "yellow" if !bright => return Ok(Self::Yellow),
190            "yellow" if bright => return Ok(Self::BrightYellow),
191            "blue" if !bright => return Ok(Self::Blue),
192            "blue" if bright => return Ok(Self::BrightBlue),
193            "magenta" if !bright => return Ok(Self::Magenta),
194            "magenta" if bright => return Ok(Self::BrightMagenta),
195            "cyan" if !bright => return Ok(Self::Cyan),
196            "cyan" if bright => return Ok(Self::BrightCyan),
197            "white" if !bright => return Ok(Self::White),
198            "white" if bright => return Ok(Self::BrightWhite),
199            _ => (),
200        }
201
202        if let Ok(v) = u8::from_str(s) {
203            return Ok(Self::Ansi(v));
204        }
205
206        if let Some(s) = s.strip_prefix('#') {
207            if s.len() == 6 {
208                let rgb = (
209                    u8::from_str_radix(&s[..2], 16),
210                    u8::from_str_radix(&s[2..4], 16),
211                    u8::from_str_radix(&s[4..], 16),
212                );
213
214                if let (Ok(r), Ok(g), Ok(b)) = rgb {
215                    return Ok(Self::Rgb(r, g, b));
216                }
217            }
218        }
219
220        Err(color_err(s))
221    }
222}
223
224impl TryFrom<&BStr> for Name {
225    type Error = Error;
226
227    fn try_from(s: &BStr) -> Result<Self, Self::Error> {
228        Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?)
229    }
230}
231
232bitflags::bitflags! {
233    /// Discriminating enum for [`Color`] attributes.
234    ///
235    /// `git-config` supports modifiers and their negators. The negating color
236    /// attributes are equivalent to having a `no` or `no-` prefix to the normal
237    /// variant.
238    #[derive(Default)]
239    pub struct Attribute: u32 {
240        const BOLD = 1 << 1;
241        const DIM = 1 << 2;
242        const ITALIC = 1 << 3;
243        const UL = 1 << 4;
244        const BLINK = 1 << 5;
245        const REVERSE = 1 << 6;
246        const STRIKE = 1 << 7;
247        /// Reset is special as we have to be able to parse it, without git actually doing anything with it
248        const RESET = 1 << 8;
249
250        const NO_DIM = 1 << 21;
251        const NO_BOLD = 1 << 22;
252        const NO_ITALIC = 1 << 23;
253        const NO_UL = 1 << 24;
254        const NO_BLINK = 1 << 25;
255        const NO_REVERSE = 1 << 26;
256        const NO_STRIKE = 1 << 27;
257    }
258}
259
260impl Display for Attribute {
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        let mut write_space = None;
263        for bit in 1..std::mem::size_of::<Attribute>() * 8 {
264            let attr = match Attribute::from_bits(1 << bit) {
265                Some(attr) => attr,
266                None => continue,
267            };
268            if self.contains(attr) {
269                if write_space.take().is_some() {
270                    write!(f, " ")?
271                }
272                match attr {
273                    Attribute::RESET => write!(f, "reset"),
274                    Attribute::BOLD => write!(f, "bold"),
275                    Attribute::NO_BOLD => write!(f, "nobold"),
276                    Attribute::DIM => write!(f, "dim"),
277                    Attribute::NO_DIM => write!(f, "nodim"),
278                    Attribute::UL => write!(f, "ul"),
279                    Attribute::NO_UL => write!(f, "noul"),
280                    Attribute::BLINK => write!(f, "blink"),
281                    Attribute::NO_BLINK => write!(f, "noblink"),
282                    Attribute::REVERSE => write!(f, "reverse"),
283                    Attribute::NO_REVERSE => write!(f, "noreverse"),
284                    Attribute::ITALIC => write!(f, "italic"),
285                    Attribute::NO_ITALIC => write!(f, "noitalic"),
286                    Attribute::STRIKE => write!(f, "strike"),
287                    Attribute::NO_STRIKE => write!(f, "nostrike"),
288                    _ => unreachable!("BUG: add new attribute flag"),
289                }?;
290                write_space = Some(());
291            }
292        }
293        Ok(())
294    }
295}
296
297#[cfg(feature = "serde")]
298impl serde::Serialize for Attribute {
299    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
300    where
301        S: serde::Serializer,
302    {
303        serializer.serialize_str(&self.to_string())
304    }
305}
306
307impl FromStr for Attribute {
308    type Err = Error;
309
310    fn from_str(mut s: &str) -> Result<Self, Self::Err> {
311        let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) {
312            s = rest;
313            true
314        } else {
315            false
316        };
317
318        match s {
319            "reset" if !inverted => Ok(Attribute::RESET),
320            "reset" if inverted => Err(color_err(s)),
321            "bold" if !inverted => Ok(Attribute::BOLD),
322            "bold" if inverted => Ok(Attribute::NO_BOLD),
323            "dim" if !inverted => Ok(Attribute::DIM),
324            "dim" if inverted => Ok(Attribute::NO_DIM),
325            "ul" if !inverted => Ok(Attribute::UL),
326            "ul" if inverted => Ok(Attribute::NO_UL),
327            "blink" if !inverted => Ok(Attribute::BLINK),
328            "blink" if inverted => Ok(Attribute::NO_BLINK),
329            "reverse" if !inverted => Ok(Attribute::REVERSE),
330            "reverse" if inverted => Ok(Attribute::NO_REVERSE),
331            "italic" if !inverted => Ok(Attribute::ITALIC),
332            "italic" if inverted => Ok(Attribute::NO_ITALIC),
333            "strike" if !inverted => Ok(Attribute::STRIKE),
334            "strike" if inverted => Ok(Attribute::NO_STRIKE),
335            _ => Err(color_err(s)),
336        }
337    }
338}
339
340impl TryFrom<&BStr> for Attribute {
341    type Error = Error;
342
343    fn try_from(s: &BStr) -> Result<Self, Self::Error> {
344        Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?)
345    }
346}