#!/usr/bin/env bash
###############################################################################
# bm — Bookmark manager CLI
#
# Usage:
# bm add <url> [name] [category] [tags] — add a bookmark
# bm rm <url|name> — remove a bookmark
# bm ls [category] — list bookmarks
# bm cats — list categories
# bm search <query> — search by name/url/tags
# bm edit <url|name> — edit a bookmark in $EDITOR
###############################################################################
DB="${XDG_DATA_HOME:-$HOME/.local/share}/karbs/bookmarks.db"
sq() { printf '%s' "${1//\'/\'\'}"; }
cmd="${1:-ls}"
shift
case "$cmd" in
add)
url="${1:?usage: bm add <url> [name] [category] [tags]}"
name="${2:-$(printf '%s' "$url" | sed 's|https\?://||; s|^www\.||; s|/$||; s|/.*||')}"
category="${3:-Uncategorized}"
tags="${4:-}"
safe_url=$(sq "$url"); safe_name=$(sq "$name")
safe_cat=$(sq "$category"); safe_tags=$(sq "$tags")
sqlite3 "$DB" "INSERT OR REPLACE INTO bookmarks (name,url,category,tags)
VALUES ('${safe_name}','${safe_url}','${safe_cat}','${safe_tags}');"
echo "Added: [${category}] ${name}"
;;
rm)
term="${1:?usage: bm rm <url|name>}"
safe=$(sq "$term")
sqlite3 "$DB" "DELETE FROM bookmarks WHERE url='${safe}' OR name='${safe}';"
echo "Removed: ${term}"
;;
ls)
filter="${1:-}"
if [ -n "$filter" ]; then
safe=$(sq "$filter")
sqlite3 "$DB" -column -header \
"SELECT id,category,name,url,tags FROM bookmarks WHERE category='${safe}' ORDER BY name;"
else
sqlite3 "$DB" -column -header \
"SELECT id,category,name,url,tags FROM bookmarks ORDER BY category,name;"
fi
;;
cats)
sqlite3 "$DB" "SELECT category, COUNT(*) as count FROM bookmarks GROUP BY category ORDER BY category;"
;;
search)
query="${1:?usage: bm search <query>}"
safe=$(sq "$query")
sqlite3 "$DB" -column -header "
SELECT category, name, url, tags FROM bookmarks
WHERE name LIKE '%${safe}%'
OR url LIKE '%${safe}%'
OR tags LIKE '%${safe}%'
OR category LIKE '%${safe}%'
ORDER BY category, name;"
;;
edit)
term="${1:?usage: bm edit <url|name>}"
safe=$(sq "$term")
row=$(sqlite3 "$DB" -separator $'\t' \
"SELECT id,name,url,category,tags,description FROM bookmarks WHERE url='${safe}' OR name='${safe}' LIMIT 1;")
[ -z "$row" ] && echo "Not found: $term" && exit 1
id=$(cut -f1 <<< "$row")
tmp=$(mktemp /tmp/bm-edit.XXXXXX)
printf '%s' "$row" | awk -F'\t' '{
print "name: " $2
print "url: " $3
print "category: " $4
print "tags: " $5
print "description: " $6
}' > "$tmp"
${EDITOR:-nvim} "$tmp"
name=$(grep '^name:' "$tmp" | sed 's/^name: *//')
url=$(grep '^url:' "$tmp" | sed 's/^url: *//')
cat=$(grep '^category:' "$tmp" | sed 's/^category: *//')
tags=$(grep '^tags:' "$tmp" | sed 's/^tags: *//')
desc=$(grep '^description:' "$tmp" | sed 's/^description: *//')
rm -f "$tmp"
sn=$(sq "$name"); su=$(sq "$url"); sc=$(sq "$cat"); st=$(sq "$tags"); sd=$(sq "$desc")
sqlite3 "$DB" "UPDATE bookmarks SET name='${sn}',url='${su}',category='${sc}',tags='${st}',description='${sd}' WHERE id=${id};"
echo "Updated: [${cat}] ${name}"
;;
*)
echo "Usage: bm <add|rm|ls|cats|search|edit> [args]"
exit 1
;;
esac