#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# SREAD — Browse research library with nnn, open in Zathura
#
# Maintainer: Kris Yotam <krisyotam@pm.me>
# 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