radix_colors_derive/
lib.rs

1#![feature(proc_macro_expand)]
2
3use proc_macro::TokenStream;
4use quote::quote;
5use serde_json::Value;
6use syn::{LitStr, parse_macro_input};
7
8enum ParsedColor {
9  RGB(u8, u8, u8),
10  RGBA(u8, u8, u8, u8),
11  P3(f32, f32, f32),
12}
13
14#[proc_macro]
15pub fn generate_color_constants(input: TokenStream) -> TokenStream {
16  let input = input.expand_expr().expect("Failed to expand input");
17
18  let str_literal = parse_macro_input!(input as LitStr).value();
19
20  // Parse the JSON content
21  let colors: Value =
22    serde_json::from_str(&str_literal).expect("Failed to parse JSON content");
23
24  // Generate the color structs and implementations
25  let mut color_tokens = quote! {};
26
27  // Helper function to convert hex to RGB
28  fn hex_to_rgb(hex: &str) -> Option<ParsedColor> {
29    if hex.len() == 9 && hex.starts_with('#') {
30      // Handle RGBA format
31      let r = u8::from_str_radix(&hex[1..3], 16).unwrap_or(0);
32      let g = u8::from_str_radix(&hex[3..5], 16).unwrap_or(0);
33      let b = u8::from_str_radix(&hex[5..7], 16).unwrap_or(0);
34      let a = u8::from_str_radix(&hex[7..9], 16).unwrap_or(0);
35      Some(ParsedColor::RGBA(r, g, b, a))
36    } else if hex.len() == 7 && hex.starts_with('#') {
37      // Handle RGB format
38      let r = u8::from_str_radix(&hex[1..3], 16).unwrap_or(0);
39      let g = u8::from_str_radix(&hex[3..5], 16).unwrap_or(0);
40      let b = u8::from_str_radix(&hex[5..7], 16).unwrap_or(0);
41      Some(ParsedColor::RGB(r, g, b))
42    }
43    // color(display-p3 0.082 0.07 0.05)
44    else if hex.starts_with("color(display-p3") {
45      let parts: Vec<&str> = hex
46        .trim_start_matches("color(display-p3")
47        .trim_end_matches(")")
48        .split_whitespace()
49        .collect();
50      if parts.len() == 3 {
51        let r = parts[0].parse::<f32>().unwrap();
52        let g = parts[1].parse::<f32>().unwrap();
53        let b = parts[2].parse::<f32>().unwrap();
54        Some(ParsedColor::P3(r, g, b))
55      } else {
56        None
57      }
58    } else {
59      None
60    }
61  }
62
63  // Process each color palette
64  if let Value::Object(palettes) = colors {
65    for (palette_name, palette_values) in palettes {
66      if let Value::Object(colors) = palette_values {
67        // Generate constant getters for each color
68        for (color_name, color_value) in colors {
69          if let Value::String(hex) = color_value {
70            let Some(color) = hex_to_rgb(&hex) else {
71              continue;
72            };
73
74            let color_ident = syn::Ident::new(
75              &format!(
76                "{}_{}",
77                palette_name.to_uppercase(),
78                color_name.to_uppercase()
79              ),
80              proc_macro2::Span::call_site(),
81            );
82
83            match color {
84              ParsedColor::RGB(r, g, b) => {
85                color_tokens.extend(quote! {
86                  pub const #color_ident: ColorU8 = ColorU8 { r: #r, g: #g, b: #b };
87                });
88              }
89              ParsedColor::RGBA(r, g, b, a) => {
90                color_tokens.extend(quote! {
91                  pub const #color_ident: ColorU8A = ColorU8A { r: #r, g: #g, b: #b, a: #a };
92                });
93              }
94              ParsedColor::P3(r, g, b) => {
95                color_tokens.extend(quote! {
96                  pub const #color_ident: ColorP3 = ColorP3 { r: #r, g: #g, b: #b };
97                });
98              }
99            }
100          }
101        }
102      }
103    }
104  }
105
106  // Generate the final output
107  let output = quote! {
108    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
109    pub struct ColorU8 {
110      pub r: u8,
111      pub g: u8,
112      pub b: u8,
113    }
114
115    impl ColorU8 {
116      pub fn hex(&self) -> String {
117        format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
118      }
119      pub fn u8(&self) -> (u8, u8, u8) {
120        (self.r, self.g, self.b)
121      }
122    }
123
124    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
125    pub struct ColorU8A {
126      pub r: u8,
127      pub g: u8,
128      pub b: u8,
129      pub a: u8,
130    }
131
132    impl ColorU8A {
133      pub fn hex(&self) -> String {
134        format!("#{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, self.a)
135      }
136      pub fn u8(&self) -> (u8, u8, u8, u8) {
137        (self.r, self.g, self.b, self.a)
138      }
139    }
140
141    #[derive(Debug, Clone, Copy, PartialEq)]
142    pub struct ColorP3 {
143      pub r: f32,
144      pub g: f32,
145      pub b: f32,
146    }
147
148    impl ColorP3 {
149      pub fn html(&self) -> String {
150        format!("color(display-p3 {} {} {})", self.r, self.g, self.b)
151      }
152      pub fn f32(&self) -> (f32, f32, f32) {
153        (self.r, self.g, self.b)
154      }
155    }
156
157    #color_tokens
158  };
159
160  output.into()
161}