use anyhow::{Context, Result};
pub const TEXTURE_TYPE_SPRITE: u32 = 8;
pub const SPRITE_MODE_SINGLE: u32 = 1;
#[derive(Debug, Clone, Default)]
pub struct MetaInfo {
pub guid: u128,
pub sprite_sheet: Vec<(i64, String)>,
pub texture_type: Option<u32>,
pub sprite_mode: Option<u32>,
}
pub fn parse(text: &str) -> Result<MetaInfo> {
let guid = parse_guid(text).context("missing or malformed `guid:` in .meta")?;
let sprite_sheet = parse_sprite_sheet(text);
let texture_type = parse_u32_field(text, "textureType:");
let sprite_mode = parse_u32_field(text, "spriteMode:");
Ok(MetaInfo {
guid,
sprite_sheet,
texture_type,
sprite_mode,
})
}
fn parse_u32_field(text: &str, key: &str) -> Option<u32> {
for line in text.lines() {
let line = line.trim_start();
if let Some(rest) = line.strip_prefix(key) {
return rest.trim().parse().ok();
}
}
None
}
fn parse_guid(text: &str) -> Option<u128> {
for line in text.lines() {
let line = line.trim_start();
if let Some(rest) = line.strip_prefix("guid:") {
let hex = rest.trim();
if hex.len() == 32 {
return u128::from_str_radix(hex, 16).ok();
}
}
}
None
}
fn parse_sprite_sheet(text: &str) -> Vec<(i64, String)> {
let mut out: Vec<(i64, String)> = Vec::new();
let mut in_sprites = false;
let mut cur_name: Option<String> = None;
let mut cur_id: Option<i64> = None;
for line in text.lines() {
let trimmed = line.trim();
if trimmed == "sprites:" {
in_sprites = true;
continue;
}
if !in_sprites {
continue;
}
if !line.starts_with(' ') && !line.starts_with('\t') && !trimmed.is_empty() {
break;
}
if let Some(after_dash) = trimmed.strip_prefix("- ") {
flush(&mut out, &mut cur_name, &mut cur_id);
absorb_kv(after_dash, &mut cur_name, &mut cur_id);
continue;
}
absorb_kv(trimmed, &mut cur_name, &mut cur_id);
}
flush(&mut out, &mut cur_name, &mut cur_id);
out
}
fn absorb_kv(line: &str, cur_name: &mut Option<String>, cur_id: &mut Option<i64>) {
if let Some(rest) = line.strip_prefix("name:") {
let s = rest.trim();
if cur_name.is_none() && !s.is_empty() {
*cur_name = Some(s.to_string());
}
} else if let Some(rest) = line.strip_prefix("internalID:") {
*cur_id = rest.trim().parse().ok();
}
}
fn flush(out: &mut Vec<(i64, String)>, name: &mut Option<String>, id: &mut Option<i64>) {
if let (Some(n), Some(i)) = (name.take(), id.take())
&& !n.is_empty()
{
out.push((i, n));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_simple_guid() {
let text = "fileFormatVersion: 2\nguid: 7d602c2080b53413fa393df6b2c0af43\n";
let info = parse(text).unwrap();
assert_eq!(info.guid, 0x7d602c2080b53413fa393df6b2c0af43_u128);
assert!(info.sprite_sheet.is_empty());
}
#[test]
fn rejects_short_guid() {
let text = "guid: deadbeef\n";
assert!(parse(text).is_err());
}
#[test]
fn parses_sprite_sheet() {
let text = "fileFormatVersion: 2
guid: 7d602c2080b53413fa393df6b2c0af43
TextureImporter:
spriteSheet:
sprites:
- serializedVersion: 2
name: spr_a
internalID: 11111
rect:
serializedVersion: 2
- serializedVersion: 2
name: spr_b
internalID: 22222
spritePackingTag:
";
let info = parse(text).unwrap();
assert_eq!(
info.sprite_sheet,
vec![(11111, "spr_a".to_string()), (22222, "spr_b".to_string()),]
);
}
#[test]
fn parses_texture_type_and_sprite_mode() {
let text = "fileFormatVersion: 2
guid: 7d602c2080b53413fa393df6b2c0af43
TextureImporter:
textureType: 8
spriteMode: 1
spriteSheet:
sprites: []
";
let info = parse(text).unwrap();
assert_eq!(info.texture_type, Some(8));
assert_eq!(info.sprite_mode, Some(1));
}
#[test]
fn missing_texture_fields_are_none() {
let text = "fileFormatVersion: 2\nguid: 7d602c2080b53413fa393df6b2c0af43\n";
let info = parse(text).unwrap();
assert_eq!(info.texture_type, None);
assert_eq!(info.sprite_mode, None);
}
}