rush_ecs_parser/ports/
toml.rs1use crate::{adapter::Parser, error::utils::ensure_syntax};
4use anyhow::Result;
5use rush_ecs_core::blueprint::{
6 Blueprint, BlueprintString, Component, ComponentType, ComponentValue, Entity,
7};
8use std::collections::BTreeMap;
9use toml::{Table, Value};
10
11#[derive(Clone, Debug, Default)]
45pub struct TomlParser {}
46
47impl Parser for TomlParser {
48 fn parse_string(&self, blueprint_string: BlueprintString) -> Result<Blueprint> {
49 let table: Table = blueprint_string.parse::<Table>().expect("invalid TOML");
51
52 ensure_syntax(
57 "World table must exist".to_string(),
58 table.contains_key("world"),
59 );
60 ensure_syntax(
61 "World table must be a table".to_string(),
62 table["world"].is_table(),
63 );
64
65 let world_table = table["world"].as_table().unwrap();
66
67 ensure_syntax(
68 "World must have a name".to_string(),
69 world_table.contains_key("name"),
70 );
71
72 ensure_syntax(
73 "World name must be a string".to_string(),
74 world_table["name"].is_str(),
75 );
76
77 ensure_syntax(
78 "World must have a description".to_string(),
79 world_table.contains_key("description"),
80 );
81
82 ensure_syntax(
83 "World description must be a string".to_string(),
84 world_table["description"].is_str(),
85 );
86
87 ensure_syntax(
88 "World must have a regions property".to_string(),
89 world_table.contains_key("regions"),
90 );
91 ensure_syntax(
92 "World regions property must be an array".to_string(),
93 world_table["regions"].is_array(),
94 );
95 ensure_syntax(
96 "World must have at least 1 region".to_string(),
97 !world_table["regions"].as_array().unwrap().is_empty(),
98 );
99 ensure_syntax(
100 "World regions property must be an array of strings".to_string(),
101 world_table["regions"].as_array().unwrap()[0].is_str(),
102 );
103
104 let regions = world_table["regions"]
106 .as_array()
107 .unwrap()
108 .iter()
109 .map(|r| r.as_str().unwrap().to_string()) .collect::<Vec<_>>();
111
112 for region in regions.iter() {
117 ensure_syntax(
118 format!("Region {region} table must exist"),
119 table.contains_key(region),
121 );
122 }
123
124 ensure_syntax(
127 "Enttiy table must exist".to_string(),
128 table.contains_key("entity"),
129 );
130 ensure_syntax(
131 "Entity table must be a table".to_string(),
132 table["entity"].is_table(),
133 );
134
135 let entity_table = table["entity"].as_table().unwrap();
136 let entities = entity_table.keys().cloned().collect::<Vec<_>>();
137
138 ensure_syntax(
139 "Entity table must have at least 1 entity properties".to_string(),
140 !entities.is_empty() &&
142 entity_table[&entities[0]].is_table(),
144 );
145
146 let world_name = world_table["name"].as_str().unwrap().to_string();
148 let world_description = world_table["description"].as_str().unwrap().to_string();
149
150 let mut blueprint = Blueprint::new(world_name, world_description);
152
153 blueprint.preload(regions.clone(), entities.clone());
156
157 for region_name in regions.iter() {
159 if let Some(region_table) = table[region_name].as_table() {
161 let entities = region_table.keys().cloned().collect::<Vec<Entity>>();
163 blueprint.add_region(region_name.clone(), entities);
164 }
165 }
166
167 for entity_name in entities.into_iter() {
169 if let Some(component_table) = entity_table[&entity_name].as_table() {
170 let mut component_type_tree: BTreeMap<Component, ComponentType> = BTreeMap::new();
172 for component_name in component_table.keys() {
173 let value = component_table
175 .get(component_name)
176 .unwrap()
177 .as_str()
178 .unwrap()
179 .to_string();
180 component_type_tree.insert(component_name.to_string(), value);
181 }
182 blueprint.add_entity(entity_name, component_type_tree);
183 }
184 }
185
186 let blueprint_regions = blueprint.regions.clone();
194
195 for region_name in regions.into_iter() {
196 if let Some(entities_in_region) = blueprint_regions.get(®ion_name) {
198 for entity_name in entities_in_region.iter() {
200 if let Some(instances) = table[®ion_name][entity_name].as_array() {
201 for instance in instances.iter() {
202 if let Some(entity_components) = instance.as_table() {
204 let mut component_tree: BTreeMap<Component, ComponentValue> =
205 BTreeMap::new();
206
207 for (toml_component, toml_value) in entity_components.into_iter() {
209 let component = toml_component.to_string();
210 let value = match toml_value {
211 Value::String(v) => ComponentValue::String(v.to_string()),
212 Value::Float(v) => ComponentValue::Float(*v),
213 Value::Integer(v) => ComponentValue::Integer(*v),
214 Value::Boolean(v) => ComponentValue::Boolean(*v),
215 _ => panic!("Unsupported data type"),
216 };
217
218 component_tree.insert(component, value);
219 }
220
221 blueprint.add_instance(
222 region_name.clone(),
223 entity_name.to_string(),
224 component_tree,
225 )?;
226 }
227 }
228 }
229 }
230 }
231 }
232
233 Ok(blueprint)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::utils::file_to_string;
241 use std::path::Path;
242
243 #[test]
244 fn test_toml_parser_file() {
245 let path = Path::new("mock/fixtures/ports/blueprint.toml");
246 let blueprint_string = file_to_string(path);
247
248 let toml_parser = TomlParser::default();
249 let blueprint = toml_parser.parse_string(blueprint_string).unwrap();
250 println!("{:?}", blueprint);
251 assert!(true)
253 }
254}