blob: 62b2542c3909521d7c7c9d2704752ee826a754f1 [file] [log] [blame]
# Copyright 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Routines used to simplify command-line options parsing.
# Usage is pretty simple:
#
# 1) Use 'shell_import option_parser' to import this file.
#
# 2) Optional: Set PROGRAM_NAME to the name of your program. If unset, the
# value of $(program_name) will be used instead. This is only used to
# display help.
#
# 3) Optional: Set PROGRAM_PARAMETERS to a string that describes your
# program's parameters (not options). This is only used to display
# help. The 'print_help' function actually dumps something like:
#
# Usage: $PROGRAM_NAME [options] $PROGRAM_PARAMETER
#
# 4) Optional: Set PROGRAM_DESCRIPTION to a long description of what your
# program does. It will be printed just after the 'Usage: ...' line
# above.
#
# 5) Register new options with 'option_register' or 'option_register_var'
# as in the following examples:
#
# option_register --myflag set_myflag "Set custom flag"
# option_register_var '--foo=<bar>' FOO "Set foo value to <bar>"
#
# See the documentation for each function for more details.
#
# 6) Call 'option_parse', as in:
#
# option_parse "$@"
#
# This parses all options and parameters. This also prints relevant
# error messages if you specify an invalid option. It also accumulates
# all parameters into $PARAM_1, $PARAM_2, $PARAM_3, ... up until
# $PARAM_COUNT which is also set by the function.
#
# --help and -? are automatically registered as options, as well as
# --verbose and --quiet so you don't have to deal with these.
# NOTE: We translate '-' into '_' when storing the options in global variables
#
_SHU_OPTIONS=""
_SHU_OPTION_FLAGS=""
_SHU_OPTION_SETTINGS=""
# Return the maximum length of a series of strings
#
# Usage: len=`max_length <string1> <string2> ...`
#
_shu_max_length () {
printf %s "$@" | tr ' ' '\n' | \
awk 'BEGIN {max=0} {len=length($1); if (len > max) max=len} END {print max}'
}
# Translate dashes to underscores
# Usage: str=`_shu_dashes_to_underscores <values>`
_shu_dashes_to_underscores () {
printf %s "$@" | tr '-' '_'
}
# Translate underscores to dashes
# Usage: str=`_shu_underscores_to_dashes <values>`
_shu_underscores_to_dashes ()
{
printf %s "$@" | tr '_' '-'
}
# Set a given option attribute
# $1: option name
# $2: option attribute
# $3: attribute value
#
_shu_option_set_attr () {
var_assign _SHU_OPTIONS_$1_$2 "$3"
}
# Get a given option attribute
# $1: option name
# $2: option attribute
#
_shu_option_get_attr () {
var_value _SHU_OPTIONS_$1_$2
}
# Register a new option
# $1: option
# $2: small abstract for the option
# $3: optional. default value
#
_shu_register_option_internal () {
optlabel=
optname=
optvalue=
opttype=
while [ -n "1" ] ; do
# Check for something like --setting=<value>
if (printf %s "$1" | grep -q -E -e '^--[^=]+=<.+>$'); then
optlabel=$(expr -- "$1" : '\(--[^=]*\)=.*')
optvalue=$(expr -- "$1" : '--[^=]*=\(<.*>\)')
opttype="long_setting"
break
fi
# Check for something like --flag
if (printf %s "$1" | grep -q -E -e '^--[^=]+$'); then
optlabel="$1"
opttype="long_flag"
break
fi
# Check for something like -f<value>
if (printf %s "$1" | grep -q -E -e '^-[A-Za-z0-9]<.+>$'); then
optlabel=$(expr -- "$1" : '\(-.\).*')
optvalue=$(expr -- "$1" : '-.\(<.*>\)')
opttype="short_setting"
break
fi
# Check for something like -f
if (printf %s "$1" | grep -q -E -e '^-.$'); then
optlabel="$1"
opttype="short_flag"
break
fi
echo "ERROR: Invalid option format: $1"
echo " Check register_option call"
exit 1
done
log "new option: type='$opttype' name='$optlabel' value='$optvalue'"
optname=$(_shu_dashes_to_underscores $optlabel)
OPTIONS="$OPTIONS $optname"
OPTIONS_TEXT="$OPTIONS_TEXT $1"
_shu_option_set_attr $optname label "$optlabel"
_shu_option_set_attr $optname otype "$opttype"
_shu_option_set_attr $optname value "$optvalue"
_shu_option_set_attr $optname text "$1"
_shu_option_set_attr $optname abstract "$2"
_shu_option_set_attr $optname default "$3"
}
# Register a new option with a function callback.
#
# $1: option
# $2: name of function that will be called when the option is parsed
# $3: small abstract for the option
# $4: optional. default value
#
option_register () {
local optname optvalue opttype optlabel
_shu_register_option_internal "$1" "$3" "$4"
_shu_option_set_attr $optname funcname "$2"
}
# Register a new option with a variable store
#
# $1: option
# $2: name of variable that will be set by this option
# $3: small abstract for the option
#
# NOTE: The current value of $2 is used as the default
#
option_register_var () {
local optname optvalue opttype optlabel
_shu_register_option_internal "$1" "$3" "$(var_value $2)"
_shu_option_set_attr $optname varname "$2"
}
# Print the help, including a list of registered options for this program
# Note: Assumes PROGRAM_PARAMETERS and PROGRAM_DESCRIPTION exist and
# correspond to the parameters list and the program description
#
_shu_option_print_help () {
local opt text abstract default AWK_SCRIPT
if [ -z "$PROGNAME" ]; then
PROGNAME=$(program_name)
fi
echo "Usage: $PROGNAME [options] $PROGRAM_PARAMETERS"
echo ""
if [ -n "$PROGRAM_DESCRIPTION" ] ; then
echo "$PROGRAM_DESCRIPTION"
echo ""
fi
echo "Valid options (defaults are in brackets):"
echo ""
maxw=$(_shu_max_length "$OPTIONS_TEXT")
AWK_SCRIPT=$(echo "{ printf \"%-${maxw}s\", \$1 }")
for opt in $OPTIONS; do
text=$(_shu_option_get_attr $opt text | awk "$AWK_SCRIPT")
abstract=$(_shu_option_get_attr $opt abstract)
default=$(_shu_option_get_attr $opt default)
if [ -n "$default" ] ; then
echo " $text $abstract [$default]"
else
echo " $text $abstract"
fi
done
echo ""
}
_shu_option_panic_no_args () {
panic "Option '$1' does not take arguments. See --help for usage."
}
_shu_option_panic_missing_arg () {
panic "Option '$1' requires an argument. See --help for usage."
}
# Parse command-line options and set PARAMETER_COUNT as well as
# PARAMETER_1, PARAMETER_2, ... PARAMETER_<n>.
#
# The caller must have called |option_register| or |option_register_var|
# to register specific options.
#
# $1+: command-line options.
option_parse () {
local opt optname otype value name fin funcname
PARAMETER_COUNT=0
while [ -n "$1" ] ; do
# If the parameter does not begin with a dash
# it is not an option.
param=$(expr -- "$1" : '^\([^\-].*\)$' || true)
if [ -n "$param" ] ; then
PARAMETER_COUNT=$(( $PARAMETER_COUNT + 1 ))
var_assign PARAMETER_$PARAMETER_COUNT "$1"
shift
continue
fi
while [ -n "1" ] ; do
# Try to match a long setting, i.e. --option=value
opt=$(expr -- "$1" : '^\(--[^=]*\)=.*$' || true)
if [ -n "$opt" ] ; then
otype="long_setting"
value=$(expr -- "$1" : '^--[^=]*=\(.*\)$' || true)
break
fi
# Try to match a long flag, i.e. --option
opt=$(expr -- "$1" : '^\(--.*\)$' || true)
if [ -n "$opt" ] ; then
otype="long_flag"
value="yes"
break
fi
# Try to match a short setting, i.e. -o<value>
opt=$(expr -- "$1" : '^\(-[A-Za-z0-9]\)..*$' || true)
if [ -n "$opt" ] ; then
otype="short_setting"
value=$(expr -- "$1" : '^-.\(.*\)$')
break
fi
# Try to match a short flag, i.e. -o
opt=$(expr -- "$1" : '^\(-.\)$' || true)
if [ -n "$opt" ] ; then
otype="short_flag"
value="yes"
break
fi
panic "Unknown option '$1'. Use --help for list of valid values."
done
#echo "Found opt='$opt' otype='$otype' value='$value'"
name=$(_shu_dashes_to_underscores $opt)
found=0
for xopt in $OPTIONS; do
if [ "$name" != "$xopt" ] ; then
continue
fi
# Check that the type is correct here
#
# This also allows us to handle -o <value> as -o<value>
#
xotype=$(_shu_option_get_attr $name otype)
if [ "$otype" != "$xotype" ] ; then
case "$xotype" in
"short_flag")
_shu_option_panic_no_args "$opt"
;;
"short_setting")
if [ -z "$2" ] ; then
_shu_option_panic_missing_arg "$opt"
fi
value="$2"
shift
;;
"long_flag")
_shu_option_panic_no_args "$opt"
;;
"long_setting")
_shu_option_panic_missing_arg "$opt"
;;
esac
fi
found=1
break
break
done
if [ "$found" = "0" ] ; then
panic "Unknown option '$opt'. See --help for usage."
fi
# Set variable or launch option-specific function.
varname=$(_shu_option_get_attr $name varname)
if [ -n "$varname" ] ; then
var_assign $varname "$value"
else
eval $(_shu_option_get_attr $name funcname) \'"$value"\'
fi
shift
done
}
_shu_do_option_help () {
_shu_option_print_help
exit 0
}
option_register "--help" _shu_do_option_help "Print this help."
option_register "--verbose" increment_verbosity "Increment verbosity."
option_register "--verbosity=<level>" set_verbosity "Set verbosity level."
option_register "--quiet" decrement_verbosity "Decrement verbosity."