use std::fmt::Display; use std::fs::File; use std::io::{BufRead, BufReader, Write}; use std::str::FromStr; use std::collections::HashMap; #[derive(Clone)] pub enum Value { Value(String), Array(Vec), } struct ConfigSection { header: String, body: HashMap, } impl Value { pub fn empty() -> Value { Value::Value("".to_string()) } pub fn value(value: &T) -> Value where T: Display, { Value::Value(format!("{}", value)) } pub fn array(array: &Vec) -> Value where T: Display, { Value::Array(array.iter().map(|v| format!("{}", v)).collect()) } pub fn set_value(&self, value: &mut T) -> Result<(), String> where T: FromStr, { match self { Value::Value(value_string) => match value_string.parse::() { Ok(val) => *value = val, Err(_) => return Err(format!("error parsing value {}", value_string)), }, _ => return Err("key_value has wrong format".to_string()), }; Ok(()) } pub fn set_array(&self, array: &mut Vec) -> Result<(), String> where T: FromStr, { match self { Value::Array(value_array) => for value_string in value_array { match value_string.parse::() { Ok(val) => array.push(val), Err(_) => return Err(format!("error parsing value {}", value_string)), } }, _ => return Err("key_value has wrong format".to_string()), }; Ok(()) } } pub fn read_config(file_name: &str) -> Result>, String> { let file = match File::open(file_name) { Ok(file) => file, Err(msg) => return Err(format!("error opening config file({}): {}", file_name, msg)), }; let mut infos = HashMap::new(); let mut current_section: Option = 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 mut value_split = trimmed_value.split(";"); let value_array = value_split.map(|v| v.trim().to_string()).collect(); 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) } pub fn write_config( file_name: &str, sections: &Vec<(String, Vec<(String, Value)>)>, ) -> Result<(), String> { let mut file = match File::create(file_name) { Ok(file) => file, Err(msg) => { return Err(format!( "error creating config file({}): {}", file_name, msg )) } }; for (header, body) in sections { let fmt_header = format!("[{}]\n", header); if let Err(_) = file.write_all(fmt_header.as_bytes()) { return Err(format!("failed writing section: {}", fmt_header)); } for (key, value) in body { 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) } } ); if let Err(_) = file.write_all(fmt_key_value.as_bytes()) { return Err(format!("failed writing key value: {}", fmt_key_value)); } } if let Err(_) = file.write_all("\n".as_bytes()) { return Err("failed writing new line".to_string()); } } Ok(()) }