From eba69bea41832032f9eb67ab96011a7e4b97a639 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 3 Oct 2023 05:41:24 +0400 Subject: [PATCH] =?UTF-8?q?d=C3=A9but=20migration=20nulib?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .udir | 30 + bash/base.args.sh | 176 ++++++ bash/base.arr.sh | 361 ++++++++++++ bash/base.core.sh | 458 +++++++++++++++ bash/base.eval.sh | 468 +++++++++++++++ bash/base.init.sh | 53 ++ bash/base.io.sh | 1291 ++++++++++++++++++++++++++++++++++++++++++ bash/base.path.sh | 158 ++++++ bash/base.sh | 19 + bash/base.split.sh | 189 +++++++ bash/base.str.sh | 140 +++++ bash/git.sh | 704 +++++++++++++++++++++++ bash/nucore.sh | 1 + bash/pretty.sh | 4 + bash/sysinfos.sh | 4 + lib/profile.d/nucore | 2 + lib/uinst/conf | 9 + lib/uinst/rootconf | 10 + load.sh | 159 ++++++ 19 files changed, 4236 insertions(+) create mode 100644 .udir create mode 100644 bash/base.args.sh create mode 100644 bash/base.arr.sh create mode 100644 bash/base.core.sh create mode 100644 bash/base.eval.sh create mode 100644 bash/base.init.sh create mode 100644 bash/base.io.sh create mode 100644 bash/base.path.sh create mode 100644 bash/base.sh create mode 100644 bash/base.split.sh create mode 100644 bash/base.str.sh create mode 100644 bash/git.sh create mode 120000 bash/nucore.sh create mode 100644 bash/pretty.sh create mode 100644 bash/sysinfos.sh create mode 100644 lib/profile.d/nucore create mode 100644 lib/uinst/conf create mode 100644 lib/uinst/rootconf create mode 100644 load.sh diff --git a/.udir b/.udir new file mode 100644 index 0000000..607803b --- /dev/null +++ b/.udir @@ -0,0 +1,30 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Utiliser 'udir --help-vars' pour une description de la signification des +# variables suivantes: +udir_desc="librairies de base pour scripts bash, awk, php, python" +udir_note="" +udir_types=(uinst) +uinc=release +uinc_options=() +uinc_args=() +preconfig_scripts=() +configure_variables=(dest) +configure_dest_for=(lib/profile.d/nucore) +config_scripts=(lib/uinst/conf) +install_profiles=true +profiledir=lib/profile.d +bashrcdir=lib/bashrc.d +defaultdir=lib/default +workdir_rsync_options=() +workdir_excludes=() +workdir_includes=() +copy_files=true +destdir=/opt +destdir_override_userhost= +destdir_ssh= +destdir_force_remote= +srcdir=. +files=() +owner=root: +modes=(u=rwX,g=rX,o=rX) +root_scripts=(lib/uinst/rootconf) diff --git a/bash/base.args.sh b/bash/base.args.sh new file mode 100644 index 0000000..19d037a --- /dev/null +++ b/bash/base.args.sh @@ -0,0 +1,176 @@ +# -*- 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] \" +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 +} diff --git a/bash/base.arr.sh b/bash/base.arr.sh new file mode 100644 index 0000000..0130dd3 --- /dev/null +++ b/bash/base.arr.sh @@ -0,0 +1,361 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: base.arr "Fonctions de base: gestion des variables tableaux" +require: base.core base.str + +function: array_count "retourner le nombre d'éléments du tableau \$1" +function array_count() { + eval "echo \${#$1[*]}" +} + +function: array_isempty "tester si le tableau \$1 est vide" +function array_isempty() { + eval "[ \${#$1[*]} -eq 0 ]" +} + +function: array_new "créer un tableau vide dans la variable \$1" +function array_new() { + eval "$1=()" +} + +function: array_copy "copier le contenu du tableau \$2 dans le tableau \$1" +function array_copy() { + eval "$1=(\"\${$2[@]}\")" +} + +function: array_add "ajouter les valeurs \$2..@ à la fin du tableau \$1" +function array_add() { + local __aa_a="$1"; shift + eval "$__aa_a+=(\"\$@\")" +} + +function: array_ins "insérer les valeurs \$2..@ au début du tableau \$1" +function array_ins() { + local __aa_a="$1"; shift + eval "$__aa_a=(\"\$@\" \"\${$__aa_a[@]}\")" +} + +function: array_del "supprimer *les* valeurs \$2 du tableau \$1" +function array_del() { + local __ad_v + local -a __ad_vs + eval ' +for __ad_v in "${'"$1"'[@]}"; do + if [ "$__ad_v" != "$2" ]; then + __ad_vs=("${__ad_vs[@]}" "$__ad_v") + fi +done' + array_copy "$1" __ad_vs +} + +function: array_addu "ajouter la valeur \$2 au tableau \$1, si la valeur n'y est pas déjà + +Retourner vrai si la valeur a été ajoutée" +function array_addu() { + local __as_v + eval ' +for __as_v in "${'"$1"'[@]}"; do + [ "$__as_v" == "$2" ] && return 1 +done' + array_add "$1" "$2" + return 0 +} + +function: array_insu "insérer la valeur \$2 au début du tableau tableau \$1, si la valeur n'y est pas déjà + +Retourner vrai si la valeur a été ajoutée." +function array_insu() { + local __as_v + eval ' +for __as_v in "${'"$1"'[@]}"; do + [ "$__as_v" == "$2" ] && return 1 +done' + array_ins "$1" "$2" + return 0 +} + +function: array_fillrange "Initialiser le tableau \$1 avec les nombres de \$2(=1) à \$3(=10) avec un step de \$4(=1)" +function array_fillrange() { + local -a __af_vs + local __af_i="${2:-1}" __af_to="${3:-10}" __af_step="${4:-1}" + while [ "$__af_i" -le "$__af_to" ]; do + __af_vs=("${__af_vs[@]}" "$__af_i") + __af_i=$(($__af_i + $__af_step)) + done + array_copy "$1" __af_vs +} + +function: array_eq "tester l'égalité des tableaux \$1 et \$2" +function array_eq() { + local -a __ae_a1 __ae_a2 + array_copy __ae_a1 "$1" + array_copy __ae_a2 "$2" + [ ${#__ae_a1[*]} -eq ${#__ae_a2[*]} ] || return 1 + local __ae_v __ae_i=0 + for __ae_v in "${__ae_a1[@]}"; do + [ "$__ae_v" == "${__ae_a2[$__ae_i]}" ] || return 1 + __ae_i=$(($__ae_i + 1)) + done + return 0 +} + +function: array_contains "tester si le tableau \$1 contient la valeur \$2" +function array_contains() { + local __ac_v + eval ' +for __ac_v in "${'"$1"'[@]}"; do + [ "$__ac_v" == "$2" ] && return 0 +done' + return 1 +} + +function: array_icontains "tester si le tableau \$1 contient la valeur \$2, sans tenir compte de la casse" +function array_icontains() { + local __ac_v + eval ' +for __ac_v in "${'"$1"'[@]}"; do + [ "${__ac_v,,} == "${2,,}" ] && return 0 +done' + return 1 +} + +function: array_find "si le tableau \$1 contient la valeur \$2, afficher l'index de la valeur. Si le tableau \$3 est spécifié, afficher la valeur à l'index dans ce tableau" +function array_find() { + local __af_i __af_v + __af_i=0 + eval ' +for __af_v in "${'"$1"'[@]}"; do + if [ "$__af_v" == "$2" ]; then + if [ -n "$3" ]; then + recho "${'"$3"'[$__af_i]}" + else + echo "$__af_i" + fi + return 0 + fi + __af_i=$(($__af_i + 1)) +done' + return 1 +} + +function: array_reverse "Inverser l'ordre des élément du tableau \$1" +function array_reverse() { + local -a __ar_vs + local __ar_v + array_copy __ar_vs "$1" + array_new "$1" + for __ar_v in "${__ar_vs[@]}"; do + array_ins "$1" "$__ar_v" + done +} + +function: array_replace "dans le tableau \$1, remplacer toutes les occurences de \$2 par \$3..*" +function array_replace() { + local __ar_sn="$1"; shift + local __ar_f="$1"; shift + local -a __ar_s __ar_d + local __ar_v + array_copy __ar_s "$__ar_sn" + for __ar_v in "${__ar_s[@]}"; do + if [ "$__ar_v" == "$__ar_f" ]; then + __ar_d=("${__ar_d[@]}" "$@") + else + __ar_d=("${__ar_d[@]}" "$__ar_v") + fi + done + array_copy "$__ar_sn" __ar_d +} + +function: array_each "Pour chacune des valeurs ITEM du tableau \$1, appeler la fonction \$2 avec les arguments (\$3..@ ITEM)" +function array_each() { + local __ae_v + local -a __ae_a + array_copy __ae_a "$1"; shift + for __ae_v in "${__ae_a[@]}"; do + "$@" "$__ae_v" + done +} + +function: array_map "Pour chacune des valeurs ITEM du tableau \$1, appeler la fonction \$2 avec les arguments (\$3..@ ITEM), et remplacer la valeur par le résultat de la fonction" +function array_map() { + local __am_v + local -a __am_a __am_vs + local __am_an="$1"; shift + local __am_f="$1"; shift + array_copy __am_a "$__am_an" + for __am_v in "${__am_a[@]}"; do + __am_vs=("${__am_vs[@]}" "$("$__am_f" "$@" "$__am_v")") + done + array_copy "$__am_an" __am_vs +} + +function: array_first "afficher la première valeur du tableau \$1" +function array_first() { + eval "recho \"\${$1[@]:0:1}\"" +} + +function: array_last "afficher la dernière valeur du tableau \$1" +function array_last() { + eval "recho \"\${$1[@]: -1:1}\"" +} + +function: array_copy_firsts "copier toutes les valeurs du tableau \$2(=\$1) dans le tableau \$1, excepté la dernière" +function array_copy_firsts() { + eval "$1=(\"\${${2:-$1}[@]:0:\$((\${#${2:-$1}[@]}-1))}\")" +} + +function: array_copy_lasts "copier toutes les valeurs du tableau \$2(=\$1) dans le tableau \$1, excepté la première" +function array_copy_lasts() { + eval "$1=(\"\${${2:-$1}[@]:1}\")" +} + +function: array_extend "ajouter le contenu du tableau \$2 au tableau \$1" +function array_extend() { + eval "$1=(\"\${$1[@]}\" \"\${$2[@]}\")" +} + +function: array_extendu "ajouter chacune des valeurs du tableau \$2 au tableau \$1, si ces valeurs n'y sont pas déjà + +Retourner vrai si au moins une valeur a été ajoutée" +function array_extendu() { + local __ae_v __ae_s=1 + eval ' +for __ae_v in "${'"$2"'[@]}"; do + array_addu "$1" "$__ae_v" && __ae_s=0 +done' + return "$__ae_s" +} + +function: array_extend_firsts "ajouter toutes les valeurs du tableau \$2 dans le tableau \$1, excepté la dernière" +function array_extend_firsts() { + eval "$1=(\"\${$1[@]}\" \"\${$2[@]:0:\$((\${#$2[@]}-1))}\")" +} + +function: array_extend_lasts "ajouter toutes les valeurs du tableau \$2 dans le tableau \$1, excepté la première" +function array_extend_lasts() { + eval "$1=(\"\${$1[@]}\" \"\${$2[@]:1}\")" +} + +function: array_xsplit "créer le tableau \$1 avec chaque élément de \$2 (un ensemble d'éléments séparés par \$3, qui vaut ':' par défaut)" +function array_xsplit() { + eval "$1=($(recho_ "$2" | lawk -v RS="${3:-:}" ' +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +}'))" #" +} + +function: array_xsplitc "variante de array_xsplit() où le séparateur est ',' par défaut" +function array_xsplitc() { + array_xsplit "$1" "$2" "${3:-,}" +} + +function: array_split "créer le tableau \$1 avec chaque élément de \$2 (un ensemble d'éléments séparés par \$3, qui vaut ':' par défaut) + +Les éléments vides sont ignorés. par exemple \"a::b\" est équivalent à \"a:b\"" +function array_split() { + eval "$1=($(recho_ "$2" | lawk -v RS="${3:-:}" ' +/^$/ { next } +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +}'))" #" +} + +function: array_splitc "variante de array_split() où le séparateur est ',' par défaut" +function array_splitc() { + array_split "$1" "$2" "${3:-,}" +} + +function: array_xsplitl "créer le tableau \$1 avec chaque ligne de \$2" +function array_xsplitl() { + eval "$1=($(recho_ "$2" | strnl2lf | lawk ' +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +}'))" #" +} + +function: array_splitl "créer le tableau \$1 avec chaque ligne de \$2 + +Les lignes vides sont ignorés." +function array_splitl() { + eval "$1=($(recho_ "$2" | strnl2lf | lawk ' +/^$/ { next } +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +}'))" #" +} + +function: array_join "afficher le contenu du tableau \$1 sous forme d'une liste de valeurs séparées par \$2 (qui vaut ':' par défaut) + +* Si \$1==\"@\", alors les éléments du tableaux sont les arguments de la fonction à partir de \$3 +* Si \$1!=\"@\" et que le tableau est vide, afficher \$3 +* Si \$1!=\"@\", \$4 et \$5 sont des préfixes et suffixes à rajouter à chaque élément" +function array_join() { + local __aj_an __aj_l __aj_j __aj_s="${2:-:}" __aj_pf __aj_sf + if [ "$1" == "@" ]; then + __aj_an="\$@" + shift; shift + else + __aj_an="\${$1[@]}" + __aj_pf="$4" + __aj_sf="$5" + fi + eval ' +for __aj_l in "'"$__aj_an"'"; do + __aj_j="${__aj_j:+$__aj_j'"$__aj_s"'}$__aj_pf$__aj_l$__aj_sf" +done' + if [ -n "$__aj_j" ]; then + recho "$__aj_j" + elif [ "$__aj_an" != "\$@" -a -n "$3" ]; then + recho "$3" + fi +} + +function: array_joinc "afficher les éléments du tableau \$1 séparés par ','" +function array_joinc() { + array_join "$1" , "$2" "$3" "$4" +} + +function: array_joinl "afficher les éléments du tableau \$1 à raison d'un élément par ligne" +function array_joinl() { + array_join "$1" " +" "$2" "$3" "$4" +} + +function: array_mapjoin "map le tableau \$1 avec la fonction \$2, puis afficher le résultat en séparant chaque élément par \$3 + +Les arguments et la sémantique sont les mêmes que pour array_join() en +tenant compte de l'argument supplémentaire \$2 qui est la fonction pour +array_map() (les autres arguments sont décalés en conséquence)" +function array_mapjoin() { + local __amj_src="$1" __amj_func="$2" __amj_sep="$3" + shift; shift; shift + if [ "$__amj_src" == "@" ]; then + local -a __amj_tmpsrc + __amj_tmpsrc=("$@") + __amj_src=__amj_tmpsrc + set -- + fi + local -a __amj_tmp + array_copy __amj_tmp "$__amj_src" + array_map __amj_tmp "$__amj_func" + array_join __amj_tmp "$__amj_sep" "$@" +} + +function: array_fix_paths "Corriger les valeurs du tableau \$1. Les valeurs contenant le séparateur \$2(=':') sont séparées en plusieurs valeurs. + +Par exemple avec le tableau input=(a b:c), le résultat est input=(a b c)" +function array_fix_paths() { + local __afp_an="$1" __afp_s="${2:-:}" + local -a __afp_vs + local __afp_v + array_copy __afp_vs "$__afp_an" + array_new "$__afp_an" + for __afp_v in "${__afp_vs[@]}"; do + array_split __afp_v "$__afp_v" "$__afp_s" + array_extend "$__afp_an" __afp_v + done +} diff --git a/bash/base.core.sh b/bash/base.core.sh new file mode 100644 index 0000000..8e9ae2b --- /dev/null +++ b/bash/base.core.sh @@ -0,0 +1,458 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: base.core "Fonctions de base: fondement" + +function: echo_ "afficher la valeur \$* sans passer à la ligne" +function echo_() { echo -n "$*"; } + +function: recho "afficher une valeur brute. + +contrairement à la commande echo, ne reconnaitre aucune option (i.e. -e, -E, -n +ne sont pas signifiants)" +function recho() { + if [[ "${1:0:2}" == -[eEn] ]]; then + local first="${1:1}"; shift + echo -n - + echo "$first" "$@" + else + echo "$@" + fi +} + +function: recho_ "afficher une valeur brute, sans passer à la ligne. + +contrairement à la commande echo, ne reconnaitre aucune option (i.e. -e, -E, -n +ne sont pas signifiants)" +function recho_() { + if [[ "${1:0:2}" == -[eEn] ]]; then + local first="${1:1}"; shift + echo -n - + echo -n "$first" "$@" + else + echo -n "$@" + fi +} + +function: _qval "Dans la chaine \$*, remplacer: +~~~ +\\ par \\\\ +\" par \\\" +\$ par \\\$ +\` par \\\` +~~~ + +Cela permet de quoter une chaine à mettre entre guillements. + +note: la protection de ! n'est pas effectuée, parce que le comportement du shell +est incohérent entre le shell interactif et les scripts. Pour une version plus +robuste, il est nécessaire d'utiliser un programme externe tel que sed ou awk" +function _qval() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + recho_ "$s" +} + +function: should_quote "Tester si la chaine \$* doit être mise entre quotes" +function should_quote() { + # pour optimiser, toujours mettre entre quotes si plusieurs arguments sont + # spécifiés ou si on spécifie une chaine vide ou de plus de 80 caractères + [ $# -eq 0 -o $# -gt 1 -o ${#1} -eq 0 -o ${#1} -gt 80 ] && return 0 + # sinon, tester si la chaine contient des caractères spéciaux + local s="$*" + s="${s//[a-zA-Z0-9]/}" + s="${s//,/}" + s="${s//./}" + s="${s//+/}" + s="${s//\//}" + s="${s//-/}" + s="${s//_/}" + s="${s//=/}" + [ -n "$s" ] +} + +function: qval "Afficher la chaine \$* quotée avec \"" +function qval() { + echo -n \" + _qval "$@" + echo \" +} + +function: qvalm "Afficher la chaine \$* quotée si nécessaire avec \"" +function qvalm() { + if should_quote "$@"; then + echo -n \" + _qval "$@" + echo \" + else + recho "$@" + fi +} + +function: qvalr "Afficher la chaine \$* quotée si nécessaire avec \", sauf si elle est vide" +function qvalr() { + if [ -z "$*" ]; then + : + elif should_quote "$@"; then + echo -n \" + _qval "$@" + echo \" + else + recho "$@" + fi +} + +function: qvals "Afficher chaque argument de cette fonction quotée le cas échéant avec \", chaque valeur étant séparée par un espace" +function qvals() { + local arg first=1 + for arg in "$@"; do + [ -z "$first" ] && echo -n " " + if should_quote "$arg"; then + echo -n \" + _qval "$arg" + echo -n \" + else + recho_ "$arg" + fi + first= + done + [ -z "$first" ] && echo +} + +function: qwc "Dans la chaine \$*, remplacer: +~~~ + \\ par \\\\ +\" par \\\" +\$ par \\\$ +\` par \\\` +~~~ +puis quoter la chaine avec \", sauf les wildcards *, ? et [class] + +Cela permet de quoter une chaine permettant de glober des fichiers, e.g +~~~ +eval \"ls \$(qwc \"\$value\")\" +~~~ + +note: la protection de ! n'est pas effectuée, parce que le comportement du shell +est incohérent entre le shell interactif et les scripts. Pour une version plus +robuste, il est nécessaire d'utiliser un programme externe tel que sed ou awk" +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b c + while [ -n "$s" ]; do + a=; b=; c= + a=; [[ "$s" == *\** ]] && { a="${s%%\**}"; a=${#a}; } + b=; [[ "$s" == *\?* ]] && { b="${s%%\?*}"; b=${#b}; } + c=; [[ "$s" == *\[* ]] && { c="${s%%\[*}"; c=${#c}; } + if [ -z "$a" -a -z "$b" -a -z "$c" ]; then + r="$r\"$s\"" + break + fi + if [ -n "$a" ]; then + [ -n "$b" ] && [ $a -lt $b ] && b= + [ -n "$c" ] && [ $a -lt $c ] && c= + fi + if [ -n "$b" ]; then + [ -n "$a" ] && [ $b -lt $a ] && a= + [ -n "$c" ] && [ $b -lt $c ] && c= + fi + if [ -n "$c" ]; then + [ -n "$a" ] && [ $c -lt $a ] && a= + [ -n "$b" ] && [ $c -lt $b ] && b= + fi + if [ -n "$a" ]; then # PREFIX* + a="${s%%\**}" + s="${s#*\*}" + [ -n "$a" ] && r="$r\"$a\"" + r="$r*" + elif [ -n "$b" ]; then # PREFIX? + a="${s%%\?*}" + s="${s#*\?}" + [ -n "$a" ] && r="$r\"$a\"" + r="$r?" + elif [ -n "$c" ]; then # PREFIX[class] + a="${s%%\[*}" + b="${s#*\[}"; b="${b%%\]*}" + s="${s:$((${#a} + ${#b} + 2))}" + [ -n "$a" ] && r="$r\"$a\"" + r="$r[$b]" + fi + done + recho_ "$r" +} + +function: qlines "Traiter chaque ligne de l'entrée standard pour en faire des chaines quotées avec '" +function qlines() { + sed "s/'/'\\\\''/g; s/.*/'&'/g" +} + +function: setv "initialiser la variable \$1 avec la valeur \$2..* + +note: en principe, la syntaxe est 'setv var values...'. cependant, la syntaxe 'setv var=values...' est supportée aussi" +function setv() { + local s__var="$1"; shift + if [[ "$s__var" == *=* ]]; then + set -- "${s__var#*=}" "$@" + s__var="${s__var%%=*}" + fi + eval "$s__var=\"\$*\"" +} + +function: _setv "Comme la fonction setv() mais ne supporte que la syntaxe '_setv var values...' + +Cette fonction est légèrement plus rapide que setv()" +function _setv() { + local s__var="$1"; shift + eval "$s__var=\"\$*\"" +} + +function: echo_setv "Afficher la commande qui serait lancée par setv \"\$@\"" +function echo_setv() { + local s__var="$1"; shift + if [[ "$s__var" == *=* ]]; then + set -- "${s__var#*=}" "$@" + s__var="${s__var%%=*}" + fi + echo "$s__var=$(qvalr "$*")" +} + +function: echo_setv2 "Afficher la commande qui recrée la variable \$1. + +Equivalent à +~~~ +echo_setv \"\$1=\${!1}\" +~~~ + +Si d'autres arguments que le nom de la variable sont spécifiés, cette fonction +se comporte comme echo_setv()" +function echo_setv2() { + local s__var="$1"; shift + if [[ "$s__var" == *=* ]]; then + set -- "${s__var#*=}" "$@" + s__var="${s__var%%=*}" + fi + if [ $# -eq 0 ]; then + echo_setv "$s__var" "${!s__var}" + else + echo_setv "$s__var" "$@" + fi +} + +function: seta "initialiser le tableau \$1 avec les valeurs \$2..@ + +note: en principe, la syntaxe est 'seta array values...'. cependant, la syntaxe +'seta array=values...' est supportée aussi" +function seta() { + local s__array="$1"; shift + if [[ "$s__array" == *=* ]]; then + set -- "${s__array#*=}" "$@" + s__array="${s__array%%=*}" + fi + eval "$s__array=(\"\$@\")" +} + +function: _seta "Comme la fonction seta() mais ne supporte que la syntaxe '_seta array values...' + +Cette fonction est légèrement plus rapide que seta()" +function _seta() { + local s__array="$1"; shift + eval "$s__array=(\"\$@\")" +} + +function: echo_seta "Afficher la commande qui serait lancée par seta \"\$@\"" +function echo_seta() { + local s__var="$1"; shift + if [[ "$s__var" == *=* ]]; then + set -- "${s__var#*=}" "$@" + s__var="${s__var%%=*}" + fi + echo "$s__var=($(qvals "$@"))" +} + +function: echo_seta2 "Afficher la commande qui recrée le tableau \$1 + +Si d'autres arguments que le nom de tableau sont spécifiés, cette fonction se +comporte comme echo_seta()" +function echo_seta2() { + local s__var="$1"; shift + if [[ "$s__var" == *=* ]]; then + set -- "${s__var#*=}" "$@" + s__var="${s__var%%=*}" + elif [ $# -eq 0 ]; then + eval "set -- \"\${$s__var[@]}\"" + fi + echo "$s__var=($(qvals "$@"))" +} + +function: setx "Initialiser une variable avec le résultat d'une commande + +* syntaxe 1: initialiser la variable \$1 avec le résultat de la commande \"\$2..@\" + ~~~ + setx var cmd + ~~~ + note: en principe, la syntaxe est 'setx var cmd args...'. cependant, la syntaxe + 'setx var=cmd args...' est supportée aussi + +* syntaxe 2: initialiser le tableau \$1 avec le résultat de la commande + \"\$2..@\", chaque ligne du résultat étant un élément du tableau + ~~~ + setx -a array cmd + ~~~ + note: en principe, la syntaxe est 'setx -a array cmd args...'. cependant, la + syntaxe 'setx -a array=cmd args...' est supportée aussi" +function setx() { + if [ "$1" == -a ]; then + shift + local s__array="$1"; shift + if [[ "$s__array" == *=* ]]; then + set -- "${s__array#*=}" "$@" + s__array="${s__array%%=*}" + fi + eval "$s__array=($("$@" | qlines))" + else + local s__var="$1"; shift + if [[ "$s__var" == *=* ]]; then + set -- "${s__var#*=}" "$@" + s__var="${s__var%%=*}" + fi + eval "$s__var="'"$("$@")"' + fi +} + +function: _setvx "Comme la fonction setx() mais ne supporte que l'initialisation d'une variable scalaire avec la syntaxe '_setvx var cmd args...' pour gagner (un peu) en rapidité d'exécution." +function _setvx() { + local s__var="$1"; shift + eval "$s__var="'"$("$@")"' +} + +function: _setax "Comme la fonction setx() mais ne supporte que l'initialisation d'un tableau avec la syntaxe '_setax array cmd args...' pour gagner (un peu) en rapidité d'exécution." +function _setax() { + local s__array="$1"; shift + eval "$s__array=($("$@" | qlines))" +} + +function: is_defined "tester si la variable \$1 est définie" +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} + +function: is_array "tester si la variable \$1 est un tableau" +function is_array() { + [[ "$(declare -p "$1" 2>/dev/null)" =~ declare\ -[^\ ]*a[^\ ]*\ ]] +} + +function: array_local "afficher les commandes pour faire une copie dans la variable locale \$1 du tableau \$2" +function array_local() { + if [ "$1" == "$2" ]; then + declare -p "$1" 2>/dev/null || echo "local -a $1" + else + echo "local -a $1; $1=(\"\${$2[@]}\")" + fi +} + +function: upvar "Implémentation de upvar() de http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference + +USAGE +~~~ +local varname && upvar varname values... +~~~ +* @param varname Variable name to assign value to +* @param values Value(s) to assign. If multiple values (> 1), an array is + assigned, otherwise a single value is assigned." +function upvar() { + if unset -v "$1"; then + if [ $# -lt 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} + +function: array_upvar "Comme upvar() mais force la création d'un tableau, même s'il y a que 0 ou 1 argument" +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} + +function: upvars "Implémentation modifiée de upvars() de http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference + +Par rapport à l'original, il n'est plus nécessaire de préfixer une variable +scalaire avec -v, et -a peut être spécifié sans argument. + +USAGE +~~~ +local varnames... && upvars [varname value | -aN varname values...]... +~~~ +* @param -a assigns remaining values to varname as array +* @param -aN assigns next N values to varname as array. Returns 1 if wrong + number of options occurs" +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function: set_debug "Passer en mode DEBUG" +function set_debug() { + export NUCORE_DEBUG=1 +} + +function: is_debug "Tester si on est en mode DEBUG" +function is_debug() { + [ -n "$NUCORE_DEBUG" ] +} + +function: lawk "Lancer GNUawk avec la librairie 'base'" +function lawk() { + gawk -i base.awk "$@" +} + +function: cawk "Lancer GNUawk avec LANG=C et la librairie 'base' + +Le fait de forcer la valeur de LANG permet d'éviter les problèmes avec la locale" +function cawk() { + LANG=C gawk -i base.awk "$@" +} + +function: lsort "Lancer sort avec support de la locale courante" +function: csort "Lancer sort avec LANG=C pour désactiver le support de la locale + +Avec LANG!=C, sort utilise les règles de la locale pour le tri, et par +exemple, avec LANG=fr_FR.UTF-8, la locale indique que les ponctuations doivent +être ignorées." +function lsort() { sort "$@"; } +function csort() { LANG=C sort "$@"; } + +function: lgrep "Lancer grep avec support de la locale courante" +function: cgrep "Lancer grep avec LANG=C pour désactiver le support de la locale" +function lgrep() { grep "$@"; } +function cgrep() { LANG=C grep "$@"; } + +function: lsed "Lancer sed avec support de la locale courante" +function: csed "Lancer sed avec LANG=C pour désactiver le support de la locale" +function lsed() { sed "$@"; } +function csed() { LANG=C sed "$@"; } + +function: ldiff "Lancer diff avec support de la locale courante" +function: cdiff "Lancer diff avec LANG=C pour désactiver le support de la locale" +function ldiff() { diff "$@"; } +function cdiff() { LANG=C diff "$@"; } diff --git a/bash/base.eval.sh b/bash/base.eval.sh new file mode 100644 index 0000000..fff1268 --- /dev/null +++ b/bash/base.eval.sh @@ -0,0 +1,468 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: base.eval "Fonctions de base: évaluation d'expressions" +require: base.str base.arr + +################################################################################ +# Chaines + +function: evals "Appliquer à une chaine de caractères une suite de traitements, e.g: +~~~ +evals var deref +suffix +~~~ +est équivalent à +~~~ +echo \"\${var}suffix\" +~~~ + +En commençant avec la valeur initiale \$1, les arguments \$2..* sont des +opérations à appliquer dans l'ordre. + +Les opérations suivantes considèrent que la valeur courante est un nom de +variable: +~~~ +:- := :? :+ deref dcount +~~~ + +Toutes les autres opérations travaillent directement avec la valeur +courante. Les opérations suivantes appliquent une transformation: +~~~ +# % / : ^ , +# -# +% -% + - mid repl +~~~ +IMPORTANT: aucune de ces fonctions ne met en échappement les valeur des +patterns. Ainsi, si un pattern contient des caractères interdits comme \\ ou \$, +il faut d'abord le traiter avec _qval() + +Les opérations suivantes font un test sur la valeur et retournent immédiatement: +~~~ += == != < > -eq -ne -lt -le -gt -ge -n -z +~~~ + +La syntaxe des opérateurs standards de bash est reprise autant que possible, i.e +si on a l'habitude d'écrire ${varOP} en bash, alors la syntaxe à utiliser à +priori est 'evals var OP' ou 'evals var deref OP' suivant les +opérateurs. + +Autres opérateurs: +~~~ +deref indirection +dcount nombre d'éléments du tableau ++#STR ajouter un préfixe +-#STR supprimer un préfixe ++%STR ou +STR ajouter un suffixe +-%STR ou -STR supprimer un suffixe +mid RANGE traiter la chaine avec strmid() +repl FROM TO traiter la chaine avec strrepl() +~~~ + +Tout autre opérateur est traité comme un appel à une fonction qui prend un seul +argument, la valeur courante, et qui affiche le résultat." +function evals() { + local -a es__tmp + local es__value="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + # l'argument est le nom de la variable + :-*|:=*|:\?*|:+*) eval 'es__value="${'"${es__value}$1"'}"';; + d|deref) es__value="${!es__value}";; + dc|dcount|ds|dsize) + es__value="${es__value}[@]" + es__tmp=("${!es__value}") + es__value="${#es__tmp[@]}" + ;; + # l'argument est la valeur de la variable + \#*|%*|/*|:*|^*|,*) eval 'es__value="${es__value'"$1"'}"';; + l|length) es__value="${#es__value}";; + =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge) + es__tmp=(\[ "$es__value" "$@" ]); "${es__tmp[@]}"; return $?;; + -n|-z) es__tmp=(\[ "$1" "$es__value" ]); "${es__tmp[@]}"; return $?;; + +#*) eval 'es__value="'"${1#+#}"'$es__value"';; + -#*) eval 'es__value="${es__value'"${1#-}"'}"';; + +%*) eval 'es__value="$es__value"'"${1#+%}";; + +*) eval 'es__value="$es__value"'"${1#+}";; + -%*) eval 'es__value="${es__value'"${1#-}"'}"';; + -*) eval 'es__value="${es__value%'"${1#-}"'}"';; + mid|strmid) eval 'es__value="$(strmid "$2" "$es__value")"'; shift;; + repl|strrepl) eval 'es__value="$(strrepl "$2" "$3" "$es__value")"'; shift; shift;; + *) es__value="$("$1" "$es__value")";; + esac + shift + done + echo "$es__value" +} + +function: setxs "équivalent à setx \$1 evals \$2..@" +function setxs() { + local -a ss__args + if [ "$1" == -a ]; then ss__args=(-a); shift; fi + local ss__var="$1"; shift + if [[ "$ss__var" == *=* ]]; then + set -- "${ss__var#*=}" "$@" + ss__var="${ss__var%%=*}" + fi + ss__args=("${ss__args[@]}" "$ss__var") + setx "${ss__args[@]}" evals "$@" +} + +function: cmds "lancer une commande avec comme argument le résultat de evals + +Par exemple, les deux commandes suivantes sont équivalentes: +~~~ +cmds CMD ARGS... // EVALARGS +CMD ARGS... \"\$(evals EVALARGS)\" +~~~" +function cmds() { + local cs__arg + local -a cs__cmd + while [ $# -gt 0 ]; do + cs__arg="$1"; shift + [ "$cs__arg" == // ] && break + cs__cmd=("${cs__cmd[@]}" "$cs__arg") + done + "${cs__cmd[@]}" "$(evals "$@")" +} + +function: evalm "construire une chaine en mixant chaines statiques et évaluations de commandes + +Par exemple, les deux commandes suivantes sont équivalentes: +~~~ +evalm //\"string\" cmd args // cmd args //\"string\" +echo \"string\$(cmd args)\$(cmd args)string\" +~~~" +function evalm() { + local em__val em__arg + local -a em__cmd + while [ $# -gt 0 ]; do + em__arg="$1" + if [ "${em__arg#//}" != "$em__arg" ]; then + em__val="$em__val${em__arg#//}" + shift + continue + fi + em__cmd=() + while [ $# -gt 0 ]; do + em__arg="$1" + [ "${em__arg#//}" != "$em__arg" ] && break + shift + if [ "${em__arg%//}" != "$em__arg" ]; then + local em__tmp="${em__arg%//}" + if [ -z "${em__tmp//\\/}" ]; then + em__arg="${em__arg#\\}" + em__cmd=("${em__cmd[@]}" "$em__arg") + continue + fi + fi + em__cmd=("${em__cmd[@]}" "$em__arg") + done + [ ${#em__cmd[*]} -gt 0 ] && em__val="$em__val$("${em__cmd[@]}")" + done + echo "$em__val" +} + +function: setxm "équivalent à setx \$1 evalm \$2..@" +function setxm() { + local -a sm__args + if [ "$1" == -a ]; then sm__args=(-a); shift; fi + local sm__var="$1"; shift + if [[ "$sm__var" == *=* ]]; then + set -- "${sm__var#*=}" "$@" + sm__var="${sm__var%%=*}" + fi + sm__args=("${sm__args[@]}" "$sm__var") + setx "${sm__args[@]}" evalm "$@" +} + +function: cmdm "lancer une commande avec comme argument le résultat de evalm + +Par exemple, les deux commandes suivantes sont équivalentes: +~~~ +cmdm CMD ARGS... // EVALARGS +CMD ARGS... \"\$(evalm EVALARGS)\" +~~~" +function cmdm() { + local cm__arg + local -a cm__cmd + while [ $# -gt 0 ]; do + cm__arg="$1"; shift + [ "$cm__arg" == // ] && break + cm__cmd=("${cm__cmd[@]}" "$cm__arg") + done + "${cm__cmd[@]}" "$(evalm "$@")" +} + +################################################################################ +# Nombres + +function: evali "Evaluer une expression numérique" +function evali() { + echo "$(($*))" +} + +################################################################################ +# Tableaux + +################################################################################ +# Composition + +function: evalc "Implémenter une syntaxe lisible et naturelle permettant d'enchainer des traitements sur une valeur. + +Par exemple, la commande +~~~ +evalc cmd1... // cmd2... // cmd3... +~~~ +est équivalente à la commande +~~~ +cmd3... \"\$(cmd2... \"\$(cmd1...)\")\" +~~~" +function evalc() { + local ec__arg ec__cmd ec__finalcmd + + while [ $# -gt 0 ]; do + ec__arg="$1"; shift + if [ "$ec__arg" == // ]; then + if [ ${#ec__cmd} -gt 0 ]; then + if [ ${#ec__finalcmd} -eq 0 ]; then ec__finalcmd="$ec__cmd" + else ec__finalcmd="$ec__cmd \$($ec__finalcmd)" + fi + fi + ec__cmd= + continue + elif [ "${ec__arg%//}" != "$ec__arg" ]; then + local tmp="${ec__arg%//}" + [ -z "${tmp//\\/}" ] && ec__arg="${ec__arg#\\}" + fi + ec__cmd="$ec__cmd \"$(_qval "$ec__arg")\"" + done + if [ ${#ec__cmd} -gt 0 ]; then + if [ ${#ec__finalcmd} -eq 0 ]; then ec__finalcmd="$ec__cmd" + else ec__finalcmd="$ec__cmd \$($ec__finalcmd)" + fi + fi + eval "$ec__finalcmd" +} + +function: setxc "équivalent à setx \$1 evalc \$2..@" +function setxc() { + local -a sx__args + if [ "$1" == -a ]; then sx__args=(-a); shift; fi + local sx__var="$1"; shift + if [[ "$sx__var" == *=* ]]; then + set -- "${sx__var#*=}" "$@" + sx__var="${sx__var%%=*}" + fi + sx__args=("${sx__args[@]}" "$sx__var") + setx "${sx__args[@]}" evalc "$@" +} + +################################################################################ +# Chainage + +function: evalp "Implémenter une syntaxe alternative permettant d'enchainer des traitements sur un flux de données. + +Par exemple, la commande +~~~ +evalp cmd1... // cmd2... // cmd3... +~~~ +affiche le résultat de la commande +~~~ +cmd1... | cmd2... | cmd3... +~~~ + +Typiquement, cette fonction permet de faciliter la *construction* d'un +enchainement de commandes par programme, ou de faciliter l'utilisation de la +fonction setx() pour récupérer le résultat d'un enchainement. Dans les autres +cas, il est plus simple et naturel d'écrire les enchainements avec la syntaxe de +bash." +function evalp() { + local ep__arg ep__cmd + + while [ $# -gt 0 ]; do + ep__arg="$1"; shift + if [ "$ep__arg" == // ]; then + ep__cmd="$ep__cmd |" + continue + elif [ "${ep__arg%//}" != "$ep__arg" ]; then + local ep__tmp="${ep__arg%//}" + if [ -z "${ep__tmp//\\/}" ]; then + ep__arg="${ep__arg#\\}" + fi + fi + ep__cmd="${ep__cmd:+$ep__cmd }\"$(_qval "$ep__arg")\"" + done + eval "$ep__cmd" +} + +function: setxp "équivalent à setx \$1 evalp \$2..@" +function setxp() { + local -a sp__args + if [ "$1" == -a ]; then sp__args=(-a); shift; fi + local sp__var="$1"; shift + if [[ "$sp__var" == *=* ]]; then + set -- "${sp__var#*=}" "$@" + sp__var="${sp__var%%=*}" + fi + sp__args=("${sp__args[@]}" "$sp__var") + setx "${sp__args[@]}" evalp "$@" +} + +function: cmdp "lancer une commande avec comme argument le résultat de evalp + +Par exemple, les deux commandes suivantes sont équivalentes: +~~~ +cmdp CMD ARGS... // EVALARGS +CMD ARGS... \"\$(evalp EVALARGS)\" +~~~" +function cmdp() { + local cp__arg + local -a cp__cmd + while [ $# -gt 0 ]; do + cp__arg="$1"; shift + [ "$cp__arg" == // ] && break + cp__cmd=("${cp__cmd[@]}" "$cp__arg") + done + "${cp__cmd[@]}" "$(evalp "$@")" +} + +################################################################################ +# Générique + +function: evalx "" +function evalx() { + : +} + +function: setxx "équivalent à setx \$1 evalx \$2..@" +function setxx() { + local -a sx__args + if [ "$1" == -a ]; then sx__args=(-a); shift; fi + local sx__var="$1"; shift + if [[ "$sx__var" == *=* ]]; then + set -- "${sx__var#*=}" "$@" + sx__var="${sx__var%%=*}" + fi + sx__args=("${sx__args[@]}" "$sx__var") + setx "${sx__args[@]}" evalx "$@" +} + +function: cmdx "lancer une commande avec comme argument le résultat de evalx + +Par exemple, les deux commandes suivantes sont équivalentes: +~~~ +cmdx CMD ARGS... // EVALARGS +CMD ARGS... \"\$(evalx EVALARGS)\" +~~~" +function cmdx() { + local cx__arg + local -a cx__cmd + while [ $# -gt 0 ]; do + cx__arg="$1"; shift + [ "$cx__arg" == // ] && break + cx__cmd=("${cx__cmd[@]}" "$cx__arg") + done + "${cx__cmd[@]}" "$(evalx "$@")" +} + +function: cmdsplitf "\ +Cette fonction doit être appelée avec N arguments (avec N>1). Elle analyse et +découpe l'argument \$N comme avec une ligne de commande du shell. Ensuite, elle +appelle la fonction \$1 avec les arguments de \$2 à \${N-1}, suivi des arguments +obtenus lors de l'analyse de l'argument \$N. Par exemple, la commande suivante: +~~~ +strsplitf cmd arg1 \"long arg2\" \"arg3 'long arg4'\" +~~~ +est équivalente à: +~~~ +cmd arg1 \"long arg2\" arg3 \"long arg4\" +~~~ + +Retourner le code 127 si la fonction à appeler n'est pas spécifiée. Retourner le +code 126 si une erreur s'est produite lors de l'analyse de l'argument \$N" +function cmdsplitf() { + [ $# -gt 0 ] || return 127 + local func count + func="$1"; shift + count=$# + if [ $count -gt 0 ]; then + eval 'set -- "${@:1:$(($count-1))}" '"${!count}" || return 126 + fi + "$func" "$@" +} + +################################################################################ +# Tests + +function: testx "Faire un test unaire avec la commande [ sur une valeur calculée avec evalx. + +Utiliser la syntaxe 'testx op cmds...' e.g. +~~~ +testx -z cmd1 // cmd2 +~~~" +function testx() { + local t__op="$1"; shift + local t__val="$(evalx "$@")" + [ $t__op "$t__val" ] +} + +function: test2x "Faire une test binaire avec la commande [ entre une valeur spécifiée et une valeur calculée avec evalx. + +Utiliser la syntaxe 'test2x value op cmds...' e.g. +~~~ +test2x value == cmd1 // cmd2 +~~~" +function test2x() { + local t__val1="$1"; shift + local t__op="$1"; shift + local t__val2="$(evalx "$@")" + [ "$t__val1" $t__op "$t__val2" ] +} + +function: testrx "Faire une test binaire avec la commande [[ entre une valeur spécifiée et une valeur calculée avec evalx. + +Utiliser la syntaxe 'testrx value op cmds...' e.g. +~~~ +testrx value == cmd1 // cmd2 +~~~" +function testrx() { + local t__val1="$1"; shift + local t__op="$1"; shift + local t__val2="$(evalx "$@")" + eval '[[ "$t__val1" '"$t__op"' "$t__val2" ]]' +} + +function: testp "Faire un test unaire avec la commande [ sur une valeur calculée avec evalp. + +Utiliser la syntaxe 'testp op cmds...' e.g. +~~~ +testp -z cmd1 // cmd2 +~~~" +function testp() { + local t__op="$1"; shift + local t__val="$(evalp "$@")" + [ $t__op "$t__val" ] +} + +function: test2p "Faire une test binaire avec la commande [ entre une valeur spécifiée et une valeur calculée avec evalp. + +Utiliser la syntaxe 'test2p value op cmds...' e.g. +~~~ +test2p value == cmd1 // cmd2 +~~~" +function test2p() { + local t__val1="$1"; shift + local t__op="$1"; shift + local t__val2="$(evalp "$@")" + [ "$t__val1" $t__op "$t__val2" ] +} + +function: testrp "Faire une test binaire avec la commande [[ entre une valeur spécifiée et une valeur calculée avec evalp. + +Utiliser la syntaxe 'testrp value op cmds...' e.g. +~~~ +testrp value == cmd1 // cmd2 +~~~" +function testrp() { + local t__val1="$1"; shift + local t__op="$1"; shift + local t__val2="$(evalp "$@")" + eval '[[ "$t__val1" '"$t__op"' "$t__val2" ]]' +} diff --git a/bash/base.init.sh b/bash/base.init.sh new file mode 100644 index 0000000..0f2b704 --- /dev/null +++ b/bash/base.init.sh @@ -0,0 +1,53 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: base.init "Fonctions de base: initialiser l'environnement" + +if [ -z "$NUCORE_NO_INIT_ENV" ]; then + # Emplacement du script courant + if [ "$0" == "-bash" ]; then + MYNAME= + MYDIR= + MYSELF= + elif [ ! -f "$0" -a -f "${0#-}" ]; then + MYNAME="$(basename -- "${0#-}")" + MYDIR="$(dirname -- "${0#-}")" + MYDIR="$(cd "$MYDIR"; pwd)" + MYSELF="$MYDIR/$MYNAME" + else + MYNAME="$(basename -- "$0")" + MYDIR="$(dirname -- "$0")" + MYDIR="$(cd "$MYDIR"; pwd)" + MYSELF="$MYDIR/$MYNAME" + fi + [ -n "$NUCOREDIR" ] || NUCOREDIR="$MYDIR" + + # Repertoire temporaire + [ -z "$TMPDIR" -a -d "$HOME/tmp" ] && TMPDIR="$HOME/tmp" + [ -z "$TMPDIR" ] && TMPDIR="${TMP:-${TEMP:-/tmp}}" + export TMPDIR + + # User + [ -z "$USER" -a -n "$LOGNAME" ] && export USER="$LOGNAME" + + # Le fichier nucorerc doit être chargé systématiquement + [ -f /etc/debian_chroot ] && NUCORE_CHROOT=1 + [ -f /etc/nucorerc ] && . /etc/nucorerc + [ -f ~/.nucorerc ] && . ~/.nucorerc + + # Type de système sur lequel tourne le script + UNAME_SYSTEM=`uname -s` + [ "${UNAME_SYSTEM#CYGWIN}" != "$UNAME_SYSTEM" ] && UNAME_SYSTEM=Cygwin + [ "${UNAME_SYSTEM#MINGW32}" != "$UNAME_SYSTEM" ] && UNAME_SYSTEM=Mingw + UNAME_MACHINE=`uname -m` + if [ -n "$NUCORE_CHROOT" ]; then + # Dans un chroot, il est possible de forcer les valeurs + [ -n "$NUCORE_UNAME_SYSTEM" ] && eval "UNAME_SYSTEM=$NUCORE_UNAME_SYSTEM" + [ -n "$NUCORE_UNAME_MACHINE" ] && eval "UNAME_MACHINE=$NUCORE_UNAME_MACHINE" + fi + + # Nom d'hôte respectivement avec et sans domaine + # contrairement à $HOSTNAME, cette valeur peut être spécifiée, comme par ruinst + [ -n "$MYHOST" ] || MYHOST="$HOSTNAME" + [ -n "$MYHOSTNAME" ] || MYHOSTNAME="${HOSTNAME%%.*}" + export MYHOST MYHOSTNAME +fi diff --git a/bash/base.io.sh b/bash/base.io.sh new file mode 100644 index 0000000..d7f46e9 --- /dev/null +++ b/bash/base.io.sh @@ -0,0 +1,1291 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: base.io "Fonctions de base: affichage et saisie" +require: base.arr + +NUCORE__TAB=$'\t' +NUCORE__LATIN1=iso-8859-1 +NUCORE__LATIN9=iso-8859-15 +NUCORE__UTF8=utf-8 +NUCORE_IENC="$NUCORE__UTF8" +NUCORE_OENC="$NUCORE__UTF8" + +if [ ! -x "$(which iconv 2>/dev/null)" ]; then + function iconv() { cat; } +fi + +function nucore__lang_encoding() { + case "${LANG,,}" in + *@euro) echo "iso-8859-15";; + *.utf-8|*.utf8) echo "utf-8";; + *) echo "iso-8859-1";; + esac +} + +function nucore__norm_encoding() { + local enc="${1,,}" + enc="${enc//[-_]/}" + case "$enc" in + latin|latin1|iso8859|iso88591|8859|88591) echo "iso-8859-1";; + latin9|iso885915|885915) echo "iso-8859-15";; + utf|utf8) echo "utf-8";; + *) echo "$1";; + esac +} + +function nucore__init_encoding() { + local DEFAULT_ENCODING="$(nucore__lang_encoding)" + [ -n "$DEFAULT_ENCODING" ] || DEFAULT_ENCODING=utf-8 + [ -n "$NUCORE_OUTPUT_ENCODING" ] || NUCORE_OUTPUT_ENCODING="$DEFAULT_ENCODING" + NUCORE_OUTPUT_ENCODING="$(nucore__norm_encoding "$NUCORE_OUTPUT_ENCODING")" + [ -n "$NUCORE_INPUT_ENCODING" ] || NUCORE_INPUT_ENCODING="$NUCORE_OUTPUT_ENCODING" + NUCORE_INPUT_ENCODING="$(nucore__norm_encoding "$NUCORE_INPUT_ENCODING")" + [ -n "$NUCORE_EDITOR_ENCODING" ] || NUCORE_EDITOR_ENCODING="$NUCORE_INPUT_ENCODING" + NUCORE_EDITOR_ENCODING="$(nucore__norm_encoding "$NUCORE_EDITOR_ENCODING")" + + NUCORE_IENC="$NUCORE_INPUT_ENCODING" + NUCORE_OENC="$NUCORE_OUTPUT_ENCODING" +} +[ -n "$NUCORE_LANG" -a -z "$LANG" ] && export NUCORE_LANG LANG="$NUCORE_LANG" +nucore__init_encoding + +function nucore_local() { +# Afficher les commandes pour rendre locales certaines variables en fonction des +# arguments: +# - opts rend locale args, pour utiliser parse_opts() à l'intérieur d'une +# fonction. +# - verbosity et interaction rendent respectivement locales NUCORE_VERBOSITY et +# NUCORE_INTERACTION. Ceci est utile pour pouvoir appeler sans risque de +# pollution de l'environnement une fonction qui utilise parse_opts() avec les +# définitions de PRETTYOPTS. +# Si aucun arguments n'est fourni, toutes les définitions sont affichées. + local arg + [ $# -gt 0 ] || set -- opts verbosity interaction + for arg in "$@"; do + case "$arg" in + parse_opts|opts|o|args) echo "local -a args";; + verbosity|v) echo "local NUCORE_VERBOSITY='$NUCORE_VERBOSITY'";; + interaction|i) echo "local NUCORE_INTERACTION='$NUCORE_INTERACTION'";; + esac + done +} + +function noerror() { +# lancer la commande "$@" et masquer son code de retour + [ $# -gt 0 ] || set : + "$@" || return 0 +} + +function noout() { +# lancer la commande "$@" en supprimant sa sortie standard + [ $# -gt 0 ] || return 0 + "$@" >/dev/null +} + +function noerr() { +# lancer la commande "$@" en supprimant sa sortie d'erreur + [ $# -gt 0 ] || return 0 + "$@" 2>/dev/null +} + +function isatty() { +# tester si STDOUT n'est pas une redirection + tty -s <&1 +} + +function in_isatty() { +# tester si STDIN n'est pas une redirection + tty -s +} + +function out_isatty() { +# tester si STDOUT n'est pas une redirection. identique à isatty() + tty -s <&1 +} + +function err_isatty() { +# tester si STDERR n'est pas une redirection + tty -s <&2 +} + +################################################################################ +# affichage + +function tooenc() { +# Transformer la valeur $1 de l'encoding $2(=$NUCORE_OENC) vers l'encoding de sortie +# $3=($NUCORE_OUTPUT_ENCODING) + local src="$1" from="${2:-$NUCORE_OENC}" to="${3:-$NUCORE_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + recho "$src" + else + iconv -f "$from" -t "$to" <<<"$src" + fi +} + +function uecho() { + tooenc "$*" +} + +function tooenc_() { +# Transformer la valeur $1 de l'encoding $2(=$NUCORE_OENC) vers l'encoding de sortie +# $3=($NUCORE_OUTPUT_ENCODING) + local src="$1" from="${2:-$NUCORE_OENC}" to="${3:-$NUCORE_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + recho_ "$src" + else + recho_ "$src" | iconv -f "$from" -t "$to" + fi +} + +function uecho_() { + tooenc_ "$*" +} + +function stooenc() { ### XXX +# Transformer la valeur lue sur stdin de $NUCORE_OENC vers l'encoding de sortie par +# défaut ($NUCORE_OUTPUT_ENCODING) + local from="${1:-$NUCORE_OENC}" to="${2:-$NUCORE_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + cat + else + iconv -f "$from" -t "$to" + fi +} + +# faut-il dater les messages de etitle, estep, ebegin? +# Faire NUCORE_EDATE=1 en début de script pour activer cette fonctionnalité +export NUCORE_EDATE +function __edate() { [ -n "$NUCORE_EDATE" ] && date +"[%d/%m/%Y-%H:%M:%S] "; } + +export NUCORE_ELOG_OVERWRITE +function __set_no_colors() { :; } +function elogto() { +# Activer NUCORE_EDATE et rediriger STDOUT et STDERR vers le fichier $1 +# Si deux fichiers sont spécifiés, rediriger STDOUT vers $1 et STDERR vers $2 +# Si aucun fichier n'est spécifié, ne pas faire de redirection +# Si la redirection est activée, forcer l'utilisation de l'encoding UTF8 +# Si NUCORE_ELOG_OVERWRITE=1, alors le fichier en sortie est écrasé. Sinon, les +# lignes en sortie lui sont ajoutées + NUCORE_EDATE=1 + if [ -n "$1" -a -n "$2" ]; then + LANG=fr_FR.UTF8 + NUCORE_OUTPUT_ENCODING="$NUCORE__UTF8" + __set_no_colors 1 + if [ -n "$NUCORE_ELOG_OVERWRITE" ]; then + exec >"$1" 2>"$2" + else + exec >>"$1" 2>>"$2" + fi + elif [ -n "$1" ]; then + LANG=fr_FR.UTF8 + NUCORE_OUTPUT_ENCODING="$NUCORE__UTF8" + __set_no_colors 1 + if [ -n "$NUCORE_ELOG_OVERWRITE" ]; then + exec >"$1" 2>&1 + else + exec >>"$1" 2>&1 + fi + fi +} + +# variables utilisées pour l'affichage indenté des messages et des titres +# __estack est la liste des invocations de 'ebegin' et 'etitle' en cours +# __tlevel est l'indentation à appliquer avant d'afficher le message +export __estack __tlevel +function __indent() { +# indenter les lignes de $1, sauf la première + if [ "${1/ +/}" != "$1" ]; then + sed "2,\$s/^/${__tlevel}/g" <<<"$1" + else + recho "$1" + fi +} +# fonctions à surcharger pour modifier la façon dont les messages sont affichés +function __eerror() { tooenc "$(__edate)${__tlevel}ERROR $(__indent "$1")"; } +function __ewarn() { tooenc "$(__edate)${__tlevel}WARNING $(__indent "$1")"; } +function __enote() { tooenc "$(__edate)${__tlevel}NOTE $(__indent "$1")"; } +function __ebanner() { + local maxi="${COLUMNS:-80}" + local -a lines + local psfix line + + psfix="$(__edate)${__tlevel}" + while [ ${#psfix} -lt $maxi ]; do psfix="$psfix="; done + + tooenc "$psfix" + maxi=$(($maxi - 1)) + array_xsplitl lines "$1" + for line in "" "${lines[@]}" ""; do + line="$(__edate)${__tlevel}= $line" + if [ ${#line} -le $maxi ]; then + while [ ${#line} -lt $maxi ]; do line="$line "; done + line="$line=" + fi + tooenc "$line" + done + tooenc "$psfix" +} +function __eimportant() { tooenc "$(__edate)${__tlevel}IMPORTANT $(__indent "$1")"; } +function __eattention() { tooenc "$(__edate)${__tlevel}ATTENTION $(__indent "$1")"; } +function __einfo() { tooenc "$(__edate)${__tlevel}INFO $(__indent "$1")"; } +function __eecho() { tooenc "$(__edate)${__tlevel}$(__indent "$1")"; } +function __eecho_() { tooenc_ "$(__edate)${__tlevel}$(__indent "$1")"; } +function __edebug() { tooenc "$(__edate)${__tlevel}DEBUG $(__indent "$1")"; } +function __estep() { tooenc "$(__edate)${__tlevel}. $(__indent "$1")"; } +function __estepe() { tooenc "$(__edate)${__tlevel}.E $(__indent "$1")"; } +function __estepw() { tooenc "$(__edate)${__tlevel}.W $(__indent "$1")"; } +function __estepn() { tooenc "$(__edate)${__tlevel}.N $(__indent "$1")"; } +function __estepi() { tooenc "$(__edate)${__tlevel}.I $(__indent "$1")"; } +function __estep_() { tooenc_ "$(__edate)${__tlevel}. $(__indent "$1")"; } +function __estepe_() { tooenc_ "$(__edate)${__tlevel}.E $(__indent "$1")"; } +function __estepw_() { tooenc_ "$(__edate)${__tlevel}.W $(__indent "$1")"; } +function __estepn_() { tooenc_ "$(__edate)${__tlevel}.N $(__indent "$1")"; } +function __estepi_() { tooenc_ "$(__edate)${__tlevel}.I $(__indent "$1")"; } +function __etitle() { tooenc "$(__edate)${__tlevel}=== $(__indent "$1")"; } +function __ebegin() { tooenc_ "$(__edate)${__tlevel}. $(__indent "$1"): "; } +function __edoto() { echo_ "."; } +function __edotw() { echo_ "w"; } +function __edotx() { echo_ "x"; } +function __edotp() { echo_ "+"; } +function __edotd() { tooenc "($1)"; } +function __eendo() { echo "[ok]"; } +function __eendx() { echo "[error]"; } +PRETTYOPTS=() +function set_verbosity() { :;} +function set_interaction() { :;} +function show_error() { +# tester respectivement si on doit afficher les messages d'erreur, +# d'avertissement, d'information, de debug + return 0 +} +function show_warn() { + return 0 +} +function show_info() { + return 0 +} +function show_verbose() { + return 0 +} +function show_debug() { + [ -n "$DEBUG" ] +} +function check_verbosity() { + return 0 +} +function get_verbosity_option() { :;} +function check_interaction() { + return 0 +} + +# note: toutes les fonctions d'affichage e* écrivent sur stderr +__epending= +function eflush() { +# Afficher les messages en attente + if [ -n "$__epending" ]; then recho "$__epending" 1>&2; __epending=; fi +} +function eclearp() { +# Supprimer les message en attente + __epending= +} +function eerror() { +# Afficher un message d'erreur + show_error || return; eflush; __eerror "$*" 1>&2 +} + +function die() { + [ $# -gt 0 ] && eerror "$@" + exit 1 +} + +function exit_with { + if [ $# -gt 0 ]; then "$@"; fi + exit $? +} + +function die_with { + [ $# -gt 0 ] && eerror "$1" + shift + [ $# -gt 0 ] && "$@" + exit 1 +} + +function die_unless() { + # Afficher $1 et quitter le script avec die() si la commande $2..@ retourne FAUX + local du__r + local du__msg="$1"; shift + if [ $# -eq 0 ]; then + [ -n "$du__msg" ] && _eerror "$du__msg" + exit 1 + elif "$@"; then + : + else + du__r=$? + [ -n "$du__msg" ] && _eerror "$du__msg" + exit $du__r + fi + return 0 +} + +function eerror_unless() { + # Afficher $1 avec eerror() si la commande $2..@ retourne FAUX. dans tous les cas, retourner le code de retour de la commande. + local eu__r + local eu__msg="$1"; shift + if [ $# -eq 0 ]; then + [ -n "$eu__msg" ] && _eerror "$eu__msg" + return 1 + elif "$@"; then + : + else + eu__r=$? + [ -n "$eu__msg" ] && _eerror "$eu__msg" + return $eu__r + fi + return 0 +} + +function die_if() { + # Afficher $1 et quitter le script avec die() si la commande $2..@ retourne VRAI. sinon, retourner le code de retour de la commande + local di__r=0 + local di__msg="$1"; shift + [ $# -eq 0 ] && return 0 + if "$@"; then + [ -n "$di__msg" ] && _eerror "$di__msg" + exit 0 + else + di__r=$? + fi + return $di__r +} + +function eerror_if() { + # Afficher $1 avec eerror() si la commande $2..@ retourne VRAI. dans tous les cas, retourner le code de retour de la commande. + local ei__r=0 + local ei__msg="$1"; shift + [ $# -eq 0 ] && return 0 + if "$@"; then + [ -n "$ei__msg" ] && _eerror "$ei__msg" + else + ei__r=$? + fi + return $ei__r +} + +function ewarn() { +# Afficher un message d'avertissement + show_warn || return; eflush; __ewarn "$*" 1>&2 +} +function enote() { +# Afficher un message d'information de même niveau qu'un avertissement + show_info || return; eflush; __enote "$*" 1>&2 +} +function ebanner() { +# Afficher un message très important encadré, puis attendre 5 secondes + show_error || return; eflush; __ebanner "$*" 1>&2; sleep 5 +} +function eimportant() { +# Afficher un message très important + show_error || return; eflush; __eimportant "$*" 1>&2 +} +function eattention() { +# Afficher un message important + show_warn || return; eflush; __eattention "$*" 1>&2 +} +function einfo() { +# Afficher un message d'information + show_info || return; eflush; __einfo "$*" 1>&2 +} +function eecho() { +# Afficher un message d'information sans préfixe + show_info || return; eflush; __eecho "$*" 1>&2 +} +function eecho_() { + show_info || return; eflush; __eecho_ "$*" 1>&2 +} +function edebug() { +# Afficher un message de debug + show_debug || return; eflush; __edebug "$*" 1>&2 +} +function trace() { +# Afficher la commande $1..@, la lancer, puis afficher son code d'erreur si une +# erreur se produit + local r cmd="$(qvals "$@")" + show_info && { eflush; __eecho "\$ $cmd" 1>&2; } + "$@"; r=$? + if [ $r -ne 0 ]; then + if show_info; then + eflush; __eecho "^ [EC #$r]" 1>&2 + elif show_error; then + eflush; __eecho "^ $cmd [EC #$r]" 1>&2; + fi + fi + return $r +} +function trace_error() { +# Lancer la commande $1..@, puis afficher son code d'erreur si une erreur se +# produit. La différence avec trace() est que la commande n'est affichée que si +# une erreur se produit. + local r + "$@"; r=$? + if [ $r -ne 0 ]; then + local cmd="$(qvals "$@")" + show_error && { eflush; __eecho "^ $cmd [EC #$r]" 1>&2; } + fi + return $r +} + +function etitle() { +# Afficher le titre $1, qui est le début éventuel d'une section. Les section +# imbriquées sont affichées indentées. La section n'est pas terminée, et il faut +# la terminer explicitement avec eend, sauf dans certains cas précis: +# - Si $2..$* est spécifié, c'est une commande. Lancer la commande dans le +# contexte de la section. Puis, la section est automatiquement terminée sauf si +# l'option -s est spécifiée, auquel cas la section reste ouverte. Si l'option -p +# est spécifiée, eclearp() est appelé pour purger les messages en attente +# - Dans le cas contraire, l'option -s est ignorée: la section doit toujours +# être terminée explicitement. +# La fonction etitled() est comme etitle(), mais le titre n'est pas affiché +# immédiatement. L'affichage effectif est effectué dès qu'une fonction e* est +# utilisée. Ceci permet, avec la fonction eclearp(), de ne pas afficher de titre +# pour une section vide. + local __t_deferred= + __t_etitle "$@" +} +function etitled() { + local __t_deferred=1 + __t_etitle "$@" +} +function __t_etitle() { + local __t_eend=default + local __t_clearp= + while [ -n "$1" ]; do + if [ "$1" == "--" ]; then + shift + break + elif [ "$1" == "-s" ]; then + __t_eend= + shift + elif [ "$1" == "--eend" ]; then + __t_eend=1 + shift + elif [ "$1" == "-p" ]; then + __t_clearp=1 + shift + else + break + fi + done + local __t_title="$1"; shift + local __t_s=0 + # etitle + [ -n "$__estack" ] && __tlevel="${__tlevel} " + __estack="$__estack:t" + if show_info; then + if [ -n "$__t_deferred" ]; then + __epending="${__epending:+$__epending +}$(__etitle "$__t_title")" + else + eflush + __etitle "$__t_title" 1>&2 + fi + fi + # commande + if [ $# -gt 0 ]; then + "$@" + __t_s=$? + [ "$__t_eend" == "default" ] && __t_eend=1 + fi + # eend + [ "$__t_eend" == "default" ] && __t_eend= + if [ -n "$__t_eend" ]; then + eend $__t_s + [ -n "$__t_clearp" ] && eclearp + fi + return $__t_s +} +function estep() { +# Afficher la description d'une opération. Cette fonction est particulièrement +# appropriée dans le contexte d'un etitle. +# Les variantes e (error), w (warning), n (note), i (info) permettent d'afficher +# des couleurs différentes, mais toutes sont du niveau info. + show_info || return; eflush; __estep "$*" 1>&2 +} +function estepe() { + show_info || return; eflush; __estepe "$*" 1>&2 +} +function estepw() { + show_info || return; eflush; __estepw "$*" 1>&2 +} +function estepn() { + show_info || return; eflush; __estepn "$*" 1>&2 +} +function estepi() { + show_info || return; eflush; __estepi "$*" 1>&2 +} +function estep_() { + show_info || return; eflush; __estep_ "$*" 1>&2 +} +function estepe_() { + show_info || return; eflush; __estepe_ "$*" 1>&2 +} +function estepw_() { + show_info || return; eflush; __estepw_ "$*" 1>&2 +} +function estepn_() { + show_info || return; eflush; __estepn_ "$*" 1>&2 +} +function estepi_() { + show_info || return; eflush; __estepi_ "$*" 1>&2 +} +function ebegin() { +# Afficher le message $1, qui décrit le début d'une opération. Cette fonction +# débute une section, qu'il faut terminer avec eend. +# Si $2..$* est spécifié, c'est une commande. Lancer la commande dans le +# contexte de la section. Puis, la section est terminée automatiquement, sauf si +# l'option -s est spécifiée, auquel cas la section reste ouverte. + local __b_eend=default + while [ -n "$1" ]; do + if [ "$1" == "--" ]; then + shift + break + elif [ "$1" == "-s" ]; then + __b_eend= + shift + elif [ "$1" == "--eend" ]; then + __b_eend=1 + shift + else + break + fi + done + local __b_msg="$1"; shift + local __b_s=0 + # ebegin + __estack="$__estack:b" + if show_info; then + eflush + __ebegin "$__b_msg" 1>&2 + fi + # commande + if [ $# -gt 0 ]; then + "$@" + __b_s=$? + [ "$__b_eend" == "default" ] && __b_eend=1 + fi + # eend + [ "$__b_eend" == "default" ] && __b_eend= + [ -n "$__b_eend" ] && eend $__b_s + return $__b_s +} +function edot() { +# Afficher une étape d'une opération, matérialisée par un point '.' ou une +# croix 'x' en cas de succès ou d'erreur. Cette fonction est particulièrement +# appropriée dans le contexte d'un ebegin. + local s=$? + show_info || return + eflush + [ -n "$1" ] && s="$1" + shift + if [ "$s" == "0" ]; then + __edoto 1>&2 + else + __edotx 1>&2 + fi + show_verbose && [ $# -gt 0 ] && __edotd "$*" 1>&2 + return $s +} +function edotw() { +# Afficher un avertissement comme étape d'une opération, matérialisée par une +# lettre 'w' (typiquement de couleur jaune). Cette fonction est particulièrement +# appropriée dans le contexte d'un ebegin. + local s=$? + show_info || return + eflush + [ -n "$1" ] && s="$1" + shift + __edotw 1>&2 + show_verbose && [ $# -gt 0 ] && __edotd "$*" 1>&2 + return $s +} +function ewait() { +# Afficher les étapes d'une opération qui dure, matérialisées par des '+' toutes +# les secondes tant que le processus $1 tourne. +# A utiliser de cette manière: +# ebegin "msg" +# cmd & +# ewait $! +# eend + [ -n "$1" ] || return 1 + if show_info; then + local count=2 + eflush + little_sleep # certains processus retournent tout de suite + while is_running "$1"; do + sleep 1 + if [ $count -gt 0 ]; then + # attendre 2 secondes avant de commencer à afficher des '+' + count=$(($count - 1)) + else + __edotp 1>&2 + fi + done + # terminer par un '.' + __edoto 1>&2 + else + # ne rien afficher, mais attendre quand même la fin de l'opération + wait "$1" + fi +} +function eend() { +# Terminer une section. +# Avec l'option -c, remettre à zéro toutes les informations de section +# Si la section en cours est un ebegin, afficher la fin de l'opération: [ok] ou +# [error] en fonction du code de retour de la dernière commande (ou de $1 si +# cette valeur est donnée) +# Si la section en cours est un etitle, marquer la fin de la section concernée +# par le titre. + local s=$? + if [ "$1" == "-c" ]; then + __estack= + __tlevel= + elif [ "${__estack%:b}" != "$__estack" ]; then + # terminer ebegin + __estack="${__estack%:b}" + show_info || return + eflush + [ -n "$1" ] && s="$1" + if [ "$s" == "0" ]; then + __eendo 1>&2 + else + __eendx 1>&2 + fi + elif [ "${__estack%:t}" != "$__estack" ]; then + # terminer etitle -s + __estack="${__estack%:t}" + __tlevel="${__tlevel% }" + fi +} +function __elinedots() { + ebegin "$1" + local line + if show_debug; then + while read line; do + __edoto 1>&2 + __edotd "$line" 1>&2 + done + else + while read line; do + __edoto 1>&2 + done + fi + eend +} +function elinedots() { +# Afficher un message comme avec ebegin "$1", puis afficher un point '.' pour +# chaque ligne lue sur stdin. Cela permet de suivre une opération. En mode +# DEBUG, afficher la ligne affichée plutôt qu'un point. +# Si $2..$* sont spécifiés, lancer la commande et suivre sa sortie. Ainsi, +# 'elinedots msg cmd args' est un raccourci pour 'cmd args | elinedots msg' + local msg="$1"; shift + if [ $# -gt 0 ]; then + "$@" | __elinedots "$msg" + else + __elinedots "$msg" + fi +} + +################################################################################ +# saisie + +function toienc() { +# Transformer la valeur de la variable $1 de l'encoding d'entrée +# $3(=$NUCORE_INPUT_ENCODING) vers l'encoding $2(=$NUCORE_IENC) + local __tie_var="$1" __tie_to="${2:-$NUCORE_IENC}" __tie_from="${3:-$NUCORE_INPUT_ENCODING}" + if [ "$__tie_from" != "$__tie_to" ]; then + _setv "$__tie_var" "$(iconv -f "$__tie_from" -t "$__tie_to" <<<"${!__tie_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 l'encoding d'entrée par défaut + [ $# -gt 0 ] || set -- REPLY + local __r_var + read "$@" + for __r_var in "$@"; do + [ -z "$__r_var" -o "${__r_var:0:1}" == "-" ] && continue # ignorer les options + toienc "$__r_var" + done +} + +function stoienc() { ### XXX +# Transformer la valeur lue sur stdin de $NUCORE_IENC vers l'encoding d'entrée par +# défaut ($NUCORE_INPUT_ENCODING) + local to="${1:-$NUCORE_IENC}" from="${2:-$NUCORE_INPUT_ENCODING}" + if [ "$from" == "$to" ]; then + cat + else + iconv -f "$from" -t "$to" + fi +} + + + +function is_interaction() { + return 1 +} + +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 + eflush + local message="$1" + local prompt="[oN]" + local r + is_yes "$default" && prompt="[On]" + if [ -n "$message" ]; then + __eecho_ "$message" 1>&2 + else + NUCORE_OENC="$NUCORE__UTF8" __eecho_ "Voulez-vous continuer?" 1>&2 + fi + NUCORE_OENC="$NUCORE__UTF8" tooenc_ " $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 + eflush + 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 __rv_readline=1 __rv_showdef=1 __rv_nl= + __rv_opts=() + [ -n "$NUCORE_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 + NUCORE_OENC="$NUCORE__UTF8" eerror "La valeur par défaut de $__rv_v doit être non vide" + return 1 + fi + _setv "$__rv_v" "$__rv_d" + return 0 + fi + + eflush + local __rv_r + while true; do + if [ -n "$__rv_msg" ]; then + __eecho_ "$__rv_msg" 1>&2 + else + NUCORE_OENC="$NUCORE__UTF8" __eecho_ "Entrez la valeur" 1>&2 + fi + if [ -n "$__rv_readline" ]; then + NUCORE_OENC="$NUCORE__UTF8" tooenc_ ": " 1>&2 + uread -e ${__rv_d:+-i"$__rv_d"} "${__rv_opts[@]}" __rv_r + else + if [ -n "$__rv_d" ]; then + if [ -n "$__rv_showdef" ]; then + tooenc_ " [$__rv_d]" 1>&2 + else + tooenc_ " [****]" 1>&2 + fi + fi + NUCORE_OENC="$NUCORE__UTF8" tooenc_ ": " 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 + 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) + local __sm_title= __sm_yourchoice= __sm_default= + local -a __sm_args + parse_opts -t: __sm_title= -m: __sm_yourchoice= -d: __sm_default= @ __sm_args -- "$@" && + set -- "${__sm_args[@]}" || ewarn "$__sm_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 + NUCORE_OENC="$NUCORE__UTF8" eerror "Le tableau $__sm_options_var doit être non vide" + return 1 + fi + [ -z "$__sm_default" ] && __sm_default="${!__sm_option_var}" + + eflush + 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 + NUCORE_OENC="$NUCORE__UTF8" __eecho_ "Entrez le numéro de l'option choisie" 1>&2 + fi + NUCORE_OENC="$NUCORE__UTF8" tooenc_ ": " 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 + NUCORE_OENC="$NUCORE__UTF8" eerror "Numéro d'option incorrect" + fi + else + NUCORE_OENC="$NUCORE__UTF8" 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 + NUCORE_OENC="$NUCORE__UTF8" tooenc "" 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) + 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= + local -a __am_args + parse_opts \ + -t: __am_title= \ + -m: __am_optyc= \ + -M: __am_actyc= \ + -e: __am_void_actions \ + -d: __am_default_action= \ + -q: __am_quit_action= \ + -o: __am_default_option= \ + @ __am_args -- "$@" && set -- "${__am_args[@]}" || { eerror "$__am_args"; return 1; } + + __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() { + eflush + local c=0 choice + while true; do + if [ $c -eq 0 ]; then + [ -n "$title" ] && __etitle "$title" 1>&2 + __eecho_ "=== Actions disponibles: " 1>&2 + tooenc "$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 + tooenc_ ": " 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 + tooenc "" 1>&2 + c=0 + fi + done + __am_select_action="$select_action" + __am_select_option= +} + +function __options_actions_menu() { + eflush + 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 + tooenc "$i*- $option" 1>&2 + else + tooenc "$i - $option" 1>&2 + fi + let i=$i+1 + done + __estepn_ "Actions disponibles: " 1>&2 + tooenc "$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 + tooenc_ ": " 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 + tooenc "" 1>&2 + c=0 + fi + done + __am_select_action="$select_action" + __am_select_option="$select_option" +} diff --git a/bash/base.path.sh b/bash/base.path.sh new file mode 100644 index 0000000..37f312c --- /dev/null +++ b/bash/base.path.sh @@ -0,0 +1,158 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: base.path "Fonctions de base: gestion des chemins et des fichiers" +require: base.core + +function: in_path "tester l'existence d'un programme dans le PATH" +function in_path() { + [ -n "$1" -a -x "$(which "$1" 2>/dev/null)" ] +} + +function: delpath "supprimer le chemin \$1 de \$2(=PATH)" +function delpath() { + local _qdir="${1//\//\\/}" + eval "export ${2:-PATH}; ${2:-PATH}"'="${'"${2:-PATH}"'#$1:}"; '"${2:-PATH}"'="${'"${2:-PATH}"'%:$1}"; '"${2:-PATH}"'="${'"${2:-PATH}"'//:$_qdir:/:}"; [ "$'"${2:-PATH}"'" == "$1" ] && '"${2:-PATH}"'=' +} + +function: addpath "Ajouter le chemin \$1 à la fin, dans \$2(=PATH), s'il n'y existe pas déjà" +function addpath() { + local _qdir="${1//\//\\/}" + eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="${'"${2:-PATH}"':+$'"${2:-PATH}"':}$1"' +} + +function: inspathm "Ajouter le chemin \$1 au début, dans \$2(=PATH), s'il n'y existe pas déjà" +function inspathm() { + local _qdir="${1//\//\\/}" + eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="$1${'"${2:-PATH}"':+:$'"${2:-PATH}"'}"' +} + +function: inspath "S'assurer que le chemin \$1 est au début de \$2(=PATH)" +function inspath() { + delpath "$@" + inspathm "$@" +} + +function: push_cwd "enregistrer le répertoire courant dans la variable \$2(=cwd) et se placer dans le répertoire \$1" +function push_cwd() { + eval "${2:-cwd}"'="$(pwd)"' + cd "$1" +} +function: pop_cwd "se placer dans le répertoire \${!\$2}(=\$cwd) puis retourner le code d'erreur \$1(=0)" +function pop_cwd() { + eval 'cd "$'"${2:-cwd}"'"' + return "${1:-0}" +} + +################################################################################ +## fichiers temporaires + +function: mktempf "générer un fichier temporaire et retourner son nom" +function mktempf() { + mktemp "${1:-"$TMPDIR/tmp.XXXXXX"}" +} + +function: mktempd "générer un répertoire temporaire et retourner son nom" +function mktempd() { + mktemp -d "${1:-"$TMPDIR/tmp.XXXXXX"}" +} + +function ac__forgetall() { NUCORE__AC_FILES=(); } +ac__forgetall +function ac__trap() { + local file + for file in "${NUCORE__AC_FILES[@]}"; do + [ -e "$file" ] && rm -rf "$file" 2>/dev/null + done + ac__forgetall +} +trap ac__trap 1 3 15 EXIT + +function: autoclean "\ +Ajouter les fichiers spécifiés à la liste des fichiers à supprimer à la fin du +programme" +function autoclean() { + local file + for file in "$@"; do + [ -n "$file" ] && NUCORE__AC_FILES=("${NUCORE__AC_FILES[@]}" "$file") + done +} + +function: ac_cleanall "\ +Supprimer *tous* les fichiers temporaires gérés par autoclean tout de suite." +function ac_cleanall() { + ac__trap +} + +function: ac_clean "\ +Supprimer les fichier temporaires \$1..@ si et seulement s'ils ont été générés +par ac_set_tmpfile() ou ac_set_tmpdir()" +function ac_clean() { + local file acfile found + local -a acfiles + for acfile in "${NUCORE__AC_FILES[@]}"; do + found= + for file in "$@"; do + if [ "$file" == "$acfile" ]; then + found=1 + [ -e "$file" ] && rm -rf "$file" 2>/dev/null + break + fi + done + [ -z "$found" ] && acfiles=("${acfiles[@]}" "$acfile") + done + NUCORE__AC_FILES=("${acfiles[@]}") +} + +function: ac_set_tmpfile "\ +Créer un fichier temporaire avec le motif \$2, l'ajouter à la liste des +fichiers à supprimer en fin de programme, et mettre sa valeur dans la +variable \$1 + +En mode debug, si (\$5 est vide ou \${!5} est une valeur vraie), et si \$3 n'est +pas vide, prendre ce fichier au lieu de générer un nouveau fichier temporaire. +Si \$4==keep, ne pas écraser le fichier \$3 s'il existe." +function ac_set_tmpfile() { + local se__d + if is_debug; then + if [ -n "$5" ]; then + is_yes "${!5}" && se__d=1 + else + se__d=1 + fi + fi + if [ -n "$se__d" -a -n "$3" ]; then + _setv "$1" "$3" + [ -f "$3" -a "$4" == keep ] || >"$3" + else + local se__t="$(mktempf "$2")" + autoclean "$se__t" + _setv "$1" "$se__t" + fi +} + +function: ac_set_tmpdir "\ +Créer un répertoire temporaire avec le motif \$2, l'ajouter à la liste des +fichiers à supprimer en fin de programme, et mettre sa valeur dans la +variable \$1 + +En mode debug, si (\$4 est vide ou \${!4} est une valeur vraie), et si \$3 n'est +pas vide, prendre ce nom de répertoire au lieu de créer un nouveau répertoire +temporaire" +function ac_set_tmpdir() { + local sr__d + if is_debug; then + if [ -n "$4" ]; then + is_yes "${!4}" && sr__d=1 + else + sr__d=1 + fi + fi + if [ -n "$sr__d" -a -n "$3" ]; then + _setv "$1" "$3" + mkdir -p "$3" + else + local sr__t="$(mktempd "$2")" + autoclean "$sr__t" + _setv "$1" "$sr__t" + fi +} diff --git a/bash/base.sh b/bash/base.sh new file mode 100644 index 0000000..0981a11 --- /dev/null +++ b/bash/base.sh @@ -0,0 +1,19 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +# shim pour les fonctions de nucore.sh au cas où ce module n'est pas chargée +if [ -z "$NUCOREDIR" -o "$NUCOREDIR" != "$NUCOREINIT" ]; then + function module:() { :; } + function function:() { :; } + function require:() { :; } +fi +##@include base.init.sh +##@include base.core.sh +##@include base.str.sh +##@include base.arr.sh +##@include base.io.sh +##@include base.eval.sh +##@include base.split.sh +##@include base.path.sh +##@include base.args.sh +module: base "Chargement de tous les modules base.*" +require: base.init base.core base.str base.arr base.io base.eval base.split base.path base.args diff --git a/bash/base.split.sh b/bash/base.split.sh new file mode 100644 index 0000000..dd79d25 --- /dev/null +++ b/bash/base.split.sh @@ -0,0 +1,189 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: base.split "Fonctions de base: analyse et découpage de valeurs" +require: base.arr + +function: splitfsep "\ +Découper \$1 de la forme first[SEPsecond] entre first, qui est placé dans la +variable \$3(=first) et second, qui est placée dans la variable \$4(=second). \$2 +est la valeur SEP. Le découpage est faite sur la *première* occurence de SEP." +function splitfsep() { + if [[ "$1" == *"$2"* ]]; then + setv "${3:-first}" "${1%%$2*}" + setv "${4:-second}" "${1#*$2}" + else + setv "${3:-first}" "$1" + setv "${4:-second}" + fi +} + +function: splitfsep2 "\ +Découper \$1 de la forme [firstSEP]second entre first, qui est placé dans la +variable \$3(=first) et second, qui est placée dans la variable \$4(=second). \$2 +est la valeur SEP. Le découpage est faite sur la *première* occurence de SEP." +function splitfsep2() { + if [[ "$1" == *"$2"* ]]; then + setv "${3:-first}" "${1%%$2*}" + setv "${4:-second}" "${1#*$2}" + else + setv "${3:-first}" + setv "${4:-second}" "$1" + fi +} + +function: splitlsep "\ +Découper \$1 de la forme first[SEPsecond] entre first, qui est placé dans la +variable \$3(=first) et second, qui est placée dans la variable \$4(=second). \$2 +est la valeur SEP. Le découpage est faite sur la *dernière* occurence de SEP." +function splitlsep() { + if [[ "$1" == *"$2"* ]]; then + setv "${3:-first}" "${1%$2*}" + setv "${4:-second}" "${1##*$2}" + else + setv "${3:-first}" "$1" + setv "${4:-second}" + fi +} + +function: splitlsep2 "\ +Découper \$1 de la forme [firstSEP]second entre first, qui est placé dans la +variable \$3(=first) et second, qui est placée dans la variable \$4(=second). \$2 +est la valeur SEP. Le découpage est faite sur la *dernière* occurence de SEP." +function splitlsep2() { + if [[ "$1" == *"$2"* ]]; then + setv "${3:-first}" "${1%$2*}" + setv "${4:-second}" "${1##*$2}" + else + setv "${3:-first}" + setv "${4:-second}" "$1" + fi +} + +function: splitvar "\ +Découper \$1 de la forme name[=value] entre le nom, qui est placé dans la +variable \$2(=name) et la valeur, qui est placée dans la variable \$3(=value)" +function splitvar() { + splitfsep "$1" = "${2:-name}" "${3:-value}" +} + +function: splitpath "\ +Découper \$1 de la forme [dir/]name entre le répertoire, qui est placé dans la +variable \$2(=dir), et le nom du fichier, qui est placé dans la variable +\$3(=name)" +function splitpath() { + splitlsep2 "$1" / "${2:-dir}" "${3:-name}" +} + +function: splitname "\ +Découper \$1 de la forme basename[.ext] entre le nom de base du fichier, qui +est placé dans la variable \$2(=basename) et l'extension, qui est placée dans +la variable \$3(=ext) + +Attention, si \$1 est un chemin, le résultat risque d'être faussé. Par exemple, +'splitname a.b/c' ne donne pas le résultat escompté." +function splitname() { + splitlsep "$1" . "${2:-basename}" "${3:-ext}" +} + +function: splithost "\ +Découper \$1 de la forme hostname[.domain] entre le nom d'hôte, qui est placé +dans la variable \$2(=hostname) et le domaine, qui est placée dans la variable +\$3(=domain)" +function splithost() { + splitfsep "$1" . "${2:-hostname}" "${3:-domain}" +} + +function: splituserhost "\ +Découper \$1 de la forme [user@]host entre le nom de l'utilisateur, qui est placé +dans la variable \$2(=user) et le nom d'hôte, qui est placée dans la variable +\$3(=host)" +function splituserhost() { + splitfsep2 "$1" @ "${2:-user}" "${3:-host}" +} + +function: splitpair "\ +Découper \$1 de la forme first[:second] entre la première valeur, qui est placé +dans la variable \$2(=src) et la deuxième valeur, qui est placée dans la variable +\$3(=dest)" +function splitpair() { + splitfsep "$1" : "${2:-src}" "${3:-dest}" +} + +function: splitproxy "\ +Découper \$1 de la forme http://[user:password@]host[:port]/ entre les valeurs +\$2(=host), \$3(=port), \$4(=user), \$5(=password) + +S'il n'est pas spécifié, port vaut 3128 par défaut" +function splitproxy() { + local sy__tmp sy__host sy__port sy__creds sy__user sy__password + + sy__tmp="${1#http://}" + if [[ "$sy__tmp" == *@* ]]; then + sy__creds="${sy__tmp%%@*}" + sy__tmp="${sy__tmp#${sy__creds}@}" + splitpair "$sy__creds" sy__user sy__password + fi + sy__tmp="${sy__tmp%%/*}" + splitpair "$sy__tmp" sy__host sy__port + [ -n "$sy__port" ] || sy__port=3128 + + setv "${2:-host}" "$sy__host" + setv "${3:-port}" "$sy__port" + setv "${4:-user}" "$sy__user" + setv "${5:-password}" "$sy__password" +} + +function: spliturl "\ +Découper \$1 de la forme scheme://[user:password@]host[:port]/path entre les +valeurs \$2(=scheme), \$3(=user), \$4(=password), \$5(=host), \$6(=port), \$7(=path) + +S'il n'est pas spécifié, port vaut 80 pour http, 443 pour https, 21 pour ftp" +function spliturl() { + local sl__tmp sl__scheme sl__creds sl__user sl__password sl__host sl__port sl__path + + sl__scheme="${1%%:*}" + sl__tmp="${1#${sl__scheme}://}" + if [[ "$sl__tmp" == */* ]]; then + sl__path="${sl__tmp#*/}" + sl__tmp="${sl__tmp%%/*}" + fi + if [[ "$sl__tmp" == *@* ]]; then + sl__creds="${sl__tmp%%@*}" + sl__tmp="${sl__tmp#${sl__creds}@}" + splitpair "$sl__creds" sl__user sl__password + fi + splitpair "$sl__tmp" sl__host sl__port + if [ -z "$sl__port" ]; then + [ "$sl__scheme" == "http" ] && sl__port=80 + [ "$sl__scheme" == "https" ] && sl__port=443 + [ "$sl__scheme" == "ftp" ] && sl__port=21 + fi + + setv "${2:-scheme}" "$sl__scheme" + setv "${3:-user}" "$sl__user" + setv "${4:-password}" "$sl__password" + setv "${5:-host}" "$sl__host" + setv "${6:-port}" "$sl__port" + setv "${7:-path}" "$sl__path" +} + +function: splitwcs "\ +Découper un nom de chemin \$1 entre la partie sans wildcards, qui est placée dans +la variables \$2(=basedir), et la partie avec wildcards, qui est placée dans la +variable \$3(=filespec)" +function splitwcs() { + local ss__p="$1" + local ss__dd="${2:-basedir}" ss__df="${3:-filespec}" ss__part ss__d ss__f + local -a ss__parts + array_split ss__parts "$ss__p" "/" + for ss__part in "${ss__parts[@]}"; do + if [[ "$ss__part" == *\** ]] || [[ "$ss__part" == *\?* ]] || [ -n "$ss__f" ]; then + ss__f="${ss__f:+$ss__f/}$ss__part" + else + ss__d="${ss__d:+$ss__d/}$ss__part" + fi + done + [ "${ss__p#/}" != "$ss__p" ] && ss__d="/$ss__d" + _setv "$ss__dd" "$ss__d" + _setv "$ss__df" "$ss__f" +} diff --git a/bash/base.str.sh b/bash/base.str.sh new file mode 100644 index 0000000..e83e714 --- /dev/null +++ b/bash/base.str.sh @@ -0,0 +1,140 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: base.str "Fonctions de base: gestion des valeurs chaines" + +function: strmid "Afficher la plage \$1 de la valeur \$2..* + +La plage peut être d'une des formes 'start', '[start]:length'. Si start est +négatif, le compte est effectué à partir de la fin de la chaine. Si length est +négatif, il est rajouté à la longueur de la chaine à partir de start" +function strmid() { + local range="$1"; shift + local str="$*" + if [[ "$range" == *:-* ]]; then + local max=${#str} + [ $max -eq 0 ] && return + local start="${range%%:*}" + [ -n "$start" ] || start=0 + while [ "$start" -lt 0 ]; do + start=$(($max$start)) + done + max=$(($max-$start)) + local length="${range#*:}" + while [ "$length" -lt 0 ]; do + length=$(($max$length)) + done + range="$start:$length" + fi + eval 'echo "${str:'" $range"'}"' +} + +function: strrepl "Remplacer dans la valeur \$3..* le motif \$1 par la chaine \$2 + +\$1 peut commencer par l'un des caractères /, #, % pour indiquer le type de recherche" +function strrepl() { + local pattern="$1"; shift + local repl="$1"; shift + local str="$*" + local cmd='echo "${str/' + if [ "${pattern#/}" != "$pattern" ]; then + pattern="${pattern#/}" + cmd="$cmd/" + elif [ "${pattern#\#}" != "$pattern" ]; then + pattern="${pattern#\#}" + cmd="$cmd#" + elif [ "${pattern#%}" != "$pattern" ]; then + pattern="${pattern#%}" + cmd="$cmd%" + fi + cmd="$cmd"'$pattern/$repl}"' + eval "$cmd" +} + +function: strlcomp "transformer dans le flux en entrée en UTF-8 certains caractères en leur équivalent transformable en latin1. + +si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée." +function strlcomp() { + if [ $# -gt 0 ]; then strlcomp <<<"$*" + else LANG=fr_FR.UTF-8 sed $' +s/[\xE2\x80\x90\xE2\x80\x91\xE2\x80\x92\xE2\x80\x93\xE2\x80\x94\xE2\x80\x95]/-/g +s/[‘’]/\x27/g +s/[«»“”]/"/g +s/[\xC2\xA0\xE2\x80\x87\xE2\x80\xAF\xE2\x81\xA0]/ /g +s/[\xE2\x80\xA6]/.../g +s/[œ]/oe/g +s/[Œ]/OE/g +s/[æ]/ae/g +s/[Æ]/AE/g +s/a\xCC\x80/à/g +s/e\xCC\x81/é/g; s/e\xCC\x80/è/g; s/e\xCC\x82/ê/g; s/e\xCC\x88/ë/g +s/i\xCC\x88/ï/g; s/i\xCC\x82/î/g +s/o\xCC\x82/ô/g; s/o\xCC\x88/ö/g +s/u\xCC\x88/ü/g; s/u\xCC\x82/û/g +s/c\xCC\xA7/ç/g +s/A\xCC\x80/À/g +s/E\xCC\x81/É/g; s/E\xCC\x80/È/g; s/E\xCC\x82/Ê/g; s/E\xCC\x88/Ë/g +s/I\xCC\x88/Ï/g; s/I\xCC\x82/Î/g +s/O\xCC\x82/Ô/g; s/O\xCC\x88/Ö/g +s/U\xCC\x88/Ü/g; s/U\xCC\x82/Û/g +s/C\xCC\xA7/Ç/g +' + fi +} + +function: strnacc "supprimer les accents dans le flux en entrée en UTF-8 + +si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée." +function strnacc() { + if [ $# -gt 0 ]; then strnacc <<<"$*" + else LANG=fr_FR.UTF-8 sed ' +s/[à]/a/g +s/[éèêë]/e/g +s/[ïî]/i/g +s/[ôö]/o/g +s/[üû]/u/g +s/[ç]/c/g +s/[À]/A/g +s/[ÉÈÊË]/E/g +s/[ÏÎ]/I/g +s/[ÔÖ]/O/g +s/[ÜÛ]/U/g +s/[Ç]/C/g +' + fi +} + +function: stripnl "Supprimer dans le flux en entrée les caractères de fin de ligne + +si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée." +function stripnl() { + if [ $# -gt 0 ]; then stripnl <<<"$*" + else tr -d '\r\n' + fi +} + +function: nl2lf "transformer dans le flux en entrée les fins de ligne en LF + +si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée." +function nl2lf() { + if [ $# -gt 0 ]; then nl2lf <<<"$*" + else lawk 'BEGIN {RS="\r|\r\n|\n"} {print}' + fi +} + +function: nl2crlf "transformer dans le flux en entrée les fins de ligne en CRLF + +si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée." +function nl2crlf() { + if [ $# -gt 0 ]; then nl2crlf <<<"$*" + else lawk 'BEGIN {RS="\r|\r\n|\n"} {print $0 "\r"}' + fi +} + +function: nl2cr "transformer dans le flux en entrée les fins de ligne en CR + +si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée." +function nl2cr() { + if [ $# -gt 0 ]; then nl2cr <<<"$*" + else lawk 'BEGIN {RS="\r|\r\n|\n"; ORS=""} {print $0 "\r"}' + fi +} diff --git a/bash/git.sh b/bash/git.sh new file mode 100644 index 0000000..1cee345 --- /dev/null +++ b/bash/git.sh @@ -0,0 +1,704 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +##@require nucore.sh +##@require base +module: git "Fonctions pour faciliter l'utilisation de git" +require: nucore base + +function: git_geturl "" +function git_geturl() { + git config --get remote.origin.url +} + +function: git_have_annex "" +function git_have_annex() { + [ -n "$(git config --get annex.uuid)" ] +} + +NUCORE_GIT_FUNCTIONS=( + git_check_gitvcs git_ensure_gitvcs + git_list_branches git_list_rbranches + git_have_branch git_have_rbranch + git_get_branch git_is_branch + git_have_remote git_track_branch + git_check_cleancheckout git_ensure_cleancheckout + git_is_ancestor git_should_ff git_should_push + git_is_merged +) +NUCORE_GIT_FUNCTIONS_MAP=( + cg:git_check_gitvcs eg:git_ensure_gitvcs + lbs:git_list_branches rbs:git_list_rbranches + hlb:git_have_branch hrb:git_have_rbranch + gb:git_get_branch ib:git_is_branch + hr:git_have_remote tb:git_track_branch + cc:git_check_cleancheckout ec:git_ensure_cleancheckout + ia:git_is_ancestor sff:git_should_ff spu:git_should_push + im:git_is_merged +) + +function: git_check_gitvcs "" +function git_check_gitvcs() { + git rev-parse --show-toplevel >&/dev/null +} + +function: git_ensure_gitvcs "" +function git_ensure_gitvcs() { + git_check_gitvcs || edie "Ce n'est pas un dépôt git" || return +} + +function: git_list_branches "" +function git_list_branches() { + git for-each-ref refs/heads/ --format='%(refname:short)' | csort +} + +function: git_list_rbranches "" +function git_list_rbranches() { + git for-each-ref "refs/remotes/${1:-origin}/" --format='%(refname:short)' | csort +} + +function: git_list_pbranches "lister les branches locales et celles qui existent dans l'origine \$1(=origin) et qui pourraient devenir une branche locale avec la commande git checkout -b" +function git_list_pbranches() { + local prefix="${1:-origin}/" + { + git for-each-ref refs/heads/ --format='%(refname:short)' + git for-each-ref "refs/remotes/$prefix" --format='%(refname:short)' | grep -F "$prefix" | cut -c $((${#prefix} + 1))- + } | grep -vF HEAD | csort -u +} + +function: git_have_branch "" +function git_have_branch() { + git_list_branches | grep -qF "$1" +} + +function: git_have_rbranch "" +function git_have_rbranch() { + git_list_rbranches "${2:-origin}" | grep -qF "$1" +} + +function: git_get_branch "" +function git_get_branch() { + git rev-parse --abbrev-ref HEAD 2>/dev/null +} + +function: git_get_branch_remote "" +function git_get_branch_remote() { + local branch="$1" + [ -n "$branch" ] || branch="$(git_get_branch)" + [ -n "$branch" ] || return + git config --get "branch.$branch.remote" +} + +function: git_get_branch_merge "" +function git_get_branch_merge() { + local branch="$1" + [ -n "$branch" ] || branch="$(git_get_branch)" + [ -n "$branch" ] || return + git config --get "branch.$branch.merge" +} + +function: git_get_branch_rbranch "" +function git_get_branch_rbranch() { + local branch="$1" remote="$2" merge + [ -n "$branch" ] || branch="$(git_get_branch)" + [ -n "$branch" ] || return + [ -n "$remote" ] || remote="$(git_get_branch_remote "$branch")" + [ -n "$remote" ] || return + merge="$(git_get_branch_merge "$branch")" + [ -n "$merge" ] || return + echo "refs/remotes/$remote/${merge#refs/heads/}" +} + +function: git_is_branch "" +function git_is_branch() { + [ "$(git_get_branch)" == "${1:-master}" ] +} + +function: git_have_remote "" +function git_have_remote() { + [ -n "$(git config --get remote.${1:-origin}.url)" ] +} + +function: git_track_branch "" +function git_track_branch() { + local branch="$1" origin="${2:-origin}" + [ -n "$branch" ] || return + git_have_remote "$origin" || return + [ "$(git config --get branch.$branch.remote)" == "$origin" ] && return + if git_have_rbranch "$branch" "$origin"; then + if git_have_branch "$branch"; then + git branch -u "$origin/$branch" "$branch" + else + git branch -t "$branch" "$origin/$branch" + fi + elif git_have_branch "$branch"; then + git push -u "$origin" "$branch" || return + fi +} + +function: git_ensure_branch " +@return 0 si la branche a été créée, 1 si elle existait déjà, 2 en cas d'erreur" +function git_ensure_branch() { + local branch="$1" source="${2:-master}" origin="${3:-origin}" + [ -n "$branch" ] || return 2 + git_have_branch "$branch" && return 1 + if git_have_rbranch "$branch" "$origin"; then + # une branche du même nom existe dans l'origine. faire une copie de cette branche + git branch -t "$branch" "$origin/$branch" || return 2 + else + # créer une nouvelle branche du nom spécifié + git_have_branch "$source" || return 2 + git branch "$branch" "$source" || return 2 + if [ -z "$NUCORE_GIT_OFFLINE" ]; then + git_have_remote "$origin" && git_track_branch "$branch" "$origin" + fi + fi + return 0 +} + +function: git_check_cleancheckout "vérifier qu'il n'y a pas de modification locales dans le dépôt correspondant au répertoire courant." +function git_check_cleancheckout() { + [ -z "$(git status --porcelain 2>/dev/null)" ] +} + +function: git_ensure_cleancheckout "" +function git_ensure_cleancheckout() { + git_check_cleancheckout || + edie "Vous avez des modifications locales. Enregistrez ces modifications avant de continuer" || return +} + +function git__init_ff() { + o="${3:-origin}" + b="$1" s="${2:-refs/remotes/$o/$1}" + b="$(git rev-parse --verify --quiet "$b")" || return 1 + s="$(git rev-parse --verify --quiet "$s")" || return 1 + return 0 +} +function git__can_ff() { + [ "$1" == "$(git merge-base "$1" "$2")" ] +} + +function: git_is_ancestor "vérifier que la branche \$1 est un ancêtre direct de la branche \$2, qui vaut par défaut refs/remotes/\${3:-origin}/\$1 +note: cette fonction retourne vrai si \$1 et \$2 identifient le même commit" +function git_is_ancestor() { + local o b s; git__init_ff "$@" || return + git__can_ff "$b" "$s" +} + +function: git_should_ff "vérifier si la branche \$1 devrait être fast-forwardée à partir de la branche d'origine \$2, qui vaut par défaut refs/remotes/\${3:-origin}/\$1 +note: cette fonction est similaire à git_is_ancestor(), mais retourne false si \$1 et \$2 identifient le même commit" +function git_should_ff() { + local o b s; git__init_ff "$@" || return + [ "$b" != "$s" ] || return 1 + git__can_ff "$b" "$s" +} + +function: git_should_push "vérifier si la branche \$1 devrait être poussée vers la branche de même nom dans l'origine \$2(=origin), parce que l'origin peut-être fast-forwardée à partir de cette branche." +function git_should_push() { + git_should_ff "refs/remotes/${2:-origin}/$1" "$1" +} + +function: git_fast_forward "vérifier que la branche courante est bien \$1, puis tester s'il faut la fast-forwarder à partir de la branche d'origine \$2, puis le faire si c'est nécessaire. la branche d'origine \$2 vaut par défaut refs/remotes/origin/\$1" +function git_fast_forward() { + local o b s; git__init_ff "$@" || return + [ "$b" != "$s" ] || return 1 + local head="$(git rev-parse HEAD)" + [ "$head" == "$b" ] || return 1 + git__can_ff "$b" "$s" || return 1 + git merge --ff-only "$s" +} + +function: git_is_merged "vérifier que les branches \$1 et \$2 ont un ancêtre commun, et que la branche \$1 a été complètement fusionnée dans la branche destination \$2" +function git_is_merged() { + local b="$1" d="$2" + b="$(git rev-parse --verify --quiet "$b")" || return 1 + d="$(git rev-parse --verify --quiet "$d")" || return 1 + [ -n "$(git merge-base "$b" "$d")" ] || return 1 + [ -z "$(git rev-list "$d..$b")" ] +} + +################################################################################ +# git annex + +NUCORE_GIT_SSH_WRAPPER= +function: git_annex_use_ssh_wrapper "" +function git_annex_use_ssh_wrapper() { + [ -n "$NUCORE_GIT_SSH_WRAPPER" ] && return + NUCORE_GIT_FORCE_PATH="$PATH" + NUCORE_GIT_FORCE_SSH="${GIT_SSH:-ssh}" + export NUCORE_GIT_FORCE_PATH NUCORE_GIT_FORCE_SSH + base_delpath "$NUCOREDIR/ssh-wrapper" NUCORE_GIT_FORCE_PATH + base_inspath "$NUCOREDIR/ssh-wrapper" PATH + NUCORE_GIT_SSH_WRAPPER=1 +} + +function: git_annex_initial "sur le dépôt \$1 fraichement cloné, vérifier s'il faut faire git annex init. Si oui, l'initialiser avec le nom d'hôte, et récupérer tous les fichiers annexés +@return 1 si une erreur s'est produite" +function git_annex_initial() { + local repodir="${1:-.}" + [ -d "$repodir" ] || return 1 + repodir="$(abspath "$repodir")" + + local GIT_DIR GIT_WORK_TREE + [ "$(cd "$repodir"; git rev-parse --is-bare-repository)" == false ] || return 0 + [ -n "$(GIT_DIR="$repodir/.git" git config --get annex.uuid)" ] && return 0 + + # ici, on sait que git annex n'a pas encore été configuré + # vérifier s'il existe des fichiers annexés + local -a links + base_array_splitl links "$(find "$repodir" -type l)" + local link hasannex= + for link in "${links[@]}"; do + link="$(readlink "$link")" + if [ "${link#.git/annex/}" != "$link" ]; then + hasannex=1 + break + elif [[ "$link" == */.git/annex/* ]]; then + hasannex=1 + break + fi + done + + if [ -n "$hasannex" ]; then + base_in_path git-annex || edie "Vous devez installer git-annex" || return + local cwd; base_push_cwd "$repodir" && + git annex init "$MYHOSTNAME" && + git annex get && + git annex sync && + base_pop_cwd || base_pop_cwd 1 || return + fi +} + +################################################################################ +# Outils de haut niveau + +function: git_commit "" +function git_commit() { + local all=auto allnew push=auto nopush args + setyesval nopush "$NUCORE_GIT_OFFLINE" + [ -n "$nopush" ] && push= + parse_opts + "${PRETTYOPTS[@]}" \ + -a,--all all=1 \ + -A,--all-new allnew=1 \ + -c,--cached all= \ + -p,--push push=1 \ + -l,--local push= \ + @ args -- "$@" && set -- "${args[@]}" || { + eerror "$args" + return 1 + } + + if [ -n "$allnew" ]; then + git add -A + all= + fi + + local message="$1"; shift + local -a cmd + cmd=(git commit) + [ -n "$message" ] && cmd=("${cmd[@]}" -m "$message") + if [ "$all" == "auto" ]; then + # Si des fichiers sont spécifiés, prendre ceux-là. + if [ -z "$*" ]; then + # Sinon, s'il y a des fichiers dans l'index, commiter uniquement ces + # fichiers + # Sinon, committer tous les fichiers modifiés + # le code suivant retourne vrai si l'index contient au moins fichier + git status --porcelain 2>/dev/null | lawk ' + BEGIN { ec = 1 } + substr($0, 1, 1) ~ /[^ ?]/ { ec = 0; exit } + END { exit ec }' || + cmd=("${cmd[@]}" -a) + fi + else + [ -n "$all" ] && cmd=("${cmd[@]}" -a) + fi + + if ! "${cmd[@]}" "$@"; then + [ "$push" == auto ] && return 1 + fi + if [ "$push" == auto ]; then + git_push --auto || return + elif [ -n "$push" ]; then + git_push --force || return + fi + return 0 +} + +function: git_update "" +function git_update() { + local args autoff=1 + parse_opts + "${PRETTYOPTS[@]}" \ + -n,--no-autoff autoff= \ + @ args -- "$@" && set -- "${args[@]}" || { + eerror "$args" + return 1 + } + + if [ -z "$autoff" ]; then + git pull "$@" + return $? + fi + + local branch orig_branch restore_branch remote rbranch pbranch + local -a branches prbranches crbranches dbranches + + base_array_splitl prbranches "$(git_list_rbranches)" + git fetch -p "$@" || return + base_array_splitl crbranches "$(git_list_rbranches)" + + # vérifier s'il n'y a pas des branches distantes qui ont été supprimées + for branch in "${prbranches[@]}"; do + if ! base_array_contains crbranches "$branch"; then + base_array_add dbranches "${branch#*/}" + fi + done + if [ ${#dbranches[*]} -gt 0 ]; then + eimportant "One or more distant branches where deleted" + for branch in "${dbranches[@]}"; do + if git_have_branch "$branch"; then + if ! ask_yesno "Do you want to delete local branch $branch?" X; then + base_array_del dbranches "$branch" + fi + fi + done + fi + if [ ${#dbranches[*]} -gt 0 ]; then + base_array_splitl branches "$(git_list_branches)" + branch="$(git_get_branch)" + if base_array_contains dbranches "$branch"; then + # si la branche courante est l'une des branches à supprimer, il faut + # basculer vers develop ou master + local swto + if [ -z "$swto" ] && base_array_contains branches develop && ! base_array_contains dbranches develop; then + swto=develop + fi + if [ -z "$swto" ] && base_array_contains branches master && ! base_array_contains dbranches master; then + swto=master + fi + if ! git_check_cleancheckout; then + echo "* There are uncommitted local changes. However current branch is slated for removal. +Make your verifications then delete the local branches: + ${swto:+$(qvals git checkout "$swto") + }$(qvals git branch -D "${dbranches[@]}")" + return 1 + fi + if [ -n "$swto" ]; then + git checkout -q "$swto" + else + echo "* Current branch is slated for removal but I don't know to which branch I should switch first. +Make your choice then delete the local branches: + $(qvals git branch -D "${dbranches[@]}")" + return 1 + fi + fi + for branch in "${dbranches[@]}"; do + git branch -D "$branch" + done + fi + + # intégrer les modifications dans les branches locales + if ! git_check_cleancheckout; then + branch="$(git_get_branch)" + remote="$(git_get_branch_remote "$branch")" + rbranch="$(git_get_branch_rbranch "$branch" "$remote")" + pbranch="${rbranch#refs/remotes/}" + if git merge -q --ff-only "$rbranch"; then + echo "* There are uncommitted local changes: only CURRENT branch were updated" + fi + return 0 + fi + + orig_branch="$(git_get_branch)" + base_array_splitl branches "$(git_list_branches)" + for branch in "${branches[@]}"; do + remote="$(git_get_branch_remote "$branch")" + rbranch="$(git_get_branch_rbranch "$branch" "$remote")" + pbranch="${rbranch#refs/remotes/}" + [ -n "$remote" -a -n "$rbranch" ] || continue + if git_is_ancestor "$branch" "$rbranch"; then + if git_should_ff "$branch" "$rbranch"; then + echo "* Fast-forwarding $branch -> $pbranch" + git checkout -q "$branch" + git merge -q --ff-only "$rbranch" + restore_branch=1 + fi + else + if [ "$branch" == "$orig_branch" ]; then + echo "* Cannot fast-forward CURRENT branch $branch from $pbranch +Try to merge manually with: git merge $pbranch" + else + echo "* Cannot fast-forward local branch $branch from $pbranch +You can merge manually with: git checkout $branch; git merge $pbranch" + fi + fi + done + [ -n "$restore_branch" ] && git checkout -q "$orig_branch" + return 0 +} + +function: git_push "" +function git_push() { + local all all_branches all_tags auto force args no_annex + parse_opts + "${PRETTYOPTS[@]}" \ + -a,--all all=1 \ + -b,--branches,--all-branches all_branches=1 \ + -t,--tags,--all-tags all_tags=1 \ + --auto auto=1 \ + -f,--force force=1 \ + -n,--no-annex no_annex=1 \ + @ args -- "$@" && set -- "${args[@]}" || { + eerror "$args" + return 1 + } + + if [ -n "$all" ]; then + # On a demandé à pusher toutes les branches et tous les tags + local r + git push --all "$@"; r=$? + if [ $r -eq 0 ]; then + git push --tags "$@"; r=$? + fi + return $r + elif [ -n "$all_branches" ]; then + # On a demandé à pusher toutes les branches + git push --all "$@" + return $? + elif [ -n "$all_tags" ]; then + # On a demandé à pusher tous les tags + git push --tags "$@" + return $? + elif [ $# -gt 0 ]; then + # Sinon, si des arguments sont spécifiés, les passer à git sans + # modification + git push "$@" + return $? + elif git_have_annex; then + # Si une annexe existe dans le dépôt, demander à git-annex de faire la + # synchronisation, sauf si --no-annex est spécifié ou si on est en mode + # automatique + if [ -z "$no_annex" -a -z "$auto" ]; then + git annex sync + return $? + fi + fi + + # sinon on push vers origin. vérifier la présence du remote + [ -n "$(git config --get remote.origin.url)" ] || { + if [ -n "$auto" ]; then + # en mode automatique, ignorer l'absence de remote + return 0 + else + eerror "Aucun remote origin n'est défini" + return 1 + fi + } + + # puis calculer la branche à pusher + local branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" + local origin="$(git config --get "branch.$branch.remote")" + if [ -n "$branch" -a "$origin" == origin ]; then + if [ -n "$auto" ]; then + # en mode automatique, ne pousser que la branche courante + git push "$origin" "$branch" || return + else + # utiliser la configuration par défaut, qui est sous debian squeeze + # de pousser toutes les branches + git push || return + fi + elif [ -n "$force" ]; then + # utiliser la configuration par défaut, qui est sous debian squeeze de + # pousser toutes les branches + git push || return + fi + return 0 +} + +function git__pclone() { + estep "$1 --> $(ppath "$2")" + mkdirof "$2" || return 1 + git clone "$1" "$2" || return 1 + if [ -z "$3" ]; then + ( + cd "$2" + if git_have_rbranch develop; then + git checkout develop || exit 1 + fi + ) || return 1 + fi + git_annex_initial "$2" || return 1 +} +function git__gitolite_info() { + local mode="$1" urlbase="$2" pattern="$3" + case "$mode" in + http) curl -fs "$urlbase/info${pattern:+"?$pattern"}";; + ssh) ssh -q "$urlbase" info ${pattern:+"$pattern"} 2>/dev/null;; + esac +} +function git__filter_repos() { + lawk -v prefix="$1" ' +NR <= 2 { next } +{ + # filtrer les projets qui ne sont pas encore créés + if (substr($0, 5, 2) == " C") next + repo = substr($0, 6) + # filtrer les projets de type wildcard + if (repo ~ /[\[\]\*]/) next + # enlever le prefixe + if (prefix != "" && substr(repo, 1, length(prefix)) != prefix) next + print repo +}' +} + +function: git_clone "" +function git_clone() { + no_clone= + update= + nodevelop= + recursive= + parse_opts "${PRETTYOPTS[@]}" \ + -n,--no-clone no_clone=1 \ + -u,--update update=1 \ + -m,--master nodevelop=1 \ + -r,--recursive recursive=1 \ + @ args -- "$@" && set -- "${args[@]}" || edie "$args" || return + + if [ -n "$recursive" ]; then + repobase="$1" + [ -n "$repobase" ] || edie "Vous devez spécifier l'url de base des dépôts à cloner" || return + if [ "${repobase#http://}" != "$repobase" -o "${repobase#https://}" != "$repobase" ]; then + # accès par http + mode=http + splitfsep "$repobase" :// scheme hostuserpath + splitfsep "$hostuserpath" / host userpath + splitfsep "$userpath" / user basepath + [ -n "$host" -a -n "$user" ] || edie "Vous devez spécifier l'hôte e.g http://host/git/basepath" || return + urlbase="$scheme://$host/$user" + else + # accès par ssh + mode=ssh + splitfsep "$repobase" : userhost basepath + splituserhost "$userhost" user host + [ -n "$user" ] || user=git + [ -n "$host" ] || edie "Vous devez spécifier l'hôte" || return + urlbase="$user@$host" + fi + basepath="${basepath%/}" + destbase="${2:-.}" + + git_annex_use_ssh_wrapper + prefix="${basepath:+$basepath/}" + base_array_splitl repos "$(set -o pipefail; git__gitolite_info "$mode" "$urlbase" "$prefix" | git__filter_repos "$prefix")" || edie || return + for repo in "${repos[@]}"; do + case "$mode" in + http) repourl="$urlbase/$repo";; + ssh) repourl="$urlbase:$repo";; + esac + setx destdir=abspath "$destbase/${repo#$prefix}" + if [ -d "$destdir" ]; then + if [ -n "$update" ]; then + ( + ${no_clone:+qvals} cd "$destdir" + ${no_clone:+qvals} git pull + ) || edie || return + else + estepe "$(ppath2 "$destdir"): répertoire existant" + fi + elif [ -n "$no_clone" ]; then + qvals git clone "$repourl" "$destdir" + else + git__pclone "$repourl" "$destdir" "$nodevelop" || edie || return + fi + done + + else + repourl="${1%.git}" + [ -n "$repourl" ] || edie "Vous devez spécifier l'url du dépôt git" || return + + destdir="$2" + if [ -z "$destdir" ]; then + splitfsep "$repourl" : userhost path + setx destdir=basename -- "$path" + destdir="${destdir%.git}" + fi + setx destdir=abspath "$destdir" + + git_annex_use_ssh_wrapper + if [ -d "$destdir" ]; then + if [ -n "$update" ]; then + ( + ${no_clone:+qvals} cd "$destdir" + ${no_clone:+qvals} git pull + ) || edie || return + else + estepe "$(ppath2 "$destdir"): répertoire existant" + fi + elif [ -n "$no_clone" ]; then + qvals git clone "$repourl" "$destdir" + else + git__pclone "$repourl" "$destdir" "$nodevelop" || edie || return + fi + fi +} + +function: git_crone "" +function git_crone() { + repourl="${1%.git}" + [ -n "$repourl" ] || edie "Vous devez spécifier l'url du dépôt git" || return + if [ "${repourl#http://}" != "$repourl" -o "${repourl#https://}" != "$repourl" ]; then + # accès par http + mode=http + splitfsep "$repourl" :// scheme hostuserpath + splitfsep "$hostuserpath" / host userpath + splitfsep "$userpath" / user path + [ -n "$host" -a -n "$user" ] || edie "Vous devez spécifier l'hôte e.g http://host/git/repo" || return + hostuser="$scheme://$host/$user" + else + # accès par ssh + mode=ssh + splitfsep "$repourl" : userhost path + splituserhost "$userhost" user host + [ -n "$user" ] || user=git + [ -n "$host" ] || edie "Vous devez spécifier l'hôte" || return + userhost="$user@$host" + fi + [ -n "$path" ] || edie "Vous devez spécifier le chemin du dépôt git" || return + + destdir="$2" + if [ -z "$destdir" ]; then + setx destdir=basename -- "$path" + destdir="${destdir%.git}" + fi + tmpdestdir= + if [ -d "$destdir" ]; then + [ -d "$destdir/.git" ] && edie "$(ppath2 "$destdir"): un dépôt existe déjà" || return + ac_set_tmpdir tmpdestdir + fi + + if [ "$mode" == http ]; then + setx result=curl -fs "$hostuser/create?$path" || edie || return + echo "$result" + [[ "$result" == FATAL:* ]] && edie || return + if [ -n "$tmpdestdir" ]; then + setxx destname=abspath "$destdir" // basename + git clone "$hostuser/$path" "$tmpdestdir/$destname" || edie || return + mv "$tmpdestdir/$destname/.git" "$destdir" || edie || return + ac_clean "$tmpdestdir" + else + git clone "$hostuser/$path" "$destdir" || edie || return + fi + elif [ "$mode" == ssh ]; then + git_annex_use_ssh_wrapper + ssh "$userhost" create "$path" || edie || return + if [ -n "$tmpdestdir" ]; then + setxx destname=abspath "$destdir" // basename + git clone "$userhost:$path" "$tmpdestdir/$destname" || edie || return + mv "$tmpdestdir/$destname/.git" "$destdir" || edie || return + ac_clean "$tmpdestdir" + else + git clone "$userhost:$path" "$destdir" || edie || return + fi + else + edie "$mode: mode non supporté" || return + fi + git_annex_initial "$destdir" || edie || return +} diff --git a/bash/nucore.sh b/bash/nucore.sh new file mode 120000 index 0000000..b22bb26 --- /dev/null +++ b/bash/nucore.sh @@ -0,0 +1 @@ +../load.sh \ No newline at end of file diff --git a/bash/pretty.sh b/bash/pretty.sh new file mode 100644 index 0000000..256731d --- /dev/null +++ b/bash/pretty.sh @@ -0,0 +1,4 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: pretty "Affichage en couleur" +require: base diff --git a/bash/sysinfos.sh b/bash/sysinfos.sh new file mode 100644 index 0000000..341a528 --- /dev/null +++ b/bash/sysinfos.sh @@ -0,0 +1,4 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: sysinfos "Informations sur le système courant" +require: base diff --git a/lib/profile.d/nucore b/lib/profile.d/nucore new file mode 100644 index 0000000..542e828 --- /dev/null +++ b/lib/profile.d/nucore @@ -0,0 +1,2 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +__uaddpath "@@dest@@/bin" PATH diff --git a/lib/uinst/conf b/lib/uinst/conf new file mode 100644 index 0000000..5357b00 --- /dev/null +++ b/lib/uinst/conf @@ -0,0 +1,9 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +source "$@" || exit 1 +source "$ULIBDIR/ulib" || exit 1 +urequire DEFAULTS +cd "$scriptdir/../.." + +# supprimer les fichiers de VCS +rm -rf .git diff --git a/lib/uinst/rootconf b/lib/uinst/rootconf new file mode 100644 index 0000000..0375bfc --- /dev/null +++ b/lib/uinst/rootconf @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +source "$@" || exit 1 +source "$ULIBDIR/ulib" || exit 1 +urequire DEFAULTS +cd "$scriptdir/../.." + +sed "s|@@""dest""@@|$dest|g" load.sh >/etc/nucore.sh + +exit 0 diff --git a/load.sh b/load.sh new file mode 100644 index 0000000..0d68741 --- /dev/null +++ b/load.sh @@ -0,0 +1,159 @@ +##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +## Charger nucore et rendre disponible les modules bash, awk, php et python +##@cooked nocomments +# Ce fichier doit être sourcé en premier. Si ce fichier n'est pas sourcé, alors +# le répertoire nucore doit être disponible dans le répertoire du script qui +# inclue ce fichier. +# Une fois ce fichier sourcé, les autres modules peuvent être importés avec +# require:() e.g. +# source /etc/nucore.sh || exit 1 +# require: other_modules +# ou pour une copie locale de nucore: +# source "$(dirname "$0")/nucore/load.sh" || exit 1 +# require: other_modules + +# vérifier version minimum de bash +if [ "x$BASH" = "x" ]; then + echo "ERROR: nucore: this script requires bash" + exit 1 +fi + +function eerror() { echo "ERROR: $*" 1>&2; } +function die() { [ $# -gt 0 ] && eerror "$*"; exit 1; } +function edie() { [ $# -gt 0 ] && eerror "$*"; return 1; } +function delpath() { local _qdir="${1//\//\\/}"; eval "export ${2:-PATH}; ${2:-PATH}"'="${'"${2:-PATH}"'#$1:}"; '"${2:-PATH}"'="${'"${2:-PATH}"'%:$1}"; '"${2:-PATH}"'="${'"${2:-PATH}"'//:$_qdir:/:}"; [ "$'"${2:-PATH}"'" == "$1" ] && '"${2:-PATH}"'='; } +function addpath() { local _qdir="${1//\//\\/}"; eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="${'"${2:-PATH}"':+$'"${2:-PATH}"':}$1"'; } +function inspathm() { local _qdir="${1//\//\\/}"; eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="$1${'"${2:-PATH}"':+:$'"${2:-PATH}"'}"'; } +function inspath() { delpath "$@"; inspathm "$@"; } + +if [ ${BASH_VERSINFO[0]} -ge 5 -o \( ${BASH_VERSINFO[0]} -eq 4 -a ${BASH_VERSINFO[1]} -ge 1 \) ]; then : +elif [ -n "$NUCORE_IGNORE_BASH_VERSION" ]; then : +else die "nucore: bash 4.1+ is required" +fi + +# Calculer emplacement de nucore +NUCOREDIR="@@dest@@" +if [ "$NUCOREDIR" = "@@""dest""@@" ]; then + # La valeur "@@"dest"@@" n'est remplacée que dans la copie de ce script + # faite dans /etc. Sinon, il faut toujours faire le calcul. Cela permet de + # déplacer la librairie n'importe où sur le disque, ce qui est + # particulièrement intéressant quand on fait du déploiement. + NUCOREDIR="${BASH_SOURCE[0]}" + if [ -f "$NUCOREDIR" -a "$(basename -- "$NUCOREDIR")" == load.sh ]; then + # Fichier sourcé depuis nucore/ + NUCORE_SOURCED=1 + NUCOREDIR="$(dirname -- "$NUCOREDIR")" + elif [ -f "$NUCOREDIR" -a "$(basename -- "$NUCOREDIR")" == nucore.sh ]; then + # Fichier sourcé depuis nucore/bash + NUCORE_SOURCED=1 + NUCOREDIR="$(dirname -- "$NUCOREDIR")/.." + else + # Fichier non sourcé. Tout exprimer par rapport au script courant + NUCORE_SOURCED= + NUCOREDIR="$(dirname -- "$0")" + if [ -d "$NUCOREDIR/nucore" ]; then + NUCOREDIR="$NUCOREDIR/nucore" + elif [ -d "$NUCOREDIR/lib/nucore" ]; then + NUCOREDIR="$NUCOREDIR/lib/nucore" + fi + fi +elif [ "${BASH_SOURCE[0]}" = /etc/nucore.sh ]; then + # Fichier chargé depuis /etc/nucore.sh + NUCORE_SOURCED=1 +fi +NUCOREDIR="$(cd "$NUCOREDIR" 2>/dev/null; pwd)" +NUCOREDIRS=("$NUCOREDIR/bash") + +# 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 +NUCOREINIT="$NUCOREDIR" + +## Modules bash +NUCORE_LOADED_MODULES=(nucore) +NUCORE_DEFAULT_MODULES=(base pretty sysinfos) + +# Si cette variable est non vide, require: recharge toujours le module, même +# s'il a déjà été chargé. Cette valeur n'est pas transitive: il faut toujours +# recharger explicitement tous les modules désirés +NUCORE_FORCE_RELOAD= + +function nucore__define_functions() { + function nucore_check_loaded() { + local module + for module in "${NUCORE_LOADED_MODULES[@]}"; do + [ "$module" == "$1" ] && return 0 + done + return 1 + } + function module:() { + NUCORE_MODULE="$1" + if ! nucore_check_loaded "$1"; then + NUCORE_LOADED_MODULES+=("$1") + fi + } + function function:() { + : + } +} + +function nucore__require:() { + local nr__module nr__nucoredir nr__found + [ $# -gt 0 ] || set DEFAULTS + + # sauvegarder valeurs globales + local nr__orig_module="$NUCORE_MODULE" + NUCORE_MODULE= + + # garder une copie de la valeur originale et casser la transitivité + local nr__force_reload="$NUCORE_FORCE_RELOAD" + local NUCORE_FORCE_RELOAD + + for nr__module in "$@"; do + nr__found= + for nr__nucoredir in "${NUCOREDIRS[@]}"; do + if [ -f "$nr__nucoredir/$nr__module.sh" ]; then + nr__found=1 + if [ -n "$nr__force_reload" ] || ! nucore_check_loaded "$nr__module"; then + NUCORE_LOADED_MODULES+=("$nr__module") + source "$nr__nucoredir/$nr__module.sh" || die + fi + break + fi + done + if [ -z "$nr__found" -a "$nr__module" == DEFAULTS ]; then + for nr__module in "${NUCORE_DEFAULT_MODULES[@]}"; do + if [ -f "$nr__nucoredir/$nr__module.sh" ]; then + nr__found=1 + if [ -n "$nr__force_reload" ] || ! nucore_check_loaded "$nr__module"; then + NUCORE_LOADED_MODULES+=("$nr__module") + source "$nr__nucoredir/$nr__module.sh" || die + fi + else + break + fi + done + fi + [ -n "$nr__found" ] || die "nucore: unable to find module $nr__module in (${NUCOREDIRS[*]})" + done + + # restaurer valeurs globales + NUCORE_MODULE="$nr__orig_module" +} + +function require:() { + [ -z "$NUCORE_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local NUCORE_REQUIRE_SET_X=1; }; if [ -n "$NUCORE_REQUIRE_SET_X" ]; then [ -n "$NUCORE_REQUIRE_SET_X_RL1" ] || local NUCORE_REQUIRE_SET_X_RL1; local NUCORE_REQUIRE_SET_X_RL2=$RANDOM; [ -n "$NUCORE_REQUIRE_SET_X_RL1" ] || NUCORE_REQUIRE_SET_X_RL1=$NUCORE_REQUIRE_SET_X_RL2; fi # désactiver set -x de manière réentrante + nucore__define_functions + nucore__require: "$@" + [ -n "$NUCORE_REQUIRE_SET_X" -a "$NUCORE_REQUIRE_SET_X_RL1" == "$NUCORE_REQUIRE_SET_X_RL2" ] && set -x + return 0 +} + +## Autres modules +[ -d "$NUCOREDIR/awk" ] && inspath "$NUCOREDIR/awk" AWKPATH; export AWKPATH +[ -d "$NUCOREDIR/python3" ] && inspath "$NUCOREDIR/python3" PYTHONPATH; export PYTHONPATH + +## Auto import DEFAULTS +nucore__define_functions +if [ -n "$NUCORE_SOURCED" -a -z "$NUCORE_NO_IMPORT_DEFAULTS" ]; then + require: DEFAULTS +fi