liquid_derive/filter/
display.rs

1use 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
8/// Struct that contains information about the `Filter` struct to generate the
9/// necessary code for `Display`.
10struct FilterStruct<'a> {
11    name: &'a Ident,
12    filter_name: String,
13    parameters: Option<Parameters<'a>>,
14    generics: &'a Generics,
15}
16
17/// The field that holds `FilterParameters`.
18enum Parameters<'a> {
19    Ident(&'a Ident),
20    Pos(usize),
21}
22
23impl<'a> Parameters<'a> {
24    /// Creates a new `Parameters` from the given `ident` (if it is
25    /// a struct with named fields) or the given position of the field
26    /// (in case of unnamed parameters).
27    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    /// Generates `impl` declaration of the given trait for the structure
46    /// represented by `self`.
47    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    /// Searches for `#[name(...)]` in order to parse `filter_name`.
56    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    /// Parses `#[name = "..."]` attribute.
75    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    /// Tries to create a new `FilterStruct` from the given `DeriveInput`
96    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
150/// Generates implementation of `Display`.
151fn 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}