liquid_derive/filter/
display.rs1use proc_macro2::{Ident, Span, TokenStream};
2use quote::{quote, ToTokens};
3use syn::spanned::Spanned as _;
4use syn::{Attribute, Data, DeriveInput, Error, Expr, ExprLit, Generics, Lit, Meta, Result};
5
6use crate::helpers::AssignOnce;
7
8struct FilterStruct<'a> {
11 name: &'a Ident,
12 filter_name: String,
13 parameters: Option<Parameters<'a>>,
14 generics: &'a Generics,
15}
16
17enum Parameters<'a> {
19 Ident(&'a Ident),
20 Pos(usize),
21}
22
23impl<'a> Parameters<'a> {
24 fn new(ident: Option<&'a Ident>, pos: usize) -> Self {
28 match ident {
29 Some(ident) => Parameters::Ident(ident),
30 None => Parameters::Pos(pos),
31 }
32 }
33}
34
35impl ToTokens for Parameters<'_> {
36 fn to_tokens(&self, tokens: &mut TokenStream) {
37 match self {
38 Parameters::Ident(ident) => ident.to_tokens(tokens),
39 Parameters::Pos(pos) => pos.to_tokens(tokens),
40 }
41 }
42}
43
44impl<'a> FilterStruct<'a> {
45 fn generate_impl(&self, trait_name: TokenStream) -> TokenStream {
48 let name = &self.name;
49 let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
50 quote! {
51 impl #impl_generics #trait_name for #name #ty_generics #where_clause
52 }
53 }
54
55 fn parse_attrs(attrs: &[Attribute]) -> Result<String> {
57 let mut evaluated_attrs = attrs.iter().filter(|attr| attr.path().is_ident("name"));
58
59 match (evaluated_attrs.next(), evaluated_attrs.next()) {
60 (Some(attr), None) => Self::parse_name_attr(attr),
61
62 (_, Some(attr)) => Err(Error::new_spanned(
63 attr,
64 "Found multiple definitions for `name` attribute.",
65 )),
66
67 _ => Err(Error::new(
68 Span::call_site(),
69 "Cannot find `name` attribute in target struct. Have you tried adding `#[name = \"...\"]`?",
70 )),
71 }
72 }
73
74 fn parse_name_attr(attr: &Attribute) -> Result<String> {
76 let meta = &attr.meta;
77 if let Meta::NameValue(meta) = meta {
78 if let Expr::Lit(ExprLit {
79 lit: Lit::Str(name),
80 ..
81 }) = &meta.value
82 {
83 Ok(name.value())
84 } else {
85 Err(Error::new(attr.span(), "Expected string literal."))
86 }
87 } else {
88 Err(Error::new_spanned(
89 meta,
90 "Couldn't parse evaluated attribute. Have you tried `#[evaluated(\"...\")]`?",
91 ))
92 }
93 }
94
95 fn from_input(input: &'a DeriveInput) -> Result<Self> {
97 let DeriveInput {
98 ident,
99 generics,
100 data,
101 attrs,
102 ..
103 } = &input;
104 let mut parameters = AssignOnce::Unset;
105
106 let fields = match data {
107 Data::Struct(data) => &data.fields,
108 Data::Enum(data) => {
109 return Err(Error::new_spanned(
110 data.enum_token,
111 "Filters cannot be `enum`s.",
112 ));
113 }
114 Data::Union(data) => {
115 return Err(Error::new_spanned(
116 data.union_token,
117 "Filters cannot be `union`s.",
118 ));
119 }
120 };
121
122 let marked = fields.iter().enumerate().filter(|(_, field)| {
123 field
124 .attrs
125 .iter()
126 .any(|attr| attr.path().is_ident("parameters"))
127 });
128
129 for (i, field) in marked {
130 let params = Parameters::new(field.ident.as_ref(), i);
131 parameters.set(params, || Error::new_spanned(
132 field,
133 "A previous field was already marked as `parameters`. Only one field can be marked as so.",
134 ))?;
135 }
136
137 let name = ident;
138 let filter_name = Self::parse_attrs(attrs)?;
139 let parameters = parameters.into_option();
140
141 Ok(Self {
142 name,
143 filter_name,
144 parameters,
145 generics,
146 })
147 }
148}
149
150fn generate_impl_display(filter: &FilterStruct<'_>) -> TokenStream {
152 let FilterStruct {
153 filter_name,
154 parameters,
155 ..
156 } = &filter;
157
158 let impl_display = filter.generate_impl(quote! { ::std::fmt::Display });
159
160 if let Some(parameters) = parameters {
161 quote! {
162 #impl_display {
163 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
164 ::std::write!(f, "{} : {}", #filter_name, &self.#parameters)
165 }
166 }
167 }
168 } else {
169 quote! {
170 #impl_display {
171 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
172 ::std::write!(f, "{}", #filter_name)
173 }
174 }
175 }
176 }
177}
178
179pub(crate) fn derive(input: &DeriveInput) -> TokenStream {
180 let filter = match FilterStruct::from_input(input) {
181 Ok(filter) => filter,
182 Err(err) => return err.to_compile_error(),
183 };
184
185 generate_impl_display(&filter)
186}