#!/usr/bin/env bash
# each -- apply command to each argument individually

. lib.bash || exit

usage() {
	echo "Usage: ${0##*/} [-env] <command> [<static args> --] <loop args>"
	echo
	echo_opt "-e" "exit if a command fails"
	echo_opt "-n" "dry run (output shell commands but don't run them)"
	echo_opt "-v" "verbose (show commands before running)"
	echo
	echo "The static arguments may include a {}, which will be replaced with the current"
	echo "loop argument in each iteration; otherwise the argument will be appended to the"
	echo "end of the command."
}

Xecho() {
	printf '%q ' "${@:1:$#-1}"
	printf '%q\n' "${!#}"
}

Xverb() {
	echo -n "Running: "; Xecho "$@"; "$@"
}

args=()
cmd=()
dryrun=0
verbose=0
errexit=0
flag=0

while getopts ":env" OPT; do
	case $OPT in
	e) errexit=1;;
	n) dryrun=1;;
	v) verbose=1;;
	*) lib:die_getopts;;
	esac
done; shift $((OPTIND-1))

if [[ -t 0 ]]; then
	# Find the *last* double-dash arg
	ldash=0
	for (( i=1; i<=$#; i++ )); do
		if [[ ${!i} == "--" ]]; then
			ldash=$i
		fi
	done
	if (( ldash )); then
		# Split command args and loop args at the last double-dash
		cmd=("${@:1:ldash-1}")
		args=("${@:ldash+1}")
	else
		# For convenience, if no double-dash then assume that only
		# the first word is the command, e.g. 'each unzip *.zip'.
		cmd=("${@:1:1}")
		args=("${@:2}")
	fi
else
	# Will read loop args from stdin, like 'xargs -i'
	cmd=("$@")
fi

if (( ! ${#cmd[@]} )); then
	echo "each: command not specified" >&2
	exit 2
fi

if (( dryrun )); then
	cmd=(Xecho "${cmd[@]}")
elif (( verbose )); then
	cmd=(Xverb "${cmd[@]}")
fi

lpos=()
for (( i=0; i<${#cmd[@]}; i++ )); do
	if [[ ${cmd[i]} == *"{}"* ]]; then
		lpos+=($i)
	fi
done
if (( !lpos )); then
	lpos=(${#cmd[@]})
	cmd+=("{}")
fi

if [[ -t 0 ]]; then
	lcmd=("${cmd[@]}")
	for arg in "${args[@]}"; do
		for i in ${lpos[@]}; do
			lcmd[i]=${cmd[i]//"{}"/"$arg"}
		done
		"${lcmd[@]}"
		if (( $? && errexit )); then
			echo "each: command failed with result $?" >&2
			exit 1
		fi
	done
else
	lcmd=("${cmd[@]}")
	while IFS= read -r arg; do
		for i in ${lpos[@]}; do
			lcmd[i]=${cmd[i]//"{}"/"$arg"}
		done
		"${lcmd[@]}" </dev/tty
		if (( $? && errexit )); then
			echo "each: command failed with result $?" >&2
			exit 1
		fi
	done
fi
