From 7796a4d1a8fa013c571d1ad553e592bea776c04f Mon Sep 17 00:00:00 2001 From: quak Date: Sat, 1 Feb 2025 16:02:36 +0100 Subject: [PATCH] implemented writing default configuration --- .../src/generator_functions.rs | 74 +++++++++++++++- config-parser/config-parser-macro/src/lib.rs | 4 +- navigate_bash_setup | 4 + src/arguments.rs | 10 +++ src/config.rs | 86 +++++++++++++++---- src/main.rs | 30 +++++-- 6 files changed, 177 insertions(+), 31 deletions(-) diff --git a/config-parser/config-parser-macro/src/generator_functions.rs b/config-parser/config-parser-macro/src/generator_functions.rs index e53446e..d843464 100644 --- a/config-parser/config-parser-macro/src/generator_functions.rs +++ b/config-parser/config-parser-macro/src/generator_functions.rs @@ -1,5 +1,5 @@ use proc_macro2::{Ident, TokenStream}; -use syn::{Attribute, Field, Meta, MetaList, Path, punctuated::Punctuated, token::Comma}; +use syn::{punctuated::Punctuated, token::Comma, Attribute, Expr, ExprLit, Field, Meta, MetaList, MetaNameValue, Path}; use quote::quote; pub fn gen_parse_from_string(config_name: &Ident, output_name: &Ident, assignments: &TokenStream) -> TokenStream { @@ -133,8 +133,76 @@ pub fn gen_default(fields: &Punctuated) -> TokenStream { } } -pub fn gen_config_assignments(fields: &Punctuated, config_map_name: &syn::Ident, output_name: &syn::Ident) -> TokenStream { - let mut assignments : TokenStream = TokenStream::new(); +pub fn gen_to_string(fields: &Punctuated) -> TokenStream { + let mut statements: TokenStream = TokenStream::new(); + let mut nested_statements: TokenStream = TokenStream::new(); + let mut comment: TokenStream = TokenStream::new(); + 'fields: for field in fields.iter() { + let attr = &field.attrs; + let name = match &field.ident { + Some(value) => value, + None => continue 'fields, + }; + let name_string = name.to_string(); + // TODO: continue here + for attribute in attr { + if let Attribute{ meta: Meta::Path( Path{segments, ..} ), .. } = attribute { + // parse nested configs or skip nonconfig elements + match segments.first() { + Some(attr_name) => { + if attr_name.ident == "nested_config" { + statements.extend(quote! { + string.push_str(&format!("\n[{}]\n", #name_string)); + string.push_str(&self.#name.to_string_nested(&format!("{}", #name_string))); + }); + nested_statements.extend(quote! { + string.push_str(&format!("\n[{}.{}]\n", parents, #name_string)); + string.push_str(&self.#name.to_string_nested(&format!("{}.{}", parents, #name_string))); + }); + continue 'fields; + } else if attr_name.ident == "no_config" { + continue 'fields; + } + }, + None => (), + } + } else if let Attribute{ meta: Meta::NameValue( MetaNameValue{path: Path{ segments, .. }, value: Expr::Lit(ExprLit{lit, ..}), ..} ), .. } = attribute { + // write comments to string + let attr_type = segments.first().unwrap(); + if attr_type.ident == "doc" { + comment = quote!{ + format!(" #{}", #lit) + }; + } + } + } + if comment.is_empty() { + comment.extend(quote! {""}); + } + let code = quote!{ + string.push_str(&format!("{} = {}{}\n", #name_string, self.#name.to_string(), #comment)); + }; + statements.extend(code.clone()); + nested_statements.extend(code); + } + quote! { + pub fn to_string(&self) -> String { + let mut string = String::new(); + #statements + string + } + + /// macro function - do not call + pub fn to_string_nested(&self, parents: &String) -> String { + let mut string = String::new(); + #nested_statements + string + } + } +} + +pub fn gen_config_assignments(fields: &Punctuated, config_map_name: &Ident, output_name: &Ident) -> TokenStream { + let mut assignments: TokenStream = TokenStream::new(); 'fields: for field in fields.iter() { let attr = &field.attrs; let name = match &field.ident { diff --git a/config-parser/config-parser-macro/src/lib.rs b/config-parser/config-parser-macro/src/lib.rs index f7f48f2..3817b76 100644 --- a/config-parser/config-parser-macro/src/lib.rs +++ b/config-parser/config-parser-macro/src/lib.rs @@ -35,6 +35,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let func_parse_map: TokenStream = gen_parse_from_map(&config_name, &output_name, &assignments); let func_to_ansi_sequences: TokenStream = gen_to_ansi_sequences(fields); let func_default: TokenStream = gen_default(fields); + let func_to_string: TokenStream = gen_to_string(fields); quote! { impl #name { @@ -42,8 +43,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { #func_parse_map #func_to_ansi_sequences #func_default - - // TODO: implement function to parse style settings + #func_to_string } }.into() } diff --git a/navigate_bash_setup b/navigate_bash_setup index 5ebcf41..e7e5750 100644 --- a/navigate_bash_setup +++ b/navigate_bash_setup @@ -21,6 +21,10 @@ function book { __call_navigate "bookmark $@" } +function nav_config { + __call_navigate "configuration $@" +} + # completion function for `book` function _book { CURRENT_WORD=${COMP_WORDS[COMP_CWORD]} diff --git a/src/arguments.rs b/src/arguments.rs index 57de548..33aa5f5 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -30,6 +30,9 @@ pub enum Action { /// navigate to bookmark and add current path to the stack bookmark(BookmarkArgs), + + /// display current configuartion (mostly for debugging) + configuration(ConfigArgs), } #[derive(Debug, Clone, Args)] @@ -117,6 +120,13 @@ pub struct BookmarkSubArgs { pub path: Option, } +#[derive(Debug, Clone, Args)] +pub struct ConfigArgs { + /// convert styles to ansi escape sequences + #[arg(short, long)] + pub convert: Option, +} + /// empty struct for subcommands with no arguments #[derive(Debug, Clone, Args)] pub struct EmptyArgs {} diff --git a/src/config.rs b/src/config.rs index c125398..40b15b1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,68 +20,109 @@ pub struct Config { #[derive(Debug, Clone, Default, ConfigParser)] pub struct GeneralSettings { + /// (bool) show stack when pushing a path to the stack #[default_value(false)] pub show_stack_on_push: bool, + + /// (bool) show stack when popping a stack entry #[default_value(false)] pub show_stack_on_pop: bool, + + /// (bool) show book marks when adding, removing or changing to a bookmark #[default_value(false)] pub show_books_on_bookmark: bool, } #[derive(Debug, Clone, Default, ConfigParser)] pub struct FormatSettings { + /// (bool) add padding before the separator if true, after if false #[default_value(true)] pub align_separators: bool, - #[default_value(" - ")] - pub stack_separator: String, + + /// (bool) replace home directory path with '~' when displaying the stack or bookmarks #[default_value(false)] pub show_home_as_tilde: bool, - #[default_value(" - ")] + + /// (string) separator between stack numbers and paths + #[default_value("' - '")] + pub stack_separator: String, + + /// (string) separator between bookmark names and paths + #[default_value("' - '")] pub bookmarks_separator: String, } #[derive(Debug, Clone, Default, ConfigParser)] pub struct StyleSettings { + /// (string) style applied to warnings #[style_config] - #[default_value("yellow, italic")] + #[default_value("'yellow, italic'")] pub warning_style: String, + + /// (string) style applied to errors #[style_config] - #[default_value("red, bold")] + #[default_value("'red, bold'")] pub error_style: String, + + /// (string) style applied to numbers when displaying the stack #[style_config] - #[default_value("default")] + #[default_value("'default'")] pub stack_number_style: String, + + /// (string) style applied to separators when displaying the stack #[style_config] - #[default_value("cyan")] + #[default_value("'cyan'")] pub stack_separator_style: String, + + /// (string) style applied to paths when displaying the stack #[style_config] - #[default_value("default")] + #[default_value("'default'")] pub stack_path_style: String, + + /// (string) style applied to punctuation (i.e. '/') when displaying the stack #[style_config] - #[default_value("magenta")] + #[default_value("'magenta'")] pub stack_punct_style: String, + + /// (string) style applied to bookmark names when displaying the bookmarks #[style_config] - #[default_value("default")] + #[default_value("'default'")] pub bookmarks_name_style: String, + + /// (string) style applied to separators when displaying the bookmarks #[style_config] - #[default_value("cyan")] + #[default_value("'cyan'")] pub bookmarks_seperator_style: String, + + /// (string) style applied to paths when displaying the bookmarks #[style_config] - #[default_value("default")] + #[default_value("'default'")] pub bookmarks_path_style: String, + + /// (string) style applied to punctuation (i.e. '/') when displaying the bookmarks #[style_config] - #[default_value("magenta")] + #[default_value("'magenta'")] pub bookmarks_punct_style: String, } impl Config { + const CONFIG_DIRECTORY_NAME: &str = "navigate"; const CONFIG_FILE_NAME: &str = "navigate.toml"; + const DEFAULT_CONFIG_NAME: &str = "default.toml"; + const DEFAULT_FILE_HEADER: &str = " +# default configuration file for `navigate` +# +# value type should be in the comment +# string values have to be quoted, both single and double quotes work +# integer and boolean values do not need quotes +# boolean values are either `true` or `false` +"; /// generates and populates a new instance of Config pub fn new(styles_as_ansi_sequences: bool) -> Result { let mut config: Config = Self::default(); // get configuration directory - let mut conf_file = match config_dir() { + let mut config_file = match config_dir() { Some(value) => value, None => { return Err(Error::other( @@ -89,12 +130,21 @@ impl Config { )) } }; - // expand path to configuration file - conf_file.push(format!("navigate/{}", Self::CONFIG_FILE_NAME)); + // expand path to configuration file and default configuration file + let mut default_file = config_file.clone(); + default_file.push(format!("{}/{}", Self::CONFIG_DIRECTORY_NAME, Self::DEFAULT_CONFIG_NAME)); + config_file.push(format!("{}/{}", Self::CONFIG_DIRECTORY_NAME, Self::CONFIG_FILE_NAME)); + + // write default configuration file if it does not exist + if !default_file.is_file() { + let mut default_string = Self::DEFAULT_FILE_HEADER.to_string(); + default_string.push_str(&config.to_string()); + _ = fs::write(default_file, default_string); + } // parse configuration file and populate config struct - if conf_file.is_file() { - let config_str = match fs::read_to_string(&conf_file) { + if config_file.is_file() { + let config_str = match fs::read_to_string(&config_file) { Ok(value) => value, Err(error) => return Err(error), }; diff --git a/src/main.rs b/src/main.rs index cf3be23..ee0bfd5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,14 +28,24 @@ fn main() -> Result<()> { Err(error) => { // config object is not ready at this point so the style // has to be created by hand - print!("echo '{}{}{}' && false", generate_style_sequence(None, Some(COLORS.fg.red), None), error, RESET_SEQ); + print!("echo -e '{}{}{}' && false", generate_style_sequence(None, Some(COLORS.fg.red), None), error, RESET_SEQ); return Ok(()) } }; let args = match Arguments::try_parse() { Ok(a) => a, Err(error) => { - output.push_error(&error.to_string()); + match error.kind() { + clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => { + output.push_info(&error.to_string()); + }, + clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => { + output.push_warning(&error.to_string()); + }, + _ => { + output.push_error(&error.to_string()); + }, + } output.print_output(&config); return Ok(()); } @@ -60,6 +70,7 @@ fn main() -> Result<()> { Action::pop(pop_args) => handle_pop(&pop_args, &config, &mut stack, &mut output), Action::stack(stack_args) => handle_stack(&stack_args, &config, &mut stack, &mut output), Action::bookmark(bookmark_args) => handle_bookmark(&bookmark_args, &config, &mut bookmarks, &mut stack, &mut output), + Action::configuration(config_args) => handle_config(&config_args, &mut output), }; if res.is_err() { @@ -156,12 +167,15 @@ fn handle_bookmark(args: &BookmarkArgs, config: &Config, bookmarks: &mut Bookmar Ok(()) } -// fn handle_config(args: &ConfigArgs, config: &Config) -> Result<()> { -// match args { -// ConfigAction::show => println!("echo '{}'", config.to_formatted_string()), -// } -// Ok(()) -// } +fn handle_config(args: &ConfigArgs, output: &mut Output) -> Result<()> { + let convert: bool = match args.convert { + Some(value) => value, + None => false, + }; + let config = Config::new(convert); + output.push_info(&format!("config = {:#?}", config)); + Ok(()) +} fn list_bookmarks(config: &Config, bookmarks: &mut Bookmarks, output: &mut Output) -> Result<()> { output.push_info(&bookmarks.to_formatted_string(config)?);