84 lines
3.1 KiB
Bash
84 lines
3.1 KiB
Bash
|
|
#!/bin/sh
|
||
|
|
# fx: automates f()'s "enter a fix" prompt by gathering candidate fixes
|
||
|
|
# from a directory of pluggable sources and letting you pick one (or
|
||
|
|
# auto-picking the top one) before piping it into f's stdin.
|
||
|
|
#
|
||
|
|
# Zero changes to f.sh: this only decides what gets typed at the
|
||
|
|
# "[LEARN] ... Enter a fix" prompt, using the stdin f() already reads.
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# . src/f.sh
|
||
|
|
# . contrib/fx/fx.sh
|
||
|
|
# fx some-command --that --might --fail
|
||
|
|
#
|
||
|
|
# Env vars:
|
||
|
|
# FX_SOURCES_DIR override the sources directory (default: sources.d
|
||
|
|
# next to this script)
|
||
|
|
# FX_AUTO if set, auto-pick the top candidate instead of
|
||
|
|
# showing a picker
|
||
|
|
# FX_NO_PROBE if set, skip fx's own diagnostic run of the command
|
||
|
|
# (cheaper, but the 30-selfdiag.sh source gets no
|
||
|
|
# output to read and will have no opinion)
|
||
|
|
#
|
||
|
|
# Source contract (see sources.d/*.sh):
|
||
|
|
# - any executable file in $FX_SOURCES_DIR
|
||
|
|
# - invoked as: sourcefile "$CMD"
|
||
|
|
# - may read $FX_OUTPUT (captured stdout+stderr of one real attempt,
|
||
|
|
# empty if FX_NO_PROBE was set)
|
||
|
|
# - prints zero or more candidate fix lines to stdout, one per line
|
||
|
|
# - non-zero exit or no output = "no opinion", silently skipped
|
||
|
|
# - filename prefix (NN-name) sets display/consideration order
|
||
|
|
#
|
||
|
|
# Adding a source: drop a new executable file in sources.d/ that follows
|
||
|
|
# the contract above. Nothing else to register or edit.
|
||
|
|
|
||
|
|
# $0 is the enclosing shell's name when this file is *sourced*, not this
|
||
|
|
# file's own path -- ${BASH_SOURCE[0]} is reliable under bash; for other
|
||
|
|
# POSIX shells, set FX_SOURCES_DIR explicitly before sourcing this file.
|
||
|
|
_fx_self="${BASH_SOURCE:-$0}"
|
||
|
|
FX_SOURCES_DIR="${FX_SOURCES_DIR:-$(CDPATH= cd -- "$(dirname -- "$_fx_self")" 2>/dev/null && pwd)/sources.d}"
|
||
|
|
unset _fx_self
|
||
|
|
|
||
|
|
fx() {
|
||
|
|
cmd="$*"
|
||
|
|
|
||
|
|
FX_OUTPUT=""
|
||
|
|
if [ -z "$FX_NO_PROBE" ]; then
|
||
|
|
FX_OUTPUT=$("$@" 2>&1)
|
||
|
|
if [ $? -eq 0 ]; then
|
||
|
|
printf '%s\n' "$FX_OUTPUT"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
export FX_OUTPUT
|
||
|
|
|
||
|
|
candidates=$(
|
||
|
|
for src in "$FX_SOURCES_DIR"/*; do
|
||
|
|
[ -x "$src" ] || continue
|
||
|
|
label=$(basename "$src")
|
||
|
|
"$src" "$cmd" 2>/dev/null | while IFS= read -r line; do
|
||
|
|
[ -n "$line" ] && printf '[%s] %s\n' "$label" "$line"
|
||
|
|
done
|
||
|
|
done | awk -F'] ' '!seen[$2]++'
|
||
|
|
)
|
||
|
|
|
||
|
|
chosen=""
|
||
|
|
if [ -n "$candidates" ]; then
|
||
|
|
if [ -n "$FX_AUTO" ]; then
|
||
|
|
chosen=$(printf '%s\n' "$candidates" | head -1 | sed 's/^\[[^]]*\] //')
|
||
|
|
elif command -v fzf >/dev/null 2>&1; then
|
||
|
|
chosen=$(printf '%s\n' "$candidates" | fzf --prompt="fix for: $cmd > " --height=40% | sed 's/^\[[^]]*\] //')
|
||
|
|
else
|
||
|
|
i=0
|
||
|
|
printf '%s\n' "$candidates" | while IFS= read -r line; do
|
||
|
|
i=$((i+1)); printf '%d) %s\n' "$i" "$line"
|
||
|
|
done
|
||
|
|
printf 'pick a number (blank to type your own): '
|
||
|
|
read -r n
|
||
|
|
[ -n "$n" ] && chosen=$(printf '%s\n' "$candidates" | sed -n "${n}p" | sed 's/^\[[^]]*\] //')
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
printf '%s\n' "$chosen" | f "$@"
|
||
|
|
}
|