Close #13 : cmdarg_helpers[] introduced, README documentation updated

This commit is contained in:
2014-05-20 08:49:46 -07:00
parent 83c4552371
commit b7f85914e7
3 changed files with 214 additions and 16 deletions

View File

@@ -167,6 +167,8 @@ cmdarg takes the pain out of creating your --help messages. For example, conside
-b,--boolean-thing : Boolean. Some boolean thing -b,--boolean-thing : Boolean. Some boolean thing
-a,--myarray v[, ...] : Array. Some array of stuff. Pass this argument multiple times for multiple values. -a,--myarray v[, ...] : Array. Some array of stuff. Pass this argument multiple times for multiple values.
You can change the formatting of help messages with helper functions. (see Helpers, below).
Setting arrays and hashes Setting arrays and hashes
========================= =========================
@@ -203,6 +205,40 @@ Similarly, cmdarg understands '--' which means "stop processing arguments, the r
... Cmdarg would parse -x and --longopt as expected, and then ${cmdarg_argv[0]} would hold "--some-thing-with-dashes", for your program to do with what it will. ... Cmdarg would parse -x and --longopt as expected, and then ${cmdarg_argv[0]} would hold "--some-thing-with-dashes", for your program to do with what it will.
Helpers
=======
cmdarg is meant to be extensible by default, so there are some places where you can hook into it to change cmdarg's behavior. By changing the members of the cmdarg_helpers hash, like this:
# Change the way arguments are described in --help
cmdarg_helpers['describe']=my_description_function
# Completely replace cmdarg's builtin --help message generator with your own
cmdarg_helpers['usage']=my_usage_function
## Description Helper
The description helper is used when you are happy with the overall structure of how cmdarg prints your usage message (header, required, optional, footer), but you want to change the way that individual arguments are described. You can do this by setting cmdarg_helpers['describe'] to the name of a bash function which accepts the following parameters (in order):
* $1 : long option to be described
* $2 : short option to be described
* $3 : argument type being described (will be one of ${CMDARG_TYPE_STRING}, ${CMDARG_TYPE_BOOLEAN}, ${CMDARG_TYPE_ARRAY} or ${CMDARG_TYPE_HASH})
* $4 : any default value that is set for the option being described
* $5 : The description for the option being described (as provided to 'cmdarg' previously)
* $6 : Flags for the option being described (a logically OR'ed bitmask of ${CMDARG_FLAG_NOARG}, ${CMDARG_FLAG_REQARG}, or ${CMDARG_FLAG_OPTARG} - although we specify this as a bitmask and advise you to treat it as such, in practice, this is usually an assignment of one of those 3 values)
* $7 : The name of any validator (if any) set for the option being described
This is every piece of information cmdarg keeps related to an argument (aside from its value). You can use these to describe the argument however you please. Your function must print the text description to stdout. The return value of your function is ignored.
For examples of this behavior, please see ./tests/test_helpers.sh
## Usage Helper
The usage helper is used when you want to completely override cmdarg's built in --help handler. Note that, when you override the usage helper, you will no longer benefit from the description helper, since that is called from inside of the default usage handler. If you override the usage helper, you will have to implement 100% of --help functionality on your own.
The short options for all specified arguments in cmdarg are kept in a hash ${CMDARG} which maps short arguments (-x) to long arguments (--long-version-of-x). However, it is not recommended that you iterate over this hash directly, as the order of hash key iteration is not guaranteed, so your --help message will change every time. To help with this, cmdarg populates two one-dimensional arrays, CMDARG_OPTIONAL and CMDARG_REQUIRED with the short options of all optional and require arguments, respectively. It is recommended that you iterate over these arrays instead of CMDARG to ensure an ordered output. It is further recommended that you still utilize cmdarg_describe to describe each individual argument, since this abstracts away the logic of how to get the flags, the type, etc of the argument, and lets you continue to provide a standard interface for your API developer(s).
For examples of this behavior, please see ./tests/test_helpers.sh, the "shunittest_test_describe_and_usage_helper" function.
getopt vs getopts getopt vs getopts
================= =================

View File

@@ -7,8 +7,8 @@ fi
CMDARG_FLAG_NOARG=0 CMDARG_FLAG_NOARG=0
CMDARG_FLAG_REQARG=1 CMDARG_FLAG_REQARG=2
CMDARG_FLAG_OPTARG=2 CMDARG_FLAG_OPTARG=4
CMDARG_TYPE_ARRAY=1 CMDARG_TYPE_ARRAY=1
CMDARG_TYPE_HASH=2 CMDARG_TYPE_HASH=2
@@ -93,7 +93,7 @@ function cmdarg
function cmdarg_info function cmdarg_info
{ {
# cmdarg <flag> <value> # cmdarg_info <flag> <value>
# #
# Sets various flags about your script that are printed during cmdarg_usage # Sets various flags about your script that are printed during cmdarg_usage
# #
@@ -109,30 +109,53 @@ function cmdarg_info
function cmdarg_describe function cmdarg_describe
{ {
local key default local longopt opt argtype default description flags validator
longopt=${CMDARG[$1]} longopt=${CMDARG[$1]}
opt=$1 opt=$1
if [ "${CMDARG_DEFAULT[$opt]}" != "" ]; then argtype=${CMDARG_TYPES[$longopt]}
default="(Default \"${CMDARG_DEFAULT[$opt]}\")" default=${CMDARG_DEFAULT[$opt]}
description=${CMDARG_DESC[$opt]}
flags="${CMDARG_FLAGS[$opt]}"
validator="${CMDARG_VALIDATORS[$opt]}"
${cmdarg_helpers['describe']} $longopt $opt $argtype "${default}" "${description}" "${flags}" "${validator}"
}
function cmdarg_describe_default
{
set -u
local longopt opt argtype default description flags validator
longopt=$1
opt=$2
argtype=$3
default="$4"
description="$5"
flags="$6"
validator="${7:-}"
set +u
if [ "${default}" != "" ]; then
default="(Default \"${default}\")"
fi fi
case ${CMDARG_TYPES[$longopt]} in case ${argtype} in
$CMDARG_TYPE_STRING) $CMDARG_TYPE_STRING)
echo "-${opt},--${longopt} v : String. ${CMDARG_DESC[$opt]} $default" echo "-${opt},--${longopt} v : String. ${description} ${default}"
;; ;;
$CMDARG_TYPE_BOOLEAN) $CMDARG_TYPE_BOOLEAN)
echo "-${opt},--${longopt} : Boolean. ${CMDARG_DESC[$opt]} $default" echo "-${opt},--${longopt} : Boolean. ${description} ${default}"
;; ;;
$CMDARG_TYPE_ARRAY) $CMDARG_TYPE_ARRAY)
echo "-${opt},--${longopt} v[, ...] : Array. ${CMDARG_DESC[$opt]}. Pass this argument multiple times for multiple values. $default" echo "-${opt},--${longopt} v[, ...] : Array. ${description}. Pass this argument multiple times for multiple values. ${default}"
;; ;;
$CMDARG_TYPE_HASH) $CMDARG_TYPE_HASH)
echo "-${opt},--${longopt} k=v{, ..} : Hash. ${CMDARG_DESC[$opt]}. Pass this argument multiple times for multiple key/value pairs. $default" echo "-${opt},--${longopt} k=v{, ..} : Hash. ${description}. Pass this argument multiple times for multiple key/value pairs. ${default}"
;; ;;
*) *)
echo "Unable to return string description for ${key}; unknown type ${CMDARG_TYPES[$opt]}" >&2 echo "Unable to return string description for ${opt}; unknown type ${argtype}" >&2
exit 1 exit 1
;; ;;
esac esac
} }
function cmdarg_usage function cmdarg_usage
@@ -264,12 +287,12 @@ function cmdarg_parse
else else
echo "Malformed argument: ${fullopt}" >&2 echo "Malformed argument: ${fullopt}" >&2
echo "While parsing: $@" >&2 echo "While parsing: $@" >&2
cmdarg_usage >&2 ${cmdarg_helpers['usage']} >&2
exit 1 exit 1
fi fi
if [[ "$opt" == "h" ]] || [[ "$longopt" == "help" ]]; then if [[ "$opt" == "h" ]] || [[ "$longopt" == "help" ]]; then
cmdarg_usage >&2 ${cmdarg_helpers['usage']} >&2
exit 1 exit 1
fi fi
@@ -285,7 +308,7 @@ function cmdarg_parse
cmdarg_set_opt "${CMDARG[$opt]}" "$optarg" cmdarg_set_opt "${CMDARG[$opt]}" "$optarg"
else else
echo "Unknown argument or invalid value : -${opt} | --${longopt}" >&2 echo "Unknown argument or invalid value : -${opt} | --${longopt}" >&2
cmdarg_usage >&2 ${cmdarg_helpers['usage']} >&2
exit 1 exit 1
fi fi
done done
@@ -322,7 +345,7 @@ function cmdarg_parse
echo "Missing arguments : ${missing}" echo "Missing arguments : ${missing}"
fi fi
echo echo
cmdarg_usage >&2 ${cmdarg_helpers['usage']} >&2
exit 1 exit 1
fi fi
@@ -403,4 +426,9 @@ declare -xA CMDARG_FLAGS
declare -xA CMDARG_TYPES declare -xA CMDARG_TYPES
# Array of all elements found after -- # Array of all elements found after --
declare -xa cmdarg_argv declare -xa cmdarg_argv
# Hash of functions that are used for user-extensible functionality
declare -xA cmdarg_helpers
cmdarg_helpers['describe']=cmdarg_describe_default
cmdarg_helpers['usage']=cmdarg_usage
CMDARG_GETOPTLIST="h" CMDARG_GETOPTLIST="h"

