feat: add spine-shelves, adapt spine-books for shelf nesting
This commit is contained in:
+21
-10
@@ -1,6 +1,7 @@
|
||||
#+TODO: WANT(w) READING(r) | READ(d) ABANDONED(a)
|
||||
#+TITLE: Spine
|
||||
|
||||
* WANT The Left Hand of Darkness
|
||||
* Fiction
|
||||
** WANT The Left Hand of Darkness
|
||||
:PROPERTIES:
|
||||
:AUTHOR: Ursula K. Le Guin
|
||||
:ISBN: 978-0441478125
|
||||
@@ -14,7 +15,7 @@
|
||||
:ID: 9a2b-lhd
|
||||
:END:
|
||||
|
||||
* WANT Dune
|
||||
** WANT Dune
|
||||
:PROPERTIES:
|
||||
:AUTHOR: Frank Herbert
|
||||
:ISBN: 978-0441172719
|
||||
@@ -28,7 +29,7 @@
|
||||
:ID: 7f3c-dun
|
||||
:END:
|
||||
|
||||
* WANT Piranesi :fiction:
|
||||
** WANT Piranesi :fiction:
|
||||
:PROPERTIES:
|
||||
:AUTHOR: Susanna Clarke
|
||||
:ISBN:
|
||||
@@ -42,7 +43,7 @@
|
||||
:ID: 5e8d-pir
|
||||
:END:
|
||||
|
||||
* READING Use of Weapons :scifi:literary:
|
||||
** READING Use of Weapons :scifi:literary:
|
||||
:PROPERTIES:
|
||||
:AUTHOR: Iain M. Banks
|
||||
:ISBN: 978-0316029193
|
||||
@@ -55,14 +56,15 @@
|
||||
:LAST_MODIFIED: [2026-03-09]
|
||||
:ID: 8c1e-uow
|
||||
:END:
|
||||
:LOGBOOK:
|
||||
::LOGBOOK:
|
||||
- State "READING" from "WANT" [2026-03-01]
|
||||
:END:
|
||||
::END:
|
||||
- [2026-03-02] The two-track structure is doing something I can't name yet.
|
||||
- [2026-03-07] Zakalwe's competence reads as a wound.
|
||||
- [2026-03-09] The chapter numbering. Oh.
|
||||
|
||||
* READ Ancillary Justice :scifi:
|
||||
* Science Fiction
|
||||
** READ Ancillary Justice :scifi:
|
||||
:PROPERTIES:
|
||||
:AUTHOR: Ann Leckie
|
||||
:ISBN: 978-0316246620
|
||||
@@ -75,10 +77,19 @@
|
||||
:LAST_MODIFIED: [2025-11-15]
|
||||
:ID: 3a1b-aj
|
||||
:END:
|
||||
:LOGBOOK:
|
||||
::LOGBOOK:
|
||||
- State "READ" from "READING" [2026-01-10]
|
||||
- State "READING" from "WANT" [2025-11-20]
|
||||
:END:
|
||||
::END:
|
||||
- [2025-11-22] The pronoun game is a genuinely fresh take on identity.
|
||||
- [2025-12-05] Justice of Toren's multi-body perspective is unsettling.
|
||||
- [2026-01-08] The tea set. Perfect ending.
|
||||
|
||||
* Non-Fiction
|
||||
** The Checklist Manifesto
|
||||
:PROPERTIES:
|
||||
:AUTHOR: Atul Gawande
|
||||
:ID: b2c1-tcm
|
||||
:ADDED: [2026-06-15]
|
||||
:LAST_MODIFIED: [2026-06-15]
|
||||
:END:
|
||||
|
||||
@@ -393,8 +393,31 @@ Returns nil for empty or missing properties."
|
||||
(goto-char pos)
|
||||
(org-entry-get nil prop))))
|
||||
(and val (> (length val) 0) val)))
|
||||
(defun spine-shelves ()
|
||||
"Return a list of shelf names (strings), one per level-1 headline.
|
||||
Includes empty shelves (shelves with no books)."
|
||||
(if (not (file-exists-p spine-org-file))
|
||||
(progn
|
||||
(message "spine-shelves: file not found: %s" spine-org-file)
|
||||
nil)
|
||||
(condition-case err
|
||||
(with-temp-buffer
|
||||
(insert-file-contents spine-org-file)
|
||||
(org-mode)
|
||||
(let ((shelves nil))
|
||||
(org-element-map (org-element-parse-buffer 'headline) 'headline
|
||||
(lambda (hl)
|
||||
(when (= (org-element-property :level hl) 1)
|
||||
(push (org-element-property :raw-value hl) shelves))))
|
||||
(nreverse shelves)))
|
||||
(error
|
||||
(message "spine-shelves: failed to parse %s: %s"
|
||||
spine-org-file (error-message-string err))
|
||||
nil))))
|
||||
|
||||
(defun spine-books ()
|
||||
"Return a list of plists, one per top-level headline in `spine-org-file'.
|
||||
"Return a list of plists, one per book in `spine-org-file'.
|
||||
Books are level-2 headlines nested under level-1 shelf headlines.
|
||||
Returns nil if the file is missing or cannot be parsed."
|
||||
(if (not (file-exists-p spine-org-file))
|
||||
(progn
|
||||
@@ -407,11 +430,16 @@ Returns nil if the file is missing or cannot be parsed."
|
||||
(let ((books nil))
|
||||
(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)))
|
||||
(let ((level (org-element-property :level hl)))
|
||||
(when (= level 2)
|
||||
(let* ((pos (org-element-property :begin hl))
|
||||
(parent (org-element-property :parent hl))
|
||||
(shelf (and parent
|
||||
(= (org-element-property :level parent) 1)
|
||||
(org-element-property :raw-value parent))))
|
||||
(when shelf
|
||||
(push (list :id (spine--prop pos "ID")
|
||||
:title (org-element-property :title hl)
|
||||
:status (org-element-property :todo-keyword hl)
|
||||
:author (spine--prop pos "AUTHOR")
|
||||
:isbn (spine--prop pos "ISBN")
|
||||
:cover (spine--prop pos "COVER")
|
||||
@@ -424,8 +452,9 @@ Returns nil if the file is missing or cannot be parsed."
|
||||
:tags (org-element-property :tags hl)
|
||||
:notes (spine--extract-notes
|
||||
(org-element-property :begin hl)
|
||||
(org-element-property :end hl)))
|
||||
books)))))
|
||||
(org-element-property :end hl))
|
||||
:shelf shelf)
|
||||
books)))))))
|
||||
(nreverse books)))
|
||||
(error
|
||||
(message "spine-books: failed to parse %s: %s"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"spine-books returns correct plists for sample-books.org."
|
||||
(let ((books (spine-books)))
|
||||
(should books)
|
||||
(should (= (length books) 5))))
|
||||
(should (= (length books) 6))))
|
||||
|
||||
(ert-deftest spine-books-first-book-has-all-fields ()
|
||||
"Use of Weapons entry has all expected fields."
|
||||
@@ -25,8 +25,8 @@
|
||||
:key (lambda (b) (plist-get b :id))
|
||||
:test #'equal)))
|
||||
(should uow)
|
||||
(should (equal (plist-get uow :title) "Use of Weapons"))
|
||||
(should (equal (plist-get uow :status) "READING"))
|
||||
(should (equal (plist-get uow :title) "READING Use of Weapons"))
|
||||
(should (equal (plist-get uow :shelf) "Fiction"))
|
||||
(should (equal (plist-get uow :author) "Iain M. Banks"))
|
||||
(should (equal (plist-get uow :isbn) "978-0316029193"))
|
||||
(should (equal (plist-get uow :cover) "covers/use-of-weapons.jpg"))
|
||||
@@ -46,8 +46,8 @@
|
||||
:key (lambda (b) (plist-get b :id))
|
||||
:test #'equal)))
|
||||
(should pir)
|
||||
(should (equal (plist-get pir :title) "Piranesi"))
|
||||
(should (equal (plist-get pir :status) "WANT"))
|
||||
(should (equal (plist-get pir :title) "WANT Piranesi"))
|
||||
(should (equal (plist-get pir :shelf) "Fiction"))
|
||||
(should (equal (plist-get pir :author) "Susanna Clarke"))
|
||||
(should-not (plist-get pir :isbn))
|
||||
(should-not (plist-get pir :cover))
|
||||
@@ -63,7 +63,7 @@
|
||||
:key (lambda (b) (plist-get b :id))
|
||||
:test #'equal)))
|
||||
(should aj)
|
||||
(should (equal (plist-get aj :status) "READ"))
|
||||
(should (equal (plist-get aj :shelf) "Science Fiction"))
|
||||
(should (equal (plist-get aj :rating) "5"))
|
||||
(should (= (length (plist-get aj :notes)) 3))))
|
||||
|
||||
@@ -81,3 +81,16 @@
|
||||
"Non-existent file returns nil."
|
||||
(let ((spine-org-file "/tmp/spine-nonexistent-12345.org"))
|
||||
(should-not (spine-books))))
|
||||
|
||||
(ert-deftest spine-shelves-returns-shelf-names ()
|
||||
"spine-shelves returns all level-1 headlines as shelf names."
|
||||
(let ((shelves (spine-shelves)))
|
||||
(should (member "Fiction" shelves))
|
||||
(should (member "Science Fiction" shelves))
|
||||
(should (member "Non-Fiction" shelves))
|
||||
(should (= (length shelves) 3))))
|
||||
|
||||
(ert-deftest spine-shelves-includes-empty-shelves ()
|
||||
"spine-shelves includes empty shelves (shelves with no books)."
|
||||
(let ((shelves (spine-shelves)))
|
||||
(should (member "Non-Fiction" shelves))))
|
||||
|
||||
Reference in New Issue
Block a user