add usage info at the top

add profiling
update menu()
This commit is contained in:
Rob Pearce 2022-06-14 17:42:57 +10:00
parent d0991b572b
commit 1a9f7bbf1d
1 changed files with 314 additions and 286 deletions

View File

@ -1,5 +1,17 @@
#!/usr/bin/env /bin/bash #!/usr/bin/env /bin/bash
####################################
# To include this in a script:
#
#. ${HOME}/code/bashtools/bashtools.sh
#if [[ -z $HAVE_BASHTOOLS ]]; then
# echo "ERROR: bashtools not installed download from https://git.nethack.net/rob/bashtools" >/dev/stderr
# exit 1
#fi
#
####################################
export BASHTOOLS_DIR="${HOME}/.bashtools" export BASHTOOLS_DIR="${HOME}/.bashtools"
if [[ $1 == "install" ]]; then if [[ $1 == "install" ]]; then
mkdir -p "$BASHTOOLS_DIR" mkdir -p "$BASHTOOLS_DIR"
@ -213,292 +225,6 @@ function getsysstats() {
DISKFREE_GB=$(echo "$DISKFREE_MB / 1024" | bc ) DISKFREE_GB=$(echo "$DISKFREE_MB / 1024" | bc )
} }
# menu [ -r RETVAR ] [-R NUMRETVAR] [-a] [-c num] [-l v|h ] [-x AB] [ -q "question text" ] [ -d default ] choice1 choice2 ... choiceN [ -D desc1 desc2 ... descN ]
function menu() {
local answer answernum autoselect bestval
local cols choice choiceformat cperrow cwidth default defaultnum desc
local idx layout mode n nchoices ndescs
local question retvar retvarnum thislen thisnum thistext
local x y repfrom repto
local found
autoselect=0
default=""
question="Select an option:"
mode="choices"
layout=v # 'v'ertical or 'h'orizontal
nchoices=0
ndescs=0
cwidth=0
retvar=_SELECTION
cperrow=-1
found=0
while [ $# -ge 1 ]; do
case $1 in
"-a")
autoselect=1
;;
"-c")
shift
cperrow="$1"
;;
"-d")
shift
default="$1"
;;
"-l")
shift
layout="$1"
;;
"-r")
shift
retvar="$1"
;;
"-R")
shift
retvarnum="$1"
;;
"-x")
shift
repfrom="${1:0:1}"
repto="${1:1:1}"
;;
"-q")
shift
question="$1"
;;
"-D")
mode="descriptions"
;;
*)
if [ "$mode" == "choices" ]; then
choice[${nchoices}]="$1"
if [[ ! -z $repfrom && ! -z $repto ]]; then
choice[${nchoices}]=`echo ${choice[$nchoices]} | tr "$repfrom" "$repto"`
fi
thislen=${#1}
if [ $thislen -gt $cwidth ]; then
cwidth=$thislen
fi
nchoices=$((nchoices + 1))
else
desc[${ndescs}]="$1"
# length of this description + length of matching choice
# + 3 for parantheses
thislen=$(( ${#1} + ${#choice[$ndescs]} + 3))
ndescs=$((ndescs + 1))
if [ $thislen -gt $cwidth ]; then
cwidth=$thislen
fi
fi
;;
esac
shift
done
# Validate layout
if [[ ! $layout =~ v|h ]] ; then
cecho "$RED" "ERROR in ask(): invalid layout. Must be 'v'ertical or 'h'orizontal. [$question]"
die
fi
# Validate descriptions
if [ $ndescs -gt $nchoices ]; then
cecho "$RED" "ERROR in ask(): Number of descriptions ($ndescs) is larger than number of choices ($nchoices)! [$question]"
die
fi
# Validate default choice (if given)
if [ -n "$default" ]; then
defaultnum=-1
for n in ${!choice[@]}; do
if [ "${choice[$n]}" == "$default" ]; then
defaultnum=$n
fi
done
if [ $defaultnum -eq -1 ]; then
cecho "$RED" "ERROR in ask(): given default '$default' does not match any choice. [$question]"
echo " choices are:"
for n in ${!choice[@]}; do
echo " ${choice[$n]}"
done
die
fi
fi
# Determine choice width - longest choice + 6 chars
# to account for 'xxx) '
cwidth=$((cwidth + 5))
# Determine maximum amount of choices per row
if [ $cperrow -eq -1 ]; then
if [ $nchoices -le 3 ]; then
cperrow=1
else
cols=$(tput cols)
cperrow=$((cols / cwidth))
fi
fi
# Reduce choices per row to minimise (nchoices % cperrow).
# ie. try to make list of choices as close to a
# grid shape as possible.
#
bestval=$(( cperrow - (nchoices % cperrow) ))
n=$((cperrow - 1))
while [ $n -ge 1 ]; do
thisval=$(( cperrow - (nchoices % n) ))
if [ $thisval -lt $bestval ]; then
bestval=$thisval
cperrow=$n
fi
n=$((n - 1))
done
# Determine how many rows we'll need for a
# vertical layout (ie. counting downwards instead
# of right).
if [ "$layout" == "v" ]; then
choicerows=$((nchoices / cperrow))
if [ $((nchoices % cperrow)) -gt 0 ]; then
choicerows=$((choicerows + 1))
fi
fi
# printf format string
choiceformat="%3s) %-$((cwidth-5))s"
answer=""
# Prompt for a choice until we get a valid answer.
while [ -z "$answer" ]; do
# Display menu
echo -e "$question"
if [ "$layout" == "h" ]; then
for n in ${!choice[@]}; do
if [ -n "${desc[$n]}" ]; then
thistext="${choice[$n]} (${desc[$n]})"
else
thistext="${choice[$n]}"
fi
thisnum=$((n + 1))
if [ "${choice[$n]}" == "${default}" ]; then
thisnum="*$thisnum"
fi
printf "$choiceformat" "$thisnum" "${thistext}"
if [ $(( (n + 1) % $cperrow )) -eq 0 ]; then
printf "\n" # newline
fi
done
else # ie. vertical
n=0
for (( y=1; y<=$choicerows; y++)); do
for (( x=0; x<$cperrow; x++)); do
idx=$((n + (x * choicerows)))
if [ $idx -lt $nchoices ]; then
if [ -n "${desc[$idx]}" ]; then
thistext="${choice[$idx]} (${desc[$idx]})"
else
thistext="${choice[$idx]}"
fi
thisnum=$((idx + 1))
if [ "${choice[$idx]}" == "${default}" ]; then
thisnum="*$thisnum"
fi
printf "$choiceformat" "$thisnum" "${thistext}"
fi
done
n=$((n + 1))
printf "\n" # newline
done
fi
printf "\n"
if [ -n "$default" ]; then
printf " (* denotes default)\n"
fi
printf -- "-> "
# If there's only one answer, just select it. Useful for
# cases where we are basing our list of choices on a dynamic variable.
if [ $autoselect -eq 1 -a $nchoices -eq 1 ]; then
answernum=1
echo "1 (autoselect)" # make it look like we typed it
else
read answernum
fi
if [ -z "$answernum" ]; then
if [ -n "$default" ]; then
answer=$default
info "[defaulting to $answer]"
# populate 'answernum' correctly
for z in ${!choice[@]}; do
if [[ ${choice[$z]} == $answer ]]; then
answernum=$((z + 1))
break
fi
done
else
printf "Invalid response.\n\n"
fi
elif [[ ! $answernum =~ ^[0-9]*$ ]] ; then
# try to match based on name
found=0
shopt -s nocasematch
for z in ${!choice[@]}; do
if [[ ${choice[$z]} =~ $answernum ]]; then
possanswernum[$found]=$((z + 1))
possanswer[$found]=${choice[z]}
found=$((found + 1))
fi
done
if [[ $found -eq 1 ]]; then
selectedposs=${possanswer[0]}
selectedpossnum=${possanswernum[0]}
answerhilite=${selectedposs/${answernum}/${BOLD}${answernum}${PLAIN}${CYAN}}
star=""
if [[ -z $BOLD ]]; then
star="*"
fi
printf "${CYAN}Matched '${star}${answerhilite}${star}'.${PLAIN}\n\n"
answernum="$selectedpossnum"
answer="$selectedposs"
elif [[ $found -gt 1 ]]; then
printf "${RED}Invalid response - '${BOLD}$answernum${PLAIN}${RED}' matches $found answers.${PLAIN}\n\n"
for z in ${!possanswer[@]}; do
echo -e " ${RED}${possanswernum[$z]}) ${possanswer[$z]}"
done
echo -e "${PLAIN}"
else
printf ${RED}"Invalid response.${PLAIN}\n\n"
fi
shopt -u nocasematch
elif [ $answernum -lt 1 -o $answernum -gt $nchoices ]; then
printf "Choice out of range.\n\n"
else
answer=${choice[$((answernum - 1))]}
fi
done
answer="${answer//\'/\\\'}"
eval "$retvar=$'$answer'"
eval "$retvarnum='$answernum'"
return 0
}
function ask() { # [-s == dont echo input] $1 = prompt $default_val $2 = return_variable_name function ask() { # [-s == dont echo input] $1 = prompt $default_val $2 = return_variable_name
local answer prompt default retvar readopts="" local answer prompt default retvar readopts=""
if [[ $1 == "-s" ]]; then if [[ $1 == "-s" ]]; then
@ -514,6 +240,294 @@ function ask() { # [-s == dont echo input] $1 = prompt $default_val $2 = return_
eval "$retvar=\"$answer\"" eval "$retvar=\"$answer\""
} }
# ask [ -r RETVAR ] [-R NUMRETVAR] [-a] [-c num] [-l v|h ] [-x AB] [ -q "question text" ] [ -d default ] choice1 choice2 ... choiceN [ -D desc1 desc2 ... descN ]
function menu() {
local answer answernum autoselect bestval
local cols choice choiceformat cperrow cwidth default defaultnum desc
local idx layout mode n nchoices ndescs
local question retvar retvarnum thislen thisnum thistext
local x y repfrom repto
local found
autoselect=0
default=""
question="Select an option:"
mode="choices"
layout=v # 'v'ertical or 'h'orizontal
nchoices=0
ndescs=0
cwidth=0
retvar=_SELECTION
cperrow=-1
found=0
while [ $# -ge 1 ]; do
case $1 in
"-a")
autoselect=1
;;
"-c")
shift
cperrow="$1"
;;
"-d")
shift
default="$1"
;;
"-l")
shift
layout="$1"
;;
"-r")
shift
retvar="$1"
;;
"-R")
shift
retvarnum="$1"
;;
"-x")
shift
repfrom="${1:0:1}"
repto="${1:1:1}"
;;
"-q")
shift
question="$1"
;;
"-D")
mode="descriptions"
;;
*)
if [ "$mode" == "choices" ]; then
choice[${nchoices}]="$1"
if [[ ! -z $repfrom && ! -z $repto ]]; then
choice[${nchoices}]=`echo ${choice[$nchoices]} | tr "$repfrom" "$repto"`
fi
thislen=${#1}
if [ $thislen -gt $cwidth ]; then
cwidth=$thislen
fi
nchoices=$((nchoices + 1))
else
desc[${ndescs}]="$1"
# length of this description + length of matching choice
# + 3 for parantheses
thislen=$(( ${#1} + ${#choice[$ndescs]} + 3))
ndescs=$((ndescs + 1))
if [ $thislen -gt $cwidth ]; then
cwidth=$thislen
fi
fi
;;
esac
shift
done
# Validate layout
if [[ ! $layout =~ v|h ]] ; then
error "error in myask(): invalid layout. Must be 'v'ertical or 'h'orizontal. [$question]"
die
fi
# Validate descriptions
if [ $ndescs -gt $nchoices ]; then
error "error in myask(): Number of descriptions ($ndescs) is larger than number of choices ($nchoices)! [$question]"
die
fi
# Validate default choice (if given)
if [ -n "$default" ]; then
defaultnum=-1
for n in ${!choice[@]}; do
if [ "${choice[$n]}" == "$default" ]; then
defaultnum=$n
fi
done
if [ $defaultnum -eq -1 ]; then
error "error in myask(): given default '$default' does not match any choice. [$question]"
csecho "$RED" " choices are:"
for n in ${!choice[@]}; do
csecho "$RED" " ${choice[$n]}"
done
die
fi
fi
# Determine choice width - longest choice + 6 chars
# to account for 'xxx) '
cwidth=$((cwidth + 5))
# Determine maximum amount of choices per row
if [ $cperrow -eq -1 ]; then
if [ $nchoices -le 3 ]; then
cperrow=1
else
cols=$(tput cols)
cperrow=$((cols / cwidth))
fi
fi
# Reduce choices per row to minimise (nchoices % cperrow).
# ie. try to make list of choices as close to a
# grid shape as possible.
#
bestval=$(( cperrow - (nchoices % cperrow) ))
n=$((cperrow - 1))
while [ $n -ge 1 ]; do
thisval=$(( cperrow - (nchoices % n) ))
if [ $thisval -lt $bestval ]; then
bestval=$thisval
cperrow=$n
fi
n=$((n - 1))
done
# Determine how many rows we'll need for a
# vertical layout (ie. counting downwards instead
# of right).
if [ "$layout" == "v" ]; then
choicerows=$((nchoices / cperrow))
if [ $((nchoices % cperrow)) -gt 0 ]; then
choicerows=$((choicerows + 1))
fi
fi
# printf format string
choiceformat="${GREEN}${BOLD}%3s${PLAIN}${GREEN}) %-$((cwidth-5))s$PLAIN"
answer=""
# Prompt for a choice until we get a valid answer.
while [ -z "$answer" ]; do
# Display menu
csecho "$GREEN" "^b$question^p"
if [ "$layout" == "h" ]; then
for n in ${!choice[@]}; do
if [ -n "${desc[$n]}" ]; then
thistext="${choice[$n]} (${desc[$n]})"
else
thistext="${choice[$n]}"
fi
thisnum=$((n + 1))
if [ "${choice[$n]}" == "${default}" ]; then
thisnum="*$thisnum"
fi
printf "$choiceformat" "$thisnum" "${thistext}"
if [ $(( (n + 1) % $cperrow )) -eq 0 ]; then
printf "\n" # newline
fi
done
else # ie. vertical
n=0
for (( y=1; y<=$choicerows; y++)); do
for (( x=0; x<$cperrow; x++)); do
idx=$((n + (x * choicerows)))
if [ $idx -lt $nchoices ]; then
if [ -n "${desc[$idx]}" ]; then
thistext="${choice[$idx]} (${desc[$idx]})"
else
thistext="${choice[$idx]}"
fi
thisnum=$((idx + 1))
if [ "${choice[$idx]}" == "${default}" ]; then
thisnum="*$thisnum"
fi
printf "$choiceformat" "$thisnum" "${thistext}"
fi
done
n=$((n + 1))
printf "\n" # newline
done
fi
printf "\n"
if [ -n "$default" ]; then
csecho "$GREEN" " ^i(* denotes default)^p"
fi
csecho -n "$GREEN" "^b-> ^p"
# If there's only one answer, just select it. Useful for
# cases where we are basing our list of choices on a dynamic variable.
if [ $autoselect -eq 1 -a $nchoices -eq 1 ]; then
answernum=1
csecho "$GREEN" "1 (autoselect)" # make it look like we typed it
else
echo -e -n "$GREEN"
read answernum
echo -e -n "$PLAIN"
fi
if [ -z "$answernum" ]; then
if [ -n "$default" ]; then
answer=$default
info "[defaulting to $answer]"
# populate 'answernum' correctly
for z in ${!choice[@]}; do
if [[ ${choice[$z]} == $answer ]]; then
answernum=$((z + 1))
break
fi
done
else
csecho "$RED" "Invalid response.\n"
fi
elif [[ ! $answernum =~ ^[0-9]*$ ]] ; then
# try to match based on name
found=0
shopt -s nocasematch
for z in ${!choice[@]}; do
if [[ ${choice[$z]} =~ $answernum ]]; then
possanswernum[$found]=$((z + 1))
possanswer[$found]=${choice[z]}
found=$((found + 1))
fi
done
if [[ $found -eq 1 ]]; then
selectedposs=${possanswer[0]}
selectedpossnum=${possanswernum[0]}
answerhilite=${selectedposs/${answernum}/^b${answernum}^p}
star=""
if [[ -z $BOLD ]]; then
star="*"
fi
csecho "$CYAN" "Matched '${star}${answerhilite}${star}'.\n"
answernum="$selectedpossnum"
answer="$selectedposs"
elif [[ $found -gt 1 ]]; then
csecho "${RED}" "Invalid response - '^b$answernum^p' matches $found answers.\n"
for z in ${!possanswer[@]}; do
csecho "$RED" " ${possanswernum[$z]}) ${possanswer[$z]}"
done
else
csecho "${RED}" "Invalid response.\n"
fi
shopt -u nocasematch
elif [ $answernum -lt 1 -o $answernum -gt $nchoices ]; then
csecho "$RED" "Choice out of range.\n"
else
answer=${choice[$((answernum - 1))]}
fi
done
answer="${answer//\'/\\\'}"
eval "$retvar=$'$answer'"
eval "$retvarnum='$answernum'"
return 0
}
function checkreqs() { # appname cpus ram_in_gb disk_in_gb function checkreqs() { # appname cpus ram_in_gb disk_in_gb
local rv=0 appname errs x local rv=0 appname errs x
appname="$1" appname="$1"
@ -599,6 +613,20 @@ function dnslookup() { # $1 = a_record_to_look_up
return $rv return $rv
} }
function profile() {
local now str diff
[[ -z $__PROFILE || $__PROFILE -ne 1 ]] && return;
#now=$(date +%Y/%m/%d-%H:%M:%S)
now=$(printf "%0.f" "$(bc <<<"$(gdate +"%s.%N")*1000")")
str="^b${FUNCNAME[1]}()^p ===> ^b$*^p"
if [[ -n $__PROF_PREV ]]; then
diff=$(echo "scale=2; ($now - $__PROF_PREV)" | bc)
str="$str $GREEN($BOLD+$diff ms$PLAIN$GREEN)^p"
fi
cecho -s "${MAGENTA}" "$str" >&2
__PROF_PREV=$now
}
#declare -fx $(bash -c "source $0 &> /dev/null; compgen -A function") #declare -fx $(bash -c "source $0 &> /dev/null; compgen -A function")
declare -fx $(compgen -A function | grep -v ^_) declare -fx $(compgen -A function | grep -v ^_)