bat-extras/lib/opt.sh

193 lines
4.7 KiB
Bash

#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019-2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
source "${LIB}/constants.sh"
# An array of functions to call before returning from `shiftopt`.
#
# If one of these functions returns a successful exit code, the
# option will be transparently skipped instead of handled.
SHIFTOPT_HOOKS=()
# A setting to change how `shiftopt` will interpret short options that consist
# of more than one character.
#
# Values:
#
# SPLIT -- Splits the option into multiple single-character short options.
# "-abc" -> ("-a" "-b" "-c")
#
# VALUE -- Uses the remaining characters as the value for the short option.
# "-abc" -> ("-a=bc")
#
# CONV -- Converts the argument to a long option.
# "-abc" -> ("--abc")
#
# PASS -- Pass the argument along as-is.
# "-abc" -> ("-abc")
#
SHIFTOPT_SHORT_OPTIONS="VALUE"
# Sets the internal _ARGV, _ARGV_INDEX, and _ARGV_LAST variables used when
# parsing options with the shiftopt and shiftval functions.
#
# Arguments:
# ... -- The program arguments.
#
# Example:
# setargs "--long=3" "file.txt"
setargs() {
_ARGV=("$@")
_ARGV_LAST="$((${#_ARGV[@]} - 1))"
_ARGV_INDEX=0
_ARGV_SUBINDEX=1
}
# Gets all the remaining unparsed arguments and saves them to a variable.
#
# Arguments:
# "-a" -- Append the arguments to the variable instead of replacing it.
# $1 -- The variable to save the args to.
#
# Example:
# getargs remaining_args
getargs() {
if [[ "$1" = "-a" || "$1" = "--append" ]]; then
if [[ "${_ARGV_INDEX}" -ne "$((_ARGV_LAST+1))" ]]; then
eval "$2=(\"\${$2[@]}\" $(printf '%q ' "${_ARGV[@]:$_ARGV_INDEX}"))"
fi
else
if [[ "${_ARGV_INDEX}" -ne "$((_ARGV_LAST+1))" ]]; then
eval "$1=($(printf '%q ' "${_ARGV[@]:$_ARGV_INDEX}"))"
else
eval "$1=()"
fi
fi
}
# Resets the internal _ARGV* variables to the original script arguments.
# This is the equivalent of storing the top-level $@ and using setargs with it.
resetargs() {
setargs "${_ARGV_ORIGINAL[@]}"
}
# INTERNAL.
#
# Increments the argv index pointer used by `shiftopt`.
_shiftopt_next() {
_ARGV_SUBINDEX=1
((_ARGV_INDEX++)) || true
}
# Gets the next option passed to the script.
#
# Variables:
# OPT -- The option name.
#
# Returns:
# 0 -- An option was read.
# 1 -- No more options were read.
#
# Example:
# while shiftopt; do
# shiftval
# echo "$OPT = $OPT_VAL"
# done
shiftopt() {
# Read the top of _ARGV.
[[ "$_ARGV_INDEX" -gt "$_ARGV_LAST" ]] && return 1
OPT="${_ARGV[$_ARGV_INDEX]}"
unset OPT_VAL
if [[ "$OPT" =~ ^-[a-zA-Z0-9_-]+=.* ]]; then
OPT_VAL="${OPT#*=}"
OPT="${OPT%%=*}"
fi
# Handle short options.
if [[ "$OPT" =~ ^-[^-]{2,} ]]; then
case "$SHIFTOPT_SHORT_OPTIONS" in
# PASS mode: "-abc=0" -> ("-abc=0")
PASS) _shiftopt_next ;;
# CONV mode: "-abc=0" -> ("--abc=0")
CONV) OPT="-${OPT}"; _shiftopt_next ;;
# VALUE mode: "-abc=0" -> ("-a=bc=0")
VALUE) {
OPT="${_ARGV[$_ARGV_INDEX]}"
OPT_VAL="${OPT:2}"
OPT="${OPT:0:2}"
_shiftopt_next
} ;;
# SPLIT mode: "-abc=0" -> ("-a=0" "-b=0" "-c=0")
SPLIT) {
OPT="-${OPT:$_ARGV_SUBINDEX:1}"
((_ARGV_SUBINDEX++)) || true
if [[ "$_ARGV_SUBINDEX" -gt "${#OPT}" ]]; then
_shiftopt_next
fi
} ;;
# ????? mode: Treat it as pass.
*)
printf "shiftopt: unknown SHIFTOPT_SHORT_OPTIONS mode '%s'" \
"$SHIFTOPT_SHORT_OPTIONS" 1>&2
_shiftopt_next
;;
esac
else
_shiftopt_next
fi
# Handle hooks.
local hook
for hook in "${SHIFTOPT_HOOKS[@]}"; do
if "$hook"; then
shiftopt
return $?
fi
done
return 0
}
# Gets the value for the current option.
#
# Variables:
# OPT_VAL -- The option value.
#
# Returns:
# 0 -- An option value was read.
# EXIT 1 -- No option value was available.
shiftval() {
# Skip if a value was already provided.
if [[ -n "${OPT_VAL+x}" ]]; then
return 0
fi
if [[ "$_ARGV_SUBINDEX" -gt 1 && "$SHIFTOPT_SHORT_OPTIONS" = "SPLIT" ]]; then
# If it's a short group argument in SPLIT mode, we grab the next argument.
OPT_VAL="${_ARGV[$((_ARGV_INDEX+1))]}"
else
# Otherwise, we can handle it normally.
OPT_VAL="${_ARGV[$_ARGV_INDEX]}"
_shiftopt_next
fi
# Error if no value is provided.
if [[ "$OPT_VAL" =~ -.* ]]; then
printc "%{RED}%s: '%s' requires a value%{CLEAR}\n" "$PROGRAM" "$ARG"
exit 1
fi
}
# -----------------------------------------------------------------------------
setargs "$@"
_ARGV_ORIGINAL=("$@")