# -*- shell-script -*-
# Copyright 2008-2011 Bob Hepple
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# http://sourceforge.net/projects/process-getopt
# http://process-getopt.sourceforge.net
# http://bhepple.freeshell.org/oddmuse/wiki.cgi/process-getopt

# $Id: process-getopt,v 1.36 2011/07/15 18:16:36 bhepple Exp $

# A wrapper around getopt.

# It buys you:
# o all the goodness of getopt
# o define options in one central place together with descriptions. 
# o fewer 'magic' and duplicated values in your code
# o Better consistency between the getopt calling parameters, the case
#   statement processing the user's options and the help/man pages
# o less spaghetti in your own code
# o easier to maintain
# o help-page and man-page printing - all from the same data sources
# o checking of option consistency at runtime
# o range checking of option values at runtime
# o pretty easy to use
# o portable to OS's without long option support - the help page
#   adapts too

# It's easier to see it in an example than to read an explanation, so
# look at boilerplate first, and then see the man page
# process-getopt(1).

# tested on bash-2.04
# Certainly would not work on plain old sh, csh, ksh ... .

# try and write this test to be legal in sh/bash-1.x:
__PG_GOOD_ENOUGH=""
if [ "x$BASH_VERSION" = "x" -o "x$BASH_VERSINFO" = "x" ]; then
    :
elif [ "${BASH_VERSINFO[0]}" -gt 2 ]; then
    __PG_GOOD_ENOUGH="1"
elif [ "${BASH_VERSINFO[0]}" -eq 2 -a "${BASH_VERSINFO[1]}" -ge 4 ]; then
    __PG_GOOD_ENOUGH="1"
fi
if [ "x$__PG_GOOD_ENOUGH" = "x" ]; then
    echo "This version of the shell does not support process-getopt" >&2
    echo "bash-2.04 or later is required" >&2
    exit 1
fi

__PG_process_getopt_usage() {
    echo "Usage: $PROG [ OPTIONS ]"
    echo
    echo "Normally sourced by other scripts rather than run by itself."
    echo "See the man page for details of functions that $PROG provides."
    echo "See http://sourceforge.net/projects/process-getopt"
    echo "See http://process-getopt.sourceforge.net"
    echo "See http://bhepple.freeshell.org/oddmuse/wiki.cgi/process-getopt"
    echo "When called without options, does nothing. Otherwise:"
    echo
    echo "Options:"
    print_all_opts
    echo "  --$__GPG_PRINTMAN_loption          print out a man page stub"
}

# make sure our address space is clean - particularly for when we call
# one shell from another and both use process-getopt. Or possibly if
# we're building something like openssl with global-options, commands
# and command-options, eg. openssl -n rsa -b arg arg etc We'd use
# process-getopt with STOP_ON_FIRST_NON_OPT set to process the global
# options then call clean_process_getopt and then use process-getopt
# again to process the command options. 
clean_process_getopt() {
	# note: the specifically does not unset _GPG_* parameters - these
	# are global constants and may still be needed.
    unset ${!__PG_*}
}

process_getopt_version() {
    echo "$__GPG_VERSION"
}

