xml2gpui_macros/
lib.rs

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
13// Updated to hold vectors of vectors to represent groups of tailwind classes
14struct 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            // Correctly parse the bracketed group
33            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            // Optionally consume a comma after the group
39            let _ = input.parse::<Token![,]>().ok();
40        }
41
42        // Consume the arrow before the default case
43        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            // Replace "-" to "_" and "/" to "_" in class name
68            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            // Fonts are little bit different
79            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}