##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## Fonctions de base: fondement
##@cooked nocomments
uprovide base.core

function echo_() {
# afficher la valeur $* sans passer à la ligne
    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)
    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)
    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
    local s="$*"
    s="${s//\\/\\\\}"
    s="${s//\"/\\\"}"
    s="${s//\$/\\\$}"
    s="${s//\`/\\\`}"
    recho_ "$s"
}
function should_quote() {
# Tester si la chaine $* doit être mise entre quotes
    local s="$*"
    local l="${#s}"
    # pour optimiser, toujours mettre entre quotes une chaine vide ou de plus de 80 caractères
    [ $l -eq 0 -o $l -gt 80 ] && return 0
    # sinon, tester si la chaine contient des caractères spéciaux
    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 "
    echo -n \"
    _qval "$@"
    echo \"
}
function qvalm() {
# Afficher la chaine $* quotée si nécessaire avec "
    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
    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 est séparée par un espace.
    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
    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 '
    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
    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()
    local __s_var="$1"; shift
    eval "$__s_var=\"\$*\""
}
function echo_setv() {
# Afficher la commande qui serait lancée par 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
# Cette fonction est équivalente à  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()
    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
    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()
    local __s_array="$1"; shift
    eval "$__s_array=(\"\$@\")"
}
function echo_seta() {
# Afficher la commande qui serait lancée par 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()
    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() {
# syntaxe 1: setx var cmd
#   initialiser la variable $1 avec le résultat de la commande "$2..@"
#   note: en principe, la syntaxe est 'setx var cmd args...'. cependant, la
#   syntaxe 'setx var=cmd args...' est supportée aussi
# syntaxe 2: setx -a array cmd
#   initialiser le tableau $1 avec le résultat de la commande "$2..@", chaque
#   ligne du résultat étant un élément du tableau
#   note: en principe, la syntaxe est 'setx -a array cmd args...'. cependant, la
#   syntaxe 'setx -a array=cmd args...' est supportée aussi
    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.
    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.
    local __s_array="$1"; shift
    eval "$__s_array=($("$@" | qlines))"
}
function evalx() {
# Implémenter une syntaxe lisible et naturelle permettant d'enchainer des
# traitements sur une valeur. Par exemple, la commande
#     evalx cmd1 [args1...] // cmd2 [args2...] // cmd3 [args3...]
# est équivalente à la commande
#     cmd3 args3 "$(cmd2 args2 "$(cmd1 args1)")"
# Retourner le dernier code d'erreur non nul, ou 0 si toutes les commandes se
# sont exécutées sans erreur.
    local __e_val __e_arg __e_r=0
    local -a __e_cmd

    local __e_first=1
    while [ $# -gt 0 ]; do
        __e_cmd=()
        while [ $# -gt 0 ]; do
            __e_arg="$1"; shift
            [ "$__e_arg" == // ] && break
            if [ "${__e_arg%//}" != "$__e_arg" ]; then
                local __e_tmp="${__e_arg%//}"
                if [ -z "${__e_tmp//\\/}" ]; then
                    __e_arg="${__e_arg#\\}"
                    __e_cmd=("${__e_cmd[@]}" "$__e_arg")
                    continue
                fi
            fi
            __e_cmd=("${__e_cmd[@]}" "$__e_arg")
        done

        if [ -n "$__e_first" ]; then
            __e_val="$("${__e_cmd[@]}")" || __e_r=$?
        else
            __e_val="$("${__e_cmd[@]}" "$__e_val")" || __e_r=$?
        fi
        __e_first=
    done
    [ -n "$__e_val" ] && echo "$__e_val"
    return $__e_r
}
function setxx() {
# équivalent à setx $1 evalx $2..@
    local -a __s_args
    if [ "$1" == -a ]; then __s_args=(-a); shift; fi
    local __s_var="$1"; shift
    if [[ "$__s_var" == *=* ]]; then
        set -- "${__s_var#*=}" "$@"
        __s_var="${__s_var%%=*}"
    fi
    __s_args=("${__s_args[@]}" "$__s_var")
    setx "${__s_args[@]}" evalx "$@"
}
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.
    local __e_arg __e_cmd

    while [ $# -gt 0 ]; do
        __e_arg="$1"; shift
        if [ "$__e_arg" == // ]; then
            __e_cmd="$__e_cmd |"
            continue
        elif [ "${__e_arg%//}" != "$__e_arg" ]; then
            local __e_tmp="${__e_arg%//}"
            if [ -z "${__e_tmp//\\/}" ]; then
                __e_arg="${__e_arg#\\}"
            fi
        fi
        __e_cmd="${__e_cmd:+$__e_cmd }\"$(_qval "$__e_arg")\""
    done
    eval "$__e_cmd"
}
function setxp() {
# équivalent à setx $1 evalp $2..@
    local -a __s_args
    if [ "$1" == -a ]; then __s_args=(-a); shift; fi
    local __s_var="$1"; shift
    if [[ "$__s_var" == *=* ]]; then
        set -- "${__s_var#*=}" "$@"
        __s_var="${__s_var%%=*}"
    fi
    __s_args=("${__s_args[@]}" "$__s_var")
    setx "${__s_args[@]}" evalp "$@"
}
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
    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
    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
    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
    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
    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
    local __t_val1="$1"; shift
    local __t_op="$1"; shift
    local __t_val2="$(evalp "$@")"
    eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]'
}

function err2out() {
# lancer la commande $@ en redirigeant la sortie d'erreur sur la sortie standard
    "$@" 2>&1
}

function is_defined() {
# tester si la variable $1 est définie
    [ -n "$(declare -p "$1" 2>/dev/null)" ]
}
function is_array() {
# tester si la variable $1 est un tableau
    case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac
    return 1
}

# cf http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference
function upvar() {
# Assign variable one scope above the caller
# Usage: local "$1" && upvar $1 "value(s)"
# Param: $1  Variable name to assign value to
# Param: $*  Value(s) to assign.  If multiple values, an array is
#            assigned, otherwise a single value is assigned.
    if unset -v "$1"; then
        if [ $# -eq 2 ]; then
            eval "$1=\"\$2\""
        else
            eval "$1=(\"\${@:2}\")"
        fi
    fi
}
function array_upvar() {
# Comme upvar() mais force la création d'un tableau
    unset -v "$1" && eval "$1=(\"\${@:2}\")"
}
function upvars() {
# Version modifiée 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 varname [varname ...] && upvars [varname value] | [-aN varname [value ...]] ...
# Options:
#   -a
#     assigns remaining values to varname as array
#   -aN
#     assigns next N values to varname as array. Returns 1 if wrong number of
#     options occurs
    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 __ab_process_pending() {
    local -a values
    case "$mode" in
    cmd) values="$("${pending[@]}")";;
    ssplit) eval "values=($("${pending[@]}"))";;
    lsplit) eval "values=($("${pending[@]}" | qlines))";;
    add) values=("${pending[@]}");;
    esac
    cmd=("${cmd[@]}" "${values[@]}")
    pending=()
}
function array_buildcmd() {
# Construire un tableau dont les éléments sont calculés à partir d'une chaine
# dont les éléments sont séparés par des marqueurs ++X, e.g:
#   ++ ou ++c[md] une chaine simple résultat de la commande
#   ++s[split] un ensemble d'arguments résultat du split du résultat de la
#     commande sur espaces
#   ++l[split] un ensemble d'arguments résultat du split du résultat de la
#     commande sur les lignes
#   ++a[dd] un ensemble d'arguments donnés directement
# Exemple:
#   array_buildcmd args echo Copie de ++ ppath "$src" ++a vers ++ ppath "$dest"
    local desta="$1"; shift; local "$desta"
    local mode=add
    local -a pending cmd
    while [ $# -gt 0 ]; do
        case "$1" in
            ++c|++cmd|++) __ab_process_pending; mode=cmd;;
            ++s|++ssplit) __ab_process_pending; mode=ssplit;;
            ++l|++lsplit) __ab_process_pending; mode=lsplit;;
            ++a|++add) __ab_process_pending; mode=add;;
            *) pending=("${pending[@]}" "$1");;
        esac
        shift
    done
    __ab_process_pending
    array_upvar "$desta" "${cmd[@]}"
}
function buildcmd() {
    local -a args
    array_buildcmd args "$@"
    qvals "${args[@]}"
}
function evalcmd() {
    local -a args
    array_buildcmd args "$@"
    "${args[@]}"
}