From 9aacccb2eee5bbe6e31a77d0c5830255330d6bcd Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Sun, 21 Jun 2026 08:18:18 -0400 Subject: [PATCH] feat: add spine-add-book write function --- spine.el | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/spine.el b/spine.el index f1636ae..35718f3 100644 --- a/spine.el +++ b/spine.el @@ -137,6 +137,71 @@ SELECTED-ID expands the matching book's detail section." ("reading_count" reading-count) ("groups" (nreverse groups)))))) +(defun spine-add-book (&rest args) + "Add a new WANT book to `spine-org-file'. +Keyword arguments: :title :author :category :format :isbn :cover + :rec_by :rec_note :date_added :initial_note +Signals an error on failure." + (let ((title (plist-get args :title)) + (author (plist-get args :author)) + (category (plist-get args :category)) + (format (plist-get args :format)) + (isbn (plist-get args :isbn)) + (cover (plist-get args :cover)) + (rec-by (plist-get args :rec_by)) + (rec-note (plist-get args :rec_note)) + (date-added (plist-get args :date_added)) + (initial-note (plist-get args :initial_note))) + (unless title + (error "spine-add-book: title is required")) + (let ((org-file (expand-file-name spine-org-file))) + ;; Create file if it doesn't exist + (unless (file-exists-p org-file) + (with-temp-file org-file + (insert "#+TODO: WANT(w) READING(r) | READ(d) ABANDONED(a)\n\n"))) + (with-current-buffer (find-file-noselect org-file) + (unwind-protect + (progn + (goto-char (point-max)) + ;; Insert new headline + (org-insert-heading nil t) + (insert title) + ;; Set TODO state + (org-todo "WANT") + ;; Set properties (only non-empty) + (when (and author (> (length author) 0)) + (org-set-property "AUTHOR" author)) + (when (and format (> (length format) 0)) + (org-set-property "FORMAT" format)) + (when (and isbn (> (length isbn) 0)) + (org-set-property "ISBN" isbn)) + (when (and cover (> (length cover) 0)) + (org-set-property "COVER" cover)) + (when (and rec-by (> (length rec-by) 0)) + (org-set-property "REC_BY" rec-by)) + (when (and rec-note (> (length rec-note) 0)) + (org-set-property "REC_NOTE" rec-note)) + (when (and date-added (> (length date-added) 0)) + (org-set-property "ADDED" (format "[%s]" date-added))) + ;; Set ID + (org-set-property "ID" + (format "%04x-%s" + (random 65535) + (substring (sha1 (concat title (number-to-string (random)))) 0 4))) + ;; Set tags + (when (and category (> (length category) 0)) + (org-set-tags (list category))) + ;; Add initial note + (when (and initial-note (> (length initial-note) 0)) + (let ((date (if (and date-added (> (length date-added) 0)) + date-added + (format-time-string "%Y-%m-%d")))) + (goto-char (org-entry-end-position)) + (insert (format "\n- [%s] %s\n" date initial-note)))) + (save-buffer) + t) + (kill-buffer)))))) + (defun spine--extract-notes (hl-begin hl-end) "Extract reading notes from headline body between HL-BEGIN and HL-END. Returns list of (date-string text-string) pairs."