saito_macros/
lib.rs

1use proc_macro::{self, TokenStream};
2use proc_macro2::TokenTree;
3use quote::quote;
4use syn::{parse_macro_input, DeriveInput};
5
6///
7/// A derive macro which implements TryFrom<u8> for an enum.
8///
9/// usage:
10/// ```rust
11/// #[derive(TryFromByte)]
12/// ```
13///
14#[proc_macro_derive(TryFromByte)]
15pub fn try_from_byte(input: TokenStream) -> TokenStream {
16    // parse the code into DeriveInput
17    let DeriveInput { ident, data, .. } = parse_macro_input!(input);
18    // Get the length of the DataEnums in the Enum(i.e. the number of variants
19    // in the enum)
20    let len = match data {
21        syn::Data::Enum(enum_item) => enum_item.variants.len(),
22        _ => panic!("TryFromByte only works on Enums"),
23    };
24    // use the length to implement TryFrom for the enum
25    let output = quote! {
26        impl TryFrom<u8> for #ident {
27            type Error = &'static str;
28            fn try_from(x: u8) -> Result<Self, Self::Error> {
29                let right_size = x >= 0 && x <= (#len as u8);
30                match right_size {
31                    true => Ok(unsafe { std::mem::transmute(x as u8) }),
32                    _ => Err("invalid #ident Value"),
33                }
34            }
35        }
36    };
37    output.into()
38}
39///
40/// A derive macro which implments Persistable save/load functions.
41///
42/// optional attribute persist_with_name can be added to specify a
43/// method which should be called to get the name of the file
44/// where the data should be stored. For example, the method might
45/// return a hex-based hash or a string like {timestamp}-{hash} for
46/// a block. Should have the signature:
47///
48/// ```rust
49/// pub fn method_name(&self) -> String
50/// ```
51///
52/// usage:
53/// ```rust
54/// #[derive(Persistable)]
55/// #[persist_with_name(get_name)]
56/// ```
57///
58#[proc_macro_derive(Persistable, attributes(persist_with_name, persist_with_data))]
59pub fn persistable(input: TokenStream) -> TokenStream {
60    let DeriveInput { ident, attrs, .. } = parse_macro_input!(input);
61    let mut with_name_method = None;
62    // If the struct has an attribute persist_with_name, read it and get the Ident
63    // of the name of the method indicated
64    for attr in attrs {
65        if attr.path.is_ident("persist_with_name") {
66            attr.tokens
67                .into_iter()
68                .for_each(|tokentree| match tokentree {
69                    TokenTree::Group(group) => {
70                        group
71                            .stream()
72                            .into_iter()
73                            .for_each(|subtokentree| match subtokentree {
74                                TokenTree::Ident(ident) => {
75                                    with_name_method = Some(ident);
76                                }
77                                _ => {}
78                            });
79                    }
80                    _ => {}
81                });
82        }
83    }
84    // if a persist_with_name attribute was not passed, we will use the
85    // ident(name of the struct/type) as the filename
86    let struct_name = &ident.to_string().to_lowercase();
87    // build a token stream for appending the filename, will be injected
88    // into the output TokenStream
89    let file_name_append_token_stream;
90    if with_name_method.is_some() {
91        file_name_append_token_stream = quote! {
92            let mut filename: String = String::from("data/");
93            filename.push_str(&self.#with_name_method());
94        };
95    } else {
96        file_name_append_token_stream = quote! {
97            let mut filename: String = String::from("data/");
98            filename.push_str(#struct_name);
99        };
100    }
101    // Build the output TokenStream which implements the save/load functions.
102    let output = quote! {
103        impl Persistable for #ident {
104            fn save(&self) {
105                let serialized = serde_json::to_string(&self).unwrap();
106                #file_name_append_token_stream
107                Storage::write(serialized.as_bytes().to_vec(), &filename);
108            }
109            fn load(relative_filename: &str) -> Self {
110                let mut filename: String = String::from("data/");
111                filename.push_str(relative_filename);
112                let serialized = Storage::read(&filename).unwrap();
113                let out = serde_json::from_str(std::str::from_utf8(&serialized[..]).unwrap()).unwrap();
114                out
115            }
116        }
117    };
118    output.into()
119}