add_opt() {
    # We need to be scrupulous here - because we're a callback, we see
    # all the caller's parameters - so we must namespace them all
    # (even if 'local') to stand a chance of not overwriting theirs!!!
    # parameters: use "" as the placeholder for a missing arg
    local __PG_NAME __PG_DESC __PG_SOPT __PG_SARG __PG_LOPT __PG_LARG __PG_TYPE __PG_RANGE
    __PG_NAME="$1" # name of the option - essential
    __PG_DESC="$2" # essential except for 'silent/secret' options
    __PG_SOPT="${3:-}" # short option letter - optional
    __PG_SARG="${4:-}" # argument label for the short option - optional
    __PG_LOPT="${5:-}" # long option - optional
    __PG_LARG="${6:-}" # argument label for the long option - optional
    __PG_TYPE="${7:-}" # type of the argument - optional
    __PG_RANGE="${8:-}" # range for the argument - optional
    local __PG_HAS_ARGS
    local __PG_L
    local __PG_ALLOWED_CHARS='[a-zA-Z0-9_][a-zA-Z0-9_]*'
    local __PG_OPT
    local __PG_SUFFIX

    [[ "$__PG_NAME" ]] || { 
        echo "$PROG: process-getopt: add_opt requires a name" >&2
        exit 1
    }
    # [[ "$__PG_NAME" =~ $__PG_ALLOWED_CHARS ]] || { ... this needs bash-3
    [[ `echo $__PG_NAME |tr -d '[:alnum:]' |tr -d '[_]'` ]] && {
        echo "$PROG: process-getopt: apt_opt: __PG_NAME (\"$__PG_NAME\") must obey the regexp $__PG_ALLOWED_CHARS" >&2
        exit 1
    }

    # check at least a short or a long option is given:
    [[ "$__PG_SOPT$__PG_LOPT" ]] || {
        echo "$PROG: process-getopt: add_opt: option $__PG_NAME needs a short or a long option" >&2
        exit 1
    }

    # check that a helper function is available by calling it without an arg
    # it is supposed to do nothing - but if it is undefined then we get
    # error 127:
    local STAT=0
    ${__PG_NAME}$__GPG_FUNC_SUFFIX 2>/dev/null || STAT=$?
    if [[ $STAT -eq 127 ]]; then
        echo "$PROG: process-getopt: add_opt: option $__PG_NAME has no helper function $__PG_NAME$__GPG_FUNC_SUFFIX" >&2
        exit 1
    fi

    # force args to be consistent:
    [[ "$__PG_LOPT" ]] && [[ ! "$__PG_LARG" ]] && __PG_LARG="$__PG_SARG"
    [[ "$__PG_SOPT" ]] && [[ ! "$__PG_SARG" ]] && __PG_SARG="$__PG_LARG"

    [[ "$__PG_SARG$__PG_LARG" ]] && __PG_HAS_ARGS="A" || __PG_HAS_ARGS=""

    # check it's not already in use
    for __PG_OPT in ${__PG_OPTION_LIST:-}; do 
        if [[ "$__PG_NAME" = "$__PG_OPT" ]]; then
            echo "$PROG: process-getopt: add_opt: option name \"$__PG_NAME\" is already in use" >&2
            exit 1
        fi
        # check that the (short) option letter is not already in use -
        # cover the case of with and without args:
        if [[ "$__PG_SOPT" ]]; then
            for __PG_SUFFIX in "" "A"; do
                __PG_L="__PG_SOPT${__PG_SUFFIX}_$__PG_OPT"
                if [[ "$__PG_SOPT" = "${!__PG_L:-}" ]]; then
                    echo "$PROG: process-getopt: add_opt: short option \"$__PG_SOPT\" is already in use by \
$__PG_OPT" >&2
                    exit 1
                fi
            done
        fi
        # check that the (long) option name is not already in use -
        # cover the case of with and without args:
        if [[ "$__PG_LOPT" ]]; then
            for __PG_SUFFIX in "" "A"; do
                __PG_L="__PG_LOPT${__PG_SUFFIX}_$__PG_OPT"
                if [[ "$__PG_LOPT" = "${!__PG_L:-}" ]]; then
                    echo "$PROG: process-getopt: add_opt: long option \"$__PG_LOPT\" is already in use by $__PG_OPT" >&2
                    exit 1
                fi
            done
        fi
    done

    if [[ "$__PG_SOPT" ]]; then
        [[ ${#__PG_SOPT} -ne 1 ]] && {
            echo "$PROG: process-getopt: add_opt: short option \"$__PG_SOPT\" for option $__PG_NAME is not a single character" >&2
            exit 1
        }
        export __PG_SOPT${__PG_HAS_ARGS}_$__PG_NAME="$__PG_SOPT"
        [[ "$__PG_SARG" ]] && export __PG_ARG_SA_$__PG_NAME="$__PG_SARG"
    fi

    if [[ "$__PG_LOPT" ]]; then
        export __PG_LOPT${__PG_HAS_ARGS}_$__PG_NAME="$__PG_LOPT"
        [[ "$__PG_LARG" ]] && export __PG_ARG_LA_$__PG_NAME="$__PG_LARG"
    fi

    if [[ "$__PG_DESC" ]]; then
        export __PG_DESC_OPT_$__PG_NAME="$__PG_DESC"
    fi

	# use a while loop just for the 'break':
	[[ "$__PG_HAS_ARGS" ]] && {
		while [[ "$__PG_TYPE" ]]; do
			case "$__PG_TYPE" in
				i|I)
					[[ "x$__PG_RANGE" = x ]] && break
					echo "$__PG_RANGE" | egrep -q "$__GPG_INT_RANGE_REGEX" && break
					;;
				r|R|f|F)
					[[ "x$__PG_RANGE" = x ]] && break
					echo "$__PG_RANGE" | egrep -q "$__GPG_FLOAT_RANGE_REGEX" && break
					;;
				s|S)
					[[ "x$__PG_RANGE" = x ]] && break
					echo "" | egrep -q "$__PG_RANGE"
					[[ $? -eq 2 ]] || break
					;;
				a|A) 
					[[ "x$__PG_RANGE" = x ]] || break
					;;
				u|U)
					__PG_TYPE=s
					__PG_RANGE="$__GPG_URL_REGEX"
					break
					;;
			esac
			echo "$PROG: process-getopt: add_opt: bad argument type ('$__PG_NAME') or range ('$__PG_RANGE') for option '$__PG_NAME'." |fmt >&2
			exit 1
		done
	}
	export __PG_TYPE_$__PG_NAME="$__PG_TYPE"
	export __PG_RANGE_$__PG_NAME="$__PG_RANGE"

    __PG_OPTION_LIST="${__PG_OPTION_LIST:-} $__PG_NAME"
}

