interact/lib/interact.bash
2021-08-25 18:31:43 +03:00

288 lines
8.5 KiB
Bash

#!/usr/bin/env bash
# _
# | | _ _
# | |____ _| |_ _____ ____ _____ ____ _| |_
# | | _ (_ _) ___ |/ ___|____ |/ ___|_ _)
# | | | | || |_| ____| | / ___ ( (___ | |_
# |_|_| |_| \__)_____)_| \_____|\____) \__)
#
# Bash powered interactive interfaces.
#
# Interact is Bash functions library that implements some
# interactive elements like menus, checkboxes and others.
# You can use Interact instead of Whiptail and Dialog.
# Interact depends only Bash native commands and basic utils.
menu() {
# Interactive menu
#
# Return string SELECTED_ITEM from array.
# Array must be passed as argument.
#
# Variables:
# MENU_PROMPT -- string with prompt message;
# MENU_INDENT -- string with intentation chars;
# MENU_HELP -- string with help message.
local menu_items=("$@") # array of items
local inp= # reset input
local pos=0 # initial cursor position
tput smcup # save screen contents
tput civis # hide cursor
while [[ ! "$inp" =~ [qQ] ]]; do
clear # clear screen
if [ "$MENU_PROMPT" ]
then echo -e "$MENU_PROMPT"
else echo -e "Select item:\n"
fi
# Print menu items
for i in "${!menu_items[@]}"; do
[ "$MENU_INDENT" ] && echo -en "$MENU_INDENT"
# Highlight selected item (invert colors)
if [ $i -eq $pos ]
then echo -e "> \e[7m${menu_items[${i}]}\e[27m"
else echo -e " ${menu_items[${i}]}"
fi
done
# Print help text
if [ ! "$MENU_HELP" ]; then
tput cup $(tput lines) 0
echo -en \
"\e[7mUse HJKL or arrow keys to move, Enter to select, q to quit\e[27m"
tput home
else echo -e "$MENU_HELP"
fi
# Read input (including arrow keys)
inp=
local escape_char=$(printf "\u1b")
read -r -s -n 1 inp # get 1 character
# Read 2 more chars
if [[ $inp == $escape_char ]]; then read -r -s -n 2 inp; fi
case "$inp" in
[[hHjJ]|'[A'|'[D' ) pos=$(( $pos - 1 )) ;; # move up
[]kKlL]|'[B'|'[C' ) pos=$(( $pos + 1 )) ;; # move down
'' ) SELECTED_ITEM="${menu_items[${pos}]}"; break ;; # enter
esac
# Jump to last item if user press "up" when pos=0 and vice versa
if [ $pos -lt 0 ]; then pos=$(( ${#menu_items[@]} - 1 )); fi
if [ $pos -gt $(( ${#menu_items[@]} - 1 )) ]; then pos=0; fi
done
tput rmcup # restore screen contents
tput cnorm # show terminal cursor
}
checklist() {
# Interactive checklist
#
# Return string CHECKED_ITEMS from array.
# Array must be passed as argument.
#
# Variables:
# CHECK_PROMPT -- string with prompt message;
# CHECK_INDENT -- string with intentation chars;
# CHECK_HELP -- string with help message.
local checklist_items=("$@") # array of items
local inp= # reset input
local pos=0 # initial cursor position
tput smcup # save screen contents
tput civis # hide terminal cursor
while [[ ! "$inp" =~ [qQ] ]]; do
clear # clear screen
if [ "$CHECK_PROMPT" ]
then echo -e "$CHECK_PROMPT"
else echo -e "Check items:\n"
fi
# Print menu items
for i in "${!checklist_items[@]}"; do
[ "$CHECK_INDENT" ] && echo -en "$CHECK_INDENT"
if [[ "${checked[@]}" =~ "${checklist_items[${i}]}" ]]
then marker="[x] "
else marker="[ ] "
fi
# Highlight selected item (invert colors)
if [ $i -eq $pos ]
then echo -e "> \e[7m${marker}${checklist_items[${i}]}\e[27m"
else echo -e " ${marker}${checklist_items[${i}]}"
fi
done
# Print help text
if [ ! "$CHECK_HELP" ]; then
tput cup $(tput lines) 0
echo -en \
"\e[7mUse HJKL or arrow keys to move, Enter to check, q to quit\e[27m"
tput home
else echo -e "$CHECK_HELP"
fi
# Read input (including arrow keys)
inp=
local escape_char=$(printf "\u1b")
read -r -s -n 1 inp # get 1 character
# Read 2 more chars
if [[ $inp == $escape_char ]]; then read -r -s -n 2 inp; fi
case "$inp" in
[[hHjJ]|'[A'|'[D' ) pos=$(( $pos - 1 )) ;; # move up
[]kKlL]|'[B'|'[C' ) pos=$(( $pos + 1 )) ;; # move down
'' )
# Check / uncheck items
if [[ "${checked[@]}" =~ "${checklist_items[${pos}]}" ]]
then
# Uncheck item
checked=( "${checked[@]/${checklist_items[${pos}]}}" )
else
# Check new item
checked+=("${checklist_items[${pos}]}")
fi
# Automove cursor
[ $pos -lt $(( ${#checklist_items[@]} - 1 )) ] \
&& pos=$(( $pos + 1 ));;
esac
# Jump to last item if user press "up" when pos=0 and vice versa
if [ $pos -lt 0 ]; then pos=$(( ${#checklist_items[@]} - 1 )); fi
if [ $pos -gt $(( ${#checklist_items[@]} - 1 )) ]; then pos=0; fi
done
# Remove blank items from array
for item in "${checked[@]}"; do
if [[ "$item" != "" ]]; then
CHECKED_ITEMS+=("$item")
fi
done
clear # clear screen
tput rmcup # restore screen contents
tput cnorm # show terminal cursor
}
messagebox() {
# Message box
#
# Variables:
# MSGBOX_TITLE -- title (centered and bold);
# MSGBOX_WIDTH -- terminal width. Default: 75 cols;
# MSGBOX_HELP -- help message.
tput smcup # save screen contents
tput civis # hide terminal cursor
clear # clear screen
local w=""
[ "$MSGBOX_WIDTH" ] && w="$MSGBOX_WIDTH" || w=75
if [ "$MSGBOX_TITLE" ]; then
local hw=$(( ( ( $w - ${#MSGBOX_TITLE} ) - 2 ) / 2 ))
local chars=0
local filler=""
while [ $chars -ne $hw ]; do filler+=" "; let chars++; done
MSGBOX_TITLE=$(tr '[:lower:]' '[:upper:]' <<< ${MSGBOX_TITLE})
echo -e "$filler \e[1m$MSGBOX_TITLE\e[0m $filler"
fi
echo -e "$@" #| fmt --width="$w" # diplay message
if [ "$MSGBOX_HELP" ]; then
echo -e "$MSGBOX_HELP"
else
tput cup $(tput lines) 0
echo -en "\e[7mPress any key to quit\e[27m"
tput home
fi
read -r -s -n 1 inp
case "$inp" in
* ) clear -x; tput rmcup; tput cnorm; return 0;;
esac
}
yesno() {
# Yes/No interactive dialog
#
# Variales:
# ASSUME_YES -- skip dialog, return "Yes";
# YN_INDENT -- indent;
# YN_HELP -- help text.
[ "$ASSUME_YES" ] && return 0
tput smcup # save screen contents
tput civis # hide terminal cursor
local pos=0
local yn=( Yes No )
local answer=
local prompt="$@"
while [[ ! "$inp" =~ [qQ] ]]; do
clear
if [ "$prompt" ]
then echo -e "$prompt"
else echo -e "Continue?\n"
fi
for i in "${!yn[@]}"; do
[ "$YN_INDENT" ] && echo -en "$YN_INDENT"
if [ $i -eq $pos ]
then echo -en "> \e[7m${yn[${i}]}\e[27m\t"
else echo -en " ${yn[${i}]}\t"
fi
done
if [ "$YN_HELP" ]; then
echo -en "$YN_HELP"
else
tput cup $(tput lines) 0
echo -en "\e[7mY - yes, N - no, q to qiut\e[27m"
tput home
fi
# Read input (including arrow keys)
local inp=
local escape_char=$(printf "\u1b")
read -r -s -n 1 inp # get 1 character
# Read 2 more chars
if [[ $inp == $escape_char ]]; then read -r -s -n 2 inp; fi
case "$inp" in
[[hHjJ]|'[A'|'[D' ) pos=$(( $pos - 1 )) ;; # move up
[]kKlL]|'[B'|'[C' ) pos=$(( $pos + 1 )) ;; # move down
[yY] ) answer=Yes; break ;;
[nN] ) answer=No; break ;;
'' ) answer="${yn[${pos}]}"; break ;; # enter
esac
# Jump to last item if user press "up" when pos=0 and vice versa
if [ $pos -lt 0 ]; then pos=$(( ${#yn[@]} - 1 )); fi
if [ $pos -gt $(( ${#yn[@]} - 1 )) ]; then pos=0; fi
done
case $answer in
Yes) answer=0;;
No) answer=1;;
esac
tput rmcup # restore screen
tput cnorm # show cursor
return $answer # return exit code
}