use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
#[derive(Debug)]
pub enum SuperError {
NoKey,
NoValue,
ElementExists(SuperValue),
IOError(std::io::Error),
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum SuperValue {
Single(String),
List(Vec<String>),
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
enum TokenType {
Character(char),
Seperator,
Backslash,
Comment,
}
fn lex_str(conf: &str, seperator: char) -> Vec<Vec<TokenType>> {
let mut output: Vec<Vec<TokenType>> = vec![];
for line in conf.lines() {
let mut buffer: Vec<TokenType> = vec![];
for line_char in line.chars() {
let got_token = if line_char == seperator {
TokenType::Seperator
} else {
match line_char {
'#' => TokenType::Comment,
'\\' => TokenType::Backslash,
t => TokenType::Character(t),
}
};
buffer.push(got_token);
}
output.push(buffer);
}
output
}
pub fn parse_custom_sep(
conf: &str,
seperator: char,
) -> Result<HashMap<String, SuperValue>, SuperError> {
let mut output: HashMap<String, SuperValue> = HashMap::new();
let tokens = lex_str(conf, seperator);
let mut _expect_new_level = false;
for token_line in tokens {
let mut buffer = vec![String::new()];
let mut ignore_special = false; let mut is_comment = false;
for token in token_line {
match token {
TokenType::Comment => {
is_comment = true;
break;
}
TokenType::Backslash => ignore_special = !ignore_special, TokenType::Seperator => {
if ignore_special {
buffer.last_mut().unwrap().push(seperator);
ignore_special = false;
} else if !buffer.last().unwrap().is_empty() {
buffer.push(String::new())
}
}
TokenType::Character(c) => buffer.last_mut().unwrap().push(c),
}
}
if is_comment {
continue;
}
let key = buffer.remove(0);
let final_value = match buffer.len() {
0 => {
_expect_new_level = true;
continue;
} 1 => SuperValue::Single(buffer[0].clone()),
_ => SuperValue::List(buffer),
};
match output.insert(key, final_value) {
Some(element) => return Err(SuperError::ElementExists(element)),
None => (),
};
}
Ok(output)
}
pub fn parse(conf: impl AsRef<str>) -> Result<HashMap<String, SuperValue>, SuperError> {
parse_custom_sep(conf.as_ref(), ' ')
}
pub fn parse_file(conf_path: PathBuf) -> Result<HashMap<String, SuperValue>, SuperError> {
let mut file = match File::open(conf_path) {
Ok(f) => f,
Err(e) => return Err(SuperError::IOError(e)),
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => parse(contents),
Err(e) => Err(SuperError::IOError(e)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_parse() {
let input = "my_key my_value";
parse(input).unwrap();
}
#[test]
fn comment_test() {
let input = "# This is a line comment, should not return any output!";
assert_eq!(HashMap::new(), parse(input).unwrap());
}
#[test]
fn backstroke_seperator_torture() {
let input = "my\\ key this\\ is\\ the\\ value";
let mut exp_output = HashMap::new();
exp_output.insert(
"my key".to_string(),
SuperValue::Single("this is the value".to_string()),
);
assert_eq!(exp_output, parse(input).unwrap())
}
#[test]
fn realistic_path() {
let input = "your_path /home/user/Cool\\ Path/x.txt";
let mut exp_output = HashMap::new();
exp_output.insert(
"your_path".to_string(),
SuperValue::Single("/home/user/Cool Path/x.txt".to_string()),
);
assert_eq!(exp_output, parse(input).unwrap());
}
#[test]
fn eol_comment() {
let input = "my_key my_value # eol comment";
parse(input).unwrap();
}
#[test]
fn item_list() {
let input = "my_key first_val second_val";
let mut exp_out = HashMap::new();
exp_out.insert(
"my_key".to_string(),
SuperValue::List(vec!["first_val".to_string(), "second_val".to_string()]),
);
assert_eq!(exp_out, parse(input).unwrap());
}
#[test]
fn custom_seperator() {
let input = "arrow>demonstration";
let mut exp_out = HashMap::new();
exp_out.insert(
"arrow".to_string(),
SuperValue::Single(String::from("demonstration")),
);
assert_eq!(exp_out, parse_custom_sep(input, '>').unwrap());
}
#[test]
fn custom_seperator_space() {
let mut output: HashMap<String, SuperValue> = HashMap::new();
output.insert(
"hi".into(),
SuperValue::Single("is cool?: no..".to_string()),
);
assert_eq!(
parse_custom_sep("hi:is cool?\\: no..", ':').unwrap(),
output
)
}
}