use crate::helpers::{config::get_config_ini_path, read_line, traits::LogIfErr};
use std::{fs::File, io::Read, path::Path};
use thiserror::Error;
pub struct INIReader<'a> {
reader: Box<dyn Read + 'a>,
section: Option<String>,
buf: [u8; 1],
}
impl INIReader<'_> {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
Ok(Self::from(Box::new(File::open(path)?) as Box<dyn Read>))
}
pub fn from_rimpy_config_ini() -> Result<Self, INIError> {
let path = get_config_ini_path().map_err(INIError::VarError)?;
Self::new(path).map_err(INIError::IOError)
}
}
impl<'a> From<Box<dyn Read + 'a>> for INIReader<'a> {
fn from(reader: Box<dyn Read + 'a>) -> Self {
Self {
reader,
section: None,
buf: [0u8],
}
}
}
impl Iterator for INIReader<'_> {
type Item = Result<INIKeyValuePair, INIError>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(Some(mut line)) = read_line(&mut self.reader, &mut self.buf).log_if_err() {
if line.len() < 3
|| !line.contains(|c: char| !c.is_whitespace())
|| line.starts_with(';')
{
continue;
}
if line.trim().starts_with('[') {
line.drain(0..=line.find('[').unwrap());
line.drain(line.find(']').unwrap()..line.len());
self.section = Some(line);
continue;
}
let parts: Vec<&str> = line.split('=').collect();
if parts.len() < 2 {
return Some(Err(INIError::InvalidData(String::from(
"Expected `=` sign when parsing line of INI file, found none.",
))));
}
let section = self.section.clone();
let key = String::from(parts[0].trim());
let value = String::from(parts[1..].join("=").trim());
return Some(Ok(INIKeyValuePair {
section,
key,
value,
}));
}
None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct INIKeyValuePair {
pub section: Option<String>,
pub key: String,
pub value: String,
}
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum INIError {
#[error("invalid INI syntax: {0}")]
InvalidData(String),
#[error("failed to read INI file: {0}")]
IOError(#[from] std::io::Error),
#[error("couldn't read env: {0}")]
VarError(#[from] std::env::VarError),
}