feat: add spine-set-status, spine-add-note, spine-set-rating

This commit is contained in:
2026-06-21 11:43:56 -04:00
parent 8c8b004500
commit 95dc4b5db4
+75
View File
@@ -250,6 +250,81 @@ Signals an error on failure."
t) t)
(kill-buffer)))))) (kill-buffer))))))
(defun spine-set-status (id status)
"Set the TODO state of book with ID to STATUS.
STATUS should be one of: WANT, READING, READ, ABANDONED.
Signals an error if the book is not found."
(let ((org-file (expand-file-name spine-org-file)))
(unless (file-exists-p org-file)
(error "spine-set-status: file not found: %s" org-file))
(with-current-buffer (find-file-noselect org-file)
(unwind-protect
(progn
(org-element-map (org-element-parse-buffer 'headline) 'headline
(lambda (hl)
(when (= (org-element-property :level hl) 1)
(let ((pos (org-element-property :begin hl)))
(when (equal id (spine--prop pos "ID"))
(goto-char pos)
(org-todo status)))))
nil 'headline)
(unless (eq (point) (point-min))
(save-buffer)
t)
(when (eq (point) (point-min))
(error "spine-set-status: no book found with ID %s" id)))
(kill-buffer)))))
(defun spine-add-note (id text &optional date)
"Append a reading note to the book with ID.
TEXT is the note content. DATE is an optional YYYY-MM-DD string
\(defaults to today). Signals an error if the book is not found."
(let ((org-file (expand-file-name spine-org-file))
(date-str (or date (format-time-string "%Y-%m-%d"))))
(unless (file-exists-p org-file)
(error "spine-add-note: file not found: %s" org-file))
(with-current-buffer (find-file-noselect org-file)
(unwind-protect
(progn
(org-element-map (org-element-parse-buffer 'headline) 'headline
(lambda (hl)
(when (= (org-element-property :level hl) 1)
(let ((hl-id (spine--prop (org-element-property :begin hl) "ID")))
(when (equal id hl-id)
(goto-char (org-element-property :end hl))
(skip-chars-backward " \t\n")
(insert (format "\n- [%s] %s\n" date-str text))))))
nil 'headline)
(unless (eq (point) (point-min))
(save-buffer)
t)
(when (eq (point) (point-min))
(error "spine-add-note: no book found with ID %s" id)))
(kill-buffer)))))
(defun spine-set-rating (id rating)
"Set the RATING property of book with ID to RATING (1-5 string).
Signals an error if the book is not found."
(let ((org-file (expand-file-name spine-org-file)))
(unless (file-exists-p org-file)
(error "spine-set-rating: file not found: %s" org-file))
(with-current-buffer (find-file-noselect org-file)
(unwind-protect
(progn
(org-element-map (org-element-parse-buffer 'headline) 'headline
(lambda (hl)
(when (= (org-element-property :level hl) 1)
(let ((pos (org-element-property :begin hl)))
(when (equal id (spine--prop pos "ID"))
(goto-char pos)
(org-set-property "RATING" rating)))))
nil 'headline)
(unless (eq (point) (point-min))
(save-buffer)
t)
(when (eq (point) (point-min))
(error "spine-set-rating: no book found with ID %s" id)))
(kill-buffer)))))
(defun spine--extract-notes (hl-begin hl-end) (defun spine--extract-notes (hl-begin hl-end)
"Extract reading notes from headline body between HL-BEGIN and HL-END. "Extract reading notes from headline body between HL-BEGIN and HL-END.
Returns list of (date-string text-string) pairs." Returns list of (date-string text-string) pairs."