started derive macro to parse configuration
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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
@@ -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
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user