get_opt_letter() {
    local __PG_NAME="$1"
    local __PG_L="__PG_SOPT_$__PG_NAME" 
    
    [[ "${!__PG_L:-}" ]] || __PG_L="__PG_SOPTA_$__PG_NAME"
    echo -n  "${!__PG_L:-}"
}

get_opt_string() {
    local __PG_NAME="$1"
    local __PG_L="__PG_LOPT_$__PG_NAME" 
    
    [[ "${!__PG_L:-}" ]] || __PG_L="__PG_LOPTA_$__PG_NAME"
    echo -n  "${!__PG_L:-}"
}

get_opt_sarg() {
    local __PG_NAME="$1"
    local __PG_L="__PG_ARG_SA_$__PG_NAME" 
    
    echo -n "${!__PG_L:-}"
}

get_opt_larg() {
    local __PG_NAME="$1"
    local __PG_L="__PG_ARG_LA_$__PG_NAME" 
    
    echo -n "${!__PG_L:-}"
}

get_opt_type() {
	local __PG_NAME="$1"
	local __PG_T="__PG_TYPE_$__PG_NAME"

	echo -n "${!__PG_T:-}"
}

get_opt_range() {
	local __PG_NAME="$1"
	local __PG_R="__PG_RANGE_$__PG_NAME"

	echo -n "${!__PG_R:-}"
}

get_opt_desc() {
    local __PG_NAME="$1"
    local __PG_L="__PG_DESC_OPT_$__PG_NAME" 
    
    echo -n "${!__PG_L:-}"
	local __PG_TYPE=$(get_opt_type "$__PG_NAME")
	[[ "x$__PG_TYPE" = x ]] || {
		echo -n ". Must be of type '$__PG_TYPE'"
		local __PG_RANGE=$(get_opt_range "$__PG_NAME")
		[[ "x$__PG_RANGE" = x ]] || {
			case "$__PG_TYPE" in
				s|S)
					echo -n " fitting regex '$__PG_RANGE'."
					;;
				*)
					echo -n " in the range '$__PG_RANGE'."
					;;
			esac
		}
	}
}

del_opt() {
    local __PG_NAME __PG_PRE
    for __PG_NAME in "$@"; do
        for __PG_PRE in __PG_SOPT_ __PG_SOPTA_ __PG_LOPT_ __PG_LOPTA_ __PG_DESC_OPT_ __PG_ARG_LA_ __PG_ARG_SA_ __PG_TYPE_ __PG_RANGE_; do
            local __PG_N=$__PG_PRE$__PG_NAME
            [[ "${!__PG_N:-}" ]] && unset $__PG_PRE$__PG_NAME
        done
        local __PG_OPT_LIST __PG_OPT
        __PG_OPT_LIST=""
        for __PG_OPT in ${__PG_OPTION_LIST:-}; do
            [[ $__PG_OPT = $__PG_NAME ]] || __PG_OPT_LIST="$__PG_OPT_LIST $__PG_OPT"
        done
        __PG_OPTION_LIST="$__PG_OPT_LIST"
    done
}

# prints only short options that take no parameter
print_short_flags() {
    local NAME
    local FLAGS=""

    for NAME in ${__PG_OPTION_LIST:-}; do
        local DESC=$(get_opt_desc $NAME)
        [[ "$DESC" ]] || continue
        local L=$(get_opt_letter $NAME)
        [[ "$L" ]] || continue
        local A=$(get_opt_sarg $NAME)
        [[ "$A" ]] && continue
        FLAGS="$FLAGS$L"
    done
    echo -n "$FLAGS"
}

# prints only long options that take no parameter
print_long_flags() {
    local NAME
    local FLAGS=""
    local SPACE=""

    for NAME in ${__PG_OPTION_LIST:-}; do 
        local DESC=$(get_opt_desc $NAME)
        [[ "$DESC" ]] || continue
        local L=$(get_opt_string $NAME)
        [[ "$L" ]] || continue
        local A=$(get_opt_larg $NAME)
        [[ "$A" ]] && continue
        printf -- "$SPACE--%s" $L
        SPACE=" "
    done
    echo -n "$FLAGS"
}

# prints only short options that take a parameter
print_short_args() {
    local MAN=$1

    local NAME FMT
    for NAME in ${__PG_OPTION_LIST:-}; do 
        local DESC=$(get_opt_desc $NAME)
        [[ "$DESC" ]] || continue
        local SARG=$(get_opt_sarg "$NAME")
        [[ "$SARG" ]] || continue
        local SOPT=$(get_opt_letter "$NAME")
        [[ "$MAN" ]] && FMT='[\\fB\-%s\\fP \\fI%s\\fP]' || FMT="[-%s <%s>]"
        printf -- "$FMT" $SOPT $SARG
    done
    echo -n "$FLAGS"
}

