implemented writing default configuration

This commit is contained in:
2025-02-01 16:02:36 +01:00
parent 88db002985
commit 7796a4d1a8
6 changed files with 177 additions and 31 deletions
@@ -1,5 +1,5 @@
use proc_macro2::{Ident, TokenStream}; 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; use quote::quote;
pub fn gen_parse_from_string(config_name: &Ident, output_name: &Ident, assignments: &TokenStream) -> TokenStream { 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<Field, Comma>) -> TokenStream {
} }
} }
pub fn gen_config_assignments(fields: &Punctuated<Field, Comma>, config_map_name: &syn::Ident, output_name: &syn::Ident) -> TokenStream { pub fn gen_to_string(fields: &Punctuated<Field, Comma>) -> TokenStream {
let mut assignments : TokenStream = TokenStream::new(); 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<Field, Comma>, config_map_name: &Ident, output_name: &Ident) -> TokenStream {
let mut assignments: TokenStream = TokenStream::new();
'fields: for field in fields.iter() { 'fields: for field in fields.iter() {
let attr = &field.attrs; let attr = &field.attrs;
let name = match &field.ident { let name = match &field.ident {
+2 -2
View File
@@ -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_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_to_ansi_sequences: TokenStream = gen_to_ansi_sequences(fields);
let func_default: TokenStream = gen_default(fields); let func_default: TokenStream = gen_default(fields);
let func_to_string: TokenStream = gen_to_string(fields);
quote! { quote! {
impl #name { impl #name {
@@ -42,8 +43,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
#func_parse_map #func_parse_map
#func_to_ansi_sequences #func_to_ansi_sequences
#func_default #func_default
#func_to_string
// TODO: implement function to parse style settings
} }
}.into() }.into()
} }
+4
View File
@@ -21,6 +21,10 @@ function book {
__call_navigate "bookmark $@" __call_navigate "bookmark $@"
} }
function nav_config {
__call_navigate "configuration $@"
}
# completion function for `book` # completion function for `book`
function _book { function _book {
CURRENT_WORD=${COMP_WORDS[COMP_CWORD]} CURRENT_WORD=${COMP_WORDS[COMP_CWORD]}
+10
View File
@@ -30,6 +30,9 @@ pub enum Action {
/// navigate to bookmark and add current path to the stack /// navigate to bookmark and add current path to the stack
bookmark(BookmarkArgs), bookmark(BookmarkArgs),
/// display current configuartion (mostly for debugging)
configuration(ConfigArgs),
} }
#[derive(Debug, Clone, Args)] #[derive(Debug, Clone, Args)]
@@ -117,6 +120,13 @@ pub struct BookmarkSubArgs {
pub path: Option<PathBuf>, pub path: Option<PathBuf>,
} }
#[derive(Debug, Clone, Args)]
pub struct ConfigArgs {
/// convert styles to ansi escape sequences
#[arg(short, long)]
pub convert: Option<bool>,
}
/// empty struct for subcommands with no arguments /// empty struct for subcommands with no arguments
#[derive(Debug, Clone, Args)] #[derive(Debug, Clone, Args)]
pub struct EmptyArgs {} pub struct EmptyArgs {}
+68 -18
View File
@@ -20,68 +20,109 @@ pub struct Config {
#[derive(Debug, Clone, Default, ConfigParser)] #[derive(Debug, Clone, Default, ConfigParser)]
pub struct GeneralSettings { pub struct GeneralSettings {
/// (bool) show stack when pushing a path to the stack
#[default_value(false)] #[default_value(false)]
pub show_stack_on_push: bool, pub show_stack_on_push: bool,
/// (bool) show stack when popping a stack entry
#[default_value(false)] #[default_value(false)]
pub show_stack_on_pop: bool, pub show_stack_on_pop: bool,
/// (bool) show book marks when adding, removing or changing to a bookmark
#[default_value(false)] #[default_value(false)]
pub show_books_on_bookmark: bool, pub show_books_on_bookmark: bool,
} }
#[derive(Debug, Clone, Default, ConfigParser)] #[derive(Debug, Clone, Default, ConfigParser)]
pub struct FormatSettings { pub struct FormatSettings {
/// (bool) add padding before the separator if true, after if false
#[default_value(true)] #[default_value(true)]
pub align_separators: bool, 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)] #[default_value(false)]
pub show_home_as_tilde: bool, 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, pub bookmarks_separator: String,
} }
#[derive(Debug, Clone, Default, ConfigParser)] #[derive(Debug, Clone, Default, ConfigParser)]
pub struct StyleSettings { pub struct StyleSettings {
/// (string) style applied to warnings
#[style_config] #[style_config]
#[default_value("yellow, italic")] #[default_value("'yellow, italic'")]
pub warning_style: String, pub warning_style: String,
/// (string) style applied to errors
#[style_config] #[style_config]
#[default_value("red, bold")] #[default_value("'red, bold'")]
pub error_style: String, pub error_style: String,
/// (string) style applied to numbers when displaying the stack
#[style_config] #[style_config]
#[default_value("default")] #[default_value("'default'")]
pub stack_number_style: String, pub stack_number_style: String,
/// (string) style applied to separators when displaying the stack
#[style_config] #[style_config]
#[default_value("cyan")] #[default_value("'cyan'")]
pub stack_separator_style: String, pub stack_separator_style: String,
/// (string) style applied to paths when displaying the stack
#[style_config] #[style_config]
#[default_value("default")] #[default_value("'default'")]
pub stack_path_style: String, pub stack_path_style: String,
/// (string) style applied to punctuation (i.e. '/') when displaying the stack
#[style_config] #[style_config]
#[default_value("magenta")] #[default_value("'magenta'")]
pub stack_punct_style: String, pub stack_punct_style: String,
/// (string) style applied to bookmark names when displaying the bookmarks
#[style_config] #[style_config]
#[default_value("default")] #[default_value("'default'")]
pub bookmarks_name_style: String, pub bookmarks_name_style: String,
/// (string) style applied to separators when displaying the bookmarks
#[style_config] #[style_config]
#[default_value("cyan")] #[default_value("'cyan'")]
pub bookmarks_seperator_style: String, pub bookmarks_seperator_style: String,
/// (string) style applied to paths when displaying the bookmarks
#[style_config] #[style_config]
#[default_value("default")] #[default_value("'default'")]
pub bookmarks_path_style: String, pub bookmarks_path_style: String,
/// (string) style applied to punctuation (i.e. '/') when displaying the bookmarks
#[style_config] #[style_config]
#[default_value("magenta")] #[default_value("'magenta'")]
pub bookmarks_punct_style: String, pub bookmarks_punct_style: String,
} }
impl Config { impl Config {
const CONFIG_DIRECTORY_NAME: &str = "navigate";
const CONFIG_FILE_NAME: &str = "navigate.toml"; 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 /// generates and populates a new instance of Config
pub fn new(styles_as_ansi_sequences: bool) -> Result<Self> { pub fn new(styles_as_ansi_sequences: bool) -> Result<Self> {
let mut config: Config = Self::default(); let mut config: Config = Self::default();
// get configuration directory // get configuration directory
let mut conf_file = match config_dir() { let mut config_file = match config_dir() {
Some(value) => value, Some(value) => value,
None => { None => {
return Err(Error::other( return Err(Error::other(
@@ -89,12 +130,21 @@ impl Config {
)) ))
} }
}; };
// expand path to configuration file // expand path to configuration file and default configuration file
conf_file.push(format!("navigate/{}", Self::CONFIG_FILE_NAME)); 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 // parse configuration file and populate config struct
if conf_file.is_file() { if config_file.is_file() {
let config_str = match fs::read_to_string(&conf_file) { let config_str = match fs::read_to_string(&config_file) {
Ok(value) => value, Ok(value) => value,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
+22 -8
View File
@@ -28,14 +28,24 @@ fn main() -> Result<()> {
Err(error) => { Err(error) => {
// config object is not ready at this point so the style // config object is not ready at this point so the style
// has to be created by hand // 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(()) return Ok(())
} }
}; };
let args = match Arguments::try_parse() { let args = match Arguments::try_parse() {
Ok(a) => a, Ok(a) => a,
Err(error) => { 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); output.print_output(&config);
return Ok(()); return Ok(());
} }
@@ -60,6 +70,7 @@ fn main() -> Result<()> {
Action::pop(pop_args) => handle_pop(&pop_args, &config, &mut stack, &mut output), 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::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::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() { if res.is_err() {
@@ -156,12 +167,15 @@ fn handle_bookmark(args: &BookmarkArgs, config: &Config, bookmarks: &mut Bookmar
Ok(()) Ok(())
} }
// fn handle_config(args: &ConfigArgs, config: &Config) -> Result<()> { fn handle_config(args: &ConfigArgs, output: &mut Output) -> Result<()> {
// match args { let convert: bool = match args.convert {
// ConfigAction::show => println!("echo '{}'", config.to_formatted_string()), Some(value) => value,
// } None => false,
// Ok(()) };
// } let config = Config::new(convert);
output.push_info(&format!("config = {:#?}", config));
Ok(())
}
fn list_bookmarks(config: &Config, bookmarks: &mut Bookmarks, output: &mut Output) -> Result<()> { fn list_bookmarks(config: &Config, bookmarks: &mut Bookmarks, output: &mut Output) -> Result<()> {
output.push_info(&bookmarks.to_formatted_string(config)?); output.push_info(&bookmarks.to_formatted_string(config)?);