nulib/bash/src/base.input.sh

583 lines
20 KiB
Bash

# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.input "Fonctions de base: saisie"
function toienc() {
# $1 étant une variable contenant une chaine encodée dans l'encoding d'entrée $2
# (qui vaut par défaut $NULIB_INPUT_ENCODING), transformer cette chaine en
# utf-8
local __var="$1" __from="${2:-$NULIB_INPUT_ENCODING}"
if [ "$__from" != "NULIB__UTF8" ]; then
_setv "$__var" "$(iconv -f "$__from" -t utf-8 <<<"${!__var}")"
fi
}
function uread() {
# Lire une valeur sur stdin et la placer dans la variable $1. On assume que la
# valeur en entrée est encodée dans $NULIB_INPUT_ENCODING
[ $# -gt 0 ] || set -- REPLY
local __var
read "$@"
for __var in "$@"; do
[ -z "$__var" -o "${__var:0:1}" == "-" ] && continue # ignorer les options
toienc "$__var"
done
}
function set_interaction() { :;}
function is_interaction() { return 1; }
function check_interaction() { return 0; }
function get_interaction_option() { :;}
function ask_yesno() {
# Afficher le message $1 suivi de [oN] ou [On] suivant que $2 vaut O ou N, puis
# lire la réponse. Retourner 0 si la réponse est vrai, 1 sinon.
# Si $1 est une option, elle est utilisée avec check_interaction pour savoir si
# on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées
# ($2=message, $3=default)
# Si $2 vaut C, la valeur par défaut est N si on est interactif, O sinon
# Si $2 vaut X, la valeur par défaut est O si on est interactif, N sinon
local interactive=1
if [[ "$1" == -* ]]; then
if [ "$1" != -- ]; then
check_interaction "$1" || interactive=
fi
shift
else
check_interaction -c || interactive=
fi
local default="${2:-N}"
if [ "$default" == "C" ]; then
[ -n "$interactive" ] && default=N || default=O
elif [ "$default" == "X" ]; then
[ -n "$interactive" ] && default=O || default=N
fi
if [ -n "$interactive" ]; then
local message="$1"
local prompt="[oN]"
local r
is_yes "$default" && prompt="[On]"
if [ -n "$message" ]; then
__eecho_ "$message" 1>&2
else
__eecho_ "Voulez-vous continuer?" 1>&2
fi
echo_ " $prompt " 1>&2
uread r
is_yes "${r:-$default}"
else
is_yes "$default"
fi
}
function ask_any() {
# Afficher le message $1 suivi du texte "[$2]" (qui vaut par défaut +Oq), puis
# lire la réponse. Les lettres de la chaine de format $2 sont numérotées de 0 à
# $((${#2} - 1)). Le code de retour est le numéro de la lettre qui a été
# sélectionnée. Cette fonction est une généralisation de ask_yesno() pour
# n'importe quel ensemble de lettres.
# La première lettre en majuscule est la lettre sélectionnée par défaut.
# La lettre O matche toutes les lettres qui signifient oui: o, y, 1, v, t
# La lettre N matche toutes les lettres qui signifient non: n, f, 0
# Il y a des raccourcis:
# +O --> On
# +N --> oN
# +C --> oN si on est en mode interactif, On sinon
# +X --> On si on est en mode interactifn oN sinon
# Si $1 est une option, elle est utilisée avec check_interaction pour savoir si
# on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées
# ($2=message, $3=format)
local interactive=1
if [[ "$1" == -* ]]; then
if [ "$1" != -- ]; then
check_interaction "$1" || interactive=
fi
shift
else
check_interaction -c || interactive=
fi
local format="${2:-+Oq}"
format="${format/+O/On}"
format="${format/+N/oN}"
if [ -n "$interactive" ]; then
format="${format/+C/oN}"
format="${format/+X/On}"
else
format="${format/+C/On}"
format="${format/+X/oN}"
fi
local i count="${#format}"
if [ -n "$interactive" ]; then
local message="${1:-Voulez-vous continuer?}"
local prompt="[$format]"
local r f lf defi
while true; do
__eecho_ "$message $prompt " 1>&2
uread r
r="$(strlower "${r:0:1}")"
i=0; defi=
while [ $i -lt $count ]; do
f="${format:$i:1}"
lf="$(strlower "$f")"
[ "$r" == "$lf" ] && return $i
if [ -z "$defi" ]; then
[ -z "${f/[A-Z]/}" ] && defi="$i"
fi
if [ "$lf" == o ]; then
case "$r" in o|y|1|v|t) return $i;; esac
elif [ "$lf" == n ]; then
case "$r" in n|f|0) return $i;; esac
fi
i=$(($i + 1))
done
[ -z "$r" ] && return ${defi:-0}
done
else
i=0
while [ $i -lt $count ]; do
f="${format:$i:1}"
[ -z "${f/[A-Z]/}" ] && return $i
i=$(($i + 1))
done
return 0
fi
}
function read_value() {
# Afficher le message $1 suivi de la valeur par défaut [$3] si elle est non
# vide, puis lire la valeur donnée par l'utilisateur. Cette valeur doit être non
# vide si $4(=O) est vrai. La valeur saisie est placée dans la variable
# $2(=value)
# Si $1 est une option, elle est utilisée avec check_interaction pour savoir si
# on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées
# ($2=message, $3=variable, $4=default, $5=required)
# En mode non interactif, c'est la valeur par défaut qui est sélectionnée. Si
# l'utilisateur requière que la valeur soit non vide et que la valeur par défaut
# est vide, afficher un message d'erreur et retourner faux
# read_password() est comme read_value(), mais la valeur saisie n'est pas
# affichée, ce qui la rend appropriée pour la lecture d'un mot de passe.
local -a __rv_opts; local __rv_readline=1 __rv_showdef=1 __rv_nl=
__rv_opts=()
[ -n "$NULIB_NO_READLINE" ] && __rv_readline=
__rv_read "$@"
}
function read_password() {
local -a __rv_opts __rv_readline= __rv_showdef= __rv_nl=1
__rv_opts=(-s)
__rv_read "$@"
}
function __rv_read() {
local __rv_int=1
if [[ "$1" == -* ]]; then
if [ "$1" != -- ]; then
check_interaction "$1" || __rv_int=
fi
shift
else
check_interaction -c || __rv_int=
fi
local __rv_msg="$1" __rv_v="${2:-value}" __rv_d="$3" __rv_re="${4:-O}"
if [ -z "$__rv_int" ]; then
# En mode non interactif, retourner la valeur par défaut
if is_yes "$__rv_re" && [ -z "$__rv_d" ]; then
eerror "La valeur par défaut de $__rv_v doit être non vide"
return 1
fi
_setv "$__rv_v" "$__rv_d"
return 0
fi
local __rv_r
while true; do
if [ -n "$__rv_msg" ]; then
__eecho_ "$__rv_msg" 1>&2
else
__eecho_ "Entrez la valeur" 1>&2
fi
if [ -n "$__rv_readline" ]; then
echo_ ": " 1>&2
uread -e ${__rv_d:+-i"$__rv_d"} "${__rv_opts[@]}" __rv_r
else
if [ -n "$__rv_d" ]; then
if [ -n "$__rv_showdef" ]; then
echo_ " [$__rv_d]" 1>&2
else
echo_ " [****]" 1>&2
fi
fi
echo_ ": " 1>&2
uread "${__rv_opts[@]}" __rv_r
[ -n "$__rv_nl" ] && echo
fi
__rv_r="${__rv_r:-$__rv_d}"
if [ -n "$__rv_r" ] || ! is_yes "$__rv_re"; then
_setv "$__rv_v" "$__rv_r"
return 0
fi
echo
done
}
function simple_menu() {
# Afficher un menu simple dont les éléments sont les valeurs du tableau
# $2(=options). L'option choisie est placée dans la variable $1(=option)
# -t TITLE: spécifier le titre du menu
# -m YOUR_CHOICE: spécifier le message d'invite pour la sélection de l'option
# -d DEFAULT: spécifier l'option par défaut. Par défaut, prendre la valeur
# actuelle de la variable $1(=option)
eval "$(local_args)"
local __sm_title= __sm_yourchoice= __sm_default=
args=(
-t:,--title __sm_title=
-m:,--prompt __sm_yourchoice=
-d:,--default __sm_default=
)
parse_args "$@"; set -- "${args[@]}"
local __sm_option_var="${1:-option}" __sm_options_var="${2:-options}"
local __sm_option __sm_options
__sm_options="$__sm_options_var[*]"
if [ -z "${!__sm_options}" ]; then
eerror "Le tableau $__sm_options_var doit être non vide"
return 1
fi
[ -z "$__sm_default" ] && __sm_default="${!__sm_option_var}"
array_copy __sm_options "$__sm_options_var"
local __sm_c=0 __sm_i __sm_choice
while true; do
if [ "$__sm_c" == "0" ]; then
# Afficher le menu
[ -n "$__sm_title" ] && __eecho "=== $__sm_title ===" 1>&2
__sm_i=1
for __sm_option in "${__sm_options[@]}"; do
if [ "$__sm_option" == "$__sm_default" ]; then
__eecho "$__sm_i*- $__sm_option" 1>&2
else
__eecho "$__sm_i - $__sm_option" 1>&2
fi
let __sm_i=$__sm_i+1
done
fi
# Afficher les choix
if [ -n "$__sm_yourchoice" ]; then
__eecho_ "$__sm_yourchoice" 1>&2
else
__eecho_ "Entrez le numéro de l'option choisie" 1>&2
fi
echo_ ": " 1>&2
uread __sm_choice
# Valeur par défaut
if [ -z "$__sm_choice" -a -n "$__sm_default" ]; then
__sm_option="$__sm_default"
break
fi
# Vérifier la saisie
if [ -n "$__sm_choice" -a -z "${__sm_choice//[0-9]/}" ]; then
if [ "$__sm_choice" -gt 0 -a "$__sm_choice" -le "${#__sm_options[*]}" ]; then
__sm_option="${__sm_options[$(($__sm_choice - 1))]}"
break
else
eerror "Numéro d'option incorrect"
fi
else
eerror "Vous devez saisir le numéro de l'option choisie"
fi
let __sm_c=$__sm_c+1
if [ "$__sm_c" -eq 5 ]; then
# sauter une ligne toutes les 4 tentatives
echo 1>&2
__sm_c=0
fi
done
_setv "$__sm_option_var" "$__sm_option"
}
function actions_menu() {
# Afficher un menu dont les éléments sont les valeurs du tableau $4(=options),
# et une liste d'actions tirées du tableau $3(=actions). L'option choisie est
# placée dans la variable $2(=option). L'action choisie est placée dans la
# variable $1(=action)
# Un choix est saisi sous la forme [action]num_option
# -t TITLE: spécifier le titre du menu
# -m OPT_YOUR_CHOICE: spécifier le message d'invite pour la sélection de
# l'action et de l'option
# -M ACT_YOUR_CHOICE: spécifier le message d'invite dans le cas où aucune option
# n'est disponible. Dans ce cas, seules les actions vides sont possibles.
# -e VOID_ACTION: spécifier qu'une action est vide, c'est à dire qu'elle ne
# requière pas d'être associée à une option. Par défaut, la dernière action
# est classée dans cette catégorie puisque c'est l'action "quitter"
# -d DEFAULT_ACTION: choisir l'action par défaut. par défaut, c'est la première
# action.
# -q QUIT_ACTION: choisir l'option "quitter" qui provoque la sortie du menu sans
# choix. par défaut, c'est la dernière action.
# -o DEFAULT_OPTION: choisir l'option par défaut. par défaut, prendre la valeur
# actuelle de la variable $2(=option)
eval "$(local_args)"
local -a __am_action_descs __am_options __am_void_actions
local __am_tmp __am_select_action __am_select_option __am_title __am_optyc __am_actyc
local __am_default_action=auto __am_quit_action=auto
local __am_default_option=
args=(
-t:,--title __am_title=
-m:,--prompt __am_optyc=
-M:,--no-prompt __am_actyc=
-e: __am_void_actions
-d: __am_default_action=
-q: __am_quit_action=
-o: __am_default_option=
)
parse_args "$@"; set -- "${args[@]}"
__am_tmp="${1:-action}"; __am_select_action="${!__am_tmp}"
__am_tmp="${2:-option}"; __am_select_option="${!__am_tmp}"
[ -n "$__am_default_option" ] && __am_select_option="$__am_default_option"
array_copy __am_action_descs "${3:-actions}"
array_copy __am_options "${4:-options}"
eerror_unless [ ${#__am_action_descs[*]} -gt 0 ] "Vous devez spécifier le tableau des actions" || return
__actions_menu || return 1
_setv "${1:-action}" "$__am_select_action"
_setv "${2:-option}" "$__am_select_option"
}
function __actions_menu() {
local title="$__am_title"
local optyc="$__am_optyc" actyc="$__am_actyc"
local default_action="$__am_default_action"
local quit_action="$__am_quit_action"
local select_action="$__am_select_action"
local select_option="$__am_select_option"
local -a action_descs options void_actions
array_copy action_descs __am_action_descs
array_copy options __am_options
array_copy void_actions __am_void_actions
# Calculer la liste des actions valides
local no_options
array_isempty options && no_options=1
local -a actions
local tmp action name
for tmp in "${action_descs[@]}"; do
splitfsep2 "$tmp" : action name
[ -n "$action" ] || action="${name:0:1}"
action="$(strlower "$action")"
array_addu actions "$action"
done
# Calculer l'action par défaut
if [ "$default_action" == auto ]; then
# si action par défaut non spécifiée, alors prendre la première action
default_action="$select_action"
if [ -n "$default_action" ]; then
array_contains actions "$default_action" || default_action=
fi
[ -n "$default_action" ] || default_action="${actions[0]}"
fi
default_action="${default_action:0:1}"
default_action="$(strlower "$default_action")"
# Calculer l'action quitter par défaut
if [ "$quit_action" == auto ]; then
# si action par défaut non spécifiée, alors prendre la dernière action,
# s'il y a au moins 2 actions
if [ ${#actions[*]} -gt 1 ]; then
quit_action="${actions[@]:$((-1)):1}"
array_addu void_actions "$quit_action"
fi
fi
quit_action="${quit_action:0:1}"
quit_action="$(strlower "$quit_action")"
# Calculer la ligne des actions à afficher
local action_title
for tmp in "${action_descs[@]}"; do
splitfsep2 "$tmp" : action name
[ -n "$action" ] || action="${name:0:1}"
[ -n "$name" ] || name="$action"
action="$(strlower "$action")"
if [ -n "$no_options" ]; then
if ! array_contains void_actions "$action"; then
array_del actions "$action"
continue
fi
fi
[ "$action" == "$default_action" ] && name="$name*"
action_title="${action_title:+$action_title/}$name"
done
if [ -n "$default_action" ]; then
# si action par défaut invalide, alors pas d'action par défaut
array_contains actions "$default_action" || default_action=
fi
if [ -n "$quit_action" ]; then
# si action quitter invalide, alors pas d'action quitter
array_contains actions "$quit_action" || quit_action=
fi
# Type de menu
if [ -n "$no_options" ]; then
if array_isempty void_actions; then
eerror "Aucune option n'est définie. Il faut définir le tableau des actions vides"
return 1
fi
__void_actions_menu
else
__options_actions_menu
fi
}
function __void_actions_menu() {
local c=0 choice
while true; do
if [ $c -eq 0 ]; then
[ -n "$title" ] && __etitle "$title" 1>&2
__eecho_ "=== Actions disponibles: " 1>&2
recho "$action_title" 1>&2
fi
if [ -n "$actyc" ]; then
__eecho_ "$actyc" 1>&2
elif [ -n "$optyc" ]; then
__eecho_ "$optyc" 1>&2
else
__eecho_ "Entrez l'action à effectuer" 1>&2
fi
echo_ ": " 1>&2
uread choice
if [ -z "$choice" -a -n "$default_action" ]; then
select_action="$default_action"
break
fi
# vérifier la saisie
choice="${choice:0:1}"
choice="$(strlower "$choice")"
if array_contains actions "$choice"; then
select_action="$choice"
break
elif [ -n "$choice" ]; then
eerror "$choice: action incorrecte"
else
eerror "vous devez saisir l'action à effectuer"
fi
let c=$c+1
if [ $c -eq 5 ]; then
# sauter une ligne toutes les 4 tentatives
echo 1>&2
c=0
fi
done
__am_select_action="$select_action"
__am_select_option=
}
function __options_actions_menu() {
local c=0 option choice action option
while true; do
if [ $c -eq 0 ]; then
[ -n "$title" ] && __etitle "$title" 1>&2
i=1
for option in "${options[@]}"; do
if [ "$option" == "$select_option" ]; then
echo "$i*- $option" 1>&2
else
echo "$i - $option" 1>&2
fi
let i=$i+1
done
__estepn_ "Actions disponibles: " 1>&2
recho "$action_title" 1>&2
fi
if [ -n "$optyc" ]; then
__eecho_ "$optyc" 1>&2
else
__eecho_ "Entrez l'action et le numéro de l'option choisie" 1>&2
fi
echo_ ": " 1>&2
uread choice
# vérifier la saisie
if [ -z "$choice" -a -n "$default_action" ]; then
action="$default_action"
if array_contains void_actions "$action"; then
select_action="$action"
select_option=
break
elif [ -n "$select_option" ]; then
select_action="$action"
break
fi
fi
action="${choice:0:1}"
action="$(strlower "$action")"
if array_contains actions "$action"; then
# on commence par un code d'action valide. cool :-)
if array_contains void_actions "$action"; then
select_action="$action"
select_option=
break
else
option="${choice:1}"
option="${option// /}"
if [ -z "$option" -a -n "$select_option" ]; then
select_action="$action"
break
elif [ -z "$option" ]; then
eerror "vous devez saisir le numéro de l'option"
elif isnum "$option"; then
if [ $option -gt 0 -a $option -le ${#options[*]} ]; then
select_action="$action"
select_option="${options[$(($option - 1))]}"
break
fi
else
eerror "$option: numéro d'option incorrecte"
fi
fi
elif isnum "$choice"; then
# on a simplement donné un numéro d'option
action="$default_action"
if [ -n "$action" ]; then
if array_contains void_actions "$action"; then
select_action="$action"
select_option=
break
else
option="${choice// /}"
if [ -z "$option" ]; then
eerror "vous devez saisir le numéro de l'option"
elif isnum "$option"; then
if [ $option -gt 0 -a $option -le ${#options[*]} ]; then
select_action="$action"
select_option="${options[$(($option - 1))]}"
break
fi
else
eerror "$option: numéro d'option incorrecte"
fi
fi
else
eerror "Vous devez spécifier l'action à effectuer"
fi
elif [ -n "$choice" ]; then
eerror "$choice: action et/ou option incorrecte"
else
eerror "vous devez saisir l'action à effectuer"
fi
let c=$c+1
if [ $c -eq 5 ]; then
# sauter une ligne toutes les 4 tentatives
echo "" 1>&2
c=0
fi
done
__am_select_action="$select_action"
__am_select_option="$select_option"
}