use std::fs;
use std::path::Path;
use std::collections::HashSet;
pub mod lexer;
pub mod parser;
pub use lexer::{Lexer, Symbol, LexerError, LexerOptions};
pub use parser::{Parser, Type, Expression, ParserError};
#[derive(Debug, Clone, Default)]
pub struct Config(HashSet<Expression>);
impl Config {
pub fn parse(path: impl AsRef<Path>, options: LexerOptions) -> Result<Self, Box<dyn std::error::Error>> {
let contents = fs::read(path)?;
let mut lexer = lexer::Lexer::new(contents, options);
let symbols = lexer.lex()?;
let mut parser = parser::Parser::new(symbols);
let config = parser.parse()?;
Ok(config)
}
pub fn set(&mut self, expression: Expression) {
if let Type::Null = expression.value {
self.0.remove(&expression);
} else {
self.0.insert(expression);
}
}
pub fn get(&self, key: impl AsRef<str>) -> Option<&Type> {
let namespaces = key.as_ref().split('.');
let mut value: Option<&Type> = None;
for key in namespaces {
if let Some(inner) = value {
if let Type::Dict(dict) = inner {
value = dict.0.get(&Expression::new(key, Type::Null)).map(|expr| &expr.value);
} else {
return None;
}
} else {
value = self.0.get(&Expression::new(key, Type::Null)).map(|expr| &expr.value);
}
}
value
}
pub fn get_integer(&self, key: impl AsRef<str>) -> Option<&i64> {
if let Type::Integer(inner) = self.get(key)? {
Some(inner)
} else {
None
}
}
pub fn get_float(&self, key: impl AsRef<str>) -> Option<&f64> {
if let Type::Float(inner) = self.get(key)? {
Some(inner)
} else {
None
}
}
pub fn get_boolean(&self, key: impl AsRef<str>) -> Option<&bool> {
if let Type::Boolean(inner) = self.get(key)? {
Some(inner)
} else {
None
}
}
pub fn get_string(&self, key: impl AsRef<str>) -> Option<&String> {
if let Type::String(inner) = self.get(key)? {
Some(inner)
} else {
None
}
}
pub fn get_array(&self, key: impl AsRef<str>) -> Option<&Vec<Type>> {
if let Type::Array(inner) = self.get(key)? {
Some(inner)
} else {
None
}
}
pub fn get_dict(&self, key: impl AsRef<str>) -> Option<&Config> {
if let Type::Dict(inner) = self.get(key)? {
Some(inner)
} else {
None
}
}
}
impl IntoIterator for Config {
type Item = Expression;
type IntoIter = <HashSet<Expression> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl std::fmt::Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Type::*;
match self {
Null => write!(f, "\x1b[34mnull\x1b[m")?,
Integer(number) => write!(f, "\x1b[33m{}\x1b[m", number)?,
Float(number) => write!(f, "\x1b[33m{}\x1b[m", number)?,
String(string) => write!(f, "\x1b[32m{:?}\x1b[m", string)?,
Boolean(bool) => write!(f, "\x1b[31m{}\x1b[m", bool)?,
Array(array) => {
write!(f, "[")?;
for (i, entry) in array.iter().enumerate() {
write!(f, "{entry}")?;
if i != array.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")?;
}
Dict(dict) => {
writeln!(f, "{{")?;
for entry in &dict.0 {
writeln!(f, " {}", format!("{entry}").replace('\n', "\n "))?;
}
write!(f, "}}")?;
}
};
Ok(())
}
}
impl std::fmt::Display for Expression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} = {}", self.key, self.value)
}
}
impl std::fmt::Display for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Config {{")?;
for entry in &self.0 {
writeln!(f, " {}", format!("{entry}").replace('\n', "\n "))?;
}
write!(f, "}}")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_get() {
let mut config = Config::default();
let mut employees = Config::default();
employees.set(Expression::new("employee-1", Type::String("John Doe".into())));
let mut clusters = Config::default();
clusters.set(Expression::new("server-1", Type::String("127.0.0.1".into())));
let mut instances = Config::default();
instances.set(Expression::new("heaven-1", Type::Dict(clusters)));
config.set(Expression::new("cities", Type::Array(Vec::new())));
config.set(Expression::new("employees", Type::Dict(employees)));
config.set(Expression::new("clusters", Type::Dict(instances)));
assert!(config.get("products").is_none());
assert!(config.get("cities").is_some());
assert!(config.get("employees").is_some());
assert!(config.get("employees.employee-1").is_some());
assert!(config.get("employees.employee-2").is_none());
assert!(config.get("clusters").is_some());
assert!(config.get("clusters.heaven-1").is_some());
assert!(config.get("clusters.heaven-1.server-1").is_some());
assert!(config.get("clusters.heaven-1.server-2").is_none());
assert!(config.get("clusters.hell-1").is_none());
assert!(config.get("clusters.hell-1.devin").is_none());
}
}