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