Skip to main content

yog_data/
value.rs

1//! The model: a tree of typed values, like NBT.
2
3use std::fmt;
4
5/// A Yog Data value.
6///
7/// The text form has no syntax of its own for the typed values (`Ref`,
8/// `Tag`, `Res`, `Text`, `Image`, `Color`): they're written as strings
9/// (`"@item:minecraft:stick"`), so every file stays JSON, and the schema of
10/// a field says which type its string is. The binary form keeps the type.
11#[derive(Debug, Clone, PartialEq, Default)]
12pub enum Value {
13    #[default]
14    Null,
15    Bool(bool),
16    Int(i64),
17    Float(f64),
18    Str(String),
19    List(Vec<Value>),
20    Map(Map),
21    /// `@item:minecraft:stick` — content of some kind, by id.
22    Ref(Ref),
23    /// `@tag:item:minecraft:planks` — a tag of content of some kind.
24    Tag(Ref),
25    /// `@Strings.title` — a mod resource (`.yres` entry).
26    Res(Res),
27    /// Yog text: `{b|bold} and {#8d6acc|color}`.
28    Text(String),
29    /// An image source: `sprite:button`, `mymod:gui/logo`, `item:minecraft:stick`.
30    Image(String),
31    /// `0xAARRGGBB`.
32    Color(u32),
33}
34
35/// A reference to content: its kind (`item`, `block`, `entity`, `recipe`,
36/// `entry`, `category`, or a mod's own) and id (`minecraft:stick`,
37/// `minecraft:furnace[facing=north]`).
38#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
39pub struct Ref {
40    pub kind: String,
41    pub id: String,
42}
43
44impl Ref {
45    pub fn new(kind: impl Into<String>, id: impl Into<String>) -> Self {
46        Self {
47            kind: kind.into(),
48            id: id.into(),
49        }
50    }
51
52    /// The namespace of the id (`minecraft` for an id without one).
53    pub fn namespace(&self) -> &str {
54        self.id.split_once(':').map_or("minecraft", |(ns, _)| ns)
55    }
56}
57
58/// A mod resource: `@<set>.<key>` — `@Strings.screen.title` is set
59/// `Strings`, key `screen.title`.
60#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
61pub struct Res {
62    pub set: String,
63    pub key: String,
64}
65
66/// A map: entries in file order, keys unique.
67#[derive(Debug, Clone, PartialEq, Default)]
68pub struct Map(Vec<(String, Value)>);
69
70impl Map {
71    pub fn new() -> Self {
72        Self(Vec::new())
73    }
74
75    pub fn with_capacity(n: usize) -> Self {
76        Self(Vec::with_capacity(n))
77    }
78
79    pub fn get(&self, key: &str) -> Option<&Value> {
80        self.0.iter().find(|(k, _)| k == key).map(|(_, v)| v)
81    }
82
83    pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
84        self.0.iter_mut().find(|(k, _)| k == key).map(|(_, v)| v)
85    }
86
87    /// The value of the `i`-th entry.
88    pub fn value_at(&self, i: usize) -> Option<&Value> {
89        self.0.get(i).map(|(_, v)| v)
90    }
91
92    /// The position of `key` among the entries.
93    pub fn index_of(&self, key: &str) -> Option<usize> {
94        self.0.iter().position(|(k, _)| k == key)
95    }
96
97    /// Sets `key`, in place if it's there, else at the end; returns the old value.
98    pub fn insert(&mut self, key: impl Into<String>, value: Value) -> Option<Value> {
99        let key = key.into();
100        match self.get_mut(&key) {
101            Some(slot) => Some(std::mem::replace(slot, value)),
102            None => {
103                self.0.push((key, value));
104                None
105            }
106        }
107    }
108
109    /// Appends without checking for the key — for decoders that already did.
110    pub(crate) fn push_unchecked(&mut self, key: String, value: Value) {
111        self.0.push((key, value));
112    }
113
114    pub fn remove(&mut self, key: &str) -> Option<Value> {
115        let i = self.index_of(key)?;
116        Some(self.0.remove(i).1)
117    }
118
119    pub fn len(&self) -> usize {
120        self.0.len()
121    }
122
123    pub fn is_empty(&self) -> bool {
124        self.0.is_empty()
125    }
126
127    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, &Value)> {
128        self.0.iter().map(|(k, v)| (k.as_str(), v))
129    }
130
131    pub fn keys(&self) -> impl ExactSizeIterator<Item = &str> {
132        self.0.iter().map(|(k, _)| k.as_str())
133    }
134
135    pub fn retain(&mut self, mut keep: impl FnMut(&str, &Value) -> bool) {
136        self.0.retain(|(k, v)| keep(k, v));
137    }
138}
139
140impl IntoIterator for Map {
141    type Item = (String, Value);
142    type IntoIter = std::vec::IntoIter<(String, Value)>;
143    fn into_iter(self) -> Self::IntoIter {
144        self.0.into_iter()
145    }
146}
147
148impl<K: Into<String>> FromIterator<(K, Value)> for Map {
149    fn from_iter<I: IntoIterator<Item = (K, Value)>>(iter: I) -> Self {
150        let mut map = Map::new();
151        for (k, v) in iter {
152            map.insert(k, v);
153        }
154        map
155    }
156}
157
158impl Value {
159    /// The type's name, for messages: `"a string"`, `"a list"`, ...
160    pub fn kind_name(&self) -> &'static str {
161        match self {
162            Value::Null => "null",
163            Value::Bool(_) => "true/false",
164            Value::Int(_) => "an integer",
165            Value::Float(_) => "a number",
166            Value::Str(_) => "a string",
167            Value::List(_) => "a list",
168            Value::Map(_) => "an object",
169            Value::Ref(_) => "a reference",
170            Value::Tag(_) => "a tag",
171            Value::Res(_) => "a resource",
172            Value::Text(_) => "a text",
173            Value::Image(_) => "an image",
174            Value::Color(_) => "a color",
175        }
176    }
177
178    pub fn is_null(&self) -> bool {
179        matches!(self, Value::Null)
180    }
181
182    pub fn as_bool(&self) -> Option<bool> {
183        match self {
184            Value::Bool(b) => Some(*b),
185            _ => None,
186        }
187    }
188
189    pub fn as_i64(&self) -> Option<i64> {
190        match self {
191            Value::Int(n) => Some(*n),
192            Value::Float(f) if f.fract() == 0.0 && f.abs() < 9.0e15 => Some(*f as i64),
193            _ => None,
194        }
195    }
196
197    pub fn as_f64(&self) -> Option<f64> {
198        match self {
199            Value::Int(n) => Some(*n as f64),
200            Value::Float(f) => Some(*f),
201            _ => None,
202        }
203    }
204
205    /// The string of a `Str` (and of the string-like typed values: `Text`, `Image`).
206    pub fn as_str(&self) -> Option<&str> {
207        match self {
208            Value::Str(s) | Value::Text(s) | Value::Image(s) => Some(s),
209            _ => None,
210        }
211    }
212
213    pub fn as_list(&self) -> Option<&[Value]> {
214        match self {
215            Value::List(l) => Some(l),
216            _ => None,
217        }
218    }
219
220    pub fn as_map(&self) -> Option<&Map> {
221        match self {
222            Value::Map(m) => Some(m),
223            _ => None,
224        }
225    }
226
227    /// `self[key]` of a map, `None` for anything else.
228    pub fn get(&self, key: &str) -> Option<&Value> {
229        self.as_map()?.get(key)
230    }
231
232    /// The value at `path`.
233    pub fn at(&self, path: &Path) -> Option<&Value> {
234        let mut v = self;
235        for seg in path.segments() {
236            v = match (seg, v) {
237                (Seg::Key(k), Value::Map(m)) => m.get(k)?,
238                (Seg::Index(i), Value::List(l)) => l.get(*i)?,
239                _ => return None,
240            };
241        }
242        Some(v)
243    }
244}
245
246impl From<bool> for Value {
247    fn from(b: bool) -> Self {
248        Value::Bool(b)
249    }
250}
251impl From<i64> for Value {
252    fn from(n: i64) -> Self {
253        Value::Int(n)
254    }
255}
256impl From<f64> for Value {
257    fn from(f: f64) -> Self {
258        Value::Float(f)
259    }
260}
261impl From<&str> for Value {
262    fn from(s: &str) -> Self {
263        Value::Str(s.to_string())
264    }
265}
266impl From<String> for Value {
267    fn from(s: String) -> Self {
268        Value::Str(s)
269    }
270}
271impl From<Vec<Value>> for Value {
272    fn from(l: Vec<Value>) -> Self {
273        Value::List(l)
274    }
275}
276impl From<Map> for Value {
277    fn from(m: Map) -> Self {
278        Value::Map(m)
279    }
280}
281
282/// One step of a [`Path`].
283#[derive(Debug, Clone, PartialEq, Eq, Hash)]
284pub enum Seg {
285    Key(String),
286    Index(usize),
287}
288
289/// Where a value is in a document: `entries[2].pages[0].item`.
290#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
291pub struct Path(Vec<Seg>);
292
293impl Path {
294    pub fn root() -> Self {
295        Self(Vec::new())
296    }
297
298    pub fn segments(&self) -> &[Seg] {
299        &self.0
300    }
301
302    pub fn is_root(&self) -> bool {
303        self.0.is_empty()
304    }
305
306    pub fn push(&mut self, seg: Seg) {
307        self.0.push(seg);
308    }
309
310    pub fn pop(&mut self) {
311        self.0.pop();
312    }
313
314    pub fn key(&self, key: &str) -> Path {
315        let mut p = self.clone();
316        p.0.push(Seg::Key(key.to_string()));
317        p
318    }
319
320    pub fn index(&self, i: usize) -> Path {
321        let mut p = self.clone();
322        p.0.push(Seg::Index(i));
323        p
324    }
325}
326
327impl fmt::Display for Path {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        if self.0.is_empty() {
330            return f.write_str("(root)");
331        }
332        for (i, seg) in self.0.iter().enumerate() {
333            match seg {
334                Seg::Key(k) if is_plain_key(k) => {
335                    if i > 0 {
336                        f.write_str(".")?;
337                    }
338                    f.write_str(k)?;
339                }
340                Seg::Key(k) => write!(f, "[{k:?}]")?,
341                Seg::Index(n) => write!(f, "[{n}]")?,
342            }
343        }
344        Ok(())
345    }
346}
347
348fn is_plain_key(k: &str) -> bool {
349    let mut chars = k.chars();
350    chars
351        .next()
352        .is_some_and(|c| c.is_ascii_alphabetic() || c == '_' || c == '$')
353        && chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
354}