nulib/bash/src/base.args.sh

487 lines
16 KiB
Bash

# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.args "Fonctions de base: analyse d'arguments"
function: local_args "Afficher des commandes pour rendre locales des variables utilisées par parse_args()
Cela permet d'utiliser parse_args() à l'intérieur d'une fonction."
function local_args() {
echo "local -a args"
echo "local NULIB_ARGS_ONERROR_RETURN=1"
echo "local NULIB_VERBOSITY=\"\$NULIB_VERBOSITY\""
echo "local NULIB_INTERACTION=\"\$NULIB_INTERACTION\""
}
function: parse_args "Analyser les arguments de la ligne de commande à partir des définitions du tableau args
Cette fonction s'utilise ainsi:
~~~"'
args=(
[desc]
[usage]
[+|-]
-o,--longopt action [optdesc]
-a:,--mandarg: action [optdesc]
-b::,--optarg:: action [optdesc]
)
parse_args "$@"; set -- "${args[@]}"
~~~'"
au retour de la fonction, args contient les arguments qui n'ont pas été traités
automatiquement.
les options --help et --help++ sont automatiquement gérées. avec --help, seules
les options standards sont affichées. --help++ affiche toutes les options. les
descriptions sont utilisées pour l'affichage de l'aide. une option avancée est
identifiée par une description qui commence par ++
desc
: description de l'objet du script ou de la fonction. cette valeur est
facultative
usage
: description des arguments du script, sans le nom du script. par exemple la
valeur '[options] FILE' générera le texte d'aide suivant:
~~~
USAGE
\$MYNAME [options] FILE
~~~
Peut contenir autant de lignes que nécessaire. Chaque ligne est préfixée du
nom du script, jusqu'à la première ligne vide. Ensuite, les lignes sont
affichées telles quelles.
Le premier espace est ignoré, ce qui permet de spécifier des USAGEs avec une
option, e.g ' -c VALUE'
+|-
: méthode d'analyse des arguments.
* Par défaut, les options sont valides n'importe où sur la ligne de commande.
* Avec '+', l'analyse s'arrête au premier argument qui n'est pas une option.
* Avec '-', les options sont valides n'importe où sur la ligne de commande,
mais les arguments ne sont pas réordonnés, et apparaissent dans l'ordre de
leur mention. IMPORTANT: dans ce cas, aucun argument ni option n'est traité,
c'est à la charge de l'utilisateur. Au retour de la fonction, args contient
l'ensemble des arguments tels qu'analysés par getopt
-o, --longopt
: option sans argument
-a:, --mandarg:
: option avec argument obligatoire
l'option peut être suivi d'une valeur qui décrit l'argument attendu e.g
-a:file pour un fichier. cette valeur est mise en majuscule lors de
l'affichage de l'aide. pour le moment, cette valeur n'est pas signifiante.
-b::, --optarg::
: option avec argument facultatif
l'option peut être suivi d'une valeur qui décrit l'argument attendu e.g
-b::file pour un fichier. cette valeur est mise en majuscule lors de
l'affichage de l'aide. pour le moment, cette valeur n'est pas signifiante.
action
: action à effectuer si cette option est utilisée. plusieurs syntaxes sont valides:
* 'NAME' met à jour la variable en fonction du type d'argument: l'incrémenter
pour une option sans argument, lui donner la valeur spécifiée pour une
option avec argument, ajouter la valeur spécifiée au tableau si l'option est
spécifiée plusieurs fois.
la valeur spéciale '.' calcule une valeur de NAME en fonction du nom de
l'option la plus longue. par exemple, les deux définitions suivantes sont
équivalentes:
~~~
-o,--short,--very-long .
-o,--short,--very-long very_long
~~~
De plus, la valeur spéciale '.' traite les options de la forme --no-opt
comme l'inverse des option --opt. par exemple, les deux définitions
suivantes sont équivalentes:
~~~
--opt . --no-opt .
--opt opt --no-opt '\$dec@ opt'
~~~
* 'NAME=VALUE' pour une option sans argument, forcer la valeur spécifiée; pour
une option avec argument, prendre la valeur spécifiée comme valeur par
défaut si la valeur de l'option est vide
* '\$CMD' CMD est évalué avec eval *dès* que l'option est rencontrée.
les valeurs suivantes sont initialisées:
* option_ est l'option utilisée, e.g --long-opt
* value_ est la valeur de l'option
les fonctions suivantes sont définies:
* 'inc@ NAME' incrémente la variable NAME -- c'est le comportement de set@ si
l'option est sans argument
* 'dec@ NAME' décrémente la variable NAME, et la rend vide quand le compte
arrive à zéro
* 'res@ NAME VALUE' initialise la variable NAME avec la valeur de l'option (ou
VALUE si la valeur de l'option est vide) -- c'est le comportement de set@
si l'option prend un argument
* 'add@ NAME VALUE' ajoute la valeur de l'option (ou VALUE si la valeur de
l'option est vide) au tableau NAME.
* 'set@ NAME' met à jour la variable NAME en fonction de la définition de
l'option (avec ou sans argument, ajout ou non à un tableau)
optdesc
: description de l'option. cette valeur est facultative. si la description
commence par ++, c'est une option avancée qui n'est pas affichée par défaut."
function parse_args() {
eval "$NULIB__DISABLE_SET_X"
local __r=
local __DIE='[ -n "$NULIB_ARGS_ONERROR_RETURN" ] && return 1 || die'
if ! is_array args; then
eerror "Invalid args definition: args must be defined"
__r=1
fi
# distinguer les descriptions des définition d'arguments
local __USAGE __DESC
local -a __DEFS __ARGS
__ARGS=("$@")
set -- "${args[@]}"
[ "${1#-}" == "$1" ] && { __DESC="$1"; shift; }
[ "${1#-}" == "$1" ] && { __USAGE="$1"; shift; }
if [ -n "$__r" ]; then
:
elif [ $# -gt 0 -a "$1" != "+" -a "${1#-}" == "$1" ]; then
eerror "Invalid args definition: third arg must be an option"
__r=1
else
__DEFS=("$@")
__parse_args || __r=1
fi
eval "$NULIB__ENABLE_SET_X"
if [ -n "$__r" ]; then
eval "$__DIE"
fi
}
function __parse_args() {
## tout d'abord, construire la liste des options
local __AUTOH=1 __AUTOHELP=1 # faut-il gérer automatiquement l'affichage de l'aide?
local __AUTOD=1 __AUTODEBUG=1 # faut-il rajouter les options -D et --debug
local __ADVHELP # y a-t-il des options avancées?
local __popt __sopts __lopts
local -a __defs
set -- "${__DEFS[@]}"
while [ $# -gt 0 ]; do
case "$1" in
+) __popt="$1"; shift; continue;;
-) __popt="$1"; shift; continue;;
-*) IFS=, read -a __defs <<<"$1"; shift;;
*) eerror "Invalid arg definition: expected option, got '$1'"; eval "$__DIE";;
esac
# est-ce que l'option prend un argument?
local __def __witharg __valdesc
__witharg=
for __def in "${__defs[@]}"; do
if [ "${__def%::*}" != "$__def" ]; then
[ "$__witharg" != : ] && __witharg=::
elif [ "${__def%:*}" != "$__def" ]; then
__witharg=:
fi
done
# définitions __sopts et __lopts
for __def in "${__defs[@]}"; do
__def="${__def%%:*}"
if [[ "$__def" == --* ]]; then
# --longopt
__def="${__def#--}"
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
# -o
__def="${__def#-}"
__sopts="$__sopts$__def$__witharg"
[ "$__def" == h ] && __AUTOH=
[ "$__def" == D ] && __AUTOD=
else
# -longopt ou longopt
__def="${__def#-}"
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
fi
[ "$__def" == help -o "$__def" == help++ ] && __AUTOHELP=
[ "$__def" == debug ] && __AUTODEBUG=
done
# sauter l'action
shift
# sauter la description le cas échéant
if [ "${1#-}" == "$1" ]; then
[ "${1#++}" != "$1" ] && __ADVHELP=1
shift
fi
done
[ -n "$__AUTOH" ] && __sopts="${__sopts}h"
[ -n "$__AUTOHELP" ] && __lopts="$__lopts${__lopts:+,}help,help++"
[ -n "$__AUTOD" ] && __sopts="${__sopts}D"
[ -n "$__AUTODEBUG" ] && __lopts="$__lopts${__lopts:+,}debug"
__sopts="$__popt$__sopts"
local -a __getopt_args
__getopt_args=(-n "$MYNAME" ${__sopts:+-o "$__sopts"} ${__lopts:+-l "$__lopts"} -- "${__ARGS[@]}")
## puis analyser et normaliser les arguments
if args="$(getopt -q "${__getopt_args[@]}")"; then
eval "set -- $args"
else
# relancer pour avoir le message d'erreur
LANG=C getopt "${__getopt_args[@]}" 2>&1 1>/dev/null
eval "$__DIE"
fi
## puis traiter les options
local __defname __resvalue __decvalue __defvalue __add __action option_ name_ value_
function inc@() {
eval "[ -n \"\$$1\" ] || let $1=0"
eval "let $1=$1+1"
}
function dec@() {
eval "[ -n \"\$$1\" ] && let $1=$1-1"
eval "[ \"\$$1\" == 0 ] && $1="
}
function res@() {
local __value="${value_:-$2}"
eval "$1=\"\$__value\""
}
function add@() {
local __value="${value_:-$2}"
eval "$1+=(\"\$__value\")"
}
function set@() {
if [ -n "$__resvalue" ]; then
res@ "$@"
elif [ -n "$__witharg" ]; then
if is_array "$1"; then
add@ "$@"
elif ! is_defined "$1"; then
# première occurrence: variable
res@ "$@"
else
# deuxième occurence: tableau
[ -z "${!1}" ] && eval "$1=()"
add@ "$@"
fi
elif [ -n "$__decvalue" ]; then
dec@ "$@"
else
inc@ "$@"
fi
}
function showhelp@() {
local help="$MYNAME" showadv="$1"
if [ -n "$__DESC" ]; then
help="$help: $__DESC"
fi
local first usage nl=$'\n'
local prefix=" $MYNAME "
local usages="${__USAGE# }"
[ -n "$usages" ] || usages="[options]"
help="$help
USAGE"
first=1
while [ -n "$usages" ]; do
usage="${usages%%$nl*}"
if [ "$usage" != "$usages" ]; then
usages="${usages#*$nl}"
else
usages=
fi
if [ -n "$first" ]; then
first=
[ -z "$usage" ] && continue
else
[ -z "$usage" ] && prefix=
fi
help="$help
$prefix$usage"
done
set -- "${__DEFS[@]}"
first=1
while [ $# -gt 0 ]; do
case "$1" in
+) shift; continue;;
-) shift; continue;;
-*) IFS=, read -a __defs <<<"$1"; shift;;
esac
if [ -n "$first" ]; then
first=
help="$help${nl}${nl}OPTIONS"
if [ -n "$__AUTOHELP" -a -n "$__ADVHELP" ]; then
help="$help
--help++
Afficher l'aide avancée"
fi
fi
# est-ce que l'option prend un argument?
__witharg=
__valdesc=value
for __def in "${__defs[@]}"; do
if [ "${__def%::*}" != "$__def" ]; then
[ "$__witharg" != : ] && __witharg=::
[ -n "${__def#*::}" ] && __valdesc="[${__def#*::}]"
elif [ "${__def%:*}" != "$__def" ]; then
__witharg=:
[ -n "${__def#*:}" ] && __valdesc="${__def#*:}"
fi
done
# description de l'option
local first=1 thelp tdesc
for __def in "${__defs[@]}"; do
__def="${__def%%:*}"
if [[ "$__def" == --* ]]; then
: # --longopt
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
: # -o
else
# -longopt ou longopt
__def="--${__def#-}"
fi
if [ -n "$first" ]; then
first=
thelp="${nl} "
else
thelp="$thelp, "
fi
thelp="$thelp$__def"
done
[ -n "$__witharg" ] && thelp="$thelp ${__valdesc^^}"
# sauter l'action
shift
# prendre la description le cas échéant
if [ "${1#-}" == "$1" ]; then
tdesc="$1"
if [ "${tdesc#++}" != "$tdesc" ]; then
# option avancée
if [ -n "$showadv" ]; then
tdesc="${tdesc#++}"
else
thelp=
fi
fi
[ -n "$thelp" ] && thelp="$thelp${nl} ${tdesc//$nl/$nl }"
shift
fi
[ -n "$thelp" ] && help="$help$thelp"
done
uecho "$help"
exit 0
}
if [ "$__popt" != - ]; then
while [ $# -gt 0 ]; do
if [ "$1" == -- ]; then
shift
break
fi
[[ "$1" == -* ]] || break
option_="$1"; shift
__parse_opt "$option_"
if [ -n "$__witharg" ]; then
# l'option prend un argument
value_="$1"; shift
else
# l'option ne prend pas d'argument
value_=
fi
eval "$__action"
done
fi
unset -f inc@ res@ add@ set@ showhelp@
args=("$@")
}
function __parse_opt() {
# $1 est l'option spécifiée
local option_="$1"
set -- "${__DEFS[@]}"
while [ $# -gt 0 ]; do
case "$1" in
+) shift; continue;;
-) shift; continue;;
-*) IFS=, read -a __defs <<<"$1"; shift;;
esac
# est-ce que l'option prend un argument?
__witharg=
for __def in "${__defs[@]}"; do
if [ "${__def%::*}" != "$__def" ]; then
[ "$__witharg" != : ] && __witharg=::
elif [ "${__def%:*}" != "$__def" ]; then
__witharg=:
fi
done
# nom le plus long
__defname=
local __found=
for __def in "${__defs[@]}"; do
__def="${__def%%:*}"
[ "$__def" == "$option_" ] && __found=1
if [[ "$__def" == --* ]]; then
# --longopt
__def="${__def#--}"
[ ${#__def} -gt ${#__defname} ] && __defname="$__def"
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
# -o
__def="${__def#-}"
[ ${#__def} -gt ${#__defname} ] && __defname="$__def"
else
# -longopt ou longopt
__def="${__def#-}"
[ ${#__def} -gt ${#__defname} ] && __defname="$__def"
fi
done
__defname="${__defname//-/_}"
# analyser l'action
__decvalue=
if [ "${1#\$}" != "$1" ]; then
name_="$__defname"
__resvalue=
__defvalue=
__action="${1#\$}"
else
if [ "$1" == . ]; then
name_="$__defname"
__resvalue=
__defvalue=
if [ "${name_#no_}" != "$name_" ]; then
name_="${name_#no_}"
__decvalue=1
fi
elif [[ "$1" == *=* ]]; then
name_="${1%%=*}"
__resvalue=1
__defvalue="${1#*=}"
else
name_="$1"
__resvalue=
__defvalue=
fi
__action="$(qvals set@ "$name_" "$__defvalue")"
fi
shift
# sauter la description le cas échéant
[ "${1#-}" == "$1" ] && shift
[ -n "$__found" ] && return 0
done
if [ -n "$__AUTOH" -a "$option_" == -h ]; then
__action="showhelp@"
return 0
fi
if [ -n "$__AUTOHELP" ]; then
if [ "$option_" == --help ]; then
__action="showhelp@"
return 0
elif [ "$option_" == --help++ ]; then
__action="showhelp@ ++"
return 0
fi
fi
if [ -n "$__AUTOD" -a "$option_" == -D ]; then
__action=set_debug
return 0
fi
if [ -n "$__AUTODEBUG" -a "$option_" == --debug ]; then
__action=set_debug
return 0
fi
# ici, l'option n'a pas été trouvée, on ne devrait pas arriver ici
eerror "Unexpected option '$option_'"; eval "$__DIE"
}