use crate::{adapter::Parser, error::utils::ensure_syntax};
use anyhow::Result;
use rush_ecs_core::blueprint::{
Blueprint, BlueprintString, Component, ComponentType, ComponentValue, Entity,
};
use std::collections::BTreeMap;
use toml::{Table, Value};
#[derive(Clone, Debug, Default)]
pub struct TomlParser {}
impl Parser for TomlParser {
fn parse_string(&self, blueprint_string: BlueprintString) -> Result<Blueprint> {
let table: Table = blueprint_string.parse::<Table>().expect("invalid TOML");
ensure_syntax(
"World table must exist".to_string(),
table.contains_key("world"),
);
ensure_syntax(
"World table must be a table".to_string(),
table["world"].is_table(),
);
let world_table = table["world"].as_table().unwrap();
ensure_syntax(
"World must have a name".to_string(),
world_table.contains_key("name"),
);
ensure_syntax(
"World name must be a string".to_string(),
world_table["name"].is_str(),
);
ensure_syntax(
"World must have a description".to_string(),
world_table.contains_key("description"),
);
ensure_syntax(
"World description must be a string".to_string(),
world_table["description"].is_str(),
);
ensure_syntax(
"World must have a regions property".to_string(),
world_table.contains_key("regions"),
);
ensure_syntax(
"World regions property must be an array".to_string(),
world_table["regions"].is_array(),
);
ensure_syntax(
"World must have at least 1 region".to_string(),
!world_table["regions"].as_array().unwrap().is_empty(),
);
ensure_syntax(
"World regions property must be an array of strings".to_string(),
world_table["regions"].as_array().unwrap()[0].is_str(),
);
let regions = world_table["regions"]
.as_array()
.unwrap()
.iter()
.map(|r| r.as_str().unwrap().to_string()) .collect::<Vec<_>>();
for region in regions.iter() {
ensure_syntax(
format!("Region {region} table must exist"),
table.contains_key(region),
);
}
ensure_syntax(
"Enttiy table must exist".to_string(),
table.contains_key("entity"),
);
ensure_syntax(
"Entity table must be a table".to_string(),
table["entity"].is_table(),
);
let entity_table = table["entity"].as_table().unwrap();
let entities = entity_table.keys().cloned().collect::<Vec<_>>();
ensure_syntax(
"Entity table must have at least 1 entity properties".to_string(),
!entities.is_empty() &&
entity_table[&entities[0]].is_table(),
);
let world_name = world_table["name"].as_str().unwrap().to_string();
let world_description = world_table["description"].as_str().unwrap().to_string();
let mut blueprint = Blueprint::new(world_name, world_description);
blueprint.preload(regions.clone(), entities.clone());
for region_name in regions.iter() {
if let Some(region_table) = table[region_name].as_table() {
let entities = region_table.keys().cloned().collect::<Vec<Entity>>();
blueprint.add_region(region_name.clone(), entities);
}
}
for entity_name in entities.into_iter() {
if let Some(component_table) = entity_table[&entity_name].as_table() {
let mut component_type_tree: BTreeMap<Component, ComponentType> = BTreeMap::new();
for component_name in component_table.keys() {
let value = component_table
.get(component_name)
.unwrap()
.as_str()
.unwrap()
.to_string();
component_type_tree.insert(component_name.to_string(), value);
}
blueprint.add_entity(entity_name, component_type_tree);
}
}
let blueprint_regions = blueprint.regions.clone();
for region_name in regions.into_iter() {
if let Some(entities_in_region) = blueprint_regions.get(®ion_name) {
for entity_name in entities_in_region.iter() {
if let Some(instances) = table[®ion_name][entity_name].as_array() {
for instance in instances.iter() {
if let Some(entity_components) = instance.as_table() {
let mut component_tree: BTreeMap<Component, ComponentValue> =
BTreeMap::new();
for (toml_component, toml_value) in entity_components.into_iter() {
let component = toml_component.to_string();
let value = match toml_value {
Value::String(v) => ComponentValue::String(v.to_string()),
Value::Float(v) => ComponentValue::Float(*v),
Value::Integer(v) => ComponentValue::Integer(*v),
Value::Boolean(v) => ComponentValue::Boolean(*v),
_ => panic!("Unsupported data type"),
};
component_tree.insert(component, value);
}
blueprint.add_instance(
region_name.clone(),
entity_name.to_string(),
component_tree,
)?;
}
}
}
}
}
}
Ok(blueprint)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::file_to_string;
use std::path::Path;
#[test]
fn test_toml_parser_file() {
let path = Path::new("mock/fixtures/ports/blueprint.toml");
let blueprint_string = file_to_string(path);
let toml_parser = TomlParser::default();
let blueprint = toml_parser.parse_string(blueprint_string).unwrap();
println!("{:?}", blueprint);
assert!(true)
}
}