# Copyright 1999-2017 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

# @ECLASS: estack.eclass
# @MAINTAINER:
# base-system@gentoo.org
# @BLURB: stack-like value storage support
# @DESCRIPTION:
# Support for storing values on stack-like variables.

if [[ -z ${_ESTACK_ECLASS} ]]; then

# @FUNCTION: estack_push
# @USAGE: <stack> [items to push]
# @DESCRIPTION:
# Push any number of items onto the specified stack.  Pick a name that
# is a valid variable (i.e. stick to alphanumerics), and push as many
# items as you like onto the stack at once.
#
# The following code snippet will echo 5, then 4, then 3, then ...
# @CODE
#		estack_push mystack 1 2 3 4 5
#		while estack_pop mystack i ; do
#			echo "${i}"
#		done
# @CODE
estack_push() {
	[[ $# -eq 0 ]] && die "estack_push: incorrect # of arguments"
	local stack_name="_ESTACK_$1_" ; shift
	eval ${stack_name}+=\( \"\$@\" \)
}

# @FUNCTION: estack_pop
# @USAGE: <stack> [variable]
# @DESCRIPTION:
# Pop a single item off the specified stack.  If a variable is specified,
# the popped item is stored there.  If no more items are available, return
# 1, else return 0.  See estack_push for more info.
estack_pop() {
	[[ $# -eq 0 || $# -gt 2 ]] && die "estack_pop: incorrect # of arguments"

	# We use the fugly _estack_xxx var names to avoid collision with
	# passing back the return value.  If we used "local i" and the
	# caller ran `estack_pop ... i`, we'd end up setting the local
	# copy of "i" rather than the caller's copy.  The _estack_xxx
	# garbage is preferable to using $1/$2 everywhere as that is a
	# bit harder to read.
	local _estack_name="_ESTACK_$1_" ; shift
	local _estack_retvar=$1 ; shift
	eval local _estack_i=\${#${_estack_name}\[@\]}
	# Don't warn -- let the caller interpret this as a failure
	# or as normal behavior (akin to `shift`)
	[[ $(( --_estack_i )) -eq -1 ]] && return 1

	if [[ -n ${_estack_retvar} ]] ; then
		eval ${_estack_retvar}=\"\${${_estack_name}\[${_estack_i}\]}\"
	fi
	eval unset \"${_estack_name}\[${_estack_i}\]\"
}

# @FUNCTION: evar_push
# @USAGE: <variable to save> [more vars to save]
# @DESCRIPTION:
# This let's you temporarily modify a variable and then restore it (including
# set vs unset semantics).  Arrays are not supported at this time.
#
# This is meant for variables where using `local` does not work (such as
# exported variables, or only temporarily changing things in a func).
#
# For example:
# @CODE
#		evar_push LC_ALL
#		export LC_ALL=C
#		... do some stuff that needs LC_ALL=C set ...
#		evar_pop
#
#		# You can also save/restore more than one var at a time
#		evar_push BUTTERFLY IN THE SKY
#		... do stuff with the vars ...
#		evar_pop     # This restores just one var, SKY
#		... do more stuff ...
#		evar_pop 3   # This pops the remaining 3 vars
# @CODE
evar_push() {
	local var val
	for var ; do
		[[ ${!var+set} == "set" ]] \
			&& val=${!var} \
			|| val="unset_76fc3c462065bb4ca959f939e6793f94"
		estack_push evar "${var}" "${val}"
	done
}

# @FUNCTION: evar_push_set
# @USAGE: <variable to save> [new value to store]
# @DESCRIPTION:
# This is a handy shortcut to save and temporarily set a variable.  If a value
# is not specified, the var will be unset.
evar_push_set() {
	local var=$1
	evar_push ${var}
	case $# in
	1) unset ${var} ;;
	2) printf -v "${var}" '%s' "$2" ;;
	*) die "${FUNCNAME}: incorrect # of args: $*" ;;
	esac
}

# @FUNCTION: evar_pop
# @USAGE: [number of vars to restore]
# @DESCRIPTION:
# Restore the variables to the state saved with the corresponding
# evar_push call.  See that function for more details.
evar_pop() {
	local cnt=${1:-bad}
	case $# in
	0) cnt=1 ;;
	1) isdigit "${cnt}" || die "${FUNCNAME}: first arg must be a number: $*" ;;
	*) die "${FUNCNAME}: only accepts one arg: $*" ;;
	esac

	local var val
	while (( cnt-- )) ; do
		estack_pop evar val || die "${FUNCNAME}: unbalanced push"
		estack_pop evar var || die "${FUNCNAME}: unbalanced push"
		[[ ${val} == "unset_76fc3c462065bb4ca959f939e6793f94" ]] \
			&& unset ${var} \
			|| printf -v "${var}" '%s' "${val}"
	done
}

# @FUNCTION: eshopts_push
# @USAGE: [options to `set` or `shopt`]
# @DESCRIPTION:
# Often times code will want to enable a shell option to change code behavior.
# Since changing shell options can easily break other pieces of code (which
# assume the default state), eshopts_push is used to (1) push the current shell
# options onto a stack and (2) pass the specified arguments to set.
#
# If the first argument is '-s' or '-u', we assume you want to call `shopt`
# rather than `set` as there are some options only available via that.
#
# A common example is to disable shell globbing so that special meaning/care
# may be used with variables/arguments to custom functions.  That would be:
# @CODE
#		eshopts_push -o noglob
#		for x in ${foo} ; do
#			if ...some check... ; then
#				eshopts_pop
#				return 0
#			fi
#		done
#		eshopts_pop
# @CODE
eshopts_push() {
	if [[ $1 == -[su] ]] ; then
		estack_push eshopts "$(shopt -p)"
		[[ $# -eq 0 ]] && return 0
		shopt "$@" || die "${FUNCNAME}: bad options to shopt: $*"
	else
		estack_push eshopts "$(shopt -p -o)"
		[[ $# -eq 0 ]] && return 0
		set "$@" || die "${FUNCNAME}: bad options to set: $*"
	fi
}

# @FUNCTION: eshopts_pop
# @USAGE:
# @DESCRIPTION:
# Restore the shell options to the state saved with the corresponding
# eshopts_push call.  See that function for more details.
eshopts_pop() {
	local s
	estack_pop eshopts s || die "${FUNCNAME}: unbalanced push"
	eval "${s}" || die "${FUNCNAME}: sanity: invalid shopt options: ${s}"
}

# @FUNCTION: eumask_push
# @USAGE: <new umask>
# @DESCRIPTION:
# Set the umask to the new value specified while saving the previous
# value onto a stack.  Useful for temporarily changing the umask.
eumask_push() {
	estack_push eumask "$(umask)"
	umask "$@" || die "${FUNCNAME}: bad options to umask: $*"
}

# @FUNCTION: eumask_pop
# @USAGE:
# @DESCRIPTION:
# Restore the previous umask state.
eumask_pop() {
	[[ $# -eq 0 ]] || die "${FUNCNAME}: we take no options"
	local s
	estack_pop eumask s || die "${FUNCNAME}: unbalanced push"
	umask ${s} || die "${FUNCNAME}: sanity: could not restore umask: ${s}"
}

# @FUNCTION: isdigit
# @USAGE: <number> [more numbers]
# @DESCRIPTION:
# Return true if all arguments are numbers.
isdigit() {
	local d
	for d ; do
		[[ ${d:-bad} == *[!0-9]* ]] && return 1
	done
	return 0
}

_ESTACK_ECLASS=1
fi #_ESTACK_ECLASS