#![allow(missing_docs)]use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; use bstr::{BStr, BString}; use crate::{value, Color}; impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut write_space = None; if let Some(fg) = self.foreground { fg.fmt(f)?; write_space = Some(()); } if let Some(bg) = self.background { if write_space.take().is_some() { write!(f, " ")?; } bg.fmt(f)?; write_space = Some(()) } if !self.attributes.is_empty() { if write_space.take().is_some() { write!(f, " ")?; } self.attributes.fmt(f)?; } Ok(()) }} fn color_err(input: impl Into<BString>) -> value::Error { value::Error::new( "Colors are specific color values and their attributes, like 'brightred', or 'blue'", input, )} impl TryFrom<&BStr> for Color { type Error = value::Error; fn try_from(s: &BStr) -> Result<Self, Self::Error> { let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?; enum ColorItem { Value(Name), Attr(Attribute), } let items = s.split_whitespace().filter_map(|s| { if s.is_empty() { return None; } Some( Name::from_str(s) .map(ColorItem::Value) .or_else(|_| Attribute::from_str(s).map(ColorItem::Attr)), ) }); let mut foreground = None; let mut background = None; let mut attributes = Attribute::empty(); for item in items { match item { Ok(item) => match item { ColorItem::Value(v) => { if foreground.is_none() { foreground = Some(v); } else if background.is_none() { background = Some(v); } else { return Err(color_err(s)); } } ColorItem::Attr(a) => attributes |= a, }, Err(_) => return Err(color_err(s)), } } Ok(Color { foreground, background, attributes, }) }} impl TryFrom<Cow<'_, BStr>> for Color { type Error = value::Error; fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> { Self::try_from(c.as_ref()) }} /// Discriminating enum for names of [`Color`] values.////// `git-config` supports the eight standard colors, their bright variants, an/// ANSI color code, or a 24-bit hex value prefixed with an octothorpe/hash.#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]#[allow(missing_docs)]pub enum Name { Normal, Default, Black, BrightBlack, Red, BrightRed, Green, BrightGreen, Yellow, BrightYellow, Blue, BrightBlue, Magenta, BrightMagenta, Cyan, BrightCyan, White, BrightWhite, Ansi(u8), Rgb(u8, u8, u8),} impl Display for Name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Normal => write!(f, "normal"), Self::Default => write!(f, "default"), Self::Black => write!(f, "black"), Self::BrightBlack => write!(f, "brightblack"), Self::Red => write!(f, "red"), Self::BrightRed => write!(f, "brightred"), Self::Green => write!(f, "green"), Self::BrightGreen => write!(f, "brightgreen"), Self::Yellow => write!(f, "yellow"), Self::BrightYellow => write!(f, "brightyellow"), Self::Blue => write!(f, "blue"), Self::BrightBlue => write!(f, "brightblue"), Self::Magenta => write!(f, "magenta"), Self::BrightMagenta => write!(f, "brightmagenta"), Self::Cyan => write!(f, "cyan"), Self::BrightCyan => write!(f, "brightcyan"), Self::White => write!(f, "white"), Self::BrightWhite => write!(f, "brightwhite"), Self::Ansi(num) => num.fmt(f), Self::Rgb(r, g, b) => write!(f, "#{:02x}{:02x}{:02x}", r, g, b), } }} #[cfg(feature = "serde")]impl serde::Serialize for Name { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { serializer.serialize_str(&self.to_string()) }} impl FromStr for Name { type Err = value::Error; fn from_str(mut s: &str) -> Result<Self, Self::Err> { let bright = if let Some(rest) = s.strip_prefix("bright") { s = rest; true } else { false }; match s { "normal" if !bright => return Ok(Self::Normal), "-1" if !bright => return Ok(Self::Normal), "normal" if bright => return Err(color_err(s)), "default" if !bright => return Ok(Self::Default), "default" if bright => return Err(color_err(s)), "black" if !bright => return Ok(Self::Black), "black" if bright => return Ok(Self::BrightBlack), "red" if !bright => return Ok(Self::Red), "red" if bright => return Ok(Self::BrightRed), "green" if !bright => return Ok(Self::Green), "green" if bright => return Ok(Self::BrightGreen), "yellow" if !bright => return Ok(Self::Yellow), "yellow" if bright => return Ok(Self::BrightYellow), "blue" if !bright => return Ok(Self::Blue), "blue" if bright => return Ok(Self::BrightBlue), "magenta" if !bright => return Ok(Self::Magenta), "magenta" if bright => return Ok(Self::BrightMagenta), "cyan" if !bright => return Ok(Self::Cyan), "cyan" if bright => return Ok(Self::BrightCyan), "white" if !bright => return Ok(Self::White), "white" if bright => return Ok(Self::BrightWhite), _ => (), } if let Ok(v) = u8::from_str(s) { return Ok(Self::Ansi(v)); } if let Some(s) = s.strip_prefix('#') { if s.len() == 6 { let rgb = ( u8::from_str_radix(&s[..2], 16), u8::from_str_radix(&s[2..4], 16), u8::from_str_radix(&s[4..], 16), ); if let (Ok(r), Ok(g), Ok(b)) = rgb { return Ok(Self::Rgb(r, g, b)); } } } Err(color_err(s)) }} impl TryFrom<&BStr> for Name { type Error = value::Error; fn try_from(s: &BStr) -> Result<Self, Self::Error> { Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) }} bitflags::bitflags! { /// Discriminating enum for [`Color`] attributes. /// /// `git-config` supports modifiers and their negators. The negating color /// attributes are equivalent to having a `no` or `no-` prefix to the normal /// variant. #[derive(Default)] pub struct Attribute: u32 { const BOLD = 1 << 1; const DIM = 1 << 2; const ITALIC = 1 << 3; const UL = 1 << 4; const BLINK = 1 << 5; const REVERSE = 1 << 6; const STRIKE = 1 << 7; /// Reset is special as we have to be able to parse it, without git actually doing anything with it const RESET = 1 << 8; const NO_DIM = 1 << 21; const NO_BOLD = 1 << 22; const NO_ITALIC = 1 << 23; const NO_UL = 1 << 24; const NO_BLINK = 1 << 25; const NO_REVERSE = 1 << 26; const NO_STRIKE = 1 << 27; }} impl Display for Attribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut write_space = None; for bit in 1..std::mem::size_of::<Attribute>() * 8 { let attr = match Attribute::from_bits(1 << bit) { Some(attr) => attr, None => continue, }; if self.contains(attr) { if write_space.take().is_some() { write!(f, " ")? } match attr { Attribute::RESET => write!(f, "reset"), Attribute::BOLD => write!(f, "bold"), Attribute::NO_BOLD => write!(f, "nobold"), Attribute::DIM => write!(f, "dim"), Attribute::NO_DIM => write!(f, "nodim"), Attribute::UL => write!(f, "ul"), Attribute::NO_UL => write!(f, "noul"), Attribute::BLINK => write!(f, "blink"), Attribute::NO_BLINK => write!(f, "noblink"), Attribute::REVERSE => write!(f, "reverse"), Attribute::NO_REVERSE => write!(f, "noreverse"), Attribute::ITALIC => write!(f, "italic"), Attribute::NO_ITALIC => write!(f, "noitalic"), Attribute::STRIKE => write!(f, "strike"), Attribute::NO_STRIKE => write!(f, "nostrike"), _ => unreachable!("BUG: add new attribute flag"), }?; write_space = Some(()); } } Ok(()) }} #[cfg(feature = "serde")]impl serde::Serialize for Attribute { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { serializer.serialize_str(&self.to_string()) }} impl FromStr for Attribute { type Err = value::Error; fn from_str(mut s: &str) -> Result<Self, Self::Err> { let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) { s = rest; true } else { false }; match s { "reset" if !inverted => Ok(Attribute::RESET), "reset" if inverted => Err(color_err(s)), "bold" if !inverted => Ok(Attribute::BOLD), "bold" if inverted => Ok(Attribute::NO_BOLD), "dim" if !inverted => Ok(Attribute::DIM), "dim" if inverted => Ok(Attribute::NO_DIM), "ul" if !inverted => Ok(Attribute::UL), "ul" if inverted => Ok(Attribute::NO_UL), "blink" if !inverted => Ok(Attribute::BLINK), "blink" if inverted => Ok(Attribute::NO_BLINK), "reverse" if !inverted => Ok(Attribute::REVERSE), "reverse" if inverted => Ok(Attribute::NO_REVERSE), "italic" if !inverted => Ok(Attribute::ITALIC), "italic" if inverted => Ok(Attribute::NO_ITALIC), "strike" if !inverted => Ok(Attribute::STRIKE), "strike" if inverted => Ok(Attribute::NO_STRIKE), _ => Err(color_err(s)), } }} impl TryFrom<&BStr> for Attribute { type Error = value::Error; fn try_from(s: &BStr) -> Result<Self, Self::Error> { Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) }}
#![allow(missing_docs)]
use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr};
use bstr::{BStr, BString};
use crate::{value, Color};
impl Display for Color {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut write_space = None;
if let Some(fg) = self.foreground {
fg.fmt(f)?;
write_space = Some(());
}
if let Some(bg) = self.background {
if write_space.take().is_some() {
write!(f, " ")?;
}
bg.fmt(f)?;
write_space = Some(())
}
if !self.attributes.is_empty() {
if write_space.take().is_some() {
write!(f, " ")?;
}
self.attributes.fmt(f)?;
}
Ok(())
}
}
fn color_err(input: impl Into<BString>) -> value::Error {
value::Error::new(
"Colors are specific color values and their attributes, like 'brightred', or 'blue'",
input,
)
}
impl TryFrom<&BStr> for Color {
type Error = value::Error;
fn try_from(s: &BStr) -> Result<Self, Self::Error> {
let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?;
enum ColorItem {
Value(Name),
Attr(Attribute),
}
let items = s.split_whitespace().filter_map(|s| {
if s.is_empty() {
return None;
}
Some(
Name::from_str(s)
.map(ColorItem::Value)
.or_else(|_| Attribute::from_str(s).map(ColorItem::Attr)),
)
});
let mut foreground = None;
let mut background = None;
let mut attributes = Attribute::empty();
for item in items {
match item {
Ok(item) => match item {
ColorItem::Value(v) => {
if foreground.is_none() {
foreground = Some(v);
} else if background.is_none() {
background = Some(v);
} else {
return Err(color_err(s));
}
}
ColorItem::Attr(a) => attributes |= a,
},
Err(_) => return Err(color_err(s)),
}
}
Ok(Color {
foreground,
background,
attributes,
})
}
}
impl TryFrom<Cow<'_, BStr>> for Color {
type Error = value::Error;
fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> {
Self::try_from(c.as_ref())
}
}
/// Discriminating enum for names of [`Color`] values.
///
/// `git-config` supports the eight standard colors, their bright variants, an
/// ANSI color code, or a 24-bit hex value prefixed with an octothorpe/hash.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[allow(missing_docs)]
pub enum Name {
Normal,
Default,
Black,
BrightBlack,
Red,
BrightRed,
Green,
BrightGreen,
Yellow,
BrightYellow,
Blue,
BrightBlue,
Magenta,
BrightMagenta,
Cyan,
BrightCyan,
White,
BrightWhite,
Ansi(u8),
Rgb(u8, u8, u8),
}
impl Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Normal => write!(f, "normal"),
Self::Default => write!(f, "default"),
Self::Black => write!(f, "black"),
Self::BrightBlack => write!(f, "brightblack"),
Self::Red => write!(f, "red"),
Self::BrightRed => write!(f, "brightred"),
Self::Green => write!(f, "green"),
Self::BrightGreen => write!(f, "brightgreen"),
Self::Yellow => write!(f, "yellow"),
Self::BrightYellow => write!(f, "brightyellow"),
Self::Blue => write!(f, "blue"),
Self::BrightBlue => write!(f, "brightblue"),
Self::Magenta => write!(f, "magenta"),
Self::BrightMagenta => write!(f, "brightmagenta"),
Self::Cyan => write!(f, "cyan"),
Self::BrightCyan => write!(f, "brightcyan"),
Self::White => write!(f, "white"),
Self::BrightWhite => write!(f, "brightwhite"),
Self::Ansi(num) => num.fmt(f),
Self::Rgb(r, g, b) => write!(f, "#{:02x}{:02x}{:02x}", r, g, b),
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl FromStr for Name {
type Err = value::Error;
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
let bright = if let Some(rest) = s.strip_prefix("bright") {
s = rest;
true
} else {
false
};
match s {
"normal" if !bright => return Ok(Self::Normal),
"-1" if !bright => return Ok(Self::Normal),
"normal" if bright => return Err(color_err(s)),
"default" if !bright => return Ok(Self::Default),
"default" if bright => return Err(color_err(s)),
"black" if !bright => return Ok(Self::Black),
"black" if bright => return Ok(Self::BrightBlack),
"red" if !bright => return Ok(Self::Red),
"red" if bright => return Ok(Self::BrightRed),
"green" if !bright => return Ok(Self::Green),
"green" if bright => return Ok(Self::BrightGreen),
"yellow" if !bright => return Ok(Self::Yellow),
"yellow" if bright => return Ok(Self::BrightYellow),
"blue" if !bright => return Ok(Self::Blue),
"blue" if bright => return Ok(Self::BrightBlue),
"magenta" if !bright => return Ok(Self::Magenta),
"magenta" if bright => return Ok(Self::BrightMagenta),
"cyan" if !bright => return Ok(Self::Cyan),
"cyan" if bright => return Ok(Self::BrightCyan),
"white" if !bright => return Ok(Self::White),
"white" if bright => return Ok(Self::BrightWhite),
_ => (),
}
if let Ok(v) = u8::from_str(s) {
return Ok(Self::Ansi(v));
}
if let Some(s) = s.strip_prefix('#') {
if s.len() == 6 {
let rgb = (
u8::from_str_radix(&s[..2], 16),
u8::from_str_radix(&s[2..4], 16),
u8::from_str_radix(&s[4..], 16),
);
if let (Ok(r), Ok(g), Ok(b)) = rgb {
return Ok(Self::Rgb(r, g, b));
}
}
}
Err(color_err(s))
}
}
impl TryFrom<&BStr> for Name {
type Error = value::Error;
fn try_from(s: &BStr) -> Result<Self, Self::Error> {
Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?)
}
}
bitflags::bitflags! {
/// Discriminating enum for [`Color`] attributes.
///
/// `git-config` supports modifiers and their negators. The negating color
/// attributes are equivalent to having a `no` or `no-` prefix to the normal
/// variant.
#[derive(Default)]
pub struct Attribute: u32 {
const BOLD = 1 << 1;
const DIM = 1 << 2;
const ITALIC = 1 << 3;
const UL = 1 << 4;
const BLINK = 1 << 5;
const REVERSE = 1 << 6;
const STRIKE = 1 << 7;
/// Reset is special as we have to be able to parse it, without git actually doing anything with it
const RESET = 1 << 8;
const NO_DIM = 1 << 21;
const NO_BOLD = 1 << 22;
const NO_ITALIC = 1 << 23;
const NO_UL = 1 << 24;
const NO_BLINK = 1 << 25;
const NO_REVERSE = 1 << 26;
const NO_STRIKE = 1 << 27;
}
}
impl Display for Attribute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut write_space = None;
for bit in 1..std::mem::size_of::<Attribute>() * 8 {
let attr = match Attribute::from_bits(1 << bit) {
Some(attr) => attr,
None => continue,
};
if self.contains(attr) {
if write_space.take().is_some() {
write!(f, " ")?
}
match attr {
Attribute::RESET => write!(f, "reset"),
Attribute::BOLD => write!(f, "bold"),
Attribute::NO_BOLD => write!(f, "nobold"),
Attribute::DIM => write!(f, "dim"),
Attribute::NO_DIM => write!(f, "nodim"),
Attribute::UL => write!(f, "ul"),
Attribute::NO_UL => write!(f, "noul"),
Attribute::BLINK => write!(f, "blink"),
Attribute::NO_BLINK => write!(f, "noblink"),
Attribute::REVERSE => write!(f, "reverse"),
Attribute::NO_REVERSE => write!(f, "noreverse"),
Attribute::ITALIC => write!(f, "italic"),
Attribute::NO_ITALIC => write!(f, "noitalic"),
Attribute::STRIKE => write!(f, "strike"),
Attribute::NO_STRIKE => write!(f, "nostrike"),
_ => unreachable!("BUG: add new attribute flag"),
}?;
write_space = Some(());
}
}
Ok(())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Attribute {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl FromStr for Attribute {
type Err = value::Error;
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) {
s = rest;
true
} else {
false
};
match s {
"reset" if !inverted => Ok(Attribute::RESET),
"reset" if inverted => Err(color_err(s)),
"bold" if !inverted => Ok(Attribute::BOLD),
"bold" if inverted => Ok(Attribute::NO_BOLD),
"dim" if !inverted => Ok(Attribute::DIM),
"dim" if inverted => Ok(Attribute::NO_DIM),
"ul" if !inverted => Ok(Attribute::UL),
"ul" if inverted => Ok(Attribute::NO_UL),
"blink" if !inverted => Ok(Attribute::BLINK),
"blink" if inverted => Ok(Attribute::NO_BLINK),
"reverse" if !inverted => Ok(Attribute::REVERSE),
"reverse" if inverted => Ok(Attribute::NO_REVERSE),
"italic" if !inverted => Ok(Attribute::ITALIC),
"italic" if inverted => Ok(Attribute::NO_ITALIC),
"strike" if !inverted => Ok(Attribute::STRIKE),
"strike" if inverted => Ok(Attribute::NO_STRIKE),
_ => Err(color_err(s)),
}
}
}
impl TryFrom<&BStr> for Attribute {
type Error = value::Error;
fn try_from(s: &BStr) -> Result<Self, Self::Error> {
Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?)
}
}