336 lines
9.5 KiB
Bash
Executable file
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
|
|
## --------------------------------------------------------------------
|