1mod utils;
2
3use crate::utils::{DaisyColorAttr, ExprOrString, Mode, PostProc};
4use darling::{FromDeriveInput, FromField, ast::Data};
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{DeriveInput, Ident};
8
9#[derive(FromField)]
10#[darling(attributes(html_view))]
11struct HtmlViewField {
12 ident: Option<Ident>,
13 #[darling(default)]
14 alias: Option<String>,
15 #[darling(default)]
16 value: Option<ExprOrString>,
17 #[darling(default)]
18 row_class: Option<String>,
19 #[darling(default)]
20 value_class: Option<String>,
21}
22
23#[derive(FromDeriveInput)]
24#[darling(attributes(html_view), supports(struct_named))]
25struct HtmlViewInput {
26 ident: Ident,
27 data: Data<(), HtmlViewField>,
28 #[darling(default)]
29 mode: Mode,
30 #[darling(default)]
31 title: Option<ExprOrString>,
32 #[darling(default)]
33 color: Option<DaisyColorAttr>,
34 #[darling(default)]
35 class: Option<ExprOrString>,
36 #[darling(default)]
37 mode_class: Option<ExprOrString>,
38 #[darling(default)]
39 postproc: PostProc,
40}
41
42#[proc_macro_derive(HtmlView, attributes(html_view))]
43pub fn derive_html_view(input: TokenStream) -> TokenStream {
44 let input = syn::parse_macro_input!(input as DeriveInput);
45 let parsed = match HtmlViewInput::from_derive_input(&input) {
46 Ok(v) => v,
47 Err(e) => return e.write_errors().into(),
48 };
49 let mode = parsed.mode;
50
51 let fields = parsed.data.take_struct().expect("expected named struct");
53 let field_items = fields.into_iter().map(|o| {
54 let field_ident = o.ident.unwrap();
55 let key = o.alias.unwrap_or_else(|| field_ident.to_string());
56 let value = match o.value {
57 Some(ExprOrString(expr)) => quote! { #expr },
58 None => quote! { self.#field_ident.html_content() },
59 };
60 let row_class = o.row_class.unwrap_or_else(|| "p-1".to_string());
61 let value_class_call = o.value_class.map(|x| quote! { .class(#x) });
62 match mode {
63 Mode::List => quote! {
64 .add(
65 html_list_row(
66 div().class("font-bold").add(#key),
67 div()#value_class_call.add(#value)
68 )
69 .add_class(#row_class)
70 )
71 },
72 Mode::Table => quote! {
73 .add(
74 tr()
75 .add(th().add(#key))
76 .add(td().add(#value))
77 )
78 },
79 Mode::TableRight => quote! {
80 .add(
81 tr()
82 .add(th().class("text-right").add(#key))
83 .add(td().add(#value))
84 )
85 },
86 }
87 });
88
89 let struct_name = parsed.ident;
90
91 let mode_class_call = parsed
92 .mode_class
93 .map(|ExprOrString(expr)| quote! { .add_class(#expr) });
94 let content = match mode {
95 Mode::List => quote! {
96 dc_list() #mode_class_call #(#field_items)*
97 },
98 Mode::Table | Mode::TableRight => quote! {
99 div()
100 .class("overflow-x-auto")
101 .add(dc_table() #mode_class_call .add(tbody() #(#field_items)*))
102 },
103 };
104
105 let title = match parsed.title {
106 Some(ExprOrString(expr)) => quote! { Some(#expr.as_ref()) },
107 None => quote! { None },
108 };
109 let color_call = parsed
110 .color
111 .map(|x| x.to_tokens())
112 .map(|x| quote! { .add_class(#x) });
113 let class_call = parsed
114 .class
115 .map(|ExprOrString(expr)| quote! { .add_class(#expr) });
116 let card = quote! { mk_card(#title, self.html_content()) #color_call #class_call };
117 let postproc_card = match parsed.postproc {
118 PostProc::None => quote! { #card },
119 PostProc::Flag => quote! { self.postproc(#card) },
120 PostProc::Custom(expr) => quote! { #expr(#card) },
121 };
122
123 quote! {
124 impl HtmlView for #struct_name {
125 fn html_content(&self) -> HtmlElement {
126 #content
127 }
128
129 fn html_view(&self) -> HtmlElement {
130 #postproc_card
131 }
132 }
133 }
134 .into()
135}