cargo-xcode 1.11.0

Make Xcode project files from Cargo projects
Documentation
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 };
        // no indent, k&r
        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) {
        // self.out.push('\n'); should it be auto?
        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 };
        // no indent, k&r
        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) {
        // self.out.push('\n'); should it be auto?
        tabs(self.out, self.indent - 1);
        self.out.push(')');
    }
}