1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::parse::{Parse, ParseStream, Parser, Result};
4use syn::Expr::Struct;
5use syn::FieldValue;
6use syn::Member;
7use syn::{parse_macro_input, parse_quote, punctuated::Punctuated, ItemStruct, LitStr, Token};
8
9static ADDRESS: &str = "addresses";
10static EMAIL: &str = "email_addresses";
11static METADATA: &str = "metadata";
12static PHONE: &str = "phone_numbers";
13static NOTES: &str = "notes";
14static TAGS: &str = "tags";
15static CORE_ATTRS: [&str; 6] = [
16 "id",
17 "created_dtm",
18 "modified_dtm",
19 "inactive_dtm",
20 "expired_dtm",
21 "activity",
22];
23
24#[proc_macro_attribute]
42pub fn scaffolding_struct(args: TokenStream, input: TokenStream) -> TokenStream {
43 let mut item_struct: ItemStruct = parse_macro_input!(input as ItemStruct);
44 let attrs = parse_macro_input!(args as Args)
45 .vars
46 .iter()
47 .map(|a| a.value())
48 .collect::<Vec<_>>();
49
50 if let syn::Fields::Named(ref mut fields) = item_struct.fields {
51 fields.named.push(
53 syn::Field::parse_named
54 .parse2(quote! { pub id: String })
55 .unwrap(),
56 );
57 fields.named.push(
59 syn::Field::parse_named
60 .parse2(quote! { pub created_dtm: i64 })
61 .unwrap(),
62 );
63 fields.named.push(
65 syn::Field::parse_named
66 .parse2(quote! { pub modified_dtm: i64 })
67 .unwrap(),
68 );
69 fields.named.push(
71 syn::Field::parse_named
72 .parse2(quote! { pub inactive_dtm: i64 })
73 .unwrap(),
74 );
75 fields.named.push(
77 syn::Field::parse_named
78 .parse2(quote! { pub expired_dtm: i64 })
79 .unwrap(),
80 );
81
82 fields.named.push(
84 syn::Field::parse_named
85 .parse2(quote! { pub activity: Vec<ActivityItem> })
86 .unwrap(),
87 );
88
89 match attrs.contains(&ADDRESS.to_string()) {
91 true => {
92 fields.named.push(
94 syn::Field::parse_named
95 .parse2(quote! { pub addresses: BTreeMap<String, Address> })
96 .unwrap(),
97 );
98 }
99 false => {}
100 }
101
102 match attrs.contains(&EMAIL.to_string()) {
103 true => {
104 fields.named.push(
106 syn::Field::parse_named
107 .parse2(quote! { pub email_addresses: BTreeMap<String, EmailAddress> })
108 .unwrap(),
109 );
110 }
111 false => {}
112 }
113
114 match attrs.contains(&METADATA.to_string()) {
116 true => {
117 fields.named.push(
119 syn::Field::parse_named
120 .parse2(quote! { pub metadata: BTreeMap<String, String> })
121 .unwrap(),
122 );
123 }
124 false => {}
125 }
126
127 match attrs.contains(&NOTES.to_string()) {
129 true => {
130 fields.named.push(
132 syn::Field::parse_named
133 .parse2(quote! { pub notes: BTreeMap<String, Note> })
134 .unwrap(),
135 );
136 }
137 false => {}
138 }
139
140 match attrs.contains(&PHONE.to_string()) {
141 true => {
142 fields.named.push(
144 syn::Field::parse_named
145 .parse2(quote! { pub phone_numbers: BTreeMap<String, PhoneNumber> })
146 .unwrap(),
147 );
148 }
149 false => {}
150 }
151
152 match attrs.contains(&TAGS.to_string()) {
154 true => {
155 fields.named.push(
157 syn::Field::parse_named
158 .parse2(quote! { pub tags: Vec<String> })
159 .unwrap(),
160 );
161 }
162 false => {}
163 }
164 }
165
166 return quote! {
167 #item_struct
168 }
169 .into();
170}
171
172#[derive(Debug)]
173struct Args {
174 pub vars: Vec<LitStr>,
175}
176
177impl Parse for Args {
178 fn parse(input: ParseStream) -> Result<Self> {
179 let vars = Punctuated::<syn::LitStr, Token![,]>::parse_terminated(input)?;
180 Ok(Args {
181 vars: vars.into_iter().collect::<Vec<LitStr>>(),
182 })
183 }
184}
185
186#[proc_macro_derive(Scaffolding)]
190pub fn scaffolding_derive(input: TokenStream) -> TokenStream {
191 let ast: syn::DeriveInput = syn::parse(input).unwrap();
194
195 impl_scaffolding(&ast)
197}
198
199fn impl_scaffolding(ast: &syn::DeriveInput) -> TokenStream {
200 let name = &ast.ident;
201 let gen = quote! {
202 impl Scaffolding for #name {
203 fn get_activity(&self, name: String) -> Vec<ActivityItem>{
204 self.activity.iter().filter(|a| a.action == name).cloned().collect()
205 }
206
207 fn log_activity(&mut self, name: String, descr: String) {
208 self.activity.push(ActivityItem::new(name, descr));
209 }
210 }
211 };
212 gen.into()
213}
214
215#[proc_macro_derive(ScaffoldingAddresses)]
217pub fn scaffolding_addresses_derive(input: TokenStream) -> TokenStream {
218 let ast: syn::DeriveInput = syn::parse(input).unwrap();
219
220 impl_scaffolding_addresses(&ast)
221}
222
223fn impl_scaffolding_addresses(ast: &syn::DeriveInput) -> TokenStream {
224 let name = &ast.ident;
225 let gen = quote! {
226 impl ScaffoldingAddresses for #name {
227 fn get_address(&self, id: String) -> Option<&Address> {
228 self.addresses.get(&id)
229 }
230
231 fn insert_address(
232 &mut self,
233 category: String,
234 line_1: String,
235 line_2: String,
236 line_3: String,
237 line_4: String,
238 country_code: String,
239 ) -> String {
240 let address = Address::new(category, line_1, line_2, line_3, line_4, country_code);
241 let id = address.id.clone();
242 self.addresses.insert(id.clone(), address);
243 id
244 }
245
246 fn modify_address(&mut self, id: String, category: String, line_1: String, line_2: String, line_3: String, line_4: String, country_code: String) {
247 self.addresses
248 .entry(id)
249 .and_modify(|addr|
250 addr.update(category, line_1, line_2, line_3, line_4, country_code)
251 );
252 }
253
254 fn search_addresses_by_category(&self, category: String) -> Vec<Address> {
255 self.addresses
256 .iter()
257 .filter(|(k,v)| v.category == category)
258 .map(|(k,v)| v.clone())
259 .collect()
260 }
261
262 fn remove_address(&mut self, id: String) {
263 self.addresses.remove(&id);
264 }
265 }
266 };
267 gen.into()
268}
269
270#[proc_macro_derive(ScaffoldingEmailAddresses)]
272pub fn scaffolding_emailaddresses_derive(input: TokenStream) -> TokenStream {
273 let ast: syn::DeriveInput = syn::parse(input).unwrap();
274
275 impl_scaffolding_emailaddresses(&ast)
276}
277
278fn impl_scaffolding_emailaddresses(ast: &syn::DeriveInput) -> TokenStream {
279 let name = &ast.ident;
280 let gen = quote! {
281 impl ScaffoldingEmailAddresses for #name {
282 fn get_email_address(&self, id: String) -> Option<&EmailAddress> {
283 self.email_addresses.get(&id)
284 }
285
286 fn insert_email_address(
287 &mut self,
288 category: String,
289 address: String,
290 ) -> String {
291 let email = EmailAddress::new(category, address);
292 let id = email.id.clone();
293 self.email_addresses.insert(id.clone(), email);
294 id
295 }
296
297 fn search_email_addresses_by_category(&self, category: String) -> Vec<EmailAddress> {
298 self.email_addresses
299 .iter()
300 .filter(|(k,v)| v.category == category)
301 .map(|(k,v)| v.clone())
302 .collect()
303 }
304
305 fn remove_email_address(&mut self, id: String) {
306 self.email_addresses.remove(&id);
307 }
308 }
309 };
310 gen.into()
311}
312
313#[proc_macro_derive(ScaffoldingNotes)]
315pub fn scaffolding_notes_derive(input: TokenStream) -> TokenStream {
316 let ast: syn::DeriveInput = syn::parse(input).unwrap();
317
318 impl_scaffolding_notes(&ast)
319}
320
321fn impl_scaffolding_notes(ast: &syn::DeriveInput) -> TokenStream {
322 let name = &ast.ident;
323 let gen = quote! {
324 impl ScaffoldingNotes for #name {
325 fn get_note(&self, id: String) -> Option<&Note> {
326 self.notes.get(&id)
327 }
328
329 fn insert_note(&mut self, auth: String, cont: Vec<u8>, acc: Option<String>) -> String {
330 let note = Note::new(auth, cont, acc);
331 let id = note.id.clone();
332 self.notes.insert(id.clone(), note);
333 id
334 }
335
336 fn modify_note(&mut self, id: String, auth: String, cont: Vec<u8>, acc: Option<String>) {
337 self.notes
338 .entry(id)
339 .and_modify(|note|
340 note.update(auth, cont, acc)
341 );
342 }
343
344 fn search_notes(&mut self, search: String) -> Vec<Note> {
345 let mut results: Vec<Note> = Vec::new();
346
347 for (key, note) in self.notes.iter() {
348 let mut cont = String::from_utf8(note.content.clone())
349 .map_err(|non_utf8| String::from_utf8_lossy(non_utf8.as_bytes()).into_owned())
350 .unwrap();
351
352 match cont.contains(&search) {
353 true => {
354 results.push(note.clone())
355 },
356 false => {},
357 }
358 }
359
360 results
361 }
362
363 fn remove_note(&mut self, id: String) {
364 self.notes.remove(&id);
365 }
366 }
367 };
368 gen.into()
369}
370
371#[proc_macro_derive(ScaffoldingPhoneNumbers)]
373pub fn scaffolding_phonenumbers_derive(input: TokenStream) -> TokenStream {
374 let ast: syn::DeriveInput = syn::parse(input).unwrap();
375
376 impl_scaffolding_phonenumbers(&ast)
377}
378
379fn impl_scaffolding_phonenumbers(ast: &syn::DeriveInput) -> TokenStream {
380 let name = &ast.ident;
381 let gen = quote! {
382 impl ScaffoldingPhoneNumbers for #name {
383 fn get_phone_number(&self, id: String) -> Option<&PhoneNumber> {
384 self.phone_numbers.get(&id)
385 }
386
387 fn insert_phone_number(
388 &mut self,
389 category: String,
390 number: String,
391 country_code: String,
392 ) -> String {
393 let phone = PhoneNumber::new(category, number, country_code);
394 let id = phone.id.clone();
395 self.phone_numbers.insert(id.clone(), phone);
396 id
397 }
398
399 fn search_phone_numbers_by_category(&self, category: String) -> Vec<PhoneNumber> {
400 self.phone_numbers
401 .iter()
402 .filter(|(k,v)| v.category == category)
403 .map(|(k,v)| v.clone())
404 .collect()
405 }
406
407 fn remove_phone_number(&mut self, id: String) {
408 self.phone_numbers.remove(&id);
409 }
410 }
411 };
412 gen.into()
413}
414
415#[proc_macro_derive(ScaffoldingTags)]
417pub fn scaffolding_tags_derive(input: TokenStream) -> TokenStream {
418 let ast: syn::DeriveInput = syn::parse(input).unwrap();
419
420 impl_scaffolding_tags(&ast)
421}
422
423fn impl_scaffolding_tags(ast: &syn::DeriveInput) -> TokenStream {
424 let name = &ast.ident;
425 let gen = quote! {
426 impl ScaffoldingTags for #name {
427 fn add_tag(&mut self, tag: String) {
428 match self.has_tag(tag.clone()) {
430 false => {
431 self.tags.push(tag);
432 },
433 true => {
434 println!("Ignoring tag {}. Tag already exists!", tag);
435 },
436 }
437 }
438 fn has_tag(&self, tag: String) -> bool {
439 let results = self.tags.iter().filter(|t| **t == tag).cloned().collect::<String>();
440 match results.len() {
441 0 => false,
442 _ => true,
443 }
444 }
445 fn remove_tag(&mut self, tag: String) {
446 let pos = self.tags.iter().position(|t| **t == tag).unwrap();
447 self.tags.remove(pos);
448 }
449 }
450 };
451 gen.into()
452}
453
454#[proc_macro_attribute]
459pub fn scaffolding_fn(args: TokenStream, input: TokenStream) -> TokenStream {
460 let mut item: syn::Item = syn::parse(input).unwrap();
461 let fn_item = match &mut item {
462 syn::Item::Fn(fn_item) => fn_item,
463 _ => panic!("expected fn"),
464 };
465 let attrs = parse_macro_input!(args as Args)
466 .vars
467 .iter()
468 .map(|a| a.value())
469 .collect::<Vec<_>>();
470
471 let name = &fn_item.sig.ident.to_string();
473
474 match name.as_ref() {
475 "new" => {
476 print!("Modifying function {} ...", name);
477 for s in 0..fn_item.block.stmts.len() {
479 match &mut fn_item.block.stmts[s] {
480 syn::Stmt::Expr(expr, None) => match expr {
481 Struct(expr_struct) => {
482 let mut modify_attr_list = vec![
484 "id",
485 "created_dtm",
486 "modified_dtm",
487 "inactive_dtm",
488 "expired_dtm",
489 "activity",
490 ];
491
492 match attrs.contains(&ADDRESS.to_string()) {
493 true => {
494 modify_attr_list.push(&ADDRESS);
495 }
496 _ => {}
497 }
498
499 match attrs.contains(&EMAIL.to_string()) {
500 true => {
501 modify_attr_list.push(&EMAIL);
502 }
503 _ => {}
504 }
505
506 match attrs.contains(&METADATA.to_string()) {
507 true => {
508 modify_attr_list.push(&METADATA);
509 }
510 _ => {}
511 }
512
513 match attrs.contains(&NOTES.to_string()) {
514 true => {
515 modify_attr_list.push(&NOTES);
516 }
517 _ => {}
518 }
519
520 match attrs.contains(&PHONE.to_string()) {
521 true => {
522 modify_attr_list.push(&PHONE);
523 }
524 _ => {}
525 }
526
527 match attrs.contains(&TAGS.to_string()) {
528 true => {
529 modify_attr_list.push(&TAGS);
530 }
531 _ => {}
532 }
533
534 for f in 0..expr_struct.fields.len() {
536 match &expr_struct.fields[f].member {
537 Member::Named(mbr) => {
538 match CORE_ATTRS.contains(&mbr.to_string().as_str()) {
539 true => {
540 modify_attr_list
543 .retain_mut(|a| *a != mbr.to_string().as_str());
544 }
545 false => {}
546 }
547 }
548 _ => {}
549 }
550 }
551
552 for attr in modify_attr_list.iter() {
554 match *attr {
556 "id" => {
557 let line: FieldValue = parse_quote! {id: defaults::id()};
558 expr_struct.fields.insert(0, line);
559 }
560 "created_dtm" => {
561 let line: FieldValue =
562 parse_quote! {created_dtm: defaults::now()};
563 expr_struct.fields.insert(0, line);
564 }
565 "modified_dtm" => {
566 let line: FieldValue =
567 parse_quote! {modified_dtm: defaults::now()};
568 expr_struct.fields.insert(0, line);
569 }
570 "inactive_dtm" => {
571 let line: FieldValue = parse_quote! {inactive_dtm: defaults::add_days(defaults::now(), 90)};
572 expr_struct.fields.insert(0, line);
573 }
574 "expired_dtm" => {
575 let line: FieldValue = parse_quote! {expired_dtm: defaults::add_years(defaults::now(), 3)};
576 expr_struct.fields.insert(0, line);
577 }
578 "activity" => {
579 let line: FieldValue = parse_quote! {activity: Vec::new()};
580 expr_struct.fields.insert(0, line);
581 }
582 "metadata" => {
583 let line: FieldValue =
584 parse_quote! {metadata: BTreeMap::new()};
585 expr_struct.fields.insert(0, line);
586 }
587 "notes" => {
588 let line: FieldValue =
589 parse_quote! {notes: BTreeMap::new()};
590 expr_struct.fields.insert(0, line);
591 }
592 "tags" => {
593 let line: FieldValue = parse_quote! {tags: Vec::new()};
594 expr_struct.fields.insert(0, line);
595 }
596 "addresses" => {
597 let line: FieldValue =
598 parse_quote! {addresses: BTreeMap::new()};
599 expr_struct.fields.insert(0, line);
600 }
601 "email_addresses" => {
602 let line: FieldValue =
603 parse_quote! {email_addresses: BTreeMap::new()};
604 expr_struct.fields.insert(0, line);
605 }
606 "phone_numbers" => {
607 let line: FieldValue =
608 parse_quote! {phone_numbers: BTreeMap::new()};
609 expr_struct.fields.insert(0, line);
610 }
611 _ => {}
612 }
613 }
614 }
615 _ => {
616 }
618 },
619 _ => {
620 }
622 }
623 }
624 }
625 _ => {
626 print!(
627 "Function {} is unsupported. Nothing to add to function ",
628 name
629 );
630 }
631 }
632
633 item.into_token_stream().into()
634}