#!/usr/bin/env bash
# Use like:
# prompt_choose choice
# "# header" \
# "'message" \
# "choice_1" "shorthand" "choice 1 description" \
# "choice_2" "shorthand 2" "choice 2 description" \
# ;
# echo "${choice}"
#
# prompt_choose returns true if choice successful, false otherwise.
# Allows usage like:
# prompt_choose cmd ... && $cmd;
# To choose a command from the list & execute it OR do nothing
#
# Shorthands are optional. If omitting, use empty string: "choice" "" "description"
function prompt_choose(){
## INIT variables
local -n output=$1
output=""
declare -a functions;
# To make it a base-1 array
functions+=("")
declare -A shorthands;
entryCount=0
shorthandCount=0
status=$open
stOpen=0
stFunction=1
stShorthand=2
print_prompt_chooser "${@}"
answer="$prompt_answer"
if [[ -n "$answer" ]];then
derive_single_answer
fi
if [[ -z "$answer" ]];then
perform_prompt || return 1;
derive_single_answer || return 1;
fi
# set output & return true
output="${answerKey}"
return 0;
}
## for prompt_choose
function perform_prompt(){
# prompt for choice
msg
msg_instruct "(q-quit)"
$has_shorthand \
&& prompt "Type the [shorthand]\n or choose (1-${entryCount}):" answer \
|| prompt "Choose (1-${entryCount}):" answer
prompt_exited "$answer" -p && return 1;
return 0
}
## for prompt_choose
## This is the function for which the MANY passed arguments are used
function print_prompt_chooser(){
# Print entries & headers
for str in "${@:2}"; do
charOne=${str:0:1}
if [[ $status -eq $stOpen && ${charOne} == "#" ]]; then
# display header
msg
header "${str:1}"
elif [[ $status -eq $stOpen && ${charOne} == "'" ]]; then
# display header
msg " ${str:1}"
elif [[ $status -eq $stOpen ]]; then
# store functions
entryCount=$(($entryCount + 1))
functions+=("${str}")
status=$stFunction
elif [[ $status -eq $stFunction ]]; then
# store shorthands
if [[ -z "$str" ]];then
has_shorthand=false
else
has_shorthand=true
prevShorthand="$str"
shorthandCount+=1;
shorthands["$str"]="$entryCount"
fi
status=$stShorthand
elif [[ $status -eq $stShorthand ]]; then
# display entry
shStr=""
if $has_shorthand;then
d=$(($entryCount + 1))
shStr="$(color_li $d)[$prevShorthand]${cOff} "
fi
msg " $(color_li $entryCount)${entryCount}.${cOff} ${shStr}${str}"
status=$stOpen
fi
done
has_shorthand=false;
if [[ $shorthandCount -gt 0 ]];then
has_shorthand=true
fi
}
## for prompt_choose
function derive_single_answer(){
# Determine function from answer
answerIndex="${shorthands[${answer}]}"
if [[ -z "$answerIndex" ]]; then
answerIndex="$answer"
fi
answerKey="${functions["${answerIndex}"]}"
# Display wrong answer message
if [[ -z "${answerKey}" ]];then
shMsg=""
if $has_shorthand;then
shMsg="\n OR a [shorthand] (without the brackets)"
fi
msg_mistake "A number 1-${entryCount}${shMsg} is required.\nYou entered " \
"${answer}" \
;
return 1;
fi;
return 0;
}
# Same usage as prompt_choose
function prompt_choose_multi(){
## INIT variables
local -n answerList="$1"
##### THESE declares & initial values (below) are a copy+paste from prompt_choose() (single)
declare -a functions;
# To make it a base-1 array
functions+=("")
declare -A shorthands;
entryCount=0
shorthandCount=0
status=$open
stOpen=0
stFunction=1
stShorthand=2
##### END COPY+PASTE
print_prompt_chooser "${@}"
# prompt for choice
msg
msg_instruct "(q-quit)"
# $has_shorthand \
# && prompt "range (1-3) and/or separate items(1,shorthand). Use [shorthand] or #." multiAnswer \
# || prompt "range (1-3) and/or separate items(1,4))" multiAnswer
# prompt_exited "$multiAnswer" -p && return 1;
$has_shorthand \
&& prompt "(1-3,shorthand-9)" multiAnswer \
|| prompt "(1-3,shorthand-9)" multiAnswer
prompt_exited "$multiAnswer" -p && return 1;
derive_multi_answer
}
# Called from prompt_choose_multi
function derive_multi_answer(){
range_start=""
range_end=""
buffer=""
for (( i=0; i<${#multiAnswer}; i++ )); do
char="${multiAnswer:$i:1}"
## shorthands cannot contain '-', so this will be a problem for some file names
if [[ "$char" == "-" ]];then
# Current buffer starts a selection range
range_start="$buffer"
buffer=""
continue
elif [[ "$char" == "," ]];then
if [[ -n "$range_start" ]];then
# We've already matched a '-'
range_end="$buffer"
buffer=""
get_current_range all_answers
answerList+=("${all_answers[@]}")
range_start=""
range_end=""
elif [[ -n "$buffer" ]];then
# We have NOT matched a '-'
range_start="$buffer"
range_end="$range_start"
get_current_range all_answers
answerList+=("${all_answers[@]}")
buffer=""
range_start=""
range_end=""
else
echo "improper syntax"
fi
continue
fi
buffer+="$char"
done
if [[ -z "$range_start" ]];then
## The loop has ended & we didn't hit any stopchars [-,]
range_start="$buffer"
fi
range_end="$buffer"
buffer=""
get_current_range all_answers
answerList+=("${all_answers[@]}")
}
## parse a user's answer from prompt_multi_choose
# Handles 1-3,6,7,9-12
# doesn't check for duplicates
# can use shorthands for range_start-range_end,and_indices
function get_current_range(){
local -n current_range="$1"
current_range=()
### copy vars from parent prompt_choose_multi() function
## functions needs renamed in the prompt_choose()
local -n -r indices="functions"
local -n -r keys="shorthands"
### Prevent leakage
local i
local start
local end
local startindex
local endIndex
# Pulling from parent scope
start="$range_start"
end="$range_end"
# Check if answer is a string key & find int index associated with that key
startIndex="${keys["${start}"]}"
if [[ -z "$startIndex" ]];then
if [[ -z "${indices["${start}"]}" ]];then
msg_notice "${start} not found"
return;
else
startIndex="$start"
fi
fi
# Repeat check for end index
endIndex="${keys["${end}"]}"
if [[ -z "$endIndex" ]];then
if [[ -z "${indices["${end}"]}" ]];then
msg_notice "${end} not found"
return;
else
endIndex="$end"
fi
fi
# Loop over values & add them to the range
i=$startIndex
while [[ $i -le $endIndex ]]; do
current_range+=("${indices["$i"]}")
i=$((i+1))
done
}
function choose_files_recursively(){
## to avoid naming conflicts AND have descendence into recursive calls
recurseDirRoot="${recurseDirRoot:-"${1}"}"
dir="${1}"
dir="${dir%%/}"
# echo "Root: ${recurseDirRoot}"
# echo "Search in: ${dir}"
if [[ ! -d "$dir" ]];then
msg_notice "${dir} is not a directory"
return
fi
files=()
dirs=()
shopt -s dotglob
for file in "$dir/"*; do
echo "${file}"
if [[ -d "$file" ]];then
dirs+=("$file")
else
files+=("$file")
fi
done
# return
### This seems to have a comprehensive sorting function
## https://stackoverflow.com/questions/7442417/how-to-sort-an-array-in-bash
### But I'm not going to integrate that now.
relDir="${dir##"$recurseDirRoot"}/"
menu=("# Choose Files and Dirs")
menu+=("'${cInstruct}${relDir}${cOff}")
for dir in "${dirs[@]}";do
name="$(basename "$dir")"
menu+=("$dir" "$name/" "")
done
for file in "${files[@]}";do
name="$(basename "$file")"
menu+=("$file" "$name" "")
done
pathList=()
msg_header "${dir}"
prompt_choose_multi pathList "${menu[@]}"
i=0
srcFiles=()
destFiles=()
projectDir="$(project_dir)"
msg
msg_instruct "Enter destination,${cOff} relative to project root\n${cCommand}(s-skip file)${cOff}"
local path
for path in "${pathList[@]}";do
dir="$(basename "$(dirname "$path")")"
base="$(basename "$path")"
if [[ -f "$path" ]]; then
fileList+=("$path")
# dirList+=("${projectDir}/${dest}")
elif [[ -d "$path" ]];then
echo "Try ${path}"
choose_files_recursively "$path"
fi
done
}