# prints only long options that take a parameter
print_long_args() {
    local MAN=$1

    local NAME FMT
    for NAME in ${__PG_OPTION_LIST:-}; do 
        local DESC=$(get_opt_desc $NAME)
        [[ "$DESC" ]] || continue
        local LARG=$(get_opt_larg "$NAME")
        [[ "$LARG" ]] || continue
        local LOPT=$(get_opt_string "$NAME")
        [[ "$MAN" ]] && FMT='[\\fB\-\-%s\\fP=\\fI%s\\fP]' || FMT="[--%s=<%s>]"
        printf -- "$FMT" $LOPT $LARG
    done
}

# prints short and long options that take a parameter
print_all_args() {
    local MAN=${1:-}

    local NAME FMT
    for NAME in ${__PG_OPTION_LIST:-}; do 
        local DESC=$(get_opt_desc $NAME)
        [[ "$DESC" ]] || continue
        local SARG=$(get_opt_sarg "$NAME")
        local LARG=$(get_opt_larg "$NAME")
        [[ "$SARG$LARG" ]] || continue

        local SOPT=$(get_opt_letter "$NAME")
        local LOPT=$(get_opt_string "$NAME")

        echo -n " ["
        [[ "$MAN" ]] && FMT='\\fB\-%s\\fP \\fI%s\\fP' || FMT="-%s <%s>"
        [[ "$SARG" ]] && printf -- "$FMT" $SOPT $SARG
        [[ "$SARG" && "$LARG" ]] && echo -n ","
        [[ "$MAN" ]] && FMT='\\fB\-\-%s\\fP=\\fI%s\\fP' || FMT="--%s=<%s>"
        [[ "$LARG" ]] && printf -- "$FMT" $LOPT $LARG
        echo "]"
    done
}

# print the help line for all options
print_all_opts() {
    local NAME
    for NAME in ${__PG_OPTION_LIST:-}; do 
        __GPG_print_opt $NAME
    done
}

# print the option line for a man page
__GPG_print_opt_man() {
    local NAME="$1"
    local L
    local DESC
    local SOPT
    local LOPT
    local SARG
    local LARG

    DESC=$(get_opt_desc $NAME)
    [[ "$DESC" ]] || return 0
    SOPT=$(get_opt_letter $NAME)
    LOPT=$(get_opt_string $NAME)
    SARG=$(get_opt_sarg $NAME)
    LARG=$(get_opt_larg $NAME)

    echo ".TP"
    echo -n ".B "
    # NB 'echo -n "-E"' swallows the -E!! and it has no --
    [[ "$SOPT" ]] && printf -- '\\fB\-%s\\fP' $SOPT
    [[ "$SOPT" ]] && [[ "$SARG" ]] && echo -n " \\fI$SARG\\fR"
    if [[ "$__GPG_LONG_GETOPT" ]]; then
        [[ "$SOPT" ]] && [[ "$LOPT" ]] && echo -n ", "
        [[ "$LOPT" ]] && printf -- '\\fB\-\-%s\\fP' $LOPT
        [[ "$LOPT" ]] && [[ "$LARG" ]] && echo -n "\\fI=$LARG\\fR"
    fi
    echo
    echo "$DESC"
}

# Create a skeleton man page - uses these parameters if defined:
# USAGE
# ARGUMENTS
# SHORT_DESC
__GPG_print_man_page() {
    FLAGS=$(print_short_flags)
    [[ "$FLAGS" ]] && FLAGS='
.RB "[" \-'$FLAGS' "]"'
    LFLAGS=$(print_long_flags | sed 's/-/\\-/g')
    [[ "$LFLAGS" ]] && LFLAGS='
[
.B '$LFLAGS'
]'
    ARGS=$(print_all_args "for-man-page")

    cat <<EOF
.TH `echo $PROG|tr '[a-z]' '[A-Z]'` 1 \" -*- nroff -*-
.SH NAME
$PROG \- $SHORT_DESC
.SH SYNOPSIS
.hy 0
.na
.B $PROG$FLAGS$LFLAGS
$ARGS
${ARGUMENTS:-}
.ad b
.hy 0
.SH DESCRIPTION
${USAGE:-}
.P
Foobar foobar foobar
.SH OPTIONS
EOF

    local NAME
    for NAME in ${__PG_OPTION_LIST:-}; do 
        __GPG_print_opt_man $NAME
    done

    cat <<EOF
.SH "EXIT STATUS"
.SH "ENVIRONMENT"
.SH "FILES"
.SH "EXAMPLES"
.SH "NOTES"
.SH "BUGS"
.SH "SEE ALSO"
.SH "AUTHOR"
Written by Foo Bar <foobar@foobar.org>
.P
.RB http://foobar.foobar.org/foobar
.SH "COPYRIGHT"
Copyright (c) 2008-2011 Robert Hepple
.br
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.P
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
.P
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
EOF
}

