diff --git a/.gitignore b/.gitignore index d01bd1a..b807e94 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,6 @@ Cargo.lock # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ + +/*.txt diff --git a/config-parser/config-parser-common/src/common.rs b/config-parser/config-parser-common/src/common.rs index ff75a9a..9a88a78 100644 --- a/config-parser/config-parser-common/src/common.rs +++ b/config-parser/config-parser-common/src/common.rs @@ -1,51 +1,39 @@ #![allow(dead_code)] -use proc_macro2::TokenStream; -use quote::quote; +pub fn parse_config_file(input: &String) -> std::io::Result> { + let mut config = std::collections::HashMap::::new(); + let lines = input.lines(); -use syn::{Attribute, Error, Field, Meta, Path, punctuated::Punctuated, token::Comma}; + for line in lines { + let line = line.trim(); + // ignore empty lines + if line.is_empty() { continue; } -pub fn gen_config_load_function(fields: &Punctuated, config_map_name: &syn::Ident) -> Result { - let mut assignments : TokenStream = TokenStream::new(); - 'field_loop: for field in fields.iter() { - let attr = &field.attrs; - let name = match &field.ident { - Some(value) => value, - // skip anonymous fields - None => continue 'field_loop, - }; - 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 'field_loop; - } else if value.ident == "no_config" { - continue 'field_loop; - }, - None => (), - } + 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()); + // check for valid input + if tokens.len() != 2 { + // println!("error in line'", line); + continue; + } + //// clean up value: remove quotes.. TODO: + //let mut options: Vec<&str> = tokens[1].split(['\'', '\"']).collect::>().join(','); + + config.insert(tokens[0].to_string(), tokens[1].to_string()); } - assignments.extend(quote! { - self.#name = match #config_map_name.get(#name_string) { - Some(value) => match value.parse::<#ty>() { - Ok(parsed) => { - _ = #config_map_name.remove(#name_string); - parsed - }, - Err(_) => { - self.#name.clone() - }, - }, - None => self.#name.clone(), - }; - }); + } - Ok(assignments.into()) + Ok(config) } diff --git a/config-parser/config-parser-common/src/format.rs b/config-parser/config-parser-common/src/format.rs index f401820..9bb7a1e 100644 --- a/config-parser/config-parser-common/src/format.rs +++ b/config-parser/config-parser-common/src/format.rs @@ -189,7 +189,7 @@ pub fn make_padding_string(len: usize) -> String { /// input format is a quoted string (either double or single) /// the style can be a combination of **one** color and /// one or more style options (bold, italic, underlined, strikethrough) -pub fn parse_style(arg: String) -> Result { +pub fn parse_style(arg: &String) -> Result { let mut colors: Vec = Vec::::new(); let mut styles: Vec = Vec::::new(); diff --git a/config-parser/config-parser-common/src/lib.rs b/config-parser/config-parser-common/src/lib.rs index 896ad65..0f5eaee 100644 --- a/config-parser/config-parser-common/src/lib.rs +++ b/config-parser/config-parser-common/src/lib.rs @@ -1,3 +1,5 @@ pub mod common; pub mod format; + +pub use common::parse_config_file; diff --git a/config-parser/config-parser-macro/src/generator_functions.rs b/config-parser/config-parser-macro/src/generator_functions.rs new file mode 100644 index 0000000..34e39e4 --- /dev/null +++ b/config-parser/config-parser-macro/src/generator_functions.rs @@ -0,0 +1,128 @@ +use proc_macro2::{Ident, TokenStream}; +use syn::{Attribute, Field, Meta, Path, punctuated::Punctuated, token::Comma}; +use quote::quote; + +pub fn gen_parse_from_string(config_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)?; + + #assignments + + if !#config_name.is_empty() { + let leftovers = #config_name.keys().cloned().collect::>(); + return Err(std::io::Error::other(format!("the following settings were not recognised: {:#?}", leftovers))); + } + Ok(()) + } + } +} + +pub fn gen_parse_from_map(config_name: &Ident, assignments: &TokenStream) -> TokenStream { + quote! { + /// **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<()> { + let mut #config_name = input; + + #assignments + + Ok(()) + } + } +} + +pub fn gen_to_ansi_sequences(fields: &Punctuated) -> TokenStream { + let mut conversions: TokenStream = TokenStream::new(); + 'fields: for field in fields.iter() { + let attr = &field.attrs; + let name = match &field.ident { + Some(value) => value, + // skip anonymous fields + None => continue 'fields, + }; + for attribute in attr { + if let Attribute{ meta: Meta::Path( Path{segments: attr_name, ..} ), .. } = attribute { + match attr_name.first() { + Some(value) => if value.ident == "style_config" { + conversions.extend(quote! { + self.#name = match config_parser::parse_style(&self.#name) { + Ok(value) => value, + Err(_) => return Err(std::io::Error::other(format!("failed to convert '{}' to ansi escape sequence", self.#name))), + }; + }); + } else if value.ident == "nested_config" { + conversions.extend(quote! { + self.#name.to_ansi_sequences()?; + }); + }, + None => (), + } + } + } + }; + quote!{ + fn to_ansi_sequences(&mut self) -> std::io::Result<()> { + #conversions + Ok(()) + } + } +} + +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_config_assignments(fields: &Punctuated, config_map_name: &syn::Ident) -> TokenStream { + let mut assignments : TokenStream = TokenStream::new(); + 'fields: for field in fields.iter() { + let attr = &field.attrs; + let name = match &field.ident { + Some(value) => value, + // skip anonymous fields + None => continue 'fields, + }; + 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; + }, + None => (), + } + } + } + assignments.extend(quote! { + self.#name = match #config_map_name.get(#name_string) { + Some(value) => match value.parse::<#ty>() { + Ok(parsed) => { + parsed + }, + Err(_) => { + self.#name.clone() + }, + }, + None => self.#name.clone(), + }; + }); + } + assignments +} + diff --git a/config-parser/config-parser-macro/src/lib.rs b/config-parser/config-parser-macro/src/lib.rs index d3ccaf5..db2f6d5 100644 --- a/config-parser/config-parser-macro/src/lib.rs +++ b/config-parser/config-parser-macro/src/lib.rs @@ -1,14 +1,17 @@ -use config_parser_common::common::gen_config_load_function; +mod generator_functions; + use proc_macro2::TokenStream; use syn::{parse_macro_input, DeriveInput}; use quote::quote; +use generator_functions::*; + /// **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(color_config, nested_config, no_config))] +#[proc_macro_derive(ConfigParser, attributes(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; @@ -18,83 +21,21 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { } else { panic!("the macro `ConfigParser` applies only to structs!"); }; - let assignments = match gen_config_load_function(fields, &config_name) { - Ok(value) => value, - Err(_) => panic!("loading config failed"), - }; + let assignments: TokenStream = gen_config_assignments(fields, &config_name); + let func_parse_string: TokenStream = gen_parse_from_string(&config_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); - if name.to_string() == "Settings" { - let string = format!("{:#?}", ast); - _ = std::fs::write("test.txt", string); - } - - let expanded_stream: TokenStream = quote! { + quote! { impl #name { - /// tries to parse config from a string - pub fn parse_from_string(&mut self, input: &String) -> std::io::Result<()> { - let mut #config_name : std::collections::HashMap = Self::parse_config_file(input)?; - - #assignments - - if !#config_name.is_empty() { - let leftovers = #config_name.keys().cloned().collect::>(); - return Err(std::io::Error::other(format!("the following settings were not recognised: {:#?}", leftovers))); - } - 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: &mut std::collections::HashMap) -> std::io::Result<()> { - let mut #config_name = input; - - #assignments - - Ok(()) - } - - 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) - } - - // TODO: implement - pub fn write_default_config(&self) -> Result<()> { - Ok(()) - } + #func_parse_string + #func_parse_map + #func_to_ansi_sequences + //#func_to_string // TODO: implement function to parse style settings } - }.into(); - expanded_stream.into() + }.into() } diff --git a/config-parser/src/lib.rs b/config-parser/src/lib.rs index bfb70ff..6380288 100644 --- a/config-parser/src/lib.rs +++ b/config-parser/src/lib.rs @@ -1,3 +1,3 @@ -pub use config_parser_common::format; +pub use config_parser_common::{common::*, format::*}; pub use config_parser_macro::ConfigParser; diff --git a/src/bookmarks.rs b/src/bookmarks.rs index eba1f1d..1960e09 100644 --- a/src/bookmarks.rs +++ b/src/bookmarks.rs @@ -9,9 +9,8 @@ use std::io::{Error, Result}; use std::path::PathBuf; use std::str::FromStr; -use config_parser::format::make_padding_string; - -use super::{apply_format, config::*}; +use super::config::*; +use config_parser::{make_padding_string, apply_format}; #[derive(Debug, Clone)] pub struct Bookmarks { @@ -132,12 +131,12 @@ impl Bookmarks { }; for (mark, path) in &self.bookmarks { let padding = make_padding_string(max_name_len - mark.len()); - let name = apply_format(mark, &config.styles.bookmarks_name); + let name = apply_format(mark, &config.styles.bookmarks_name_style); let separator = apply_format( &config.format.bookmarks_separator, - &config.styles.bookmarks_seperator, + &config.styles.bookmarks_seperator_style, ); - let path = apply_format(path.to_str().unwrap(), &config.styles.bookmarks_path); + let path = apply_format(path.to_str().unwrap(), &config.styles.bookmarks_path_style); if config.format.align_separators { buffer.push_str(&format!("{}{}{}{}\n", name, padding, separator, path)); } else { diff --git a/src/config.rs b/src/config.rs index de40eec..0513349 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,8 +7,9 @@ use dirs::config_dir; use std::fs; use std::io::{Error, Result}; use std::path::PathBuf; -use config_parser::ConfigParser; -use config_parser::format::*; +use config_parser::*; + +use crate::debug::debug_print; #[derive(Debug, Clone)] pub struct Config { @@ -42,25 +43,25 @@ pub struct FormatSettings { #[derive(Debug, Clone, Default, ConfigParser)] pub struct StyleSettings { - #[color_config] - pub stack_number: String, - #[color_config] - pub stack_separator: String, - #[color_config] - pub stack_path: String, - #[color_config] - pub bookmarks_name: String, - #[color_config] - pub bookmarks_seperator: String, - #[color_config] - pub bookmarks_path: String, + #[style_config] + pub stack_number_style: String, + #[style_config] + pub stack_separator_style: String, + #[style_config] + pub stack_path_style: String, + #[style_config] + pub bookmarks_name_style: String, + #[style_config] + pub bookmarks_seperator_style: String, + #[style_config] + pub bookmarks_path_style: String, } impl Config { const CONFIG_FILE_NAME: &str = "navigate.conf"; /// generates and populates a new instance of Config - pub fn new() -> Result { + pub fn new(styles_as_ansi_sequences: bool) -> Result { let mut config = Config { conf_file: PathBuf::new(), settings: Settings { @@ -75,12 +76,12 @@ impl Config { align_separators: false, }, styles: StyleSettings { - stack_number: String::new(), - stack_separator: String::new(), - stack_path: String::new(), - bookmarks_name: String::new(), - bookmarks_seperator: String::new(), - bookmarks_path: String::new(), + stack_number_style: String::new(), + stack_separator_style: String::new(), + stack_path_style: String::new(), + bookmarks_name_style: String::new(), + bookmarks_seperator_style: String::new(), + bookmarks_path_style: String::new(), }, }, }; @@ -113,6 +114,10 @@ impl Config { //config.set_default_settings()?; //config.parse_color_settings()?; + if styles_as_ansi_sequences { + config.settings.to_ansi_sequences()?; + } + Ok(config) } @@ -140,40 +145,25 @@ impl Config { self.settings.format.bookmarks_separator = default_separator.clone(); } - if self.settings.styles.stack_number.is_empty() { - self.settings.styles.stack_number = default_number_color.clone(); + if self.settings.styles.stack_number_style.is_empty() { + self.settings.styles.stack_number_style = default_number_color.clone(); } - if self.settings.styles.stack_separator.is_empty() { - self.settings.styles.stack_separator = default_separator_color.clone(); + if self.settings.styles.stack_separator_style.is_empty() { + self.settings.styles.stack_separator_style = default_separator_color.clone(); } - if self.settings.styles.stack_path.is_empty() { - self.settings.styles.stack_path = default_path_color.clone(); + if self.settings.styles.stack_path_style.is_empty() { + self.settings.styles.stack_path_style = default_path_color.clone(); } - if self.settings.styles.bookmarks_name.is_empty() { - self.settings.styles.bookmarks_name = default_number_color.clone(); + if self.settings.styles.bookmarks_name_style.is_empty() { + self.settings.styles.bookmarks_name_style = default_number_color.clone(); } - if self.settings.styles.bookmarks_seperator.is_empty() { - self.settings.styles.bookmarks_seperator = default_separator_color.clone(); + if self.settings.styles.bookmarks_seperator_style.is_empty() { + self.settings.styles.bookmarks_seperator_style = default_separator_color.clone(); } - if self.settings.styles.bookmarks_path.is_empty() { - self.settings.styles.bookmarks_path = default_path_color.clone(); + if self.settings.styles.bookmarks_path_style.is_empty() { + self.settings.styles.bookmarks_path_style = default_path_color.clone(); } Ok(()) } - - /// convert color settings to ansi escape sequences - pub fn parse_color_settings(&mut self) -> Result<()> { - self.settings.styles.stack_number = parse_style(self.settings.styles.stack_number.clone())?; - self.settings.styles.stack_separator = - parse_style(self.settings.styles.stack_separator.clone())?; - self.settings.styles.stack_path = parse_style(self.settings.styles.stack_path.clone())?; - self.settings.styles.bookmarks_name = - parse_style(self.settings.styles.bookmarks_name.clone())?; - self.settings.styles.bookmarks_seperator = - parse_style(self.settings.styles.bookmarks_seperator.clone())?; - self.settings.styles.bookmarks_path = - parse_style(self.settings.styles.bookmarks_path.clone())?; - Ok(()) - } } diff --git a/src/main.rs b/src/main.rs index 048400f..5c0dd93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use arguments::*; use clap::Parser; use config::*; use bookmarks::*; -use config_parser::format::*; +use config_parser::*; use stack::Stack; use std::env::{current_dir, var}; use std::io::{Error, Result}; @@ -25,7 +25,7 @@ fn main() -> Result<()> { return Ok(()); } }; - let config = match Config::new() { + let config = match Config::new(true) { Ok(value) => value, Err(error) => { print!("echo '{}{}{}' && false", style_error, error, RESET_SEQ); diff --git a/src/stack.rs b/src/stack.rs index 1dade72..c655b76 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -41,12 +41,12 @@ impl Stack { let max_num_len = self.stack.len().to_string().len(); for (n, item) in self.stack.iter().rev().enumerate() { let padding = make_padding_string(max_num_len - n.to_string().len()); - let number = apply_format(&n.to_string(), &config.styles.stack_number); + let number = apply_format(&n.to_string(), &config.styles.stack_number_style); let separator = apply_format( &config.format.stack_separator, - &config.styles.stack_separator, + &config.styles.stack_separator_style, ); - let path = apply_format(item.to_str().unwrap(), &config.styles.stack_path); + let path = apply_format(item.to_str().unwrap(), &config.styles.stack_path_style); if config.format.align_separators { buffer.push_str(&format!("{}{}{}{}\n", number, padding, separator, path)); } else {