use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;
use super::reader::Reader;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ResourceType {
Integer,
String,
StrArray,
IntArray,
Boolean,
Color,
Id,
Theme,
Plural,
Float,
Media,
Profile,
Pattern,
Symbol,
Other(u32),
}
struct TypeRow {
kind: ResourceType,
code: u32,
name: &'static str,
object_code: Option<u32>,
}
const fn row(kind: ResourceType, code: u32, name: &'static str, object_code: Option<u32>) -> TypeRow {
TypeRow { kind, code, name, object_code }
}
const TYPES: &[TypeRow] = &[
row(ResourceType::Integer, 8, "integer", Some(10007)),
row(ResourceType::String, 9, "string", Some(10003)),
row(ResourceType::StrArray, 10, "strarray", Some(10009)),
row(ResourceType::IntArray, 11, "intarray", Some(10006)),
row(ResourceType::Boolean, 12, "boolean", Some(10005)),
row(ResourceType::Color, 14, "color", Some(10001)),
row(ResourceType::Id, 15, "id", None),
row(ResourceType::Theme, 16, "theme", None),
row(ResourceType::Plural, 17, "plural", Some(10004)),
row(ResourceType::Float, 18, "float", Some(10002)),
row(ResourceType::Media, 19, "media", Some(20000)),
row(ResourceType::Profile, 20, "profile", None),
row(ResourceType::Pattern, 22, "pattern", Some(10008)),
row(ResourceType::Symbol, 23, "symbol", Some(40000)),
];
impl ResourceType {
fn row(self) -> Option<&'static TypeRow> {
TYPES.iter().find(|row| row.kind == self)
}
pub fn from_code(code: u32) -> ResourceType {
TYPES.iter().find(|row| row.code == code).map_or(ResourceType::Other(code), |row| row.kind)
}
pub fn code(self) -> u32 {
match self {
ResourceType::Other(code) => code,
named => named.row().map_or(0, |row| row.code),
}
}
pub fn from_object_code(code: u32) -> Option<ResourceType> {
TYPES.iter().find(|row| row.object_code == Some(code)).map(|row| row.kind)
}
pub fn object_code(self) -> Option<u32> {
self.row().and_then(|row| row.object_code)
}
}
impl fmt::Display for ResourceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.row() {
Some(row) => f.write_str(row.name),
None => write!(f, "type{}", self.code()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("unknown resource type {0:?}")]
pub struct UnknownResourceType(String);
impl FromStr for ResourceType {
type Err = UnknownResourceType;
fn from_str(s: &str) -> Result<ResourceType, UnknownResourceType> {
if let Some(row) = TYPES.iter().find(|row| row.name == s) {
return Ok(row.kind);
}
s.strip_prefix("type")
.and_then(|code| code.parse().ok())
.map(ResourceType::from_code)
.ok_or_else(|| UnknownResourceType(s.to_owned()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceRef<'s> {
pub kind: ResourceType,
pub name: &'s str,
}
impl<'s> ResourceRef<'s> {
pub fn parse(text: &'s str) -> Option<ResourceRef<'s>> {
let (kind, name) = match text.strip_prefix('$') {
Some(rest) => rest.split_once(':')?,
None => text.strip_prefix("app.")?.split_once('.')?,
};
if name.is_empty() {
return None;
}
Some(ResourceRef { kind: kind.parse().ok()?, name })
}
}
impl fmt::Display for ResourceRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "${}:{}", self.kind, self.name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Attr<'a> {
pub name: Cow<'a, str>,
pub value: Cow<'a, str>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResourceValue<'a> {
Text(Cow<'a, str>),
File(Cow<'a, str>),
Array(Vec<Cow<'a, str>>),
Plural(Vec<Attr<'a>>),
Map {
parent: Option<Cow<'a, str>>,
attrs: Vec<Attr<'a>>,
},
Raw(&'a [u8]),
}
impl<'a> ResourceValue<'a> {
pub(super) fn decode(kind: ResourceType, bytes: &'a [u8], terminated: bool) -> ResourceValue<'a> {
let bytes = match bytes.split_last() {
Some((0, rest)) if terminated => rest,
_ => bytes,
};
let text = || String::from_utf8_lossy(bytes);
let raw = ResourceValue::Raw(bytes);
match kind {
ResourceType::Integer
| ResourceType::String
| ResourceType::Boolean
| ResourceType::Color
| ResourceType::Id
| ResourceType::Float
| ResourceType::Symbol => ResourceValue::Text(text()),
ResourceType::Media | ResourceType::Profile => ResourceValue::File(text()),
ResourceType::StrArray | ResourceType::IntArray => strings(bytes).map_or(raw, ResourceValue::Array),
ResourceType::Plural => match strings(bytes) {
Some(items) if items.len() % 2 == 0 => ResourceValue::Plural(pairs(items)),
_ => raw,
},
ResourceType::Theme | ResourceType::Pattern => match strings(bytes) {
Some(mut items) if items.len() % 2 == 1 => {
let parent = items.remove(0);
ResourceValue::Map { parent: Some(parent), attrs: pairs(items) }
}
Some(items) => ResourceValue::Map { parent: None, attrs: pairs(items) },
None => raw,
},
ResourceType::Other(_) => raw,
}
}
pub fn archive_path(&self) -> Option<&str> {
match self {
ResourceValue::File(path) => Some(path.split_once('/').map_or(path.as_ref(), |(_, rest)| rest)),
ResourceValue::Text(_)
| ResourceValue::Array(_)
| ResourceValue::Plural(_)
| ResourceValue::Map { .. }
| ResourceValue::Raw(_) => None,
}
}
}
fn strings(bytes: &[u8]) -> Option<Vec<Cow<'_, str>>> {
let mut reader = Reader::new(bytes);
let mut items = Vec::new();
while reader.remaining() > 0 {
items.push(String::from_utf8_lossy(reader.sized16("list item").ok()?));
if reader.take(1, "list item terminator").ok()? != [0] {
return None;
}
}
Some(items)
}
fn pairs(items: Vec<Cow<'_, str>>) -> Vec<Attr<'_>> {
let mut items = items.into_iter();
std::iter::from_fn(|| Some(Attr { name: items.next()?, value: items.next()? })).collect()
}
struct Quoted<'s>(&'s str);
impl fmt::Display for Quoted<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&asc_query::text::quoted(self.0))
}
}
fn write_attrs(f: &mut fmt::Formatter<'_>, attrs: &[Attr<'_>], mut first: bool) -> fmt::Result {
for attr in attrs {
if !first {
f.write_str(", ")?;
}
first = false;
write!(f, "{}: {}", Quoted(&attr.name), Quoted(&attr.value))?;
}
Ok(())
}
impl fmt::Display for ResourceValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResourceValue::Text(text) => write!(f, "{}", Quoted(text)),
ResourceValue::File(path) => f.write_str(path),
ResourceValue::Array(items) => {
f.write_str("[")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", Quoted(item))?;
}
f.write_str("]")
}
ResourceValue::Plural(attrs) => {
f.write_str("{")?;
write_attrs(f, attrs, true)?;
f.write_str("}")
}
ResourceValue::Map { parent, attrs } => {
f.write_str("{")?;
if let Some(parent) = parent {
write!(f, "parent: {}", Quoted(parent))?;
}
write_attrs(f, attrs, parent.is_none())?;
f.write_str("}")
}
ResourceValue::Raw(bytes) => write!(f, "<{} bytes>", bytes.len()),
}
}
}