Skip to main content

struct_record/
lib.rs

1use std::str::FromStr;
2
3use convert_case::Casing;
4use paste::paste;
5use proc_macro2::{TokenStream, TokenTree};
6use quote::quote;
7use syn::spanned::Spanned;
8
9struct RecordParams(syn::Type, syn::Ident, Option<syn::LitStr>);
10impl syn::parse::Parse for RecordParams {
11  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
12    let value_type = input.parse()?;
13    let _ = input.parse::<syn::Token![,]>();
14    let ident = input.parse()?;
15    let _ = input.parse::<syn::Token![,]>();
16    let header = input.parse().ok();
17    Ok(RecordParams(value_type, ident, header))
18  }
19}
20
21const GENERIC_ERROR_MSG: &str = "Expected to be used with an enum";
22
23#[proc_macro_attribute]
24pub fn record(
25  params: proc_macro::TokenStream,
26  ast: proc_macro::TokenStream,
27) -> proc_macro::TokenStream {
28  let RecordParams(value_type, ident, header) = syn::parse2(params.into()).expect("Invalid params");
29
30  let ast_tokens: TokenStream = ast.clone().into();
31
32  let mut ast_iter = ast_tokens.clone().into_iter();
33
34  let enum_props_group = ast_iter
35    .find_map(|next| {
36      if let TokenTree::Group(group) = next {
37        Some(group)
38      } else {
39        None
40      }
41    })
42    .expect(GENERIC_ERROR_MSG);
43
44  let struct_props: Vec<TokenStream> = enum_props_group
45    .stream()
46    .into_iter()
47    .filter_map(|token| {
48      if let TokenTree::Ident(ident) = token {
49        let ident_str = ident.to_string();
50        let snake = ident_str.to_case(convert_case::Case::Snake);
51        let snake_ident = syn::Ident::new(&snake, ident_str.span());
52
53        Some(quote! {
54          pub #snake_ident: #value_type ,
55        })
56      } else {
57        None
58      }
59    })
60    .collect();
61
62  let header_token = header
63    .map(|token| TokenStream::from_str(&token.value()).expect(GENERIC_ERROR_MSG))
64    .unwrap_or(TokenStream::new());
65
66  let output = quote! {
67    #ast_tokens
68    #header_token
69    struct #ident {
70      #(#struct_props)*
71    }
72  };
73
74  output.into()
75}