Skip to main content

pfx_materials/
library.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::material::{Material, Recipe};
6
7pub const FIXTURE: &str = include_str!("../fixtures/library.toml");
8
9#[derive(Clone, Debug, Default, PartialEq)]
10pub struct Library {
11    materials: BTreeMap<String, Material>,
12}
13
14#[derive(Default, Serialize, Deserialize)]
15#[serde(default, deny_unknown_fields)]
16struct File {
17    materials: BTreeMap<String, Recipe>,
18}
19
20impl Library {
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    pub fn fixture() -> Self {
26        Self::from_toml(FIXTURE).expect("the fixture library parses")
27    }
28
29    pub fn from_toml(text: &str) -> Result<Self, String> {
30        let file: File = toml::from_str(text).map_err(|err| err.to_string())?;
31        let mut materials = BTreeMap::new();
32        for (name, recipe) in file.materials {
33            if !valid_name(&name) {
34                return Err(format!(
35                    "material name {name:?} needs printable characters without spaces at its ends"
36                ));
37            }
38            let material = recipe
39                .into_material()
40                .map_err(|err| format!("materials.{name}: {err}"))?;
41            materials.insert(name, material);
42        }
43        Ok(Self { materials })
44    }
45
46    pub fn to_toml(&self) -> Result<String, String> {
47        let file = File {
48            materials: self
49                .materials
50                .iter()
51                .map(|(name, material)| (name.clone(), Recipe::from(*material)))
52                .collect(),
53        };
54        toml::to_string(&file).map_err(|err| err.to_string())
55    }
56
57    pub fn get(&self, name: &str) -> Option<&Material> {
58        self.materials.get(name)
59    }
60
61    pub fn contains(&self, name: &str) -> bool {
62        self.materials.contains_key(name)
63    }
64
65    pub fn insert(&mut self, name: &str, material: Material) -> Result<Option<Material>, String> {
66        if !valid_name(name) {
67            return Err(format!(
68                "material name {name:?} needs printable characters without spaces at its ends"
69            ));
70        }
71        Ok(self.materials.insert(name.to_owned(), material))
72    }
73
74    pub fn names(&self) -> impl Iterator<Item = &str> {
75        self.materials.keys().map(String::as_str)
76    }
77
78    pub fn iter(&self) -> impl Iterator<Item = (&str, &Material)> {
79        self.materials
80            .iter()
81            .map(|(name, material)| (name.as_str(), material))
82    }
83
84    pub fn len(&self) -> usize {
85        self.materials.len()
86    }
87
88    pub fn is_empty(&self) -> bool {
89        self.materials.is_empty()
90    }
91}
92
93fn valid_name(name: &str) -> bool {
94    !name.is_empty() && name.trim() == name && !name.chars().any(char::is_control)
95}