1use std::borrow::Cow;
4use std::fmt;
5use std::str::FromStr;
6
7use super::reader::Reader;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12#[non_exhaustive]
13pub enum ResourceType {
14 Integer,
16 String,
18 StrArray,
20 IntArray,
22 Boolean,
24 Color,
26 Id,
28 Theme,
30 Plural,
32 Float,
34 Media,
36 Profile,
38 Pattern,
40 Symbol,
42 Other(u32),
44}
45
46struct TypeRow {
47 kind: ResourceType,
48 code: u32,
50 name: &'static str,
51 object_code: Option<u32>,
54}
55
56const fn row(kind: ResourceType, code: u32, name: &'static str, object_code: Option<u32>) -> TypeRow {
57 TypeRow { kind, code, name, object_code }
58}
59
60const TYPES: &[TypeRow] = &[
61 row(ResourceType::Integer, 8, "integer", Some(10007)),
62 row(ResourceType::String, 9, "string", Some(10003)),
63 row(ResourceType::StrArray, 10, "strarray", Some(10009)),
64 row(ResourceType::IntArray, 11, "intarray", Some(10006)),
65 row(ResourceType::Boolean, 12, "boolean", Some(10005)),
66 row(ResourceType::Color, 14, "color", Some(10001)),
67 row(ResourceType::Id, 15, "id", None),
68 row(ResourceType::Theme, 16, "theme", None),
69 row(ResourceType::Plural, 17, "plural", Some(10004)),
70 row(ResourceType::Float, 18, "float", Some(10002)),
71 row(ResourceType::Media, 19, "media", Some(20000)),
72 row(ResourceType::Profile, 20, "profile", None),
73 row(ResourceType::Pattern, 22, "pattern", Some(10008)),
74 row(ResourceType::Symbol, 23, "symbol", Some(40000)),
75];
76
77impl ResourceType {
78 fn row(self) -> Option<&'static TypeRow> {
79 TYPES.iter().find(|row| row.kind == self)
80 }
81
82 pub fn from_code(code: u32) -> ResourceType {
84 TYPES.iter().find(|row| row.code == code).map_or(ResourceType::Other(code), |row| row.kind)
85 }
86
87 pub fn code(self) -> u32 {
89 match self {
90 ResourceType::Other(code) => code,
91 named => named.row().map_or(0, |row| row.code),
92 }
93 }
94
95 pub fn from_object_code(code: u32) -> Option<ResourceType> {
101 TYPES.iter().find(|row| row.object_code == Some(code)).map(|row| row.kind)
102 }
103
104 pub fn object_code(self) -> Option<u32> {
106 self.row().and_then(|row| row.object_code)
107 }
108}
109
110impl fmt::Display for ResourceType {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self.row() {
115 Some(row) => f.write_str(row.name),
116 None => write!(f, "type{}", self.code()),
117 }
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
123#[error("unknown resource type {0:?}")]
124pub struct UnknownResourceType(String);
125
126impl FromStr for ResourceType {
127 type Err = UnknownResourceType;
128
129 fn from_str(s: &str) -> Result<ResourceType, UnknownResourceType> {
132 if let Some(row) = TYPES.iter().find(|row| row.name == s) {
133 return Ok(row.kind);
134 }
135 s.strip_prefix("type")
136 .and_then(|code| code.parse().ok())
137 .map(ResourceType::from_code)
138 .ok_or_else(|| UnknownResourceType(s.to_owned()))
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub struct ResourceRef<'s> {
146 pub kind: ResourceType,
148 pub name: &'s str,
153}
154
155impl<'s> ResourceRef<'s> {
156 pub fn parse(text: &'s str) -> Option<ResourceRef<'s>> {
160 let (kind, name) = match text.strip_prefix('$') {
161 Some(rest) => rest.split_once(':')?,
162 None => text.strip_prefix("app.")?.split_once('.')?,
163 };
164 if name.is_empty() {
165 return None;
166 }
167 Some(ResourceRef { kind: kind.parse().ok()?, name })
168 }
169}
170
171impl fmt::Display for ResourceRef<'_> {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 write!(f, "${}:{}", self.kind, self.name)
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
181#[non_exhaustive]
182pub struct Attr<'a> {
183 pub name: Cow<'a, str>,
185 pub value: Cow<'a, str>,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
192pub enum ResourceValue<'a> {
193 Text(Cow<'a, str>),
197 File(Cow<'a, str>),
200 Array(Vec<Cow<'a, str>>),
202 Plural(Vec<Attr<'a>>),
204 Map {
206 parent: Option<Cow<'a, str>>,
208 attrs: Vec<Attr<'a>>,
210 },
211 Raw(&'a [u8]),
214}
215
216impl<'a> ResourceValue<'a> {
217 pub(super) fn decode(kind: ResourceType, bytes: &'a [u8], terminated: bool) -> ResourceValue<'a> {
220 let bytes = match bytes.split_last() {
221 Some((0, rest)) if terminated => rest,
222 _ => bytes,
223 };
224 let text = || String::from_utf8_lossy(bytes);
225 let raw = ResourceValue::Raw(bytes);
226 match kind {
227 ResourceType::Integer
228 | ResourceType::String
229 | ResourceType::Boolean
230 | ResourceType::Color
231 | ResourceType::Id
232 | ResourceType::Float
233 | ResourceType::Symbol => ResourceValue::Text(text()),
234 ResourceType::Media | ResourceType::Profile => ResourceValue::File(text()),
235 ResourceType::StrArray | ResourceType::IntArray => strings(bytes).map_or(raw, ResourceValue::Array),
236 ResourceType::Plural => match strings(bytes) {
237 Some(items) if items.len() % 2 == 0 => ResourceValue::Plural(pairs(items)),
238 _ => raw,
239 },
240 ResourceType::Theme | ResourceType::Pattern => match strings(bytes) {
241 Some(mut items) if items.len() % 2 == 1 => {
243 let parent = items.remove(0);
244 ResourceValue::Map { parent: Some(parent), attrs: pairs(items) }
245 }
246 Some(items) => ResourceValue::Map { parent: None, attrs: pairs(items) },
247 None => raw,
248 },
249 ResourceType::Other(_) => raw,
250 }
251 }
252
253 pub fn archive_path(&self) -> Option<&str> {
256 match self {
257 ResourceValue::File(path) => Some(path.split_once('/').map_or(path.as_ref(), |(_, rest)| rest)),
258 ResourceValue::Text(_)
259 | ResourceValue::Array(_)
260 | ResourceValue::Plural(_)
261 | ResourceValue::Map { .. }
262 | ResourceValue::Raw(_) => None,
263 }
264 }
265}
266
267fn strings(bytes: &[u8]) -> Option<Vec<Cow<'_, str>>> {
270 let mut reader = Reader::new(bytes);
271 let mut items = Vec::new();
272 while reader.remaining() > 0 {
273 items.push(String::from_utf8_lossy(reader.sized16("list item").ok()?));
274 if reader.take(1, "list item terminator").ok()? != [0] {
275 return None;
276 }
277 }
278 Some(items)
279}
280
281fn pairs(items: Vec<Cow<'_, str>>) -> Vec<Attr<'_>> {
282 let mut items = items.into_iter();
283 std::iter::from_fn(|| Some(Attr { name: items.next()?, value: items.next()? })).collect()
284}
285
286struct Quoted<'s>(&'s str);
288
289impl fmt::Display for Quoted<'_> {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 f.write_str(&asc_query::text::quoted(self.0))
292 }
293}
294
295fn write_attrs(f: &mut fmt::Formatter<'_>, attrs: &[Attr<'_>], mut first: bool) -> fmt::Result {
296 for attr in attrs {
297 if !first {
298 f.write_str(", ")?;
299 }
300 first = false;
301 write!(f, "{}: {}", Quoted(&attr.name), Quoted(&attr.value))?;
302 }
303 Ok(())
304}
305
306impl fmt::Display for ResourceValue<'_> {
312 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313 match self {
314 ResourceValue::Text(text) => write!(f, "{}", Quoted(text)),
315 ResourceValue::File(path) => f.write_str(path),
316 ResourceValue::Array(items) => {
317 f.write_str("[")?;
318 for (i, item) in items.iter().enumerate() {
319 if i > 0 {
320 f.write_str(", ")?;
321 }
322 write!(f, "{}", Quoted(item))?;
323 }
324 f.write_str("]")
325 }
326 ResourceValue::Plural(attrs) => {
327 f.write_str("{")?;
328 write_attrs(f, attrs, true)?;
329 f.write_str("}")
330 }
331 ResourceValue::Map { parent, attrs } => {
332 f.write_str("{")?;
333 if let Some(parent) = parent {
334 write!(f, "parent: {}", Quoted(parent))?;
335 }
336 write_attrs(f, attrs, parent.is_none())?;
337 f.write_str("}")
338 }
339 ResourceValue::Raw(bytes) => write!(f, "<{} bytes>", bytes.len()),
340 }
341 }
342}