Skip to main content

shah_macros/
lib.rs

1use syn::parse_quote;
2use utils::traitor::{Traitor, TraitorField};
3
4mod api;
5mod command;
6mod entity;
7mod enum_code;
8mod enum_int;
9mod flags;
10mod legacy;
11mod model;
12mod perms;
13mod routes;
14mod schema;
15mod utils;
16
17use proc_macro::TokenStream;
18
19/// Example:
20/// ```ignore
21/// #[derive(Debug, Default, shah::Command)]
22/// enum MyCommands {
23///     #[default]
24///     Help,
25///     Run,
26///     Master { gene: shah::models::Gene }
27/// }
28/// ```
29#[proc_macro_derive(Command)]
30pub fn command(code: TokenStream) -> TokenStream {
31    command::command(code)
32}
33
34/// Example:
35/// ```ignore
36/// #[derive(Debug, shah::EnumCode)]
37/// #[enum_code(u8)]
38/// pub enum Schema {
39///     Model(Model), // 0u8
40///     Array { len: usize }, // 1u8
41///     U8,  // 2u8
42///     Gene // 3u8
43/// }
44/// ```
45#[proc_macro_derive(EnumCode, attributes(enum_code))]
46pub fn enum_code(code: TokenStream) -> TokenStream {
47    enum_code::enum_code(code)
48}
49
50/// Example:
51/// ```ignore
52/// #[shah::legacy]
53/// mod items {
54///
55///     /// the derive macros from **Base** are set for all children
56///     #[derive(Debug, Serialize, Deserialize, ToSchema)]
57///     pub struct Base {
58///         gene: Gene,
59///         is_alive: bool,
60///     }
61///
62///     impl From<&Review> for Base {
63///         fn from(value: &Review) -> Self {
64///             Self {
65///                 gene: value.gene,
66///                 is_alive: value.is_alive(),
67///             }
68///         }
69///     }
70///
71///     // child 1
72///     pub struct ReviewInfo {
73///         // gene: Gene,
74///         // is_alive: bool,
75///         user: Gene,
76///     }
77///
78///     impl From<&Review> for ReviewInfo {
79///         fn from(value: &Review) -> Self {
80///             Self { user: value.user }
81///         }
82///     }
83///
84///     // child 2
85///     pub struct EateryReviewInfo {
86///         // gene: Gene,
87///         // is_alive: bool,
88///         user: Option<EateryReviewUserInfo>,
89///     }
90///
91///     impl From<&Review> for EateryReviewInfo {
92///         fn from(review: &Review) -> Self {
93///             Self { user: None }
94///         }
95///     }
96/// }
97/// ```
98#[proc_macro_attribute]
99pub fn legacy(_args: TokenStream, code: TokenStream) -> TokenStream {
100    let item = syn::parse_macro_input!(code as syn::ItemMod);
101    legacy::legacy(item).unwrap_or_else(syn::Error::into_compile_error).into()
102}
103
104/// enum_ini is a two way conversion enum `<->` u16
105/// default **start** is `0` and default **ty** is `u8`
106/// Example:
107/// ```ignore
108/// #[shah::enum_int(u16)]
109/// #[derive(Debug, Default, Clone, Copy)]
110/// pub enum ExampleError {
111///     #[default]
112///     Unknown = 0,
113///     UserNotFound,
114///     BadPhone,
115///     BadStr,
116/// }
117/// ```
118#[proc_macro_attribute]
119pub fn enum_int(args: TokenStream, code: TokenStream) -> TokenStream {
120    let item = syn::parse_macro_input!(code as syn::ItemEnum);
121    enum_int::enum_int(item, args)
122        .unwrap_or_else(syn::Error::into_compile_error)
123        .into()
124}
125
126#[proc_macro_attribute]
127pub fn api(args: TokenStream, code: TokenStream) -> TokenStream {
128    type Args = syn::punctuated::Punctuated<syn::MetaNameValue, syn::Token![,]>;
129    let item = syn::parse_macro_input!(code as syn::ItemMod);
130    let attrs = syn::parse_macro_input!(args with Args::parse_terminated);
131
132    api::api(attrs, item).unwrap_or_else(syn::Error::into_compile_error).into()
133}
134
135#[proc_macro_attribute]
136pub fn model(_args: TokenStream, code: TokenStream) -> TokenStream {
137    let item = syn::parse_macro_input!(code as syn::ItemStruct);
138    model::model(item).unwrap_or_else(syn::Error::into_compile_error).into()
139}
140
141#[proc_macro_attribute]
142pub fn flags(args: TokenStream, code: TokenStream) -> TokenStream {
143    let item = syn::parse_macro_input!(code as syn::ItemStruct);
144    let args = syn::parse_macro_input!(args with flags::Args::parse_terminated);
145    flags::flags(args, item)
146        .unwrap_or_else(syn::Error::into_compile_error)
147        .into()
148}
149
150#[proc_macro_derive(Entity, attributes(entity))]
151/// Derive macro generating an impl of the trait `Entity`.
152///
153/// You can use `#[entity(gene)]`, `#[entity(flags)]` and `#[entity(growth)]`
154/// to set custom fields for these methods.
155pub fn entity(code: TokenStream) -> TokenStream {
156    entity::entity(code)
157}
158
159#[proc_macro_derive(Belt, attributes(belt))]
160/// Derive macro generating an impl of the trait `Belt`.
161/// You can use `#[belt(next)]`, `#[belt(past)]` and `#[belt(buckle)]`
162/// to set custom fields for these methods.
163pub fn belt(code: TokenStream) -> TokenStream {
164    let ci = crate_ident();
165    let inp = syn::parse_macro_input!(code as syn::DeriveInput);
166
167    let gene = parse_quote!(#ci::models::Gene);
168
169    let tr = Traitor::new(
170        "belt",
171        parse_quote!(#ci::db::belt::Belt),
172        [
173            TraitorField::new("next", &gene, false),
174            TraitorField::new("past", &gene, false),
175            TraitorField::new("buckle", &gene, false),
176        ],
177    );
178    tr.derive(inp).unwrap_or_else(syn::Error::into_compile_error).into()
179}
180
181#[proc_macro_derive(Buckle, attributes(buckle))]
182pub fn buckle(code: TokenStream) -> TokenStream {
183    let ci = crate_ident();
184    let inp = syn::parse_macro_input!(code as syn::DeriveInput);
185
186    let gene = parse_quote!(#ci::models::Gene);
187    let pu64 = parse_quote!(u64);
188
189    let tr = Traitor::new(
190        "buckle",
191        parse_quote!(#ci::db::belt::Buckle),
192        [
193            TraitorField::new("head", &gene, false),
194            TraitorField::new("tail", &gene, false),
195            TraitorField::new("belt_count", &pu64, true),
196        ],
197    );
198    tr.derive(inp).unwrap_or_else(syn::Error::into_compile_error).into()
199}
200
201#[proc_macro_derive(Duck, attributes(duck))]
202pub fn duck(code: TokenStream) -> TokenStream {
203    let ci = crate_ident();
204    let inp = syn::parse_macro_input!(code as syn::DeriveInput);
205    let gene = parse_quote!(#ci::models::Gene);
206    let tr = Traitor::new(
207        "duck",
208        parse_quote!(#ci::db::pond::Duck),
209        [TraitorField::new("pond", &gene, false)],
210    );
211    tr.derive(inp).unwrap_or_else(syn::Error::into_compile_error).into()
212}
213
214#[proc_macro_derive(Pond, attributes(pond))]
215pub fn pond(code: TokenStream) -> TokenStream {
216    let ci = crate_ident();
217    let inp = syn::parse_macro_input!(code as syn::DeriveInput);
218
219    let gene = parse_quote!(#ci::models::Gene);
220    let pru8 = parse_quote!(u8);
221    let gene_id = parse_quote!(#ci::models::GeneId);
222
223    let tr = Traitor::new(
224        "pond",
225        parse_quote!(#ci::db::pond::Pond),
226        [
227            TraitorField::new("next", &gene, false),
228            TraitorField::new("past", &gene, false),
229            TraitorField::new("origin", &gene, false),
230            TraitorField::new("stack", &gene_id, true),
231            TraitorField::new("alive", &pru8, true),
232            TraitorField::new("empty", &pru8, true),
233        ],
234    );
235    tr.derive(inp).unwrap_or_else(syn::Error::into_compile_error).into()
236}
237
238#[proc_macro_derive(Origin, attributes(origin))]
239pub fn origin(code: TokenStream) -> TokenStream {
240    let ci = crate_ident();
241    let inp = syn::parse_macro_input!(code as syn::DeriveInput);
242
243    let gene = parse_quote!(#ci::models::Gene);
244    let pu64 = parse_quote!(u64);
245
246    let tr = Traitor::new(
247        "origin",
248        parse_quote!(#ci::db::pond::Origin),
249        [
250            TraitorField::new("head", &gene, false),
251            TraitorField::new("tail", &gene, false),
252            TraitorField::new("pond_count", &pu64, true),
253            TraitorField::new("item_count", &pu64, true),
254        ],
255    );
256    tr.derive(inp).unwrap_or_else(syn::Error::into_compile_error).into()
257}
258
259#[proc_macro_derive(ShahSchema)]
260pub fn schema(code: TokenStream) -> TokenStream {
261    schema::schema(code)
262}
263
264#[proc_macro]
265pub fn routes(code: TokenStream) -> TokenStream {
266    routes::routes(code)
267}
268
269#[proc_macro]
270pub fn perms(code: TokenStream) -> TokenStream {
271    perms::perms(code)
272}
273
274fn crate_ident() -> syn::Ident {
275    // let found_crate = crate_name("shah").unwrap();
276    // let name = match &found_crate {
277    //     FoundCrate::Itself => "shah",
278    //     FoundCrate::Name(name) => name,
279    // };
280    ident!("shah")
281}
282
283macro_rules! ident {
284    ($name:literal) => {
285        syn::Ident::new($name, proc_macro2::Span::call_site())
286    };
287    ($name:expr) => {
288        syn::Ident::new($name, proc_macro2::Span::call_site())
289    };
290}
291pub(crate) use ident;
292
293macro_rules! err {
294    ($span:expr, $($msg:literal),*) => {
295        Err(syn::Error::new($span, concat!( $($msg),* )))
296    };
297    ($span:expr, $msg:expr) => {
298        Err(syn::Error::new($span, $msg))
299    };
300}
301
302pub(crate) use err;