rorm_macro/
lib.rs

1//! This crate tries to follow the base layout proposed by a [ferrous-systems.com](https://ferrous-systems.com/blog/testing-proc-macros/#the-pipeline) blog post.
2extern crate proc_macro;
3use proc_macro::TokenStream;
4use proc_macro2::Span;
5use quote::quote;
6
7#[proc_macro_derive(DbEnum)]
8pub fn derive_db_enum(input: TokenStream) -> TokenStream {
9    rorm_macro_impl::derive_db_enum(input.into(), rorm_macro_impl::MacroConfig::default()).into()
10}
11
12#[proc_macro_derive(Model, attributes(rorm))]
13pub fn derive_model(input: TokenStream) -> TokenStream {
14    rorm_macro_impl::derive_model(input.into(), rorm_macro_impl::MacroConfig::default()).into()
15}
16
17#[proc_macro_derive(Patch, attributes(rorm))]
18pub fn derive_patch(input: TokenStream) -> TokenStream {
19    rorm_macro_impl::derive_patch(input.into(), rorm_macro_impl::MacroConfig::default()).into()
20}
21
22#[proc_macro_attribute]
23pub fn rorm_main(args: TokenStream, item: TokenStream) -> TokenStream {
24    let main = syn::parse_macro_input!(item as syn::ItemFn);
25    let feature = syn::parse::<syn::LitStr>(args)
26        .unwrap_or_else(|_| syn::LitStr::new("rorm-main", Span::call_site()));
27
28    (if main.sig.ident == "main" {
29        quote! {
30            #[cfg(feature = #feature)]
31            fn main() -> Result<(), String> {
32                let mut file = ::std::fs::File::create(".models.json").map_err(|err| err.to_string())?;
33                ::rorm::write_models(&mut file)?;
34                return Ok(());
35            }
36            #[cfg(not(feature = #feature))]
37            #main
38        }
39    } else {
40        quote! {
41            compile_error!("only allowed on main function");
42            #main
43        }
44    }).into()
45}
46
47/// ```ignored
48/// impl_tuple!(some_macro, 2..5);
49///
50/// // produces
51///
52/// some_macro!(0: T0, 1: T1);               // tuple of length 2
53/// some_macro!(0: T0, 1: T1, 2: T2);        // tuple of length 3
54/// some_macro!(0: T0, 1: T1, 2: T2, 3: T3); // tuple of length 4
55/// ```
56#[proc_macro]
57pub fn impl_tuple(args: TokenStream) -> TokenStream {
58    // handwritten without dependencies just for fun
59    use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenTree as TT};
60
61    let args = Vec::from_iter(args);
62    let [TT::Ident(macro_ident), TT::Punct(comma), TT::Literal(start), TT::Punct(fst_dot), TT::Punct(snd_dot), TT::Literal(end)] =
63        &args[..]
64    else {
65        panic!()
66    };
67    if *comma != ','
68        || *fst_dot != '.'
69        || *snd_dot != '.' && matches!(fst_dot.spacing(), Spacing::Alone)
70    {
71        panic!();
72    }
73
74    let start: usize = start.to_string().parse().unwrap();
75    let end: usize = end.to_string().parse().unwrap();
76
77    let mut tokens = TokenStream::default();
78    for until in start..end {
79        let mut impl_args = TokenStream::new();
80        for index in 0..until {
81            impl_args.extend([
82                TT::Literal(Literal::usize_unsuffixed(index)),
83                TT::Punct(Punct::new(':', Spacing::Alone)),
84                TT::Ident(Ident::new(&format!("T{index}"), Span::call_site())),
85                TT::Punct(Punct::new(',', Spacing::Alone)),
86            ]);
87        }
88        tokens.extend([
89            TT::Ident(macro_ident.clone()),
90            TT::Punct(Punct::new('!', Spacing::Alone)),
91            TT::Group(Group::new(Delimiter::Parenthesis, impl_args)),
92            TT::Punct(Punct::new(';', Spacing::Alone)),
93        ]);
94    }
95    tokens
96}