bemoji/bemoji
Marty Oehme d44cbf5b74
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Add long-form option parsing
So far we could only add single-letter (POSIX) options: `-e`, `-n` and
so forth. Since we will run out of alphabet at some point, this commit
introduces parsing of multi-letter long-form (gnu) options (plus any
modern application should really support it anyways):
`--echo`, `--noline`.

Additionally, we support supplying long-form options that supply a value
both in the spaced (`--hist-limit 0`) and the equals (`--hist-limit=0`)
forms. Short, long, spaced, equals can be mixed and matched between
freely.

Lastly, we retain the ability to concatenate short options as before
(`-ne` is valid, as is `-P0` for the respective options above).

This should cover all bases and does not complicate the code too much to
keep a coherent overview.

Changed several code samples in the documentation to make
use of short- or long-form options to point out
possibility.
2023-08-26 14:37:44 +02:00

301 lines
8.9 KiB
Bash
Executable file

#!/usr/bin/env bash
bm_version=0.3.0
# Emoji default database location
bm_db_location=${BEMOJI_DB_LOCATION:-"${XDG_DATA_HOME:-$HOME/.local/share}/bemoji"}
# Setting custom emoji list file location:
# BEMOJI_CUSTOM_LIST=/my/location/emojis.txt
# Setting custom recent emoji history:
# BEMOJI_HISTORY_LOCATION=/path/to/my/recents/directory
bm_state_dir="${BEMOJI_HISTORY_LOCATION:-${XDG_STATE_HOME:-$HOME/.local/state}}"
bm_history_file="${bm_state_dir}/bemoji-history.txt"
# Command to run after user chooses an emoji
bm_default_cmd="$BEMOJI_DEFAULT_CMD"
# Newline after echo
bm_echo_newline=${BEMOJI_ECHO_NEWLINE:-true}
# Do not save choices
bm_private_mode=${BEMOJI_PRIVATE_MODE:-false}
# Do not sort results
bm_limit_recent="$BEMOJI_LIMIT_RECENT"
# Report usage
usage() {
echo "Usage: $(basename "$0") [-t | -c | -e] [-f <filepath> ] [-p] [-P] [-D <choices>]" 1>&2
echo
echo "A simple emoji picker. Runs on bemenu/wofi/rofi/dmenu by default."
echo "Invoked without arguments sends the picked emoji to the clipboard."
echo
echo " Command options (can be combined):"
echo " -t, --type Simulate typing the emoji choice with the keyboard."
echo " -c, --clip Send emoji choice to the clipboard. (default)"
echo " -e, --echo Only echo out the picked emoji."
echo ""
echo " Other options:"
echo " -n, --noline Do not print a newline after the picked emoji."
echo " -p, --private Do not save picked emoji to recent history."
echo " -P, --hist-limit <number> Limit number of recent emoji to display."
echo " -D, --download <choice> Choose from default lists to download."
echo " Valid choices: all|none|emoji|math|nerd (multiple choices possible)."
echo " -f, --file <filepath> Use a custom emoji database. Can be a url which will be retrieved."
echo " -v, --version Display current program version and directory configuration."
echo " -h, --help Show this help."
echo
exit "$1"
}
version() {
printf "v%s\ndatabase=%s\nhistory=%s\n" "$bm_version" "$bm_db_location" "$bm_history_file"
exit
}
parse_cli() {
while getopts cD:ef:hnpP:tv-: arg "$@"; do
case "$arg" in
c) bm_cmds+=(_clipper) ;;
D) _opt_set_download_list "${OPTARG}" ;;
e) bm_cmds+=(cat) ;;
f) _opt_set_custom_list "${OPTARG}" ;;
h) usage 0 ;;
n) bm_echo_newline=false ;;
p) bm_private_mode=true ;;
P) _opt_set_hist_limit "${OPTARG}" ;;
t) bm_cmds+=(_typer) ;;
v) version ;;
-)
LONG_OPTARG="${OPTARG#*=}"
case "$OPTARG" in
clip) bm_cmds+=(_clipper) ;;
download=?*) _opt_set_download_list "${LONG_OPTARG}" ;;
download*)
_opt_set_download_list "${*:$OPTIND:1}"
OPTIND=$((OPTIND + 1))
;;
echo) bm_cmds+=(cat) ;;
file=?*) _opt_set_custom_list "${LONG_OPTARG}" ;;
file*)
_opt_set_custom_list "${*:$OPTIND:1}"
OPTIND=$((OPTIND + 1))
;;
help) usage 0 ;;
noline) bm_echo_newline=false ;;
private) bm_private_mode=true ;;
hist-limit=?*) _opt_set_hist_limit "${LONG_OPTARG}" ;;
hist-limit*)
_opt_set_hist_limit "${*:$OPTIND:1}"
OPTIND=$((OPTIND + 1))
;;
type) bm_cmds+=(_typer) ;;
version) version ;;
'') break ;;
*)
echo "Unknown option: ${OPTARG}" 1>&2
usage 1
;;
esac
;;
\?) exit 2 ;;
esac
done
shift $((OPTIND - 1))
OPTIND=1
}
_opt_set_custom_list() {
BEMOJI_CUSTOM_LIST="$1"
}
_opt_set_download_list() {
BEMOJI_DOWNLOAD_LIST="$1"
}
_opt_set_hist_limit() {
bm_limit_recent="$1"
}
prepare_db() {
# Create list directory
if [ ! -d "$bm_db_location" ]; then
mkdir -p "$bm_db_location"
fi
if [ -n "$BEMOJI_DOWNLOAD_LIST" ]; then
# Populate default lists
if echo "$BEMOJI_DOWNLOAD_LIST" | grep -q -e 'none'; then
printf "Not downloading a default emoji list.\n"
return
elif echo "$BEMOJI_DOWNLOAD_LIST" | grep -q -e 'all'; then
dl_default_emoji
dl_math_symbols
dl_nerd_symbols
return
else
if echo "$BEMOJI_DOWNLOAD_LIST" | grep -q -e 'emoji'; then
dl_default_emoji
fi
if echo "$BEMOJI_DOWNLOAD_LIST" | grep -q -e 'math'; then
dl_math_symbols
fi
if echo "$BEMOJI_DOWNLOAD_LIST" | grep -q -e 'nerd'; then
dl_nerd_symbols
fi
fi
fi
if [ -n "$(find "$bm_db_location" -maxdepth 0 -type d -empty 2>/dev/null)" ]; then
dl_default_emoji
fi
}
dl_default_emoji() {
local emojis
emojis=$(curl -sSL "https://unicode.org/Public/emoji/latest/emoji-test.txt")
printf "%s" "$emojis" | sed -ne 's/^.*; fully-qualified.*# \(\S*\) \S* \(.*$\)/\1 \2/gp' >"$bm_db_location/emojis.txt"
printf "Downloaded default emoji set.\n"
}
dl_math_symbols() {
curl -sSL "https://unicode.org/Public/math/latest/MathClassEx-15.txt" |
grep -ve '^#' | cut -d';' -f3,7 | sed -e 's/;/ /' >"$bm_db_location/math.txt"
printf "Downloaded math symbols set.\n"
}
dl_nerd_symbols() {
local nerd all
nerd=$(curl -sSL "https://raw.githubusercontent.com/ryanoasis/nerd-fonts/master/css/nerd-fonts-generated.css")
all+=$(printf "%s" "$nerd" | sed -ne '/\.nf-/p' -e '/\s*[^_]content:/p' | sed -e 'N;s/^\.nf-\(.*\):before.* content: \"\\\(.*\)\";/\\U\2 \1/')
echo -e "$all" > "$bm_db_location/nerdfont.txt"
printf "Downloaded nerdfont symbols set.\n"
}
gather_emojis() {
if [ -n "$BEMOJI_CUSTOM_LIST" ] && [ -f "$BEMOJI_CUSTOM_LIST" ]; then
result=$(cat "$BEMOJI_CUSTOM_LIST")
elif [ -n "$BEMOJI_CUSTOM_LIST" ] && curl -fsSI "$BEMOJI_CUSTOM_LIST" >/dev/null 2>&1; then
result=$(curl -sSL "$BEMOJI_CUSTOM_LIST")
else
result=$(cat "$bm_db_location"/*.txt)
fi
if [ -n "$bm_limit_recent" ] && [ "$bm_limit_recent" -eq 0 ]; then
printf "%s" "$result"
return
fi
printf "%s\n%s" "$(get_most_recent "$bm_limit_recent")" "$result" | cat -n - | sort -uk2 | sort -n | cut -f2-
}
get_most_recent() {
limit=${1}
recent_file="$bm_history_file"
if [ ! -f "$recent_file" ]; then
touch "$recent_file"
fi
# TODO improve this messy line
local result
result=$(sed -e '/^$/d' "$recent_file" |
sort |
uniq -c |
sort -k1rn |
sed -e 's/^\s*//' |
cut -d' ' -f2-)
if [ -z "$limit" ]; then
echo "$result"
else
echo "$result" | head -n "$limit"
fi
}
add_to_recent() {
if [ -z "$1" ]; then return; fi
if [ ! -d "$bm_state_dir" ]; then
mkdir -p "$bm_state_dir"
fi
echo "$1" >>"$bm_history_file"
}
# Set default clipboard util
_clipper() {
if [ -n "$BEMOJI_CLIP_CMD" ]; then
# shellcheck disable=SC2068
${BEMOJI_CLIP_CMD[@]}
elif [ -n "$WAYLAND_DISPLAY" ] && command -v wl-copy >/dev/null 2>&1; then
wl-copy
elif [ -n "$DISPLAY" ] && command -v xclip >/dev/null 2>&1; then
xclip -selection clipboard
elif [ -n "$DISPLAY" ] && command -v xsel >/dev/null 2>&1; then
xsel -b
else
printf "No suitable clipboard tool found."
exit 1
fi
}
# Set default typing uti
_typer() {
if [ -n "$BEMOJI_TYPE_CMD" ]; then
# shellcheck disable=SC2068
${BEMOJI_TYPE_CMD[@]}
return
fi
totype=$(cat -)
if [ -n "$WAYLAND_DISPLAY" ] && command -v wtype >/dev/null 2>&1; then
wtype -s 30 "$totype"
elif [ -n "$DISPLAY" ] && command -v xdotool >/dev/null 2>&1; then
xdotool type --delay 30 "$totype"
else
printf "No suitable typing tool found."
exit 1
fi
}
# Set default picker util
_picker() {
if [ -n "$BEMOJI_PICKER_CMD" ]; then
# shellcheck disable=SC2068
${BEMOJI_PICKER_CMD[@]}
elif command -v bemenu >/dev/null 2>&1; then
bemenu -p 🔍 -i -l 20
elif command -v wofi >/dev/null 2>&1; then
wofi -p 🔍 -i --show dmenu
elif command -v rofi >/dev/null 2>&1; then
rofi -p 🔍 -i -dmenu --kb-custom-1 "Alt+1" --kb-custom-2 "Alt+2"
elif command -v dmenu >/dev/null 2>&1; then
dmenu -p 🔍 -i -l 20
else
printf "No suitable picker tool found."
exit 1
fi
}
parse_cli "$@"
[ -n "$BEMOJI_CUSTOM_LIST" ] || prepare_db
result=$(gather_emojis | _picker)
exit_value="$?"
[ "$bm_private_mode" = true ] || add_to_recent "$result"
result=$(echo "$result" | grep -o '^\S\+' | tr -d '\n')
case "$exit_value" in
1)
exit 1
;;
0)
if [ ${#bm_cmds[@]} -eq 0 ]; then
if [ -n "$bm_default_cmd" ]; then
# shellcheck disable=SC2068
echo "$result" | ${bm_default_cmd[@]}
exit
fi
bm_cmds+=(_clipper)
fi
for cmd in "${bm_cmds[@]}"; do
[ "$bm_echo_newline" = true ] && echo_opts= || echo_opts=-n
echo $echo_opts "$result" | "$cmd"
done
;;
10)
echo "$result" | _clipper
;;
11)
echo "$result" | _typer
;;
esac
exit