|
#!/usr/bin/env bash |
|
|
|
declare plfile='' |
|
|
|
# credit: https://github.com/ppo/bash-colors |
|
if [[ -f "${HOME}/.local/bin/bash-color.sh" ]]; then |
|
source "${HOME}/.local/bin/bash-color.sh" |
|
else |
|
# or copy & paste the `c()` function from https://github.com/ppo/bash-colors/blob/master/bash-colors.sh#L3 |
|
c() { :; } |
|
fi |
|
function die() { echo -e "$(c Ri)ERROR$(c)$(c i): $*.$(c) $(c 0Wdi)exit ...$(c)" >&2; exit 1; } |
|
function cleanfile() { local file="${1:-}"; test -f "${file}" && rm -f -- "${file}"; } |
|
function restoreCursor() { printf "\033[?25h" >&2; } |
|
function cleanupSpinner() { printf '\r\033[K' >&2; restoreCursor; } |
|
function cleanupOnExit() { restoreCursor 2>/dev/null || true; cleanfile "${plfile:-}"; } |
|
|
|
# ensure cursor restoration and tempfile removal on any exit |
|
trap cleanupOnExit EXIT |
|
|
|
function withSpinner() { |
|
local msg="${1:-}"; shift |
|
local __resultvar="$1"; shift |
|
|
|
local spinner=( |
|
"$(c Rs)⣄$(c)" |
|
"$(c Ys)⣆$(c)" |
|
"$(c Gs)⡇$(c)" |
|
"$(c Bs)⠏$(c)" |
|
"$(c Ms)⠋$(c)" |
|
"$(c Ys)⠹$(c)" |
|
"$(c Gs)⢸$(c)" |
|
"$(c Bs)⣰$(c)" |
|
"$(c Ms)⣠$(c)" |
|
) |
|
|
|
local frame=0 |
|
local output |
|
local cmdPid |
|
local pgid='' |
|
local interrupted=0 |
|
local tmpout tmperr |
|
local innerStatus=0 # subshell exit code |
|
|
|
# hide cursor and print prefix |
|
printf "\033[?25l" >&2 |
|
printf "%s" "${msg:+${msg} }" >&2 |
|
|
|
# kill subprocess group on job control + Ctrl-C |
|
local hadMonitoring |
|
hadMonitoring="$(set -o | awk '/monitor/ {print $2}')" # on/off |
|
set -m |
|
trap 'interrupted=1; [[ -n "${pgid}" ]] && kill -TERM -- -"${pgid}" 2>/dev/null' INT |
|
|
|
tmpout="$(mktemp "/tmp/tmpout.XXXXXX")" || return 1 |
|
tmperr="$(mktemp "/tmp/tmperr.XXXXXX")" || { rm -f "${tmpout}"; return 1; } |
|
|
|
# subshell, use the exit code of the command as the exit code of the subshell |
|
output="$( |
|
{ |
|
local cmdStatus=0 |
|
|
|
# execute the command in subshell, redirect stdout -> tmpout, stderr -> tmperr |
|
"$@" >"${tmpout}" 2>"${tmperr}" & |
|
cmdPid=$! |
|
pgid="$(ps -o pgid= "${cmdPid}" | tr -d ' ')" |
|
|
|
# spinner loop |
|
while kill -0 "${cmdPid}" 2>/dev/null && (( interrupted == 0 )); do |
|
printf "\r\033[K%s%b" "${msg:+${msg} }" "${spinner[frame]}" >&2 |
|
(( frame = (frame + 1) % ${#spinner[@]} )) |
|
sleep 0.08 |
|
done |
|
|
|
if (( interrupted )); then |
|
# if ctrl-c happened, kill the process group, and returns 130 |
|
wait "${cmdPid}" 2>/dev/null || true |
|
cmdStatus=130 |
|
else |
|
# if command finished: get the real exit code; using close errexit to avoid exiting on non-zero |
|
set +e |
|
wait "${cmdPid}" |
|
cmdStatus=$? |
|
set -e |
|
fi |
|
|
|
cat "${tmpout}" # print the command output from temp file, it will be captured by $(...) |
|
exit "${cmdStatus}" # exit code of subshell, so the $? outside is $cmdStatus, not 0 |
|
} |
|
)" |
|
|
|
# subshell exit code (== $cmdStatus) |
|
innerStatus=$? |
|
|
|
[[ "${hadMonitoring}" == "off" ]] && set +m |
|
cleanupSpinner # cursor revert to beginning of line + clear line + show cursor |
|
trap - INT # dismiss the INT trap, to avoid impacting the next commands |
|
|
|
# if Ctrl-C happened, just print interrupted message, no stderr shows |
|
if (( innerStatus == 130 )); then |
|
printf "\r\033[K%b✗%b Interrupted!%b\033[K\n" "$(c 0Ri)" "$(c 0Ci)" "$(c)" >&2 |
|
else |
|
# not stopped by Ctrl+C AND exit code != 0, print stderr |
|
if (( innerStatus != 0 )) && [[ -s "${tmperr}" ]]; then |
|
# print the stderr content |
|
printf "%b>> exit code : %b%d%b\n" "$(c Wdi)" "$(c 0Mi)" "${innerStatus}" "$(c)" >&2 |
|
printf "%b>> stderr : %b%s%b\n" "$(c Wdi)" "$(c 0Mi)" "$(cat "${tmperr}")" "$(c)" >&2 |
|
fi |
|
# nothing to be processed for success case |
|
# printf "\r\033[K\033[32m✓\033[0m Done!\033[K\n" >&2 |
|
printf "\r" >&2 |
|
fi |
|
|
|
# cleanup temp files |
|
cleanfile "${tmpout:-}" |
|
cleanfile "${tmperr:-}" |
|
|
|
# put the stdout into the variable provided by caller |
|
printf -v "${__resultvar}" '%s' "${output}" |
|
# return the inner command's exit code |
|
return "${innerStatus}" |
|
} |
|
|
|
function main() { |
|
plfile="$(mktemp "/tmp/ccm-payload.XXXXXX.json")" |
|
|
|
local response='' |
|
local curlExit=128 |
|
if withSpinner '' response \ |
|
curl -sS --http1.1 https://api.openai.com/v1/chat/completions \ |
|
-H "Authorization: Bearer ${OPENAI_API_KEY}" \ |
|
-H "Content-Type: application/json" \ |
|
.... |
|
then |
|
curlExit=0 |
|
else |
|
curlExit=$? |
|
fi |
|
|
|
# if curl error |
|
if (( curlExit != 0 )); then die "curl failed with exit code: $(c 0Mi)${curlExit}$(c)"; fi |
|
|
|
# if error |
|
if jq -e '.error' >/dev/null <<<"${response}"; then |
|
echo "${response}" | jq . >&2 |
|
die "OpenAI API returned an error (see above)" |
|
fi |
|
|
|
# show response message |
|
echo "${response}" | jq -r '.choices[0].message.content' |
|
} |
|
|
|
main "$@" |
|
|
|
# vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh: |