1use 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 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 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 let mut fields = vec![];
139
140 item_iter.next(); 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 }; 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(); }
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 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 gen.into()
306}