use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum BlockStates {
Variants {
variants: HashMap<String, Variant>,
},
Multipart {
#[serde(rename = "multipart")]
cases: Vec<multipart::Case>,
},
}
impl BlockStates {
pub fn variants(&self) -> Option<&HashMap<String, Variant>> {
match self {
Self::Variants { ref variants } => Some(variants),
Self::Multipart { .. } => None,
}
}
pub fn cases(&self) -> Option<&[multipart::Case]> {
match self {
Self::Variants { .. } => None,
Self::Multipart { cases: multipart } => Some(&multipart[..]),
}
}
pub fn into_multipart(self) -> Vec<multipart::Case> {
match self {
Self::Multipart { cases } => cases,
Self::Variants { variants } => {
if variants.len() == 1 {
let variant = variants
.into_iter()
.map(|(_, variant)| variant)
.next()
.unwrap();
let case = multipart::Case {
when: None,
apply: variant,
};
vec![case]
} else {
variants
.into_iter()
.map(|(state_values, variant)| {
let state_values: HashMap<String, multipart::StateValue> = state_values
.split(',')
.map(|state_value| {
let split: Vec<&str> = state_value.split('=').collect();
(split[0], split[1])
})
.map(|(state, value)| {
(String::from(state), multipart::StateValue::from(value))
})
.collect();
let condition = multipart::Condition { and: state_values };
let when_clause = multipart::WhenClause::Single(condition);
multipart::Case {
when: Some(when_clause),
apply: variant,
}
})
.collect()
}
}
}
}
}
impl Default for BlockStates {
fn default() -> Self {
Self::Variants {
variants: Default::default(),
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum Variant {
Single(ModelProperties),
Multiple(Vec<ModelProperties>),
}
impl Default for Variant {
fn default() -> Self {
Self::Single(Default::default())
}
}
impl Variant {
pub fn models(&self) -> &[ModelProperties] {
match self {
Self::Single(model) => std::slice::from_ref(model),
Self::Multiple(models) => &models[..],
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct ModelProperties {
pub model: String,
#[serde(default = "ModelProperties::default_rotation")]
pub x: i32,
#[serde(default = "ModelProperties::default_rotation")]
pub y: i32,
#[serde(rename = "uvlock", default = "ModelProperties::default_uv_lock")]
pub uv_lock: bool,
#[serde(default = "ModelProperties::default_weight")]
pub weight: u32,
}
impl ModelProperties {
pub(crate) const fn default_rotation() -> i32 {
0
}
pub(crate) const fn default_uv_lock() -> bool {
false
}
pub(crate) const fn default_weight() -> u32 {
1
}
}
impl Default for ModelProperties {
fn default() -> Self {
Self {
model: Default::default(),
x: Self::default_rotation(),
y: Self::default_rotation(),
uv_lock: Self::default_uv_lock(),
weight: Self::default_weight(),
}
}
}
pub mod multipart {
use super::*;
#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq)]
pub struct Case {
pub when: Option<WhenClause>,
pub apply: Variant,
}
impl Case {
pub fn applies<'a, I>(&self, state_values: I) -> bool
where
I: IntoIterator<Item = (&'a str, &'a StateValue)> + Clone,
{
if let Some(ref when_clause) = self.when {
when_clause.applies(state_values)
} else {
true
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum WhenClause {
Single(Condition),
Or {
#[serde(rename = "OR")]
or: Vec<Condition>,
},
}
impl WhenClause {
pub fn conditions(&self) -> &[Condition] {
match self {
Self::Single(condition) => std::slice::from_ref(condition),
Self::Or { or } => &or[..],
}
}
pub fn applies<'a, I>(&self, state_values: I) -> bool
where
I: IntoIterator<Item = (&'a str, &'a StateValue)> + Clone,
{
self.conditions()
.iter()
.any(|condition| condition.applies(state_values.clone()))
}
}
#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq)]
pub struct Condition {
#[serde(flatten)]
pub and: HashMap<String, StateValue>,
}
impl Condition {
pub fn applies<'a, I>(&self, state_values: I) -> bool
where
I: IntoIterator<Item = (&'a str, &'a StateValue)>,
{
let state_values: HashMap<&'a str, &'a StateValue> = state_values.into_iter().collect();
self.and.iter().all(|(state, required_value)| {
state_values
.get(state.as_str())
.map(|value| *required_value == **value)
.unwrap_or(false)
})
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(untagged)]
pub enum StateValue {
Bool(bool),
String(String),
}
impl StateValue {
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(b) => Some(*b),
Self::String(s) if s == "true" => Some(true),
Self::String(s) if s == "false" => Some(false),
_ => None,
}
}
}
impl PartialEq for StateValue {
fn eq(&self, other: &Self) -> bool {
match self {
Self::String(s) => {
match other {
Self::Bool(other_b) => {
self.as_bool().map(|b| b == *other_b).unwrap_or(false)
}
Self::String(other_s) => {
s == other_s
|| s.split('|').any(|s| s == other_s)
|| other_s.split('|').any(|other_s| s == other_s)
}
}
}
Self::Bool(b) => {
if let Some(other_b) = other.as_bool() {
*b == other_b
} else {
false
}
}
}
}
}
impl From<bool> for StateValue {
fn from(source: bool) -> Self {
Self::Bool(source)
}
}
impl<'a> From<&'a str> for StateValue {
fn from(source: &'a str) -> Self {
Self::String(String::from(source))
}
}
impl From<String> for StateValue {
fn from(source: String) -> Self {
Self::String(source)
}
}
}
#[cfg(test)]
mod test {
use super::multipart::*;
use super::*;
use maplit::hashmap;
fn make_single_variant(model_name: &str) -> Variant {
Variant::Single(ModelProperties {
model: String::from(model_name),
..Default::default()
})
}
fn do_test(
blockstates: BlockStates,
state_values: &HashMap<String, StateValue>,
expected_models: &[&'static str],
) {
let cases = blockstates.into_multipart();
let actual_models = cases
.iter()
.filter(|case| {
case.applies(
state_values
.iter()
.map(|(state, value)| (state.as_str(), value)),
)
})
.flat_map(|case| case.apply.models())
.map(|model_properties| model_properties.model.as_str())
.collect::<Vec<_>>();
assert_eq!(&actual_models[..], expected_models);
}
#[test]
fn test_single_variant() {
let blockstates = BlockStates::Variants {
variants: hashmap! {
String::from("") => make_single_variant("model1"),
},
};
let state_values = HashMap::default();
do_test(blockstates, &state_values, &["model1"]);
}
#[test]
fn test_variants() {
let blockstates = BlockStates::Variants {
variants: hashmap! {
String::from("var1=foo,var2=true") => make_single_variant("model1"),
String::from("var1=foo,var2=false") => make_single_variant("model2"),
},
};
let state_values = hashmap! {
String::from("var1") => StateValue::from("foo"),
String::from("var2") => StateValue::from("false"),
};
do_test(blockstates, &state_values, &["model2"]);
}
#[test]
fn test_multipart() {
let blockstates = BlockStates::Multipart {
cases: vec![
Case {
when: None,
apply: make_single_variant("model1"),
},
Case {
when: Some(WhenClause::Single(Condition {
and: hashmap! {
String::from("var1") => StateValue::from("foo|bar"),
String::from("var2") => StateValue::from(true),
},
})),
apply: make_single_variant("model2"),
},
],
};
let state_values = hashmap! {
String::from("var1") => StateValue::from("bar"),
String::from("var2") => StateValue::from("true"),
};
do_test(blockstates, &state_values, &["model1", "model2"]);
}
}