diff --git a/README.md b/README.md index 61b2956..ab0f4cf 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ These things are the majority of what most people would want to keep safe, but e ### Linux -1. Install p7zip, adb, curl, bc, pv and optionally secure-delete. If you're on Debian or Ubuntu, run this command: `sudo apt update; sudo apt install p7zip-full adb curl bc pv secure-delete`. +1. Install p7zip, adb, curl, whiptail, pv and optionally secure-delete. If you're on Debian or Ubuntu, run this command: `sudo apt update; sudo apt install p7zip-full adb curl whiptail pv secure-delete`. 2. [Download](https://github.com/mrrfv/open-android-backup/releases/latest) the Open Android Backup bundle, which contains the script and companion app in one package. You can also grab an experimental build (heavily discouraged) by clicking on [this link](https://github.com/mrrfv/open-android-backup/archive/refs/heads/master.zip) or cloning. 3. Enable [developer options](https://developer.android.com/studio/debug/dev-options#enable) and USB debugging on your device, then run `backup.sh` in a terminal. @@ -59,7 +59,7 @@ These things are the majority of what most people would want to keep safe, but e /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # If you already have Homebrew installed, just run these 2 commands: brew install --cask android-platform-tools -brew install p7zip pv bash +brew install p7zip pv bash dialog ``` 2. Follow the steps 2 and 3 from the install guide for Linux. diff --git a/backup-windows.ps1 b/backup-windows.ps1 index 368acdf..ddc7bea 100644 --- a/backup-windows.ps1 +++ b/backup-windows.ps1 @@ -13,7 +13,7 @@ wsl --shutdown wsl sudo apt update wsl sudo apt dist-upgrade -y Write-Output "Installing dependencies and setting up environment..." -wsl sudo apt install p7zip-full secure-delete curl bc dos2unix pv kdialog '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev -y +wsl sudo apt install p7zip-full secure-delete whiptail curl dos2unix pv kdialog '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev -y Write-Output "Converting files - this may take several minutes..." wsl bash -c "sudo find ./ -name '*.sh' -type f -print0 | sudo xargs -0 dos2unix --" Clear-Host diff --git a/backup.sh b/backup.sh index 9cd13cc..bc52f27 100755 --- a/backup.sh +++ b/backup.sh @@ -4,7 +4,30 @@ set -e # Application metadata - don't change # This is used to download a stable, compatible version of the Android companion app as well as ensure backwards compatibility, # so it should match the tag name in GitHub Releases. -APP_VERSION="v1.0.4" +APP_VERSION="v1.0.7" + +# We use whiptail for showing dialogs. +# Whiptail is used similarly as dialog, but we can't install it on macOS using Homebrew IIRC. +# So we need to fall back to dialog if whiptail is not available. +# Check if whiptail is installed +if command -v whiptail &> /dev/null; then + # Whiptail is installed, no action needed. Do nothing. + : +else + # Check if dialog is installed + if command -v dialog &> /dev/null; then + echo "Whiptail is not installed, but dialog is. Defining whiptail as a function that calls dialog." + # Define whiptail as a function that calls dialog with the same arguments + whiptail() { + dialog "$@" + } + else + # Neither whiptail nor dialog are installed + echo "Error: Neither whiptail nor dialog are installed. Exiting." + exit 1 + fi +fi + SOURCE="${BASH_SOURCE[0]}" while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink @@ -14,9 +37,6 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli done DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" -# Load Inquirer.sh -source "$DIR/inquirer-sh/list_input.sh" -source "$DIR/inquirer-sh/text_input.sh" # --- # Load all functions in ./functions @@ -26,7 +46,7 @@ check_adb_connection if [ ! -v mode ]; then modes=( 'Wired' 'Wireless' ) - list_input "Connection method:" modes mode + select_option_from_list "Choose the connection method. Wireless is experimental and still requires a device connected for pairing." modes[@] mode fi if [ "$mode" = 'Wireless' ]; then @@ -40,9 +60,11 @@ if [ ! -v export_method ]; then cecho "Choose the exporting method." cecho "- Pick 'tar' first, as it is fast and most reliable, but might not work on all devices." cecho "- If the script crashes, pick 'adb' instead, which works on all devices." + cecho "Press Enter to pick your preferred method." + wait_for_enter export_methods=( 'tar' 'adb' ) - list_input "Exporting method:" export_methods export_method + select_option_from_list "Choose the exporting method." export_methods[@] export_method fi clear @@ -53,9 +75,11 @@ if [ ! -v use_hooks ]; then cecho "Choose 'yes' if you have installed your own hooks and would like to use them." cecho "Read README.md for more information." cecho "USING HOOKS IS A SECURITY RISK! THEY HAVE THE EXACT SAME PERMISSIONS AS THIS SCRIPT, AND THUS CAN WIPE YOUR ENTIRE DEVICE OR SEND ALL YOUR DATA TO A REMOTE SERVER. If you are selecting 'yes', please make sure that you have read and understood the code in hooks.sh." + cecho "Press Enter to choose." + wait_for_enter should_i_use_hooks=( 'no' 'yes' ) - list_input "Use hooks:" should_i_use_hooks use_hooks + select_option_from_list "Use hooks? Pick No if unsure or security-conscious." should_i_use_hooks[@] use_hooks fi clear @@ -80,9 +104,11 @@ then cecho "The options below allow you to securely erase this data, making it harder for law enforcement and other adversaries to view your files." cecho "Your choice will also apply to cleanups, i.e. if the script has previously crashed without removing the files." cecho "Fast is considered insecure and can only be recommended on encrypted disks. Slow takes more time than the former, and it's safe enough for most people (2 passes). Extra Slow is only recommended for the paranoid (Gutmann method)." + cecho "Press Enter to pick your data erase mode." + wait_for_enter data_erase_choices=( "Fast" "Slow" "Extra Slow" ) - list_input "Data Erase Mode:" data_erase_choices data_erase_choice + select_option_from_list "Choose the Data Erase Mode." data_erase_choices[@] data_erase_choice clear fi @@ -93,7 +119,7 @@ fi if [ ! -v selected_action ]; then actions=( 'Backup' 'Restore' ) - list_input "What do you want to do?" actions selected_action + select_option_from_list "What do you want to do?" actions[@] selected_action fi # The companion app is required regardless of whether we're backing up the device or not, diff --git a/functions/backup_func.sh b/functions/backup_func.sh index cbf3d89..0de9b96 100644 --- a/functions/backup_func.sh +++ b/functions/backup_func.sh @@ -4,8 +4,6 @@ function backup_func() { while true; do if [ ! -v archive_path ]; then - echo "Note: Backups will first be made on the drive this script is located in, and then will be copied to the specified location." - # Check if we're running on Windows. # If we are, then we will open a file chooser instead of asking the user for the file path thru CLI # due to compatibility issues. @@ -17,7 +15,11 @@ function backup_func() { wait_for_enter archive_path=$(kdialog --getexistingdirectory /mnt/c 2>/dev/null | tail -n 1 | sed 's/\r$//' || true) else - text_input "Please enter the backup location. Enter '.' for the current working directory." archive_path "." + get_text_input \ + "Please enter the backup location. Enter '.' for the current working directory. + Note: Backups will first be made on the drive this script is located in, and then will be copied to the specified location." \ + archive_path \ + "." fi fi diff --git a/functions/helper.sh b/functions/helper.sh index ba69c43..901a592 100644 --- a/functions/helper.sh +++ b/functions/helper.sh @@ -71,11 +71,83 @@ function install_companion_app() { ) # Grant permissions for permission in "${permissions[@]}"; do - adb shell pm grant mrrfv.backup.companion "$permission" || cecho "Couldn't assign permission $permission to the companion app - this is not a fatal error, and you will just have to allow this permission in the app." 1>&2 + adb shell pm grant mrrfv.backup.companion "$permission" || cecho "Couldn't assign permission $permission to the companion app - this is not a fatal error, and you will just have to allow this permission in the app." 1>&2 done fi } +# A function that takes a prompt, an array of options, and a result variable as arguments +# and uses whiptail to display a menu for selecting an option +# The selected option is stored in the result variable +# If no option is selected or an error occurs, the function exits with an error message +function select_option_from_list() { + # Check if the number of arguments is 3 + if [[ $# -ne 3 ]]; then + echo "Usage: select_option_from_list prompt options[@] result_var" + exit 1 + fi + + # Assign the arguments to local variables + local prompt="$1" + local options=("${!2}") # Use indirect expansion to get the array from the second argument + local result_var="$3" + + # Check if the options array is empty + if [[ ${#options[@]} -eq 0 ]]; then + echo "No options provided. Exiting." + exit 1 + fi + + # Build an array of whiptail options from the options array + local whiptail_options=() + for ((i=0; i<${#options[@]}; i++)); do + whiptail_options+=("$i" "${options[$i]}") + done + + # Use whiptail to display a menu and get the selected index + local selected_index=$(whiptail --title "Select an option" --menu "$prompt" $LINES $COLUMNS $(( $LINES - 8 )) "${whiptail_options[@]}" 3>&1 1>&2 2>&3) + + # Check if whiptail exited with a non-zero status or if no option was selected + if [[ $? -ne 0 || -z "$selected_index" ]]; then + echo "No option selected or whiptail error. Exiting." + exit 1 + fi + + # Get the selected option from the options array using the selected index + local selected_option="${options[$selected_index]}" + + # Use indirect assignment to store the selected option in the result variable + eval $result_var="'$selected_option'" +} + + +function get_text_input() { + if [[ $# -ne 2 ]]; then + echo "Invalid usage. Usage: get_text_input prompt result_var [default_text]" + exit 1 + fi + + local prompt="$1" + local result_var="$2" + local default_text="$3" + + while true; do + local text_input=$(whiptail --title "$prompt" --inputbox "" $LINES $COLUMNS "$default_text" 3>&1 1>&2 2>&3) + + if [[ $? -ne 0 ]]; then + echo "No text entered or whiptail error. Exiting." + exit 1 + fi + + if [[ -z "$text_input" ]]; then + whiptail --title "Error" --msgbox "Text cannot be empty. Please enter some text." $LINES $COLUMNS + else + eval $result_var="'$text_input'" + break + fi + done +} + function remove_backup_tmp() { # only run if backup-tmp exists if [ -d backup-tmp ]; then diff --git a/functions/restore_func.sh b/functions/restore_func.sh index fe94fc7..526dcb8 100644 --- a/functions/restore_func.sh +++ b/functions/restore_func.sh @@ -15,7 +15,7 @@ function restore_func() { archive_path=$(kdialog --getopenfilename /mnt/c 2>/dev/null | tail -n 1 | sed 's/\r$//' || true) echo "$archive_path" else - text_input "Please provide the location of the backup archive to restore (drag-n-drop):" archive_path + get_text_input "Please provide the location of the backup archive to restore (drag-n-drop):" archive_path fi fi diff --git a/functions/wireless_connection.sh b/functions/wireless_connection.sh index d084978..1b4cb68 100644 --- a/functions/wireless_connection.sh +++ b/functions/wireless_connection.sh @@ -18,8 +18,8 @@ function wireless_connection() { if (( android_version > 10 )); then cecho "Running on Android 11 or higher - automatic wireless connections are not supported." cecho "Please open the settings app on your device, and search for 'Wireless debugging'. Enable the option, press 'Pair device with pairing code', and enter the IP address and port of your device below:" - # Bug: inquirer.sh's text_input doesn't support colons on windows - #text_input "Device IP & Port:" device_ip_port "$device_ip" + # TODO: use get_text_input instead of read + #get_text_input "Device IP & Port:" device_ip_port "$device_ip" read -p "Pairing IP address & Port: " device_ip_port cecho "Pairing device..." adb pair "$device_ip_port" diff --git a/inquirer-sh/LICENSE b/inquirer-sh/LICENSE deleted file mode 100644 index e35d995..0000000 --- a/inquirer-sh/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/inquirer-sh/checkbox_input.sh b/inquirer-sh/checkbox_input.sh deleted file mode 100644 index 7df833c..0000000 --- a/inquirer-sh/checkbox_input.sh +++ /dev/null @@ -1,374 +0,0 @@ -#!/bin/bash - - -# store the current set options -OLD_SET=$- -set -e - -arrow="$(echo -e '\xe2\x9d\xaf')" -checked="$(echo -e '\xe2\x97\x89')" -unchecked="$(echo -e '\xe2\x97\xaf')" - -black="$(tput setaf 0)" -red="$(tput setaf 1)" -green="$(tput setaf 2)" -yellow="$(tput setaf 3)" -blue="$(tput setaf 4)" -magenta="$(tput setaf 5)" -cyan="$(tput setaf 6)" -white="$(tput setaf 7)" -bold="$(tput bold)" -normal="$(tput sgr0)" -dim=$'\e[2m' - -print() { - echo "$1" - tput el -} - -join() { - local IFS=$'\n' - local _join_list - eval _join_list=( '"${'${1}'[@]}"' ) - local first=true - for item in ${_join_list[@]}; do - if [ "$first" = true ]; then - printf "%s" "$item" - first=false - else - printf "${2-, }%s" "$item" - fi - done -} - -function gen_env_from_options() { - local IFS=$'\n' - local _indices - local _env_names - local _checkbox_selected - eval _indices=( '"${'${1}'[@]}"' ) - eval _env_names=( '"${'${2}'[@]}"' ) - - for i in $(gen_index ${#_env_names[@]}); do - _checkbox_selected[$i]=false - done - - for i in ${_indices[@]}; do - _checkbox_selected[$i]=true - done - - for i in $(gen_index ${#_env_names[@]}); do - printf "%s=%s\n" "${_env_names[$i]}" "${_checkbox_selected[$i]}" - done -} - -on_default() { - true; -} - -on_keypress() { - local OLD_IFS - local IFS - local key - OLD_IFS=$IFS - local on_up=${1:-on_default} - local on_down=${2:-on_default} - local on_space=${3:-on_default} - local on_enter=${4:-on_default} - local on_left=${5:-on_default} - local on_right=${6:-on_default} - local on_ascii=${7:-on_default} - local on_backspace=${8:-on_default} - _break_keypress=false - while IFS="" read -rsn1 key; do - case "$key" in - $'\x1b') - read -rsn1 key - if [[ "$key" == "[" ]]; then - read -rsn1 key - case "$key" in - 'A') eval $on_up;; - 'B') eval $on_down;; - 'D') eval $on_left;; - 'C') eval $on_right;; - esac - fi - ;; - ' ') eval $on_space ' ';; - [a-z0-9A-Z\!\#\$\&\+\,\-\.\/\;\=\?\@\[\]\^\_\{\}\~]) eval $on_ascii $key;; - $'\x7f') eval $on_backspace $key;; - '') eval $on_enter $key;; - esac - if [ $_break_keypress = true ]; then - break - fi - done - IFS=$OLD_IFS -} - -gen_index() { - local k=$1 - local l=0 - if [ $k -gt 0 ]; then - for l in $(seq $k) - do - echo "$l-1" | bc - done - fi -} - -cleanup() { - # Reset character attributes, make cursor visible, and restore - # previous screen contents (if possible). - tput sgr0 - tput cnorm - stty echo - - # Restore `set e` option to its orignal value - if [[ $OLD_SET =~ e ]] - then set -e - else set +e - fi -} - -control_c() { - cleanup - exit $? -} - -select_indices() { - local _select_list - local _select_indices - local _select_selected=() - eval _select_list=( '"${'${1}'[@]}"' ) - eval _select_indices=( '"${'${2}'[@]}"' ) - local _select_var_name=$3 - eval $_select_var_name\=\(\) - for i in $(gen_index ${#_select_indices[@]}); do - eval $_select_var_name\+\=\(\""${_select_list[${_select_indices[$i]}]}"\"\) - done -} - - - - - -on_checkbox_input_up() { - remove_checkbox_instructions - tput cub "$(tput cols)" - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf " ${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf " ${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi - tput el - - if [ $_current_index = 0 ]; then - _current_index=$((${#_checkbox_list[@]}-1)) - tput cud $((${#_checkbox_list[@]}-1)) - tput cub "$(tput cols)" - else - _current_index=$((_current_index-1)) - - tput cuu1 - tput cub "$(tput cols)" - tput el - fi - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi -} - -on_checkbox_input_down() { - remove_checkbox_instructions - tput cub "$(tput cols)" - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf " ${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf " ${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi - - tput el - - if [ $_current_index = $((${#_checkbox_list[@]}-1)) ]; then - _current_index=0 - tput cuu $((${#_checkbox_list[@]}-1)) - tput cub "$(tput cols)" - else - _current_index=$((_current_index+1)) - tput cud1 - tput cub "$(tput cols)" - tput el - fi - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi -} - -on_checkbox_input_enter() { - local OLD_IFS - OLD_IFS=$IFS - _checkbox_selected_indices=() - _checkbox_selected_options=() - IFS=$'\n' - - for i in $(gen_index ${#_checkbox_list[@]}); do - if [ "${_checkbox_selected[$i]}" = true ]; then - _checkbox_selected_indices+=($i) - _checkbox_selected_options+=("${_checkbox_list[$i]}") - fi - done - - tput cud $((${#_checkbox_list[@]}-${_current_index})) - tput cub "$(tput cols)" - - for i in $(seq $((${#_checkbox_list[@]}+1))); do - tput el1 - tput el - tput cuu1 - done - tput cub "$(tput cols)" - - tput cuf $((${#prompt}+3)) - printf "${cyan}$(join _checkbox_selected_options)${normal}" - tput el - - tput cud1 - tput cub "$(tput cols)" - tput el - - _break_keypress=true - IFS=$OLD_IFS -} - -on_checkbox_input_space() { - remove_checkbox_instructions - tput cub "$(tput cols)" - tput el - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - _checkbox_selected[$_current_index]=false - else - _checkbox_selected[$_current_index]=true - fi - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi -} - -remove_checkbox_instructions() { - if [ $_first_keystroke = true ]; then - tput cuu $((${_current_index}+1)) - tput cub "$(tput cols)" - tput cuf $((${#prompt}+3)) - tput el - tput cud $((${_current_index}+1)) - _first_keystroke=false - fi -} - -# for vim movements -on_checkbox_input_ascii() { - local key=$1 - case $key in - "j" ) on_checkbox_input_down;; - "k" ) on_checkbox_input_up;; - esac -} - -_checkbox_input() { - local i - local j - prompt=$1 - eval _checkbox_list=( '"${'${2}'[@]}"' ) - _current_index=0 - _first_keystroke=true - - trap control_c SIGINT EXIT - - stty -echo - tput civis - - print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Press to select, to finalize)${normal}" - - for i in $(gen_index ${#_checkbox_list[@]}); do - _checkbox_selected[$i]=false - done - - if [ -n "$3" ]; then - eval _selected_indices=( '"${'${3}'[@]}"' ) - for i in ${_selected_indices[@]}; do - _checkbox_selected[$i]=true - done - fi - - for i in $(gen_index ${#_checkbox_list[@]}); do - tput cub "$(tput cols)" - if [ $i = 0 ]; then - if [ "${_checkbox_selected[$i]}" = true ]; then - print "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$i]} ${normal}" - else - print "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$i]} ${normal}" - fi - else - if [ "${_checkbox_selected[$i]}" = true ]; then - print " ${green}${checked}${normal} ${_checkbox_list[$i]} ${normal}" - else - print " ${unchecked} ${_checkbox_list[$i]} ${normal}" - fi - fi - tput el - done - - for j in $(gen_index ${#_checkbox_list[@]}); do - tput cuu1 - done - - on_keypress on_checkbox_input_up on_checkbox_input_down on_checkbox_input_space on_checkbox_input_enter on_default on_default on_checkbox_input_ascii -} - -checkbox_input() { - _checkbox_input "$1" "$2" - _checkbox_input_output_var_name=$3 - select_indices _checkbox_list _checkbox_selected_indices $_checkbox_input_output_var_name - - unset _checkbox_list - unset _break_keypress - unset _first_keystroke - unset _current_index - unset _checkbox_input_output_var_name - unset _checkbox_selected_indices - unset _checkbox_selected_options - - cleanup -} - -checkbox_input_indices() { - _checkbox_input "$1" "$2" "$3" - _checkbox_input_output_var_name=$3 - - eval $_checkbox_input_output_var_name\=\(\) - for i in $(gen_index ${#_checkbox_selected_indices[@]}); do - eval $_checkbox_input_output_var_name\+\=\(${_checkbox_selected_indices[$i]}\) - done - - unset _checkbox_list - unset _break_keypress - unset _first_keystroke - unset _current_index - unset _checkbox_input_output_var_name - unset _checkbox_selected_indices - unset _checkbox_selected_options - - cleanup -} diff --git a/inquirer-sh/inquirer.sh b/inquirer-sh/inquirer.sh deleted file mode 100644 index f0b1c6d..0000000 --- a/inquirer-sh/inquirer.sh +++ /dev/null @@ -1,641 +0,0 @@ -#!/bin/bash - - -# store the current set options -OLD_SET=$- -set -e - -arrow="$(echo -e '\xe2\x9d\xaf')" -checked="$(echo -e '\xe2\x97\x89')" -unchecked="$(echo -e '\xe2\x97\xaf')" - -black="$(tput setaf 0)" -red="$(tput setaf 1)" -green="$(tput setaf 2)" -yellow="$(tput setaf 3)" -blue="$(tput setaf 4)" -magenta="$(tput setaf 5)" -cyan="$(tput setaf 6)" -white="$(tput setaf 7)" -bold="$(tput bold)" -normal="$(tput sgr0)" -dim=$'\e[2m' - -print() { - echo "$1" - tput el -} - -join() { - local IFS=$'\n' - local _join_list - eval _join_list=( '"${'${1}'[@]}"' ) - local first=true - for item in ${_join_list[@]}; do - if [ "$first" = true ]; then - printf "%s" "$item" - first=false - else - printf "${2-, }%s" "$item" - fi - done -} - -function gen_env_from_options() { - local IFS=$'\n' - local _indices - local _env_names - local _checkbox_selected - eval _indices=( '"${'${1}'[@]}"' ) - eval _env_names=( '"${'${2}'[@]}"' ) - - for i in $(gen_index ${#_env_names[@]}); do - _checkbox_selected[$i]=false - done - - for i in ${_indices[@]}; do - _checkbox_selected[$i]=true - done - - for i in $(gen_index ${#_env_names[@]}); do - printf "%s=%s\n" "${_env_names[$i]}" "${_checkbox_selected[$i]}" - done -} - -on_default() { - true; -} - -on_keypress() { - local OLD_IFS - local IFS - local key - OLD_IFS=$IFS - local on_up=${1:-on_default} - local on_down=${2:-on_default} - local on_space=${3:-on_default} - local on_enter=${4:-on_default} - local on_left=${5:-on_default} - local on_right=${6:-on_default} - local on_ascii=${7:-on_default} - local on_backspace=${8:-on_default} - _break_keypress=false - while IFS="" read -rsn1 key; do - case "$key" in - $'\x1b') - read -rsn1 key - if [[ "$key" == "[" ]]; then - read -rsn1 key - case "$key" in - 'A') eval $on_up;; - 'B') eval $on_down;; - 'D') eval $on_left;; - 'C') eval $on_right;; - esac - fi - ;; - ' ') eval $on_space ' ';; - [a-z0-9A-Z\!\#\$\&\+\,\-\.\/\;\=\?\@\[\]\^\_\{\}\~]) eval $on_ascii $key;; - $'\x7f') eval $on_backspace $key;; - '') eval $on_enter $key;; - esac - if [ $_break_keypress = true ]; then - break - fi - done - IFS=$OLD_IFS -} - -gen_index() { - local k=$1 - local l=0 - if [ $k -gt 0 ]; then - for l in $(seq $k) - do - echo "$l-1" | bc - done - fi -} - -cleanup() { - # Reset character attributes, make cursor visible, and restore - # previous screen contents (if possible). - tput sgr0 - tput cnorm - stty echo - - # Restore `set e` option to its orignal value - if [[ $OLD_SET =~ e ]] - then set -e - else set +e - fi -} - -control_c() { - cleanup - exit $? -} - -select_indices() { - local _select_list - local _select_indices - local _select_selected=() - eval _select_list=( '"${'${1}'[@]}"' ) - eval _select_indices=( '"${'${2}'[@]}"' ) - local _select_var_name=$3 - eval $_select_var_name\=\(\) - for i in $(gen_index ${#_select_indices[@]}); do - eval $_select_var_name\+\=\(\""${_select_list[${_select_indices[$i]}]}"\"\) - done -} - - - - -on_checkbox_input_up() { - remove_checkbox_instructions - tput cub "$(tput cols)" - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf " ${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf " ${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi - tput el - - if [ $_current_index = 0 ]; then - _current_index=$((${#_checkbox_list[@]}-1)) - tput cud $((${#_checkbox_list[@]}-1)) - tput cub "$(tput cols)" - else - _current_index=$((_current_index-1)) - - tput cuu1 - tput cub "$(tput cols)" - tput el - fi - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi -} - -on_checkbox_input_down() { - remove_checkbox_instructions - tput cub "$(tput cols)" - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf " ${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf " ${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi - - tput el - - if [ $_current_index = $((${#_checkbox_list[@]}-1)) ]; then - _current_index=0 - tput cuu $((${#_checkbox_list[@]}-1)) - tput cub "$(tput cols)" - else - _current_index=$((_current_index+1)) - tput cud1 - tput cub "$(tput cols)" - tput el - fi - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi -} - -on_checkbox_input_enter() { - local OLD_IFS - OLD_IFS=$IFS - _checkbox_selected_indices=() - _checkbox_selected_options=() - IFS=$'\n' - - for i in $(gen_index ${#_checkbox_list[@]}); do - if [ "${_checkbox_selected[$i]}" = true ]; then - _checkbox_selected_indices+=($i) - _checkbox_selected_options+=("${_checkbox_list[$i]}") - fi - done - - tput cud $((${#_checkbox_list[@]}-${_current_index})) - tput cub "$(tput cols)" - - for i in $(seq $((${#_checkbox_list[@]}+1))); do - tput el1 - tput el - tput cuu1 - done - tput cub "$(tput cols)" - - tput cuf $((${#prompt}+3)) - printf "${cyan}$(join _checkbox_selected_options)${normal}" - tput el - - tput cud1 - tput cub "$(tput cols)" - tput el - - _break_keypress=true - IFS=$OLD_IFS -} - -on_checkbox_input_space() { - remove_checkbox_instructions - tput cub "$(tput cols)" - tput el - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - _checkbox_selected[$_current_index]=false - else - _checkbox_selected[$_current_index]=true - fi - - if [ "${_checkbox_selected[$_current_index]}" = true ]; then - printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" - else - printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}" - fi -} - -remove_checkbox_instructions() { - if [ $_first_keystroke = true ]; then - tput cuu $((${_current_index}+1)) - tput cub "$(tput cols)" - tput cuf $((${#prompt}+3)) - tput el - tput cud $((${_current_index}+1)) - _first_keystroke=false - fi -} - -# for vim movements -on_checkbox_input_ascii() { - local key=$1 - case $key in - "j" ) on_checkbox_input_down;; - "k" ) on_checkbox_input_up;; - esac -} - -_checkbox_input() { - local i - local j - prompt=$1 - eval _checkbox_list=( '"${'${2}'[@]}"' ) - _current_index=0 - _first_keystroke=true - - trap control_c SIGINT EXIT - - stty -echo - tput civis - - print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Press to select, to finalize)${normal}" - - for i in $(gen_index ${#_checkbox_list[@]}); do - _checkbox_selected[$i]=false - done - - if [ -n "$3" ]; then - eval _selected_indices=( '"${'${3}'[@]}"' ) - for i in ${_selected_indices[@]}; do - _checkbox_selected[$i]=true - done - fi - - for i in $(gen_index ${#_checkbox_list[@]}); do - tput cub "$(tput cols)" - if [ $i = 0 ]; then - if [ "${_checkbox_selected[$i]}" = true ]; then - print "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$i]} ${normal}" - else - print "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$i]} ${normal}" - fi - else - if [ "${_checkbox_selected[$i]}" = true ]; then - print " ${green}${checked}${normal} ${_checkbox_list[$i]} ${normal}" - else - print " ${unchecked} ${_checkbox_list[$i]} ${normal}" - fi - fi - tput el - done - - for j in $(gen_index ${#_checkbox_list[@]}); do - tput cuu1 - done - - on_keypress on_checkbox_input_up on_checkbox_input_down on_checkbox_input_space on_checkbox_input_enter on_default on_default on_checkbox_input_ascii -} - -checkbox_input() { - _checkbox_input "$1" "$2" - _checkbox_input_output_var_name=$3 - select_indices _checkbox_list _checkbox_selected_indices $_checkbox_input_output_var_name - - unset _checkbox_list - unset _break_keypress - unset _first_keystroke - unset _current_index - unset _checkbox_input_output_var_name - unset _checkbox_selected_indices - unset _checkbox_selected_options - - cleanup -} - -checkbox_input_indices() { - _checkbox_input "$1" "$2" "$3" - _checkbox_input_output_var_name=$3 - - eval $_checkbox_input_output_var_name\=\(\) - for i in $(gen_index ${#_checkbox_selected_indices[@]}); do - eval $_checkbox_input_output_var_name\+\=\(${_checkbox_selected_indices[$i]}\) - done - - unset _checkbox_list - unset _break_keypress - unset _first_keystroke - unset _current_index - unset _checkbox_input_output_var_name - unset _checkbox_selected_indices - unset _checkbox_selected_options - - cleanup -} - - - - -on_list_input_up() { - remove_list_instructions - tput cub "$(tput cols)" - - printf " ${_list_options[$_list_selected_index]}" - tput el - - if [ $_list_selected_index = 0 ]; then - _list_selected_index=$((${#_list_options[@]}-1)) - tput cud $((${#_list_options[@]}-1)) - tput cub "$(tput cols)" - else - _list_selected_index=$((_list_selected_index-1)) - - tput cuu1 - tput cub "$(tput cols)" - tput el - fi - - printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" -} - -on_list_input_down() { - remove_list_instructions - tput cub "$(tput cols)" - - printf " ${_list_options[$_list_selected_index]}" - tput el - - if [ $_list_selected_index = $((${#_list_options[@]}-1)) ]; then - _list_selected_index=0 - tput cuu $((${#_list_options[@]}-1)) - tput cub "$(tput cols)" - else - _list_selected_index=$((_list_selected_index+1)) - tput cud1 - tput cub "$(tput cols)" - tput el - fi - printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" -} - -on_list_input_enter_space() { - local OLD_IFS - OLD_IFS=$IFS - IFS=$'\n' - - tput cud $((${#_list_options[@]}-${_list_selected_index})) - tput cub "$(tput cols)" - - for i in $(seq $((${#_list_options[@]}+1))); do - tput el1 - tput el - tput cuu1 - done - tput cub "$(tput cols)" - - tput cuf $((${#prompt}+3)) - printf "${cyan}${_list_options[$_list_selected_index]}${normal}" - tput el - - tput cud1 - tput cub "$(tput cols)" - tput el - - _break_keypress=true - IFS=$OLD_IFS -} - -remove_list_instructions() { - if [ $_first_keystroke = true ]; then - tput cuu $((${_list_selected_index}+1)) - tput cub "$(tput cols)" - tput cuf $((${#prompt}+3)) - tput el - tput cud $((${_list_selected_index}+1)) - _first_keystroke=false - fi -} - -_list_input() { - local i - local j - prompt=$1 - eval _list_options=( '"${'${2}'[@]}"' ) - - _list_selected_index=0 - _first_keystroke=true - - trap control_c SIGINT EXIT - - stty -echo - tput civis - - print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Use arrow keys)${normal}" - - for i in $(gen_index ${#_list_options[@]}); do - tput cub "$(tput cols)" - if [ $i = 0 ]; then - print "${cyan}${arrow} ${_list_options[$i]} ${normal}" - else - print " ${_list_options[$i]}" - fi - tput el - done - - for j in $(gen_index ${#_list_options[@]}); do - tput cuu1 - done - - on_keypress on_list_input_up on_list_input_down on_list_input_enter_space on_list_input_enter_space - -} - - -list_input() { - _list_input "$1" "$2" - local var_name=$3 - eval $var_name=\'"${_list_options[$_list_selected_index]}"\' - unset _list_selected_index - unset _list_options - unset _break_keypress - unset _first_keystroke - - cleanup -} - -list_input_index() { - _list_input "$1" "$2" - local var_name=$3 - eval $var_name=\'"$_list_selected_index"\' - unset _list_selected_index - unset _list_options - unset _break_keypress - unset _first_keystroke - - cleanup -} - - - - -on_text_input_left() { - remove_regex_failed - if [ $_current_pos -gt 0 ]; then - tput cub1 - _current_pos=$(($_current_pos-1)) - fi -} - -on_text_input_right() { - remove_regex_failed - if [ $_current_pos -lt ${#_text_input} ]; then - tput cuf1 - _current_pos=$(($_current_pos+1)) - fi -} - -on_text_input_enter() { - remove_regex_failed - - if [[ "$_text_input" =~ $_text_input_regex && "$(eval $_text_input_validator "$_text_input")" = true ]]; then - tput cub "$(tput cols)" - tput cuf $((${#_read_prompt}-19)) - printf "${cyan}${_text_input}${normal}" - tput el - tput cud1 - tput cub "$(tput cols)" - tput el - eval $var_name=\'"${_text_input}"\' - _break_keypress=true - else - _text_input_regex_failed=true - tput civis - tput cud1 - tput cub "$(tput cols)" - tput el - printf "${red}>>${normal} $_text_input_regex_failed_msg" - tput cuu1 - tput cub "$(tput cols)" - tput cuf $((${#_read_prompt}-19)) - tput el - _text_input="" - _current_pos=0 - tput cnorm - fi -} - -on_text_input_ascii() { - remove_regex_failed - local c=$1 - - if [ "$c" = '' ]; then - c=' ' - fi - - local rest="${_text_input:$_current_pos}" - _text_input="${_text_input:0:$_current_pos}$c$rest" - _current_pos=$(($_current_pos+1)) - - tput civis - printf "$c$rest" - tput el - if [ ${#rest} -gt 0 ]; then - tput cub ${#rest} - fi - tput cnorm -} - -on_text_input_backspace() { - remove_regex_failed - if [ $_current_pos -gt 0 ]; then - local start="${_text_input:0:$(($_current_pos-1))}" - local rest="${_text_input:$_current_pos}" - _current_pos=$(($_current_pos-1)) - tput cub 1 - tput el - tput sc - printf "$rest" - tput rc - _text_input="$start$rest" - fi -} - -remove_regex_failed() { - if [ $_text_input_regex_failed = true ]; then - _text_input_regex_failed=false - tput sc - tput cud1 - tput el1 - tput el - tput rc - fi -} - -text_input_default_validator() { - echo true; -} - -text_input() { - local prompt=$1 - local var_name=$2 - local _text_input_regex="${3:-"\.+"}" - local _text_input_regex_failed_msg=${4:-"Input validation failed"} - local _text_input_validator=${5:-text_input_default_validator} - local _read_prompt_start=$'\e[32m?\e[39m\e[1m' - local _read_prompt_end=$'\e[22m' - local _read_prompt="$( echo "$_read_prompt_start ${prompt} $_read_prompt_end")" - local _current_pos=0 - local _text_input_regex_failed=false - local _text_input="" - printf "$_read_prompt" - - - trap control_c SIGINT EXIT - - stty -echo - tput cnorm - - on_keypress on_default on_default on_text_input_ascii on_text_input_enter on_text_input_left on_text_input_right on_text_input_ascii on_text_input_backspace - eval $var_name=\'"${_text_input}"\' - - cleanup -} diff --git a/inquirer-sh/list_input.sh b/inquirer-sh/list_input.sh deleted file mode 100644 index c5bd8f6..0000000 --- a/inquirer-sh/list_input.sh +++ /dev/null @@ -1,301 +0,0 @@ -#!/bin/bash - - -# store the current set options -OLD_SET=$- -set -e - -arrow="$(echo -e '\xe2\x9d\xaf')" -checked="$(echo -e '\xe2\x97\x89')" -unchecked="$(echo -e '\xe2\x97\xaf')" - -black="$(tput setaf 0)" -red="$(tput setaf 1)" -green="$(tput setaf 2)" -yellow="$(tput setaf 3)" -blue="$(tput setaf 4)" -magenta="$(tput setaf 5)" -cyan="$(tput setaf 6)" -white="$(tput setaf 7)" -bold="$(tput bold)" -normal="$(tput sgr0)" -dim=$'\e[2m' - -print() { - echo "$1" - tput el -} - -join() { - local IFS=$'\n' - local _join_list - eval _join_list=( '"${'${1}'[@]}"' ) - local first=true - for item in ${_join_list[@]}; do - if [ "$first" = true ]; then - printf "%s" "$item" - first=false - else - printf "${2-, }%s" "$item" - fi - done -} - -function gen_env_from_options() { - local IFS=$'\n' - local _indices - local _env_names - local _checkbox_selected - eval _indices=( '"${'${1}'[@]}"' ) - eval _env_names=( '"${'${2}'[@]}"' ) - - for i in $(gen_index ${#_env_names[@]}); do - _checkbox_selected[$i]=false - done - - for i in ${_indices[@]}; do - _checkbox_selected[$i]=true - done - - for i in $(gen_index ${#_env_names[@]}); do - printf "%s=%s\n" "${_env_names[$i]}" "${_checkbox_selected[$i]}" - done -} - -on_default() { - true; -} - -on_keypress() { - local OLD_IFS - local IFS - local key - OLD_IFS=$IFS - local on_up=${1:-on_default} - local on_down=${2:-on_default} - local on_space=${3:-on_default} - local on_enter=${4:-on_default} - local on_left=${5:-on_default} - local on_right=${6:-on_default} - local on_ascii=${7:-on_default} - local on_backspace=${8:-on_default} - _break_keypress=false - while IFS="" read -rsn1 key; do - case "$key" in - $'\x1b') - read -rsn1 key - if [[ "$key" == "[" ]]; then - read -rsn1 key - case "$key" in - 'A') eval $on_up;; - 'B') eval $on_down;; - 'D') eval $on_left;; - 'C') eval $on_right;; - esac - fi - ;; - ' ') eval $on_space ' ';; - [a-z0-9A-Z\!\#\$\&\+\,\-\.\/\;\=\?\@\[\]\^\_\{\}\~]) eval $on_ascii $key;; - $'\x7f') eval $on_backspace $key;; - '') eval $on_enter $key;; - esac - if [ $_break_keypress = true ]; then - break - fi - done - IFS=$OLD_IFS -} - -gen_index() { - local k=$1 - local l=0 - if [ $k -gt 0 ]; then - for l in $(seq $k) - do - echo "$l-1" | bc - done - fi -} - -cleanup() { - # Reset character attributes, make cursor visible, and restore - # previous screen contents (if possible). - tput sgr0 - tput cnorm - stty echo - - # Restore `set e` option to its orignal value - if [[ $OLD_SET =~ e ]] - then set -e - else set +e - fi -} - -control_c() { - cleanup - exit $? -} - -select_indices() { - local _select_list - local _select_indices - local _select_selected=() - eval _select_list=( '"${'${1}'[@]}"' ) - eval _select_indices=( '"${'${2}'[@]}"' ) - local _select_var_name=$3 - eval $_select_var_name\=\(\) - for i in $(gen_index ${#_select_indices[@]}); do - eval $_select_var_name\+\=\(\""${_select_list[${_select_indices[$i]}]}"\"\) - done -} - - - -# Support VIM hjkl move -on_list_input_ascii() { - key=$1 - if [[ $key == 'k' || $key == 'K' || $key == 'h' || $key == 'H' ]]; then - on_list_input_up - elif [[ $key == 'j' || $key == 'J' || $key == 'l' || $key == 'L' ]]; then - on_list_input_down - fi -} - -on_list_input_up() { - remove_list_instructions - tput cub "$(tput cols)" - - printf " ${_list_options[$_list_selected_index]}" - tput el - - if [ $_list_selected_index = 0 ]; then - _list_selected_index=$((${#_list_options[@]}-1)) - tput cud $((${#_list_options[@]}-1)) - tput cub "$(tput cols)" - else - _list_selected_index=$((_list_selected_index-1)) - - tput cuu1 - tput cub "$(tput cols)" - tput el - fi - - printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" -} - -on_list_input_down() { - remove_list_instructions - tput cub "$(tput cols)" - - printf " ${_list_options[$_list_selected_index]}" - tput el - - if [ $_list_selected_index = $((${#_list_options[@]}-1)) ]; then - _list_selected_index=0 - tput cuu $((${#_list_options[@]}-1)) - tput cub "$(tput cols)" - else - _list_selected_index=$((_list_selected_index+1)) - tput cud1 - tput cub "$(tput cols)" - tput el - fi - printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" -} - -on_list_input_enter_space() { - local OLD_IFS - OLD_IFS=$IFS - IFS=$'\n' - - tput cud $((${#_list_options[@]}-${_list_selected_index})) - tput cub "$(tput cols)" - - for i in $(seq $((${#_list_options[@]}+1))); do - tput el1 - tput el - tput cuu1 - done - tput cub "$(tput cols)" - - tput cuf $((${#prompt}+3)) - printf "${cyan}${_list_options[$_list_selected_index]}${normal}" - tput el - - tput cud1 - tput cub "$(tput cols)" - tput el - - _break_keypress=true - IFS=$OLD_IFS -} - -remove_list_instructions() { - if [ $_first_keystroke = true ]; then - tput cuu $((${_list_selected_index}+1)) - tput cub "$(tput cols)" - tput cuf $((${#prompt}+3)) - tput el - tput cud $((${_list_selected_index}+1)) - _first_keystroke=false - fi -} - -_list_input() { - local i - local j - prompt=$1 - eval _list_options=( '"${'${2}'[@]}"' ) - - _list_selected_index=0 - _first_keystroke=true - - trap control_c SIGINT EXIT - - stty -echo - tput civis - - print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Use arrow keys)${normal}" - - for i in $(gen_index ${#_list_options[@]}); do - tput cub "$(tput cols)" - if [ $i = 0 ]; then - print "${cyan}${arrow} ${_list_options[$i]} ${normal}" - else - print " ${_list_options[$i]}" - fi - tput el - done - - for j in $(gen_index ${#_list_options[@]}); do - tput cuu1 - done - - on_keypress on_list_input_up on_list_input_down on_list_input_enter_space on_list_input_enter_space on_list_input_up on_list_input_down on_list_input_ascii on_list_input_up - -} - - -list_input() { - _list_input "$1" "$2" - local var_name=$3 - eval $var_name=\'"${_list_options[$_list_selected_index]}"\' - unset _list_selected_index - unset _list_options - unset _break_keypress - unset _first_keystroke - - cleanup -} - -list_input_index() { - _list_input "$1" "$2" - local var_name=$3 - eval $var_name=\'"$_list_selected_index"\' - unset _list_selected_index - unset _list_options - unset _break_keypress - unset _first_keystroke - - cleanup -} diff --git a/inquirer-sh/text_input.sh b/inquirer-sh/text_input.sh deleted file mode 100644 index 9e61684..0000000 --- a/inquirer-sh/text_input.sh +++ /dev/null @@ -1,284 +0,0 @@ -#!/bin/bash - - -# store the current set options -OLD_SET=$- -set -e - -arrow="$(echo -e '\xe2\x9d\xaf')" -checked="$(echo -e '\xe2\x97\x89')" -unchecked="$(echo -e '\xe2\x97\xaf')" - -black="$(tput setaf 0)" -red="$(tput setaf 1)" -green="$(tput setaf 2)" -yellow="$(tput setaf 3)" -blue="$(tput setaf 4)" -magenta="$(tput setaf 5)" -cyan="$(tput setaf 6)" -white="$(tput setaf 7)" -bold="$(tput bold)" -normal="$(tput sgr0)" -gray="\033[1;30m" -dim=$'\e[2m' - -print() { - echo "$1" - tput el -} - -join() { - local IFS=$'\n' - local _join_list - eval _join_list=( '"${'${1}'[@]}"' ) - local first=true - for item in ${_join_list[@]}; do - if [ "$first" = true ]; then - printf "%s" "$item" - first=false - else - printf "${2-, }%s" "$item" - fi - done -} - -function gen_env_from_options() { - local IFS=$'\n' - local _indices - local _env_names - local _checkbox_selected - eval _indices=( '"${'${1}'[@]}"' ) - eval _env_names=( '"${'${2}'[@]}"' ) - - for i in $(gen_index ${#_env_names[@]}); do - _checkbox_selected[$i]=false - done - - for i in ${_indices[@]}; do - _checkbox_selected[$i]=true - done - - for i in $(gen_index ${#_env_names[@]}); do - printf "%s=%s\n" "${_env_names[$i]}" "${_checkbox_selected[$i]}" - done -} - -on_default() { - true; -} - -on_keypress() { - local OLD_IFS - local IFS - local key - OLD_IFS=$IFS - local on_up=${1:-on_default} - local on_down=${2:-on_default} - local on_space=${3:-on_default} - local on_enter=${4:-on_default} - local on_left=${5:-on_default} - local on_right=${6:-on_default} - local on_ascii=${7:-on_default} - local on_backspace=${8:-on_default} - _break_keypress=false - while IFS="" read -rsn1 key; do - case "$key" in - $'\x1b') - read -rsn1 key - if [[ "$key" == "[" ]]; then - read -rsn1 key - case "$key" in - 'A') eval $on_up;; - 'B') eval $on_down;; - 'D') eval $on_left;; - 'C') eval $on_right;; - esac - fi - ;; - ' ') eval $on_space ' ';; - [a-z0-9A-Z\!\#\$\&\+\,\-\.\/\;\=\?\@\[\]\^\_\{\}\~]) eval $on_ascii $key;; - $'\x7f') eval $on_backspace $key;; - '') eval $on_enter $key;; - esac - if [ $_break_keypress = true ]; then - break - fi - done - IFS=$OLD_IFS -} - -gen_index() { - local k=$1 - local l=0 - if [ $k -gt 0 ]; then - for l in $(seq $k) - do - echo "$l-1" | bc - done - fi -} - -cleanup() { - # Reset character attributes, make cursor visible, and restore - # previous screen contents (if possible). - tput sgr0 - tput cnorm - stty echo - - # Restore `set e` option to its orignal value - if [[ $OLD_SET =~ e ]] - then set -e - else set +e - fi -} - -control_c() { - cleanup - exit $? -} - -select_indices() { - local _select_list - local _select_indices - local _select_selected=() - eval _select_list=( '"${'${1}'[@]}"' ) - eval _select_indices=( '"${'${2}'[@]}"' ) - local _select_var_name=$3 - eval $_select_var_name\=\(\) - for i in $(gen_index ${#_select_indices[@]}); do - eval $_select_var_name\+\=\(\""${_select_list[${_select_indices[$i]}]}"\"\) - done -} - - - - - -on_text_input_left() { - remove_regex_failed - if [ $_current_pos -gt 0 ]; then - tput cub1 - _current_pos=$(($_current_pos-1)) - fi -} - -on_text_input_right() { - remove_regex_failed - if [ $_current_pos -lt ${#_text_input} ]; then - tput cuf1 - _current_pos=$(($_current_pos+1)) - fi -} - -on_text_input_enter() { - remove_regex_failed - - # Set default value if it has one - _text_input=$([ -z "$_text_input" ] && echo $_text_default_value || echo $_text_input) - - # Only use validator to check, because you can use regexp in your validator - if [[ "$(eval $_text_input_validator "$_text_input")" = true ]]; then - tput cub "$(tput cols)" - tput cuf $((${#_read_prompt}-${#_text_default_tip} - 36)) - printf "${cyan}${_text_input}${normal}" - tput el - tput cud1 - tput cub "$(tput cols)" - tput el - eval $var_name=\'"${_text_input}"\' - _break_keypress=true - else - _text_input_regex_failed=true - tput civis - tput cud1 - tput cub "$(tput cols)" - tput el - printf "${red}>>${normal} $_text_input_regex_failed_msg" - tput cuu1 - tput cub "$(tput cols)" - tput cuf $((${#_read_prompt}-19)) - tput el - _text_input="" - _current_pos=0 - tput cnorm - fi -} - -on_text_input_ascii() { - remove_regex_failed - local c=$1 - - if [ "$c" = '' ]; then - c=' ' - fi - - local rest="${_text_input:$_current_pos}" - _text_input="${_text_input:0:$_current_pos}$c$rest" - _current_pos=$(($_current_pos+1)) - - tput civis - printf "$c$rest" - tput el - if [ ${#rest} -gt 0 ]; then - tput cub ${#rest} - fi - tput cnorm -} - -on_text_input_backspace() { - remove_regex_failed - if [ $_current_pos -gt 0 ]; then - local start="${_text_input:0:$(($_current_pos-1))}" - local rest="${_text_input:$_current_pos}" - _current_pos=$(($_current_pos-1)) - tput cub 1 - tput el - tput sc - printf "$rest" - tput rc - _text_input="$start$rest" - fi -} - -remove_regex_failed() { - if [ $_text_input_regex_failed = true ]; then - _text_input_regex_failed=false - tput sc - tput cud1 - tput el1 - tput el - tput rc - fi -} - -text_input_default_validator() { - echo true; -} - -text_input() { - local prompt=$1 - local var_name=$2 - local _text_default_value=$3 - # If there are default value, then show as a gray tip - local _text_default_tip=$([ -z "$_text_default_value" ] && echo "" || echo "(${_text_default_value})") - local _text_input_regex_failed_msg=${4:-"Input validation failed"} - local _text_input_validator=${5:-text_input_default_validator} - local _read_prompt_start=$'\e[32m?\e[39m\e[1m' - local _read_prompt_end=$'\e[22m' - local _read_prompt="$( echo "$_read_prompt_start ${prompt} ${gray}${_text_default_tip}${normal} $_read_prompt_end")" - local _current_pos=0 - local _text_input_regex_failed=false - local _text_input="" - printf "$_read_prompt" - - - trap control_c SIGINT EXIT - - stty -echo - tput cnorm - - on_keypress on_default on_default on_text_input_ascii on_text_input_enter on_text_input_left on_text_input_right on_text_input_ascii on_text_input_backspace - eval $var_name=\'"${_text_input}"\' - - cleanup -}