started derive macro to parse configuration

This commit is contained in:
2024-12-30 11:08:51 +01:00
parent 26733820f8
commit e9f91575f2
6 changed files with 269 additions and 76 deletions
+1
View File
@@ -9,3 +9,4 @@ dirs = "5.0.1"
serde = { version = "1.0.216", features = [ "std", "derive" ] }
sysinfo = "0.32.0"
toml = "0.8.19"
derive_config_parser = { path = "derive_config_parser" }
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "derive_config_parser"
version = "0.1.0"
autotests = false
edition = "2021"
publish = false
[lib]
proc-macro = true
[dependencies]
quote = "1.0.38"
syn = { version = "2.0.92", features = ["extra-traits"] }
+45
View File
@@ -0,0 +1,45 @@
use core::panic;
use proc_macro::TokenStream;
/// **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<String>`
/// which write a default configuration, in case the documentation is lacking
#[proc_macro_derive(ConfigParser)]
pub fn derive(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let name = &ast.ident;
let fields = if let syn::Data::Struct(syn::DataStruct{ fields: syn::Fields::Named(syn::FieldsNamed{ ref named, .. }), .. }) = ast.data {
named
} else {
panic!("failed to parse struct fields");
};
println!("{:#?}", ast);
// let string = format!("{:#?}", ast);
// _ = std::fs::write("test.txt", string);
let expanded_stream: TokenStream = quote::quote! {
impl #name {
pub fn parse_config(&mut self, input: &String) -> std::io::Result<()> {
let mut setting_names = std::collections::HashMap::new();
let lines = input.lines();
for line in lines {
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;
}
setting_names.insert(tokens[0], tokens[1]);
}
//println!("{:#?}", setting_names);
Ok(())
}
}
}.into();
expanded_stream
// TokenStream::new()
}
+12 -33
View File
@@ -5,25 +5,10 @@
use crate::format::*;
use dirs::config_dir;
use serde::{Deserialize, Serialize};
use std::fs;
use std::io::{Error, Result};
use std::path::PathBuf;
// macro_rules! generate_field_names {
// ($(#[$attr:meta])* struct $name:ident { $($fname:ident : $ftype:ty),* }) => {
// $(#[$attr])* struct $name {
// $($fname : $ftype),*
// }
// impl $name {
// fn field_names() -> &'static [&'static str] {
// static NAMES: &'static [&'static str] = &[$(stringify!($fname)),*];
// NAMES
// }
// }
// }
// }
use derive_config_parser::ConfigParser;
#[derive(Debug, Clone)]
pub struct Config {
@@ -31,32 +16,28 @@ pub struct Config {
pub settings: Settings,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(default)]
#[derive(Debug, Clone, Default)]
pub struct Settings {
pub general: GeneralSettings,
pub format: FormatSettings,
pub styles: StyleSettings,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(default)]
#[derive(Debug, Clone, Default)]
pub struct GeneralSettings {
pub show_stack_on_push: bool,
pub show_stack_on_pop: bool,
pub show_stack_on_bookmark: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(default)]
#[derive(Debug, Clone, Default)]
pub struct FormatSettings {
pub stack_separator: String,
pub bookmarks_separator: String,
pub align_separators: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(default)]
#[derive(Debug, Clone, Default, ConfigParser)]
pub struct StyleSettings {
pub stack_number: String,
pub stack_separator: String,
@@ -121,9 +102,7 @@ impl Config {
/// formats and prints config to string
pub fn to_formatted_string(&self) -> Result<String> {
let mut buffer = String::new();
buffer = format!("{:#?}", self.settings);
Ok(buffer)
Ok(format!("{:#?}", self.settings))
}
/// reads and parses the configuration file
@@ -191,16 +170,16 @@ impl Config {
/// convert color settings to ansi escape sequences
pub fn parse_color_settings(&mut self) -> Result<()> {
self.settings.styles.stack_number = parse_color(self.settings.styles.stack_number.clone())?;
self.settings.styles.stack_number = parse_style(self.settings.styles.stack_number.clone())?;
self.settings.styles.stack_separator =
parse_color(self.settings.styles.stack_separator.clone())?;
self.settings.styles.stack_path = parse_color(self.settings.styles.stack_path.clone())?;
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_color(self.settings.styles.bookmarks_name.clone())?;
parse_style(self.settings.styles.bookmarks_name.clone())?;
self.settings.styles.bookmarks_seperator =
parse_color(self.settings.styles.bookmarks_seperator.clone())?;
parse_style(self.settings.styles.bookmarks_seperator.clone())?;
self.settings.styles.bookmarks_path =
parse_color(self.settings.styles.bookmarks_path.clone())?;
parse_style(self.settings.styles.bookmarks_path.clone())?;
Ok(())
}
}
+60 -43
View File
@@ -191,53 +191,17 @@ pub fn make_padding_string(len: usize) -> String {
}
/// convert color setting to ansi escape sequence
pub fn parse_color(color: String) -> Result<String> {
pub fn parse_style(string: String) -> Result<String> {
// check for numbered color
if let Ok(numbered) = color.parse::<u8>() { // TODO: only accept numbers between 16 and 256
return Ok(generate_256color_sequence(
ColorContext::Foreground,
numbered,
));
if let Ok(sequence) = parse_numbered_color(&string) {
return Ok(sequence);
}
// check for rgb color
if color.as_bytes()[0] == b'#' && color.len() == 7 {
// match u8::from_str_radix(&color, 16) {
let red = match u8::from_str_radix(&color[1..=2], 16) {
Ok(value) => value,
Err(_) => {
return Err(Error::other(format!(
"-- failed to parse rgb color `{}` in config file",
color
)))
}
};
let green = match u8::from_str_radix(&color[3..=4], 16) {
Ok(value) => value,
Err(_) => {
return Err(Error::other(format!(
"-- failed to parse rgb color `{}` in config file",
color
)))
}
};
let blue = match u8::from_str_radix(&color[5..=6], 16) {
Ok(value) => value,
Err(_) => {
return Err(Error::other(format!(
"-- failed to parse rgb color `{}` in config file",
color
)))
}
};
return Ok(generate_rgb_sequence(
ColorContext::Foreground,
red,
green,
blue,
));
if let Ok(sequence) = parse_rgb_color(&string) {
return Ok(sequence);
}
// check for named color
match color.to_ascii_lowercase().as_str() {
match string.to_ascii_lowercase().as_str() {
"black" => Ok(generate_style_sequence(None, Some(COLORS.fg.black), None)),
"red" => Ok(generate_style_sequence(None, Some(COLORS.fg.red), None)),
"green" => Ok(generate_style_sequence(None, Some(COLORS.fg.green), None)),
@@ -249,7 +213,60 @@ pub fn parse_color(color: String) -> Result<String> {
"default" => Ok(generate_style_sequence(None, Some(COLORS.fg.default), None)),
_ => Err(Error::other(format!(
"-- could not parse color `{}` in config file",
color
string
))),
}
}
fn parse_numbered_color(string: &String) -> Result<String> {
// check for numbered color
if let Ok(number) = string.parse::<u8>() {
if number >= 16 {
return Ok(generate_256color_sequence(
ColorContext::Foreground,
number,
));}
}
Err(Error::other(format!("no numbered color found in '{}'", string)))
}
fn parse_rgb_color(string: &String) -> Result<String> {
// check for rgb color
if string.as_bytes()[0] == b'#' && string.len() == 7 {
// match u8::from_str_radix(&color, 16) {
let red = match u8::from_str_radix(&string[1..=2], 16) {
Ok(value) => value,
Err(_) => {
return Err(Error::other(format!(
"-- failed to parse rgb color `{}` in config file",
string
)))
}
};
let green = match u8::from_str_radix(&string[3..=4], 16) {
Ok(value) => value,
Err(_) => {
return Err(Error::other(format!(
"-- failed to parse rgb color `{}` in config file",
string
)))
}
};
let blue = match u8::from_str_radix(&string[5..=6], 16) {
Ok(value) => value,
Err(_) => {
return Err(Error::other(format!(
"-- failed to parse rgb color `{}` in config file",
string
)))
}
};
return Ok(generate_rgb_sequence(
ColorContext::Foreground,
red,
green,
blue,
));
}
Err(Error::other(format!("no rgb color found in '{}'", string)))
}
+137
View File
@@ -0,0 +1,137 @@
#![allow(dead_code)]
use dirs::config_dir;
use std::collections::HashMap;
use std::fs;
use std::io::{Error, Result};
use std::path::PathBuf;
const DEFAULT_SETTINGS: &[&str] = &[
"show_stack_on_push=false",
"show_stack_on_pop=false",
"show_stack_on_bookmark=false",
"stack_separator= - ",
"bookmark_separator= - ",
"stack_number=default, bold",
"stack_separator=cyan",
"stack_path=default",
"bookmark_name=default",
"bookmark_separator=cyan",
"bookmark_path=default",
];
#[derive(Debug, Clone)]
pub struct Config {
conf_file_path: PathBuf,
pub settings: HashMap<&'static str, &'static str>,
}
impl Config {
const CONFIG_DIR_NAME: &str = "navigate/";
const CONFIG_FILE_NAME: &str = "navigate.conf";
pub fn new() -> Result<Self> {
let mut settings = HashMap::<&str, &str>::new();
// fill the hashmaps with the defined settings and their default values
for item in DEFAULT_SETTINGS {
let tokens = item.split(['=']).collect::<Vec<&str>>();
if tokens.len() != 2 {
panic!("-- fix default format settings")
}
settings.insert(tokens.first().unwrap(), tokens.last().unwrap());
}
let mut config = Config {
conf_file_path: PathBuf::new(),
settings,
};
// get configuration directory
config.conf_file_path = match config_dir() {
Some(value) => value,
None => {
return Err(Error::other(
"-- failed to retrieve configuration directory",
))
}
};
// expand path to configuration file
config.conf_file_path.push(format!(
"{}{}",
Self::CONFIG_DIR_NAME,
Self::CONFIG_FILE_NAME,
));
// parse configuration file and populate config struct
if config.parse_config().is_err() {
config.write_config()?;
}
// config.parse_color_settings()?;
Ok(config)
}
/// formats and prints config to string
pub fn to_formatted_string(&self) -> Result<String> {
// TODO implement
Ok("hi".to_owned())
}
/// parse config file
fn parse_config(&mut self) -> Result<()> {
if !self.conf_file_path.is_file() {
return Err(Error::other("-- config file does not exist"));
}
let config = match fs::read_to_string(&self.conf_file_path.clone()) {
Ok(value) => value,
Err(error) => return Err(error),
};
for line in config.lines() {
// ignore comments
if line.starts_with("#") {
continue;
}
let token = line.split(['=']).collect::<Vec<&str>>();
let key = match token.first() {
Some(value) => value,
None => return Err(Error::other(format!("-- failed to parse '{}'", line))),
};
let value = match token.last() {
Some(value) => value,
None => return Err(Error::other(format!("-- failed to parse '{}'", line))),
};
if self.settings.contains_key(key) {
if let Some(entry) = self.settings.get_mut(key) {
*entry = value.clone();
}
} else {
println!("-- ignored unknown setting : {}", line);
}
}
// self.settings.entry(key).and_modify(|entry| *entry = value);
Ok(())
}
/// write configuration file
fn write_config(&self) -> Result<()> {
let mut buffer = String::new();
buffer.push_str("# `navigate` settings\n");
for (name, value) in self.settings.clone() {
buffer.push_str(&format!("{}={}\n", name, value));
}
buffer.push('\n');
if fs::write(self.conf_file_path.clone(), buffer).is_err() {
return Err(Error::other("-- failed to write configuration file"));
}
Ok(())
}
}