quick save

This commit is contained in:
2025-01-18 22:27:33 +01:00
parent 1abb470dd6
commit 281ca56741
6 changed files with 145 additions and 104 deletions
@@ -1,23 +1,49 @@
#![allow(dead_code)]
pub fn parse_config_file(input: &String) -> std::io::Result<std::collections::HashMap<String, String>> {
let mut config = std::collections::HashMap::<String, String>::new();
use std::collections::HashMap;
/// Holds config, value is either a `String` or a nested `ConfigMap`
pub type ConfigMap = HashMap<String, ConfigElement>;
/// Element of Config, either a `String` for a setting
/// or a nested `ConfigMap`
pub enum ConfigElement {
Setting(String),
Nested(ConfigMap),
}
pub fn parse_config_file(input: &String) -> (ConfigMap, Vec<String>) {
let mut config = ConfigMap::new();
let mut pointer: &mut ConfigMap = &mut config;
let mut messages: Vec<String> = Vec::<String>::new();
let lines = input.lines();
for line in lines {
for (n, line) in lines.enumerate() {
let line = line.trim();
// ignore empty lines
if line.is_empty() { continue; }
if line.starts_with("[") {
if line.starts_with("[[") {
messages.push(format!("error on line #{n} - arrays are not supported in config file:\n{}", line));
} else if line.starts_with("[") {
// check for table
if !line.ends_with("]") {
// TODO: implement error handling
} else if line.contains(' ') {
// TODO: implement error handling
messages.push(format!("error on line #{n} - table name is not properly terminated (missing ']'):\n{}", line));
} else if line.contains([' ', '\t']) {
messages.push(format!("error on line #{n} - no white space allowed in table names"));
}
let mut tokens: Vec<&str> = line.split(['.', '[', ']']).map(|entry| entry.trim()).collect();
tokens.retain(|entry| !entry.is_empty());
pointer = &mut config;
for token in tokens {
pointer = match pointer.entry(token.to_string()).or_insert(ConfigElement::Nested(ConfigMap::new())) {
ConfigElement::Nested(entry) => entry,
_ => {
panic!("error occured handling line #{} - tried to insert a `Nested` element into ConfigMap, but found a `Setting` element of the same name ({})", n, token);
}
};
}
//let tokens = line.split('.');
// TODO: implement hirarchical map
} else {
// check for config
let mut tokens: Vec<&str> = line.split('=').map(|entry| entry.trim()).collect();
@@ -27,13 +53,15 @@ pub fn parse_config_file(input: &String) -> std::io::Result<std::collections::Ha
// println!("error in line'", line);
continue;
}
// clean up value: remove quotes.. TODO:
let option = tokens[1].replace(&['\"', '\''][..], "");
// remove characters not wanted in output
let key = tokens[0];
let value = tokens[1].replace(&['\"', '\''][..], "");
config.insert(tokens[0].to_string(), option);
pointer.insert(key.to_string(), ConfigElement::Setting(value));
}
}
Ok(config)
(config, messages)
}
@@ -2,4 +2,8 @@
pub mod common;
pub mod format;
pub use common::parse_config_file;
pub use common::{
ConfigElement,
ConfigMap,
parse_config_file,
};
@@ -1,15 +1,15 @@
use proc_macro2::{Ident, TokenStream};
use syn::{Attribute, Field, Meta, Path, punctuated::Punctuated, token::Comma};
use syn::{Attribute, Field, Meta, MetaList, Path, punctuated::Punctuated, token::Comma};
use quote::quote;
pub fn gen_parse_from_string(config_name: &Ident, assignments: &TokenStream) -> TokenStream {
pub fn gen_parse_from_string(config_name: &Ident, output_name: &Ident, assignments: &TokenStream) -> TokenStream {
quote! {
/// tries to parse config from a string
/// if **convert_styles** == true, the settings marked with
/// `style_config` are converted to ansi escape sequences to
/// style terminal ouput
pub fn parse_from_string(&mut self, input: &String) -> std::io::Result<()> {
let mut #config_name : std::collections::HashMap<String, String> = parse_config_file(input)?;
let (mut #config_name, mut #output_name) : (ConfigMap, Vec<String>) = parse_config_file(input);
#assignments
@@ -27,8 +27,9 @@ pub fn gen_parse_from_map(config_name: &Ident, assignments: &TokenStream) -> Tok
/// **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: &mut std::collections::HashMap<String, String>) -> std::io::Result<()> {
pub fn parse_from_map(&mut self, input: &mut ConfigMap) -> std::io::Result<()> {
let mut #config_name = input;
let mut output: String = String::new();
#assignments
@@ -74,15 +75,6 @@ pub fn gen_to_ansi_sequences(fields: &Punctuated<Field, Comma>) -> TokenStream {
}
}
pub fn gen_to_string(name: &Ident, fields: &Punctuated<Field, Comma>) -> TokenStream {
quote! {
/// prints configuration to `String`
//pub fn to_string() -> String {
// let mut default = "# default configuration file for `navigate`\n".to_string();
//}
}
}
pub fn gen_default(fields: &Punctuated<Field, Comma>) -> TokenStream {
let mut defaults: TokenStream = TokenStream::new();
'fields: for field in fields.iter() {
@@ -94,33 +86,44 @@ pub fn gen_default(fields: &Punctuated<Field, Comma>) -> TokenStream {
};
let ty = &field.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 == "default_value" {
let default_value = get_attribute_value(attribute);
defaults.extend(quote! {
#name: #default_value,
match attribute {
Attribute { meta: Meta::Path(Path{segments, ..}), .. } => {
let attr_name = match segments.first() {
Some(value) => value,
None => panic!("no valid attribute found!"),
};
if attr_name.ident == "nested_config" {
defaults.extend(quote!{
#name: #ty::default(),
});
} else if value.ident == "nested_config" {
defaults.extend(quote! {
#name: #ty.default()?;
}
},
Attribute { meta: Meta::List(MetaList{ path: Path{ segments, .. }, tokens, .. }), .. } => {
let attr_name = match segments.first() {
Some(value) => value,
None => panic!("no valid attribute found!"),
};
if attr_name.ident == "default_value" {
defaults.extend(quote!{
#name: #tokens.into(),
});
},
None => (),
}
}
},
_ => (),
}
}
};
quote!{
/// returns an instance with default values
pub fn default(&mut self) -> std::io::Result<()> {
#defaults
Ok(())
pub fn default() -> Self {
Self {
#defaults
}
}
}
}
pub fn gen_config_assignments(fields: &Punctuated<Field, Comma>, config_map_name: &syn::Ident) -> TokenStream {
pub fn gen_config_assignments(fields: &Punctuated<Field, Comma>, config_map_name: &syn::Ident, output_name: &syn::Ident) -> TokenStream {
let mut assignments : TokenStream = TokenStream::new();
'fields: for field in fields.iter() {
let attr = &field.attrs;
@@ -132,39 +135,44 @@ pub fn gen_config_assignments(fields: &Punctuated<Field, Comma>, config_map_name
let name_string: String = name.to_string();
let ty = &field.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" {
assignments.extend(quote! {
self.#name.parse_from_map(&mut #config_map_name);
});
continue 'fields;
} else if value.ident == "no_config" {
continue 'fields;
if let Attribute{ meta: Meta::Path( Path{segments, ..} ), .. } = attribute {
match segments.first() {
Some(attr_name) => {
if attr_name.ident == "nested_config" {
assignments.extend(quote! {
if let Some(value) = #config_map_name.get(#name_string) {
self.#name.parse_from_map(&mut value);
} else {
#output_name.push(&format!("no table `{}` found in config file", #name_string));
}
});
continue 'fields;
} else if attr_name.ident == "no_config" {
continue 'fields;
}
},
None => (),
}
}
//} else if let Attribute{ meta: Meta::List()}
}
assignments.extend(quote! {
self.#name = match #config_map_name.get(#name_string) {
Some(value) => match value.parse::<#ty>() {
if let Some(value) = #config_map_name.get(#name_string) {
self.#name = match value.parse::<#ty>() {
Ok(parsed) => {
parsed
},
// TODO: implement warnings about errors here
Err(_) => {
#output_name.push(format!("failed to parse value found for `{}`", #name_string));
self.#name.clone()
},
},
None => self.#name.clone(),
};
} else {
#output_name.push(format!("could not find `{}` in config file", #name_string));
self.#name = self.#name.clone();
};
});
}
assignments
}
fn get_attribute_value(attribute: &Attribute) -> TokenStream {
//if let Attribute{ Meta::List }
TokenStream::new()
}
+15 -9
View File
@@ -11,31 +11,37 @@ use generator_functions::*;
/// which parses a string and fills the fills recognised values into the struct
/// - implements `write_default_config() -> Result<String>`
/// which write a default configuration, in case the documentation is lacking
#[proc_macro_derive(ConfigParser, attributes(default_value, nested_config, no_config, style_config))]
#[proc_macro_derive(
ConfigParser,
attributes(
default_value,
nested_config,
no_config,
style_config
)
)]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let config_name = syn::Ident::new("config", name.span()); // TODO: use correct span
let config_name = syn::Ident::new("config", name.span());
let output_name = syn::Ident::new("output", name.span());
let fields = if let syn::Data::Struct(syn::DataStruct{ fields: syn::Fields::Named(syn::FieldsNamed{ ref named, .. }), .. }) = ast.data {
named
} else {
panic!("the macro `ConfigParser` applies only to structs!");
};
if name.to_string() == "GeneralSettings" {
println!("ast = {:#?}", ast);
}
let assignments: TokenStream = gen_config_assignments(fields, &config_name);
let func_parse_string: TokenStream = gen_parse_from_string(&config_name, &assignments);
let assignments: TokenStream = gen_config_assignments(fields, &config_name, &output_name);
let func_parse_string: TokenStream = gen_parse_from_string(&config_name, &output_name, &assignments);
let func_parse_map: TokenStream = gen_parse_from_map(&config_name, &assignments);
let func_to_ansi_sequences: TokenStream = gen_to_ansi_sequences(fields);
//let func_to_string = gen_to_string(&name, &fields);
let func_default: TokenStream = gen_default(fields);
quote! {
impl #name {
#func_parse_string
#func_parse_map
#func_to_ansi_sequences
//#func_to_string
#func_default
// TODO: implement function to parse style settings
}