diff --git a/Cargo.toml b/Cargo.toml index a80feb3..18d6b48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,4 @@ dirs = "5.0.1" serde = { version = "1.0.216", features = [ "std", "derive" ] } sysinfo = "0.32.0" toml = "0.8.19" -derive_config_parser = { path = "derive_config_parser" } +config_parser = { path = "config_parser" } diff --git a/derive_config_parser/Cargo.toml b/config_parser/Cargo.toml similarity index 60% rename from derive_config_parser/Cargo.toml rename to config_parser/Cargo.toml index 3a57365..062078e 100644 --- a/derive_config_parser/Cargo.toml +++ b/config_parser/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "derive_config_parser" +name = "config_parser" version = "0.1.0" autotests = false edition = "2021" @@ -10,5 +10,6 @@ proc-macro = true [dependencies] quote = "1.0.38" -syn = { version = "2.0.92", features = ["extra-traits"] } +syn = { version = "2.0.94", features = ["full", "extra-traits"] } + diff --git a/config_parser/src/common.rs b/config_parser/src/common.rs new file mode 100644 index 0000000..57a1503 --- /dev/null +++ b/config_parser/src/common.rs @@ -0,0 +1,70 @@ +#![allow(dead_code)] + +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; +use quote::{quote}; + +use syn::{Attribute, Error, Field, Meta, Path, punctuated::Punctuated, token::Comma}; + +pub fn gen_config_load_function(fields: &Punctuated, config_map_name: &String) -> Result { + let assignments = fields.iter().map(|f|{ + let attr = &f.attrs; + let name = match &f.ident { + Some(value) => value, + // skip anonymous fields + None => return quote! {}, + }; + let name_string: String = name.to_string(); + let ty = &f.ty; + for attribute in attr { + if let Attribute{ meta: Meta::Path( Path{segments: attr_name, ..} ), .. } = attribute { + match attr_name.first() { + Some(value) => if value.ident == "nested_config" { + return quote! {#name.parse_from_map(&#config_map_name)} + }, + None => (), + } + } + } + quote! { + self.#name = match #config_map_name.get(#name_string) { + Some(value) => value, + None => self.#name, + } + } + }); + Ok(quote! {assignments}.into()) +} + +//pub fn parse_config_file(input: &String) -> std::io::Result> { +// let mut config = std::collections::HashMap::::new(); +// let lines = input.lines(); +// +// for line in lines { +// let line = line.trim(); +// // ignore empty lines +// if line.is_empty() { continue; } +// +// if line.starts_with("[") { +// // check for table +// if !line.ends_with("]") { +// // TODO: implement error handling +// } else if line.contains(' ') { +// // TODO: implement error handling +// } +// //let tokens = line.split('.'); +// // TODO: implement hirarchical map +// } else { +// // check for config +// let mut tokens: Vec<&str> = line.split('=').map(|entry| entry.trim()).collect(); +// tokens.retain(|entry| !entry.is_empty()); +// if tokens.len() != 2 { +// // println!("error in line'", line); +// continue; +// } +// config.insert(tokens[0].to_string(), tokens[1].to_string()); +// } +// } +// Ok(config) +//} +// diff --git a/config_parser/src/lib.rs b/config_parser/src/lib.rs new file mode 100644 index 0000000..3226523 --- /dev/null +++ b/config_parser/src/lib.rs @@ -0,0 +1,92 @@ +use common::gen_config_load_function; +use proc_macro::{TokenStream}; // TODO: change to proc_macro2, to hopefully fix #assignments +use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Ident}; +use quote::{quote}; + +#[macro_use] +mod common; + +/// **for structs only** +/// - implements `parse_config(&mut self, input: &String) -> Result<()>` +/// which parses a string and fills the fills recognised values into the struct +/// - implements `write_default_config() -> Result` +/// which write a default configuration, in case the documentation is lacking +#[proc_macro_derive(ConfigParser, attributes(nested_config, testtest))] +pub fn derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let name = &ast.ident; + let config_name = "config".to_string(); //syn::Ident::new("config", name.span()); // TODO: use correct span + let fields = if let syn::Data::Struct(syn::DataStruct{ fields: syn::Fields::Named(syn::FieldsNamed{ ref named, .. }), .. }) = ast.data { + named + } else { + todo!("use `syn::Error` to return comprehensive error message"); + }; + let assignments = match gen_config_load_function(fields, &config_name) { + Ok(value) => value, + Err(error) => panic!("loading config failed"), + }; + + //let assign_fields = fields.iter().map(|f|{ + // if let Some(name) = &f.ident { + // quote! { + // if + // } + // } + //}); + + //let string = format!("{:#?}", ast); + //_ = std::fs::write("test.txt", string); + + let expanded_stream: TokenStream = quote::quote! { + impl #name { + pub fn parse_from_string(&mut self, input: &String) -> std::io::Result<()> { + let #config_name : std::collections::HashMap = Self::parse_config_file(input)?; + #assignments + Ok(()) + } + + /// **do not call** + /// this function needs to be public for nested configs but is not intended + /// to be called by the user + pub fn parse_from_map(&mut self, input: &std::collections::HashMap) -> std::io::Result<()> { + todo!(); + } + + fn parse_config_file(input: &String) -> std::io::Result> { + let mut config = std::collections::HashMap::::new(); + let lines = input.lines(); + + for line in lines { + let mut line = line.trim(); + // ignore empty lines + if line.is_empty() { continue; } + + if line.starts_with("[") { + // check for table + if !line.ends_with("]") { + // TODO: implement error handling + } else if line.contains(' ') { + // TODO: implement error handling + } + //let tokens = line.split('.'); + // TODO: implement hirarchical map + } else { + // check for config + let mut tokens: Vec<&str> = line.split('=').map(|entry| entry.trim()).collect(); + tokens.retain(|entry| !entry.is_empty()); + if tokens.len() != 2 { + // println!("error in line'", line); + continue; + } + config.insert(tokens[0].to_string(), tokens[1].to_string()); + } + + } + Ok(config) + } + } + }.into(); + expanded_stream + //TokenStream::new() +} + diff --git a/derive_config_parser/src/lib.rs b/derive_config_parser/src/lib.rs deleted file mode 100644 index 6cfb546..0000000 --- a/derive_config_parser/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ - -use core::panic; -use proc_macro::TokenStream; - -/// **for structs only** -/// - implements `parse_config(&mut self, input: &String) -> Result<()>` -/// which parses a string and fills the fills recognised values into the struct -/// - implements `write_default_config() -> Result` -/// which write a default configuration, in case the documentation is lacking -#[proc_macro_derive(ConfigParser)] -pub fn derive(input: TokenStream) -> TokenStream { - let ast = syn::parse_macro_input!(input as syn::DeriveInput); - let name = &ast.ident; - let fields = if let syn::Data::Struct(syn::DataStruct{ fields: syn::Fields::Named(syn::FieldsNamed{ ref named, .. }), .. }) = ast.data { - named - } else { - panic!("failed to parse struct fields"); - }; - - println!("{:#?}", ast); -// let string = format!("{:#?}", ast); -// _ = std::fs::write("test.txt", string); - - let expanded_stream: TokenStream = quote::quote! { - impl #name { - pub fn parse_config(&mut self, input: &String) -> std::io::Result<()> { - let mut setting_names = std::collections::HashMap::new(); - let lines = input.lines(); - for line in lines { - let mut tokens: Vec<&str> = line.split('=').map(|entry| entry.trim()).collect(); - tokens.retain(|entry| !entry.is_empty()); - if tokens.len() != 2 { - // println!("error in line'", line); - continue; - } - setting_names.insert(tokens[0], tokens[1]); - } - //println!("{:#?}", setting_names); - Ok(()) - } - } - }.into(); - expanded_stream -// TokenStream::new() -}