add_std_opts() {
    get_opt_name -$__GPG_HELP_option >/dev/null && __GPG_HELP_option=
    get_opt_name --$__GPG_HELP_loption  >/dev/null && __GPG_HELP_loption=
    if [[ "$__GPG_HELP_option$__GPG_HELP_loption" ]]; then
        add_opt HELP "print this help and exit" "$__GPG_HELP_option" "" "$__GPG_HELP_loption"
    fi

    get_opt_name -$__GPG_VERSION_option  >/dev/null && __GPG_VERSION_option=
    get_opt_name --$__GPG_VERSION_loption  >/dev/null && __GPG_VERSION_loption=
    if [[ "$__GPG_VERSION_option$__GPG_VERSION_loption" ]]; then
        add_opt VERSION "print version and exit" "$__GPG_VERSION_option" "" "$__GPG_VERSION_loption"
    fi

    get_opt_name -$__GPG_VERBOSE_option >/dev/null  && __GPG_VERBOSE_option=
    get_opt_name --$__GPG_VERBOSE_loption >/dev/null  && __GPG_VERBOSE_loption=
    if [[ "$__GPG_VERBOSE_option$__GPG_VERBOSE_loption" ]]; then
        add_opt VERBOSE "do it verbosely" "$__GPG_VERBOSE_option" "" "$__GPG_VERBOSE_loption"
    fi

    get_opt_name -$__GPG_QUIET_option >/dev/null  && __GPG_QUIET_option=
    get_opt_name --$__GPG_QUIET_loption  >/dev/null && __GPG_QUIET_loption=
    if [[ "$__GPG_QUIET_option$__GPG_QUIET_loption" ]]; then
        add_opt QUIET "do it quietly" "$__GPG_QUIET_option" "" "$__GPG_QUIET_loption"
    fi

    add_opt PRINT_MAN_PAGE "" "" "" "$__GPG_PRINTMAN_loption"
    add_opt END_OPTIONS "explicitly ends the options" "-"
}

# Note that this function is called in a sub-shell so variables
# defined here will not be available globally. Also, it's called in
# $(...) so any std out goes to the caller which is probably not a
# good thing.
call_getopt() {
    # ${!prefix*} is bash 2.04 (and posix) but not in sh:
    local SHORT_OPTIONS=""
    local SHORT_OPTIONS_ARG=""
    local LONG_OPTIONS=""
    local LONG_OPTIONS_ARG=""
    local STOP_EARLY=""
    local OPT
    local TEMP

    for OPT in ${!__PG_SOPT_*}; do
        [[ "${!OPT}" = "-" ]] && continue # special case of '--'
        SHORT_OPTIONS="$SHORT_OPTIONS${!OPT}"
    done
    for OPT in ${!__PG_SOPTA_*}; do 
        SHORT_OPTIONS_ARG="$SHORT_OPTIONS_ARG${!OPT}:"
    done
    for OPT in ${!__PG_LOPT_*}; do 
        [[ "$LONG_OPTIONS" ]] && LONG_OPTIONS="$LONG_OPTIONS,"
        LONG_OPTIONS="$LONG_OPTIONS${!OPT}"
    done
    for OPT in ${!__PG_LOPTA_*}; do 
        [[ "$LONG_OPTIONS_ARG" ]] && LONG_OPTIONS_ARG="$LONG_OPTIONS_ARG,"
        LONG_OPTIONS_ARG="$LONG_OPTIONS_ARG${!OPT}:"
    done

    [[ "$STOP_ON_FIRST_NON_OPT" ]] && STOP_EARLY="+"
    if [[ "${__GPG_LONG_GETOPT:-''}" ]]; then
        local SHORT_ARGS=""
        local LONG_ARGS=""
        [[ -n "$SHORT_OPTIONS$SHORT_OPTIONS_ARG" ]] && SHORT_ARGS="-o $STOP_EARLY$SHORT_OPTIONS$SHORT_OPTIONS_ARG"
        [[ -n "$LONG_OPTIONS" ]] && LONG_ARGS="--long $LONG_OPTIONS"
        [[ -n "$LONG_OPTIONS_ARG" ]] && LONG_ARGS="$LONG_ARGS --long $LONG_OPTIONS_ARG"
        TEMP=$(getopt $SHORT_ARGS $LONG_ARGS -n "$PROG" -- "$@") || exit 1
    else
        TEMP=$(getopt $SHORT_OPTIONS$SHORT_OPTIONS_ARG "$@") || exit 1
    fi
    
    echo "$TEMP"
}

