RMusicBot/src/config_handler.rs

304 lines
11 KiB
Rust

#![allow(unused)]
//! Config file handler
//! Cares about formatting and type conversion
use anyhow::{Context, Result};
use std::{
collections::HashMap,
fmt::Display,
fs::File,
io::{BufRead, BufReader, Write},
path::Path,
str::FromStr,
};
/// Value abstraction to convert to and from values
#[derive(Clone, Debug)]
pub enum Value {
Value(String),
Array(Vec<String>),
}
struct ConfigSection {
header: String,
body: HashMap<String, Value>,
}
impl Value {
/// Creates an empty value
pub fn empty() -> Value {
Value::Value("".to_string())
}
/// Creates an empty array value
pub fn empty_array() -> Value {
Value::Array(Vec::new())
}
/// Create a value `Value::Array(Vec<String>)`, internal conversion to string
///
/// # Arguments
///
/// `array` array of type, type has to implement `Display` trait
#[deprecated]
pub fn from_array<T: Display>(array: &[T]) -> Self {
Value::Array(array.iter().map(|v| format!("{}", v)).collect())
}
/// Creates a value `Value::Value(String)`, internal conversion to string
///
/// # Arguments
///
/// `value` type has to implement `Display` trait
#[deprecated]
pub fn from_value<T: Display>(value: &T) -> Self {
Value::Value(format!("{}", value))
}
pub fn to_array<T: FromStr>(&self) -> Result<Vec<T>> {
match self {
Value::Array(value_array) => {
let mut target_array = Vec::with_capacity(value_array.len());
for value_string in value_array {
match value_string.parse::<T>() {
Ok(val) => target_array.push(val),
Err(_) => {
return Err(anyhow::Error::msg(format!(
"ConfigHandler: Error while parsing Value Array: {}",
value_string
)));
}
}
}
Ok(target_array)
}
_ => Err(anyhow::Error::msg(
"ConfigHandler: Error when requesting the wrong value type",
)),
}
}
pub fn to_value<T: FromStr>(&self) -> Result<T> {
match self {
Value::Value(value_string) => match value_string.parse::<T>() {
Ok(val) => Ok(val),
Err(_) => Err(anyhow::Error::msg(format!(
"ConfigHandler: Error while parsing Value Array: {}",
value_string
))),
},
_ => Err(anyhow::Error::msg(
"ConfigHandler: Error when requesting the wrong value type",
)),
}
}
}
impl<'a, T: Display> From<&'a T> for Value {
fn from(v: &'a T) -> Self {
Value::Value(format!("{}", v))
}
}
impl<'a, T: Display> From<&'a [T]> for Value {
fn from(v: &'a [T]) -> Self {
Value::Array(v.iter().map(|v| format!("{}", v)).collect())
}
}
/// Handler struct
pub struct ConfigHandler {}
impl ConfigHandler {
/// Reads the given config file
///
/// # Arguments
///
/// `file_name` file that is going to be read
pub fn read_config(
file_name: impl AsRef<Path>,
) -> Result<HashMap<String, HashMap<String, Value>>> {
let file = File::open(&file_name).with_context({
let file_name = file_name.as_ref().to_str().unwrap().to_string();
|| file_name
})?;
let mut infos = HashMap::new();
let mut current_section: Option<ConfigSection> = None;
for line_res in BufReader::new(file).lines() {
if let Ok(line) = line_res {
let mut trimmed = line.trim().to_string();
if trimmed.starts_with('#') || trimmed.is_empty() {
continue;
} else if trimmed.starts_with('[') && trimmed.ends_with(']') {
trimmed.remove(0);
trimmed.pop();
if let Some(ref section) = current_section {
infos.insert(section.header.clone(), section.body.clone());
}
current_section = Some(ConfigSection {
header: trimmed,
body: HashMap::new(),
});
} else {
let mut split = trimmed.split('=');
let key = match split.nth(0) {
Some(key) => key.trim().to_string(),
None => {
println!("cannot get key from line: {}", trimmed);
continue;
}
};
let value = match split.last() {
Some(value) => value.trim().to_string(),
None => {
println!("cannot get value from line: {}", trimmed);
continue;
}
};
if value.starts_with('[') && value.ends_with(']') {
let mut trimmed_value = value;
trimmed_value.remove(0);
trimmed_value.pop();
let value_split = trimmed_value.split(',');
let mut value_array = Vec::new();
for v in value_split {
let trimmed = v.trim();
if !trimmed.is_empty() {
value_array.push(trimmed.to_string());
}
}
if let Some(ref mut section) = current_section {
section.body.insert(key, Value::Array(value_array));
}
} else if let Some(ref mut section) = current_section {
section.body.insert(key, Value::Value(value));
}
}
}
}
// also push the last section
if let Some(section) = current_section {
infos.insert(section.header, section.body);
}
Ok(infos)
}
/// writes a formatted config file
///
/// # Arguments
///
/// `file_name` the file to which the config gets written
/// `sections` the sections and keys that are going to be written
pub fn write_config(
file_name: impl AsRef<Path>,
sections: &[(&str, Vec<(&str, Value)>)],
) -> Result<()> {
let mut file = File::create(file_name)?;
for (header, body) in sections {
let fmt_header = format!("[{}]\n", header);
file.write_all(fmt_header.as_bytes())?;
for (key, value) in body.iter() {
let fmt_key_value = format!(
"{} = {}\n",
key,
match value {
Value::Value(val) => val.clone(),
Value::Array(array) => {
let mut array_value = "[".to_string();
for (i, val) in array.iter().enumerate() {
// if element is not the last one
if i != array.len() - 1 {
array_value = format!("{}{}, ", array_value, val);
} else {
array_value = format!("{}{}", array_value, val);
}
}
format!("{}]", array_value)
}
}
);
file.write_all(fmt_key_value.as_bytes())?;
}
file.write_all("\n".as_bytes())?;
}
Ok(())
}
}
#[macro_export]
macro_rules! create_settings_section {
($struct_name:ident, $section_key:expr, {$($var:ident: $var_type:ty,)* $([$array:ident: $array_type:ty],)*} $(,$($derive:ident,)*)?) => {
#[derive(Default, Debug, Clone, PartialEq $(, $($derive,)* )? )]
pub struct $struct_name {
$(
pub $var: $var_type,
)*
$(
pub $array: Vec<$array_type>,
)*
}
impl $struct_name {
const SECTION_KEY: &'static str = $section_key;
pub fn load(
parsed_config: &std::collections::HashMap<String, std::collections::HashMap<String, Value>>
) -> anyhow::Result<Self> {
let mut me = Self::default();
if let Some(section) = parsed_config.get(Self::SECTION_KEY) {
$(
if let Some(value) = section.get(stringify!($var)) {
me.$var = value.to_value()?;
}
)*
$(
if let Some(array) = section.get(stringify!($array)) {
me.$array = array.to_array()?;
}
)*
}
Ok(me)
}
pub fn store(&self) -> (&str, Vec<(&str, Value)>) {
(
Self::SECTION_KEY,
vec![
$(
(stringify!($var), Value::from(&self.$var)),
)*
$(
(stringify!($array), Value::from(self.$array.as_slice())),
)*
]
)
}
}
};
}
#[macro_export]
macro_rules! create_settings_container {
($struct_name:ident, {$($var:ident: $var_type:ty$(,)?)*} $(,$($derive:ident,)*)? ) => {
#[derive(Default, Debug, Clone, PartialEq $(, $($derive,)* )? )]
pub struct $struct_name {
pub file_name: assetpath::AssetPath,
$(
pub $var: $var_type,
)*
}
impl $struct_name {
pub fn load(file: impl Into<assetpath::AssetPath>) -> anyhow::Result<Self> {
let file = file.into();
let parsed_stats_settings = ConfigHandler::read_config(&file.full_path())?;
Ok($struct_name {
file_name: file,
$(
$var: <$var_type>::load(&parsed_stats_settings)?,
)*
})
}
pub fn load_with_default(&mut self, file: impl Into<assetpath::AssetPath>) -> anyhow::Result<()> {
let file = file.into();
let parsed_stats_settings = ConfigHandler::read_config(&file.full_path())?;
self.file_name = file;
$(
self.$var = <$var_type>::load(&parsed_stats_settings)?;
)*
Ok(())
}
pub fn store(&self) -> anyhow::Result<()> {
ConfigHandler::write_config(
&self.file_name.full_path(),
&[
$(
self.$var.store(),
)*
]
)
}
}
};
}