Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 11 additions & 21 deletions crates/anstyle-git/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,9 @@ fn parse_color(word: &str) -> Result<Option<anstyle::Color>, ()> {
"white" => Some(anstyle::AnsiColor::White.into()),
_ => {
if let Some(hex) = word.strip_prefix('#') {
let l = hex.len();
if l != 3 && l != 6 {
return Err(());
}
let l = l / 3;
if let (Ok(r), Ok(g), Ok(b)) = (
u8::from_str_radix(&hex[0..l], 16),
u8::from_str_radix(&hex[l..(2 * l)], 16),
u8::from_str_radix(&hex[(2 * l)..(3 * l)], 16),
) {
Some(anstyle::Color::from((r, g, b)))
} else {
return Err(());
match anstyle::Color::from_hex_str(hex) {
None => return Err(()),
c => c,
}
} else if let Ok(n) = word.parse::<u8>() {
Some(anstyle::Color::from(n))
Expand Down Expand Up @@ -217,12 +207,12 @@ mod tests {
test!("#204060" => RgbColor(0x20,0x40,0x60).on_default());
test!("#1a2b3c" => RgbColor(0x1a,0x2b,0x3c).on_default());
test!("#000" => RgbColor(0,0,0).on_default());
test!("#cba" => RgbColor(0xc,0xb,0xa).on_default());
test!("#cba " => RgbColor(0xc,0xb,0xa).on_default());
test!("#987 #135" => RgbColor(9,8,7).on(RgbColor(1, 3, 5)));
test!("#987 #135 " => RgbColor(9,8,7).on(RgbColor(1, 3, 5)));
test!("#123 #abcdef" => RgbColor(1,2,3).on(RgbColor(0xab, 0xcd, 0xef)));
test!("#654321 #a9b" => RgbColor(0x65,0x43,0x21).on(RgbColor(0xa, 0x9, 0xb)));
test!("#cba" => RgbColor(0xcc, 0xbb, 0xaa).on_default());
test!("#cba " => RgbColor(0xcc, 0xbb, 0xaa).on_default());
test!("#987 #135" => RgbColor(0x99, 0x88, 0x77).on(RgbColor(0x11, 0x33, 0x55)));
test!("#987 #135 " => RgbColor(0x99, 0x88, 0x77).on(RgbColor(0x11, 0x33, 0x55)));
test!("#123 #abcdef" => RgbColor(0x11, 0x22, 0x33).on(RgbColor(0xab, 0xcd, 0xef)));
test!("#654321 #a9b" => RgbColor(0x65, 0x43, 0x21).on(RgbColor(0xaa, 0x99, 0xbb)));

test!("bold cyan white" => Cyan.on(White).bold());
test!("bold cyan nobold white" => Cyan.on(White));
Expand All @@ -232,8 +222,8 @@ mod tests {
test!("italic cyan white" => Cyan.on(White).italic());
test!("strike cyan white" => Cyan.on(White).strikethrough());
test!("blink #050505 white" => RgbColor(5,5,5).on(White).blink());
test!("bold #987 green" => RgbColor(9,8,7).on(Green).bold());
test!("strike #147 #cba" => RgbColor(1,4,7).on(RgbColor(0xc, 0xb, 0xa)).strikethrough());
test!("bold #987 green" => RgbColor(0x99, 0x88, 0x77).on(Green).bold());
test!("strike #147 #cba" => RgbColor(0x11, 0x44, 0x77).on(RgbColor(0xcc, 0xbb, 0xaa)).strikethrough());
}

#[test]
Expand Down
44 changes: 44 additions & 0 deletions crates/anstyle/src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ pub enum Color {
}

impl Color {
/// Parse a `Color` from a hex string of length 3 or 6.
///
/// Strings of length 3 (`abc`) will get interpreted as though each character were doubled
/// (`aabbcc`), as in HTML or Git.
///
/// This does not handle an initial `#`; strip it before calling this function. This allows the
/// caller to distinguish hex colors from other possible inputs, such as named or numbered
/// colors.
///
/// The result will always be a 24-bit `RgbColor`.
///
/// Returns `None` on parse error.
#[inline]
pub fn from_hex_str(hex: &str) -> Option<Self> {
Some(RgbColor::from_hex_str(hex)?.into())
}

/// Create a [`Style`][crate::Style] with this as the foreground
#[inline]
pub fn on(self, background: impl Into<Self>) -> crate::Style {
Expand Down Expand Up @@ -476,6 +493,33 @@ impl From<AnsiColor> for Ansi256Color {
pub struct RgbColor(pub u8, pub u8, pub u8);

impl RgbColor {
/// Parse an `RgbColor` from a hex string of length 3 or 6.
///
/// Strings of length 3 (`abc`) will get interpreted as though each character were doubled
/// (`aabbcc`), as in HTML or Git.
///
/// This does not handle an initial `#`; strip it before calling this function. This allows the
/// caller to distinguish hex colors from other possible inputs, such as named or numbered
/// colors.
///
/// Returns `None` on parse error.
pub fn from_hex_str(hex: &str) -> Option<Self> {
let l = hex.len();
if l != 3 && l != 6 {
return None;
}
let l = l / 3;
let (Ok(r), Ok(g), Ok(b)) = (
u8::from_str_radix(&hex[0..l], 16),
u8::from_str_radix(&hex[l..(2 * l)], 16),
u8::from_str_radix(&hex[(2 * l)..(3 * l)], 16),
) else {
return None;
};
let m = if l == 1 { 0x11 } else { 1 };
Some(Self(r * m, g * m, b * m))
}

/// Create a [`Style`][crate::Style] with this as the foreground
#[inline]
pub fn on(self, background: impl Into<Color>) -> crate::Style {
Expand Down
Loading