1use indexmap::IndexMap;
2use source_kv::{Value, Deserializer};
3use crate::interner::{VmtKey, intern_key};
4
5#[derive(Debug, Clone, PartialEq)]
6pub struct Vmt {
7 pub shader: String,
8 pub properties: IndexMap<VmtKey, Vec<Value>>,
9}
10
11impl Vmt {
12 pub fn new(shader: &str) -> Self {
14 Self {
15 shader: shader.to_lowercase(),
16 properties: IndexMap::new(),
17 }
18 }
19
20 pub fn from_str(input: &str) -> Result<Self, source_kv::Error> {
22 let mut de = Deserializer::from_str(input);
23 let root = de.parse_root()?;
24
25 if let Value::Obj(mut root_map) = root {
26 if let Some((shader, mut values)) = root_map.pop() {
27 if let Some(Value::Obj(props)) = values.pop() {
28 let mut properties = IndexMap::with_capacity(props.len());
29 for (k, v) in props {
30 properties.insert(intern_key(&k), v);
31 }
32 return Ok(Self {
33 shader: shader.to_lowercase(),
34 properties
35 });
36 }
37 }
38 }
39 Err(source_kv::Error::Message("Invalid VMT: Missing shader root or body".into()))
40 }
41
42 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, source_kv::Error> {
44 let content = std::fs::read_to_string(path)?;
45 Self::from_str(&content)
46 }
47
48 pub fn set_string(&mut self, key: &str, value: &str) -> &mut Self {
50 self.properties.insert(
51 intern_key(key),
52 vec![Value::Str(value.to_string())]
53 );
54 self
55 }
56
57 pub fn set_flag(&mut self, key: &str, enabled: bool) -> &mut Self {
59 self.set_string(key, if enabled { "1" } else { "0" })
60 }
61
62 pub fn remove(&mut self, key: &str) -> &mut Self {
64 let base = key.to_lowercase();
65 self.properties.shift_remove(base.as_str());
66 self.properties.shift_remove(format!("${}", base).as_str());
67 self.properties.shift_remove(format!("%{}", base).as_str());
68 self
69 }
70
71 pub fn get_raw(&self, key: &str) -> Option<&Value> {
73 let base = key.to_lowercase();
74 self.properties.get(base.as_str())
75 .or_else(|| self.properties.get(format!("${}", base).as_str()))
76 .or_else(|| self.properties.get(format!("%{}", base).as_str()))
77 .or_else(|| {
78 if base.starts_with('$') || base.starts_with('%') {
79 let raw = &base[1..];
80 self.properties.get(raw)
81 .or_else(|| self.properties.get(format!("${}", raw).as_str()))
82 .or_else(|| self.properties.get(format!("%{}", raw).as_str()))
83 } else {
84 None
85 }
86 })
87 .and_then(|v| v.first())
88 }
89
90 pub fn get_string(&self, key: &str) -> Option<String> {
92 self.get_raw(key).and_then(|v| v.as_str().map(String::from))
93 }
94
95 pub fn get_f32(&self, key: &str) -> Option<f32> {
97 self.get_string(key)?.parse::<f32>().ok()
98 }
99
100 pub fn get_i32(&self, key: &str) -> Option<i32> {
102 self.get_string(key)?.parse::<i32>().ok()
103 }
104
105 pub fn get_bool(&self, key: &str) -> bool {
107 match self.get_string(key).as_deref() {
108 Some("1") | Some("true") => true,
109 _ => false,
110 }
111 }
112
113 pub fn get_color(&self, key: &str) -> Option<[f32; 3]> {
116 let val = self.get_string(key)?;
117 let val = val.trim();
118
119 if val.starts_with('[') && val.ends_with(']') {
120 let parts: Vec<f32> = val[1..val.len()-1]
121 .split_whitespace()
122 .filter_map(|s| s.parse().ok())
123 .collect();
124 if parts.len() >= 3 { return Some([parts[0], parts[1], parts[2]]); }
125 } else if val.starts_with('{') && val.ends_with('}') {
126 let parts: Vec<f32> = val[1..val.len()-1]
127 .split_whitespace()
128 .filter_map(|s| s.parse::<u8>().ok().map(|v| v as f32 / 255.0))
129 .collect();
130 if parts.len() >= 3 { return Some([parts[0], parts[1], parts[2]]); }
131 }
132 None
133 }
134
135 pub fn add_proxy<'a, I>(&mut self, name: &str, params: I) -> &mut Self
137 where
138 I: IntoIterator<Item = (&'a str, &'a str)>,
139 {
140 let name_lower = name.to_lowercase();
141 let mut proxy_params = IndexMap::new();
142 for (k, v) in params {
143 proxy_params.insert(k.to_string(), vec![Value::Str(v.to_string())]);
144 }
145
146 let proxy_obj = Value::Obj(proxy_params);
147
148 let proxies_vec = self.properties.entry(intern_key("proxies"))
149 .or_insert_with(|| vec![Value::Obj(IndexMap::new())]);
150
151 if let Some(Value::Obj(map)) = proxies_vec.first_mut() {
152 map.entry(name_lower)
153 .or_insert_with(Vec::new)
154 .push(proxy_obj);
155 }
156
157 self
158 }
159
160 pub fn to_string(&self) -> Result<String, source_kv::Error> {
162 let mut root_map = IndexMap::new();
163 let mut props = IndexMap::new();
164
165 for (k, v) in &self.properties {
166 props.insert(k.to_string(), v.clone());
167 }
168
169 root_map.insert(self.shader.clone(), vec![Value::Obj(props)]);
170
171 source_kv::to_string(&Value::Obj(root_map))
172 }
173
174 pub fn to_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), source_kv::Error> {
176 let content = self.to_string()?;
177 std::fs::write(path, content)?;
178 Ok(())
179 }
180}