use std::io::Cursor;
use crabstep::deserializer::iter::Property;
use plist::{Dictionary, Value};
const MAX_DEPTH: usize = 8;
#[derive(Clone, Copy)]
pub struct ScannerResult<'a> {
objects: &'a [Value],
index: usize,
depth: usize,
}
impl<'a> ScannerResult<'a> {
#[must_use]
pub fn root(plist: &'a Value) -> Option<Self> {
let body = plist.as_dictionary()?;
let objects = body.get("$objects")?.as_array()?;
let top = body.get("$top")?.as_dictionary()?;
let index = top
.get("dd-result")
.or_else(|| top.get("root"))
.and_then(uid_index)?;
Some(Self {
objects,
index,
depth: 0,
})
}
#[must_use]
pub fn kind(&self) -> Option<&'a str> {
self.field_string("T")
}
#[must_use]
pub fn value(&self) -> Option<&'a str> {
self.field_string("V")
}
#[must_use]
pub fn matched(&self) -> Option<&'a str> {
self.field_string("MS")
}
pub fn children(&self) -> impl Iterator<Item = ScannerResult<'a>> + '_ {
self.child_indices()
.unwrap_or_default()
.into_iter()
.map(|index| ScannerResult {
objects: self.objects,
index,
depth: self.depth + 1,
})
}
fn dict(&self) -> Option<&'a Dictionary> {
self.objects.get(self.index)?.as_dictionary()
}
fn field_string(&self, key: &str) -> Option<&'a str> {
let reference = self.dict()?.get(key)?;
self.objects.get(uid_index(reference)?)?.as_string()
}
fn child_indices(&self) -> Option<Vec<usize>> {
if self.depth >= MAX_DEPTH {
return None;
}
let sub_results = self.dict()?.get("SR")?;
let array = self
.objects
.get(uid_index(sub_results)?)?
.as_dictionary()?
.get("NS.objects")?
.as_array()?;
Some(array.iter().filter_map(uid_index).collect())
}
}
pub trait FromScannerResult: Sized {
const MARKERS: &[&[u8]] = &[];
fn from_scanner_result(result: &ScannerResult<'_>) -> Option<Self>;
fn from_attribute<'p>(value: &Property<'p, 'p>) -> Option<Self> {
let data = value.as_data()?;
if !Self::MARKERS.is_empty()
&& !Self::MARKERS
.iter()
.any(|marker| data.windows(marker.len()).any(|window| window == *marker))
{
return None;
}
let plist = Value::from_reader(Cursor::new(data)).ok()?;
Self::from_scanner_result(&ScannerResult::root(&plist)?)
}
}
fn uid_index(value: &Value) -> Option<usize> {
usize::try_from(value.as_uid()?.get()).ok()
}