diff --git a/config-parser/config-parser-common/src/common.rs b/config-parser/config-parser-common/src/common.rs index f3d48ca..9889ffe 100644 --- a/config-parser/config-parser-common/src/common.rs +++ b/config-parser/config-parser-common/src/common.rs @@ -1,23 +1,49 @@ #![allow(dead_code)] -pub fn parse_config_file(input: &String) -> std::io::Result> { - let mut config = std::collections::HashMap::::new(); +use std::collections::HashMap; + +/// Holds config, value is either a `String` or a nested `ConfigMap` +pub type ConfigMap = HashMap; + +/// 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) { + let mut config = ConfigMap::new(); + let mut pointer: &mut ConfigMap = &mut config; + let mut messages: Vec = Vec::::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 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 = parse_config_file(input)?; + let (mut #config_name, mut #output_name) : (ConfigMap, Vec) = 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) -> 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) -> TokenStream { } } -pub fn gen_to_string(name: &Ident, fields: &Punctuated) -> 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) -> TokenStream { let mut defaults: TokenStream = TokenStream::new(); 'fields: for field in fields.iter() { @@ -94,33 +86,44 @@ pub fn gen_default(fields: &Punctuated) -> 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, config_map_name: &syn::Ident) -> TokenStream { +pub fn gen_config_assignments(fields: &Punctuated, 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, 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() -} diff --git a/config-parser/config-parser-macro/src/lib.rs b/config-parser/config-parser-macro/src/lib.rs index 47cdede..acd9496 100644 --- a/config-parser/config-parser-macro/src/lib.rs +++ b/config-parser/config-parser-macro/src/lib.rs @@ -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` /// 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 } diff --git a/src/config.rs b/src/config.rs index a89ebc6..d16dd84 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,42 +22,57 @@ pub struct Config { pub struct GeneralSettings { #[default_value(false)] pub show_stack_on_push: bool, - #[no_config] + #[default_value(false)] pub show_stack_on_pop: bool, - #[default_value = false] + #[default_value(false)] pub show_books_on_bookmark: bool, } #[derive(Debug, Clone, Default, ConfigParser)] pub struct FormatSettings { + #[default_value(true)] pub align_separators: bool, + #[default_value(" - ")] pub stack_separator: String, + #[default_value(false)] pub stack_home_as_tilde: bool, + #[default_value(" - ")] pub bookmarks_separator: String, + #[default_value(false)] pub book_home_as_tilde: bool, } #[derive(Debug, Clone, Default, ConfigParser)] pub struct StyleSettings { #[style_config] + #[default_value("yellow, italic")] pub warning_style: String, #[style_config] + #[default_value("red, bold")] pub error_style: String, #[style_config] + #[default_value("default")] pub stack_number_style: String, #[style_config] + #[default_value("cyan")] pub stack_separator_style: String, #[style_config] + #[default_value("default")] pub stack_path_style: String, #[style_config] + #[default_value("magenta")] pub stack_punct_style: String, #[style_config] + #[default_value("default")] pub bookmarks_name_style: String, #[style_config] + #[default_value("cyan")] pub bookmarks_seperator_style: String, #[style_config] + #[default_value("default")] pub bookmarks_path_style: String, #[style_config] + #[default_value("magenta")] pub bookmarks_punct_style: String, } @@ -66,32 +81,7 @@ impl Config { /// generates and populates a new instance of Config pub fn new(styles_as_ansi_sequences: bool) -> Result { - let mut config = Config { - general: GeneralSettings { - show_stack_on_push: false, - show_stack_on_pop: false, - show_books_on_bookmark: false, - }, - format: FormatSettings { - align_separators: false, - stack_separator: " - ".to_owned(), - stack_home_as_tilde: true, - bookmarks_separator: " - ".to_owned(), - book_home_as_tilde: true, - }, - styles: StyleSettings { - warning_style: "default".to_owned(), - error_style: "default".to_owned(), - stack_number_style: "default".to_owned(), - stack_separator_style: "default".to_owned(), - stack_path_style: "default".to_owned(), - stack_punct_style: "default".to_owned(), - bookmarks_name_style: "default".to_owned(), - bookmarks_seperator_style: "default".to_owned(), - bookmarks_path_style: "default".to_owned(), - bookmarks_punct_style: "default".to_owned(), - }, - }; + let mut config = Self::default(); // get configuration directory let mut conf_file = match config_dir() { Some(value) => value, diff --git a/src/main.rs b/src/main.rs index 492bbb4..38bc558 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use bookmarks::*; use config_parser::*; use output::Output; use stack::Stack; +use util::to_rooted; use std::env::{current_dir, var}; use std::io::{Error, Result}; use std::path::{Path, PathBuf}; @@ -181,17 +182,21 @@ fn remove_bookmarks(args: &BookmarkSubArgs, config: &Config, bookmarks: &mut Boo /// push path to stack and print command to navigate to provided path fn push_path(path: &Path, stack: &mut Stack, config: &Config, output: &mut Output) -> Result<()> { + let mut path = path.to_path_buf(); + let mut current_path: PathBuf = current_dir()?; + to_rooted(&mut path)?; + to_rooted(&mut current_path)?; if !path.is_dir() { return Err(Error::other("-- invalid path argument")); + } else if !(path == current_path) { + stack.push_entry(¤t_path)?; + output.push_command(&format!("cd -- {}", match path.canonicalize()?.to_str() { + Some(value) => value, + None => return Err(Error::other("-- failed to print provided path as string")), + })); } - let current_path: PathBuf = current_dir()?; - stack.push_entry(¤t_path)?; if config.general.show_stack_on_push { output.push_info(&stack.to_formatted_string(&config)?); } - output.push_command(&format!("cd -- {}", match path.canonicalize()?.to_str() { - Some(value) => value, - None => return Err(Error::other("-- failed to print provided path as string")), - })); Ok(()) }