##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## Fonctions de base: gestion des valeurs chaines scalaires
##@cooked nocomments
# Note: contient du code spécifique à bash 4. Le module base.compat réimplémente
# les fonctions concernées pour les rendre compatible avec bash >= 2.x
uprovide base.string
urequire base.core

# IMPORTANT: aucune des fonctions suivantes 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()

function straddp() {
# ajouter le préfixe $1 à $2..*
    local p="$1"; shift
    echo "$p$*"
}
function strdelp() {
# enlever le préfixe $1 à $2..*
    local p="$1"; shift
    local str="$*"
    echo "${str#$p}"
}
function strdelp2() {
# enlever le préfixe $1 le plus long à $2..*
    local p="$1"; shift
    local str="$*"
    echo "${str##$p}"
}
function stradds() {
# ajouter le suffixe $1 à $2..*
    local s="$1"; shift
    echo "$*$s"
}
function strdels() {
# enlever le suffixe $1 à $2..*
    local s="$1"; shift
    local str="$*"
    echo "${str%$s}"
}
function strdels2() {
# enlever le suffixe le plus long $1 à $2..*
    local s="$1"; shift
    local str="$*"
    echo "${str%%$s}"
}
function strlower() {
# afficher en minuscule la valeur $*
    local str="$*"
    echo "${str,,}"
}
function strlower1() {
# afficher la valeur $* après avoir converti la première lettre en minuscule
    local str="$*"
    echo "${str,}"
}
function strlowers() {
# afficher les valeurs $1..* après avoir converti leur première lettre en
# minuscule
    local str="$*"
    echo "${*,}"
}
function strupper() {
# afficher en majuscule la valeur $*
    local str="$*"
    echo "${str^^}"
}
function strupper1() {
# afficher la valeur $* après avoir converti la première lettre en majuscule
    local str="$*"
    echo "${str^}"
}
function struppers() {
# afficher les valeurs $1..* après avoir converti leur première lettre en
# majuscule
    echo "${*^}"
}
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
    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
    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 strops() {
# Appliquer à une chaine de caractères une suite de traitements, e.g:
# 'strops 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
# 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 'strops var OP' ou 'strop 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()
    local -a __s_tmp
    local __s_value="$1"; shift
    while [ $# -gt 0 ]; do
        case "$1" in
        # l'argument est le nom de la variable
        :-*|:=*|:\?*|:+*) eval '__s_value="${'"${__s_value}$1"'}"';;
        d|deref) __s_value="${!__s_value}";;
        dc|dcount|ds|dsize)
            __s_value="${__s_value}[@]"
            __s_tmp=("${!__s_value}")
            __s_value="${#__s_tmp[@]}"
            ;;
        # l'argument est la valeur de la variable
        \#*|%*|/*|:*|^*|,*) eval '__s_value="${__s_value'"$1"'}"';;
        l|length) __s_value="${#__s_value}";;
        =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge)
            __s_tmp=(\[ "$__s_value" "$@" ]); "${__s_tmp[@]}"; return $?;;
        -n|-z) __s_tmp=(\[ "$1" "$__s_value" ]); "${__s_tmp[@]}"; return $?;;
        +#*) eval '__s_value="'"${1#+#}"'$__s_value"';;
        -#*) eval '__s_value="${__s_value'"${1#-}"'}"';;
        +%*) eval '__s_value="$__s_value"'"${1#+%}";;
        +*) eval '__s_value="$__s_value"'"${1#+}";;
        -%*) eval '__s_value="${__s_value'"${1#-}"'}"';;
        -*) eval '__s_value="${__s_value%'"${1#-}"'}"';;
        mid|strmid) eval '__s_value="$(strmid "$2" "$__s_value")"'; shift;;
        repl|strrepl) eval '__s_value="$(strrepl "$2" "$3" "$__s_value")"'; shift; shift;;
        *) echo 1>&2 "strops: unknown operator: $1";;
        esac
        shift
    done
    echo "$__s_value"
}

function first_char() {
# retourner le premier caractère de la chaine $*
    local str="$*"
    echo "${str:0:1}"
}
function last_char() {
# retourner le dernier caractère de la chaine $*
    local str="$*"
    echo "${str: -1:1}"
}
function first_chars() {
# retourner tous les caractères de la chaine $*, excepté le dernier
    local str="$*"
    recho "${str:0:$((${#1}-1))}"
}
function last_chars() {
# retourner tous les caractères de la chaine $*, excepté le premier
    local str="$*"
    recho "${str:1}"
}
function first_char_is() {
# Tester si le premier caractère de la chaine $1 est $2
    [ "${1:0:1}" == "$2" ]
}
function last_char_is() {
# Tester si le dernier caractère de la chaine $1 est $2
    [ "${1:$((-1)):1}" == "$2" ]
}
function beginswith() {
# Tester si la chaine $1 commence par le wildcard $2
    local str="$1" pattern="$2"
    eval '[ "${str#$pattern}" != "$str" ]'
}
function endswith() {
# Tester si la chaine $1 se termine par le wildcard $2
    local str="$1" pattern="$2"
    eval '[ "${str%$pattern}" != "$str" ]'
}

function strsplitf() {
# 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
    [ $# -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" "$@"
}
function strecho() { recho "$@"; }
function strqvals() {
# Afficher chaque argument à part avec des quotes. A chainer avec strsplitf()
    qvals "$@"
}
function strqlines() {
# Afficher chaque ligne des fichiers spécifiés comme un argument. A chainer avec
# strsplitf()
    local -a lines
    _setax lines cat "$@"
    qvals "${lines[@]}"
}
function strqarray() {
# Afficher chaque valeur des tableaux $@ comme un argument. A chainer avec
# strsplitf()
    local __a __s="qvals"
    for __a in "$@"; do __s="$__s \"\${$__a[@]}\""; done
    eval "$__s"
}

function evals() {
# Enchainer des traitements sur des chaines de caractères, comme pour la fonction
# evalx(). Il y a cependant quelques différences:
# - Seules certains fonctions spécifiques peuvent être utilisées: elles sont
#   reconnaissables à leur préfixe 'str'. En effet, lors de l'utilisation d'une
#   commande par evals(), le préfixe 'str' est systématiquement ajouté.
# - La fonction strsplitf() est traitée de façon particulière pour lancer une
#   commande avec le préfixe 'str'
# Ainsi, la commande suivante:
#     evals cmd1 // splitf cmd2
# est équivalente à la commande:
#     strplitf strcmd2 "$(strcmd1)"
    local __e_val __e_arg __e_r=0
    local __e_firstcmd __e_firstarg __e_splitf
    local -a __e_cmd

    __e_firstcmd=1
    while [ $# -gt 0 ]; do
        __e_cmd=()
        __e_firstarg=1 # premier argument
        __e_splitf= # premier argument après splitf
        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
            if [ -n "$__e_firstarg" ]; then
                __e_cmd=("str$__e_arg")
                __e_firstarg=
                [ "$__e_arg" == "splitf" ] && __e_splitf=1
            elif [ -n "$__e_splitf" ]; then
                __e_cmd=("${__e_cmd[@]}" "str$__e_arg")
                __e_splitf=
            else
                __e_cmd=("${__e_cmd[@]}" "$__e_arg")
            fi
        done

        if [ -n "$__e_firstcmd" ]; then
            __e_val="$("${__e_cmd[@]}")" || __e_r=$?
        else
            __e_val="$("${__e_cmd[@]}" "$__e_val")" || __e_r=$?
        fi
        __e_firstcmd=
    done
    [ -n "$__e_val" ] && echo "$__e_val"
    return $__e_r
}