__GPG_print_opt() {
    local NAME="$1"
    local L
    local N
    local DESC
    local SOPT
    local LOPT
    local SARG
    local LARG

    DESC=$(get_opt_desc $NAME)
    [[ "$DESC" ]] || return 0
    SOPT=$(get_opt_letter $NAME)
    LOPT=$(get_opt_string $NAME)
    SARG=$(get_opt_sarg $NAME)
    LARG=$(get_opt_larg $NAME)

    LINE=""
    for (( N=0 ; ${#LINE} < __GPG_SHORT_OPT_COL ; N++ )) ; do LINE="$LINE " ; done
    [[ "$SOPT" ]] && LINE="${LINE}-$SOPT"
    [[ "$SOPT" ]] && [[ "$SARG" ]] && LINE="${LINE} $SARG"
    if [[ "$__GPG_LONG_GETOPT" ]]; then
        [[ "$SOPT" ]] && [[ "$LOPT" ]] && LINE="$LINE, "
        if [[ "$LOPT" ]]; then
            [[ "$SOPT" ]] && LONG_START=${LONG_COL_OPT:-} || LONG_START=$__GPG_SHORT_OPT_COL
            for (( N=0 ; ${#LINE} < LONG_START ; N++ )); do 
                LINE="$LINE "
            done
            [[ "$LOPT" ]] && LINE="${LINE}--$LOPT"
            [[ "$LOPT" ]] && [[ "$LARG" ]] && LINE="$LINE=$LARG"
        fi
    fi
    LINE="$LINE "
    while (( ${#LINE} < __GPG_OPT_DOC_COL - 1)) ; do LINE="$LINE " ; done
    # NB 'echo "-E"' swallows the -E!! and it has no -- so use printf
    printf -- "%s" "$LINE"
    
    FIRST="FIRST_yes"
    if (( ${#LINE} >= __GPG_OPT_DOC_COL )); then
        echo
        FIRST=""
    fi
    local WIDTH=$(( __GPG_RMARGIN - __GPG_OPT_DOC_COL ))
    if ! type fmt &> /dev/null || [[ "$WIDTH" -lt 10 ]]; then
        printf -- "%s\n" "$DESC"
        return 0
    fi

    export __PG_INDENT=""
    while (( ${#__PG_INDENT} < __GPG_OPT_DOC_COL - 1)); do __PG_INDENT="$__PG_INDENT "; done
    echo "$DESC" |fmt -w "$WIDTH" -s | { 
        while read L; do 
            [[ "$FIRST" ]] || 
            echo -n "$__PG_INDENT"; FIRST=""; printf -- "%s\n" "$L"
        done 
    }
    unset __PG_INDENT
}

# honour GNU ARGP_HELP_FMT parameter
__GPG_load_help_fmt() {
    [[ "${ARGP_HELP_FMT:-}" ]] || return 0
    OFS="$IFS"
    IFS=','
    set -- $ARGP_HELP_FMT
    IFS="$OFS"
    while [[ "$1" ]]; do
        case "$1" in
            short-opt-col*)
                __GPG_SHORT_OPT_COL=$(echo "$1"|cut -d'=' -f 2)
                shift
                ;;
            long-opt-col*)
                __GPG_LONG_OPT_COL=$(echo "$1"|cut -d'=' -f 2)
                shift
                ;;
            opt-doc-col*)
                __GPG_OPT_DOC_COL=$(echo "$1"|cut -d'=' -f 2)
                shift
                ;;
            rmargin*)
                __GPG_RMARGIN=$(echo "$1"|cut -d'=' -f 2)
                shift
                ;;
            *)
                shift
                ;;
        esac
    done
}

__GPG_default_usage() {
    local FLAGS=$(print_short_flags)
    [[ "$FLAGS" ]] && FLAGS="[-$FLAGS]"
    local LFLAGS=$(print_long_flags)
    [[ "$LFLAGS" ]] && LFLAGS=" [$LFLAGS]"
    local ARGS=$(print_all_args)
    local FMT="fmt -w $_GPG_COLUMNS -s"
    type fmt &> /dev/null || FMT=cat
    echo -e "Usage: $PROG $FLAGS$LFLAGS\n$ARGS ${ARGUMENTS:-}" |$FMT
    echo
    echo "${SHORT_DESC:-}${USAGE:-}" |$FMT
    echo
    echo "Options:"
    print_all_opts
}

__GPG_check_type_and_value() {
	local VALUE="$1"
	local TYPE="$2"
	local RANGE="$3"

	# just using 'while' for the sake of the 'break':
	while [[ "$TYPE" ]]; do
		case "$TYPE" in
			i|I)
				echo "$VALUE" | egrep -q "$__GPG_INT_REGEX" || break
				[[ "x$RANGE" = x ]] || {
					local LOWER=$(echo "$RANGE" | cut -d- -f1)
					local UPPER=$(echo "$RANGE" | cut -d- -f2)
					[[ "$LOWER" && "$VALUE" -lt "$LOWER" ]] && break
					[[ "$UPPER" && "$VALUE" -gt "$UPPER" ]] && break
				}
				return 0
				;;
			r|R|f|F)
				echo "$VALUE" | egrep -q "$FLOAT_REGEX" || break
				[[ "x$RANGE" = x ]] || {
					local LOWER=$(echo "$RANGE" | cut -d- -f1)
					local UPPER=$(echo "$RANGE" | cut -d- -f2)
					[[ "$LOWER" ]] && {
						awk "BEGIN {if ($VALUE < $LOWER) {exit 1} else {exit 0}}" || break
					}
					[[ "$UPPER" ]] && {
						awk "BEGIN {if ($VALUE > $UPPER) {exit 1} else {exit 0}}" || break
					}
				}
				return 0
				;;
			s|S)
				[[ "x$RANGE" = x ]] || {
					echo "$VALUE" | egrep -q "$RANGE" || break
				}
				return 0
				;;
			a|A)
				local VAL
				for VAL in $RANGE; do
					[[ "$VAL" = "$VALUE" ]] && return 0
				done
				break
				;;
		esac
	done

	(
		echo -n "$PROG: value '$VALUE' given for option '$NAME' must be of type '$TYPE'"
		[[ "x$RANGE" = x ]] || {
			case "$TYPE" in
				s|S)
					echo -n " and must fit REGEX '$RANGE'"
					;;
				a|A)
					echo -n " and must be one of these values: $RANGE"
					;;
				i|I|f|F) echo -n " and in range '$RANGE'"
					;;
			esac
		}
		echo
	) | fmt >&2
	exit 1
}
    
# need to be very careful here with local parameters as this calls
# NAME_func and if that defines a parameter used here then there's a
# conflict - namespace everything here to minimise the chances
process_opts() {
    local __PG_SHIFT_NUM=0
    while true; do
        local __PG_OPTION="${1:-}"
		local __PG_VALUE="${2:-}"
        if  [[ "$__GPG_HELP_option"  &&  "-$__GPG_HELP_option"  == "$__PG_OPTION" ]] ||
            [[ "$__GPG_HELP_loption" && "--$__GPG_HELP_loption" == "$__PG_OPTION" ]]; then
            usage 2>/dev/null || __GPG_default_usage
            exit 0
        fi

        if  [[ "$__GPG_VERSION_option"  &&  "-$__GPG_VERSION_option"  == "$__PG_OPTION" ]] ||
            [[ "$__GPG_VERSION_loption" && "--$__GPG_VERSION_loption" == "$__PG_OPTION" ]]; then
            echo "$PROG: version $VERSION"
            exit 0 
        fi

        if  [[ "$__GPG_VERBOSE_option"  &&  "-$__GPG_VERBOSE_option"  == "$__PG_OPTION" ]] ||
            [[ "$__GPG_VERBOSE_loption" && "--$__GPG_VERBOSE_loption" == "$__PG_OPTION" ]]; then
            VERBOSE="VERBOSE_yes"
            ((__PG_SHIFT_NUM++))
            shift
            continue
        fi

        if  [[ "$__GPG_QUIET_option"  &&  "-$__GPG_QUIET_option"  == "$__PG_OPTION" ]] ||
            [[ "$__GPG_QUIET_loption" && "--$__GPG_QUIET_loption" == "$__PG_OPTION" ]]; then
            VERBOSE=""
            ((__PG_SHIFT_NUM++))
            shift
            continue
        fi

        case "$__PG_OPTION" in
        --$__GPG_PRINTMAN_loption)
            __GPG_print_man_page
            exit 0
            ;;

        --)
            ((__PG_SHIFT_NUM++))
            break
            ;;
            
        *) 
            local __PG_NAME=$(get_opt_name "$__PG_OPTION")
            if [[ -z "$__PG_NAME" ]]; then
                echo "$PROG: process-getopt: process_opts: no function for option \"$__PG_OPTION\"" >&2
                exit 1
            fi

			local __PG_TYPE=$(get_opt_type "$__PG_NAME")
			[[ "$__PG_TYPE" ]] && {
				local RANGE=$(get_opt_range "$__PG_NAME")
				__GPG_check_type_and_value "$__PG_VALUE" "$__PG_TYPE" "$RANGE"
			}
            $__PG_NAME$__GPG_FUNC_SUFFIX "$__PG_OPTION" "$__PG_VALUE"; local __PG_STAT=$?
            shift
            ((__PG_SHIFT_NUM++))
            [[ "$__PG_STAT" -eq 127 ]] && exit 0 # no such function
            
            # now let's adjust for options with args:
            local __PG_SA="__PG_ARG_SA_$__PG_NAME"
            local __PG_LA="__PG_ARG_LA_$__PG_NAME"
            if [[ "${!__PG_SA:-}${!__PG_LA:-}" ]]; then
                shift
                ((__PG_SHIFT_NUM++))
            fi
            ;;
        esac
    done
    #clean_process_getopt
    return $__PG_SHIFT_NUM
}

