#! /usr/bin/env bash

#+
# NAME:
#	get_options.sh
# PURPOSE:
#	Parses command line options for a bash script
# CATEGORY:
#	general/bash
# CALLING SEQUENCE:
#	get_options $*
# INPUTS:
#	$*			all cmd line arguments
# OUTPUTS:
#	One variable for each of the options defined in array VALID_OPTIONS.
#	Variable OPTIONS_ARGV contains everything that was not interpreted
#	as an option. The same list of arguments is stored in array
#	OPTIONS_ARGS, but to be available to caller it must be declared
#	explicitly with 'declare -A OPTIONS_ARGS'
# EXAMPLE
#	VERSION_MESSAGE="Version 2.11 --- Paul Hick (UCSD/CAIDA; pphick@caida.org) --- 16-Jul-2012"
#	declare -A OPTIONS_ARGS
#	declare -A VALID_OPTIONS
#	declare -A VALID_DESCRIPTION
#	VALID_OPTIONS=(	\
#		[-V,--version]=bool-VERSION		\
#		[-h,--help]=bool-HELP			\
#	)
#	. get_options.sh
#	get_options $*
#	unset get_options
#
#	NARGV=${#OPTIONS_ARGS[@]}
#	ARGV1="${OPTIONS_ARGS[0]}"
#	ARGV2="${OPTIONS_ARGS[1]}"
#
# PROCEDURE:
#	Short and long options can be combined as
#		[-X,--word-x --word-y]=type-VAR_NAME_X:DEFAULT::RESTRICTIONS
#	The short option is optional. There must be at least one long
#	option present. Allthough not strictly necessary, it is
#	recommended to use only one short option, and put it at the
#	start of the definition as shown above] (this will ensure an
#	orderly display of options with the --options keyword).
#
#	The entry between brackets (-X,--letter-x) and the name following the
#	equal sign (VAR_NAME_X or bool-VAR_NAME_X) are mandatory. The components
#	following the single colon (DEFAULT) and double colon (RESTRICTIONS)
#	are optional.
#
#	The entry between brackets is the cmd line option as specified
#	when calling the command (for one-letter options it is permitted
#	to concatenate, i.e. '-xyz' is the same as '-x -y -z').
#
#	The entry VAR_NAME_X is the name of a bash variable that will be set.
#
#	The "type" prefix can be set to 'bool', 'integer', 'array', 'string'
#	or can be left undefined. An undefined type is the same as 'string'.
#
#	'bool' indicates a boolean option (i.e. an option that is 'off' by default,
#	and 'on' only if specified on the cmd line. Boolean options cannot be given
#	an explicit value through the cmd line: the bash variable will always
#	exist and will be set to 0 (not set, 'off') or 1 (set, 'on').
#
#	'integer' indicates an integer option. In this case the input value will be
#	explicitly tested to be a numerical value. The DEFAULT and RESTRICTIONS
#	(if specified) must also be integer.
#
#	'array' indicates an array variable, and is set using a list of key, value
#	pairs for an associative array, or (TODO) as a list of values only for
#	a regular array.
#
#	The entry DEFAULT is an optional default value for VAR_NAME_X if it is not
#	specified. For boolean options the default is ignored.
#	If the default specification is omitted then VAR_NAME_X will not be defined
#	if the corresponding option is not present on the cmd line.
#
#	The entry RESTRICTIONS represents a restriction on the allowed value of the
#	key. This can be an integer range specified as a pair of integers separated
#	by a dash, e.g. '1-5'. Alternatively, it can be a set of strings specified
#	as a comma-separated list, e.g. 'one,two,three'. An 'exit 1' is executed
#	if the input value does not match the restriction.
#
#	In addition to VAR_NAME the variable var_name (lowercase) is set to the
#	string used on the cmd line to invoke the option, e.g. if --help is used
#	the the bash variable help="--help" is set.
#
#	Two types of options are common:
#	'-x', '--letter-x'
#		boolean on/off switches get an implicit ON value of 1, and must
#		be defined as [-X]=bool-VAR_NAME_X.
#		VAR_NAME_X will be 1 (on) if -x is set, or 0 (off) if not
#
#	'-x value', '-x=value', '--letter-x value', '--letter-x=value'
#		Usually defined as [-X]=VAr_NAME_X
#		VAR_NAME_X is set to the string 'value'. If the option is not used,
#		then VAR_NAME_X will be undefined.
#
#	Array variables are specified as:
#	'--array key1:value1,key2:value2,key3:value3'
#		Defined as [--array]=array-VAR_NAME_X
#		VAR_NAME_X is set using the specified key,value pairs
#			VAR_NAME_X[key1]=value1   , etc.
#		Note that the array needs to be declared explicitly using
#			declare -A VAR_NAME_X
#		(this is due to a limitation in scope for bash arrays, I think)
#	
#	Putting -- (double-dash, followed by a space) on the cmd line will
#	terminate the search for options. Everything after the double-dash
#	ends up in OPTIONS_ARGV
#
#	There are five 'reserved' options which are always available:
#	-o,--options    prints a list of available options.
#                       Sets variable __OPTIONS__
#	-h,--help       print the documentation header (everything between
#	                lines "#+" and "#-").
#	                Sets variable __HELP__
#	-n,--dry-run    can be used to define a 'dry run'
#	                Sets variable DRYRUN
#	   --debug <n>  verbose output
#	                Sets variable DEBUG
#	-v,--verbose    verbose output; same as --debug 1
#	                Sets variable VERBOSE
#	-V,--version    prints the version message in VERSION_MESSAGE
#	                Sets variable __VERSION__
#
#	--debug is an integer option; the other four are boolean.
#
#	By default, unrecognized options results in an abort, with an error
#	message pointing out the error.
#
#	Instead of aborting it is possible to collect these unrecognized
#	options in a single variable. To activate this option set
#		__DEPTH_CHARGE__=1
#	prior to calling get_options. The unrecognized variables are then
#	collected in __depth_charge__. In theory this could be passed as
#	argument to a called script.
#	This is still experimental, so use at your own risk !!
#
#	By default cmd-line keywords can be abbreviated, as long as the
#	abbreviations identifies a unique keyword. To override, and force
#	that keywords must be completely specified set:
#		__NO_ABBREVIATIONS__=1
# MODIFICATION HISTORY:
#	JUL-2011, Paul Hick (UCSD/CAIDA)
#	AUG-2011, Paul Hick (UCSD/CAIDA)
#		Added -v (--verbose) as "reserved" option.
#		Substantial rewrite to remove restriction on providing defaults
#		for non-boolean options.
#		Added test for presence of reserved options
#	JUN-2012, Paul Hick (UCSD/CAIDA)
#		Added array OPTIONS_ARGS for easier access to remaining
#		(unprocessed) cmd line arguments.
#		--options now prints a list of options sorted alphabetically
#	SEP-2012, Paul Hick (UCSD/CAIDA)
#		Added capability to limit input values for keys to a specific
#		set of strings, or range of integers.
#	OCT-2012, Paul Hick (UCSD/CAIDA)
#		Added __DEPTH_CHARGE__ option to capture unrecognized options
#		into the variable __depth_charge__
#	JAN-2013, Paul Hick (UCSD/CAIDA; pphick@caida.org)
#		Added code to options_set_key to allow for abbreviation
#		of cmd line arguments. __NO_ABBREVIATIONS__ can be set to suppress
#		this (in which case keywords must match exactly).
#		Added reserved option DEBUG
#		Added tiny_* echo routines.
#		Fair amount of changes to handle number of patological inputs.
#		Long options are now mandatory; a single short option can
#		be used alongside a long option.
#-

