use std::result::Result as StdResult;
use std::io::{Result as IoResult, Write};
use std::io::{self, BufRead};
use std::fs::File;
use std::fmt::Display;
use std::path::Path;
use std::str::FromStr;
use std::fmt::Debug;
const COMMENT_TAG: &str = "#";
const START_SECTION_TAG: &str = "[";
const END_SECTION_TAG: &str = "]";
const ASSIGN_TAG: &str = "=";
pub const GLOBAL_SECTION: &str = "GLOBAL";
struct KeyValuePair {
key: String,
value: String,
line_cnt: usize,
}
impl Display for KeyValuePair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "key: {}, value: {}\n", self.key, self.value)
}
}
impl PartialEq for KeyValuePair {
fn eq(&self, other: &Self) -> bool {
self.key == other.key && self.value == other.value
}
}
impl KeyValuePair {
fn new(key: String, value: String, line_cnt: usize) -> Self {
Self {
key,
value,
line_cnt
}
}
}
struct Section {
name: String,
values: Vec<KeyValuePair>
}
impl Display for Section {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}]\n", self.name)?;
let mut iter = self.values.iter();
while let Some(key_value) = iter.next() {
write!(f, "{}", key_value)?
}
write!(f, "\n")
}
}
impl PartialEq for Section {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.values == other.values
}
}
impl Section {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
values: vec![],
}
}
fn add(&mut self, key: String, value: String, line_cnt: usize) -> StdResult<(), usize> {
let mut iter = self.values.iter_mut();
while let Some(key_value) = iter.next() {
if key_value.key == key {
return StdResult::Err(key_value.line_cnt.clone());
}
}
self.values.push(KeyValuePair::new(key, value, line_cnt));
StdResult::Ok(())
}
fn get(&self, key: &str) -> Option<&String> {
let mut iter = self.values.iter();
while let Some(key_value) = iter.next() {
if key_value.key == key {
return Some(&key_value.value);
}
}
None
}
fn set(&mut self, key: &str, value: String) -> bool {
let mut iter = self.values.iter_mut();
while let Some(key_value) = iter.next() {
if key_value.key == key {
key_value.value = value;
return true;
}
}
false
}
fn unload(&mut self) {
self.values.clear();
}
}
const OPENING_FILE_ERROR_MESSAGE_IDX: usize = 0usize;
const MISSING_START_SECTION_TAG_MESSAGE_IDX: usize = OPENING_FILE_ERROR_MESSAGE_IDX + 1usize;
const MISSING_END_SECTION_TAG_MESSAGE_IDX: usize = MISSING_START_SECTION_TAG_MESSAGE_IDX + 1usize;
const MISSING_ASSIGN_TAG_MESSAGE_IDX: usize = MISSING_END_SECTION_TAG_MESSAGE_IDX + 1usize;
const MISSING_KEY_MESSAGE_IDX: usize = MISSING_ASSIGN_TAG_MESSAGE_IDX + 1usize;
const DUPLICATED_KEY_MESSAGE_IDX: usize = MISSING_KEY_MESSAGE_IDX + 1usize;
const SECTION_NOT_FOUND_MESSAGE_IDX: usize = DUPLICATED_KEY_MESSAGE_IDX + 1usize;
const KEY_NOT_FOUND_MESSAGE_IDX: usize = SECTION_NOT_FOUND_MESSAGE_IDX + 1usize;
const PARSING_ERROR_MESSAGE_IDX: usize = KEY_NOT_FOUND_MESSAGE_IDX + 1usize;
const WRITING_FILE_ERROR_MESSAGE_IDX: usize = PARSING_ERROR_MESSAGE_IDX + 1usize;
const READING_FILE_ERROR_MESSAGE_IDX: usize = WRITING_FILE_ERROR_MESSAGE_IDX + 1usize;
const ALREADY_INITIALIZED_MESSAGE_IDX: usize = READING_FILE_ERROR_MESSAGE_IDX + 1usize;
pub const MESSAGES_NUMBER: usize = ALREADY_INITIALIZED_MESSAGE_IDX + 1usize;
const SETTINGS_MESSAGES: [&str; MESSAGES_NUMBER] = [
"Error opening settings file: '{}': '{}'",
"Missing start section tag '{}' at line '{}' of settings file: '{}'",
"Missing end section tag '{}' at line '{}' of settings file: '{}'",
"Missing assign tag '{}' at line '{}' of settings file: '{}'",
"Missing key at line '{}' of settings file: '{}'",
"Duplicated key '{}' at line '{}' previously defined at line '{}' of settings file: '{}'",
"Section '{}' not found",
"Section '{}' key '{}' not found",
"Section '{}' key '{}', Parsing error: '{}'",
"Error writing file: '{}': '{}'",
"Error reading file: '{}' at line {}: '{}'",
"Settings already initialized using file: '{}'"
];
pub struct SettingsValue<T> {
pub value: T,
pub error: String
}
enum LineType {
EmptyLine, SectionLine(String), KeyAndValue(String, String), BadFormattedLine(String) }
pub struct Settings {
path: String,
sections: Vec<Section>,
messages_table: Vec<String>
}
impl Settings {
pub fn new() -> Self {
let mut settings = Self {
path: String::from(""),
sections: vec![],
messages_table: vec![]
};
for message in SETTINGS_MESSAGES {
settings.messages_table.push(message.to_string());
}
settings
}
pub fn new_locale_messages(settings_messages: &[&str; MESSAGES_NUMBER]) -> Self {
let mut settings = Self {
path: String::from(""),
sections: vec![],
messages_table: vec![]
};
for message in *settings_messages {
settings.messages_table.push(message.to_string());
}
settings
}
pub fn load<P>(&mut self, path: P) -> StdResult<(), String> where P: AsRef<Path> {
if self.is_initialize() {
return StdResult::Err(
self.format_message(ALREADY_INITIALIZED_MESSAGE_IDX, vec![&self.path]));
}
let result = self.load_private(path);
if StdResult::Ok(()) != result {
self.unload();
}
result
}
pub fn save(&self) -> StdResult<(), String> {
let mut line_texts: Vec<String> = vec![];
if self.is_initialize() {
match File::open(&self.path) {
IoResult::Ok(settings_file) => {
let lines = io::BufReader::new(settings_file).lines();
let mut line_cnt = 1usize;
for line in lines {
match line {
IoResult::Ok(line_text) => {
line_texts.push(line_text);
},
IoResult::Err(ioerror) => {
let error = format!("{:#}", ioerror);
let line = format!("{}", line_cnt);
return StdResult::Err(self.format_message(READING_FILE_ERROR_MESSAGE_IDX,
vec![&self.path, &line, &error]));
}
}
line_cnt = line_cnt + 1;
}
},
IoResult::Err(ioerror) => {
let error = format!("{:#}", ioerror);
return StdResult::Err(self.format_message(OPENING_FILE_ERROR_MESSAGE_IDX,
vec![&self.path, &error]));
}
}
let mut sections_iter = self.sections.iter();
while let Some(section) = sections_iter.next() {
let mut values_iter = section.values.iter();
while let Some(key_value) = values_iter.next() {
if let Some(index) = line_texts[key_value.line_cnt - 1].find(COMMENT_TAG) {
let comment = &line_texts[key_value.line_cnt - 1][index..];
line_texts[key_value.line_cnt - 1] = format!("{} {} {} {}", key_value.key, ASSIGN_TAG, key_value.value, comment);
} else {
line_texts[key_value.line_cnt - 1] = format!("{} {} {}", key_value.key, ASSIGN_TAG, key_value.value);
}
}
}
match File::create(&self.path) {
IoResult::Ok(mut settings_file) => {
for line_text in line_texts {
if let IoResult::Err(ioerror) = settings_file.write_all(format!("{}\n", line_text).as_bytes()) {
let error = format!("{:#}", ioerror);
return StdResult::Err(self.format_message(WRITING_FILE_ERROR_MESSAGE_IDX,
vec![&self.path, &error]));
} else {
if let IoResult::Err(ioerror) = settings_file.flush() {
let error = format!("{:#}", ioerror);
return StdResult::Err(self.format_message(WRITING_FILE_ERROR_MESSAGE_IDX,
vec![&self.path, &error]));
}
}
}
},
IoResult::Err(ioerror) => {
let error = format!("{:#}", ioerror);
return StdResult::Err(self.format_message(OPENING_FILE_ERROR_MESSAGE_IDX,
vec![&self.path, &error]));
}
}
}
StdResult::Ok(())
}
pub fn get<T: FromStr + Display>(&self, section_name: &str, key: &str, default_value: T) -> SettingsValue<T> where <T as FromStr>::Err: Debug {
let mut result = SettingsValue {value: default_value, error: String::from("")};
if let Some(section) = self.get_section(section_name) {
if let Some(value) = section.get(key) {
match value.parse::<T>() {
StdResult::Ok(parsed_value) => {
result.value = parsed_value;
},
StdResult::Err(error) => {
let error = format!("{:#?}", error);
let sname = section_name.to_string();
let kname = key.to_string();
result.error = self.format_message(PARSING_ERROR_MESSAGE_IDX,
vec![&sname, &kname, &error]);
}
}
} else {
let sname = section_name.to_string();
let kname = key.to_string();
result.error = self.format_message(KEY_NOT_FOUND_MESSAGE_IDX,
vec![&sname, &kname]);
}
} else {
let sname = section_name.to_string();
result.error = self.format_message(SECTION_NOT_FOUND_MESSAGE_IDX,
vec![&sname]);
}
result
}
pub fn set<T: Display>(&mut self, section_name: &str, key: &str, value: T) -> StdResult<(), String> {
if let Some(section) = self.get_section_mut(section_name) {
if !section.set(key, value.to_string()) {
let sname = section_name.to_string();
let kname = key.to_string();
return StdResult::Err(self.format_message(KEY_NOT_FOUND_MESSAGE_IDX,
vec![&sname, &kname]));
}
StdResult::Ok(())
} else {
let sname = section_name.to_string();
return StdResult::Err(self.format_message(SECTION_NOT_FOUND_MESSAGE_IDX,
vec![&sname]));
}
}
pub fn section_exists(&self, section_name: &str) -> bool {
let mut result = false;
if let Some(_) = self.get_section(section_name) {
result = true;
}
result
}
pub fn key_exists(&self, section_name: &str, key: &str) -> bool {
let mut result = false;
if let Some(section) = self.get_section(section_name) {
if let Some(_) = section.get(key) {
result = true;
}
}
result
}
fn load_private<P>(&mut self, path: P) -> StdResult<(), String> where P: AsRef<Path> {
let path_str = path.as_ref().as_os_str().to_str().unwrap_or("");
match File::open(path_str) {
IoResult::Ok(settings_file) => {
let lines = io::BufReader::new(settings_file).lines();
let mut line_cnt = 1usize;
let mut current_section = String::from(GLOBAL_SECTION);
for line in lines {
match line {
IoResult::Ok(line_text) => {
match self.line_type(&line_text, &line_cnt, path_str) {
LineType::SectionLine(section_name) => {
if current_section != section_name {
current_section = section_name;
}
},
LineType::KeyAndValue(key, value) => {
self.add_to_section(¤t_section, key, value, line_cnt.clone(), path_str)?;
},
LineType::BadFormattedLine(error) => {
return StdResult::Err(error);
},
LineType::EmptyLine => {
}
}
},
IoResult::Err(ioerror) => {
let error = format!("{:#}", ioerror);
let line = format!("{}", line_cnt);
return StdResult::Err(self.format_message(READING_FILE_ERROR_MESSAGE_IDX,
vec![&path_str.to_string(), &line, &error]));
}
}
line_cnt = line_cnt + 1;
}
},
IoResult::Err(ioerror) => {
let error = format!("{:#}", ioerror);
return StdResult::Err(self.format_message(OPENING_FILE_ERROR_MESSAGE_IDX,
vec![&path_str.to_string(), &error]));
}
}
self.path = path_str.to_string();
StdResult::Ok(())
}
fn unload(&mut self) {
let mut iter = self.sections.iter_mut();
while let Some(section) = iter.next() {
section.unload();
}
self.sections.clear();
}
fn format_message(&self, message_idx: usize, params: Vec<&String>) -> String {
let mut message = self.messages_table[message_idx].clone();
let mut i = 0usize;
while let Some(_) = message.find("{}"){
message = message.replacen("{}", ¶ms[i].to_owned(), 1);
i = i + 1;
if i >= params.len() {
break;
}
}
message
}
fn is_initialize(&self) -> bool {
0 != self.path.len()
}
fn line_type(&self, line_text: &String, line_cnt: &usize, settings_file: &str) -> LineType {
let mut trimmed_line = line_text.clone();
if let Some(index) = trimmed_line.find(COMMENT_TAG) {
trimmed_line.truncate(index)
}
let trimmed_line = trimmed_line.trim();
if 0 == trimmed_line.len() {
return LineType::EmptyLine;
}
let starts_with = trimmed_line.starts_with(START_SECTION_TAG);
let ends_with = trimmed_line.ends_with(END_SECTION_TAG);
if starts_with && ends_with {
let mut section_name = trimmed_line[1..trimmed_line.len() - 1].to_string();
if 0 == section_name.len() {
section_name = String::from(GLOBAL_SECTION);
}
return LineType::SectionLine(section_name);
} else if starts_with && !ends_with {
let tag = END_SECTION_TAG.to_string();
let line = format!("{}", line_cnt);
let path = settings_file.to_string();
let error = self.format_message(MISSING_END_SECTION_TAG_MESSAGE_IDX,
vec![&tag, &line, &path]);
return LineType::BadFormattedLine(error);
} else if !starts_with && ends_with {
let tag = START_SECTION_TAG.to_string();
let line = format!("{}", line_cnt);
let path = settings_file.to_string();
let error = self.format_message(MISSING_START_SECTION_TAG_MESSAGE_IDX,
vec![&tag, &line, &path]);
return LineType::BadFormattedLine(error);
}
if let Some(assign_pos) = trimmed_line.find(ASSIGN_TAG) {
let (mut key, mut value) = trimmed_line.split_at(assign_pos);
key = key.trim();
let removed_assign = value.replace(ASSIGN_TAG, "");
value = removed_assign.trim();
if 0 == key.len() {
let line = format!("{}", line_cnt);
let path = settings_file.to_string();
let error = self.format_message(MISSING_KEY_MESSAGE_IDX,
vec![&line, &path]);
return LineType::BadFormattedLine(error);
}
return LineType::KeyAndValue(key.to_string(), value.to_string());
}
let tag = ASSIGN_TAG.to_string();
let line = format!("{}", line_cnt);
let path = settings_file.to_string();
let error = self.format_message(MISSING_ASSIGN_TAG_MESSAGE_IDX,
vec![&tag, &line, &path]);
return LineType::BadFormattedLine(error);
}
fn add_to_section(&mut self, section_name: &String, key: String, value: String, line_cnt: usize, settings_file: &str) -> StdResult<(), String> {
let mut iter = self.sections.iter_mut();
while let Some(section) = iter.next() {
if section.name == *section_name {
let kname = key.clone();
if let StdResult::Err(previous_line) = section.add(key, value, line_cnt) {
let line = format!("{}", line_cnt);
let previous_line = format!("{}", previous_line);
let path = settings_file.to_string();
let error = self.format_message(DUPLICATED_KEY_MESSAGE_IDX,
vec![&kname, &line, &previous_line, &path]);
return StdResult::Err(error);
}
return StdResult::Ok(());
}
}
let mut section = Section::new(§ion_name);
let _ = section.add(key, value, line_cnt);
self.sections.push(section);
StdResult::Ok(())
}
fn get_section(&self, section_name: &str) -> Option<&Section> {
let mut iter = self.sections.iter();
while let Some(section) = iter.next() {
if section.name == section_name {
return Some(section);
}
}
None
}
fn get_section_mut(&mut self, section_name: &str) -> Option<&mut Section> {
let mut iter = self.sections.iter_mut();
while let Some(section) = iter.next() {
if section.name == section_name {
return Some(section);
}
}
None
}
}
impl Display for Settings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Settings path: {}\n", self.path)?;
let mut iter = self.sections.iter();
while let Some(section) = iter.next() {
write!(f, "{}", section)?;
}
write!(f, "====================================================================\n")
}
}
impl Drop for Settings {
fn drop(&mut self) {
if let StdResult::Err(error) = self.save() {
eprint!("'{}': {:#?}", self.path, error);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::{self, Builder};
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[test]
fn load_errors() {
let mut settings_file_path ="goofy.ini";
let mut settings = Settings::new();
assert_ne!(Result::Ok(()), settings.load(settings_file_path));
settings_file_path ="test_files";
assert_ne!(Result::Ok(()), settings.load(settings_file_path));
}
#[test]
fn missing_start_section_tag() {
let settings_file_path ="test_files/missing_start_section_tag.ini";
let mut settings = Settings::new();
let error = StdResult::Err(
format!("Missing start section tag '{}' at line '{}' of settings file: '{}'",
START_SECTION_TAG, 7, settings_file_path));
assert_eq!(error, settings.load(settings_file_path));
}
#[test]
fn missing_end_section_tag() {
let settings_file_path ="test_files/missing_end_section_tag.ini";
let mut settings = Settings::new();
let error = StdResult::Err(
format!("Missing end section tag '{}' at line '{}' of settings file: '{}'",
END_SECTION_TAG, 11, settings_file_path));
assert_eq!(error, settings.load(settings_file_path));
}
#[test]
fn missing_assign_tag() {
let settings_file_path ="test_files/missing_assign_tag.ini";
let mut settings = Settings::new();
let error = StdResult::Err(
format!("Missing assign tag '{}' at line '3' of settings file: '{}'", ASSIGN_TAG, settings_file_path));
assert_eq!(error, settings.load(settings_file_path));
}
#[test]
fn missing_key() {
let settings_file_path ="test_files/missing_key.ini";
let mut settings = Settings::new();
let error = StdResult::Err(
format!("Missing key at line '5' of settings file: '{}'", settings_file_path)
);
assert_eq!(error, settings.load(settings_file_path));
}
#[test]
fn duplicated_key() {
let settings_file_path ="test_files/duplicated_key.ini";
let mut settings = Settings::new();
let error = StdResult::Err(
format!("Duplicated key 'key1' at line '5' previously defined at line '2' of settings file: '{}'", settings_file_path)
);
assert_eq!(error, settings.load(settings_file_path));
}
#[test]
fn key_value_to_global() {
let settings_file_path ="test_files/key_value_to_global.ini";
let mut settings = Settings::new();
assert_eq!(Result::Ok(()), settings.load(settings_file_path));
let settings_dump =
"Settings path: test_files/key_value_to_global.ini
[GLOBAL]
key: key1, value: true
key: key2, value: 123
key: key3, value: 234.35
key: key4, value: abc
[SECTION_1]
key: key1, value: def
====================================================================
".to_string();
assert_eq!(settings_dump, format!("{}", settings));
}
#[test]
fn set_get_errors() {
let settings_file_path ="test_files/set_get_errors.ini";
let mut settings = Settings::new();
assert_eq!(Result::Ok(()), settings.load(settings_file_path));
assert_eq!("Section 'GENERLA' not found".to_string(), settings.get("GENERLA", "enabled", false).error);
assert_eq!("Section 'GENERAL' key 'enable' not found".to_string(), settings.get("GENERAL", "enable", false).error);
let error = "a123".parse::<i32>().err().unwrap();
let mut error_as_string = format!("Section 'GENERAL' key 'integer_value', Parsing error: '{:#?}'", error);
assert_eq!(error_as_string, settings.get("GENERAL", "integer_value", 10).error);
let error = "123a.35".parse::<f32>().err().unwrap();
error_as_string = format!("Section 'GENERAL' key 'float_value', Parsing error: '{:#?}'", error);
assert_eq!(error_as_string, settings.get("GENERAL", "float_value", -1.0f32).error);
let error = "ciao".parse::<bool>().err().unwrap();
error_as_string = format!("Section 'GENERAL' key 'enabled', Parsing error: '{:#?}'", error);
assert_eq!(error_as_string, settings.get("GENERAL", "enabled", true).error);
let mut error = "Section 'GLOBAL' not found".to_string();
assert_eq!(StdResult::Err(error), settings.set(GLOBAL_SECTION, "enabled", true));
error = "Section 'GENERAL' key 'enable' not found".to_string();
assert_eq!(StdResult::Err(error), settings.set("GENERAL", "enable", false));
}
#[test]
fn no_section_name() {
let settings_file_path ="test_files/no_section_name.ini";
let mut settings = Settings::new();
assert_eq!(Result::Ok(()), settings.load(settings_file_path));
let result = settings.get(GLOBAL_SECTION, "title", "???".to_string());
assert!((result.error.len() == 0 && result.value == "Test empty section name".to_string()));
}
#[test]
fn get_set_ok() {
let settings_file_path ="test_files/settings.ini";
let mut settings = Settings::new();
assert_eq!(Result::Ok(()), settings.load(settings_file_path));
let mut result = settings.get(GLOBAL_SECTION, "bool_value", false);
assert!(result.error.len() == 0 && true == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "bool_value", false));
result = settings.get(GLOBAL_SECTION, "bool_value", true);
assert!(result.error.len() == 0 && false == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "bool_value", true));
result = settings.get(GLOBAL_SECTION, "bool_value", false);
assert!(result.error.len() == 0 && true == result.value);
let mut result = settings.get(GLOBAL_SECTION, "i32_value", -1000i32);
assert!(result.error.len() == 0 && -100i32 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "i32_value", -1000i32));
result = settings.get(GLOBAL_SECTION, "i32_value", -100i32);
assert!(result.error.len() == 0 && -1000i32 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "i32_value", -100i32));
result = settings.get(GLOBAL_SECTION, "i32_value", -1000i32);
assert!(result.error.len() == 0 && -100i32 == result.value);
let mut result = settings.get(GLOBAL_SECTION, "u32_value", 1000u32);
assert!(result.error.len() == 0 && 100u32 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "u32_value", 1000u32));
result = settings.get(GLOBAL_SECTION, "u32_value", 100u32);
assert!(result.error.len() == 0 && 1000u32 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "u32_value", 100u32));
result = settings.get(GLOBAL_SECTION, "u32_value", 1000u32);
assert!(result.error.len() == 0 && 100u32 == result.value);
let mut result = settings.get(GLOBAL_SECTION, "i64_value", -2000i64);
assert!(result.error.len() == 0 && -200i64 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "i64_value", -2000i64));
result = settings.get(GLOBAL_SECTION, "i64_value", -200i64);
assert!(result.error.len() == 0 && -2000i64 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "i64_value", -200i64));
result = settings.get(GLOBAL_SECTION, "i64_value", -2000i64);
assert!(result.error.len() == 0 && -200i64 == result.value);
let mut result = settings.get(GLOBAL_SECTION, "u64_value", 2000u64);
assert!(result.error.len() == 0 && 200u64 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "u64_value", 2000u64));
result = settings.get(GLOBAL_SECTION, "u64_value", 200u64);
assert!(result.error.len() == 0 && 2000u64 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "u64_value", 200u64));
result = settings.get(GLOBAL_SECTION, "u64_value", 2000u64);
assert!(result.error.len() == 0 && 200u64 == result.value);
let mut result = settings.get(GLOBAL_SECTION, "isize_value", -3000isize);
assert!(result.error.len() == 0 && -300isize == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "isize_value", -3000isize));
result = settings.get(GLOBAL_SECTION, "isize_value", -300isize);
assert!(result.error.len() == 0 && -3000isize == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "isize_value", -300isize));
result = settings.get(GLOBAL_SECTION, "isize_value", -3000isize);
assert!(result.error.len() == 0 && -300isize == result.value);
let mut result = settings.get(GLOBAL_SECTION, "usize_value", 3000usize);
assert!(result.error.len() == 0 && 300usize == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "usize_value", 3000usize));
result = settings.get(GLOBAL_SECTION, "usize_value", 300usize);
assert!(result.error.len() == 0 && 3000usize == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "usize_value", 300usize));
result = settings.get(GLOBAL_SECTION, "usize_value", 3000usize);
assert!(result.error.len() == 0 && 300usize == result.value);
let mut result = settings.get(GLOBAL_SECTION, "f32_value", -32.400f32);
assert!(result.error.len() == 0 && -400.32f32 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "f32_value", -32.400f32));
result = settings.get(GLOBAL_SECTION, "f32_value", -400.32f32);
assert!(result.error.len() == 0 && -32.400f32 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "f32_value", -400.32f32));
result = settings.get(GLOBAL_SECTION, "f32_value", -32.400f32);
assert!(result.error.len() == 0 && -400.32f32 == result.value);
let mut result = settings.get(GLOBAL_SECTION, "f64_value", 64.400f64);
assert!(result.error.len() == 0 && 400.64f64 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "f64_value", 64.400f64));
result = settings.get(GLOBAL_SECTION, "f64_value", 400.64f64);
assert!(result.error.len() == 0 && 64.400f64 == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "f64_value", 400.64f64));
result = settings.get(GLOBAL_SECTION, "f64_value", 64.400f64);
assert!(result.error.len() == 0 && 400.64f64 == result.value);
let mut result = settings.get(GLOBAL_SECTION, "string_value", "boh!!!".to_string());
assert!(result.error.len() == 0 && "The quick brown fox jump over the lazy dog" == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "string_value", "boh!!!".to_string()));
result = settings.get(GLOBAL_SECTION, "string_value", "???".to_string());
assert!(result.error.len() == 0 && "boh!!!" == result.value);
assert!(Result::Ok(()) == settings.set(GLOBAL_SECTION, "string_value", "The quick brown fox jump over the lazy dog".to_string()));
result = settings.get(GLOBAL_SECTION, "string_value", "boh!!!".to_string());
assert!(result.error.len() == 0 && "The quick brown fox jump over the lazy dog" == result.value);
let mut original_settings = Settings::new();
let _ = original_settings.load("test_files/original_settings.ini");
assert!(settings.sections == original_settings.sections);
}
#[test]
fn section_or_key_exists() {
let mut settings = Settings::new();
assert_eq!(StdResult::Ok(()), settings.load("test_files/settings.ini"));
assert_eq!(false, settings.section_exists("GOOFY"));
assert_eq!(true, settings.section_exists(GLOBAL_SECTION));
assert_eq!(false, settings.key_exists(GLOBAL_SECTION, "enabled"));
assert_eq!(true, settings.key_exists(GLOBAL_SECTION, "string_value"));
}
#[test]
fn settings_with_locale_messages() {
const IT_SETTINGS_MESSAGES: [&str; MESSAGES_NUMBER] = [
"Errore apertura file di settings: '{}': '{}'",
"Manca il tag di inizio sezione '{}' alla linea '{}' del file di settings: '{}'",
"Manca il tag di fine sezione '{}' alla linea '{}' del file di settings: '{}'",
"Manca il tag di assegnazione '{}' alla linea '{}' del file di settings: '{}'",
"Manca la chiave alla linea '{}' del file di settings: '{}'",
"Chiave duplicata '{}' alla linea '{}' precedentemente definita alla linea '{}' del file di settings: '{}'",
"Sezione '{}' non trovata",
"Sezione '{}' chiave '{}' non trovata",
"Sezione '{}' chiave '{}', Errore di analisi: '{}'",
"Errore scrittura file: '{}': '{}'",
"Errore lettura file: '{}' alla line {}: '{}'",
"Settings giĆ inizializzato utilizzando il file: '{}'"
];
let mut settings = Settings::new_locale_messages(&IT_SETTINGS_MESSAGES);
let ioerror = File::open("goofy.ini").err().unwrap();
let os_message = format!("{:#}", ioerror);
assert_eq!(Result::Err(format!("Errore apertura file di settings: 'goofy.ini': '{}'", os_message)), settings.load("goofy.ini"));
assert_eq!(Result::Err("Manca il tag di inizio sezione '[' alla linea '7' del file di settings: 'test_files/missing_start_section_tag.ini'".to_string()), settings.load("test_files/missing_start_section_tag.ini"));
assert_eq!(Result::Err("Manca il tag di fine sezione ']' alla linea '11' del file di settings: 'test_files/missing_end_section_tag.ini'".to_string()), settings.load("test_files/missing_end_section_tag.ini"));
assert_eq!(Result::Err("Manca il tag di assegnazione '=' alla linea '3' del file di settings: 'test_files/missing_assign_tag.ini'".to_string()), settings.load("test_files/missing_assign_tag.ini"));
assert_eq!(Result::Err("Manca la chiave alla linea '5' del file di settings: 'test_files/missing_key.ini'".to_string()), settings.load("test_files/missing_key.ini"));
assert_eq!(Result::Err("Chiave duplicata 'key1' alla linea '5' precedentemente definita alla linea '2' del file di settings: 'test_files/duplicated_key.ini'".to_string()), settings.load("test_files/duplicated_key.ini"));
assert_eq!(Result::Ok(()), settings.load("test_files/set_get_errors.ini"));
assert_eq!("Sezione 'GENERALE' non trovata".to_string(), settings.get("GENERALE", "enabled", false).error);
assert_eq!("Sezione 'GENERAL' chiave 'enable' non trovata".to_string(), settings.get("GENERAL", "enable", true).error);
assert_eq!("Sezione 'GENERAL' chiave 'float_value', Errore di analisi: 'ParseFloatError {\n kind: Invalid,\n}'".to_string(), settings.get("GENERAL", "float_value", -1.0f32).error);
assert_eq!(Result::Err("Settings giĆ inizializzato utilizzando il file: 'test_files/set_get_errors.ini'".to_string()), settings.load("test_files/settings.ini"));
assert_eq!(true , settings.get("LOG", "enabled", false).value);
}
#[test]
fn multi_thread_settings() {
let settings_file_path ="test_files/settings.ini";
let mut settings = Settings::new();
assert_eq!(Result::Ok(()), settings.load(settings_file_path));
let settings= Arc::new(Mutex::new(settings));
let thread1_settings = settings.clone();
let builder = Builder::new();
let thread_handler = builder.name("threa1".to_string()).spawn(move || {
println!("{} START", thread::current().name().unwrap_or("???"));
thread::sleep(Duration::from_millis(3000));
let _ = thread1_settings.lock().unwrap().set(GLOBAL_SECTION, "bool_value", false);
println!("{} END", thread::current().name().unwrap_or("???"));
}).unwrap();
let mut wait = true;
let mut cnt = 1;
while wait {
{
wait = settings.lock().unwrap().get(GLOBAL_SECTION, "bool_value", true).value;
}
thread::sleep(Duration::from_millis(100));
print!(".");
std::io::stdout().flush().unwrap_or(());
if 0 == cnt % 4 {
println!("");
}
cnt = cnt + 1;
}
println!("");
thread_handler.join().unwrap_or(());
{
assert!(Result::Ok(()) == settings.lock().unwrap().set(GLOBAL_SECTION, "bool_value", true));
}
let result = settings.lock().unwrap().get(GLOBAL_SECTION, "bool_value", false);
assert!(result.error.len() == 0 && true == result.value);
}
}