get_opt_name() {
    # returns the name for an option letter or word
    local __PG_OPT="$1" # an option eg -c or --foobar
    local __PG_NAME
    for __PG_NAME in ${__PG_OPTION_LIST:-}; do 
        local __PG_L=__PG_LOPT_$__PG_NAME
        local __PG_LA=__PG_LOPTA_$__PG_NAME
        local __PG_S=__PG_SOPT_$__PG_NAME
        local __PG_SA=__PG_SOPTA_$__PG_NAME
        if  [[ "--${!__PG_L:-}" = "$__PG_OPT" ]] || \
            [[ "--${!__PG_LA:-}" = "$__PG_OPT" ]] || \
            [[ "-${!__PG_S:-}" = "$__PG_OPT" ]] || \
            [[ "-${!__PG_SA:-}" = "$__PG_OPT" ]]; then
            echo "${__PG_NAME}"
            return 0
        fi
    done
    return 1
}

__GPG_VERSION="2.0"
__GPG_STANDALONE=""
STOP_ON_FIRST_NON_OPT=${STOP_ON_FIRST_NON_OPT:-}
# First some safety checks:
[[ "$PROG" ]] || { 
    PROG=$(basename $0)
    if [[ "$PROG" != "process-getopt" ]]; then
        echo "$PROG: process-getopt: \$PROG not defined, bailing" >&2; 
        exit 1; 
    fi
    # Otherwise, we're running as a standalone - perhaps to get the
    # version or help
    VERSION=$__GPG_VERSION
    usage() { __PG_process_getopt_usage; }
    __GPG_STANDALONE="__GPG_STANDALONE_yes"
}

