#!/bin/bash
#
# Bash library and executable that helps managing interactive choices, while supporting multiple frontends: NONE, whiptail, dialog or zenity.
#
# Copyright © 2021-2025 Jean-Jacques Brucker <jjbrucker@foopgp.org>
# Copyright © 2024 Henri GEIST <geist.henri@laposte.net>
#
# SPDX-License-Identifier: LGPL-3.0-only

# TODO:
#   * Add a --timeout global option

if [[ "$1" == --bash-completion ]] ; then
	BL_tmp_a=$("$BASH_SOURCE" --help | sed -nE 's:^ {2,4}([a-z0-9_]+\>).*:\1:p')
	BL_tmp_o=$(for a in $BL_tmp_a ; do
		echo "[$a]=\"$(eval echo $("$BASH_SOURCE" $a --help | sed -n 's,^  ... \(--[a-z_-]\+[^ ]*\).*,\1,p' | sed 's_<_{_ ; s_|_,_g ; s_>_}_ '))\""
	done)
	eval '_bl_interactive_completion()
	{
		local cur coptions="--help --version"
		local a actions="'"$BL_tmp_a"'"
		local -A aoptions=('"$BL_tmp_o"')

		COMPREPLY=()
		cur=${COMP_WORDS[COMP_CWORD]}

		case ${COMP_WORDS[COMP_CWORD-1]} in
			--help|--version)
				# should exit without executing any actions, so do not complete anything
				return 0 ;;
			--text)
				return 0 ;;
			--frontend)
				COMPREPLY=( $(compgen -W "whiptail dialog zenity NONE" -- $cur ) )
				return 0 ;;
		esac
		case $cur in
			-*)
				if a=$(grep -o "\<\('"${BL_tmp_a//$'\n'/\\|}"'\)\>" <<< "${COMP_WORDS[@]}") ; then
					COMPREPLY=( $(compgen -W "$coptions ${aoptions[$a]}" -- $cur ) )
					return 0
				fi
				COMPREPLY=( $(compgen -W "$coptions --frontend --zenity-extra" -- $cur ) )
				return 0 ;;
		esac
		# complete actions if none in ${COMP_WORDS[@]}
		grep -q "\<\('"${BL_tmp_a//$'\n'/\\|}"'\)\>" <<< "${COMP_WORDS[@]}" || COMPREPLY=( $(compgen -W "$actions" -- $cur ) )
		return 0
	}'
	unset BL_tmp_a BL_tmp_o
	complete -F _bl_interactive_completion "$(basename "$BASH_SOURCE")" "$BASH_SOURCE"
	return 0
fi

# If sourcing while _bl_interactive_parseoptions is already set, execute the function and return without reloading rest of file.
if [[ "$BASH_SOURCE" != "$0" ]] && [[ "$(type -t _bl_interactive_parseoptions)" == function ]] ; then
	_bl_interactive_parseoptions "$@"
	return $?
fi

### Constants ###

BL_INTERACTIVE_NAME="$(basename "$(readlink -f "$BASH_SOURCE")" )"
BL_INTERACTIVE_VERSION="0.3.5-1"
BL_INTERACTIVE_FUNCTIONS=( $(sed -n 's,^\(bl_[^( ]*\) *().*,\1,p' "$BASH_SOURCE") )
readonly BL_INTERACTIVE_VERSION BL_INTERACTIVE_FUNCTIONS BL_INTERACTIVE_NAME

declare -r BL_INTERACTIVE_EMAIL_REGEX="([a-zA-Z0-9_.%+-]+)@(([a-zA-Z0-9.-]+)\.([a-zA-Z]+))"

[[ "$BASH_SOURCE" == "$0" ]] && declare -r BL_INTERACTIVE_isprogram=1 || declare -r BL_INTERACTIVE_isprogram=0

### Others Globals ###

: "${BL_INTERACTIVE_FRONTEND:=NONE}"

if ((BL_INTERACTIVE_isprogram)) ; then
	TEXTDOMAIN="bashlibs"
	TEXTDOMAINDIR="$(dirname "$(readlink -f "$BASH_SOURCE")" )/../share/locale"
	# Require Bash 5.2. - https://www.kurokatta.org/grumble/2023/11/bash-translated-strings#fnref3
	shopt -s noexpand_translation
fi

BL_INTERACTIVE_chelpmsg="
"$"Help managing interactive choices, while supporting multiple frontends: NONE, whiptail, dialog or zenity.""

MAIN OPTIONS:
  -f, --frontend PROGRAM   "$"Select a frontend program"" {NONE,whiptail,dialog,zenity} - "$"Environment variable: ""BL_INTERACTIVE_FRONTEND, "$"default: ""$BL_INTERACTIVE_FRONTEND
  -z, --zenity-extra ARGS  "$"Extra args for zenity (if used)"" (zenity --help-general) - "$"Environment variable: ""BL_INTERACTIVE_ZENITY_EXTRA, "$"default: ""$BL_INTERACTIVE_ZENITY_EXTRA"

### internal functions ###

_bl_interactive_parseoptions() {
    local BL_INTERACTIVE_FRONTEND_VERSION
	local npp=$#
	for ((;$#;)) ; do
		case "$1" in
			-f|--frontend)
				shift ; BL_INTERACTIVE_FRONTEND="$1" ;; # WARNING: dialog use to be compatible with whiptail, but it's very buggy.
			-z|--zenity-extra)
				shift ; BL_INTERACTIVE_ZENITY_EXTRA="$1" ;;
			-h|--help)
				printf "%s\n%s%s\n" "$BL_INTERACTIVE_usage" "$BL_INTERACTIVE_chelpmsg" "$BL_INTERACTIVE_shelpmsg" ;
				return 1 ;;
			-V|--version)
				printf "%s %s\n" "$BL_INTERACTIVE_NAME" "$BL_INTERACTIVE_VERSION" ;
				return 1 ;;
			--)
				shift ; break ;;
			-*) printf -- "$BL_INTERACTIVE_NAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$BASH_SOURCE" >&2 ; return 2 ;;
			*)
				break ;;
		esac
		shift
	done
	BL_INTERACTIVE_NOPTIONS=$((npp-$#))

    if [[ "$BL_INTERACTIVE_FRONTEND" != NONE ]] ; then
		if [[ "$BL_INTERACTIVE_FRONTEND" =~ ^(whiptail|dialog|zenity)$ ]] ; then
			if ! BL_INTERACTIVE_FRONTEND_VERSION=$("$BL_INTERACTIVE_FRONTEND" --version) ; then
				printf "%s: fallback to %s\n" "$BL_INTERACTIVE_NAME" "BL_INTERACTIVE_FRONTEND=NONE" >&2
				BL_INTERACTIVE_FRONTEND=NONE
			fi
		else
			printf "%s: unknow frontend '%s'\n" "$BL_INTERACTIVE_NAME" "$BL_INTERACTIVE_FRONTEND" >&2
			return 2
		fi
    fi
}

# using ( subshell ) for factorization : all new variables are "local" and {fd} is automatically close >&- while carrying return value
_bl_interactive_frontend() (
	unset last mtext rtext # localy clean such eventual global variables
	for ((;$#;)) ; do
		case "$1" in
			--mtext) shift; mtext="$1";;
			--rtext) shift; rtext="$1";;
			--last)  shift; last+=("$1");;
			--)      shift; break;;
			-*)      printf "%s: unrecognized option '%s'\n" "$FUNCNAME" "$1" >&2; return 2;;
			*)       break;;
		esac
		shift
	done

	# because whiptail don't scroll to the end when its text is very big. Duplicate the main text at its begining.
	# Note: we could use LINES and COLUMNS instead of an arbitrary strlen value, but there are not enough reliable.
	((${#rtext}<512)) || rtext="  $mtext"$'\n\n'"$rtext"
	if [[ ${#last[@]} -gt 0 ]] ; then
		$BL_INTERACTIVE_FRONTEND "$@" "$rtext"$'\n\n'"  $mtext" 0 $((COLUMNS/2)) "${last[@]}" {fd}>&1 1>&2 2>&"${fd}"
		exit $?
	else
		$BL_INTERACTIVE_FRONTEND "$@" "$rtext"$'\n\n'"  $mtext" 0 $((COLUMNS/2)) {fd}>&1 1>&2 2>&"${fd}"
		exit $?
	fi
)

_bl_interactive_zenity() {
	# zenity use to write many useless and annoying things on stderr
	zenity "$@" $BL_INTERACTIVE_ZENITY_EXTRA 2> >(grep -vE "(^$|AdwMessageDialog 0x|GtkLabel 0x)" >&2)
}

_bl_interactive_indexof() {
	for ((;$#;)) ; do
		case "$1" in
			--) shift ; break ;;
			*)  break ;;
		esac
		shift
	done
	local asize value=$1
	shift
	asize=$#
	((asize)) || { printf "Usage: %s [--] VALUE ARRAY...\n" "$FUNCNAME" >&2 ; return 2 ;}
	while (($#)) ; do
		[[ "$1" != "${value}" ]] || break
		shift
	done
	(($#)) || return 1
	echo $((asize-$#))
}

### public functions / program actions ###

bl_msgstop() {
	local name
	((BL_INTERACTIVE_isprogram)) && name="$BL_INTERACTIVE_NAME ${FUNCNAME:3}" || name="$FUNCNAME"
	local usage="Usage: $name [OPTIONS] MESSAGE..."
	local helpmsg="
"$"Display a MESSAGE and wait for user to press [Enter].""

OPTIONS:
  -T, --title TITLE        "$"Title to display before the PREAMBLE""
  -t, --text PREAMBLE      "$"Text to display before the MESSAGE""
"
	local ttle pr
	for ((;$#;)) ; do
		case "$1" in
			-T|--title) shift ; ttle="$1" ;;
			-t|--text) shift ; pr="$1" ;;
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_INTERACTIVE_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done

	local message="$*"
	if [[ "$BL_INTERACTIVE_FRONTEND" =~ ^(whiptail|dialog)$ ]] ; then
		_bl_interactive_frontend --rtext "$pr" --mtext "$message" -- ${ttle:+ --title "$ttle"} --msgbox
		return $?
	elif [[ "$BL_INTERACTIVE_FRONTEND" == zenity ]] ; then
		_bl_interactive_zenity --title "$ttle" --info --text "$pr"$'\n\n'"  $message"
		return $?
	fi

	local rep
	[[ -z "$ttle" ]] || echo $'\e[2m'"$ttle"$'\e[0m' >&2
	echo "$pr" >&2
	message=$'\e[1m'"$message"$'\e[0m'
	read -rp "  $message"$' \e[2m(press Enter to continue)\e[0m ' rep
}

bl_input() {
	local name
	((BL_INTERACTIVE_isprogram)) && name="$BL_INTERACTIVE_NAME ${FUNCNAME:3}" || name="$FUNCNAME"
	local usage="Usage: $name [OPTIONS] QUESTION"
	local helpmsg="
"$"Helper to ask for a string.""

OPTIONS:
  -d, --default STRING     "$"Default initialized string""
  -T, --title TITLE        "$"Title to display before the PREAMBLE""
  -t, --text PREAMBLE      "$"Text to display before the QUESTION""
  -e, --email              "$"Expect an email address""
  -I, --iso-8601[=FMT]     "$"Input should describe time, output date/time in ISO 8601 format"" (\$ man date).
      --utc-iso-8601[=FMT] "$"Same as --iso-8601 but using Coordinated Universal Time (UTC).""
  -P, --password           "$"Hide input chars with '*'""
"
	local def ttle pr emailaddr=false iso8601 boxt="inputbox" zenityopts
	for ((;$#;)) ; do
		case "$1" in
			-e|--email) emailaddr=true ;;
			--utc-iso-8601*)
				iso8601+=" --utc" ;&
			-I*|--iso-8601*)
				iso8601+=" ${1/--utc-/--}"
				# test eventual [=FMT]
				date $iso8601 --date now > /dev/null || return $?
				;;
			-d|--default) shift ; def="$1" ;;
			-P|--pass*) boxt="passwordbox" ; zenityopts+="--hide-text " ;;
			-T|--title) shift ; ttle="$1" ;;
			--preamble|-t|--text) shift ; pr="$1" ;;
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_INTERACTIVE_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done

	local question="$*" rep

	if ! [[ "$BL_INTERACTIVE_FRONTEND" =~ ^(whiptail|dialog|zenity)$ ]] ; then
		question=$'\e[1m'"$question"$'\e[0m'
		[[ -z "$def" ]] || question+=" [$def]"
		[[ -z "$ttle" ]] || echo $'\e[2m'"$ttle"$'\e[0m' >&2
	fi
	while true ; do
		if [[ "$BL_INTERACTIVE_FRONTEND" =~ ^(whiptail|dialog)$ ]] ; then
			rep=$(_bl_interactive_frontend --rtext "$pr" --mtext "$question ?" --last "$def" -- ${ttle:+ --title "$ttle"} --nocancel --${boxt}) || return $?
		elif [[ "$BL_INTERACTIVE_FRONTEND" == zenity ]] ; then
			rep=$(_bl_interactive_zenity --title "$ttle" --entry --text "$pr"$'\n\n'"  $question ?" --entry-text "$def" $zenityopts) || return $?
		else
			echo "$pr" >&2
			if [[ "${boxt}" == passwordbox ]] ; then
				local prompt char
				prompt="  $question"$' \e[1m?\e[0m '
				while IFS= read -p "$prompt" -r -s -n 1 char ; do
					[[ $char != $'\0' ]] || break
					if [[ $char == $'\x7f' ]] || [[ $char == $'\x08' ]] ; then
						# Backspace
						if ((${#rep} > 0)) ; then
							prompt=$'\x08 \x08'
							rep="${rep:: -1}"
						else
							prompt=''
						fi
					elif [[ $char == $'\x15' ]] ; then
						# Ctrl-U -> Erase all
						if ((${#rep} > 0)) ; then
							prompt=$(printf "%0${#rep}d%${#rep}s%0${#rep}d" "0" " " | sed "s,0,"$'\x08'",g" )
							rep=''
						fi
					else
						prompt='*'
						rep+="$char"
					fi
				done
				echo >&2
			else
				read -rp "  $question"$' \e[1m?\e[0m ' rep
			fi
		fi
		rep=${rep:-$def}
		if $emailaddr && ! [[ "$rep" =~ ^${BL_INTERACTIVE_EMAIL_REGEX}$ ]] ; then
			pr="> Please enter a valid email address <"
			continue
		elif [[ "$iso8601" ]] && ! rep=$(date $iso8601 --date "$rep") ; then
			pr="> Please enter a valid date (or time description) <"
			continue
		fi
		break
	done
	echo "${rep:-${def}}"
}

bl_yesno() {
	local name
	((BL_INTERACTIVE_isprogram)) && name="$BL_INTERACTIVE_NAME ${FUNCNAME:3}" || name="$FUNCNAME"
	local usage="Usage: $name [OPTIONS] QUESTION"
	local helpmsg="
"$"Helper for a yes/no QUESTION (binary choice).""
"$"If REPLY == 'yes', return 0""
"$"If REPLY == 'no', return 1""
"$"Else loop asking the question.""

OPTIONS:
      --default=<yes|no>   "$"Set a default answer""
  -T, --title TITLE        "$"Title to display before the PREAMBLE""
  -t, --text PREAMBLE      "$"Text to display before the QUESTION""
"
	local def ttle pr
	for ((;$#;)) ; do
		case "$1" in
			--default=yes) def="yes" ;;
			--default=no) def="no" ;;
			-T|--title) shift ; ttle="$1" ;;
			-p|--preamble|-t|--text) shift ; pr="$1" ;;
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_INTERACTIVE_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done

	local question="$*"

	if [[ "$BL_INTERACTIVE_FRONTEND" =~ ^(whiptail|dialog)$ ]] ; then
		[[ "$def" == no ]] && def="--defaultno" || unset def
		_bl_interactive_frontend --rtext "$pr" --mtext "$question ?" -- ${ttle:+ --title "$ttle"} $def --yesno
		return $?
	elif [[ "$BL_INTERACTIVE_FRONTEND" == zenity ]] ; then
		[[ "$def" == no ]] && def="--default-cancel" || unset def
		_bl_interactive_zenity --title "$ttle" --question --text "$pr"$'\n\n'"  $question ?" $def
		return $?
	fi

	local rep
	question=$'\e[1m'"$question"$'\e[0m \e[2m(y/n)\e[0m'
	[[ -z "$def" ]] || question+=" [$def]"

	[[ -z "$ttle" ]] || echo $'\e[2m'"$ttle"$'\e[0m' >&2
	while true ; do
		echo "$pr" >&2
		read -rp "  $question"$' \e[1m?\e[0m ' rep
		rep=${rep:-$def}
		case "${rep,,}" in
		  y|yes|oui|'sí'|ja|si|'да')
			return 0 ;;
		  n|no|non|nein|'нет')
			return 1 ;;
		  *) echo $'>\e[3m Please answer "yes" or "no" \e[0m<' >&2 ;;
		esac
	done
}

bl_radiolist() {
	local name
	((BL_INTERACTIVE_isprogram)) && name="$BL_INTERACTIVE_NAME ${FUNCNAME:3}" || name="$FUNCNAME"
	local usage="Usage: $name [OPTIONS] [--] ITEMS..."
	local output def ttle pr nperline=3 n i ret=0 rv=0 inarray
	local helpmsg="
"$"Helper for a single choice in a list (like radiobutton).""
"$"Display (stderr) numbered list of item and loop asking for a number in this list""
"$"Then echo (stdout) selected number (1-n).""

OPTIONS:
  -i, --output-index         "$"Output index (= number - 1) instead of number""
  -v, --output-value         "$"Output value instead of number""
  -d, --default NUMBER       "$"Set given number as a default answer, negative count from the end"" (BL_INTERACTIVE_FRONTEND!=zenity)
  -I, --default-index INDEX  "$"Set given index as a default answer, negative count from the end"" (BL_INTERACTIVE_FRONTEND!=zenity)
  -D, --default-value STRING "$"Set first occurence of value as a default answer"" (BL_INTERACTIVE_FRONTEND!=zenity)
  -T, --title TITLE          "$"Title to display before the Text/Question""
  -t, --text TEXT            "$"Text/Question to display before the list""
  -n, --num-per-line NUMBER  "$"Number of items per line""(BL_INTERACTIVE_FRONTEND==NONE, default: $nperline)
"
	for ((;$#;)) ; do
		case "$1" in
			-i|--output-index) output=index ;;
			-v|--output-value) output=value ;;
			-d|--default) shift ; def="$1" ;;
			-I|--default-index) shift ; def=$(($1<0?$1:$1+1)) ;;
			-D|--default-value) shift ; def_value="$1" ;;
			-T|--title) shift ; ttle="$1" ;;
			-p|--preamble|-t|--text) shift ; pr="$1" ;;
			-n|--num*) shift ; nperline="$1" ;;
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) echo "$FUNCNAME $BL_INTERACTIVE_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done
	n=$#
	((n)) || { printf "$FUNCNAME: Error: "$"need arguments (items)""\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 1 ; }

	if [[ "$BL_INTERACTIVE_FRONTEND" == zenity ]] ; then
		ret=$(_bl_interactive_zenity --title "$ttle" --list --text " $pr" --column="" "$@")
		rv=$?
		case "$output" in
			value) echo "$ret" ;;
			*) ret=$(_bl_interactive_indexof "$ret" "$@") ;;&
			index) echo $((ret)) ;;
			*) echo $((ret+1)) ;;
		esac
		return "$rv"
	fi

	if [[ -v def_value ]] ; then
		# Search index of given default value
		def="$(_bl_interactive_indexof "$def_value" "$@")"
		[[ -z "$def" ]] || ((++def))
	fi

	# Save ITEMS in inarray if output == "value"
	if [[ "$output" == "value" ]] ; then inarray=("$@") ; fi

	# Manage negative number
	((def>=0)) || ((def=n+1+def))

	if [[ "$BL_INTERACTIVE_FRONTEND" =~ ^(whiptail|dialog)$ ]] ; then
		local rv entry_options=()
		for ((i=1;i<=n;i++)) ; do
			entry_options+=("$i" "$1")
			((i==def)) && entry_options+=("ON") || entry_options+=("OFF")
			shift
		done
		while (( (!rv) && (ret<1 || ret>n) )) ; do
			ret=$($BL_INTERACTIVE_FRONTEND ${ttle:+ --title "$ttle"} --notags --nocancel --radiolist " $pr" 0 0 "$n" "${entry_options[@]}" {fd}>&1 1>&2 2>&"${fd}")
			rv=$?
		done
	else
		[[ -z "$ttle" ]] || echo $'\e[2m'"$ttle"$'\e[0m' >&2
		printf "%s" " $pr" >&2
		for ((i=0;i<n;)) ; do
			if ((i%nperline)) ; then
				printf "\t\t" >&2
			else
				printf "\n\t" >&2
			fi
			printf "%d) %s" "$((++i))" "$1" >&2
			shift
		done
		echo >&2

		local question=$'\e[1m'"Reply"$'\e[0m \e[2m'"(1-$n)"$'\e[0m'
		((!def)) || question+=" [$def]"
		# the '!' in following test allow to re-ask the question if ret is not a number
		while ! ((ret>0 && ret<=n)) ; do
			read -rp "  $question"$' \e[1m?\e[0m ' ret
			ret=${ret:-$def}
		done
	fi

	case "$output" in
		index) echo $((ret-1)) ;;
		value) echo "${inarray[$((ret-1))]}" ;;
		*) echo "$ret" ;;
	esac
	return "$rv"
}

