usual_proc/
lib.rs

1// #![feature(proc_macro_diagnostic)]
2
3use proc_macro::TokenStream;
4use proc_macro2::{
5    Ident, Literal, Span as Span2, TokenStream as TokenStream2, TokenTree as TokenTree2,
6};
7use quote::quote;
8use regex::{Match, Regex};
9use syn::{parse_macro_input, LitStr};
10
11#[proc_macro]
12pub fn query(items: TokenStream) -> TokenStream {
13    let items = proc_macro2::TokenStream::from(items);
14    let mut item_iter = items.clone().into_iter();
15
16    let text = match item_iter.next() {
17        Some(TokenTree2::Literal(literal)) => {
18            let stream = TokenStream2::from(TokenTree2::Literal(literal)).into();
19            parse_macro_input!(stream as LitStr).value()
20        }
21        _ => panic!("The first argument of `query!` must be a string literal."),
22    };
23
24    let re = Regex::new(r"\{([^\}:\s]+)(?:::([\w,]+))?\s*(?:as (\w+))?\}").unwrap();
25
26    let mut matches = re.find_iter(&text).into_iter().collect::<Vec<Match>>();
27    matches.reverse();
28    let mut output_string = text.clone();
29    let mut value_injections = vec![];
30
31    for m in matches {
32        for cap in re.captures_iter(m.as_str()) {
33            let mut cap_iter = cap.iter();
34            let _full = cap_iter.next().unwrap().unwrap().as_str();
35
36            let model_name = cap_iter.next().unwrap().unwrap().as_str();
37            let field_names = match cap_iter.next() {
38                Some(Some(inner_match)) => {
39                    let value = inner_match.as_str();
40
41                    value.split(",").collect::<Vec<&str>>()
42                }
43                _ => vec![],
44            };
45
46            let table_name = match cap_iter.next() {
47                Some(Some(inner_match)) => Some(inner_match.as_str()),
48                _ => None,
49            };
50
51            if field_names.len() > 0 {
52                let mut fields = vec![];
53
54                let _ = &field_names.into_iter().for_each(|f| {
55                    let model_ident = Ident::new(model_name, Span2::call_site());
56                    let column_literal = Literal::string(f);
57
58                    match table_name {
59                        Some(table_name) => {
60                            let table_name = Literal::string(table_name);
61                            fields.push(quote! { <#model_ident>::column_with_prefix_and_table(#column_literal, Some(<#model_ident>::prefix()), Some(#table_name)) });
62                        }
63                        None => {
64                            fields.push(quote! { <#model_ident>::column_with_prefix_and_table(#column_literal, Some(<#model_ident>::prefix()), None) });
65                        }
66                    }
67                });
68
69                // Reverse the fields order since we're going back to front for the matches -- they'll get switched when we reverse the whole array.
70                fields.reverse();
71                value_injections.append(&mut fields);
72
73                output_string.replace_range(
74                    m.range(),
75                    &std::iter::repeat("{}")
76                        .take(value_injections.len())
77                        .collect::<Vec<&str>>()
78                        .join(", "),
79                );
80            } else {
81                let ident = Ident::new(model_name, Span2::call_site());
82                let initial_injection_count = value_injections.len();
83
84                match table_name {
85                    Some(table_name) => {
86                        let table_name = Literal::string(table_name);
87                        value_injections.push(quote! { <#ident>::columns_with_table(#table_name) });
88                    }
89                    None => {
90                        value_injections.push(quote! { <#ident>::columns() });
91                    }
92                }
93
94                output_string.replace_range(
95                    m.range(),
96                    &std::iter::repeat("{}")
97                        .take(value_injections.len() - initial_injection_count)
98                        .collect::<Vec<&str>>()
99                        .join(", "),
100                );
101            }
102        }
103    }
104
105    value_injections.reverse();
106
107    let gen = quote! {
108        format!(#output_string, #( #value_injections,)*)
109    };
110
111    // proc_macro::Span::call_site()
112    //     .note("Thruster code output")
113    //     .note(gen.to_string())
114    //     .emit();
115
116    gen.into()
117}
118
119struct Field {
120    name: String,
121    ty: String,
122}
123
124#[proc_macro]
125pub fn partial(items: TokenStream) -> TokenStream {
126    let items = proc_macro2::TokenStream::from(items);
127    let mut item_iter = items.clone().into_iter();
128
129    let model = match item_iter.next() {
130        Some(TokenTree2::Ident(ident)) => ident,
131        _ => panic!("The first argument of `query!` must be a string literal."),
132    };
133    let model_name = model.to_string();
134    let partial_ident = Ident::new(&format!("Partial{}", model.to_string()), Span2::call_site());
135    let partial_ident_name = partial_ident.to_string();
136
137    // Accept more arguments -- really not sure if we need this.
138    let mut fields = vec![];
139
140    item_iter.next(); // skip punctuation
141
142    while let Some(ident) = item_iter.next() {
143        let field_name = match ident {
144            TokenTree2::Ident(i) => i,
145            _ => panic!("All arguments after the first must be identifiers."),
146        };
147
148        match item_iter.next() {
149            Some(TokenTree2::Ident(i)) => if i.to_string() != "as" { panic!("A field identifier must be followed by `as Type`, for example, `content as String`.") },
150            _ => panic!("A field identifier must be followed by `as Type`, for example, `content as String`."),
151        }; // skip punctuation
152
153        let ty = match item_iter.next() {
154            Some(TokenTree2::Ident(i)) => {
155                i
156            },
157            _ => panic!("A field identifier must be followed by `as Type`, for example, `content as String`."),
158        };
159
160        fields.push(Field {
161            name: field_name.to_string(),
162            ty: ty.to_string(),
163        });
164
165        item_iter.next(); // skip punctuation
166    }
167
168    let field_declarations = fields
169        .iter()
170        .map(|f| {
171            let field_name = Ident::new(&f.name, Span2::call_site());
172            let field_type = Ident::new(&f.ty, Span2::call_site());
173            quote! {
174                pub #field_name: #field_type
175            }
176        })
177        .collect::<Vec<_>>();
178
179    let field_initializers = fields
180        .iter()
181        .map(|f| {
182            let field_name = Ident::new(&f.name, Span2::call_site());
183            let field_key = field_name.to_string();
184            quote! {
185                #field_name: row.try_get(format!("{}{}", Self::prefix(), #field_key).as_str())
186                .expect(&format!("You messed up while trying to get {} ({}{}) from {}", #field_key, Self::prefix(), #field_key, #partial_ident_name))
187            }
188        })
189        .collect::<Vec<_>>();
190
191    let field_keys = fields.iter().map(|f| f.name.clone()).collect::<Vec<_>>();
192
193    let gen = quote! {
194        |r| {
195            #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
196            #[serde(rename_all = "camelCase")]
197            struct #partial_ident {
198                #(
199                    #field_declarations
200                ),*
201            };
202
203            impl Model for #partial_ident {
204                fn prefix() -> &'static str {
205                    concat!(#model_name, "__")
206                }
207
208                fn from_row_starting_index(_index: usize, row: &impl TryGetRow) -> Self {
209                    #partial_ident {
210                        #( #field_initializers ),*
211                    }
212                }
213
214                fn columns_list() -> Vec<&'static str> {
215                    vec![#( #field_keys ),*]
216                }
217            }
218
219            #partial_ident::from_row_starting_index(0, r)
220        }
221    };
222
223    // proc_macro::Span::call_site()
224    //     .note("Thruster code output")
225    //     .note(gen.to_string())
226    //     .emit();
227
228    gen.into()
229}
230
231#[proc_macro_derive(UsualModel, attributes(unusual))]
232pub fn usual_model_macro_derive(items: TokenStream) -> TokenStream {
233    let ast: syn::DeriveInput = syn::parse(items).unwrap();
234
235    let name = Ident::from(ast.ident);
236    let (fields, skipped) = match ast.data {
237        syn::Data::Struct(data_struct) => match data_struct.fields {
238            syn::Fields::Named(named_fields) => {
239                let (skipped, fields): (Vec<syn::Field>, Vec<syn::Field>) =
240                    named_fields.named.into_iter().partition(|field| {
241                        !field.attrs.iter().any(|attr| {
242                            attr.path.segments.first().unwrap().ident.to_string() == "unusual"
243                        })
244                    });
245
246                (
247                    skipped
248                        .into_iter()
249                        .map(|field| Ident::from(field.ident.unwrap()))
250                        .collect::<Vec<Ident>>(),
251                    fields
252                        .into_iter()
253                        .map(|field| Ident::from(field.ident.unwrap()))
254                        .collect::<Vec<Ident>>(),
255                )
256            }
257            _ => panic!("Can only derive named fields of struct"),
258        },
259        _ => panic!("Can only derive fields of struct"),
260    };
261
262    let gen = quote! {
263        impl Model for #name {
264            fn from_row_starting_index(_index: usize, row: &impl TryGetRow) -> Self {
265              #name {
266                #(
267                    #fields: row.try_get(format!("{}{}", Self::prefix(), stringify!(#fields)).as_str())
268                    .expect(&format!("You messed up while trying to get {} ({}{}) from {}", stringify!(#fields), Self::prefix(), stringify!(#fields), stringify!(#name)))
269                ),*,
270                #(
271                    #skipped: Default::default()
272                ),*
273            }
274        }
275
276            fn from_row_with_prefix(prefix: &str, row: &impl TryGetRow) -> Self {
277              #name {
278                  #(
279                      #fields: row.try_get(format!("{}{}", prefix, stringify!(#fields)).as_str())
280                          .expect(&format!("You messed up while trying to get {} ({}{}) from {}", stringify!(#fields), prefix, stringify!(#fields), stringify!(#name)))
281                  ),*,
282                  #(
283                      #skipped: Default::default()
284                  ),*
285              }
286            }
287
288            fn columns_list() -> Vec<&'static str> {
289                vec![#(
290                    stringify!(#fields)
291                ),*]
292            }
293
294            fn prefix() -> &'static str {
295              concat!(stringify!(#name), "__")
296            }
297        }
298    };
299
300    // proc_macro::Span::call_site()
301    //     .note("Thruster code output")
302    //     .note(gen.to_string())
303    //     .emit();
304
305    gen.into()
306}