Skip to main content

yog_data/shown/
mod.rs

1//! [`Text`]: what a player reads, and [`Lang`]: a translation key.
2
3use substrate_data::binary::{Decoder, Writer};
4use substrate_data::frame::{Packer, Unpacker};
5use substrate_data::schema::{Expansion, NamedType};
6use substrate_data::typed::{Spelled, classify};
7use substrate_data::{Id, Kind, Reader, Ref, Schema, Shape, Value, ValueRef};
8
9use crate::{Res, kinds};
10
11/// A translation key of the game's or a mod's lang files: `@lang:gui.done`.
12pub type Lang = Id<kinds::Lang>;
13
14/// A text a player reads: Yog text as written (`{b|bold}`), a `.yres`
15/// text (`@res:Strings.title`), the name of some content
16/// (`@item:minecraft:stick` shows "Stick"), or a translation key
17/// (`@lang:gui.done`).
18#[derive(Debug, Clone, PartialEq)]
19pub enum Text {
20    Literal(String),
21    Res(Res),
22    NameOf(Ref),
23    Lang(String),
24}
25
26impl Default for Text {
27    fn default() -> Self {
28        Text::Literal(String::new())
29    }
30}
31
32impl Text {
33    /// The reference it is, if it's one (`@res:…`, `@lang:…`, a name's).
34    pub fn to_ref(&self) -> Option<Ref> {
35        match self {
36            Text::Literal(_) => None,
37            Text::Res(r) => Some(Id::<kinds::Res>::new(r.name()).to_ref()),
38            Text::NameOf(r) => Some(r.clone()),
39            Text::Lang(k) => Some(Lang::new(k.clone()).to_ref()),
40        }
41    }
42
43    /// What the tool reading it makes of it: its words as written, else
44    /// what the resolver expands its reference to (a `.yres` text's words
45    /// or translation key, an item's name), else its reference as written.
46    pub fn resolve(self, cx: &mut Reader<'_>) -> Option<String> {
47        let Some(r) = self.to_ref() else {
48            return match self {
49                Text::Literal(s) => Some(s),
50                _ => None,
51            };
52        };
53        match cx.expand_ref(&r) {
54            Expansion::Kept => Some(r.to_string()),
55            Expansion::Value(Value::Str(s)) => Some(s),
56            Expansion::Value(other) => {
57                cx.problem(format!("`{r}` expands to {}, not a text", other.kind()));
58                None
59            }
60            Expansion::Failed => None,
61        }
62    }
63
64    /// What reference `r` is as a text (a resolver's `expand` gets the
65    /// reference a text is).
66    pub fn of_ref(r: Ref) -> Option<Text> {
67        let kind = r.kind.as_str();
68        if kind == kinds::Res::NAME {
69            return Res::parse(r.id.as_str()).map(Text::Res);
70        }
71        if kind == kinds::Lang::NAME {
72            return Some(Text::Lang(r.id.as_str().to_owned()));
73        }
74        Some(Text::NameOf(r))
75    }
76}
77
78impl NamedType for Text {
79    const NAME: &'static str = yog_ids::typed::TEXT;
80    type Stored = Value;
81    fn to_stored(&self) -> Value {
82        match self {
83            Text::Literal(s) => Value::Str(substrate_data::typed::escape_at(s)),
84            Text::Res(r) => substrate_data::Schema::write(r),
85            Text::NameOf(r) => Value::Ref(r.clone()),
86            Text::Lang(k) => substrate_data::Schema::write(&Lang::new(k.clone())),
87        }
88    }
89    fn from_stored(stored: Value) -> Option<Self> {
90        match stored {
91            Value::Ref(r) => Text::of_ref(r),
92            Value::Str(s) => match classify(&s).ok()? {
93                Spelled::Plain(p) => Some(Text::Literal(p)),
94                Spelled::Ref(r) => Text::of_ref(r),
95                Spelled::Bytes(_) => None,
96            },
97            _ => None,
98        }
99    }
100}
101
102/// Read as its reference when it's one, so a tool sees every reference a
103/// text makes; every other form is its stored form's.
104impl Schema for Text {
105    fn shape() -> Shape {
106        Shape::named::<Text>()
107    }
108    fn read(v: &ValueRef<'_>, cx: &mut Reader<'_>) -> Option<Self> {
109        let is_ref = match v {
110            ValueRef::Ref { .. } => true,
111            ValueRef::Str(s) => matches!(classify(s), Ok(Spelled::Ref(_))),
112            _ => false,
113        };
114        if !is_ref {
115            return String::read(v, cx).map(Text::Literal);
116        }
117        let r = substrate_data::schema::read_ref(v, cx, &[])?;
118        Text::of_ref(r.clone()).or_else(|| {
119            cx.problem(format!("`{r}` isn't a text"));
120            None
121        })
122    }
123    fn write(&self) -> Value {
124        self.to_stored()
125    }
126    fn encode(&self, w: &mut Writer) {
127        self.to_stored().encode(w);
128    }
129    fn decode(d: &mut Decoder<'_>) -> Option<Self> {
130        Value::decode(d).and_then(Text::from_stored)
131    }
132    fn pack(&self, p: &mut Packer) {
133        self.to_stored().pack(p);
134    }
135    fn unpack(u: &mut Unpacker<'_>) -> Option<Self> {
136        Value::unpack(u).and_then(Text::from_stored)
137    }
138}
139
140/// A field that is a text, kept resolved ([`Text::resolve`]):
141/// `#[data(with = "yog_data::TextOf")]`.
142#[derive(Debug)]
143pub struct TextOf;
144
145impl substrate_data::with::With<String> for TextOf {
146    fn shape() -> Shape {
147        Text::shape()
148    }
149    fn read(v: &ValueRef<'_>, cx: &mut Reader<'_>) -> Option<String> {
150        Text::read(v, cx)?.resolve(cx)
151    }
152    fn write(f: &String) -> Value {
153        Text::Literal(f.clone()).write()
154    }
155}