feat: add spine-books model function with empty-prop nil handling
This commit is contained in:
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
:ID: 5e8d-pir
|
:ID: 5e8d-pir
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
* READING Use of Weapons
|
* READING Use of Weapons :scifi:literary:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:AUTHOR: Iain M. Banks
|
:AUTHOR: Iain M. Banks
|
||||||
:ISBN: 978-0316029193
|
:ISBN: 978-0316029193
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
(require 'simple-httpd)
|
(require 'simple-httpd)
|
||||||
(require 'mustache)
|
(require 'mustache)
|
||||||
(require 'ht)
|
(require 'ht)
|
||||||
|
(require 'cl-lib)
|
||||||
|
|
||||||
;; --- configuration ----------------------------------------------------
|
;; --- configuration ----------------------------------------------------
|
||||||
|
|
||||||
@@ -26,6 +27,9 @@
|
|||||||
nil means localhost only. Set to a string like \"0.0.0.0\" to listen on
|
nil means localhost only. Set to a string like \"0.0.0.0\" to listen on
|
||||||
all interfaces.")
|
all interfaces.")
|
||||||
|
|
||||||
|
(defvar spine-org-file "~/spine/books.org"
|
||||||
|
"Path to the spine.org data file.")
|
||||||
|
|
||||||
(defvar spine-template-dir
|
(defvar spine-template-dir
|
||||||
(expand-file-name "templates/"
|
(expand-file-name "templates/"
|
||||||
(file-name-directory load-file-name))
|
(file-name-directory load-file-name))
|
||||||
@@ -43,6 +47,78 @@ Returns the rendered string."
|
|||||||
(buffer-string))
|
(buffer-string))
|
||||||
context))
|
context))
|
||||||
|
|
||||||
|
(defun spine--h (text)
|
||||||
|
"HTML-escape TEXT."
|
||||||
|
(when text
|
||||||
|
(let ((s text))
|
||||||
|
(setq s (replace-regexp-in-string "&" "&" s))
|
||||||
|
(setq s (replace-regexp-in-string "<" "<" s))
|
||||||
|
(setq s (replace-regexp-in-string ">" ">" s))
|
||||||
|
(setq s (replace-regexp-in-string "\"" """ s))
|
||||||
|
s)))
|
||||||
|
|
||||||
|
(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."
|
||||||
|
(let ((notes nil))
|
||||||
|
(save-excursion
|
||||||
|
(goto-char hl-begin)
|
||||||
|
;; Skip past PROPERTIES and LOGBOOK drawers
|
||||||
|
(while (re-search-forward
|
||||||
|
"^[ \t]*:\\(PROPERTIES\\|LOGBOOK\\):" hl-end t)
|
||||||
|
(re-search-forward "^[ \t]*:END:" hl-end t))
|
||||||
|
;; Collect note lines: "- [YYYY-MM-DD] text"
|
||||||
|
(while (re-search-forward
|
||||||
|
"^- \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)\\] \\(.*\\)"
|
||||||
|
hl-end t)
|
||||||
|
(push (list (match-string 1) (match-string 2)) notes)))
|
||||||
|
(nreverse notes)))
|
||||||
|
|
||||||
|
|
||||||
|
(defun spine--prop (pos prop)
|
||||||
|
"Get Org property PROP at headline position POS.
|
||||||
|
Returns nil for empty or missing properties."
|
||||||
|
(let ((val (save-excursion
|
||||||
|
(goto-char pos)
|
||||||
|
(org-entry-get nil prop))))
|
||||||
|
(and val (> (length val) 0) val)))
|
||||||
|
(defun spine-books ()
|
||||||
|
"Return a list of plists, one per top-level headline in `spine-org-file'.
|
||||||
|
Returns nil if the file is missing or cannot be parsed."
|
||||||
|
(unless (file-exists-p spine-org-file)
|
||||||
|
(message "spine-books: file not found: %s" spine-org-file)
|
||||||
|
(cl-return-from spine-books nil))
|
||||||
|
(condition-case err
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert-file-contents spine-org-file)
|
||||||
|
(org-mode)
|
||||||
|
(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)))
|
||||||
|
(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")
|
||||||
|
:format (spine--prop pos "FORMAT")
|
||||||
|
:rec_by (spine--prop pos "REC_BY")
|
||||||
|
:rec_note (spine--prop pos "REC_NOTE")
|
||||||
|
:rating (spine--prop pos "RATING")
|
||||||
|
:added (spine--prop pos "ADDED")
|
||||||
|
:tags (org-element-property :tags hl)
|
||||||
|
:notes (spine--extract-notes
|
||||||
|
(org-element-property :begin hl)
|
||||||
|
(org-element-property :end hl)))
|
||||||
|
books)))))
|
||||||
|
(nreverse books)))
|
||||||
|
(error
|
||||||
|
(message "spine-books: failed to parse %s: %s"
|
||||||
|
spine-org-file (error-message-string err))
|
||||||
|
nil)))
|
||||||
|
|
||||||
(defvar spine-skip-server-start nil
|
(defvar spine-skip-server-start nil
|
||||||
"When non-nil, do not start the HTTP server.
|
"When non-nil, do not start the HTTP server.
|
||||||
Set by test harnesses that only need the model functions.")
|
Set by test harnesses that only need the model functions.")
|
||||||
|
|||||||
Reference in New Issue
Block a user