Skip to main content

org_gdocs/
open.rs

1//! Q3 — the `open` command: report the linked doc's browser URL for elisp to
2//! `browse-url`.
3//!
4//! A pure, read-only lookup over the file's tool-owned header keywords (DI-3): it
5//! reads `#+GDOC_ID:` (required) and `#+GDOC_URL:` (deriving the canonical edit
6//! URL from the id when the keyword is absent), and never touches the body or the
7//! network. A file with no linked doc surfaces the typed [`Error::NotLinked`]
8//! rather than panicking (EI-1/EI-2).
9
10use crate::envelope;
11use crate::error::{Error, Result};
12use crate::google::docs::DocumentRef;
13use crate::orgfile;
14use crate::sexp::Sexp;
15
16/// The linked document a file points at.
17#[derive(Debug, Clone)]
18pub struct OpenOutcome {
19    /// The linked document id (`#+GDOC_ID:`).
20    pub document_id: String,
21    /// The browser URL — the stored `#+GDOC_URL:`, or the canonical edit URL
22    /// derived from the id.
23    pub url: String,
24}
25
26/// Resolve the linked document URL from `content`'s header keywords.
27///
28/// # Errors
29///
30/// Returns [`Error::NotLinked`] when the file carries no `#+GDOC_ID:`.
31pub fn open(content: &str) -> Result<OpenOutcome> {
32    let document_id = orgfile::read_keyword(content, "GDOC_ID").ok_or(Error::NotLinked)?;
33    let url = orgfile::read_keyword(content, "GDOC_URL")
34        .unwrap_or_else(|| DocumentRef::from_id(document_id.clone()).url);
35    Ok(OpenOutcome { document_id, url })
36}
37
38/// Build the success envelope for an `open` lookup (A5).
39#[must_use]
40pub fn envelope(outcome: &OpenOutcome) -> Sexp {
41    envelope::ok(
42        "open",
43        vec![
44            ("document-id", Sexp::string(outcome.document_id.clone())),
45            ("url", Sexp::string(outcome.url.clone())),
46        ],
47    )
48}
49
50#[cfg(test)]
51mod tests {
52    use super::open;
53    use crate::error::Error;
54
55    #[test]
56    fn open_prefers_the_stored_url() {
57        let content =
58            "#+GDOC_ID: DOC1\n#+GDOC_URL: https://docs.google.com/document/d/DOC1/edit\n* Intro\n";
59        let outcome = open(content).expect("open succeeds");
60        assert_eq!(outcome.document_id, "DOC1");
61        assert_eq!(outcome.url, "https://docs.google.com/document/d/DOC1/edit");
62    }
63
64    #[test]
65    fn open_derives_url_when_only_id_is_present() {
66        let content = "#+GDOC_ID: ABC123\n* Intro\nbody\n";
67        let outcome = open(content).expect("open succeeds");
68        assert_eq!(
69            outcome.url,
70            "https://docs.google.com/document/d/ABC123/edit"
71        );
72    }
73
74    #[test]
75    fn open_without_a_linked_doc_is_not_linked_error() {
76        let content = "* Intro\nNo link here.\n";
77        assert!(matches!(open(content), Err(Error::NotLinked)));
78    }
79}