From 770b9cb79f675e707a1f34a43be4ab2fa40a44ff Mon Sep 17 00:00:00 2001 From: backwardspy Date: Sun, 10 Mar 2024 20:10:20 +0000 Subject: [PATCH] feat: add FromStr & deserialization support --- build.rs | 31 ++++++++++++++++-- src/lib.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 112 insertions(+), 15 deletions(-) diff --git a/build.rs b/build.rs index ee6c972..1c5cd02 100644 --- a/build.rs +++ b/build.rs @@ -60,6 +60,7 @@ fn main() -> Result<(), Box> { make_colorname_index_impl(&mut code_writer, sample_flavor)?; make_colorname_display_impl(&mut code_writer, sample_flavor)?; make_colorname_identifier_impl(&mut code_writer, sample_flavor)?; + make_colorname_fromstr_impl(&mut code_writer, sample_flavor)?; make_palette_const(&mut code_writer, &palette)?; Ok(()) @@ -99,7 +100,7 @@ fn make_flavorcolors_struct(w: &mut W, palette: &Palette) -> Result<() r#"/// All of the colors for a particular flavor of Catppuccin. /// Obtained via [`Flavor::colors`]. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct FlavorColors {{ {} }}"#, @@ -147,7 +148,7 @@ fn make_colorname_enum(w: &mut W, palette: &Palette) -> Result<(), Box w, "/// Enum of all named Catppuccin colors. Can be used to index into a [`FlavorColors`]. #[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = \"serde\", derive(serde::Serialize))] +#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))] pub enum ColorName {{ {} }}", @@ -237,6 +238,32 @@ fn make_colorname_identifier_impl( Ok(()) } +fn make_colorname_fromstr_impl( + w: &mut W, + sample_flavor: &Flavor, +) -> Result<(), Box> { + let match_arms = sample_flavor + .colors + .keys() + .map(|name| format!("{:?} => Ok(ColorName::{}),", name, titlecase(name))) + .collect::>(); + writeln!( + w, + "impl std::str::FromStr for ColorName {{ + type Err = ParseColorNameError; + + fn from_str(s: &str) -> Result {{ + match s {{ + {} + _ => Err(ParseColorNameError), + }} + }} +}}", + match_arms.join("\n ") + )?; + Ok(()) +} + fn make_palette_const(w: &mut W, palette: &Palette) -> Result<(), Box> { writeln!( w, diff --git a/src/lib.rs b/src/lib.rs index 90f035a..2a2ef2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,7 @@ //! This adds [serde](https://crates.io/crates/serde) as a dependency. //! //! Example: [`examples/serde.rs`](https://github.com/catppuccin/rust/blob/main/examples/serde.rs) -use std::{fmt, marker::PhantomData, ops::Index}; +use std::{fmt, marker::PhantomData, ops::Index, str::FromStr}; include!(concat!(env!("OUT_DIR"), "/generated_palette.rs")); @@ -69,7 +69,7 @@ include!(concat!(env!("OUT_DIR"), "/generated_palette.rs")); /// /// Can be iterated over, in which case the flavors are yielded in the canonical order: /// Latte, Frappé, Macchiato, Mocha. -#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Palette { /// The light flavor. pub latte: Flavor, @@ -83,7 +83,7 @@ pub struct Palette { /// Enum of all four flavors of Catppuccin. Can be used to index [`Palette`]. #[derive(Clone, Copy, PartialEq, Eq, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum FlavorName { /// The light flavor. Latte, @@ -105,7 +105,7 @@ pub struct FlavorIterator<'a> { /// Color represented as individual red, green, and blue channels. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Rgb { /// Red channel. pub r: u8, @@ -121,7 +121,7 @@ pub struct Hex(Rgb); /// Color represented as individual hue (0-359), saturation (0-1), and lightness (0-1) channels. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Hsl { /// Hue channel. pub h: f64, @@ -133,7 +133,7 @@ pub struct Hsl { /// A single color in the Catppuccin palette. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Color { /// The [`ColorName`] for this color. pub name: ColorName, @@ -155,7 +155,7 @@ pub struct Color { /// /// Can be iterated over, in which case the colors are yielded in order. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Flavor { /// The name of the flavor. pub name: FlavorName, @@ -211,12 +211,33 @@ impl fmt::Display for Hex { } #[cfg(feature = "serde")] -impl serde::Serialize for Hex { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) +mod _hex { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use crate::{Hex, Rgb}; + + impl Serialize for Hex { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } + } + + impl<'de> Deserialize<'de> for Hex { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let hex: String = Deserialize::deserialize(deserializer)?; + let hex: u32 = u32::from_str_radix(hex.trim_start_matches('#'), 16) + .map_err(serde::de::Error::custom)?; + let r = ((hex >> 16) & 0xff) as u8; + let g = ((hex >> 8) & 0xff) as u8; + let b = (hex & 0xff) as u8; + Ok(Self(Rgb { r, g, b })) + } } } @@ -231,6 +252,33 @@ impl fmt::Display for FlavorName { } } +/// Error type for parsing a [`FlavorName`] from a string. +#[derive(Debug, PartialEq, Eq)] +pub struct ParseFlavorNameError; +impl std::error::Error for ParseFlavorNameError {} +impl std::fmt::Display for ParseFlavorNameError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "invalid flavor identifier, expected one of: latte, frappe, frappé, macchiato, mocha" + ) + } +} + +impl FromStr for FlavorName { + type Err = ParseFlavorNameError; + + fn from_str(s: &str) -> Result { + match s { + "latte" => Ok(Self::Latte), + "frappe" | "frappé" => Ok(Self::Frappe), + "macchiato" => Ok(Self::Macchiato), + "mocha" => Ok(Self::Mocha), + _ => Err(ParseFlavorNameError), + } + } +} + impl FlavorName { /// Get the flavor's identifier; the lowercase key used to identify the flavor. /// This differs from `to_string` in that it's intended for machine usage @@ -334,6 +382,16 @@ impl<'a> IntoIterator for &'a Flavor { } } +/// Error type for parsing a [`ColorName`] from a string. +#[derive(Debug, PartialEq, Eq)] +pub struct ParseColorNameError; +impl std::error::Error for ParseColorNameError {} +impl std::fmt::Display for ParseColorNameError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid color identifier") + } +} + impl Index for Flavor { type Output = Color; @@ -379,6 +437,18 @@ impl From for css_colors::RGB { } } +#[cfg(feature = "css-colors")] +impl From for css_colors::HSL { + fn from(value: Color) -> Self { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + Self { + h: css_colors::Angle::new(value.hsl.h as u16), + s: css_colors::Ratio::from_f32(value.hsl.s as f32), + l: css_colors::Ratio::from_f32(value.hsl.l as f32), + } + } +} + #[cfg(feature = "ansi-term")] impl Color { /// Paints the given input with a color à la [ansi_term](https://docs.rs/ansi_term/latest/ansi_term/)