use crate::model::KeyValuePairs;
use normalized_line_endings::{Normalized, LF};
use std::path::Path;
use std::{fs, io};
pub fn load_from_string(input: &str) -> KeyValuePairs {
Loader::new(input, &['"']).load()
}
pub fn load_from_string_markers(input: &str, markers: &[char]) -> KeyValuePairs {
Loader::new(input, markers).load()
}
pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<KeyValuePairs> {
Ok(load_from_string(&fs::read_to_string(path)?))
}
pub fn load_from_file_markers<P: AsRef<Path>>(path: P, markers: &[char]) -> io::Result<KeyValuePairs> {
Ok(load_from_string_markers(&fs::read_to_string(path)?, markers))
}
pub const NULL: char = 0 as char;
#[derive(Copy, Clone)]
enum State {
Key,
KeyExt,
Value,
ValueExt,
}
struct Loader<'a> {
state: State,
buffer: String,
key: String,
marker: char,
markers: &'a [char],
input: &'a str,
output: KeyValuePairs,
}
impl<'a> Loader<'a> {
fn new(input: &'a str, markers: &'a [char]) -> Self {
Loader {
state: State::Key,
buffer: String::new(),
key: String::new(),
marker: 0 as char,
markers,
input,
output: KeyValuePairs::new(),
}
}
fn load(mut self) -> KeyValuePairs {
let mut chars = self.input.chars().normalized().peekable();
loop {
let current_char = chars.next().unwrap_or(NULL);
if current_char == NULL {
return self.output;
}
let next_char = chars.peek().cloned().unwrap_or(NULL);
match self.state {
State::Key => match (current_char, next_char) {
(ch, _) if self.is_allowed_marker(ch) => {
self.marker = ch;
self.clear_buffer(State::KeyExt);
}
(LF, _) => self.consume_non_empty_key(),
(ch, _) => self.consume_char(ch),
},
State::KeyExt => match (current_char, next_char) {
(ch, LF) if self.is_marker(ch) => self.consume_key(),
(ch, _) => self.consume_char(ch),
},
State::Value => match (current_char, next_char) {
(ch, _) if self.is_allowed_marker(ch) => {
self.marker = ch;
self.clear_buffer(State::ValueExt);
}
(LF, _) => self.consume_non_empty_value(),
(ch1, ch2) => {
self.consume_char(ch1);
if ch2 == NULL {
self.consume_non_empty_value();
}
}
},
State::ValueExt => match (current_char, next_char) {
(ch, LF) if self.is_marker(ch) => self.consume_value(),
(ch, _) => self.consume_char(ch),
},
}
}
}
fn consume_char(&mut self, ch: char) {
self.buffer.push(ch);
}
fn consume_key(&mut self) {
self.key.clear();
self.key.push_str(&self.buffer);
self.buffer.clear();
self.state = State::Value;
}
fn consume_non_empty_key(&mut self) {
self.buffer = self.buffer.trim().to_string();
if !self.buffer.is_empty() {
self.consume_key();
}
}
fn consume_non_empty_value(&mut self) {
self.buffer = self.buffer.trim().to_string();
if !self.buffer.is_empty() {
self.consume_value();
}
}
fn consume_value(&mut self) {
self.output.key_value_pairs.insert(self.key.clone(), self.buffer.clone());
self.output.ordered_keys.push(self.key.clone());
self.output.ordered_values.push(self.buffer.clone());
self.key.clear();
self.buffer.clear();
self.state = State::Key;
}
fn clear_buffer(&mut self, next_state: State) {
self.buffer.clear();
self.state = next_state;
}
fn is_allowed_marker(&self, ch: char) -> bool {
self.markers.contains(&ch)
}
fn is_marker(&self, ch: char) -> bool {
ch == self.marker
}
}