#!/usr/bin/env bash set -euo pipefail ############################################################################### # SREAD — Browse research library with nnn, open in Zathura # # Maintainer: Kris Yotam # License: MIT # Created: 2026-02-15 # # Categories: # Papers → ~/research/library/papers/ # Texts → ~/research/library/texts/ # Primary Texts → ~/research/library/primary-texts/ # Books → ~/Calibre Library/ # # Select a category, then browse with nnn. Enter on any file opens in Zathura. ############################################################################### ############################################################################### # config ############################################################################### LIBRARY="$HOME/research/library" CALIBRE="$HOME/Calibre Library" declare -A CATEGORIES=( [papers]="$LIBRARY/papers" [texts]="$LIBRARY/texts" [primary-texts]="$LIBRARY/primary-texts" [books]="$CALIBRE" ) CATEGORY_ORDER=(papers texts primary-texts books) ############################################################################### # helpers ############################################################################### have_cmd() { command -v "$1" >/dev/null 2>&1; } die() { echo "ERROR: $1" >&2; exit 1; } have_cmd fzf || die "fzf not found" have_cmd nnn || die "nnn not found" have_cmd zathura || die "zathura not found" ############################################################################### # ui ############################################################################### print_banner() { local cols cols="$(tput cols 2>/dev/null || echo 80)" if have_cmd figlet; then figlet -w "$cols" -f big "LIBRARY" 2>/dev/null \ || figlet -w "$cols" "LIBRARY" else echo "LIBRARY" fi echo echo " research library" echo " curator: Kris Yotam" echo " script: sread" echo echo " \"A room without books is like a body without a soul.\"" echo " — Marcus Tullius Cicero" echo } ############################################################################### # calibre — flat "Title — Author" list via metadata.db, opened with fzf ############################################################################### browse_calibre() { local db="$CALIBRE/metadata.db" if [ ! -f "$db" ]; then echo "Calibre metadata.db not found" read -rp "Press Enter..." _ return fi # Query all books: title|author|relative-path|format|data-name local entries entries=$(sqlite3 "$db" " SELECT b.title, COALESCE(a.name, 'Unknown'), b.path, COALESCE(d.format, 'pdf'), COALESCE(d.name, b.title) FROM books b LEFT JOIN books_authors_link bal ON bal.book = b.id LEFT JOIN authors a ON a.id = bal.author LEFT JOIN data d ON d.book = b.id WHERE d.format IS NOT NULL ORDER BY b.title " 2>/dev/null) if [ -z "$entries" ]; then echo "No books in Calibre Library" read -rp "Press Enter..." _ return fi # Build display lines and parallel path array local -a lines=() local -a paths=() while IFS='|' read -r title author relpath fmt fname; do [ -z "$title" ] && continue local ext ext=$(echo "$fmt" | tr '[:upper:]' '[:lower:]') lines+=("${title} — ${author}") paths+=("$CALIBRE/$relpath/$fname.$ext") done <<< "$entries" # fzf selection local choice choice=$(printf '%s\n' "${lines[@]}" | fzf --prompt="book> " --reverse) || return # Find matching index local i for i in "${!lines[@]}"; do if [[ "${lines[$i]}" == "$choice" ]]; then local file="${paths[$i]}" if [ -f "$file" ]; then zathura "$file" &>/dev/null & disown else echo "File not found: $(basename "$file")" read -rp "Press Enter..." _ fi return fi done } ############################################################################### # main ############################################################################### main() { clear print_banner while true; do # Select category via fzf local category category="$( printf "%s\n" "${CATEGORY_ORDER[@]}" "quit" | fzf --prompt="section> " --height=8 --reverse )" || exit 0 [[ "$category" == "quit" ]] && exit 0 if [[ "$category" == "books" ]]; then # Calibre: flat fzf list from metadata.db browse_calibre else local target="${CATEGORIES[$category]}" if [[ ! -d "$target" ]]; then echo "Directory not found: $target" read -rp "Press Enter..." _ clear print_banner continue fi # Launch nnn with Zathura opener, hidden files off NNN_OPTS="deQ" \ NNN_OPENER="$HOME/.local/bin/nnn-zathura" \ NNN_FALLBACK_OPENER="${NNN_OPENER:-xdg-open}" \ nnn "$target" fi # Loop back to category selection clear print_banner done } main