134
tests/test_helpers.sh Normal file
View File

@@ -0,0 +1,134 @@
#!/usr/bin/bash4
source $(dirname ${BASH_SOURCE[0]})/../cmdarg.sh
function shunittest_test_usage_helper
{
function usage_helper
{
echo "LOL I AM A HELPER"
return 0
}
function parser {
cmdarg_purge
cmdarg_helpers['usage']=usage_helper
cmdarg_parse --help
}
[[ "$(parser 2>&1)" == "LOL I AM A HELPER" ]] || return 1
}
function shunittest_test_describe_helper
{
function always_succeed
{
return 0
}
function describe
{
set -u
local longopt opt argtype default description
longopt=$1
opt=$2
argtype=$3
default="$4"
description="$5"
flags="$6"
validator="$7"
set +u
echo "${opt}:${longopt}:${argtype}:${description}:${default}:${flags}:${validator}"
}
cmdarg_helpers['describe']=describe
function parser
{
declare -a array
declare -A hash
cmdarg_purge
cmdarg 's:' 'string' 'some string' '12345' always_succeed
cmdarg 'b' 'boolean' 'some boolean'
cmdarg 'a?[]' 'array' 'some array'
cmdarg 'H?{}' 'hash' 'some hash'
set -x
[[ "$(cmdarg_describe s)" == "s:string:${CMDARG_TYPE_STRING}:some string:12345:${CMDARG_FLAG_REQARG}:always_succeed" ]] || return 1
[[ "$(cmdarg_describe b)" == "b:boolean:${CMDARG_TYPE_BOOLEAN}:some boolean::${CMDARG_FLAG_NOARG}:" ]] || return 1
[[ "$(cmdarg_describe a)" == "a:array:${CMDARG_TYPE_ARRAY}:some array::${CMDARG_FLAG_OPTARG}:" ]] || return 1
[[ "$(cmdarg_describe H)" == "H:hash:${CMDARG_TYPE_HASH}:some hash::${CMDARG_FLAG_OPTARG}:" ]] || return 1
set +x
}
parser
}
# This test adds no value to the test suite, it simply serves as an example of how to override
# both the describe AND usage helpers
function shunittest_test_describe_and_usage_helper
{
function always_succeed
{
return 0
}
function describe
{
set -u
local longopt opt argtype default description
longopt=$1
opt=$2
argtype=$3
default="$4"
description="$5"
flags="$6"
validator="$7"
set +u
echo "${opt}:${longopt}:${argtype}:${description}:${default}:${flags}:${validator}"
}
function usage
{
echo "I ignore the default header and footer, and substitute my own."
echo "I do not indent my arguments or separate optional and required."
# cmdarg helpfully separates options into OPTIONAL or REQUIRED arrays
# so that you don't have to sort the keys for uniform --help message output
# and so you can easily break arguments out into required/optional blocks
# in the usage message ... our helper doesn't care, it just prints them all
# together, but it still uses the sorted lists.
for shortopt in ${CMDARG_OPTIONAL[@]} ${CMDARG_REQUIRED[@]}
do
cmdarg_describe $shortopt
done
}
cmdarg_helpers['describe']=describe
cmdarg_helpers['usage']=usage
function parser
{
declare -a array
declare -A hash
cmdarg_purge
cmdarg 's:' 'string' 'some string' '12345' always_succeed
cmdarg 'b' 'boolean' 'some boolean'
cmdarg 'a?[]' 'array' 'some array'
cmdarg 'H?{}' 'hash' 'some hash'
cmdarg_parse --help
}
output="I ignore the default header and footer, and substitute my own.
I do not indent my arguments or separate optional and required.
s:string:${CMDARG_TYPE_STRING}:some string:12345:${CMDARG_FLAG_REQARG}:always_succeed
b:boolean:${CMDARG_TYPE_BOOLEAN}:some boolean::${CMDARG_FLAG_NOARG}:
a:array:${CMDARG_TYPE_ARRAY}:some array::${CMDARG_FLAG_OPTARG}:
H:hash:${CMDARG_TYPE_HASH}:some hash::${CMDARG_FLAG_OPTARG}:"
set +e
capture="$(parser 2>&1)"
if [[ "${capture}" != "$output" ]]; then
echo "${capture}" > /tmp/$$.parser 2>&1
echo "${output}" > /tmp/$$.output
diff -y /tmp/$$.output /tmp/$$.parser
return 1
fi
set -e
}