-
Notifications
You must be signed in to change notification settings - Fork 87
Allocate a lot less #202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Allocate a lot less #202
Changes from 26 commits
7b06a7b
1ad8d71
82fa2c2
19faeba
19eec9b
0c6f6c3
d540f3d
b217771
0ae0b78
57c53ea
ecd6355
098fa4f
34eaf1a
baf9453
1e04c4d
962a9ed
d5cd420
3e7932b
d2d4d93
383397e
362f667
2cd3492
432ebe7
aae4077
2497bd0
a122bea
5270a3c
a561d42
b0778d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| use std::{borrow::Cow, cmp, env, str::FromStr}; | ||
| use core::{cmp, fmt::Write}; | ||
| use std::{borrow::Cow, convert::Into, env, str::FromStr}; | ||
| use Color::{ | ||
| AnsiColor, Black, Blue, BrightBlack, BrightBlue, BrightCyan, BrightGreen, BrightMagenta, | ||
| BrightRed, BrightWhite, BrightYellow, Cyan, Green, Magenta, Red, TrueColor, White, Yellow, | ||
|
|
@@ -34,58 +35,120 @@ fn truecolor_support() -> bool { | |
|
|
||
| #[allow(missing_docs)] | ||
| impl Color { | ||
| /// Converts the foreground [`Color`] into a [`&'static str`](str) | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// If the color is a `TrueColor` or `AnsiColor`, it will return [`NotStaticColor`] as an Error | ||
| const fn to_fg_static_str(self) -> Option<&'static str> { | ||
| match self { | ||
| Self::Black => Some("30"), | ||
| Self::Red => Some("31"), | ||
| Self::Green => Some("32"), | ||
| Self::Yellow => Some("33"), | ||
| Self::Blue => Some("34"), | ||
| Self::Magenta => Some("35"), | ||
| Self::Cyan => Some("36"), | ||
| Self::White => Some("37"), | ||
| Self::BrightBlack => Some("90"), | ||
| Self::BrightRed => Some("91"), | ||
| Self::BrightGreen => Some("92"), | ||
| Self::BrightYellow => Some("93"), | ||
| Self::BrightBlue => Some("94"), | ||
| Self::BrightMagenta => Some("95"), | ||
| Self::BrightCyan => Some("96"), | ||
| Self::BrightWhite => Some("97"), | ||
| Self::TrueColor { .. } | Self::AnsiColor(..) => None, | ||
| } | ||
| } | ||
|
|
||
| /// Write [`to_fg_str`](Self::to_fg_str) to the given [`Formatter`](core::fmt::Formatter) without allocating | ||
| pub(crate) fn to_fg_write(self, f: &mut dyn core::fmt::Write) -> Result<(), core::fmt::Error> { | ||
| match self.to_fg_static_str() { | ||
| Some(s) => f.write_str(s), | ||
| None => match self { | ||
| Black | Red | Green | Yellow | Blue | Magenta | Cyan | White | BrightBlack | ||
| | BrightRed | BrightGreen | BrightYellow | BrightBlue | BrightMagenta | ||
| | BrightCyan | BrightWhite => unreachable!(), | ||
| AnsiColor(code) => write!(f, "38;5;{code}"), | ||
| TrueColor { r, g, b } if !truecolor_support() => Self::TrueColor { r, g, b } | ||
| .closest_color_euclidean() | ||
| .to_fg_write(f), | ||
| TrueColor { r, g, b } => write!(f, "38;2;{r};{g};{b}"), | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| #[must_use] | ||
| pub fn to_fg_str(&self) -> Cow<'static, str> { | ||
| match *self { | ||
| Self::Black => "30".into(), | ||
| Self::Red => "31".into(), | ||
| Self::Green => "32".into(), | ||
| Self::Yellow => "33".into(), | ||
| Self::Blue => "34".into(), | ||
| Self::Magenta => "35".into(), | ||
| Self::Cyan => "36".into(), | ||
| Self::White => "37".into(), | ||
| Self::BrightBlack => "90".into(), | ||
| Self::BrightRed => "91".into(), | ||
| Self::BrightGreen => "92".into(), | ||
| Self::BrightYellow => "93".into(), | ||
| Self::BrightBlue => "94".into(), | ||
| Self::BrightMagenta => "95".into(), | ||
| Self::BrightCyan => "96".into(), | ||
| Self::BrightWhite => "97".into(), | ||
| Self::TrueColor { .. } if !truecolor_support() => { | ||
| self.closest_color_euclidean().to_fg_str() | ||
| } | ||
| Self::AnsiColor(code) => format!("38;5;{code}").into(), | ||
| Self::TrueColor { r, g, b } => format!("38;2;{r};{g};{b}").into(), | ||
| self.to_fg_static_str().map_or_else( | ||
| || { | ||
| // Not static, we can use the default formatter | ||
| let mut buf = String::new(); | ||
| // We write into a String, we do not expect an error. | ||
| let _ = self.to_fg_write(&mut buf); | ||
| buf.into() | ||
| }, | ||
| Into::into, | ||
| ) | ||
| } | ||
|
|
||
| /// Converts the background [`Color`] into a [`&'static str`](str) | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// If the color is a `TrueColor` or `AnsiColor`, it will return [`NotStaticColor`] as an Error | ||
| const fn to_bg_static_str(self) -> Option<&'static str> { | ||
| match self { | ||
| Self::Black => Some("40"), | ||
| Self::Red => Some("41"), | ||
| Self::Green => Some("42"), | ||
| Self::Yellow => Some("43"), | ||
| Self::Blue => Some("44"), | ||
| Self::Magenta => Some("45"), | ||
| Self::Cyan => Some("46"), | ||
| Self::White => Some("47"), | ||
| Self::BrightBlack => Some("100"), | ||
| Self::BrightRed => Some("101"), | ||
| Self::BrightGreen => Some("102"), | ||
| Self::BrightYellow => Some("103"), | ||
| Self::BrightBlue => Some("104"), | ||
| Self::BrightMagenta => Some("105"), | ||
| Self::BrightCyan => Some("106"), | ||
| Self::BrightWhite => Some("107"), | ||
| Self::TrueColor { .. } | Self::AnsiColor(..) => None, | ||
| } | ||
| } | ||
|
|
||
| /// Write [`to_bg_str`](Self::to_fg_str) to the given [`Formatter`](core::fmt::Formatter) without allocating | ||
| pub(crate) fn to_bg_write(self, f: &mut dyn Write) -> Result<(), core::fmt::Error> { | ||
| match self.to_bg_static_str() { | ||
| Some(s) => f.write_str(s), | ||
| None => match self { | ||
| Black | Red | Green | Yellow | Blue | Magenta | Cyan | White | BrightBlack | ||
| | BrightRed | BrightGreen | BrightYellow | BrightBlue | BrightMagenta | ||
| | BrightCyan | BrightWhite => unreachable!(), | ||
| AnsiColor(code) => write!(f, "48;5;{code}"), | ||
| TrueColor { r, g, b } if !truecolor_support() => Self::TrueColor { r, g, b } | ||
| .closest_color_euclidean() | ||
| .to_fg_write(f), | ||
| TrueColor { r, g, b } => write!(f, "48;2;{r};{g};{b}"), | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| #[must_use] | ||
| pub fn to_bg_str(&self) -> Cow<'static, str> { | ||
| match *self { | ||
| Self::Black => "40".into(), | ||
| Self::Red => "41".into(), | ||
| Self::Green => "42".into(), | ||
| Self::Yellow => "43".into(), | ||
| Self::Blue => "44".into(), | ||
| Self::Magenta => "45".into(), | ||
| Self::Cyan => "46".into(), | ||
| Self::White => "47".into(), | ||
| Self::BrightBlack => "100".into(), | ||
| Self::BrightRed => "101".into(), | ||
| Self::BrightGreen => "102".into(), | ||
| Self::BrightYellow => "103".into(), | ||
| Self::BrightBlue => "104".into(), | ||
| Self::BrightMagenta => "105".into(), | ||
| Self::BrightCyan => "106".into(), | ||
| Self::BrightWhite => "107".into(), | ||
| Self::AnsiColor(code) => format!("48;5;{code}").into(), | ||
| Self::TrueColor { .. } if !truecolor_support() => { | ||
| self.closest_color_euclidean().to_bg_str() | ||
| } | ||
| Self::TrueColor { r, g, b } => format!("48;2;{r};{g};{b}").into(), | ||
| } | ||
| self.to_bg_static_str().map_or_else( | ||
| || { | ||
| // Not static, we can use the default formatter | ||
| let mut buf = String::new(); | ||
| // Writing into a String should be always valid. | ||
| let _ = self.to_bg_write(&mut buf); | ||
| buf.into() | ||
| }, | ||
| Into::into, | ||
| ) | ||
| } | ||
|
|
||
| /// Gets the closest plain color to the `TrueColor` | ||
|
|
@@ -96,7 +159,7 @@ impl Color { | |
| g: g1, | ||
| b: b1, | ||
| } => { | ||
| let colors = vec![ | ||
| let colors = [ | ||
| Black, | ||
| Red, | ||
| Green, | ||
|
|
@@ -262,8 +325,77 @@ fn parse_hex(s: &str) -> Option<Color> { | |
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
|
|
||
| pub use super::*; | ||
|
|
||
| #[test] | ||
| /// Test that `fmt` and `to_str` are the same | ||
| fn fmt_and_to_str_same() { | ||
|
Comment on lines
+331
to
+333
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this test touches on something interesting. the But that's probably for a separate PR. The reason I bring it up is that, if I'm reading this correctly, what this tests is "did we copy and paste the logic between the two functions?" I'm not a fan of that type of test. Rather a test like this: #[test]
fn are_some() {
// Do foo and bar return the same value?
assert_eq!(x.foo(), x.bar());
}I'd rather have tests like this: #[test]
fn test_foo() {
assert_eq!(x.foo(), some_expected_value);
}
#[test]
fn test_bar() {
assert_eq!(x.bar(), some_expected_value);
}That might look redundant, but redundancy is OK in tests IMO.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I reworked the code, so that there should be as little code duplication as possible. The requirement is, that formatting and the to_str method should always have the same output.
Another option would be to just write a doc comment and hope that everyone reads it. |
||
| use core::fmt::Display; | ||
| use itertools::Itertools; | ||
| use Color::*; | ||
|
|
||
| /// Helper structs to call the method | ||
| struct FmtFgWrapper(Color); | ||
| impl Display for FmtFgWrapper { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| self.0.to_fg_write(f) | ||
| } | ||
| } | ||
| struct FmtBgWrapper(Color); | ||
| impl Display for FmtBgWrapper { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| self.0.to_bg_write(f) | ||
| } | ||
| } | ||
|
|
||
| // Actual test | ||
|
|
||
| let colors = [ | ||
| Black, | ||
| Red, | ||
| Green, | ||
| Yellow, | ||
| Blue, | ||
| Magenta, | ||
| Cyan, | ||
| White, | ||
| BrightBlack, | ||
| BrightRed, | ||
| BrightGreen, | ||
| BrightYellow, | ||
| BrightBlue, | ||
| BrightMagenta, | ||
| BrightCyan, | ||
| BrightWhite, | ||
| ] | ||
| .into_iter() | ||
| .chain( | ||
| // Iterator over TrueColors | ||
| // r g b | ||
| // 0 0 0 | ||
| // 0 0 1 | ||
| // 0 0 2 | ||
| // 0 0 3 | ||
| // 0 1 0 | ||
| // .. | ||
| // 3 3 3 | ||
| (0..4) | ||
| .combinations_with_replacement(3) | ||
| .map(|rgb| Color::TrueColor { | ||
| r: rgb[0], | ||
| g: rgb[1], | ||
| b: rgb[2], | ||
| }), | ||
| ) | ||
| .chain((0..4).map(Color::AnsiColor)); | ||
|
|
||
| for color in colors { | ||
| assert_eq!(color.to_fg_str(), FmtFgWrapper(color).to_string()); | ||
| assert_eq!(color.to_bg_str(), FmtBgWrapper(color).to_string()); | ||
| } | ||
| } | ||
|
|
||
| mod from_str { | ||
| pub use super::*; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need
dynhere? I think we can use a generic.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
The core::fmt::Formatter wraps a &dyn Writer. I thought the implementation was to just deference the writer. But instead it implements Write itself and just delegates the calls to the wrapped Writer in Formatter.