use std::borrow::Cow;
use std::fmt::{Display, Write};
pub struct Plist {
out: String,
}
pub fn quote(s: &str) -> Cow<'_, str> {
if !s.is_empty() && s.as_bytes().iter().copied().all(|b| b.is_ascii_alphabetic() || b.is_ascii_digit() || b == b'.' || b == b'_' || b == b'/') {
Cow::Borrowed(s)
} else {
let s = s.replace('\\', "\\\\").replace('\n', "\\n").replace('"', "\\\"");
Cow::Owned(["\"", &s, "\""].concat())
}
}
impl Plist {
pub fn new() -> Self {
let mut out = String::with_capacity(1 << 15);
out.push_str("// !$*UTF8*$!\n");
Self { out }
}
pub fn root(&mut self) -> Object<'_> {
Object::new(&mut self.out, 1)
}
pub fn fin(mut self) -> String {
self.out.push('\n');
self.out
}
}
pub struct Object<'out> {
indent: u16,
out: &'out mut String,
}
impl<'a> Object<'a> {
pub fn comment(&mut self, c: impl Display) -> &mut Self {
tabs(self.out, self.indent);
writeln!(self.out, "/* {c} */").unwrap();
self
}
pub fn section_comment(&mut self, c: impl Display) -> &mut Self {
writeln!(self.out, "/* {c} */").unwrap();
self
}
pub fn nl(&mut self) -> &mut Self {
self.out.push('\n');
self
}
pub fn kv(&mut self, k: impl Display, v: impl Display) -> &mut Self {
tabs(self.out, self.indent);
writeln!(self.out, "{k} = {v};").unwrap();
self
}
pub fn kvq(&mut self, k: impl Display, v: impl Display) -> &mut Self {
tabs(self.out, self.indent);
let v = v.to_string();
let v = quote(&v);
writeln!(self.out, "{k} = {v};").unwrap();
self
}
fn kv_start(&mut self, k: impl Display, comment: Option<&str>) {
tabs(self.out, self.indent);
write!(self.out, "{k} ").unwrap();
if let Some(c) = comment {
write!(self.out, "/* {c} */ ").unwrap();
}
self.out.push_str("= ");
}
pub fn array(&mut self, k: impl Display, comment: Option<&str>, v: impl FnOnce(&mut Array)) -> &mut Self {
self.kv_start(k, comment);
v(&mut Array::new(self.out, self.indent + 1));
self.out.push_str(";\n");
self
}
pub fn obj(&mut self, k: impl Display, comment: Option<&str>, v: impl FnOnce(&mut Object)) -> &mut Self {
self.kv_start(k, comment);
v(&mut Object::new(self.out, self.indent + 1));
self.out.push_str(";\n");
self
}
fn new(out: &'a mut String, indent: u16) -> Self {
let m = Self { indent, out };
m.out.push_str("{\n");
m
}
}
fn tabs(out: &mut String, n: u16) {
out.extend(std::iter::repeat('\t').take(n as usize));
}
impl Drop for Object<'_> {
fn drop(&mut self) {
tabs(self.out, self.indent - 1);
self.out.push('}');
}
}
pub struct Array<'out> {
indent: u16,
out: &'out mut String,
}
impl<'a> Array<'a> {
fn new(out: &'a mut String, indent: u16) -> Self {
let m = Self { indent, out };
m.out.push_str("(\n");
m
}
pub(crate) fn q(&mut self, arg: impl Display) -> &mut Self {
tabs(self.out, self.indent);
writeln!(self.out, "\"{arg}\",").unwrap();
self
}
pub(crate) fn v(&mut self, arg: impl Display) -> &mut Self {
tabs(self.out, self.indent);
writeln!(self.out, "{arg},").unwrap();
self
}
pub(crate) fn raw(&mut self, arg: impl Display) -> &mut Self {
write!(self.out, "{arg}").unwrap();
self
}
}
impl Drop for Array<'_> {
fn drop(&mut self) {
tabs(self.out, self.indent - 1);
self.out.push(')');
}
}