use Format;
use Type;
use SupportedType;
use std::ops::{Add,AddAssign};
use std::io::prelude::*;
use std::collections::HashMap;
use std::fs::File;
use std::fs;
use failure::Error;
#[derive(Serialize,Deserialize,Clone)]
pub struct Settings<T> where T : Format + Clone {
global : HashMap<String,Type>,
ioconfig: T,
}
impl<T> Settings<T> where T : Format + Clone {
pub fn new(config : T) -> Settings<T> {
Settings {
global : HashMap::new(),
ioconfig : config
}
}
pub fn new_and_load(config : T) -> Settings<T> {
let mut setting = Settings::new(config);
if let Err(error) = setting.load() {
warn!("{}",error);
}
setting
}
fn from_flat(flat_hash : &Settings<T>) -> Settings<T> {
let mut new_hash = Settings::new(flat_hash.ioconfig.clone());
for (key,value) in flat_hash.global.iter() {
if let Err(error) = new_hash.set_value(&key,&value) {
warn!("Error setting {}:{}, {}",key,value,error);
}
}
new_hash
}
pub fn create_from(mut file : &File, config : T) -> Result<Settings<T>,Error> {
let mut buf : String = String::new();
file.read_to_string(&mut buf)?;
if buf.len() > 0 {
let hash = Format::from_str::<T>(&config,&buf)?;
Ok(Settings{
global : hash,
ioconfig : config
})
} else {
Ok(Settings{ global: HashMap::new(), ioconfig : config })
}
}
pub fn create_from_or_empty(file : &File, config : T) -> Settings<T> {
match Settings::create_from(file,config.clone()) {
Ok(settings) => { settings }
Err(_) => { Settings::new(config) }
}
}
pub fn load(&mut self) -> Result<(),Error> {
let path = self.ioconfig.get_path_and_file();
info!("Loading from {}",path);
let mut file = File::open(&path)?;
info!("{} loaded.",path);
self.load_from(&mut file)
}
pub fn load_from(&mut self, file : &mut File) -> Result<(),Error> {
let mut buf : String = String::new();
file.read_to_string(&mut buf)?;
if buf.len() > 0 {
let hash = Format::from_str::<T>(&self.ioconfig,&buf)?;
self.global = hash;
Ok(())
} else {
Err(format_err!("Error loading from buffer"))
}
}
pub fn save(&self) -> Result<(),Error> {
let path = self.ioconfig.get_path_and_file();
info!("Saving to {}",path);
fs::create_dir_all(self.ioconfig.get_path())?;
let mut file = File::create(path)?;
self.save_to(&mut file)
}
pub fn save_to(&self, mut file : &File) -> Result<(),Error> {
match self.ioconfig.to_string(&self.global){
Err(error) => return Err(error),
Ok(settings_string) => {
match file.write(settings_string.as_bytes()){
Ok(_) => Ok(()),
Err(error) => Err(format_err!("{}",error)),
}
}
}
}
#[allow(dead_code)]
fn get_value_absolute(&self, key_path : &str) -> Option<Type> {
if let Some(result) = self.global.get(key_path) {
return Some(result.clone());
} else {
return None;
}
}
pub fn get_value(&self, key_path : &str) -> Option<Type> {
let path_tree : Vec<&str> = key_path.split(".").collect();
let mut subtree : &Type = &Type::Text("Empty".to_string());
for i in 0..path_tree.len() {
if i == 0 {
if let Some(ref part) = self.global.get(&path_tree[i].to_string()) {
subtree = part;
} else { return None }
} else {
match *subtree {
Type::Complex(ref hash) => {
if let Some(ref part) = hash.get(&path_tree[i].to_string()) {
subtree = part;
} else { return None }
},
_ => { return None }
}
}
}
return Some(subtree.clone());
}
pub fn get_value_or<A:?Sized>(&self, key_path : &str, default_value : &A) -> Type
where A : SupportedType,
{
match self.get_value(key_path) {
Some(value) => value,
None => default_value.wrap(),
}
}
pub fn set_value<A:?Sized>(&mut self, key_path : &str, value : &A) -> Result<(),Error>
where A : SupportedType ,
{
let mut global : Vec<Type> = Vec::new();
let path_tree : Vec<&str> = key_path.split(".").collect();
for i in 0..path_tree.len()-1 {
if i == 0 {
if let Some(part) = self.global.remove(&path_tree[i].to_string()) {
if let Type::Complex(hash) = part {
global.push(Type::Complex(hash));
} else { global.push(Type::Complex(HashMap::new())); }
} else { global.push(Type::Complex(HashMap::new())); }
} else {
let index = global.len()-1; let mut push_me : Option<Type> = None;
if let Type::Complex(ref mut mut_parts) = global[index] {
if let Some(part) = mut_parts.remove(&path_tree[i].to_string()) {
if let Type::Complex(hash) = part {
push_me = Some(Type::Complex(hash));
}
}
}
match push_me {
None => global.push(Type::Complex(HashMap::new())),
Some(push_me) => global.push(push_me)
}
}
}
global.push(value.wrap());
if global.len() > 1 {
for i in (1..global.len()).rev() {
let temp_part = global.remove(i);
if let Type::Complex(ref mut parts_minus_1) = global[i-1] {
parts_minus_1.insert(path_tree[i].to_string(),temp_part);
}
}
}
self.global.insert(path_tree[0].to_string(),global.remove(0));
Ok(())
}
pub fn delete_key(&mut self, key_path : &str) -> Option<Type> {
let mut global : Vec<Type> = Vec::new();
let path_tree : Vec<&str> = key_path.split(".").collect();
let mut returned_value : Option<Type> = None;
for i in 0..path_tree.len()-1 {
if i == 0 {
if let Some(part) = self.global.remove(&path_tree[i].to_string()) {
if let Type::Complex(hash) = part {
global.push(Type::Complex(hash));
} else { global.push(Type::Complex(HashMap::new())); }
} else { global.push(Type::Complex(HashMap::new())); }
} else {
let index = global.len()-1;
let mut push_me : Option<Type> = None;
if let Type::Complex(ref mut mut_parts) = global[index] {
if let Some(part) = mut_parts.remove(&path_tree[i].to_string()) {
if let Type::Complex(hash) = part {
push_me = Some(Type::Complex(hash));
}
}
}
match push_me {
None => global.push(Type::None),
Some(push_me) => global.push(push_me)
}
}
}
if path_tree.len() == 1 {
returned_value = self.global.remove(key_path);
} else if global.len() > 0 && path_tree.len() > 0 {
let index = global.len()-1;
if let Type::Complex(ref mut parts_two) = global[index] {
returned_value = parts_two.remove(path_tree[path_tree.len()-1]);
}
}
if global.len() > 0 {
self.global.insert(path_tree[0].to_string(),global.remove(0));
}
returned_value
}
pub fn delete_file(&self) -> bool {
let path = self.ioconfig.get_path_and_file();
info!("Deleting {}",path);
match fs::remove_file(path){
Err(_) => false,
Ok(_) => true,
}
}
pub fn keys(&self) -> Vec<String> {
let mut keys : Vec<String> = Vec::new();
let flat = Settings::flatten(&self);
for k in flat.global.keys() {
keys.push(k.to_string());
}
keys
}
fn get_flat_hash(&self) -> Settings<T> {
Settings::flatten(self)
}
#[allow(dead_code)]
fn is_flat(&self) -> bool {
for (_,value) in self.global.iter() {
if value.is_complex() { return false; }
}
if self.global.len() > 0 {
true
} else {
false
}
}
fn flatten(hash_to_flatten : &Settings<T>) -> Settings<T> {
let mut flat_hash : HashMap<String,Type> = HashMap::new();
for (key,value) in hash_to_flatten.global.iter() {
let temp_type : Type = value.flatten(Some(key.to_string()));
if let Type::Complex(hash) = temp_type {
for (k2,v2) in hash.iter() {
flat_hash.insert(k2.to_string(),v2.clone());
}
} else {
flat_hash.insert(key.to_string(),temp_type);
}
}
Settings {
global : flat_hash,
ioconfig : hash_to_flatten.ioconfig.clone()
}
}
}
impl<T> Add for Settings<T> where T : Format + Clone {
type Output = Settings<T>;
fn add(self, other: Settings<T>) -> Settings<T> {
let mut flat_self = self.get_flat_hash();
let flat_other = other.get_flat_hash();
for (key,value) in flat_other.global.iter() {
flat_self.global.insert(key.to_string(),value.clone());
}
Settings::from_flat(&flat_self)
}
}
impl<T> AddAssign for Settings<T> where T : Format + Clone {
fn add_assign(&mut self, other:Settings<T>) {
let flat_other = other.get_flat_hash();
for (key,value) in flat_other.global.iter() {
let _ = self.set_value(&key,&value);
}
}
}
#[cfg(test)]
mod tests {
use SupportedType;
use Format;
use SettingsRaw;
use Type;
use Settings;
use failure::Error;
use std::collections::HashMap;
#[derive(Clone)]
struct Configuration { }
impl Format for Configuration {
fn filename(&self) -> String { "".to_string() }
fn folder(&self) -> String { "".to_string() }
fn from_str<T>(&self,_:&str) -> Result<SettingsRaw,Error> where T : Format + Clone {
Ok(HashMap::<String,Type>::new())
}
fn to_string<T:?Sized>(&self,_:&T) -> Result<String,Error> where T : SupportedType {
Ok("unimplemented".to_string())
}
}
#[test]
fn set_and_get_value() {
let mut test_obj = Settings::new(Configuration{});
assert_eq!(test_obj.set_value("a.b.c.d","mortan").is_ok(),true);
assert_eq!(test_obj.set_value("a.b.f",&4453).is_ok(),true);
assert_eq!(test_obj.set_value("a.is_enabled",&true).is_ok(),true);
assert_eq!(test_obj.set_value("single",&true).is_ok(),true);
assert_eq!(test_obj.get_value("a.b.c.d"),Some(Type::Text("mortan".to_string())));
assert_eq!(test_obj.get_value("a.b.f"),Some(Type::Int(4453)));
assert_eq!(test_obj.get_value("a.is_enabled"),Some(Type::Switch(true)));
assert_eq!(test_obj.get_value("single"),Some(Type::Switch(true)));
}
#[test]
fn get_value_or() {
let mut test_obj = Settings::new(Configuration{});
assert_eq!(test_obj.set_value("a.b.c.d","mortan").is_ok(),true);
assert_eq!(test_obj.set_value("a.b.c.e","bobby lee").is_ok(),true);
assert_eq!(test_obj.get_value_or("a.b.c.d", "not going to be used"),Type::Text("mortan".to_string()));
assert_eq!(test_obj.get_value_or("a.b.c.f", "will be used"),Type::Text("will be used".to_string()));
}
#[test]
fn add() {
let mut test_obj = Settings::new(Configuration{});
assert_eq!(test_obj.set_value("other.count",&23).is_ok(),true);
assert_eq!(test_obj.set_value("other.thing",&false).is_ok(),true);
let mut test_obj2 = Settings::new(Configuration{});
assert!(test_obj2.set_value("user.place","space").is_ok());
assert!(test_obj2.set_value("other.thing",&132.23).is_ok());
let test_obj3 = test_obj.clone() + test_obj2.clone();
assert_eq!(test_obj3.get_value("other.thing"),Some(Type::Float(132.23)));
assert_eq!(test_obj3.get_value("other.count"),Some(Type::Int(23)));
assert_eq!(test_obj3.get_value("user.place"),Some(Type::Text("space".to_string())));
let test_obj3 = test_obj2.clone() + test_obj.clone();
assert_eq!(test_obj3.get_value("other.thing"),Some(Type::Switch(false)));
assert_eq!(test_obj3.get_value("other.count"),Some(Type::Int(23)));
assert_eq!(test_obj3.get_value("user.place"),Some(Type::Text("space".to_string())));
test_obj += test_obj2;
assert_eq!(test_obj.get_value("other.thing"),Some(Type::Float(132.23)));
assert_eq!(test_obj.get_value("other.count"),Some(Type::Int(23)));
assert_eq!(test_obj.get_value("user.place"),Some(Type::Text("space".to_string())));
}
#[test]
fn flattening() {
let mut flat_gen = Settings::new(Configuration{});
assert!(flat_gen.set_value("user.name","the username").is_ok());
assert!(flat_gen.set_value("user.email","someone@someplace.com").is_ok());
assert!(flat_gen.set_value("software.version",&23).is_ok());
assert!(flat_gen.set_value("software.update_available",&false).is_ok());
assert!(flat_gen.is_flat() == false);
let flat = flat_gen.get_flat_hash();
assert!(flat.is_flat() == true);
assert_eq!(flat.get_value("user"),None);
assert_eq!(flat.get_value("software"),None);
assert_eq!(flat.get_value_absolute("user.name"),Some(Type::Text("the username".to_string())));
assert_eq!(flat.get_value_absolute("software.version"),Some(Type::Int(23)));
let fluff = Settings::from_flat(&flat);
assert!(fluff.is_flat() == false);
assert_eq!(fluff.get_value_absolute("user.name"),None);
assert_eq!(fluff.get_value("user.name"),Some(Type::Text("the username".to_string())));
assert_eq!(fluff.get_value_absolute("software.version"),None);
assert_eq!(fluff.get_value("software.version"),Some(Type::Int(23)));
}
#[test]
fn keys() {
let mut flat_gen = Settings::new(Configuration{});
assert!(flat_gen.set_value("user.name","the username").is_ok());
assert!(flat_gen.set_value("user.email","someone@someplace.com").is_ok());
assert!(flat_gen.set_value("software.version",&23).is_ok());
assert!(flat_gen.set_value("software.update_available",&false).is_ok());
let mut count = 0;
let mut total_count = 0;
for k in flat_gen.keys() {
if k == "user.name" { count +=1; }
if k == "user.email" { count +=1; }
if k == "software.version" { count +=1; }
if k == "software.update_available" { count +=1; }
total_count += 1;
}
assert!(count == 4);
assert!(total_count == 4);
}
#[test]
fn deleting() {
let mut setting = Settings::new(Configuration{});
assert!(setting.set_value("user.name","the username").is_ok());
assert!(setting.set_value("user.email","someone@someplace.com").is_ok());
assert!(setting.set_value("software.version",&23).is_ok());
assert!(setting.set_value("software.update_available",&false).is_ok());
assert_eq!(setting.get_value("software.version"),Some(Type::Int(23)));
assert_eq!(setting.delete_key("software.version"),Some(Type::Int(23)));
assert_eq!(None,setting.get_value("software.version"));
assert_eq!(setting.get_value("software.update_available"),Some(Type::Switch(false)));
assert_eq!(setting.get_value("user.email"),Some(Type::Text("someone@someplace.com".to_string())));
assert_eq!(setting.get_value("user.name"),Some(Type::Text("the username".to_string())));
setting.delete_key("user");
assert_eq!(None,setting.get_value("software.version"));
assert_eq!(None,setting.get_value("user.name"));
assert_eq!(None,setting.get_value("user.email"));
assert_eq!(setting.get_value("software.update_available"),Some(Type::Switch(false)));
}
}