From cd2fcf90e1d525d97d1c9218f95124a8de9aa5a1 Mon Sep 17 00:00:00 2001 From: Linus Vogel Date: Mon, 17 Aug 2026 17:34:55 +0200 Subject: [PATCH] first working version --- .gitignore | 1 + .idea/lfcm.iml | 1 + Cargo.toml | 1 + src/lib.rs | 97 +++++++++++++++++++++++++++++++++----- tests/fixtures/test1.yaml | 1 + tests/integration_tests.rs | 14 +++++- 6 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 tests/fixtures/test1.yaml diff --git a/.gitignore b/.gitignore index 6936990..0668c27 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk Cargo.lock +.idea/ \ No newline at end of file diff --git a/.idea/lfcm.iml b/.idea/lfcm.iml index bbe0a70..e4f2f92 100644 --- a/.idea/lfcm.iml +++ b/.idea/lfcm.iml @@ -4,6 +4,7 @@ + diff --git a/Cargo.toml b/Cargo.toml index 5de5e20..169f9c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,5 @@ proc-macro = true quote = "1" proc-macro2 = "1.0" syn = "1.0" +config = "0.15.25" diff --git a/src/lib.rs b/src/lib.rs index 1653857..23c20d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,8 @@ extern crate proc_macro; extern crate proc_macro2; -use proc_macro2::TokenStream; use std::fmt::{Debug, Formatter}; -use proc_macro2::{Group, TokenTree}; +use proc_macro2::Literal; use quote::quote; use syn::parse::{Parse, ParseStream}; use syn::{parse_macro_input, Expr, Type, Ident, braced}; @@ -148,26 +147,34 @@ impl Debug for ParseConfigDefinition { } #[proc_macro] -pub fn config(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn config(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // parse the input and prepare the output let definition: ParseConfigDefinition = parse_macro_input!(input as ParseConfigDefinition); - let mut out = proc_macro2::TokenStream::new(); + let mut out = quote!{ + use ::config as _config_deser; + + pub enum LFCMError { + UnableToBuild, + ParseError(_config_deser::ConfigError) + } + }; // generate the class Structure let mut config_sections: Vec = Vec::new(); walk_config_sections(&definition, &mut config_sections); - for config_section in config_sections.iter() { - println!("Section: {}", config_section.name.span().source_text().unwrap_or("".to_string())); - } - - for section in config_sections.iter() { - emit_section_struct(&mut out, section) - } - // TODO: generate implementations for necessary output + for section in config_sections.iter() { + // Generate the struct representation of the configuration + emit_section_struct(&mut out, section); + // TODO: generate Deserialization code + // generate the deserialization code for the configuration + emit_section_deser_impl(&mut out, section); + } + + // TODO: generate the combined deserialization function + emit_deser_impl(&mut out, definition); - println!("Seems done"); proc_macro::TokenStream::from(out) } @@ -220,6 +227,7 @@ fn emit_section_struct(out: &mut proc_macro2::TokenStream, section: &ParseConfig let type_name = capitalize_name(§ion.name.to_string()); let type_name_ident = Ident::new(type_name.as_str(), section.name.span()); let struct_stream = quote! { + #[derive(Debug)] pub struct #type_name_ident { #(#vars),* #(#subsections),* @@ -227,4 +235,67 @@ fn emit_section_struct(out: &mut proc_macro2::TokenStream, section: &ParseConfig }; out.extend(struct_stream.into_iter()); +} + +fn emit_section_deser_impl(out: &mut proc_macro2::TokenStream, section: &ParseConfigSection) { + // generate deserialisation for all variables + let var_deser_parts = section.vars.iter().map(|var| { + let var_name = var.name.clone(); + let var_name_lit = Literal::string(var.name.to_string().as_str()); + let var_default = var.default.clone(); + quote!{ + #var_name: match cfg.get((prefix.to_owned() + #var_name_lit).as_str()) { + Ok (value) => value, + Err (_config_deser::ConfigError::NotFound(_)) => #var_default, + Err (x) => return Err(LFCMError::ParseError(x)) + } + } + }).collect::>(); + + // generate deserialisation calls for all substructs + let subsection_deser_parts = section.subsections.iter().map(|subsection| { + let subsection_name = subsection.name.clone(); + let subsection_type_name = Ident::new(capitalize_name(&subsection.name.to_string()).as_str(), subsection.name.span()); + let subsection_name_lit = Literal::string(subsection.name.to_string().as_str()); + quote!{ + #subsection_name: #subsection_type_name::deser(cfg, (prefix.to_owned() + #subsection_name_lit + ".").as_str())? + } + }).collect::>(); + + let section_type_name_ident = Ident::new(capitalize_name(section.name.to_string().as_str()).as_str(), section.name.span()); + + out.extend(quote! { + impl #section_type_name_ident { + fn deser(cfg: &_config_deser::Config, prefix: &str) -> Result { + Ok (Self { + #(#var_deser_parts,)* + #(#subsection_deser_parts,)* + }) + } + } + }) +} + +fn emit_deser_impl(out: &mut proc_macro2::TokenStream, definition: ParseConfigDefinition) { + // name of the config object + let cfg_type_name = capitalize_name(&definition.name.to_string()); + let cfg_type_name_ident = Ident::new(cfg_type_name.as_str(), definition.name.span()); + + // this is a new time + out.extend(quote! { + pub fn lfcm_load_config(files: Vec<&str>) -> Result<#cfg_type_name_ident, LFCMError> { + let mut _read_config = _config_deser::Config::builder(); + for path in files { + _read_config = _read_config.add_source(_config_deser::File::with_name(path)); + } + let _cfg_data = match _read_config.build() { + Err (error) => { + return Err (LFCMError::UnableToBuild) + }, + Ok (cfg) => cfg + }; + + #cfg_type_name_ident::deser(&_cfg_data, "") + } + }); } \ No newline at end of file diff --git a/tests/fixtures/test1.yaml b/tests/fixtures/test1.yaml new file mode 100644 index 0000000..2cf60f7 --- /dev/null +++ b/tests/fixtures/test1.yaml @@ -0,0 +1 @@ +rand: 15 \ No newline at end of file diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 5d54e24..56d85ee 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,5 +1,7 @@ +use config::ConfigError; use lfcm::config; + config! { param name = Config; section top { @@ -7,11 +9,21 @@ config! { section database { var seed: u64 = 42; } + section test { + var reps: u64 = 10; + } } } + #[test] fn test_parser() { - //println!("{:?}"); + let config = match lfcm_load_config(vec![ + "tests/fixtures/test1.yaml", + ]) { + Ok(config) => config, + Err(e) => return, + }; + println!("{:?}", config); } \ No newline at end of file