aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-06 15:19:20 -0500
committerCraig Jennings <c@cjennings.net>2026-06-06 15:19:20 -0500
commit25bde86b9edf9c23f14ddc17c8c894c7a8ea83bd (patch)
tree2af23484ee510daba945106af91f67c2d6db38fa /modules
parent6be56495a3e1e91ed2300367352f30ffa5ce3b3f (diff)
downloaddotemacs-25bde86b9edf9c23f14ddc17c8c894c7a8ea83bd.tar.gz
dotemacs-25bde86b9edf9c23f14ddc17c8c894c7a8ea83bd.zip
fix(nov): name EPUB bookmarks "Author, Title" from the filename
In a nov buffer m is bound to bookmark-set. nov's nov-bookmark-make-record names the record after the buffer name, the raw EPUB filename. So bookmarks read like "Frege_ A Guide for the Perplexed - Edward Kanterian.epub". I advise nov-bookmark-make-record to rebuild the name from the record's filename: split on the last " - " into title and author per Calibre's "<Title> - <Author>.epub" naming, restore the colon Calibre sanitized to "_ ", and reorder to "Author, Title". That book becomes "Edward Kanterian, Frege: A Guide for the Perplexed". I pulled the name from the filename rather than the embedded EPUB metadata on purpose. In real books the embedded metadata is the worse copy (truncated titles, authors in "Last, First" sort form, lost punctuation), while the filenames come from Calibre's curated database. A separate task tracks embedding the good metadata back into the files. cj/--nov-clean-title and cj/--nov-bookmark-name-from-file are pure and carry the logic. The advice is a thin wrapper. 10 ERT tests cover colon restoration, the last-separator split, and the no-separator fallback.
Diffstat (limited to 'modules')
-rw-r--r--modules/calibredb-epub-config.el48
1 files changed, 48 insertions, 0 deletions
diff --git a/modules/calibredb-epub-config.el b/modules/calibredb-epub-config.el
index 4243e509..9f09e392 100644
--- a/modules/calibredb-epub-config.el
+++ b/modules/calibredb-epub-config.el
@@ -327,6 +327,54 @@ Try to use the Calibre book id from the parent folder name (for example,
("t" . nov-goto-toc)
("C-c C-b" . cj/nov-jump-to-calibredb)))
+;; ------------------------- Nov bookmark naming -------------------------------
+;; In a nov buffer "m" is bound to `bookmark-set' (above). nov's
+;; `nov-bookmark-make-record' names the record after `(buffer-name)' -- the EPUB
+;; filename, extension and all. Rebuild it as "Author, Title" parsed from the
+;; filename: under Calibre's "<Title> - <Author>.epub" naming the filename is
+;; more complete than the EPUB's embedded metadata (which carries truncated
+;; titles and author-sort "Last, First" forms).
+
+(defun cj/--nov-clean-title (s)
+ "Clean a title or author S parsed from an EPUB filename, or nil when blank.
+Restores a colon where Calibre sanitized \":\" to \"_\" (\"Frege_ A Guide\"
+-> \"Frege: A Guide\"), turns any leftover underscore into a space, and
+collapses runs of whitespace."
+ (when (stringp s)
+ (let* ((colon (replace-regexp-in-string "_ " ": " s))
+ (spaced (replace-regexp-in-string "_" " " colon))
+ (out (string-trim (replace-regexp-in-string "[ \t]+" " " spaced))))
+ (and (not (string-empty-p out)) out))))
+
+(defun cj/--nov-bookmark-name-from-file (path)
+ "Return \"Author, Title\" derived from an EPUB PATH's filename, or nil.
+Splits the filename (sans extension) on its last \" - \" into title and
+author per Calibre's \"<Title> - <Author>\" convention, restoring colons and
+reordering to \"Author, Title\". Falls back to the cleaned whole name when
+there is no \" - \" separator."
+ (when (and (stringp path) (not (string-empty-p path)))
+ (let ((base (file-name-sans-extension (file-name-nondirectory path))))
+ (if (string-match "\\`\\(.+\\) - \\(.+\\)\\'" base)
+ (let ((title (cj/--nov-clean-title (match-string 1 base)))
+ (author (cj/--nov-clean-title (match-string 2 base))))
+ (cond ((and author title) (format "%s, %s" author title))
+ (title title)
+ (author author)
+ (t nil)))
+ (cj/--nov-clean-title base)))))
+
+(defun cj/--nov-bookmark-rename-record (record)
+ "Replace RECORD's bookmark name with \"Author, Title\" from its EPUB filename.
+Advice (:filter-return) on `nov-bookmark-make-record'. RECORD is
+\(NAME . ALIST) carrying a `filename'; left unchanged when no name derives."
+ (let ((name (cj/--nov-bookmark-name-from-file
+ (alist-get 'filename (cdr record)))))
+ (if name (cons name (cdr record)) record)))
+
+(with-eval-after-load 'nov
+ (advice-add 'nov-bookmark-make-record :filter-return
+ #'cj/--nov-bookmark-rename-record))
+
(defun cj/--nov-image-padding-cols (col-width img-px font-width-px)
"Return left-padding columns to center an IMG-PX-wide image in COL-WIDTH cols.
FONT-WIDTH-PX is the column width in pixels; clamped up to 1 so a zero or