Skip to main content

yog_data/model/
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    /// Bytes as they are — a picture, a sound — written `@bytes:<base64>` as text.
36    Bytes(Vec<u8>),
37}
38
39/// A reference to content: its kind (`item`, `block`, `entity`, `recipe`,
40/// `entry`, `category`, or a mod's own) and id (`minecraft:stick`,
41/// `minecraft:furnace[facing=north]`).
42#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
43pub struct Ref {
44    pub kind: String,
45    pub id: String,
46}
47
48impl Ref {
49    pub fn new(kind: impl Into<String>, id: impl Into<String>) -> Self {
50        Self {
51            kind: kind.into(),
52            id: id.into(),
53        }
54    }
55
56    /// The namespace of the id (`minecraft` for an id without one).
57    pub fn namespace(&self) -> &str {
58        yog_ids::split(&self.id).0
59    }
60}
61
62/// A mod resource: `@<set>.<key>` — `@Strings.screen.title` is set
63/// `Strings`, key `screen.title`.
64#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
65pub struct Res {
66    pub set: String,
67    pub key: String,
68}
69
70impl Res {
71    /// `Set.key`: what the resources list it by.
72    pub fn name(&self) -> String {
73        format!("{}.{}", self.set, self.key)
74    }
75}
76
77impl std::fmt::Display for Res {
78    /// `@Set.key`, as files write it.
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        write!(f, "{}{}.{}", yog_ids::sep::TYPED, self.set, self.key)
81    }
82}
83
84pub use super::map::Map;
85
86impl Value {
87    /// The type's name, for messages: `"a string"`, `"a list"`, ...
88    pub fn kind_name(&self) -> &'static str {
89        match self {
90            Value::Null => "null",
91            Value::Bool(_) => "true/false",
92            Value::Int(_) => "an integer",
93            Value::Float(_) => "a number",
94            Value::Str(_) => "a string",
95            Value::List(_) => "a list",
96            Value::Map(_) => "an object",
97            Value::Ref(_) => "a reference",
98            Value::Tag(_) => "a tag",
99            Value::Res(_) => "a resource",
100            Value::Lang(_) => "a translation key",
101            Value::Text(_) => "a text",
102            Value::Image(_) => "an image",
103            Value::Color(_) => "a color",
104            Value::Bytes(_) => "bytes",
105        }
106    }
107
108    pub fn is_null(&self) -> bool {
109        matches!(self, Value::Null)
110    }
111
112    pub fn as_bool(&self) -> Option<bool> {
113        match self {
114            Value::Bool(b) => Some(*b),
115            _ => None,
116        }
117    }
118
119    pub fn as_i64(&self) -> Option<i64> {
120        match self {
121            Value::Int(n) => Some(*n),
122            Value::Float(f) if f.fract() == 0.0 && f.abs() < 9.0e15 => Some(*f as i64),
123            _ => None,
124        }
125    }
126
127    pub fn as_f64(&self) -> Option<f64> {
128        match self {
129            Value::Int(n) => Some(*n as f64),
130            Value::Float(f) => Some(*f),
131            _ => None,
132        }
133    }
134
135    /// The string of a `Str` (and of the string-like typed values: `Text`, `Image`).
136    pub fn as_str(&self) -> Option<&str> {
137        match self {
138            Value::Str(s) | Value::Text(s) | Value::Image(s) => Some(s),
139            _ => None,
140        }
141    }
142
143    pub fn as_list(&self) -> Option<&[Value]> {
144        match self {
145            Value::List(l) => Some(l),
146            _ => None,
147        }
148    }
149
150    pub fn as_map(&self) -> Option<&Map> {
151        match self {
152            Value::Map(m) => Some(m),
153            _ => None,
154        }
155    }
156
157    /// `self[key]` of a map, `None` for anything else.
158    pub fn get(&self, key: &str) -> Option<&Value> {
159        self.as_map()?.get(key)
160    }
161
162    /// The value at `path`.
163    pub fn at(&self, path: &Path) -> Option<&Value> {
164        let mut v = self;
165        for seg in path.segments() {
166            v = match (seg, v) {
167                (Seg::Key(k), Value::Map(m)) => m.get(k)?,
168                (Seg::Index(i), Value::List(l)) => l.get(*i)?,
169                _ => return None,
170            };
171        }
172        Some(v)
173    }
174
175    /// The value `segs` lead to, to change.
176    pub fn at_mut(&mut self, segs: &[Seg]) -> Option<&mut Value> {
177        let mut v = self;
178        for seg in segs {
179            v = match (seg, v) {
180                (Seg::Key(k), Value::Map(m)) => m.get_mut(k)?,
181                (Seg::Index(i), Value::List(l)) => l.get_mut(*i)?,
182                _ => return None,
183            };
184        }
185        Some(v)
186    }
187}
188
189impl From<bool> for Value {
190    fn from(b: bool) -> Self {
191        Value::Bool(b)
192    }
193}
194impl From<i64> for Value {
195    fn from(n: i64) -> Self {
196        Value::Int(n)
197    }
198}
199impl From<f64> for Value {
200    fn from(f: f64) -> Self {
201        Value::Float(f)
202    }
203}
204impl From<&str> for Value {
205    fn from(s: &str) -> Self {
206        Value::Str(s.to_string())
207    }
208}
209impl From<String> for Value {
210    fn from(s: String) -> Self {
211        Value::Str(s)
212    }
213}
214impl From<Vec<Value>> for Value {
215    fn from(l: Vec<Value>) -> Self {
216        Value::List(l)
217    }
218}
219impl From<Map> for Value {
220    fn from(m: Map) -> Self {
221        Value::Map(m)
222    }
223}
224
225/// One step of a [`Path`].
226#[derive(Debug, Clone, PartialEq, Eq, Hash, crate::Schema)]
227#[data(tag = "type")]
228pub enum Seg {
229    Key(String),
230    Index(usize),
231}
232
233/// Where a value is in a document: `entries[2].pages[0].item`.
234#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
235pub struct Path(Vec<Seg>);
236
237impl Path {
238    pub fn root() -> Self {
239        Self(Vec::new())
240    }
241
242    pub fn segments(&self) -> &[Seg] {
243        &self.0
244    }
245
246    pub fn is_root(&self) -> bool {
247        self.0.is_empty()
248    }
249
250    pub fn push(&mut self, seg: Seg) {
251        self.0.push(seg);
252    }
253
254    pub fn pop(&mut self) {
255        self.0.pop();
256    }
257
258    pub fn key(&self, key: &str) -> Path {
259        let mut p = self.clone();
260        p.0.push(Seg::Key(key.to_string()));
261        p
262    }
263
264    /// `rest` under this path.
265    pub fn then(&self, rest: &Path) -> Path {
266        let mut p = self.clone();
267        p.0.extend(rest.0.iter().cloned());
268        p
269    }
270
271    pub fn index(&self, i: usize) -> Path {
272        let mut p = self.clone();
273        p.0.push(Seg::Index(i));
274        p
275    }
276}
277
278/// A path as its steps.
279impl crate::Schema for Path {
280    fn shape() -> crate::Shape {
281        Vec::<Seg>::shape()
282    }
283    fn read(v: &crate::ValueRef<'_>, cx: &mut crate::Reader) -> Option<Self> {
284        Vec::<Seg>::read(v, cx).map(Path)
285    }
286    fn write(&self) -> Value {
287        self.0.write()
288    }
289}
290
291impl fmt::Display for Path {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        if self.0.is_empty() {
294            return f.write_str("(root)");
295        }
296        for (i, seg) in self.0.iter().enumerate() {
297            match seg {
298                Seg::Key(k) if is_plain_key(k) => {
299                    if i > 0 {
300                        f.write_str(".")?;
301                    }
302                    f.write_str(k)?;
303                }
304                Seg::Key(k) => write!(f, "[{k:?}]")?,
305                Seg::Index(n) => write!(f, "[{n}]")?,
306            }
307        }
308        Ok(())
309    }
310}
311
312fn is_plain_key(k: &str) -> bool {
313    let mut chars = k.chars();
314    chars
315        .next()
316        .is_some_and(|c| c.is_ascii_alphabetic() || c == '_' || c == '$')
317        && chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
318}