tftio-org-gdocs 0.1.1

Sync org-mode documents to Google Docs and pull reviewer comments back into org-mode
Documentation
//! The CLI ↔ Emacs result envelope (A5): a single s-expression the binary prints
//! for domain commands, which `org-gdocs.el` reads with `(read …)`.
//!
//! Shape:
//!
//! ```text
//! (ok    <command> (<key> <value>) …)
//! (error <command> "<message>")
//! ```
//!
//! `<command>` is a bare symbol (`push`/`pull`/`clean`/`open`/`auth`); each field
//! is a `(key value)` list so elisp can `assq` it. This is JSON-free by design —
//! see [`crate::sexp`].

use crate::sexp::Sexp;

/// Build a success envelope: `(ok <command> (key value) …)`.
#[must_use]
pub fn ok(command: &str, fields: Vec<(&str, Sexp)>) -> Sexp {
    let mut items = Vec::with_capacity(fields.len() + 2);
    items.push(Sexp::symbol("ok"));
    items.push(Sexp::symbol(command));
    for (key, value) in fields {
        items.push(Sexp::list(vec![Sexp::symbol(key), value]));
    }
    Sexp::list(items)
}

/// Build an error envelope: `(error <command> "<message>")`.
#[must_use]
pub fn error(command: &str, message: &str) -> Sexp {
    Sexp::list(vec![
        Sexp::symbol("error"),
        Sexp::symbol(command),
        Sexp::string(message),
    ])
}

/// The Emacs-lisp boolean symbol for a flag.
#[must_use]
pub const fn flag(value: bool) -> &'static str {
    if value { "t" } else { "nil" }
}

#[cfg(test)]
mod tests {
    use super::{error, flag, ok};
    use crate::sexp::Sexp;

    #[test]
    fn ok_envelope_renders_command_and_fields() {
        let rendered = ok(
            "push",
            vec![
                ("document-id", Sexp::string("DOC1")),
                ("created", Sexp::symbol(flag(true))),
            ],
        )
        .render();
        assert_eq!(rendered, "(ok push (document-id \"DOC1\") (created t))");
    }

    #[test]
    fn error_envelope_renders_message() {
        assert_eq!(
            error("open", "no linked document").render(),
            "(error open \"no linked document\")"
        );
    }
}