1use 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
11pub type Lang = Id<kinds::Lang>;
13
14#[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 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 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 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
102impl 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#[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}