Skip to content
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

Implement FromStr method for Rgb<S, u8> #157

Merged
merged 1 commit into from Jan 18, 2020
Merged

Conversation

mswift42
Copy link
Contributor

This enables converting hex colors like '#00ddff' or '#fff' to Rgb.

this closes #148.

Copy link
Owner

@Ogeon Ogeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this! There are just a few points that will need to be changes, primarily to maintain the #[no_std] support, but also a couple of other details.

@@ -2,6 +2,7 @@ use core::any::TypeId;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
use std::str::FromStr;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This need to be changed to get it from core instead, for the #[no_std] support to work.

Suggested change
use std::str::FromStr;
use core::str::FromStr;

@@ -953,14 +954,50 @@ where
}
}

impl FromStr for Rgb {
Copy link
Owner

@Ogeon Ogeon Dec 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this may be the more convenient option for many cases, I would still prefer to implement it for Rgb<Srgb, u8>. I think that's less surprising than only having it for f32. Ideally both would be supported, but then what about if it's a u16 hex string? Or even larger? So, in the end I think it's better to start small, as you have, but for Rgb<Srgb, u8>.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking some more about this, I don't think there's any reason to limit it to only Srgb.

Suggested change
impl FromStr for Rgb {
impl<S: RgbStandard> FromStr for Rgb<S, u8> {

It would also be great to have one for Alpha<Rgb<S, u8>>.

@@ -953,14 +954,50 @@ where
}
}

impl FromStr for Rgb {
type Err = Box<dyn std::error::Error>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have to change to something that isn't Boxed, due to the #[no_std] support requirement. A simple error type like

enum FromHexError {
    ParseIntError(ParseIntError),
    HexFormatError(&'static str), // Like "invalid hex code length" or "unexpected hex code prefix"
}

with all the impls it would need, would do the trick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I struggle with this. Sorry, I am fairly new to rust, but there is no Error type in core only in std.

Do you have a suggestion how to do this ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok. No worries! :) You are right, it seems like the Error trait is only in std. In that case we will have to make use of conditional compilation if we want to use it (it's good practice to implement Error, so we should). #[no_std] is a particularly prickly feature. If we use std anywhere, unconditionally, it's all out the window, and it's hard to notice unless it's used on a platform that doesn't support the std library. I have set up a CI test to detect it, and that's what made this PR fail the tests.

For this case we have to give up the convenience of having the boxed trait in either case, since Box is also not available, so the first step would be to add an enum like the one I suggested and change this line like this:

Suggested change
type Err = Box<dyn std::error::Error>;
type Err = FromHexError;

That way, the parsing will work both with and without std.

Then the FromHexError type will need some traits (I would look at ParseIntError for inspiration), including Error. That's where we will have to check if the library user is allowing the std library:

#[cfg(feature = "std")] // Checks if the "std" feature from Cargo.toml is enabled
impl std::error::Error for FromHexError {
    // `std` is safe to use in this `impl` item, because of the `cfg` attribute.
    // It will not be included at all if the "std" feature is disabled.
    //
    // ...
}

The same goes for any other trait that is not in core.

impl FromStr for Rgb {
type Err = Box<dyn std::error::Error>;

// parses a color hex code of format '#ff00bb' or '#abc' into a
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It mentions "#" being part of the format, but never checks that it's there. Better to be strict and relax the rules later, rather than the opposite. Besides, it would also be technically possible to support strings without the "#". Maybe that should be added too?

Also, capital "P" in "parses". 🙂

@mswift42
Copy link
Contributor Author

mswift42 commented Dec 8, 2019 via email

fn from_str(hex_code: &str) -> Result<Self, Self::Err> {
match hex_code.len() {
3 | 4 | 6 | 7 => {
let (red, green, blue, factor) = if hex_code.len() == 3 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just stopping by to see how it's going. Looks promising! I would just recommend to either drop this if-else chain and only use the match, or the other way around. Both would work. As it is now, you are checking the length twice, at least. 🙂

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An additional idea, to halve the amount of cases, is to check for the existence of # at the beginning first. If it's there, you slice it away. That way, you are down to only two possible lengths when parsing the components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've done it this way. (December is a really busy month, :)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it is. But don't feel like you have to rush it or anything. :)

@Ogeon
Copy link
Owner

Ogeon commented Dec 20, 2019

I think this looks really good! The only addition I think would be good to have would be to implement Error for FromHexError, as we talked about. 🙂 After that I'm definitely prepared to merge this, if you are happy with it. But, once again, take your time and let me know if anything is still unclear.

Comment on lines 979 to 983
write!(f, "ParseIntError: {}", e)
}
FromHexError::HexFormatError(s) => {
write!(f, "HexFormatError: {}, please use format '#fff', 'fff', '#ffffff' or\
'ffffff'.", s)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to skip the ParseIntError: and HexFormatError: prefixes. I don't think they will be that interesting to someone who would read the error message, to be honest. They just want to know what went wrong and how to fix it. 🙂

@Ogeon
Copy link
Owner

Ogeon commented Jan 5, 2020

This looks really nice! Well done! There's just that detail with the error messages. Also, I think the common convention in general is to just have a plain sentence without an initial capital letter, so there's that too.

@mswift42
Copy link
Contributor Author

mswift42 commented Jan 5, 2020

Ok, done, and a happy new year to you 🍾

@Ogeon
Copy link
Owner

Ogeon commented Jan 5, 2020

Thanks and happy new year to you too! Oh, I forgot, you may want to squash those commits. Just to keep the history a bit shorter.

@Ogeon
Copy link
Owner

Ogeon commented Jan 18, 2020

Sorry, I didn't see the notification for the squash. It must have gotten lost somewhere...

Thanks you!

bors r+

bors bot added a commit that referenced this pull request Jan 18, 2020
157: implement FromStr method for Srgb<f32>. r=Ogeon a=mswift42

This enables converting hex colors like '#00ddff' or '#fff' to Rgb. 

this closes #148.

Co-authored-by: mswift42 <martin.haesler@gmail.com>
@bors
Copy link
Contributor

bors bot commented Jan 18, 2020

Build succeeded

@bors bors bot merged commit 4b4e0a4 into Ogeon:master Jan 18, 2020
@Ogeon Ogeon changed the title implement FromStr method for Srgb<f32>. Implement FromStr method for Srgb<f32> Jul 4, 2021
@Ogeon Ogeon changed the title Implement FromStr method for Srgb<f32> Implement FromStr method for Rgb<S, u8> Jul 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Working with Hex codes
2 participants