use super::{
error::{Error, ErrorKind, TagError},
parser::{self, Value},
};
use crate::common::{color::Color, percentage::Percentage};
use fraction::Fraction;
use itertools::{Either, Itertools};
use regex::Regex;
use std::{collections::HashMap, hash::Hash, mem::replace};
#[derive(Default)]
pub struct Config(pub HashMap<String, ConfigItem>, pub Box<Maps>);
#[derive(Default)]
pub struct Maps {
pub string: HashMap<String, Value>,
pub regex: Vec<(Regex, Value)>,
pub number: HashMap<Fraction, Value>,
pub percentage: HashMap<Percentage, Value>,
pub boolean: HashMap<bool, Value>,
pub color: HashMap<Color, Value>,
pub tuple: Vec<(Vec<Value>, Value)>,
pub set: Vec<(Vec<Value>, Value)>,
}
pub enum ConfigItem {
Value(Value),
Struct(Config),
Map(Vec<(Value, Config)>),
}
impl Config {
fn insert(&mut self, value: parser::ConfigItem) -> Option<ConfigItem> {
let Config(target, maps) = self;
match value {
parser::ConfigItem::Property(key, value) => match key {
parser::Value::Identifier(id) => target.insert(id, ConfigItem::Value(value)),
parser::Value::String(s) => maps.string.insert(s, value).map(ConfigItem::Value),
parser::Value::Regex(r) => {
maps.regex.push((r, value));
None
}
parser::Value::Number(n) => maps.number.insert(n, value).map(ConfigItem::Value),
parser::Value::Percentage(p) => {
maps.percentage.insert(p, value).map(ConfigItem::Value)
}
parser::Value::Boolean(b) => maps.boolean.insert(b, value).map(ConfigItem::Value),
parser::Value::Color(c) => maps.color.insert(c, value).map(ConfigItem::Value),
parser::Value::Tuple(t) => {
maps.tuple.push((t, value));
None
}
parser::Value::Set(s) => {
maps.set.push((s, value));
None
}
},
parser::ConfigItem::Block(key, value) => {
target.insert(key, ConfigItem::Struct(value.into()))
}
parser::ConfigItem::NamedBlock(key, name, value) => {
let t = target
.entry(key)
.or_insert_with(|| ConfigItem::Map(Vec::new()));
if let ConfigItem::Map(t) = t {
t.push((name, value.into()));
None
} else {
Some(replace(t, ConfigItem::Map(vec![(name, value.into())])))
}
}
}
}
}
impl From<parser::Config> for Config {
fn from(value: parser::Config) -> Self {
let mut target = Config::default();
for item in value {
target.insert(item);
}
target
}
}
#[inline]
pub fn get_item_raw(config: &mut Config, name: &'static str) -> Result<ConfigItem, Error> {
config
.0
.remove(name)
.ok_or(ErrorKind::MissingField(name).into())
}
#[inline]
pub fn get_item<T>(config: &mut Config, name: &'static str) -> Result<T, Error>
where
ConfigItem: TryInto<T, Error = Error>,
{
get_item_raw(config, name)?.try_into().tag(name)
}
#[inline]
pub fn get_item_struct<T: TryFrom<Config, Error = Error>>(
config: &mut Config,
name: &'static str,
) -> Result<T, Error> {
match get_item_raw(config, name)? {
ConfigItem::Struct(s) => s.try_into(),
_ => Err(ErrorKind::WrongType("block").into()),
}
.tag(name)
}
impl TryFrom<ConfigItem> for String {
type Error = Error;
fn try_from(value: ConfigItem) -> Result<Self, Self::Error> {
match value {
ConfigItem::Value(Value::String(s)) => Ok(s),
_ => Err(ErrorKind::WrongType("string").into()),
}
}
}
impl TryFrom<ConfigItem> for Regex {
type Error = Error;
fn try_from(value: ConfigItem) -> Result<Self, Self::Error> {
match value {
ConfigItem::Value(Value::Regex(r)) => Ok(r),
_ => Err(ErrorKind::WrongType("regex").into()),
}
}
}
impl TryFrom<ConfigItem> for Fraction {
type Error = Error;
fn try_from(value: ConfigItem) -> Result<Self, Self::Error> {
match value {
ConfigItem::Value(Value::Number(n)) => Ok(n),
_ => Err(ErrorKind::WrongType("number").into()),
}
}
}
impl TryFrom<ConfigItem> for bool {
type Error = Error;
fn try_from(value: ConfigItem) -> Result<Self, Self::Error> {
match value {
ConfigItem::Value(Value::Boolean(b)) => Ok(b),
_ => Err(ErrorKind::WrongType("boolean").into()),
}
}
}
impl TryFrom<ConfigItem> for Color {
type Error = Error;
fn try_from(value: ConfigItem) -> Result<Self, Self::Error> {
match value {
ConfigItem::Value(Value::Color(c)) => Ok(c),
_ => Err(ErrorKind::WrongType("color").into()),
}
}
}
pub fn get_item_named_struct<T, O>(
config: &mut Config,
name: &'static str,
mut filter: impl FnMut(Value) -> Option<O>,
) -> Result<Vec<(O, T)>, Error>
where
Config: TryInto<T, Error = Error>,
{
match get_item_raw(config, name)? {
ConfigItem::Map(s) => s
.into_iter()
.filter_map(|(v, c)| filter(v).map(|k| (k, c)))
.map(|(k, c)| c.try_into().map(|t| (k, t)))
.collect(),
_ => Err(ErrorKind::WrongType("block").into()),
}
.tag(name)
}
#[inline]
pub fn get_item_map<K, V, M, MI>(config: &mut Config, name: &'static str) -> Result<M, Error>
where
K: MappedProperty<Output = MI>,
V: FilteredFrom<Value>,
M: MapOrVec<K, V>,
MI: MapOrVec<K, Value>,
{
let maps = config
.0
.get_mut(name)
.ok_or(Error::from(ErrorKind::MissingField(name)))
.tag(name)?;
match maps {
&mut ConfigItem::Struct(ref mut x) => {
let Config(_, ref mut m) = x;
let target = K::get(m); let (taken, left): (M, MI) = std::mem::take(target) .into_iter()
.partition_map(|(k, v)| match V::filtered_from(v) {
FilteredMapResult::Take(v) => Either::Left((k, v)),
FilteredMapResult::Leave(v) => Either::Right((k, v)),
});
*target = left;
Ok(taken)
}
_ => Err(ErrorKind::WrongType("block").into()),
}
.tag(name)
}
pub trait MapOrVec<K, V>: IntoIterator<Item = (K, V)> + Default + Extend<(K, V)> {}
impl<K, V> MapOrVec<K, V> for Vec<(K, V)> {}
impl<K: Eq + Hash, V> MapOrVec<K, V> for HashMap<K, V> {}
pub trait MappedProperty {
type Output;
fn get(maps: &mut Maps) -> &mut Self::Output;
}
impl MappedProperty for String {
type Output = HashMap<String, Value>;
fn get(maps: &mut Maps) -> &mut Self::Output {
&mut maps.string
}
}
impl MappedProperty for Regex {
type Output = Vec<(Regex, Value)>;
fn get(maps: &mut Maps) -> &mut Self::Output {
&mut maps.regex
}
}
impl MappedProperty for Fraction {
type Output = HashMap<Fraction, Value>;
fn get(maps: &mut Maps) -> &mut Self::Output {
&mut maps.number
}
}
impl MappedProperty for Percentage {
type Output = HashMap<Percentage, Value>;
fn get(maps: &mut Maps) -> &mut Self::Output {
&mut maps.percentage
}
}
impl MappedProperty for bool {
type Output = HashMap<bool, Value>;
fn get(maps: &mut Maps) -> &mut Self::Output {
&mut maps.boolean
}
}
impl MappedProperty for Color {
type Output = HashMap<Color, Value>;
fn get(maps: &mut Maps) -> &mut Self::Output {
&mut maps.color
}
}
impl MappedProperty for Vec<Value> {
type Output = Vec<(Vec<Value>, Value)>;
fn get(maps: &mut Maps) -> &mut Self::Output {
&mut maps.tuple
}
}
pub enum FilteredMapResult<Input, Output> {
Take(Output),
Leave(Input),
}
pub trait FilteredFrom<T>: Sized {
fn filtered_from(value: T) -> FilteredMapResult<T, Self>;
}
impl FilteredFrom<Value> for String {
fn filtered_from(value: Value) -> FilteredMapResult<Value, Self> {
match value {
Value::String(s) => FilteredMapResult::Take(s),
_ => FilteredMapResult::Leave(value),
}
}
}
impl FilteredFrom<Value> for Regex {
fn filtered_from(value: Value) -> FilteredMapResult<Value, Self> {
match value {
Value::Regex(r) => FilteredMapResult::Take(r),
_ => FilteredMapResult::Leave(value),
}
}
}
impl FilteredFrom<Value> for Fraction {
fn filtered_from(value: Value) -> FilteredMapResult<Value, Self> {
match value {
Value::Number(n) => FilteredMapResult::Take(n),
_ => FilteredMapResult::Leave(value),
}
}
}
impl FilteredFrom<Value> for Percentage {
fn filtered_from(value: Value) -> FilteredMapResult<Value, Self> {
match value {
Value::Percentage(p) => FilteredMapResult::Take(p),
_ => FilteredMapResult::Leave(value),
}
}
}
impl FilteredFrom<Value> for Color {
fn filtered_from(value: Value) -> FilteredMapResult<Value, Self> {
match value {
Value::Color(c) => FilteredMapResult::Take(c),
_ => FilteredMapResult::Leave(value),
}
}
}
pub struct Identifier(pub String);
impl FilteredFrom<Value> for Identifier {
fn filtered_from(value: Value) -> FilteredMapResult<Value, Self> {
match value {
Value::Identifier(t) => FilteredMapResult::Take(Identifier(t)),
_ => FilteredMapResult::Leave(value),
}
}
}
impl FilteredFrom<Value> for Vec<Value> {
fn filtered_from(value: Value) -> FilteredMapResult<Value, Self> {
match value {
Value::Tuple(t) => FilteredMapResult::Take(t),
_ => FilteredMapResult::Leave(value),
}
}
}