first working version

This commit is contained in:
Linus Vogel 2026-08-17 17:34:55 +02:00
parent 92b8593be9
commit cd2fcf90e1
6 changed files with 101 additions and 14 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target /target
**/*.rs.bk **/*.rs.bk
Cargo.lock Cargo.lock
.idea/

1
.idea/lfcm.iml generated
View File

@ -4,6 +4,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.idea/dictionaries" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />

View File

@ -15,4 +15,5 @@ proc-macro = true
quote = "1" quote = "1"
proc-macro2 = "1.0" proc-macro2 = "1.0"
syn = "1.0" syn = "1.0"
config = "0.15.25"

View File

@ -1,9 +1,8 @@
extern crate proc_macro; extern crate proc_macro;
extern crate proc_macro2; extern crate proc_macro2;
use proc_macro2::TokenStream;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use proc_macro2::{Group, TokenTree}; use proc_macro2::Literal;
use quote::quote; use quote::quote;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::{parse_macro_input, Expr, Type, Ident, braced}; use syn::{parse_macro_input, Expr, Type, Ident, braced};
@ -148,26 +147,34 @@ impl Debug for ParseConfigDefinition {
} }
#[proc_macro] #[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 // parse the input and prepare the output
let definition: ParseConfigDefinition = parse_macro_input!(input as ParseConfigDefinition); 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 // generate the class Structure
let mut config_sections: Vec<ParseConfigSection> = Vec::new(); let mut config_sections: Vec<ParseConfigSection> = Vec::new();
walk_config_sections(&definition, &mut config_sections); walk_config_sections(&definition, &mut config_sections);
for config_section in config_sections.iter() {
println!("Section: {}", config_section.name.span().source_text().unwrap_or("<no-name?>".to_string()));
}
for section in config_sections.iter() {
emit_section_struct(&mut out, section)
}
// TODO: generate implementations for necessary output // 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) 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(&section.name.to_string()); let type_name = capitalize_name(&section.name.to_string());
let type_name_ident = Ident::new(type_name.as_str(), section.name.span()); let type_name_ident = Ident::new(type_name.as_str(), section.name.span());
let struct_stream = quote! { let struct_stream = quote! {
#[derive(Debug)]
pub struct #type_name_ident { pub struct #type_name_ident {
#(#vars),* #(#vars),*
#(#subsections),* #(#subsections),*
@ -227,4 +235,67 @@ fn emit_section_struct(out: &mut proc_macro2::TokenStream, section: &ParseConfig
}; };
out.extend(struct_stream.into_iter()); 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::<Vec<_>>();
// 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::<Vec<_>>();
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<Self, LFCMError> {
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, "")
}
});
} }

1
tests/fixtures/test1.yaml vendored Normal file
View File

@ -0,0 +1 @@
rand: 15

View File

@ -1,5 +1,7 @@
use config::ConfigError;
use lfcm::config; use lfcm::config;
config! { config! {
param name = Config; param name = Config;
section top { section top {
@ -7,11 +9,21 @@ config! {
section database { section database {
var seed: u64 = 42; var seed: u64 = 42;
} }
section test {
var reps: u64 = 10;
}
} }
} }
#[test] #[test]
fn test_parser() { fn test_parser() {
//println!("{:?}"); let config = match lfcm_load_config(vec![
"tests/fixtures/test1.yaml",
]) {
Ok(config) => config,
Err(e) => return,
};
println!("{:?}", config);
} }