### Init ###

if ((BL_INTERACTIVE_isprogram)) ; then
	BL_INTERACTIVE_usage="Usage: $(basename "$BASH_SOURCE") [MAIN_OPTIONS]... ACTION [OPTIONS]... [ARGUMENTS]..."
	BL_INTERACTIVE_shelpmsg="
  -h, --help               "$"Show help and exit.""
  -V, --version            "$"Show version and exit.""

ACTIONS:
$(for f in "${BL_INTERACTIVE_FUNCTIONS[@]}" ; do printf "   %-19s%s\n" "${f:3}" "$($f --help | sed -n '/^$/{n;p;q}')" ; done)

All actions support a --help option, eg:
$ $BASH_SOURCE ${BL_INTERACTIVE_FUNCTIONS:3} --help

$BL_INTERACTIVE_NAME is also bash library, see:
$ source $BASH_SOURCE --help"
else
	BL_INTERACTIVE_usage="Usage: source $BASH_SOURCE [MAIN_OPTIONS]..."
	BL_INTERACTIVE_shelpmsg="
      --bash-completion    set completion for $BASH_SOURCE program and return (without loading anything else)

Functions:
$(for f in "${BL_INTERACTIVE_FUNCTIONS[@]}" ; do printf "   %-19s%s\n" "${f}" "$($f --help | sed -n '/^$/{n;p;q}')" ; done)

"$"Reminder: when used as a library, all functions calls share the same environment variables, i.e. the same global options."""
fi

# Parse Options
_BL_INTERACTIVE_RETVAL=0
_bl_interactive_parseoptions "$@" || _BL_INTERACTIVE_RETVAL="$?"

# Do nothing else if sourced
[[ "$BASH_SOURCE" == "$0" ]] || return 0

[[ "$_BL_INTERACTIVE_RETVAL" != "1"  ]] || exit 0
[[ "$_BL_INTERACTIVE_RETVAL" < 2 ]] || exit $_BL_INTERACTIVE_RETVAL

### Run ###
set -e
shift "$BL_INTERACTIVE_NOPTIONS"
if function=$(grep -o "\<bl_$1\>" <<< "${BL_INTERACTIVE_FUNCTIONS[@]}") ; then
	shift
	$function "$@"
	exit $?
else
	printf "$BL_INTERACTIVE_NAME: Error: "$"Unrecognized action"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$BASH_SOURCE" >&2
	exit 2
fi

