1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{
6 parse::{Parse, ParseStream, Result},
7 parse_macro_input,
8 punctuated::Punctuated,
9 token::Comma,
10 Expr, Ident, LitStr, Token,
11};
12
13struct TailwindToGpuiInput {
15 element_name: Ident,
16 class_name: Ident,
17 tailwind_class_groups: Vec<Vec<LitStr>>,
18 default_case: Box<Expr>,
19}
20
21impl Parse for TailwindToGpuiInput {
22 fn parse(input: ParseStream) -> Result<Self> {
23 let element_name: Ident = input.parse()?;
24 input.parse::<Token![,]>()?;
25 let class_name: Ident = input.parse()?;
26 input.parse::<Token![,]>()?;
27
28 let mut tailwind_class_groups = Vec::new();
29
30 while !input.peek(Token![_]) {
31 let content;
32 syn::bracketed!(content in input);
34 let classes = Punctuated::<LitStr, Comma>::parse_terminated(&content)?;
35
36 tailwind_class_groups.push(classes.into_iter().collect());
37
38 let _ = input.parse::<Token![,]>().ok();
40 }
41
42 input.parse::<Token![_]>()?;
44 input.parse::<Token![=>]>()?;
45
46 let default_case: Expr = input.parse()?;
47
48 Ok(TailwindToGpuiInput {
49 element_name,
50 class_name,
51 tailwind_class_groups,
52 default_case: Box::new(default_case),
53 })
54 }
55}
56#[proc_macro]
57pub fn tailwind_to_gpui(input: TokenStream) -> TokenStream {
58 let TailwindToGpuiInput {
59 element_name,
60 class_name,
61 tailwind_class_groups,
62 default_case,
63 } = parse_macro_input!(input as TailwindToGpuiInput);
64
65 let tailwind_matches = tailwind_class_groups.iter().flat_map(|group| {
66 group.iter().map(|class| {
67 let method_name = Ident::new(
69 class
70 .value()
71 .replace("-", "_")
72 .replace("/", "_")
73 .replace(".", "p")
74 .as_str(),
75 class.span(),
76 );
77
78 if class.value().starts_with("font-") {
80 let font_weight = Ident::new(match class.value().replace("font-", "").as_str() {
81 "thin" => "THIN",
82 "extralight" => "EXTRA_LIGHT",
83 "light" => "LIGHT",
84 "normal" => "NORMAL",
85 "medium" => "MEDIUM",
86 "semibold" => "SEMIBOLD",
87 "bold" => "BOLD",
88 "extrabold" => "EXTRA_BOLD",
89 "black" => "BLACK",
90 _ => "NORMAL",
91 }, class.span());
92 quote! {
93 #class => #element_name.font_weight(FontWeight::#font_weight),
94 }
95 } else {
96 quote! {
97 #class => #element_name.#method_name(),
98 }
99 }
100 })
101 });
102
103 let expanded = quote! {
104 match #class_name {
105 #(#tailwind_matches)*
106 _ => #default_case
107 }
108 };
109
110 TokenStream::from(expanded)
111}