# -*- 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 < .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 $?: :