carpenter_derive/
lib.rs

1use proc_macro::TokenStream;
2use syn::{self, Ident, parse_macro_input, DeriveInput, Fields};
3use quote::{quote, ToTokens};
4
5
6#[proc_macro_derive(ConfigManager)] 
7pub fn config_derive_macro(item: TokenStream) -> TokenStream {
8    let ast: DeriveInput = parse_macro_input!(item as DeriveInput);
9    return impl_config_trait(ast);
10}
11
12fn impl_config_trait(ast: syn::DeriveInput) -> TokenStream {
13    // Data check
14    let data: syn::DataStruct = match ast.data {
15        syn::Data::Struct(data) => data,
16        syn::Data::Enum(_) => panic!("Enums are not supported by Carpenter"),
17        syn::Data::Union(_) => panic!("Unions are not supported by Carpenter"),
18    };
19
20    // Props
21    let config_id: &Ident = &ast.ident; 
22
23    let settings_name: String = format!("{}Factory", config_id);
24    let setting_id: Ident = syn::Ident::new(&settings_name, config_id.span());
25
26    let fields = match data.fields {
27        Fields::Named(fields) => fields.named,
28        _ => panic!("Only named fields are supported by Carpenter")
29    };
30
31    let field_read_statements = fields.iter().map(|field| {
32        let field_name = field.ident.as_ref().expect("Field name not found");
33        let field_type = &field.ty;
34
35        if field_type.to_token_stream().to_string() == "String" {
36            quote! {
37                #field_name: {
38                    let mut string_buffer = Vec::new();
39                    loop {
40                        let mut byte = [0; 1];
41                        if stream.read_exact(&mut byte).is_err() || byte[0] == 0 {
42                            break;
43                        }
44                        string_buffer.push(byte[0]);
45                    }
46                    String::from_utf8_lossy(&string_buffer).to_string()
47                },
48            }
49        } else {
50            quote! {
51                #field_name: #field_type::read_from(stream, order)?,
52            }
53        }
54    });
55
56    let mut field_write_statements = Vec::new();
57    for field in &fields {
58        let field_name = field.ident.as_ref().expect("Field name not found");
59
60        let field_write_expr;
61        if field.ty.to_token_stream().to_string() == "String" {
62            field_write_expr = quote! {
63                for byte in self.#field_name.bytes() {
64                    stream.write_all(&[byte])?;
65                }
66                stream.write_all(&[0])?;
67            };
68        } 
69        else {
70            field_write_expr = quote! {
71                self.#field_name.write_to(stream, order)?;
72            };
73
74        }
75        
76        field_write_statements.push(field_write_expr);
77    }
78    
79    
80    // Expansion
81    let expanded = quote!{
82        use carpenter::ConfigPath;
83        use std::fs::File;
84        use std::io::{Cursor, Write, Read};
85        use std::path::PathBuf;
86        use bytestream::{StreamWriter, ByteOrder, StreamReader};
87        
88
89        struct #setting_id {
90            path: PathBuf,
91            username: String, 
92            application_name: String,
93            config_name: String,
94        }
95
96        impl #setting_id {
97            /// Tries to create all of the directories.
98            fn create_dir(&self) -> Result<(), std::io::Error> {
99                std::fs::create_dir_all(&self.path)?;
100                Ok(())
101            }
102            
103            /// Tries to create the file.
104            fn create_file(&self) -> Result<(), std::io::Error> {
105                let config_file_path = self.path.join(&self.config_name);
106                let config_file = File::create(config_file_path)?;
107                Ok(())
108            }
109
110            /// Tries to save the struct to the config file.
111            fn save(&self, config_struct: &#config_id) -> Result<(), std::io::Error> {
112                self.create_dir()?;
113                self.create_file()?;
114
115                let config_file_path = self.path.join(&self.config_name);
116                let mut buffer = Vec::<u8>::new();
117                config_struct.write_to(&mut buffer, ByteOrder::BigEndian)?;
118
119                std::fs::write(config_file_path, buffer)?;
120                Ok(())
121            }
122
123            /// Tries to read the config file and parse it to the struct.
124            fn read(&self) -> Result<#config_id, std::io::Error> {
125                let config_file_path = self.path.join(&self.config_name);
126                let mut buffer = std::fs::read(config_file_path)?;
127                let mut cursor = Cursor::new(buffer);
128                return Ok(#config_id::read_from(&mut cursor, ByteOrder::BigEndian)?);
129            }
130        }
131
132        impl #config_id {
133            /// Creates a Builder for this struct.
134            /// 
135            /// # Example
136            /// ```rust
137            /// let config_factory = Config::init_config(
138            ///    "meloencoding", // Username
139            ///    "config-rs-test", // Application name
140            ///    "test.bin" // Config file name. File extention is optional
141            /// );
142            /// ```
143            /// On this Builder struct you can call `save()` and `read()`
144            /// # Examples
145            /// ```rust
146            /// // To save your config
147            /// let sample_config = Config {
148            ///     a: 400,
149            ///     b: true,
150            ///     c: String::from("Hey"),
151            /// };
152            /// 
153            /// config_factory.save(&sample_config)?;
154            /// 
155            /// // To read the saved config
156            /// assert_eq!(sample_config, config_factory.read()?);
157            /// ```
158            pub fn init_config(username: &str, application_name: &str, config_name: &str) -> #setting_id {
159                let builder = #setting_id {
160                    path: PathBuf::from(ConfigPath::new(username, application_name).inner.clone()),
161                    username: username.to_string(), 
162                    application_name: application_name.to_string(), 
163                    config_name: config_name.to_string()
164                };
165                return builder;
166            }
167        }
168        
169        impl StreamReader for #config_id {
170            fn read_from<R: Read>(stream: &mut R, order: ByteOrder) -> Result<#config_id, std::io::Error> {
171                Ok(#config_id {
172                    #(#field_read_statements)*
173                })
174            }
175        }
176
177        impl StreamWriter for #config_id {
178            fn write_to<W: Write>(&self, stream: &mut W, order: bytestream::ByteOrder) -> Result<(), std::io::Error> {
179                #(#field_write_statements)*
180                Ok(())
181            }
182        }
183    };
184
185    return expanded.into();
186}