450 lines
18 KiB
Plaintext
450 lines
18 KiB
Plaintext
|
##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||
|
## Analyse des arguments de la ligne de commande
|
||
|
##@cooked nocomments
|
||
|
##@include base.core
|
||
|
##@include base.string
|
||
|
##@include base.array
|
||
|
uprovide base.args
|
||
|
urequire base.core base.string base.array #XXX maj de cette liste
|
||
|
|
||
|
function __po_parse_optdescs() {
|
||
|
# Parser et normaliser la description des options valides. A l'issue de
|
||
|
# l'appel de cette méthode, 3 tableaux sont initialisés, dont chaque
|
||
|
# position correspond à une option:
|
||
|
# - options_ contient la liste des options valides e.g. (-o --longo...)
|
||
|
# - names_ contient la liste des variables qu'il faut mettre à jour pour ces
|
||
|
# options e.g. (name name...)
|
||
|
# - flags_ contient la liste des flags pour les options: '' pour une option
|
||
|
# simple, ':' pour option avec argument obligatoire, '::' pour option avec
|
||
|
# argument facultatif, '$' pour une commande à lancer au lieu d'une variable
|
||
|
# à mettre à jour.
|
||
|
# De plus, les variables suivantes sont initialisées:
|
||
|
# - destargs_ obtient la variable qui doit être initialisée avec le reste
|
||
|
# des arguments.
|
||
|
# - opts_ et longopts_ sont les arguments à utiliser pour la commande getopt
|
||
|
# du package util-linux
|
||
|
# Retourner le nombre d'arguments à shifter
|
||
|
local -a optdescs_
|
||
|
local optdesc_ option_ name_ flag_ value_
|
||
|
local reset_
|
||
|
local shift_
|
||
|
local i_ count_
|
||
|
|
||
|
let shift_=0
|
||
|
while [ -n "$1" ]; do
|
||
|
if [ "$1" == -- ]; then
|
||
|
shift
|
||
|
let shift_=$shift_+1
|
||
|
break
|
||
|
elif [ "$1" == "%" ]; then
|
||
|
reset_=1
|
||
|
shift
|
||
|
let shift_=$shift_+1
|
||
|
elif [ "$1" == "-" -o "$1" == "+" ]; then
|
||
|
# annuler les précédentes options + ou -
|
||
|
if [ "${opts_#+}" != "$opts_" ]; then
|
||
|
opts_="${opts_#+}"
|
||
|
elif [ "${opts_#-}" != "$opts_" ]; then
|
||
|
opts_="${opts_#-}"
|
||
|
fi
|
||
|
# puis rajouter l'option
|
||
|
opts_="$1$opts_"
|
||
|
shift
|
||
|
let shift_=$shift_+1
|
||
|
elif [ "$1" == "@" ]; then
|
||
|
destargs_="$2"
|
||
|
shift; shift
|
||
|
let shift_=$shift_+2
|
||
|
elif [[ "$1" == --* ]] || [[ "$1" == -* ]]; then
|
||
|
array_split optdescs_ "$1" ","
|
||
|
if [ "$2" == . ]; then
|
||
|
local autoname_=
|
||
|
for optdesc_ in "${optdescs_[@]}"; do
|
||
|
if [ ${#optdesc_} -gt ${#autoname_} ]; then
|
||
|
autoname_="$optdesc_"
|
||
|
fi
|
||
|
done
|
||
|
while [ -n "$autoname_" -a "${autoname_#-}" != "$autoname_" ]; do autoname_="${autoname_#-}"; done
|
||
|
while [ -n "$autoname_" -a "${autoname_%:}" != "$autoname_" ]; do autoname_="${autoname_%:}"; done
|
||
|
autoname_="${autoname_//-/_}"
|
||
|
shift; shift
|
||
|
set -- dummy "$autoname_" "$@"
|
||
|
fi
|
||
|
for optdesc_ in "${optdescs_[@]}"; do
|
||
|
if [[ "$2" == \$* ]]; then
|
||
|
name_="$2"
|
||
|
if [[ "$optdesc_" == *:: ]]; then
|
||
|
option_="${optdesc_%::}"
|
||
|
flag_='::$'
|
||
|
elif [[ "$optdesc_" == *: ]]; then
|
||
|
option_="${optdesc_%:}"
|
||
|
flag_=':$'
|
||
|
else
|
||
|
option_="$optdesc_"
|
||
|
flag_='$'
|
||
|
fi
|
||
|
elif [[ "$optdesc_" == *:: ]]; then
|
||
|
option_="${optdesc_%::}"
|
||
|
if [[ "$2" == *=* ]]; then
|
||
|
# la valeur mentionnée est toujours ignorée
|
||
|
name_="${2%%=*}="
|
||
|
[ -n "$reset_" ] && eval "$name_="
|
||
|
else
|
||
|
name_="$2"
|
||
|
[ -n "$reset_" ] && eval "$name_=()"
|
||
|
fi
|
||
|
flag_=::
|
||
|
elif [[ "$optdesc_" == *: ]]; then
|
||
|
option_="${optdesc_%:}"
|
||
|
if [[ "$2" == *=* ]]; then
|
||
|
# la valeur mentionnée est toujours ignorée
|
||
|
name_="${2%%=*}="
|
||
|
[ -n "$reset_" ] && eval "$name_="
|
||
|
else
|
||
|
name_="$2"
|
||
|
[ -n "$reset_" ] && eval "$name_=()"
|
||
|
fi
|
||
|
flag_=:
|
||
|
else
|
||
|
option_="$optdesc_"
|
||
|
name_="$2"
|
||
|
[ -n "$reset_" ] && eval "$name_="
|
||
|
flag_=
|
||
|
fi
|
||
|
|
||
|
if i_="$(array_find options_ "$option_")"; then
|
||
|
# supprimer l'ancienne occurence
|
||
|
options_=("${options_[@]:0:$i_}" "${options_[@]:$(($i_ + 1))}")
|
||
|
names_=("${names_[@]:0:$i_}" "${names_[@]:$(($i_ + 1))}")
|
||
|
flags_=("${flags_[@]:0:$i_}" "${flags_[@]:$(($i_ + 1))}")
|
||
|
fi
|
||
|
options_=("${options_[@]}" "$option_")
|
||
|
names_=("${names_[@]}" "$name_")
|
||
|
flags_=("${flags_[@]}" "$flag_")
|
||
|
done
|
||
|
shift; shift
|
||
|
let shift_=$shift_+2
|
||
|
else
|
||
|
break
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
i_=0
|
||
|
count_=${#options_[*]}
|
||
|
while [ $i_ -lt $count_ ]; do
|
||
|
option_="${options_[$i_]}"
|
||
|
flag_="${flags_[$i_]}"
|
||
|
i_=$(($i_ + 1))
|
||
|
|
||
|
# pour construire longopts_ et opts_, enlever $ de flag
|
||
|
flag_="${flag_%$}"
|
||
|
if [[ "$option_" == --* ]]; then
|
||
|
longopts_="${longopts_:+$longopts_,}${option_#--}$flag_"
|
||
|
elif [[ "$option_" == -* ]]; then
|
||
|
opts_="$opts_${option_#-}$flag_"
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
return $shift_
|
||
|
}
|
||
|
function __po_check_options() {
|
||
|
# vérifier la validité des options mentionnées dans les arguments
|
||
|
# Si les options sont valides, retourner 0 et afficher la chaine des
|
||
|
# arguments traitées.
|
||
|
# Sinon, retourner 1 et initialiaser la variable $destargs_ avec le message
|
||
|
# d'erreur.
|
||
|
local -a getopt_args_
|
||
|
getopt_args_=(-o "$opts_" ${longopts_:+-l "$longopts_"} -- "$@")
|
||
|
local args_
|
||
|
if args_="$(getopt -q "${getopt_args_[@]}")"; then
|
||
|
recho "$args_"
|
||
|
return 0
|
||
|
else
|
||
|
# relancer la commande pour avoir le message d'erreur
|
||
|
LANG=C getopt "${getopt_args_[@]}" 2>&1 1>/dev/null
|
||
|
return 1
|
||
|
fi
|
||
|
}
|
||
|
function __po_process_options() {
|
||
|
# Traiter les options
|
||
|
while [ -n "$1" ]; do
|
||
|
if [ "$1" == -- ]; then
|
||
|
shift
|
||
|
break
|
||
|
elif [[ "$1" == -* ]]; then
|
||
|
local i_
|
||
|
let i_=0
|
||
|
for option_ in "${options_[@]}"; do
|
||
|
[ "$1" == "${options_[$i_]}" ] && break
|
||
|
let i_=$i_+1
|
||
|
done
|
||
|
name_="names_[$i_]"; name_="${!name_}"
|
||
|
flag_="flags_[$i_]"; flag_="${!flag_}"
|
||
|
function inc@ { eval "let $1=\$$1+1"; }
|
||
|
function res@ { set_var "$1" "${value_:-$2}"; }
|
||
|
function add@ { array_add "$1" "${value_:-$2}"; }
|
||
|
if [ -z "$name_" ]; then
|
||
|
# option non reconnue. ce cas aurait dû être traité par
|
||
|
# __po_check_options.
|
||
|
ewarn "$1: option non reconnue, elle sera ignorée"
|
||
|
elif [[ "$flag_" == *\$ ]]; then
|
||
|
if [[ "$flag_" == :* ]]; then
|
||
|
value_="$2"; shift
|
||
|
function set@ { res@ "$@"; }
|
||
|
else
|
||
|
value_=
|
||
|
function set@ { inc@ "$@"; }
|
||
|
fi
|
||
|
eval "${name_#\$}"
|
||
|
elif [ "$flag_" == "" ]; then
|
||
|
if [[ "$name_" == *=* ]]; then
|
||
|
set_var "${name_%%=*}" "${name_#*=}"
|
||
|
else
|
||
|
inc@ "$name_"
|
||
|
fi
|
||
|
elif [ "$flag_" == ":" -o "$flag_" == "::" ]; then
|
||
|
value_="$2"; shift
|
||
|
if [ "${name_%=}" != "$name_" ]; then
|
||
|
set_var "${name_%=}" "$value_"
|
||
|
elif [[ "$name_" == *=* ]]; then
|
||
|
set_var "${name_%%=*}" "${name_#*=}"
|
||
|
else
|
||
|
array_add "$name_" "$value_"
|
||
|
fi
|
||
|
fi
|
||
|
else
|
||
|
break
|
||
|
fi
|
||
|
shift
|
||
|
done
|
||
|
unset -f inc@ res@ add@ set@
|
||
|
[ -n "$destargs_" ] &&
|
||
|
set_array "$destargs_" @ "$@"
|
||
|
return 0
|
||
|
}
|
||
|
function parse_opts() {
|
||
|
# Analyser des arguments. Cette fonction doit être appelée avec une description
|
||
|
# des options à analyser, suivie des arguments proprement dits. En fonction des
|
||
|
# options rencontrées, certaines variables sont mises à jour.
|
||
|
# Les arguments de cette fonction sont donc de la forme 'optdescs -- args'
|
||
|
|
||
|
# optdescs est une suite d'arguments d'une des formes suivantes: 'opt,opt... var',
|
||
|
# 'opt,opt... $cmd', '@ var', '+', '-', '%'
|
||
|
# - Dans 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 ('' 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:
|
||
|
# '--opt .' est équivalent à '--opt opt'
|
||
|
# '--opt: .' est équivalent à '--opt: opt='
|
||
|
# Avec la forme '-o:' ou '--longopt:', l'option prend un argument obligatoire.
|
||
|
# Avec la forme '-o::' ou '--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]' met à jour le
|
||
|
# tableau NAME, en lui ajoutant la valeur VALUE. Par défaut, VALUE vaut $value_
|
||
|
# - Dans la forme '@ var', var est un nom de tableau qui est initialisé avec le
|
||
|
# reste des arguments.
|
||
|
# - Avec les caractères '-' ou '+', l'on influe sur la méthode d'analyse. Par
|
||
|
# défaut, les options sont valides n'importe ou 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.
|
||
|
# - Toutes les variables mentionnées après le caractère '%' sont
|
||
|
# initialisées. Elle sont garanties d'être vides
|
||
|
# Si opt est définie plusieurs fois, la dernière définition est celle qui est
|
||
|
# retenue, e.g. dans l'exemple suivant, l'option -o prend une valeur et met à
|
||
|
# jour la variable second:
|
||
|
# parse_opts -o,--longo first=1 -o: second= ....
|
||
|
|
||
|
# Si une erreur se produit pendant l'analyse, retourner 1. Si '@ var' est
|
||
|
# spécifié, insérer le texte de l'erreur comme unique élément du tableau var.
|
||
|
# Une suggestion d'utilisation est donc celle-ci:
|
||
|
# parse_opts ... @ args -- "$@" && set -- "${args[@]}" || die "$args"
|
||
|
|
||
|
# D'abord, parser et normaliser les options
|
||
|
# options_ contient la liste des options (-o --longo...)
|
||
|
# names_ contient la liste des variables qu'il faut mettre à jour (name name...)
|
||
|
# flags_ contient la liste des flags pour les options: '' pour une option
|
||
|
# simple, ':' pour option avec argument obligatoire, '::' pour option avec
|
||
|
# argument facultatif
|
||
|
local -a options_ names_ flags_ destargs_
|
||
|
local opts_ longopts_
|
||
|
__po_parse_optdescs "$@" || shift $?
|
||
|
local args_
|
||
|
if args_="$(__po_check_options "$@")"; then
|
||
|
eval "set -- $args_"
|
||
|
__po_process_options "$@"
|
||
|
else
|
||
|
[ -n "$destargs_" ] && set_var "$destargs_" "$args_"
|
||
|
return 1
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
function parse_args_check() {
|
||
|
# Simplifier l'utilisation de parse_opts(). En entrée, le tableau args doit être
|
||
|
# initialisé avec la liste des options. En sortie, ce tableau contient la liste
|
||
|
# des arguments restant sur la ligne de commande. En cas d'erreur, retourner 1.
|
||
|
# Exemple d'utilisation:
|
||
|
# args=(-c:,--config: .)
|
||
|
# parse_args_check "$@" || return
|
||
|
parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" && return 0
|
||
|
eerror "$args"
|
||
|
return 1
|
||
|
}
|
||
|
function parse_args() {
|
||
|
# Simplifier l'utilisation de parse_opts(). En entrée, le tableau args doit être
|
||
|
# initialisé avec la liste des options. En sortie, ce tableau contient la liste
|
||
|
# des arguments restant sur la ligne de commande. En cas d'erreur, quitter le
|
||
|
# script avec die()
|
||
|
parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" || die "$args"
|
||
|
}
|
||
|
|
||
|
HELP_DESC=
|
||
|
HELP_USAGE=
|
||
|
HELP_OPTIONS=
|
||
|
function __genparse_shortopt() {
|
||
|
local LC_COLLATE=C
|
||
|
local shortopt="${1//[^A-Z]}"
|
||
|
shortopt="$(strlower "${shortopt:0:1}")"
|
||
|
[ -n "$shortopt" ] && echo "$shortopt"
|
||
|
}
|
||
|
function genparse() {
|
||
|
# Afficher une ligne de commande à évaluer pour simplifier l'utilisation de
|
||
|
# parse_opts(). Une fonction display_help() par défaut est définie et les
|
||
|
# options appropriées de parse_opts sont utilisées pour reconnaître les options
|
||
|
# spécifiées par les arguments.
|
||
|
# Cette fonction peut être utilisée de cette manière:
|
||
|
# HELP_DESC=...
|
||
|
# HELP_ARG_DESC=... # pour chaque arg
|
||
|
# eval "$(genparse [args...])"
|
||
|
# D'autres variables peuvent être définies: HELP_USAGE, HELP_OPTIONS,
|
||
|
# HELP_ARG_OPTION. Consulter le source pour connaitre leur utilisation
|
||
|
# Les arguments de cette fonction sont de la forme 'sansarg' pour une option
|
||
|
# simple qui ne prend pas d'argument ou 'avecarg=[default-value]' pour une
|
||
|
# option qui prend un argument. Les options générées sont des options
|
||
|
# longues. 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.
|
||
|
# Par exemple, la commande suivante:
|
||
|
# genparse Force enCoding=utf-8 input= long-Option=
|
||
|
# affichera ceci:
|
||
|
# function display_help() {
|
||
|
# [ -n "$HELP_USAGE" ] || HELP_USAGE="USAGE
|
||
|
# $scriptname [options]"
|
||
|
# [ -n "$HELP_OPTIONS" ] || HELP_OPTIONS="OPTIONS
|
||
|
# ${HELP_FORCE_OPTION:- -f, --force${HELP_FORCE_DESC:+
|
||
|
# $HELP_FORCE_DESC}}
|
||
|
# ${HELP_ENCODING_OPTION:- -c, --encoding VALUE${HELP_ENCODING_DESC:+
|
||
|
# ${HELP_ENCODING_DESC}}}
|
||
|
# ${HELP_INPUT_OPTION:- --input VALUE${HELP_INPUT_DESC:+
|
||
|
# ${HELP_INPUT_DESC}}}
|
||
|
# ${HELP_LONG_OPTION_OPTION:- -o, --long-option VALUE${HELP_LONG_OPTION_DESC:+
|
||
|
# ${HELP_LONG_OPTION_DESC}}}"
|
||
|
# uecho "${HELP_DESC:+$HELP_DESC
|
||
|
#
|
||
|
# }$HELP_USAGE${HELP_OPTIONS:+
|
||
|
#
|
||
|
# $HELP_OPTIONS}"
|
||
|
# }
|
||
|
#
|
||
|
# force=
|
||
|
# encoding=utf-8
|
||
|
# input=""
|
||
|
# long_option=""
|
||
|
# parse_opts "${PRETTYOPTS[@]}" \
|
||
|
# --help '$exit_with display_help' \
|
||
|
# -f,--force force=1 \
|
||
|
# -c:,--encoding: encoding= \
|
||
|
# --input: input= \
|
||
|
# -o:,--long-option: long_option= \
|
||
|
# @ args -- "$@" && set -- "${args[@]}" || die "$args"
|
||
|
|
||
|
local -a names descs vars options
|
||
|
local i desc var option name uname value shortopt
|
||
|
|
||
|
# analyser les arguments
|
||
|
for var in "$@"; do
|
||
|
if [[ "$var" == *=* ]]; then
|
||
|
splitvar "$var" name value
|
||
|
shortopt="$(__genparse_shortopt "$name")"
|
||
|
option="$(strlower "$name")"
|
||
|
name="${option//-/_}"
|
||
|
array_add names "$name"
|
||
|
array_add descs "${shortopt:+-$shortopt, }--$option VALUE"
|
||
|
array_add vars "$(set_var_cmd "$name" "$value")"
|
||
|
array_add options "${shortopt:+-$shortopt:,}--$option: $name="
|
||
|
else
|
||
|
name="$var"
|
||
|
shortopt="$(__genparse_shortopt "$name")"
|
||
|
option="$(strlower "$name")"
|
||
|
name="${option//-/_}"
|
||
|
array_add names "$name"
|
||
|
array_add descs "${shortopt:+-$shortopt, }--$option"
|
||
|
array_add vars "$name="
|
||
|
array_add options "${shortopt:+-$shortopt,}--$option $name=1"
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
# afficher la commande parse_opts
|
||
|
echo -n 'function display_help() {
|
||
|
[ -n "$HELP_USAGE" ] || HELP_USAGE="USAGE
|
||
|
$scriptname'
|
||
|
[ -n "$descs" ] && echo -n ' [options]'
|
||
|
echo '"'
|
||
|
if [ -n "$descs" ]; then
|
||
|
echo -n ' [ -n "$HELP_OPTIONS" ] || HELP_OPTIONS="OPTIONS'
|
||
|
i=0
|
||
|
while [ $i -lt ${#descs[*]} ]; do
|
||
|
name="${names[$i]}"
|
||
|
uname="$(strupper "$name")"
|
||
|
desc="${descs[$i]}"
|
||
|
echo -n "
|
||
|
\${HELP_${uname}_OPTION:- $desc\${HELP_${uname}_DESC:+
|
||
|
\${HELP_${uname}_DESC//
|
||
|
/
|
||
|
}}}"
|
||
|
i=$(($i + 1))
|
||
|
done
|
||
|
echo '"'
|
||
|
fi
|
||
|
echo ' uecho "${HELP_DESC:+$HELP_DESC
|
||
|
|
||
|
}$HELP_USAGE${HELP_OPTIONS:+
|
||
|
|
||
|
$HELP_OPTIONS}"
|
||
|
}
|
||
|
'
|
||
|
for var in "${vars[@]}"; do
|
||
|
echo "$var"
|
||
|
done
|
||
|
echo 'parse_opts "${PRETTYOPTS[@]}" \'
|
||
|
echo ' --help '\''$exit_with display_help'\'' \'
|
||
|
for option in "${options[@]}"; do
|
||
|
echo " $option \\"
|
||
|
done
|
||
|
echo ' @ args -- "$@" && set -- "${args[@]}" || die "$args"'
|
||
|
}
|