use std::{
collections::{HashMap, hash_map},
str::FromStr,
path::Path,
fs,
fmt::Display,
};
use crate::{
SlopValue,
error::{SlopError, SlopResult},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Slop {
items: HashMap<String, SlopValue>,
}
impl Slop {
pub fn new() -> Self {
Self { items: HashMap::new() }
}
#[inline(always)]
pub fn open<P: AsRef<Path>>(path: P) -> SlopResult<Self> {
fs::read_to_string(path)?.parse()
}
pub fn iter(&self) -> hash_map::Iter<'_, String, SlopValue> {
self.items.iter()
}
pub fn iter_mut(&mut self) -> hash_map::IterMut<'_, String, SlopValue> {
self.items.iter_mut()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn contains_key(&self, key: &str) -> bool {
self.items.contains_key(key)
}
pub fn get(&self, key: &str) -> Option<&SlopValue> {
self.items.get(key)
}
#[inline(always)]
pub fn get_string(&self, key: &str) -> Option<&String> {
self.get(key)?.string()
}
#[inline(always)]
pub fn get_list(&self, key: &str) -> Option<&Vec<String>> {
self.get(key)?.list()
}
pub fn insert<V: Into<SlopValue>>(&mut self, key: String, value: V)
-> SlopResult<Option<SlopValue>>
{
if key.chars().any(|c| c == '=') || key.ends_with('{') {
Err(SlopError::InvalidKey(key))
} else {
Ok(self.items.insert(key, value.into()))
}
}
pub fn insert_unchecked<V: Into<SlopValue>>(&mut self, key: String, value: V)
-> Option<SlopValue>
{
self.items.insert(key, value.into())
}
pub fn append_slop_string(&mut self, slop_str: &str) -> Result<(), SlopError>
{
let lines: Vec<&str> = slop_str.split('\n').collect();
let mut skip_lines = 0usize;
for i in 0..lines.len() {
if skip_lines > 0 {
skip_lines -= 1;
continue;
}
let line = clean_up_line(lines[i]);
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, value)) = parse_string_kv(line) {
self.insert(key.to_string(), value)?;
} else if let Some((key, value, skip))
= parse_list_kv(&lines, i)?
{
self.insert(key.to_string(), value)?;
skip_lines = skip;
} else {
return Err(SlopError::InvalidLine(i, line.to_string()));
}
}
Ok(())
}
pub fn to_string_pretty(&self) -> String {
self.items.iter().fold(String::new(), |mut acc, (k, v)| {
acc.push_str(&k);
acc.push_str(&v.to_string_pretty());
acc.push('\n');
acc
})
}
#[inline(always)]
pub fn save<P: AsRef<Path>>(&self, path: P) -> SlopResult<()> {
Ok(fs::write(path, self.to_string())?)
}
#[inline(always)]
pub fn save_pretty<P: AsRef<Path>>(&self, path: P) -> SlopResult<()> {
Ok(fs::write(path, self.to_string_pretty())?)
}
}
impl Display for Slop {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.items.iter().fold(String::new(), |mut acc, (k, v)| {
acc.push_str(k);
acc.push_str(&v.to_string());
acc.push('\n');
acc
}))
}
}
impl FromStr for Slop {
type Err = SlopError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut slop = Slop::new();
slop.append_slop_string(s)?;
Ok(slop)
}
}
impl IntoIterator for Slop {
type Item = (String, SlopValue);
type IntoIter = <HashMap<String, SlopValue> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
}
}
#[inline]
fn clean_up_line(line: &str) -> &str {
line.strip_suffix('\r').unwrap_or(line).trim_start()
}
fn parse_string_kv(line: &str) -> Option<(&str, SlopValue)> {
let (key, value) = line.split_once('=')?;
Some((key, value.into()))
}
fn parse_list_kv<'a>(lines: &'a Vec<&'a str>, start_index: usize)
-> Result<Option<(&'a str, SlopValue, usize)>, SlopError>
{
let key = if let Some(k) = clean_up_line(lines[start_index]).strip_suffix('{') {
k
} else {
return Ok(None);
};
let mut values = vec![];
for i in (start_index + 1)..lines.len() {
let line = clean_up_line(lines[i]);
if line == "}" {
return Ok(Some((key, values.into(), i - start_index)));
}
values.push(line.to_string());
}
Err(SlopError::UnclosedList(start_index, lines[start_index].to_string()))
}