implémenter la gestion des arguments

This commit is contained in:
Jephté Clain 2023-10-13 23:51:05 +04:00
parent ce5b166f59
commit 65446a33b4
24 changed files with 436 additions and 182 deletions

View File

@ -1,176 +0,0 @@
# -*- 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"
require: base.arr
function: myargs_local "Afficher des commandes pour rendre locales des variables utilisées par myargs()
Cela permet d'utiliser myargs() à l'intérieur d'une fonction. Par défaut, la génération automatique de l'autocomplete est désactivée."
function myargs_local() {
# par défaut, désactiver génération de autocomplete
echo "local NUCORE_ARGS_HELP_DESC NUCORE_ARGS_HELP_USAGE NUCORE_ARGS_HELP_OPTIONS args"
echo "local NUCORE_ARGS_DISABLE_AC=1"
echo "local NUCORE_ARGS_ONERROR_RETURN=1"
}
function: myargs: "Débuter la description des arguments reconnus par ce script.
Arguments
: \$1 est un résumé de l'objet de ce script
: \$2 est le nom du script s'il est différent de \$MYNAME
Le mode opératoire est généralement le suivant:
~~~
myargs:
desc \"faire un traitement\"
usage \"MYNAME [options] <args>\"
arg -o:,--output:file output= \"spécifier le fichier destination\"
arg -h:,--host:host hosts+ \"spécifier les hôtes concernés\"
arg -c,--count count=1
parse \"\$@\"; set -- \"\${args[@]}\"
~~~"
function myargs:() {
NUCORE_ARGS_HELP_DESC=
NUCORE_ARGS_HELP_USAGE=
NUCORE_ARGS_HELP_OPTIONS=()
args=()
function desc() { myargs_desc "$@"; }
function usage() { myargs_usage "$@"; }
function arg() { myargs_add "$@"; }
function parse() { myargs_parse "$@"; }
}
function: myargs_desc ""
function myargs_desc() {
NUCORE_ARGS_HELP_DESC="$*"
}
function: myargs_usage ""
function myargs_usage() {
NUCORE_ARGS_HELP_USAGE="$*"
}
function: myargs_add "Ajouter une définition d'option
Syntaxes
: arg MODE
: arg [MODE] -OPTIONS ACTION DESC
: arg [MODE] VARIABLE DESC
MODE peut être l'un des caractères '+', '-', '%' et a un effet sur l'analyse
entière de la ligne de commande
* Les caractères '+' et '-' influent sur la méthode d'analyse. 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 ou sur la ligne de commande, mais les arguments
ne sont pas réordonnés, et apparaissent dans l'ordre de leur mention.
* Le caractère '%' demande que toutes les variables mentionnées à partir de ce
moment soient initialisées. Elle sont garanties d'être vides.
Avec la première syntaxe, on définit précisément l'option. Deux formes sont
supportées. La forme détermine le type d'action
* Avec la forme '-OPT VAR[=VALUE]', OPT est une description d'option, VAR un nom
de variable à mettre à jour, et VALUE une valeur éventuelle pour les options
sans argument. Si plusieurs options sont mentionnées, séparées par des
virgules, alors tous les options partagent les mêmes paramètres.
OPT peut être de la forme '-o' ou '--longopt' pour des options sans arguments.
Dans ce cas, VAR obtient le nombre de fois que l'option est mentionnée (vide
pour aucune mention, '1' pour une seule mention, etc.), sauf si on utilise la
forme VAR=VALUE, auquel cas la variable obtient la valeur VALUE, et le nombre
d'occurences de l'option n'est pas compté.
Pour faciliter la lecture:
* '--longopt .' est équivalent à '--longopt longopt'
* '--longopt: .' est équivalent à '--longopt: longopt='
Avec les formes '-o:' et '--longopt:', l'option prend un argument obligatoire.
Avec les formes '-o::' et '--longopt::', l'option prend un argument facultatif
(dans ce cas, la valeur de l'option sur la ligne de commande doit
obligatoirement être collée à l'option.)
Si ces options sont mentionnées plusieurs fois sur la ligne de commande, alors
la variable de destination est un tableau qui contient toutes les valeurs. Le
traitement de la valeur d'une variable dépend de la forme utilisée.
* Avec une option sans argument, le comportement est celui décrit ci-dessus.
* Avec une option qui prend des arguments, la forme '-o: VAR' considère que
VAR est un tableau qui contiendra toutes les valeurs mentionnées dans les
options. Avec la forme '-o: VAR=', la variable n'est pas un tableau et
contient toujours la dernière valeur spécifiée.
* Dans la forme 'opt \$cmd', la commande cmd est executée avec eval *dès* que
l'option est rencontrée. La variable option_ contient l'option, e.g. '-o' ou
'--longopt'. Le cas échéant, la variable value_ contient la valeur de
l'option. La fonction 'set@ NAME' met à jour la variable NAME, soit en lui
donnant la valeur \$value_, soit en l'incrémentant, suivant le type d'option.
La fonction 'inc@ NAME' incrémente la variable NAME, 'res@ NAME [VALUE]'
initialise la variable à la valeur VALUE, 'add@ NAME [VALUE]' ajoute VALUE à
la fin du tableau NAME. Par défaut, VALUE vaut \$value_
Avec la deuxième syntaxe, l'option est déterminée sur la base du nom de la
variable.
* Une variable de la forme 'sansarg' est pour une option simple qui ne prend pas
d'argument
* Une variable de la forme 'avecarg=[default-value]' est pour une option qui
prend un argument.
L'option générée est une option longue. En l'occurence, les options générées
sont respectivement '--sansarg' et '--avecarg:'
Les variables et les options sont toujours en minuscule. Pour les variables, le
caractère '-' est remplacé par '_'. Si une option contient une lettre en
majuscule, l'option courte correspondante à cette lettre sera aussi reconnue.
"
function myargs_add() {
# description des options
array_add args "${@:1:2}"
# puis construire la description de l'option pour l'aide
local -a os; local o odesc
array_split os "$1" ,
for o in "${os[@]}"; do
o="${o%%:*}"
[ -n "$odesc" ] && odesc="$odesc, "
odesc="$odesc$o"
done
for o in "${os[@]}"; do
if [[ "$o" == *:* ]]; then
if [ "${2#\$}" != "$2" ]; then
o=ARG
else
o="${2%%=*}"
o="${o^^}"
fi
[ -n "$odesc" ] && odesc="$odesc "
odesc="$odesc$o"
fi
break
done
array_add NUCORE_ARGS_HELP_OPTIONS "$odesc"
[ -n "$3" ] && array_add NUCORE_ARGS_HELP_OPTIONS "$3"
}
function: myargs_show_help ""
function myargs_show_help() {
local help="$MYNAME"
[ -n "$NUCORE_ARGS_HELP_DESC" ] && help="$help: $NUCORE_ARGS_HELP_DESC"
[ -n "$NUCORE_ARGS_HELP_USAGE" ] && help="$help
USAGE
$NUCORE_ARGS_HELP_USAGE"
[ ${#NUCORE_ARGS_HELP_OPTIONS[*]} -gt 0 ] && help="$help
OPTIONS"
echo "$help"
for help in "${NUCORE_ARGS_HELP_OPTIONS[@]}"; do
echo "$help"
done
}
function: myargs_parse ""
function myargs_parse() {
[ -z "$NUCORE_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local NUCORE_ARGS_SET_X=1; }
local r=0
if ! parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@"; then
edie "$args"
r=$?
fi
[ -n "$NUCORE_ARGS_SET_X" ] && set -x; return $r
}

View File

@ -1 +0,0 @@
../load.sh

382
bash/src/base.args.sh Normal file
View File

@ -0,0 +1,382 @@
# -*- 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"
require: base.arr
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 args"
echo "local NUCORE_ARGS_ONERROR_RETURN=1"
}
function: parse_args "Analyser les arguments de la ligne de commande à partir des définitions du tableau args
Cette fonction s'utilise ainsi:
~~~"'
args=(
[gendesc]
[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.
l'option --help est automatiquement gérée. les descriptions sont utilisées pour
l'affichage de l'aide.
gendesc
: 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
~~~
+|-
: 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 ou 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
-b::, --optarg::
: option avec argument facultatif
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
~~~
* '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
* '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"
function parse_args() {
local NUCORE_NO_DISABLE_SET_X=1 #XXX
[ -z "$NUCORE_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local NUCORE_ARGS_SET_X=1; }
local r=0
# distinguer les descriptions des définition d'arguments
local NUCORE_ARGS_USAGE NUCORE_ARGS_DESC
local -a NUCORE_ARGS_DEFS NUCORE_ARGS_ARGS
NUCORE_ARGS_ARGS=("$@")
set -- "${args[@]}"
[ "${1#-}" == "$1" ] && { NUCORE_ARGS_DESC="$1"; shift; }
[ "${1#-}" == "$1" ] && { NUCORE_ARGS_USAGE="$1"; shift; }
if [ "$1" != "+" -a "${1#-}" == "$1" ]; then
eerror "Invalid args definition: third arg must be an option"
r=1
else
NUCORE_ARGS_DEFS=("$@")
__parse_args || r=1
fi
[ -n "$NUCORE_ARGS_SET_X" ] && set -x; return $r
}
function __parse_args() {
local __die='[ -n "$NUCORE_ARGS_ONERROR_RETURN" ] && return 1 || die'
## tout d'abord, construire la liste des options
local popt sopts lopts autohelp=1
local -a defs
set -- "${NUCORE_ARGS_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=
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%::}"; 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"
else
# -longopt ou longopt
def="${def#-}"
lopts="$lopts${lopts:+,}$def$witharg"
fi
[ "$def" == help ] && autohelp=
done
# sauter l'action
shift
# sauter la description le cas échéant
[ "${1#-}" == "$1" ] && shift
done
[ -n "$autohelp" ] && lopts="$lopts${lopts:+,}help"
sopts="$popt$sopts"
local -a getopt_args
getopt_args=(-n "$MYNAME" ${sopts:+-o "$sopts"} ${lopts:+-l "$lopts"} -- "${NUCORE_ARGS_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 defvalue autoadd option_ name_ value_
function inc@() {
if [ -n "$2" ]; then
# forcer une valeur
eval "$1=\"\$2\""
else
# incrémenter
eval "[ -n \"\$$1\" ] || let $1=0"
eval "let $1=$1+1"
fi
}
function res@() {
local value_="${value_:-$2}"
eval "$1=\"\$value_\""
}
function add@() {
local value_="${value_:-$2}"
eval "$1+=(\"\$value_\")"
}
function set@() {
if [ -n "$witharg" ]; then
if [ -n "$autoadd" ]; 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
else
res@ "$@"
fi
else
inc@ "$@"
fi
}
function showhelp@() {
local help="$MYNAME"
if [ -n "$NUCORE_ARGS_DESC" ]; then
help="$help: $NUCORE_ARGS_DESC"
fi
help="$help
USAGE
$MYNAME"
if [ -n "$NUCORE_ARGS_USAGE" ]; then
help="$help $NUCORE_ARGS_USAGE"
else
help="$help [options]"
fi
help="$help
OPTIONS"
set -- "${NUCORE_ARGS_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
# description de l'option
local first=1
for def in "${defs[@]}"; do
def="${def%::}"; 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=
help="$help
"
else
help="$help, "
fi
help="$help$def"
done
[ -n "$witharg" ] && help="$help VALUE"
# sauter l'action
shift
# prendre la description le cas échéant
if [ "${1#-}" == "$1" ]; then
help="$help
$1"
shift
fi
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 -- "${NUCORE_ARGS_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="${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
if [ "${1#\$}" != "$1" ]; then
name_="$defname"
defvalue=
autoadd=
action="${1#\$}"
else
if [ "$1" == . ]; then
name_="$defname"
defvalue=
autoadd=1
elif [[ "$1" == *=* ]]; then
name_="${1%%=*}"
defvalue="${1#*=}"
autoadd=
else
name_="$1"
defvalue=
autoadd=1
fi
action="$(qvals set@ "$name_" "$defvalue")"
fi
shift
# sauter la description le cas échéant
[ "${1#-}" == "$1" ] && shift
[ -n "$found" ] && return 0
done
# ici, l'option n'a pas été trouvée, on ne devrait pas arriver ici
if [ "$option_" == --help -a -n "$autohelp" ]; then
action="showhelp@"
return 0
fi
eerror "Unexpected option '$option_'"; eval "$__die"
}

1
bash/src/nucore.sh Symbolic link
View File

@ -0,0 +1 @@
../../load.sh

48
bash/tests/test-args.sh Executable file
View File

@ -0,0 +1,48 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../src/nucore.sh" || exit 1
count=
fixed=
mopt=
dmopt=
oopt=
doopt=
unset a1
a2=()
a3=
a4=x
args=(
"tester la gestion des arguments"
-o,--eopt count "incrémenter count"
-f,--fixed fixed=42 "spécifier fixed"
-a:,--mopt mopt "spécifier mopt"
-A:,--dmopt dmopt=default "spécifier dmopt"
-b::,--oopt oopt "spécifier oopt"
-B::,--doopt doopt=default "spécifier doopt"
-n,--autoinc . "incrémenter autoinc"
-v:,--autoval . "spécifier autoval"
-x: a1 "autoadd a1 qui n'est pas défini"
-y: a2 "autoadd a2 qui est défini à ()"
-z: a3 "autoadd a3 qui est défini à vide"
-t: a4 "autoadd a4 qui est défini à une valeur non vide"
-s '$echo "sans_arg option=$option_, name=$name_, value=$value_"'
-S:: '$echo "avec_arg option=$option_, name=$name_, value=$value_"'
)
parse_args "$@"; set -- "${args[@]}"
echo "\
args=($*)
count=$count
fixed=$fixed
mopt=$mopt
dmopt=$dmopt
oopt=$oopt
doopt=$doopt
autoinc=$autoinc
autoval=$autoval
a1=(${a1[*]}) #${#a1[*]}
a2=(${a2[*]}) #${#a2[*]}
a3=(${a3[*]}) #${#a3[*]}
a4=(${a4[*]}) #${#a4[*]}
"

10
load.sh
View File

@ -44,9 +44,9 @@ if [ "$NUCOREDIR" = "@@""dest""@@" ]; then
NUCORE_SOURCED=1
NUCOREDIR="$(dirname -- "$NUCOREDIR")"
elif [ -f "$NUCOREDIR" -a "$(basename -- "$NUCOREDIR")" == nucore.sh ]; then
# Fichier sourcé depuis nucore/bash
# Fichier sourcé depuis nucore/bash/src
NUCORE_SOURCED=1
NUCOREDIR="$(dirname -- "$NUCOREDIR")/.."
NUCOREDIR="$(dirname -- "$NUCOREDIR")/../.."
else
# Fichier non sourcé. Tout exprimer par rapport au script courant
NUCORE_SOURCED=
@ -62,7 +62,7 @@ elif [ "${BASH_SOURCE[0]}" = /etc/nucore.sh ]; then
NUCORE_SOURCED=1
fi
NUCOREDIR="$(cd "$NUCOREDIR" 2>/dev/null; pwd)"
NUCOREDIRS=("$NUCOREDIR/bash")
NUCOREDIRS=("$NUCOREDIR/bash/src")
# marqueur pour vérifier que nucore a réellement été chargé. il faut avoir $NUCOREINIT == $NUCOREDIR
# utilisé par le module base qui doit pouvoir être inclus indépendamment
@ -149,8 +149,8 @@ function require:() {
}
## Autres modules
[ -d "$NUCOREDIR/awk" ] && inspath "$NUCOREDIR/awk" AWKPATH; export AWKPATH
[ -d "$NUCOREDIR/python3" ] && inspath "$NUCOREDIR/python3" PYTHONPATH; export PYTHONPATH
[ -d "$NUCOREDIR/awk/src" ] && inspath "$NUCOREDIR/awk/src" AWKPATH; export AWKPATH
[ -d "$NUCOREDIR/python3/src" ] && inspath "$NUCOREDIR/python3/src" PYTHONPATH; export PYTHONPATH
## Auto import DEFAULTS
nucore__define_functions