. tiny_bits.sh

options_set_value () {

	#+
	# NAME:
	#	options_set_value
	# PURPOSE:
	# CALLING SEQUENCE:
	#	options_set_value <cmd-line-chunk>
	# INPUTS:
	#	<cmd-line-chunk>	piece of cmd line following
	#						an equal sign or a space, and
	#						representing the value for an option
	#	<other-chunk>		the cmd-line chunk before the equal
	#						sign or space
	#						(is it not better to use a piece of 'one_name'
	#						here?? Preferably the long option def??)
	#
	#	In addition, up to 6 global bash variables set
	#	by the last call to options_set_key are available:
	#		one_name, one_control, one_argument, one_type, one_range, one_default
	#
	# MODIFICATION HISTORY:
	#	JAN-2013, Paul Hick (UCSD/CAIDA; pphick@caida.org)
	#		Added documentation.
	#-

	local __CALLER__="options_set_value"

	if [ -z "$one_type" ]; then

		if [ -n "$1" ]; then
			tiny_die "syntax error: value '$1' cannot be assigned"
		fi

	elif [ $one_type = "__depth_charge__" ]; then

		if [ -n "$2" ]; then
			if [ -z "$__depth_charge__" ]; then
				__depth_charge__="$one_name"
			else
				__depth_charge__+=" $one_name"
			fi
			if [ -n "$1" ]; then
				__depth_charge__+=" $1"
			fi
		fi

	else		# Valid option

		local value
		eval value=\$$one_control
		if [ -n "$value" ]; then
			tiny_die "control variable '$one_control' already set to '$value'"	
		fi

		if [ $one_type = "a" ]; then	# Type: 'array'

			# This section has not been tested very well
			# The 'declare' does not work for some reason.
			# The array is created correctly, but it looks like it
			# has local scope only. To make this work the array needs to
			# be explicitly declared by the calling script

			# declare -A $one_control

			local value=$1

			if [[ $value =~ : ]]; then

				# Construct an associative array of key-value pairs

				local looking_for_key=1
				local is_key
				local to_colon
				local to_comma
				local field
				local key

				while [[ $value =~ (:|,) ]]; do
					to_colon=${value%%:*}
					to_comma=${value%%,*}

					if [ ${#to_colon} -lt ${#to_comma} ]; then
						field=$to_colon
						if [ $looking_for_key -eq 0 ]; then
							tiny_die "expected to find value at start of '$value'; found key '$field' instead"
						fi
						if [ -z "$field" ]; then
							tiny_die "found empty key at start of '$value'"
						fi
						is_key=1
						value=${value#*:}
						looking_for_key=0
					else
						field=$to_comma
						if [ $looking_for_key -eq 1 ]; then
							tiny_die "expected to find key at start of '$value'; found value '$field' instead"
						fi
						is_key=0
						value=${value#*,}
						looking_for_key=1
					fi

					if [ $is_key -eq 1 ]; then
						key=$field
					else
						eval $one_control["\$key"]="\$field"
					fi
				done

				if [ $looking_for_key -eq 1 ]; then
					field=$value
					if [ -z "$field" ]; then
						tiny_die "found empty key at end of '$1'"
					fi
					tiny_die "missing value for key '$field' at end of '$1'"
				fi

				eval $one_control[\$key]="\$value"

			else			# Construct a regular array

				local to_comma
				local key=-1

				while [[ $value =~ , ]]; do
					((key++))
					to_comma=${value%%,*}
					value=${value#*,}
					eval $one_control["\$key"]="\$to_comma"
				done

				((key++))
				eval $one_control["\$key"]="\$value"

			fi

		else

			# Some sanity checks:
			# - if a range is specified, make sure it is respected
			# - Check that integer type only receives a numerical value

			local atype=${__valid_types__[$one_type]}

			if [ -n "$one_range" ]; then
				if [[ $one_range =~ ^([0-9]+)-([0-9]+)$ ]]; then	# Numerical range
					local lower_limit=${BASH_REMATCH[1]}
					local upper_limit=${BASH_REMATCH[2]}
					if [[ sa =~ $one_type ]]; then
						tiny_die "cannot restrict '$atype' key '$one_name' to numerical range '$one_range', must be 'integer'"
					fi
					if [[ ! $1 =~ ^[0-9]+$ ]]; then
						tiny_die "illegal value '$1' for '$atype' key '$one_name', numerical value required for match to range '$one_range'"
					fi
					if [ $1 -lt $lower_limit -o $1 -gt $upper_limit ]; then
						tiny_die "illegal value '$1' for key '$one_name', allowed values are '$one_range'"
					fi
				else
					if [[ ! $1 =~ ^($one_range)$ ]]; then			# Set of strings
						tiny_die "illegal value '$1' for key '$one_name', allowed values are '$one_range'"
					fi
				fi
			elif [ $one_type = 'i' ]; then
				if [[ ! $1 =~ ^[0-9]+$ ]]; then
					tiny_die "illegal value '$1' for '$atype' key '$one_name', numerical value required"
				fi
			fi

			eval $one_control="\$1"			# Assign value to control variable

		fi

		# $2 is the keyword name as specified on the command line (e.g. --help).
		# (the actual matter does not matter)
		# This is assigned to the lowercase version of the bash variable used to
		# store the input value.
		# So for the boolean option
		#	[--help]=bool-HELP
		# if the bash variable 'HELP' is set to 1, the variable 'help' is set to '--help'
		# For the string option
		#	[--allow-dups]=ALLOW_DUPS:no
		# if the bash variable 'ALLOW_DUPS is set to 'yes', the variable 'allow-dups'
		# is set to '--allow_dups yes'.

		if [ -n "$2" ]; then
			options_base_arg "$one_name"
			if [ -n "$1" -a $one_type != b ]; then
				base_arg+=" $1"
			fi
			eval $one_argument="\$base_arg"
		fi

	fi

	options_clear_key
}

options_base_arg () {
	if [[ $1 =~ -- ]]; then
		base_arg="--${1#*--}"	# Remove everything before the first --
	else
		base_arg="-${1#*-}"		# Remove everything before the first -
	fi
	base_arg=${base_arg%%,*}	# Remove everything after the first ,
}

options_clear_key () {
	unset one_name
	unset one_type
	unset one_control
	unset one_argument
	unset one_default
	unset one_range
}

options_set_key () {
	#+
	# NAME:
	#	options_set_key
	# PURPOSE:
	#	Processing a piece of the options specified on the
	#	command line.
	# CALLING SEQUENCE:
	#	options_set_key <cmd-line-chunk>
	# INPUTS:
	#	cmd-line-chunk	piece of cmd line (e.g. '--dry-run').
	#					The piece starts with '-' or '--',
	#					and continues upto the next space or '='.
	# OUTPUTS:
	#	Up to 6 global bash variables are set:
	#		one_name, one_control, one_argument, one_type,
	#		one_range, one_default.
	#
	#	one_name
	#
	#	If the cmd line chunk is not recognized as a valid
	#	key in VALID_OPTIONS it is set to the whole input chunk
	#	(this becomes a 'depth_charge' option)
	#	In this case one_type='__depth_charge__'.
	#
	#	If the chunk is recognized as a valid option,
	#	it is set to the matching VALID_OPTIONS key.
	#	example: '--dry-run,-n'.
	#	In this case one_type is one of 'b'(ool), 'i'(nteger),
	#	's'(tring) or 'a'(rray)
	#
	#	one_type
	#
	#	one of: 'b','i','s','a' or '__depth_charge__'
	#	See description of 'one_name' above.
	#
	#	For an unrecognized 'depth_charge' chunk no other global vars
	#	are set.
	#
	#	For a recognized option from VALID_OPTIONS, two additial
	#	global var are always set:
	#
	#	one_control
	#
	#	the bash control variable that will be assigned the value
	#	specified on the cmd line
	#	example: DRYRUN (must contain at least one uppercase char
	#	to differentiate it from 'one_argument')
	#
	#	one_argument
	#
	#	The lowercase version of 'one_control'. This receives
	#	a string describing the option as specified on the cmd line,
	#	example: '--dry-run', '--verbose 2'.
	#
	#	Two more global vars may or may not be defind:
	#
	#	one_range
	#
	#	An integer range of values if one_type='i', or a
	#	list of string, separated by a comma or vertical bar,
	#	if one_type='s'.
	#		
	#	one_default
	#
	#	Default value for 'string' or 'array' types.
	#	The default for 'integer' is zero. This is used if the
	#	options is not specified on the cmd line.
	#
	# RESTRICTIONS:
	#	The function will abort if one of these conditions is met:
	#	- the specified chunk matches more than one valid option
	#	- if the chunk cannot be matched to a valid option
	#	  (unless __DEPTH_CHARGE__ is set).
	#	- if the chunk matches a valid option for which no control
	#	  variable is specified in VALID_OPTIONS
	#	- if the chunk matches a valid option for which an invalid
	#	  type is specified in VALID_OPTIONS.
	#	- if the control variable does not contain any uppercase chars
	# MODIFICATION HISTORY:
	#	JAN-2013, Paul Hick (UCSD/CAIDA; pphick@caida.org)
	#		Added documentation.
	#-

	local __CALLER__="$__CALLER__[set_key]"

	# This cannot happen. If it does there is a bug somewhere
	# All global one_* vars should be unset at this point

	if [ -n "$one_type" ]; then
		tiny_die "oops, 'one_type' still defined to '$one_type'"
	fi

	local match=""
	local match_count=0

	# Check for $1="-". Abort for now.
	# TODO:
	# Should be possible to do something more useful
	# if VALID_OPTIONS[-] existed.
	# Also $1="--" if "--=xyz" on cmd line might be
	# useful if __NO_ABBREVIATIONS__ is set (otherwise it
	# $1 would match every long option!

	if [ "$1" = "-" -o "$1" = "--" ]; then
		tiny_die "standalone dash(es) specified, '$1'"
	fi

	if [ $__NO_ABBREVIATIONS__ -eq 1 ]; then

		local A;
		for A in "${!VALID_OPTIONS[@]}"; do
			if [[ ,$A, =~ ,$1, ]]; then
				if [ -n "$match" ]; then
					match+=" "
				fi
				match+="$A"
				((match_count++))
			fi
		done

	else

		# Loop over all valid key names (keys of
		# associative array VALID_OPTION)

		local A;
		for A in "${!VALID_OPTIONS[@]}"; do
			if [[ $A =~ ^$1|,$1 ]]; then
				if [ -n "$match" ]; then
					match+=" "
				fi
				match+="$A"
				((match_count++))
			fi
		done

	fi

	# Never happens if __NO_ABBREVIATIONS__ is set

	if [ $match_count -gt 1 ]; then
		tiny_die "option not unique: '$1' matches '$match'"
	fi

	if [ $match_count -eq 0 ]; then

		# No match found; abort unless __DEPTH_CHARGE__ is set
	
		if [ $__DEPTH_CHARGE__ -eq 0 ]; then
			tiny_die "unrecognized option '$1' encountered"
		fi

		one_name=$1				# Keyname from cmd line
		one_type="__depth_charge__"

	else

		if [[ ! $match =~ -- ]]; then
			tiny_die "option defs like '$match', containing short options only are not allowed"
		fi

		one_name=$match
		local one_option=${VALID_OPTIONS[$match]}

		if [[ "$one_option" =~ ::: ]]; then
			one_range=${one_option#*:::}		# Part after triple colon
			one_option=${one_option%:::*}		# Part before triple colon
		elif [[ "$one_option" =~ :: ]]; then
			one_range=${one_option#*::}			# Part after double colon
			one_option=${one_option%::*}		# Part before double colon
		fi

		if [ -n "$one_range" ]; then
			if [[ $one_range =~ , ]]; then		# Assume comma-separated list
				one_range=${one_range//,/|}		# Replace , by | (makes a regex)
			fi
		fi

		# Look for colon separating control var and default value

		if [[ "$one_option" =~ : ]]; then		# Colon present
			one_control=${one_option%:*}		# Part before colon
			one_default=${one_option#*:}		# Part after colon
		else
			one_control=$one_option
		fi

		# Look for dash in keyname, defining type

		if [[ "$one_control" =~ - ]]; then
			one_type=${one_control%-*}			# Part before dash
			one_control=${one_control#*-}		# Part after dash
			one_type=${one_type:0:1}
		else
			one_type="s"
		fi

		# Boolean type is same as integer with range 0-1
		# (and implicit default 0)

		if [ $one_type = b ]; then
			one_range="0-1"
		fi

		if [[ bi =~ $one_type ]] && [ -z "$one_default" ]; then
			one_default=0
		fi

		if [ -z "$one_control" ]; then
			tiny_die "missing control variable in '$one_name'"
		fi

		one_argument=${one_control,,}

		if [ $one_argument = $one_control ]; then
			tiny_die "control var '$one_control' for option '$one_name' contains no uppercase chars"
		fi

		if [ -z "$one_type" ]; then
			tiny_die "missing type specification in '$one_name'"
		fi

		if [[ ! bisa =~ $one_type ]]; then
			tiny_die "invalid type '${__valid_types__[$one_type]}' for '$one_name'; must be 'bool', 'integer', 'string' or 'array'"
		fi

	fi
}

options_set_missing_value () {

	local __CALLER__="options_set_missing_value"

	if [ -n "$one_type" ]; then
		if [ $one_type = "__depth_charge__" ]; then
			options_set_value "" set
		else
			if [[ 'sa' =~ $one_type ]]; then
				tiny_die "missing value for string or array option '$one_name'"
			fi
			options_set_value "1" set
		fi
	fi
}

get_options () {

	#local __CALLER__="get_options"

	local search_terminated=0
	local key
	local value
	local A
	local B

	# Make sure reserved options are not being used already.

	for A in -o --options -h --help -n --dry-run -v --verbose -V --version --debug; do
		if [ -n "${VALID_OPTIONS[$A]}" ] ; then
			tiny_die "'$A' is a reserved option, and cannot be redefined"
		fi
	done

	# Defined reserved options

	A="-o,--options"
        VALID_OPTIONS[$A]=bool-__OPTIONS__
	VALID_DESCRIPTION[$A]="print list of options"

	A="-h,--help"
        VALID_OPTIONS[$A]=bool-__HELP__
	VALID_DESCRIPTION[$A]="print documentation header"

	A="-n,--dry-run"
        VALID_OPTIONS[$A]=bool-DRYRUN
	VALID_DESCRIPTION[$A]="make 'dry run'"

	A="-v,--verbose"
        VALID_OPTIONS[$A]=bool-VERBOSE
	VALID_DESCRIPTION[$A]="verbose output; same as '--debug 1'"

	A="--debug"
        VALID_OPTIONS[$A]=integer-DEBUG:::0-999
	VALID_DESCRIPTION[$A]="debug level"

	if [ -n "$VERSION_MESSAGE" ]; then
		A="-V,--version"
        	VALID_OPTIONS[$A]=bool-__VERSION__
		VALID_DESCRIPTION[$A]="print version message"
	fi

	declare A __valid_types__
	__valid_types__=(
		[b]="bool"		/
		[i]="integer"	/
		[s]="string"	/
		[a]="array"		/
	)

	((OPTIONS_I=-1))
	OPTIONS_ARGV=""

	if [ -z "$__DEPTH_CHARGE__" ]; then
		__DEPTH_CHARGE__=0
	fi
	if [ -z "$__NO_ABBREVIATIONS__" ]; then
		__NO_ABBREVIATIONS__=0
	fi

	# Loop over all command line options

	local INPUT_COMMAND="'`basename $0` $*'"

	#for A in $*; do
	while [ -n "$1" ]; do
		A=$1
		shift	

		if [ "$A" = "--" ]; then				# Terminate search for option
												# (everything following this goes into OPTIONS_ARGV)
			options_set_missing_value
			search_terminated=1

		elif [ $search_terminated -eq 1 ]; then	# Everything after '--' is non-option

			if [ "$A" != "--" ]; then
				((OPTIONS_I++))
				OPTIONS_ARGS[$OPTIONS_I]=$A
				OPTIONS_ARGV="$OPTIONS_ARGV $A"
			fi

		elif [[ $A =~ ^-- ]]; then				# Start of long option

			options_set_missing_value

			# Check for cmd-line syntax with equal sign separating
			# key and value: key=value

			key="${A%=*}"						# Part before equal sign (if present)
			options_set_key "$key"
			if [[ "$A" =~ = ]]; then			# Equal sign present
				if [ $one_type = b ]; then
					tiny_die "boolean option '$one_name' followed by '=' sign"
				fi
				options_set_value "${A#*=}" set	# Value is part following = sign
			elif [ $one_type = b ]; then
				options_set_missing_value
			fi

		elif [[ $A =~ ^- ]]; then				# Start of short option

			options_set_missing_value

			# The first char in $key is the dash (-).
			# If $key is more than two chars long, it is a concatenation
			# of multiple short options. Process $key one char at a time.
			# All except the last char in the concatenation must be boolean.

			local c=${A%=*}						# Part before equal sign
												# (whole $A if no equal sign present)
			local c=${#c}						# Number of chars in $A before equal sign
			((c--))								# Do not process the last char just yet

			local p=0
			if [ $c -eq 0 ]; then				# No char after dash
				key="-"
			else								# At least one char after dash
				local p=1						# Start at 2nd char (after dash)
				while [ $p -lt $c ]; do			# Process all short options in $key
					key="-${A:$p:1}"
					options_set_key "$key"
					if [[ "sa" =~ $one_type ]]; then
						tiny_die "'${__valid_types__[$one_key]}' option '$one_name' cannot be bundled in '$A'"
					fi
					options_set_missing_value
					((p++))
				done
				key="-${A:$p:1}"				 # Last short option in $A
			fi

			options_set_key "$key"		 		# Part before equal sign
			if [[ "$A" =~ = ]]; then			# Equal sign present
				if [ $one_type = b ]; then
					tiny_die "boolean option '$one_name' followed by '=' sign"
				fi
				options_set_value "${A#*=}" set	# Value is part following = sign
			elif [ $one_type = b ]; then
				options_set_missing_value
			fi
	
		else

			# $A does not start with a dash or double-dash so is either
			# a value or a part of argv

			if [ -z "$one_type" ]; then
				((OPTIONS_I++))
				OPTIONS_ARGS[$OPTIONS_I]=$A
				OPTIONS_ARGV="$OPTIONS_ARGV $A"
			elif [ $one_type = __depth_charge ]; then
				tiny_die "unable to unambiguously assign '$A' as value of depth-charge '$one_name'"
			else
				options_set_value "$A" set
			fi

		fi

	done

	options_set_missing_value

	# Loop over all options looking for options for which
	# the control variable has not been set yet. This means
	# that the option was not specified on the cmd line.
	# Set the control variable to 'one_default', if it
	# exists (it will exist for boolean and integer options.
	# The control variable may still be undefined
	# for types 'string' and 'array'

	for A in ${!VALID_OPTIONS[*]}; do
		options_base_arg "$A"
		options_set_key "$base_arg"
		eval value=\$$one_control
		if [ -z "$value" -a -n "$one_default" ]; then
			options_set_value "$one_default"
		else
			options_clear_key
		fi
	done

	if [ -n "$OPTIONS_ARGV" ]; then		# Strip leading white space
		OPTIONS_ARGV=${OPTIONS_ARGV:1}
	fi

	if [ $__HELP__ -eq 1 ]; then
		hash_documentation.sh $0
		exit 0
	fi

	if [ $__OPTIONS__ -eq 1 ]; then

		local sorted_options=`for A in ${!VALID_OPTIONS[*]}; do echo $A; done | sort`
		for A in $sorted_options; do
			local message=""
			if [[ $A =~ ^-- ]]; then
				message+="   "
			fi
			options_set_key $A
			message+="$A"
			if [ $one_type != b ]; then
				message+=" $one_control"
				if [ -n "$one_default" ]; then
					message+=" [$one_default]"
				fi
			fi
			options_clear_key
			tiny_warn_bare "$message"
			if [ -n "${VALID_DESCRIPTION[$A]}" ]; then
				tiny_yell_bare "        ${VALID_DESCRIPTION[$A]}"
			fi
		done

		exit 0
	fi

	if [ -n "$__VERSION__" ] && [ $__VERSION__ -eq 1 ]; then
		if [ -n "$VERSION_MESSAGE" ]; then
			echo "$VERSION_MESSAGE"
		else
			echo "(no version message variable; please, set VERSION_MESSSAGE)"
		fi

		exit 0
	fi

	# This is a bit ugly, but it has the advantage that
	# verbosity is controlled by VERBOSE only (and DEBUG
	# can be ignored). It has the disadvantage that if
	# VERBOSE -eq 1 (i.e. verbosity ON) would not work as
	# expected for VERBOSE > 1.

	if [ $DEBUG -gt $VERBOSE ]; then
		VERBOSE=$DEBUG
	fi

	# To see the output from the print messages set --debug 2 or 3

	tiny_debug "$INPUT_COMMAND"
	tiny_debug ">>>>>>>>>>"
	tiny_debug_bare "cmd line options used: '$*'"
	local key_found=0
	local argA
	for A in ${!VALID_OPTIONS[*]}; do
		options_set_key $A
		eval arg=\$$one_argument
		local message
		if [ -n "$arg" ]; then
			((key_found++))
			tiny_debug_bare " ${VALID_DESCRIPTION[$A]}"
			if [ $one_type = a ]; then
				eval local keys=\${!$one_control[*]}
				for B in $keys; do
					eval message=\"$one_control[$B]=\${$one_control[$B]}\"
					tiny_message_bare "    $message" 2
				done
				eval message=\"    \($one_argument=\'\$arg\'\)\"
			else
				eval message=\"\$one_control=\$$one_control \($one_argument=\'\$arg\'\)\"
			fi
			tiny_debug_bare "    $message"
		else
			tiny_message_bare "*${VALID_DESCRIPTION[$A]}" 3
			message=$one_control
			eval arg=\$$one_control
			if [ -n "$arg" ]; then
				message+="=$arg ($A)"
			else
				message+=" not set ($A)"
			fi
			tiny_message_bare "    $message" 3
		fi
		options_clear_key
	done
	if [ $key_found -eq 0 ]; then
		tiny_debug_bare "no options set"
	else
		tiny_debug_bare "$key_found option(s) set"
	fi
	tiny_debug_bare ""
	tiny_debug_bare "remaining (unprocessed) arguments returned in OPTIONS_ARGV:"
	if [ -n "$OPTIONS_ARGV" ]; then
		tiny_debug_bare "    OPTIONS_ARGV='$OPTIONS_ARGV'"
	else
		tiny_debug_bare "    (none)"
	fi
	if [ $__DEPTH_CHARGE__ -eq 1 ]; then
		tiny_debug_bare ""
		if [ -n "$__depth_charge__" ]; then
			tiny_debug_bare "__depth_charge__='$__depth_charge__'"
		else
			tiny_debug_bare "__depth_charge__='(none)'"
		fi
	fi

	tiny_debug "<<<<<<<<<<"

	unset options_set_key
	unset options_set_value
	unset __valid_types__
}
