feat: adapt spine-add-book for shelf-based nesting

This commit is contained in:
2026-06-21 22:52:00 -04:00
parent 858fc6c13b
commit 609e168932
2 changed files with 63 additions and 24 deletions
+23 -14
View File
@@ -227,13 +227,13 @@ SELECTED-ID expands the matching book's detail section."
nil)))))))) nil))))))))
))))) )))))
(defun spine-add-book (&rest args) (defun spine-add-book (&rest args)
"Add a new WANT book to `spine-org-file'. "Add a new book to shelf `:shelf' in `spine-org-file'.
Keyword arguments: :title :author :category :format :isbn :cover Keyword arguments: :title :author :shelf :format :isbn :cover
:rec_by :rec_note :date_added :initial_note :rec_by :rec_note :date_added :initial_note
Signals an error on failure." :shelf is required. Signals an error if the shelf doesn't exist."
(let ((title (plist-get args :title)) (let ((title (plist-get args :title))
(shelf (plist-get args :shelf))
(author (plist-get args :author)) (author (plist-get args :author))
(category (plist-get args :category))
(format (plist-get args :format)) (format (plist-get args :format))
(isbn (plist-get args :isbn)) (isbn (plist-get args :isbn))
(cover (plist-get args :cover)) (cover (plist-get args :cover))
@@ -243,20 +243,32 @@ Signals an error on failure."
(initial-note (plist-get args :initial_note))) (initial-note (plist-get args :initial_note)))
(unless title (unless title
(error "spine-add-book: title is required")) (error "spine-add-book: title is required"))
(unless shelf
(error "spine-add-book: :shelf is required"))
(let ((org-file (expand-file-name spine-org-file))) (let ((org-file (expand-file-name spine-org-file)))
;; Create file if it doesn't exist
(unless (file-exists-p org-file) (unless (file-exists-p org-file)
(with-temp-file org-file (error "spine-add-book: file %s does not exist; create shelves first"
(insert "#+TODO: WANT(w) READING(r) | READ(d) ABANDONED(a)\n\n"))) org-file))
(with-current-buffer (find-file-noselect org-file) (with-current-buffer (find-file-noselect org-file)
(unwind-protect (unwind-protect
(progn (progn
(goto-char (point-max)) ;; Find the shelf headline
;; Insert new headline (goto-char (point-min))
(let ((shelf-found nil))
(while (and (not shelf-found)
(re-search-forward
(format "^\\*+[ \t]+%s[ \t]*$" (regexp-quote shelf))
nil t))
(when (= (org-current-level) 1)
(setq shelf-found t)))
(unless shelf-found
(error "spine-add-book: shelf \"%s\" not found" shelf)))
;; Go to end of shelf section
(org-end-of-subtree)
;; Insert new book headline at level 2
(org-insert-heading nil t) (org-insert-heading nil t)
(org-demote-subtree)
(insert title) (insert title)
;; Set TODO state
(org-todo "WANT")
;; Set properties (only non-empty) ;; Set properties (only non-empty)
(when (and author (> (length author) 0)) (when (and author (> (length author) 0))
(org-set-property "AUTHOR" author)) (org-set-property "AUTHOR" author))
@@ -279,9 +291,6 @@ Signals an error on failure."
(substring (sha1 (concat title (number-to-string (random)))) 0 4))) (substring (sha1 (concat title (number-to-string (random)))) 0 4)))
;; Set last modified ;; Set last modified
(org-set-property "LAST_MODIFIED" (format-time-string "[%Y-%m-%d]")) (org-set-property "LAST_MODIFIED" (format-time-string "[%Y-%m-%d]"))
;; Set tags
(when (and category (> (length category) 0))
(org-set-tags (list category)))
;; Add initial note ;; Add initial note
(when (and initial-note (> (length initial-note) 0)) (when (and initial-note (> (length initial-note) 0))
(let ((date (if (and date-added (> (length date-added) 0)) (let ((date (if (and date-added (> (length date-added) 0))
+40 -10
View File
@@ -16,18 +16,20 @@
(ignore-errors (delete-file spine-org-file))) (ignore-errors (delete-file spine-org-file)))
(ert-deftest spine-add-book-creates-headline () (ert-deftest spine-add-book-creates-headline ()
"spine-add-book creates a new WANT headline." "spine-add-book creates a new book under the given shelf."
(spine-add-book-test--cleanup) (spine-add-book-test--cleanup)
(unwind-protect (unwind-protect
(progn (progn
(spine-add-book :title "Test Book" :author "Author Name") (with-temp-file spine-org-file
(insert "* Fiction\n\n"))
(spine-add-book :title "Test Book" :author "Author Name" :shelf "Fiction")
(let ((books (spine-books))) (let ((books (spine-books)))
(should books) (should books)
(should (= (length books) 1)) (should (= (length books) 1))
(let ((book (car books))) (let ((book (car books)))
(should (equal (plist-get book :title) "Test Book")) (should (equal (plist-get book :title) "Test Book"))
(should (equal (plist-get book :status) "WANT"))
(should (equal (plist-get book :author) "Author Name")) (should (equal (plist-get book :author) "Author Name"))
(should (equal (plist-get book :shelf) "Fiction"))
(should (plist-get book :id))))) (should (plist-get book :id)))))
(spine-add-book-test--cleanup))) (spine-add-book-test--cleanup)))
@@ -36,6 +38,8 @@
(spine-add-book-test--cleanup) (spine-add-book-test--cleanup)
(unwind-protect (unwind-protect
(progn (progn
(with-temp-file spine-org-file
(insert "* Fiction\n\n"))
(spine-add-book (spine-add-book
:title "Full Test" :title "Full Test"
:author "Jane Doe" :author "Jane Doe"
@@ -45,8 +49,8 @@
:rec_by "Friend" :rec_by "Friend"
:rec_note "You'll love this" :rec_note "You'll love this"
:date_added "2026-06-21" :date_added "2026-06-21"
:category "scifi" :initial_note "Looking forward to this"
:initial_note "Looking forward to this") :shelf "Fiction")
(let* ((books (spine-books)) (let* ((books (spine-books))
(book (car books))) (book (car books)))
(should (equal (plist-get book :title) "Full Test")) (should (equal (plist-get book :title) "Full Test"))
@@ -57,7 +61,6 @@
(should (equal (plist-get book :rec_by) "Friend")) (should (equal (plist-get book :rec_by) "Friend"))
(should (equal (plist-get book :rec_note) "You'll love this")) (should (equal (plist-get book :rec_note) "You'll love this"))
(should (equal (plist-get book :added) "[2026-06-21]")) (should (equal (plist-get book :added) "[2026-06-21]"))
(should (equal (plist-get book :tags) '("scifi")))
(should (equal (plist-get book :notes) (should (equal (plist-get book :notes)
'(("2026-06-21" "Looking forward to this")))))) '(("2026-06-21" "Looking forward to this"))))))
(spine-add-book-test--cleanup))) (spine-add-book-test--cleanup)))
@@ -67,10 +70,13 @@
(spine-add-book-test--cleanup) (spine-add-book-test--cleanup)
(unwind-protect (unwind-protect
(progn (progn
(spine-add-book :title "Minimal Book") (with-temp-file spine-org-file
(insert "* Fiction\n\n"))
(spine-add-book :title "Minimal Book" :shelf "Fiction")
(let* ((books (spine-books)) (let* ((books (spine-books))
(book (car books))) (book (car books)))
(should (equal (plist-get book :title) "Minimal Book")) (should (equal (plist-get book :title) "Minimal Book"))
(should (equal (plist-get book :shelf) "Fiction"))
(should-not (plist-get book :author)) (should-not (plist-get book :author))
(should-not (plist-get book :format)) (should-not (plist-get book :format))
(should-not (plist-get book :isbn)) (should-not (plist-get book :isbn))
@@ -81,7 +87,14 @@
"spine-add-book signals error when title is missing." "spine-add-book signals error when title is missing."
(spine-add-book-test--cleanup) (spine-add-book-test--cleanup)
(unwind-protect (unwind-protect
(should-error (spine-add-book :author "No Title")) (should-error (spine-add-book :author "No Title" :shelf "Fiction"))
(spine-add-book-test--cleanup)))
(ert-deftest spine-add-book-requires-shelf ()
"spine-add-book signals error when shelf is missing."
(spine-add-book-test--cleanup)
(unwind-protect
(should-error (spine-add-book :title "No Shelf"))
(spine-add-book-test--cleanup))) (spine-add-book-test--cleanup)))
(ert-deftest spine-add-book-generates-unique-ids () (ert-deftest spine-add-book-generates-unique-ids ()
@@ -89,10 +102,27 @@
(spine-add-book-test--cleanup) (spine-add-book-test--cleanup)
(unwind-protect (unwind-protect
(progn (progn
(spine-add-book :title "Book One") (with-temp-file spine-org-file
(spine-add-book :title "Book Two") (insert "* Fiction\n\n"))
(spine-add-book :title "Book One" :shelf "Fiction")
(spine-add-book :title "Book Two" :shelf "Fiction")
(let* ((books (spine-books)) (let* ((books (spine-books))
(id1 (plist-get (nth 0 books) :id)) (id1 (plist-get (nth 0 books) :id))
(id2 (plist-get (nth 1 books) :id))) (id2 (plist-get (nth 1 books) :id)))
(should (not (equal id1 id2))))) (should (not (equal id1 id2)))))
(spine-add-book-test--cleanup))) (spine-add-book-test--cleanup)))
(ert-deftest spine-add-book-creates-under-correct-shelf ()
"spine-add-book places the book under the named shelf."
(spine-add-book-test--cleanup)
(unwind-protect
(progn
(with-temp-file spine-org-file
(insert "* Fiction\n\n* Non-Fiction\n\n"))
(spine-add-book :title "Sci-Fi Book" :shelf "Fiction")
(spine-add-book :title "History Book" :shelf "Non-Fiction")
(let ((books (spine-books)))
(should (= (length books) 2))
(should (equal (plist-get (nth 0 books) :shelf) "Fiction"))
(should (equal (plist-get (nth 1 books) :shelf) "Non-Fiction"))))
(spine-add-book-test--cleanup)))