nix/bin/wb
2025-12-02 23:00:51 -06:00

336 lines
9.5 KiB
Bash
Executable file

#!/usr/bin/env bash
thisFile=$(realpath "$0")
DOTFILES="${DOTFILES:-$HOME/dotfiles}"
BOOKMARKS_FILE="${DOTFILES}/secrets/wb.txt"
# Decrypt bookmarks from sops
getBookmarks() {
if [[ -f "${BOOKMARKS_FILE}" ]]; then
# Unencrypted file (for development/testing)
cat "${BOOKMARKS_FILE}"
elif [[ -f "${BOOKMARKS_FILE%.txt}.enc" ]]; then
# Encrypted file
sops --decrypt --input-type binary "${BOOKMARKS_FILE%.txt}.enc" 2>/dev/null
else
echo "Error: No bookmarks file found" >&2
exit 1
fi
}
# Defaults
isCopy=0
editMode=0
q=""
noFuzzy=0
queryMode=0
if ! type fzf >/dev/null 2>&1; then
noFuzzy=1
fi
cleanup() {
rm -f /tmp/.wb
clear-pbcopy 60 &
}
trap cleanup EXIT
bks() {
getBookmarks | awk '!/^[[:space:]]*#/ && !/^[[:space:]]*$/ {print $1}'
}
execute() {
# --------------------------------------------------------------------
# If key == $q, then it's a match, then we do the following:
# a. Copy the "url" to the clipboard (with "pbcopy" in Mac or
# "clip" in Linux). How do we know it's a Mac, we just simply
# check if there's a ~/Music folder in your homedir. If
# there is, it's likely a Mac, else it's Linux.
# b. I have a "clear-pbcopy" script that basically clears the
# clipboard after 20 seconds (for security reason) because
# you don't want to accidentally copy-paste your personal
# links in your email or your chat applications. It is as simple as
# a oneline command:
# "sleep 20; echo "nothing-here" | pbcopy"
# c. If the first argument is "-c" then we mean we just want to
# copy the string to the clipboard, so we just continue
# d. Then we see if the "url" contains "http" or "www", if so
# we open it with ~/bin/browser. You must have your own
# "browser" script that opens your favorite web browser, e.g.
# in Mac, my "~/bin/browser" contains:
# open -a /Applications/Firefox.app $*
# However, if the "url" does not contain http/www,
# e.g. the key and url line is like this:
# mytodofile ~/Documents/mytodofile.txt
# then I open it "open" command line provided by Mac
# d. We are done (exiting)
# --------------------------------------------------------------------
echo ""
# ---------------------- a
echo " copying $url"
echo "$url" | cb
# ---------------------- b
if type clear-pbcopy >/dev/null 2>&1; then
clear-pbcopy 60 &
fi
# ---------------------- c
if [ $isCopy == 1 ]; then
echo " Copied to clipboard"
echo ""
exit
fi
# ---------------------- d
echo " open $url"
x-open $url
# ---------------------- d
echo ""
exit
}
fuzzy() {
# Use awk to parse bookmarks in a single pass (much faster than bash loop)
formatted=$(getBookmarks | awk '
# Skip decorative headers and section dividers
/^#[-=~# ]*$/ || /^# *--/ || /^##+/ { next }
# Accumulate description and extract tags from comments
/^#[^#]/ {
sub(/^# */, "")
desc = desc (desc ? " " : "") $0
# Extract @tags
n = split($0, words, " ")
for (i = 1; i <= n; i++) {
if (words[i] ~ /^@/) {
tags = tags (tags ? " " : "") words[i]
}
}
next
}
# Handle bookmark lines (non-comment, non-empty)
/^[^#[:space:]]/ {
key = $1
# Skip @-prefixed keys
if (key ~ /^@/) { desc = ""; tags = ""; next }
# Get URL (everything after first field)
url = $0
sub(/^[^ \t]+[ \t]+/, "", url)
# Clean description
gsub(/\n/, " ", desc)
if (desc == "") desc = "-"
if (tags == "") tags = "-"
print key "\t" url "\t" desc "\t" tags
desc = ""
tags = ""
}
')
selected_line=$(echo "$formatted" |
fzf -i --with-nth=1 \
--delimiter=$'\t' \
--query="$q" \
--prompt="🔍 Select bookmark: " \
--preview='bash -c "url=\$(echo {} | cut -f2); desc=\$(echo {} | cut -f3); tags=\$(echo {} | cut -f4); echo -e \"\$url\n\nDesc: \$desc\n\nTags: \$tags\""' \
--preview-window=down:12:wrap)
if [[ -n "$selected_line" ]]; then
IFS=$'\t' read -r key url _ <<<"$selected_line"
execute "$url"
else
echo " No selection made"
echo ""
fi
}
help() {
echo ""
echo "Usage: wb [-c] [-e] <key> [key2] [key3] ..."
echo ""
echo "Options:"
echo " -c Copy to clipboard without opening"
echo " -e Edit the bookmarks file"
echo " -nf No Fuzzy Match"
echo " -h Show help"
echo ""
echo "Examples:"
echo " wb cnn # Open the cnn bookmark"
echo " wb -c gmail # Copy Gmail URL without opening"
echo " wb 230 zoom # Open bookmark matching '230.*zoom'"
echo " wb ai sci zoom # Open bookmark matching 'ai.*sci.*zoom'"
echo ""
}
# --------------------------------------------------------------------
# [ 70-char wide ]
# 34567890123456789012345678901234567890123456789012345678901234567890
# 1 2 3 4 5 6 7
# --------------------------------------------------------------------
# --------------------------------------------------------------------
# 0. Setup $thisFile and
# if "wb" is called with no arguments, then
# just open this "wb" file with "e" --> emacs
# You can change "e" to "vi" or other text editor
# --------------------------------------------------------------------
# Parse arguments
args=()
while [ $# -gt 0 ]; do
case "$1" in
-c)
isCopy=1
;;
-e)
editMode=1
;;
-h | --help)
help
exit 0
;;
-nf)
noFuzzy=1
;;
-q)
queryMode=1
;;
-*)
echo "Unknown option: $1"
exit 1
;;
*)
args+=("$1")
;;
esac
shift
done
# Build pattern from multiple arguments (e.g., "wb 230 si te" -> "230.*si.*te")
if [ ${#args[@]} -gt 0 ]; then
q="${args[0]}"
for ((i = 1; i < ${#args[@]}; i++)); do
q="$q.*${args[$i]}"
done
fi
if [[ $editMode == 1 ]]; then
encFile="${BOOKMARKS_FILE%.txt}.enc"
if [[ -f "$encFile" ]]; then
echo "Editing encrypted bookmarks file"
# Decrypt to the .txt file (matches .sops.yaml path_regex), edit, re-encrypt
trap "rm -f '${BOOKMARKS_FILE}'" EXIT
sops --decrypt --input-type binary --output-type binary "$encFile" >"${BOOKMARKS_FILE}"
${EDITOR:-vim} "${BOOKMARKS_FILE}"
sops --encrypt --input-type binary --output-type binary "${BOOKMARKS_FILE}" >"$encFile"
rm -f "${BOOKMARKS_FILE}"
echo "Saved and re-encrypted"
elif [[ -f "${BOOKMARKS_FILE}" ]]; then
echo "Editing ${BOOKMARKS_FILE}"
${EDITOR:-vim} "${BOOKMARKS_FILE}"
else
echo "Error: No bookmarks file found"
exit 1
fi
exit
fi
if [[ $queryMode == 1 ]]; then
if [[ -z "$q" ]]; then
bs=$(bks)
else
bs=$(bks | grep -i "$q")
fi
num=$(echo "$bs" | wc -l | tr -d ' ')
bs=$(echo "$bs" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')
echo "$num bookmarks: ($bs)"
exit
fi
if [[ $q == "" ]]; then
if [[ $noFuzzy == 1 ]]; then
help
else
fuzzy
fi
exit
fi
## --------------------------------------------------------------------
## 1. Find all bookmarks matching the pattern using regex
## Store matches in /tmp/.wb (using awk for speed)
## --------------------------------------------------------------------
tempFile=/tmp/.wb
getBookmarks | awk -v pattern="$q" '
!/^[[:space:]]*#/ && !/^[[:space:]]*$/ && $1 ~ pattern {print}
' >"$tempFile"
hitCount=$(wc -l <"$tempFile" | tr -d ' ')
## --------------------------------------------------------------------
## 2. Handle results based on hit count
## --------------------------------------------------------------------
# No hits found
if [ $hitCount -eq 0 ]; then
echo ""
if [[ $noFuzzy == 1 ]]; then
echo " '$q' not found"
echo " Available bookmarks matching pattern:"
bks | grep -iE "$q" | cat -n
else
fuzzy
fi
echo ""
exit
fi
# Multiple hits - check for exact match first
if [ $hitCount -gt 1 ]; then
exactHit=0
while read -r key url misc; do
if [[ "$key" == "$q" ]]; then
exactHit=1
echo "$key $url" >"$tempFile"
break
fi
done <"$tempFile"
if [ $exactHit -eq 1 ]; then
hitCount=1
fi
fi
# Still multiple hits - show options or use fuzzy
if [ $hitCount -gt 1 ]; then
if [[ $noFuzzy == 1 ]]; then
echo ""
echo " Multiple matches for '$q':"
sort "$tempFile" | while read -r key url misc; do
echo " $key"
done
echo ""
else
fuzzy
fi
exit
fi
# Single hit - execute
if [ $hitCount -eq 1 ]; then
read -r key url _ <"$tempFile"
execute "$url"
fi
## --------------------------------------------------------------------
## 5. Bookmarks are now stored in secrets/wb.txt (or secrets/wb.enc when encrypted)
## To encrypt: sops --encrypt --input-type binary --output-type binary secrets/wb.txt > secrets/wb.enc
## Then remove secrets/wb.txt
## --------------------------------------------------------------------