[[ "$VERSION" ]] || { 
    echo "$PROG: process-getopt: \$VERSION not defined, bailing" >&2 
    exit 1
}

_GPG_COLUMNS=`tput cols`
[[ -z "$_GPG_COLUMNS" || "$_GPG_COLUMNS" -lt 60 || "$_GPG_COLUMNS" -gt 5000 ]] && _GPG_COLUMNS=80
((_GPG_COLUMNS--))

__PG_OPTION_LIST=""
__GPG_LONG_GETOPT=""
# decide if this getopt supports long options:
{ 
    getopt --test &>/dev/null; __PG_STAT=$? 
} || : 
[[ $__PG_STAT -eq 4 ]] && __GPG_LONG_GETOPT="__GPG_LONG_GETOPT_yes"

__GPG_HELP_loption="help"
__GPG_HELP_option="h"
__GPG_VERBOSE_loption="verbose"
__GPG_VERBOSE_option="v"
__GPG_QUIET_option="q"
__GPG_QUIET_loption="quiet"
__GPG_VERSION_loption="version"
__GPG_VERSION_option="V"
__GPG_PRINTMAN_loption="print-man-page"
__GPG_FUNC_SUFFIX="_func"

__GPG_SHORT_OPT_COL=2
__GPG_LONG_OPT_COL=6
__GPG_OPT_DOC_COL=29
__GPG_RMARGIN=_GPG_COLUMNS

__GPG_INT_REGEX="[+-]*[[:digit:]]+"
__GPG_INT_RANGE_REGEX="$__GPG_INT_REGEX-$__GPG_INT_REGEX"
__GPG_FLOAT_REGEX="[+-]*[[:digit:]]+(\\.[[:digit:]]+)*"
__GPG_FLOAT_RANGE_REGEX="$__GPG_FLOAT_REGEX-$__GPG_FLOAT_REGEX"
# FIXME: this needs a few tweaks:
__GPG_URL_REGEX="(nfs|http|https|ftp|file)://[[:alnum:]_.-]*[^[:space:]]*"

# stubs to satisfy add_opt on standard opts:
HELP_func() { :; }
VERBOSE_func() { :; }
QUIET_func() { :; }
VERSION_func() { :; }
PRINT_MAN_PAGE_func() { :; }
END_OPTIONS_func() { :; }

__GPG_load_help_fmt

[[ "$__GPG_RMARGIN" -gt "$_GPG_COLUMNS" ]] && __GPG_RMARGIN=$_GPG_COLUMNS

# normally, this is called without args - but we can also respond to
# normal options too!
[[ "$__GPG_STANDALONE" ]] && {
    add_std_opts
    __PG_TEMP=$(call_getopt "$@") || exit 1
    eval set -- "$__PG_TEMP"
    process_opts "$@"
    shift "$?"
}

# just to make sure we don't return with non-zero $?:
:
