Compare commits

..

10 Commits

13 changed files with 784 additions and 651 deletions

View File

@@ -16,5 +16,5 @@ BEGIN {
name = name " " format_disambiguation name = name " " format_disambiguation
sub("<<disambiguation>>", $4, name) sub("<<disambiguation>>", $4, name)
sub("<<name>>", $3, name) sub("<<name>>", $3, name)
print l, name, $1 print l, name, "0", $1
} }

View File

@@ -4,18 +4,20 @@ BEGIN {
delete local_recordings[0] delete local_recordings[0]
if (file_local_recordings) { if (file_local_recordings) {
while ((getline < file_local_recordings) == 1) while ((getline < file_local_recordings) == 1)
local_recordings[$1] = $2 local_recordings[$1] = 1
close(file_local_recordings) close(file_local_recordings)
} }
} }
{ {
gsub("&", "\\\\&") gsub("&", "\\\\&")
id = $1 parentid = $1
med = $2 id = $2
nr = $3 med = $3
dur = $4 nr = $4
title = $5 dur = $5
artist = $6 title = $6
artist = $7
deco = local_recordings[id] ? $8 : ""
# Parse duration # Parse duration
if (dur) { if (dur) {
dur = int(dur / 1000) dur = int(dur / 1000)
@@ -36,6 +38,7 @@ BEGIN {
sub("<<artist>>", artist, line) sub("<<artist>>", artist, line)
sub("<<duration>>", dur, line) sub("<<duration>>", dur, line)
l = local_recordings[id] ? format_local : "" l = local_recordings[id] ? format_local : ""
c = id == current_id ? format_current : ""
sortk = med" "nr sortk = med" "nr
print sortk, l, line, id ":" local_recordings[id] print sortk, l, c, line, parentid, id ":" deco
} }

View File

@@ -58,5 +58,5 @@ BEGIN {
line_year = year ? format_year : "" line_year = year ? format_year : ""
sub("<<year>>", year, line_year) sub("<<year>>", year, line_year)
sortk = year ? year : 0 sortk = year ? year : 0
print sortk, l, line_type, line_release, line_year, line_sectype, id print sortk, l, line_type, line_release, line_year, line_sectype, "0", id
} }

View File

@@ -9,17 +9,18 @@ BEGIN {
} }
} }
{ {
id = $1 parentid = $1
status = $2 id = $2
year = substr($3, 1, 4) + 0 status = $3
year = substr($4, 1, 4) + 0
year = year == 0 ? "" : year year = year == 0 ? "" : year
covercount = $4 covercount = $5
label = $5 label = $6
trackcnt = $6 trackcnt = $7
media = $7 media = $8
country = $8 country = $9
title = $9 title = $10
artist = $10 artist = $11
switch (status) { switch (status) {
case "Official": line_status = release_official; break case "Official": line_status = release_official; break
case "Promotion": line_status = release_promotion; break case "Promotion": line_status = release_promotion; break
@@ -53,5 +54,5 @@ BEGIN {
sub("<<country>>", country, line) sub("<<country>>", country, line)
sortk = year ? year : 0 sortk = year ? year : 0
l = local_releases[id] ? format_local : "" l = local_releases[id] ? format_local : ""
print sortk, l, line, id ":" local_releases[id] print sortk, l, line, parentid, id ":" local_releases[id]
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,79 +4,6 @@ USER_AGENT="$APP_NAME/$APP_VERSION ($APP_WEBSITE)"
SLEEP_ON_ERROR=1 SLEEP_ON_ERROR=1
export MB_BROWSE_STEPS export MB_BROWSE_STEPS
__mpv_command() {
echo "mpv_command: $*" >>/tmp/foo
printf "{ \"command\": [\"%s\"] }\n" "$1" | $SOCAT - "$MPV_SOCKET"
}
__mpv_command_with_arg() {
echo "mpv_command_1: $*" >>/tmp/foo
printf "{ \"command\": [\"%s\", \"%s\"] }\n" "$1" "$2" | $SOCAT - "$MPV_SOCKET"
}
__mpv_command_with_args2() {
echo "mpv_command_2: $*" >>/tmp/foo
printf "{ \"command\": [\"%s\", \"%s\", \"%s\"] }\n" "$1" "$2" "$3" | $SOCAT - "$MPV_SOCKET"
}
__mpv_get() {
__mpv_command_with_arg "expand-text" "$2" | $JQ -r '.data'
}
mpv_playlist_count() {
__mpv_get '${playlist/count}'
}
mpv_playlist_position() {
__mpv_get '${playlist-pos}'
}
mpv_quit() {
__mpv_command "quit"
}
mpv_start() {
MPV_SOCKET="$(mktemp --suffix=.sock)"
trap 'mpv_quit; rm -f "$MPV_SOCKET"' EXIT INT
$MPV --no-config --no-terminal --input-ipc-server="$MPV_SOCKET" --idle --no-osc --no-input-default-bindings &
}
mpv_play_file() {
__mpv_command_with_arg "loadfile" "$1"
}
mpv_queue_file() {
__mpv_command_with_args2 "loadfile" "$1" "append-play"
}
mpv_play_list() {
__mpv_command_with_arg "loadlist" "$1"
}
mpv_queue_list() {
__mpv_command_with_arg "loadlist" "$1" "append-play"
}
mpv_next() {
__mpv_command "playlist-next"
}
mpv_prev() {
__mpv_command "playlist-prev"
}
mpv_seek_forward() {
__mpv_command_with_arg "seek" "10"
}
mpv_seek_backward() {
__mpv_command_with_arg "seek" "-10"
}
mpv_toggle_pause() {
__mpv_command_with_arg "cycle" "pause"
}
__api_mb() { __api_mb() {
tmpout=$(mktemp) tmpout=$(mktemp)
for _ in $(seq "$MB_MAX_RETRIES"); do for _ in $(seq "$MB_MAX_RETRIES"); do

View File

@@ -13,3 +13,7 @@ info() {
debug() { debug() {
echo "${DBG}DEBUG${OFF} ${INFO}$(date)${OFF}: $*" >/dev/stderr echo "${DBG}DEBUG${OFF} ${INFO}$(date)${OFF}: $*" >/dev/stderr
} }
foo() {
echo "${DBG}DEBUG${OFF} ${INFO} [$$] $(date)${OFF}: $*" >>/tmp/foo
}

25
src/sh/keys.sh Normal file
View File

@@ -0,0 +1,25 @@
KEYS_HALFPAGE_DOWN="${KEYS_HALFPAGE_DOWN:-"ctrl-d"}"
KEYS_HALFPAGE_UP="${KEYS_HALFPAGE_UP:-"ctrl-u"}"
KEYS_BROWSE="${KEYS_BROWSE:-"alt-b"}"
KEYS_OUT="${KEYS_OUT:-"ctrl-h"}"
KEYS_IN="${KEYS_IN:-"ctrl-l"}"
KEYS_SELECT_ARTIST="${KEYS_SELECT_ARTIST:-"ctrl-a"}"
KEYS_FILTER_LOCAL="${KEYS_FILTER_LOCAL:-"alt-l"}"
KEYS_FILTER_PRIMARY="${KEYS_FILTER_PRIMARY:-"alt-1"}"
KEYS_FILTER_SECONDARY="${KEYS_FILTER_SECONDARY:-"alt-2"}"
KEYS_SWITCH_ARTIST_ALBUM="${KEYS_SWITCH_ARTIST_ALBUM:-"tab"}"
KEYS_SWITCH_LOCAL_REMOTE="${KEYS_SWITCH_LOCAL_REMOTE:-"ctrl-/"}"
KEYS_PLAY="${KEYS_PLAY:-"enter"}"
KEYS_QUEUE="${KEYS_QUEUE:-"ctrl-alt-m"}"
KEYS_TOGGLE_PLAY_PAUSE="${KEYS_TOGGLE_PLAY_PAUSE:-"space"}"
KEYS_PLAY_NEXT="${KEYS_PLAY_NEXT:-"l,right"}"
KEYS_PLAY_PREV="${KEYS_PLAY_PREV:-"h,left"}"
KEYS_SEEK_FORWARD="${KEYS_SEEK_FORWARD:-"L,shift-right"}"
KEYS_SEEK_BACKWARD="${KEYS_SEEK_BACKWARD:-"H,shift-left"}"
KEYS_SHOW_PLAYLIST="${KEYS_SHOW_PLAYLIST:-"ctrl-p"}"
export KEYS_HALFPAGE_DOWN KEYS_HALFPAGE_UP KEYS_BROWSE KEYS_OUT KEYS_IN \
KEYS_SELECT_ARTIST KEYS_FILTER_LOCAL KEYS_FILTER_PRIMARY KEYS_FILTER_SECONDARY \
KEYS_SWITCH_ARTIST_ALBUM KEYS_SWITCH_LOCAL_REMOTE KEYS_PLAY KEYS_QUEUE \
KEYS_TOGGLE_PLAY_PAUSE KEYS_PLAY_NEXT KEYS_PLAY_PREV KEYS_SEEK_FORWARD \
KEYS_SEEK_BACKWARD KEYS_SHOW_PLAYLIST

234
src/sh/lists.sh Normal file
View File

@@ -0,0 +1,234 @@
# List release groups of given artist
# argument $1: MB artist id
list_releasegroups() {
name=$(mb_artist "$1" | $JQ -r '.name')
mb_artist_releasegroups "$1" |
$JQ -r '."release-groups"[] | [
.id,
."primary-type",
(."secondary-types" // []|join(";")),
."first-release-date",
.title,
(."artist-credit" | map(([.name, .joinphrase]|join(""))) | join(""))
] | join("\t")' |
awk \
-F "\t" \
-v artist="$name" \
-v file_local_releasegroups="${LOCALDATA_RELEASEGROUPS:-}" \
-v format_release="$RGV_RELEASE" \
-v format_release_w_artist="$RGV_RELEASE_W_ARTIST" \
-v format_year="$RGV_YEAR" \
-v format_album="$FORMAT_TYPE_ALBUM" \
-v format_single="$FORMAT_TYPE_SINGLE" \
-v format_ep="$FORMAT_TYPE_EP" \
-v format_broadcast="$FORMAT_TYPE_BROADCAST" \
-v format_other="$FORMAT_TYPE_OTHER" \
-v format_has_secondary="$FORMAT_TYPE_HAS_SECONDARY" \
-v format_secondary="$FORMAT_TYPE_SECONDARY" \
-v format_compilation="$FORMAT_TYPE_SECONDARY_COMPILATION" \
-v format_soundtrack="$FORMAT_TYPE_SECONDARY_SOUNDTRACK" \
-v format_spokenword="$FORMAT_TYPE_SECONDARY_SPOKENWORD" \
-v format_interview="$FORMAT_TYPE_SECONDARY_INTERVIEW" \
-v format_audiobook="$FORMAT_TYPE_SECONDARY_AUDIOBOOK" \
-v format_audiodrama="$FORMAT_TYPE_SECONDARY_AUDIODRAMA" \
-v format_live="$FORMAT_TYPE_SECONDARY_LIVE" \
-v format_remix="$FORMAT_TYPE_SECONDARY_REMIX" \
-v format_djmix="$FORMAT_TYPE_SECONDARY_DJMIX" \
-v format_mixtape="$FORMAT_TYPE_SECONDARY_MIXTAPE" \
-v format_demo="$FORMAT_TYPE_SECONDARY_DEMO" \
-v format_fieldrec="$FORMAT_TYPE_SECONDARY_FIELDREC" \
-v format_local="$FORMAT_LOCAL" \
"$AWK_RELEASEGROUPS" |
sort -n -r |
cut -d "$(printf '\t')" -f 2- |
column -t -s "$(printf '\t')" |
sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|'
}
# List releases in given relese group
# argument $1: MB release-group id
list_releases() {
title="$(mb_releasegroup "$1" |
$JQ -r '.title')"
artist="$(mb_releasegroup "$1" |
$JQ -r '."artist-credit" | map(([.name, .joinphrase]|join(""))) | join("")')"
mb_releasegroup_releases "$1" |
$JQ -r --arg rid "$1" '."releases"[] | [
$rid,
.id,
.status,
.date,
."cover-art-archive".count,
(."label-info" | map(.label.name) | unique | join(", ")),
(.media | map(."track-count") | add),
(.media | map(.format) | unique | join(", ")),
.country,
.title,
(."artist-credit" | map(([.name, .joinphrase]|join(""))) | join(""))
] | join("\t")' |
awk \
-F "\t" \
-v file_local_releases="${LOCALDATA_RELEASES:-}" \
-v release_official="$FORMAT_STATUS_OFFICIAL" \
-v release_promotion="$FORMAT_STATUS_PROMO" \
-v release_bootleg="$FORMAT_STATUS_BOOTLEG" \
-v release_pseudo="$FORMAT_STATUS_PSEUDO" \
-v release_withdrawn="$FORMAT_STATUS_WITHDRAWN" \
-v release_expunged="$FORMAT_STATUS_EXPUNGED" \
-v release_cancelled="$FORMAT_STATUS_CANCELLED" \
-v release_format="$RV_FORMAT" \
-v release_format_title_artist="$RV_TITLE_ARTIST" \
-v release_format_title="$RV_TITLE" \
-v release_format_artist="$RV_ARTIST" \
-v rg_artist="$artist" \
-v rg_title="$title" \
-v format_local="$FORMAT_LOCAL" \
"$AWK_RELEASES" |
sort -n -r |
cut -d "$(printf '\t')" -f 2- |
column -t -s "$(printf '\t')" |
sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\):\(.*$\)$|\t\1\t\2\t\3|'
}
# List recordings of given release
# argument $1: MB release id
list_recordings() {
deco="$(grep "$1" "$LOCALDATA_RELEASES" | cut -d "$(printf '\t')" -f 2)"
if [ "$deco" ]; then
rectmp=$(mktemp)
$JQ -r '.tracks | keys | join("\n")' "$deco" >"$rectmp"
fi
mb_release "$1" |
$JQ -r \
--arg rid "$1" \
--arg deco "$deco" \
'.media[] |
.position as $pos |
.tracks[] | [
$rid,
.id,
$pos,
.number,
.length,
.recording.title,
(.recording."artist-credit" | map([.name, .joinphrase] | join("")) | join("")),
$deco
] |
join("\t")' |
awk \
-F "\t" \
-v file_local_recordings="${rectmp:-}" \
-v format="$REC_FORMAT" \
-v format_local="$FORMAT_LOCAL" \
"$AWK_RECORDINGS" |
sort -k1,1n -k2,2g |
cut -d "$(printf '\t')" -f 2- |
column -t -s "$(printf '\t')" -R 3,4,7 |
sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\):\(.*$\)$|\t\1\t\2\t\3|'
if [ "${rectmp:-}" ] && [ -f "$rectmp" ]; then
rm -f "$rectmp"
fi
}
# List artists (local)
list_local_artists() {
cat "$LOCALDATA_ARTISTS_VIEW" 2>/dev/null
}
# List release groups (local)
list_local_releasegroups() {
cat "$LOCALDATA_RELEASEGROUPS_VIEW" 2>/dev/null
}
# List releases (local)
list_local_releases() {
cat "$LOCALDATA_RELEASES_VIEW" 2>/dev/null
}
# Generate playlist from MB release ID and path to decoration
# @argument $1: MusicBrainz Release ID
# @argument $2: Path to decoration file
# @argument $3: MusicBrainz Track ID to select (optional)
generate_playlist() {
printf "#EXTM3U\n"
dir="$(dirname "$2")"
mb_release "$1" |
$JQ -r \
--slurpfile decofile "$2" \
--arg base "$dir" \
--arg deco "$2" \
--arg tid "${3:-}" \
'$decofile[].tracks as $filenames |
. |
.id as $rid |
.media[] |
.position as $pos |
.tracks |
if ($tid == "") then . else map(select(.id == $tid)) end |
map({
t: [
$rid,
.id,
$pos,
.number,
.length,
.title,
(."artist-credit" | map([.name, .joinphrase] | join("")) | join("")),
$deco
] | join("\t"),
length: (.length / 1000 | round | tostring),
$pos,
number: .number,
file: $filenames[.id]
}) |
map(if(.number | type == "string" and test("^[0-9]+$")) then .number |= tonumber else . end) |
sort_by([.pos, .number]) |
map("#EXTINF:" + .length + "," + .t + "\n" + $base + "/" + .file)[]'
}
# Generate artist list from JSON
list_artists_from_json() {
cat |
$JQ -r 'map([.artist.id, .artist.type, .name] | join("\t")) | join("\n")' |
awk \
-F "\t" \
-v file_local_artists="${LOCALDATA_ARTISTS:-}" \
-v format_person="$AV_PERSON" \
-v format_group="$AV_GROUP" \
-v format_disambiguation="$AV_DISAMBIGUATION" \
-v format_local="$FORMAT_LOCAL" \
"$AWK_ARTISTS" |
column -t -s "$(printf '\t')" |
sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|'
}
# Generate playlist view
list_playlist() {
count=$(mpv_playlist_count)
[ "$count" -eq 0 ] && return 0
mpvquery=""
for i in $(seq 0 $((count - 1))); do
mpvquery="$mpvquery\${playlist/$i/current}\t\${playlist/$i/title}\n"
done
mpvtmp=$(mktemp)
# Get playlist information from mpv
__mpv_get "$mpvquery" | grep '.' >"$mpvtmp"
# Get MusicBrainz Track ID of current recording
current_id=$(grep "^yes" "$mpvtmp" | cut -d "$(printf '\t')" -f 3)
# Get file to be used in file_local_recordings
rectmp=$(mktemp)
cut -d "$(printf '\t')" -f 3 "$mpvtmp" >"$rectmp"
# Get list
cut -d "$(printf '\t')" -f 2- "$mpvtmp" |
awk \
-F "\t" \
-v file_local_recordings="${rectmp:-}" \
-v format="$REC_FORMAT_NO_NUMBER" \
-v format_current="$FORMAT_CURRENT" \
-v current_id="$current_id" \
"$AWK_RECORDINGS" |
cut -d "$(printf '\t')" -f 2- |
column -t -s "$(printf '\t')" -R 5 |
sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\):\(.*$\)$|\t\1\t\2\t\3|'
rm -f "$rectmp" "$mpvtmp"
}

View File

@@ -150,7 +150,7 @@ load_local() {
"$AWK_ARTISTS" | "$AWK_ARTISTS" |
sort | sort |
column -t -s "$(printf '\t')" | column -t -s "$(printf '\t')" |
sed 's| \+\([0-9a-f-]\+\)$|\t\1|' >"$LOCALDATA_ARTISTS_VIEW" sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_ARTISTS_VIEW"
info "Precomputing releasegroup view" info "Precomputing releasegroup view"
while IFS= read -r rgid; do while IFS= read -r rgid; do
mb_releasegroup "$rgid" | $JQ -r '[ mb_releasegroup "$rgid" | $JQ -r '[
@@ -192,11 +192,12 @@ load_local() {
sort -n -r | sort -n -r |
cut -d "$(printf '\t')" -f 2- | cut -d "$(printf '\t')" -f 2- |
column -t -s "$(printf '\t')" | column -t -s "$(printf '\t')" |
sed 's| \+\([0-9a-f-]\+\)$|\t\1|' >"$LOCALDATA_RELEASEGROUPS_VIEW" sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\)$|\t\1\t\2|' >"$LOCALDATA_RELEASEGROUPS_VIEW"
info "Precomputing release view" info "Precomputing release view"
list_releases | cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES" |
while IFS= read -r rid; do while IFS= read -r rid; do
mb_release "$rid" | $JQ -r '[ mb_release "$rid" | $JQ -r '[
"0",
.id, .id,
.status, .status,
.date, .date,
@@ -228,20 +229,5 @@ load_local() {
sort -n -r | sort -n -r |
cut -d "$(printf '\t')" -f 2- | cut -d "$(printf '\t')" -f 2- |
column -t -s "$(printf '\t')" | column -t -s "$(printf '\t')" |
sed 's| \+\([0-9a-f-]\+\):\(.*$\)$|\t\1\t\2|' >"$LOCALDATA_RELEASES_VIEW" sed 's| \+\([0-9a-f-]\+\) \+\([0-9a-f-]\+\):\(.*$\)$|\t\1\t\2\t\3|' >"$LOCALDATA_RELEASES_VIEW"
}
# List all releases
list_releases() {
cut -d "$(printf '\t')" -f 1 "$LOCALDATA_RELEASES"
}
# List all release groups
list_releasegroups() {
cat "$LOCALDATA_RELEASEGROUPS"
}
# List all album artists
list_artists() {
cat "$LOCALDATA_ARTISTS"
} }

68
src/sh/mpv.sh Normal file
View File

@@ -0,0 +1,68 @@
__mpv_command() {
printf "{ \"command\": [\"%s\"] }\n" "$1" | $SOCAT - "$MPV_SOCKET"
}
__mpv_command_with_arg() {
printf "{ \"command\": [\"%s\", \"%s\"] }\n" "$1" "$2" | $SOCAT - "$MPV_SOCKET"
}
__mpv_command_with_args2() {
printf "{ \"command\": [\"%s\", \"%s\", \"%s\"] }\n" "$1" "$2" "$3" | $SOCAT - "$MPV_SOCKET"
}
__mpv_get() {
__mpv_command_with_arg "expand-text" "$1" | $JQ -r '.data'
}
mpv_playlist_count() {
__mpv_get '${playlist-count}'
}
mpv_playlist_position() {
__mpv_get '${playlist-pos}'
}
mpv_quit() {
__mpv_command "quit"
}
mpv_start() {
MPV_SOCKET="$(mktemp --suffix=.sock)"
trap 'mpv_quit >/dev/null; rm -f "$MPV_SOCKET"' EXIT INT
$MPV --no-config --no-terminal --input-ipc-server="$MPV_SOCKET" --idle --no-osc --no-input-default-bindings &
}
mpv_play_list() {
t=$(mktemp)
cat >"$t"
__mpv_command_with_arg "loadlist" "$t"
rm -f "$t"
}
mpv_queue_list() {
foo "mpv_queue_list"
t=$(mktemp)
cat >"$t"
__mpv_command_with_args2 "loadlist" "$t" "append-play"
rm -f "$t"
}
mpv_next() {
__mpv_command "playlist-next"
}
mpv_prev() {
__mpv_command "playlist-prev"
}
mpv_seek_forward() {
__mpv_command_with_arg "seek" "10"
}
mpv_seek_backward() {
__mpv_command_with_arg "seek" "-10"
}
mpv_toggle_pause() {
__mpv_command_with_arg "cycle" "pause"
}

View File

@@ -11,6 +11,8 @@ CLIFE="\033[38;5;251m"
OFF="\033[m" OFF="\033[m"
FORMAT_LOCAL="${FORMAT_LOCAL:-"🔆"}" FORMAT_LOCAL="${FORMAT_LOCAL:-"🔆"}"
export FORMAT_LOCAL
FORMAT_CURRENT="${FORMAT_CURRENT:-"👉"}"
# Prompts # Prompts
SEARCH_PROMPT=${SEARCH_PROMPT:-"🔎 〉"} SEARCH_PROMPT=${SEARCH_PROMPT:-"🔎 〉"}
@@ -73,3 +75,4 @@ RV_ARTIST="${FAINT}by ${CARTIST}<<artist>>$OFF"
# Recording view # Recording view
REC_FORMAT="${CNOTE}${FAINT}<<med>>\t${CNOTE}<<nr>>$OFF\t${CTITLE}<<title>>\t${CARTIST}<<artist>>\t${CXXX}<<duration>>$OFF" REC_FORMAT="${CNOTE}${FAINT}<<med>>\t${CNOTE}<<nr>>$OFF\t${CTITLE}<<title>>\t${CARTIST}<<artist>>\t${CXXX}<<duration>>$OFF"
REC_FORMAT_NO_NUMBER="${CTITLE}<<title>>\t${CARTIST}<<artist>>\t${CXXX}<<duration>>$OFF"

View File

@@ -1,5 +1,5 @@
if command -v "fzf" >/dev/null; then if command -v "fzf" >/dev/null; then
FZF="fzf --black" FZF="fzf --black --ansi --cycle"
else else
err "Did not find the command-line fuzzy finder fzf." err "Did not find the command-line fuzzy finder fzf."
exit 1 exit 1