#!/bin/sh book_conf="${XDG_CONFIG_HOME:-$HOME/.config}/dbook/dbook.conf" book_data="${XDG_DATA_HOME:-$HOME/.local/share}/dbook" w_menu="wmenu" x_menu="dmenu" ico_dbk=" " ico_url="󰖟 " ico_doc=" " ico_pic=" " ico_vid=" " ico_mus="󰸪 " ico_fil=" " ico_txt=" " ico_dir=" " usage() { cat <<- EOF Usage: dbook [ command ] [ option ] a bookmark manager using dmenu Commands: help show this help cruft insert [ value [ key ] ], inset a new key save [ value [ key ] ], inset but make a cp if it's a file rm [ key ], delete a key Options: -t [ key ], type the vlaue -c [ key ], copy the value to clipboard EOF } note() { : "${1:?}" command -v notify-send > /dev/null && notify-send -- "${ico_dbk} dbook" "$1" } die() { : "${1:?}" note "$1" printf "\033[31;1merr: %b\033[0m\n" "$1" 1>&2 exit "${2:-1}" } warn() { : "${1:?}" note "$1" printf "\033[33;1merr: %b\033[0m\n" "$1" 1>&2 } dep_check() { : "${1:?}" for dep; do command -v "$dep" 1>/dev/null || die "$dep not found, please install it" 127 done unset dep } load_icon() { case "$(file --brief --dereference --mime-type "$1")" in image/*) printf "%s" "$ico_pic" ;; video/*) printf "%s" "$ico_vid" ;; audio/*) printf "%s" "$ico_mus" ;; text/*) printf "%s" "$ico_txt" ;; inode/*) printf "%s" "$ico_dir" ;; application/pdf) printf "%s" "$ico_doc" ;; *'No such file or directory)') if echo "$1" | grep -qxE '^(https?://)?[^/]*\.[A-Za-z0-9]*(/.*)?'; then printf "%s" "$ico_url" else printf "%s" "$ico_fil" fi esac } parse_data() { # usage parse_data [ getval ] line= _key= _value= while read -r line do [ -z "$line" ] && continue _key="${line%%|*}" _value="${line##*|}" # trim leading and trailing white spaces _key="${_key#"${_key%%[![:space:]]*}"}" _key="${_key%"${_key##*[![:space:]]}"}" _value="${_value#"${_value%%[![:space:]]*}"}" _value="${_value%"${_value##*[![:space:]]}"}" case "$_key" in #*) continue esac if [ "$1" = "getval" ]; then if [ "$2" = "$_key" ]; then echo "$_value" return 0 fi else echo "$(load_icon "$_value")" "$_key" fi done < "$book_conf" unset _key _value [ "$1" = "getval" ] && return 1 } rm_data() { # usage rm_data : "${1:?}" cl= line= file= value= old_ifs="$IFS" IFS= while read -r line do cl="$line\n" key="${cl%%|*}" # trim leading and trailing white spaces key=${key#"${key%%[![:space:]]*}"} key=${key%"${key##*[![:space:]]}"} # catch match if [ "$key" = "$1" ]; then value="${cl##*|}" # trim leading and trailing white spaces value="${value#"${value%%[![:space:]]*}"}" value="${value%"${value##*[![:space:]]}"}" # delete if saved case "$value" in "$book_data"*) rm -f "$value" esac continue fi file="${file}${cl}" done < "$book_conf" IFS="$old_ifs" # shellcheck disable=SC2059 printf "$file" > "$book_conf" unset cl line file value old_ifs } clip() { # usage: clip [ file/text ] : "${1:?}" : "${2:?}" if [ -n "$WAYLAND_DISPLAY" ]; then dep_check "wl-copy" [ "$1" = "text" ] && printf "%s" "$2" | wl-copy [ "$1" = "file" ] && printf "file://%s" "$2" | wl-copy -t text/uri-list elif [ -n "$DISPLAY" ]; then dep_check "xclip" [ "$1" = "text" ] && printf "%s" "$2" | xclip -selection clipboard [ "$1" = "file" ] && printf "file://%s" "$2" | xclip -selection clipboard "$1" -t text/uri-list fi note "${1} coppied to clipboard" } set_menu() { if [ -n "$WAYLAND_DISPLAY" ]; then menu="$w_menu" elif [ -n "$DISPLAY" ]; then menu="$x_menu" else die "tty not supported" fi } ip_menu() { printf "" | "$menu" -p "${ico_dbk} ${1:?} " } key_menu() { key="$(parse_data | "$menu" -l 25 -p "${ico_dbk} key ")" || return 1 echo "${key#* }" } ######## # MAIN # ######## key= value= [ -d "$book_data" ] || mkdir -p "$book_data" [ -d "$book_conf" ] || mkdir -p "${book_conf%/*}" set_menu case "$1" in -*) ;; "") ;; *) command="$1" && shift esac case "$command" in insert|save) value="${1:-$(ip_menu "value")}" || exit 1 key="${2:-$(ip_menu "key")}" || exit 1 parse_data getval "$key" > /dev/null && die "key already in use" case "$command" in insert) [ -e "$value" ] && value="$(realpath "$value")" ;; save) [ -d "$value" ] && warn "${value} is a directory, will not be saved" if [ -f "$value" ]; then cp "$value" "$book_data" value="${book_data}/$(basename "$value")" fi ;; esac printf "%s\t|\t%s\n" "$key" "$value" >> "$book_conf" ;; rm) key="${1:-$(key_menu)}" || exit 1 val="$(parse_data getval "$key")" case "$val" in ${book_data}/*) rm "$val";; esac rm_data "$key" ;; "") dep_check "xdg-open" [ -s "$book_conf" ] || die "no bookmarks, try dbook -h" key="${2:-$(key_menu)}" || exit 1 value="$(parse_data getval "$key")" || die "${key}: no such key" case "$1" in -t) if [ -z "$WAYLAND_DISPLAY" ] then dep_check "xdotool" xdotool type --delay 20 "$value" else dep_check "wtype" wtype -d 20 "$value" fi ;; -c) case "$(file --brief --dereference --mime-type "$value")" in text/*) clip text "$(cat "$value")" ;; *'No such file or directory)') clip text "$value" ;; *) clip file "$value" esac ;; "") xdg-open "$value" 2> /dev/null || clip text "$value" ;; *) die "${1}: invalid option" esac ;; help) usage ;; *) die "$command, invalid command" esac