use std::fs;
use std::io::BufReader;
use crate::{Error, Job, Result};
pub fn plist_to_json(job: &Job) -> Result<()> {
let value = read_plist(job)?;
let json = serde_json::to_string_pretty(&plist_to_serde(value))
.map_err(|e| Error::other(format!("json serialize: {e}")))?;
fs::write(job.output, json + "\n")?;
Ok(())
}
pub fn json_to_plist(job: &Job) -> Result<()> {
let raw = fs::read_to_string(job.input)?;
let value: serde_json::Value = serde_json::from_str(&raw)
.map_err(|e| Error::other(format!("invalid JSON: {e}")))?;
let plist_value = serde_to_plist(value);
plist_value
.to_file_xml(job.output)
.map_err(|e| Error::other(format!("plist write: {e}")))?;
Ok(())
}
fn read_plist(job: &Job) -> Result<plist::Value> {
let file = fs::File::open(job.input)?;
plist::Value::from_reader(BufReader::new(file))
.map_err(|e| Error::other(format!("plist read: {e}")))
}
fn plist_to_serde(value: plist::Value) -> serde_json::Value {
use serde_json::Value as J;
match value {
plist::Value::Array(arr) => {
J::Array(arr.into_iter().map(plist_to_serde).collect())
}
plist::Value::Dictionary(dict) => {
let mut map = serde_json::Map::with_capacity(dict.len());
for (k, v) in dict {
map.insert(k, plist_to_serde(v));
}
J::Object(map)
}
plist::Value::Boolean(b) => J::Bool(b),
plist::Value::Integer(i) => i
.as_signed()
.map(|v| J::Number(v.into()))
.or_else(|| i.as_unsigned().map(|v| J::Number(v.into())))
.unwrap_or(J::Null),
plist::Value::Real(f) => serde_json::Number::from_f64(f)
.map(J::Number)
.unwrap_or(J::Null),
plist::Value::String(s) => J::String(s),
plist::Value::Date(d) => J::String(format!("{d:?}")),
plist::Value::Data(bytes) => {
let mut hex = String::with_capacity(bytes.len() * 2);
for b in bytes {
let _ = std::fmt::Write::write_fmt(&mut hex, format_args!("{b:02x}"));
}
J::String(hex)
}
plist::Value::Uid(uid) => J::Number(uid.get().into()),
_ => J::Null,
}
}
fn serde_to_plist(value: serde_json::Value) -> plist::Value {
use serde_json::Value as J;
match value {
J::Null => plist::Value::String(String::new()),
J::Bool(b) => plist::Value::Boolean(b),
J::Number(n) => {
if let Some(i) = n.as_i64() {
plist::Value::Integer(i.into())
} else if let Some(u) = n.as_u64() {
plist::Value::Integer(u.into())
} else {
plist::Value::Real(n.as_f64().unwrap_or(0.0))
}
}
J::String(s) => plist::Value::String(s),
J::Array(items) => {
plist::Value::Array(items.into_iter().map(serde_to_plist).collect())
}
J::Object(map) => {
let mut dict = plist::Dictionary::new();
for (k, v) in map {
dict.insert(k, serde_to_plist(v));
}
plist::Value::Dictionary(dict)
}
}
}