# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8

##################################################
# Gestion des fichiers

function filter_conf() {
    # filtrer un fichier de configuration en enlevant les commentaires et les lignes vides
    # $1==-m fusionner les lignes qui se terminent par \
    local merge=cat
    if [ "$1" == -m ]; then
        merge='awk '\''substr($0, length($0)) == "\\" { while (substr($0, length($0)) == "\\") { getline nextline; $0 = substr($0, 1, length($0) - 1) nextline }; print; next } {print}'\'
    fi
    grep -v '^#' | grep -v '^$' | eval "$merge"
}

##################################################
# Gestion des fichiers et répertoires temporaires
function make_tempfile() {
    # $1=template du fichier à créer
    local mktmpdir=
    if [ "$1" == "-d" ]; then
        shift
        mktmpdir=1
    fi
    local template="${1:-$TMPDIR/tmp.XXXXXX}"
    local base="${template%%X*}"
    local XXXs="${template#$base}"
    [ -z "$XXXs" ] && XXXs="XXXXXX"
    local nbX="${#XXXs}"

    local n="$$0" suff
    while true; do
        suff="$n"
        while [ ${#suff} -lt $nbX ]; do suff="0$suff"; done

        tempfile="$base$suff"
        if [ -n "$mktmpdir" ]; then
            # répertoire temporaire
            if [ ! -d "$tempfile" ]; then
                mkdir "$tempfile"
                break
            fi
        else
            # fichier temporaire
            if [ ! -f "$tempfile" ]; then
                touch "$tempfile"
                break
            fi
        fi

        n=$(($n + 1))
    done

    echo "$tempfile"
}

# autoclean: gérer une liste de fichiers temporaires à supprimer en fin de programme
__ac_files=()
function __ac_trap() {
    local file_
    for file_ in "${__ac_files[@]}"; do
        [ -e "$file_" ] && rm -rf "$file_"
    done
}
trap __ac_trap 1 3 15 EXIT
function autoclean() {
    # Ajouter $1..$n à la liste des fichiers à supprimer à la fin du programme
    local file_
    for file_ in "$@"; do
        [ -n "$file_" ] && array_add __ac_files "$file_"
    done
}
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
    local tmpfile_="$(mktempf "$2")"
    autoclean "$tmpfile_"
    set_var "$1" "$tmpfile_"
}
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
    local tmpdir_="$(mktempd "$2")"
    autoclean "$tmpdir_"
    set_var "$1" "$tmpdir_"
}

##################################################
# args: gestion d'un ensemble d'arguments

function read_args() {
    # Lire les arguments et les mettre sur plusieurs lignes dans la variable
    # dont le nom est donné dans $1. Ceci permet de façon fiable de traiter des
    # arguments avec des espaces dedans.

    # note: cela suppose bien sûr que les arguments ne contiennent pas de retour
    # à la ligne!!!
    if [ -z "$1" ]; then return 1; fi

    local varname_="$1"; shift
    local args_ arg_

    for arg_ in "$@"; do
        args_="${args_:+$args_
}$arg_"
    done
    set_var "$varname_" "$args_"
}

function next_arg() {
    # Soit la variable args dont le nom est donné dans $1, et la variable next
    # dont le nom est donné dans $2. Mettre à jour next avec la première ligne
    # de la variable args. Mettre à jour args avec le reste des lignes.
    if [ -z "$1" -o -z "$2" ]; then return 1; fi

    local args_="$1"
    local arg_="$2"
    local line_ first_=1 first_line_ last_lines_

    eval "$((echo "${!args_}"; echo "---<eof>---") | while read line_; do
        if [ "$line_" == "---<eof>---" ]; then
            echo "first_line_=\"$(quote_arg "$first_line_")\"
last_lines_=\"$(echo "$last_lines_" | awk '/.+/ { print; next }' | quote_string)\""
        elif [ -n "$first_" ]; then
            first_=
            first_line_="$line_"
        else
            last_lines_="$last_lines_
$line_"
        fi
    done)"
    set_var "$args_" "$last_lines_"
    set_var "$arg_" "$first_line_"
    if [ -z "$first_line_" ]; then return 1; fi
}

function get_option() {
    # $1 est un argument de la forme "option:value"
    # $2 est une liste d'options valides, séparées par des espaces
    # $3 est un suffixe à rajouter au nom de variable

    # Si $1 est une option valide (de la forme 'option:value' avec option
    # faisant partie de la liste (optionnelle) des options valides) alors mettre
    # à jour la variable $option avec la valeur value. sinon, retourner un code
    # d'erreur

    local arg_="$1"
    local valid_options_="$2" valid_option_ is_valid_ option_ value_
    local suffix_="$3"

    option_="$(expr "$arg_" : "\(..*\):")"
    [ -z "$option_" ] && return 1

    if [ -n "$2" ]; then
        # vérifier que $option est valide
        is_valid_=
        for valid_option_ in $valid_options_; do
            if [ "$option_" == "$valid_option_" ]; then
                is_valid_=1
                break
            fi
        done
        [ -z "$is_valid_" ] && return 1
    fi

    value_="${1##$option_:}"
    set_var "$option_$suffix_" "$value_"
}

##################################################
# input: fonctions pour poser des question et saisir des valeurs

function ask_yesno() {
    # $1=message, $2=default (N par défaut)
    # Afficher le message $message suivi de [oN] ou [On] suivant que $default
    # vaut O ou N. Retourner 0 si la réponse est oui, 1 sinon

    # Si default vaut C, alors la valeur par défaut dépend du niveau
    # d'interactivité: N si on est interactif et O si on est pas interactif

    # Support de l'affichage de la question suivant le niveau d'interaction
    # et/ou de verbosité Le message ne sera affiché que si le niveau
    # d'interaction (ou de verbosité) courant est supérieur ou égal à celui qui
    # est spécifié en argument. Sinon, la valeur par défaut est retournée
    local r
    check_verbosity "$1"; r=$?; [ $r -le 1 ] && shift

    local message="$1"
    local prompt="[oN]"
    local default="${2:-N}"
    if [ "$default" == "C" ]; then
        if [ $r -ne 1 ]; then
            default=N
        else
            default=O
        fi
    fi
    is_yes "$default" && prompt="[On]"

    if [ $r -ne 1 ]; then
        if [ -n "$message" ]; then
            tooenc_ "$message"
        else
            tooenc_ "Voulez-vous continuer?" "$UTF8"
        fi
        echo_ " $prompt "
        uread r
    else
        r=
    fi

    is_yes "${r:-$default}"
}

function read_value() {
    # $1=message, $2=varname, $3=default, $4=refuse_empty
    # Afficher le message $message suivi la valeur par défaut [$default] si elle
    # est non vide, puis lire la valeur donnée par l'utilisateur. Cette valeur
    # doit être non vide si $refuse_empty est vrai (ce qui est le cas par
    # défaut)
    # La valeur saisie est placée dans la variable $varname (par défaut value)

    # note: les variables locales sont suffixées de _ pour éviter que $varname
    # soit égal à une des variables locales.

    # Support l'affichage de la question suivant le niveau de "verbosité"
    # Le message ne sera affiché que si le niveau de verbosité courant est
    # supérieur ou égal à celui qui est spécifié en argument. Sinon, la valeur
    # par défaut est retournée. (note: si l'utilisateur requière que la valeur
    # soit non vide et que la valeur par défaut est vide, le script s'arrête
    # avec une erreur)
    local r_
    check_verbosity "$1"; r_=$?; [ $r_ -le 1 ] && shift

    local message_="$1"
    local varname_="${2:-value}"
    local default_="$3"
    local refuse_empty_="${4:-O}"

    local read_value_=
    if [ $r_ -ne 1 ]; then
        read_value_=1
    elif [ -z "$default_" ] && is_yes "$refuse_empty_"; then
        read_value_=1
    fi

    if [ -n "$read_value_" ]; then
        while true; do
            if [ -n "$message_" ]; then
                tooenc_ "$message_"
            else
                tooenc_ "Entrez la valeur" "$UTF8"
            fi
            [ -n "$default_" ] && tooenc_ " [$default_]"
            echo_ ": "
            uread r_; r_="${r_:-$default_}"
            if [ -n "$r_" ] || ! is_yes "$refuse_empty_"; then
                set_var "$varname_" "$r_"
                return 0
            fi
        done
    else
        if [ -z "$default_" ] && is_yes "$refuse_empty_"; then
            OENC="$UTF8" die "La valeur par défaut de $varname_ doit être non vide"
        else
            set_var "$varname_" "$default_"
        fi
    fi
}

##################################################
# menu: fonctions afficher un menu

function simple_menu() {
    # $1=option_varname
    # $2=options (une par ligne)
    # $3..*=votre_choix:, titre_du_menu:, choix_par_defaut:

    # Afficher un menu dont les membres sont les lignes de $options. Un choix
    # doit être saisi sous la forme num_option. L'option choisie est placée dans
    # la variable $option_varname

    # note: les variables locales sont suffixées de _ pour éviter que
    # $option_varname soit égal à une des variables locales.

    local tab_="	" # contient une tabulation
    local i_ c_
    local options_one_by_line_ options_ option_
    local choice_ choice_option_
    local option_varname_
    local votre_choix_ titre_du_menu_ choix_par_defaut_

    # nom de la variable destination
    option_varname_="$1"; shift
    if [ -z "$option_varname_" ]; then
        OENC="$UTF8" eerror "option_varname doit être spécifié"
        return 1
    fi
    choix_par_defaut_="${!option_varname_}"

    # Construire le tableau des options
    options_one_by_line_="$1"; shift
    if [ -z "$options_one_by_line_" ]; then
        OENC="$UTF8" eerror "options doit être non vide"
        return 1
    fi
    i_=0
    while next_arg options_one_by_line_ option_; do
        options_[$i_]="$option_"
        i_=$(($i_ + 1))
    done

    # autres paramètres
    while [ -n "$1" ]; do
        ! get_option "$1" "votre_choix titre_du_menu choix_par_defaut" _ && break
        shift
    done

    c_=0
    while true; do
        if [ "$c_" == "0" ]; then
            # Afficher le menu
            [ -n "$titre_du_menu_" ] && tooenc "=== $titre_du_menu_ ==="
            i_=1
            for option_ in "${options_[@]}"; do
                if [ "$option_" == "$choix_par_defaut_" ]; then
                    echo "$i_*- $option_"
                else
                    echo "$i_ - $option_"
                fi
                i_=$(($i_ + 1))
            done
        fi

        # Afficher les choix
        if [ -n "$votre_choix_" ]; then
            tooenc_ "$votre_choix_"
        else
            tooenc_ "Entrez le numéro de l'option choisie" "$UTF8"
        fi
        echo_ ": "
        uread choice_

        # valeur par défaut
        if [ -z "$choice_" -a -n "$choix_par_defaut_" ]; then
            choice_option_="$choix_par_defaut_"
            break
        fi
        # vérifier la saisie
        choice_option_="$(expr "$choice_" : "[ $tab_]*\([0-9]*\)")"
        if [ -n "$choice_option_" ] && [ "$choice_option_" -gt "0" -a "$choice_option_" -le "${#options_[*]}" ]; then
            # numéro d'option correct
            choice_option_="${options_[$(($choice_option_ - 1))]}"
            break
        else
            OENC="$UTF8" eerror "Numéro d'option incorrect"
        fi

        c_=$(($c_ + 1))
        if [ "$c_" == "5" ]; then
            echo "" # sauter une ligne toutes les 4 tentatives
            c_=0
        fi
    done

    set_var "$option_varname_" "$choice_option_"
}

function actions_menu() {
    # $1=action_varname, $2=option_varname
    # $3=actions (séparées par des espaces), $4=options (une par ligne)
    # $5..*=action_quitter:, action_par_defaut:, actions_vides:, votre_choix:,
    #       titre_du_menu:, message_sans_options:

    # Afficher un menu dont les membres sont les lignes de $options. Sur
    # chacunes de ces entrées de menu, les actions de $actions peuvent être
    # appliquées. Un choix doit être saisi sous la forme [action]num_option. Si
    # action n'est pas précisé, il s'agit de l'action par défaut qui est
    # sélectionnée. Par défaut, action_par_defaut est la première action, et
    # action_quitter (qui provoque la sortie du menu sans choix) est la dernière
    # action

    # actions est une chaine d'éléments de la forme "k:Description" séparés par
    # des espaces, où k est un lettre, et Description une description de
    # l'action. Dans la chaine Description, les caractères '_' sont remplacés
    # par des espaces pour l'affichage

    # l'action choisie est placée dans la variable $action_varname. l'option
    # choisie est placée dans la variable $option_varname

    # note: les variables locales sont suffixées de _ pour éviter que
    # $action_varname ou $option_varname soient égals à une des variables
    # locales.

    local tab_="	" # contient une tabulation
    local i_ c_ error_displayed_
    local options_one_by_line_ options_ option_
    local actions_sep_by_space_ actions_ action_ action_key_ action_name_ action_names_
    local actions_vides_ action_vide_
    local action_quitter_
    local action_par_defaut_
    local choice_ choice_ok_ choice_action_ choice_option_
    local action_varname_ option_varname_
    local sans_options_ message_sans_options_

    action_varname_="$1"; shift
    option_varname_="$1"; shift
    if [ -z "$action_varname_" -o -z "$option_varname_" ]; then
        OENC="$UTF8" eerror "action_varname et option_varname doivent être spécifiés"
        return 1
    fi

    # actions possibles
    actions_sep_by_space_="$1"; shift
    if [ -z "$actions_sep_by_space_" ]; then
        OENC="$UTF8" eerror "actions doit être non vide"
        return 1
    fi

    # Construire le tableau des options
    options_one_by_line_="$1"; shift
    if [ -n "$options_one_by_line_" ]; then
        i_=0
        while next_arg options_one_by_line_ option_; do
            options_[$i_]="$option_"
            i_=$(($i_ + 1))
        done
    else
        sans_options_=1
    fi

    # autres paramètres
    while [ -n "$1" ]; do
        ! get_option "$1" "actions_vides action_quitter action_par_defaut votre_choix titre_du_menu message_sans_options" _ && break
        shift
    done
    if [ -n "$sans_options_" -a -z "$actions_vides_" ]; then
        OENC="$UTF8" eerror "options doit être non vide"
        return 1
    fi

    # Construire le tableau des actions
    i_=0
    for action_ in $actions_sep_by_space_; do
        action_key_="$(first_char "$action_")"
        action_="$(last_chars "$action_")"
        if [ "$(first_char "$action_")" == ":" ]; then
            action_name_="$(last_chars "$action_")"
            action_name_="${action_name_//_/ }"
        else
            action_name_="$action_key_"
        fi

        [ -z "$action_par_defaut_" ] && action_par_defaut_="$action_key_"
        [ "$action_key_" == "$action_par_defaut_" ] && action_name_="$action_name_*"
        action_names_="${action_names_:+$action_names_/}$action_name_"

        actions_[$i_]="$(echo $action_key_ | awk '{print tolower($0)}')"
        i_=$(($i_ + 1))
    done
    [ -z "$action_quitter_" ] && action_quitter_="$action_key_"
    if [ "$action_par_defaut_" == "$action_quitter_" ]; then
        OENC="$UTF8" eerror "action_par_defaut et action_quitter doivent être différents"
        return 1
    fi
    actions_vides_="$actions_vides_ $action_quitter_"

    # Lecture des choix de l'utilisateur
    c_=0
    while true; do
        if [ "$c_" == "0" ]; then
            # Afficher le menu
            [ -n "$titre_du_menu_" ] && tooenc "=== $titre_du_menu_ ==="
            if [ -z "$sans_options_" ]; then
                i_=1
                for option_ in "${options_[@]}"; do
                    tooenc "$i_ - $option_"
                    i_=$(($i_ + 1))
                done
            else
                if [ -n "$message_sans_options_" ]; then
                    tooenc "$message_sans_options_"
                else
                    tooenc "Pas d'options disponibles" "$UTF8"
                fi
            fi
        fi

        # Afficher les choix
        tooenc_ "
Actions disponibles: " "$UTF8"
        tooenc "$action_names_"
        if [ -n "$votre_choix_" ]; then
            tooenc_ "$votre_choix_"
        else
            tooenc_ "Entrez le numéro de l'option choisie" "$UTF8"
        fi
        echo_ ": "
        read choice_

        # vérifier la saisie
        choice_option_="$(expr "$choice_" : "\([0-9]*\)")"
        if [ -n "$choice_option_" ]; then
            # on a donné le numéro de l'option sans l'action. retourner l'action par défaut
            if [ -z "$sans_options_" -a "$choice_option_" -gt "0" -a "$choice_option_" -le "${#options_[*]}" ]; then
                # numéro d'option correct
                choice_option_="${options_[$(($choice_option_ - 1))]}"
                choice_action_="$action_par_defaut_"
                break
            else
                OENC="$UTF8" eerror "Numéro d'option incorrect"
            fi

        else
            choice_action_="$(expr "$choice_" : "\([^ $tab_]\)")"
            choice_ok_=
            error_displayed_=
            for action_ in "${actions_[@]}"; do
                if [ "$choice_action_" == "$action_" ]; then
                    # action correcte

                    # est-ce une action vide (une action qui ne requière pas de numéro d'option)
                    for action_vide_ in $actions_vides_; do
                        if [ "$choice_action_" == "$action_vide_" ]; then
                            # retourner comme option tous les arguments de
                            # l'action vide, en trimant les espaces de début
                            choice_option_="$(expr "$choice_" : "[^ $tab_][ $tab_]*\(.*\)")"
                            choice_ok_=1
                            break
                        fi
                    done
                    [ -n "$choice_ok_" ] && break

                    # lire le numéro d'option associé à l'action
                    choice_option_="$(expr "$choice_" : "[^ $tab_][ $tab_]*\([0-9]*\)")"
                    if [ -n "$choice_option_" ] && [ "$choice_option_" -gt "0" -a "$choice_option_" -le "${#options_[*]}" ]; then
                        # numéro d'option correct
                        choice_option_="${options_[$(($choice_option_ - 1))]}"
                        choice_ok_=1
                    else
                        OENC="$UTF8" eerror "Il faut spécifier un numéro d'option correct avec l'action"
                        error_displayed_=1
                    fi
                    [ -n "$choice_ok_" ] && break
                fi
            done
            [ -n "$choice_ok_" ] && break
            [ -z "$error_displayed_" ] && OENC="$UTF8" eerror "Action incorrecte"
        fi

        c_=$(($c_ + 1))
        if [ "$c_" == "5" ]; then
            echo "" # sauter une ligne toutes les 4 tentatives
            c_=0
        fi
    done

    set_var "$action_varname_" "$choice_action_"
    set_var "$option_varname_" "$choice_option_"
}

##################################################
# fileopts: opérations sur les fichiers

function file_get_vars() {
    # lire les variables dans un fichier
    local OENC="$UTF8"
    local done_ prefix_ whole_file_ file_ vars_ var_ script_

    # traiter les arguments
    done_=
    while [ -z "$done_" ]; do
        case "$1" in
        --prefix|-p)
            # prefixe pour la variable
            prefix_="$2"
            shift; shift
            ;;
        --whole-file|-w)
            # lire les variables dans le fichier en entier, et pas seulement
            # dans l'en-tête.
            whole_file_=1
            shift
            ;;
        *)
            done_=1
            ;;
        esac
    done

    # obtenir le nom du fichier
    file_="$1"; shift
    if [ ! -f "$file_" ]; then
        # fichier inexistant
        ewarn "Fichier inexistant: $file_"
        return 1
    fi

    # initialiser les valeurs par défaut
    vars_=
    while [ -n "$1" ]; do
        var_="$1"; shift
        vars_="${vars_:+$vars_ }$var_"
        set_var "$var_" "$1"; shift
    done

    # puis parcourir le fichier pour initialiser la valeur définitive des
    # variables
    if [ -z "$whole_file_" ]; then
        script_="/^ *$/ { exit }
"
    fi

    for var_ in $vars_; do
        script_="$script_
"'$0 ~ "^ *#* *" prefix "'"$var_"'=" {
    # enlever les caractères inutiles
    sub(" *#* *" prefix "'"$var_"'=", "")
    # enlever éventuellement les quotes autour de la valeur
    if ($0 ~ /^".*" *$/) {
        $0 = substr($0, 2) # premier quote
        match($0, /" *$/)
        $0 = substr($0, 1, RSTART - 1) # dernier quote
    }
    # remplacer dans les valeurs les occurences de quotes
    gsub("\"", "\\\"")
    # afficher la ligne à évaluer
    print "'"$var_"'=\"" $0 "\""
}
'
    done

    eval "$(uawk -v prefix="$prefix_" "$script_" <"$file_")"
}

function file_set_vars() {
    # écrire les variables dans un fichier. Le fichier *doit exister*
    local OENC="$UTF8"
    local done_ quote_ comment_ prefix_ whole_file_ file_ var_ script_

    # traiter les arguments
    done_=
    while [ -z "$done_" ]; do
        case "$1" in
        --quote-always|-q)
            # toujours mettre les valeurs entre quotes
            quote_=1
            shift
            ;;
        --comment-prefix|-c)
            # commentaire à préfixer
            comment_="$2"
            shift; shift
            ;;
        --prefix|-p)
            # prefixe pour la variable
            prefix_="$2"
            shift; shift
            ;;
        --whole-file|-w)
            # écrire les variables dans le fichier en entier, et pas seulement
            # dans l'en-tête.
            whole_file_=1
            shift
            ;;
        *)
            done_=1
            ;;
        esac
    done

    # obtenir le nom du fichier
    file_="$1"; shift
    if [ ! -f "$file_" ]; then
        # fichier inexistant
        ewarn "Fichier inexistant: $file_"
        return 1
    fi

    # puis parcourir le fichier pour mettre à jour les valeurs
    script_='BEGIN {
    # faut-il mettre à jour les variables?
    update_vars = 1'
    for var_ in "$@"; do
        script_="$script_
    ${var_}_done = 0"
    done
    script_="$script_
}"

    script_="$script_"'
function quote_value_maybe(value) {
    if (quote_always || index(value, " ") != 0) {
        return "\"" value "\""
    } else {
        return value
    }
}

function write_all_remaining_vars() {'
    for var_ in "$@"; do
        value="${!var_}"
        script_="$script_"'
    if (! '"$var_"'_done) {
        print comment prefix "'"$var_"'=" quote_value_maybe("'"${value//\"/\\\"}"'")
        '"$var_"'_done = 1
    }'
    done
    script_="$script_
}"

    if [ -z "$whole_file_" ]; then
        script_="$script_"'
/^[ \t]*$/ {
    write_all_remaining_vars()
    update_vars = 0
}'
    fi

    script_="$script_"'
update_vars && comment == "" && $0 ~ "^ *#* *.*=.*$" {
    #comment = gensub("^( *#* *).*=.*$", "\\1", 1)
    comment = ""
    if (match($0, /^ *#* */) != 0) {
        comment = substr($0, RSTART, RLENGTH)
    }
}'

    for var_ in "$@"; do
        value="${!var_}"
        script_="$script_"'
update_vars && ! '"$var_"'_done && $0 ~ "^ *#* *" prefix "'"$var_"'=" {
    #$0 = gensub("^( *#* *" prefix "'"$var_"'=).*$", "\\1", 1)
    match($0, "^ *#* *" prefix "'"$var_"'=")
    $0 = substr($0, RSTART, RLENGTH)
    $0 = $0 quote_value_maybe("'"${value//\"/\\\"}"'")
    print
    '"$var_"'_done = 1
    next
}'
    done

    script_="$script_"'
{
    print
}
END {
    if (update_vars) {
        write_all_remaining_vars()
    }
}'

    uawk -v quote_always="$quote_" -v comment="$comment_" -v prefix="$prefix_" "$script_" <"$file_" >"$file_.$$" &&
    /bin/mv "$file_.$$" "$file_"
}

function file_get_properties() {
    # lire les propriétés d'un fichier de propriété java ou xml
    if endswith "$1" .xml; then
        file_get_xml_properties "$@"
    else
        file_get_java_properties "$@"
    fi
}

function file_set_properties() {
    # écrire les propriétés d'un fichier de propriété java ou xml
    local done_ create_
    while [ -z "$done_" ]; do
        case "$1" in
        --create|-c)
            create_=1
            shift
            ;;
        *)
            done_=1
            ;;
        esac
    done
    if endswith "$1" .xml; then
        file_set_xml_properties ${create_:+-c} "$@"
    else
        file_set_java_properties ${create_:+-c} "$@"
    fi
}


function file_get_java_properties() {
    # lire les propriétés d'un fichier de propriétés java. note: les noms de
    # propriété java peuvent contenir le caractère "." mais pas les noms de
    # variable bash. La conversion est faite automatiquement. Par exemple::
    #    file_get_properties build.properties path.to.package "default value"
    # charge la valeur de la propriété dans la variable path_to_package

    # $1=nom du fichier de propriété
    # $2..n=propriétés qu'il faut lire et valeurs par défaut de ces propriétés
    #   $__2*i  __=nom de la propriété
    #   $__2*i+1__=valeur par défaut de la propriété si la valeur n'existe pas
    #              dans le fichier
    local OENC="$UTF8"
    local file_="$1"; shift
    if [ ! -f "$file_" ]; then
        # fichier inexistant
        ewarn "Fichier de propriété inexistant: $file_"
        return 1
    fi

    local var_ sh_var_ awk_var_
    local script
    while [ -n "$1" ]; do
        # pour chacune des variables...
        var_="$1"; shift
        sh_var_="${var_//./_}"    # nom de la variable shell traduite ("." --> "_")
        sh_var_="${sh_var_//-/_}" #                                   ("-" --> "_")
        awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk

        # initialiser la valeur par défaut
        set_var "$sh_var_" "$1"; shift

        # et créer le script qui affichera sa valeur
        script="$script"'
$0 ~ "^[ \t]*'"$(quote_awk "$awkre_var_")"'=" {
    # enlever les caractères de début
    sub("^[ \t]*'"$(quote_awk "$awkre_var_")"'=", "")
    value = $0
    # éventuellement ajouter les lignes de continuation
    while (substr(value, length(value), 1) == "\\") {
        getline
        sub("^[ \t]*", "")
        value = substr(value, 1, length(value) - 1) $0
    }
    gsub("\"", "\\\"", value)
    print "'"$sh_var_"'=\"" value "\""
}
'
    done

    eval "$(awk "$script" <"$file_")"
}

function file_set_java_properties() {
    # écrire des propriétés dans un fichier de propriétés java.

    # $1=nom du fichier de propriété
    # $2..n=propriétés qu'il faut écrire et valeurs de ces propriétés
    #   $__2*i  __=nom de la propriété
    #   $__2*i+1__=valeur de la propriété
    # traiter les arguments
    local OENC="$UTF8"
    local done_ create_
    while [ -z "$done_" ]; do
        case "$1" in
        --create|-c)
            # créer le fichier s'il n'existe pas
            create_=1
            shift
            ;;
        *)
            done_=1
            ;;
        esac
    done

    local file_="$1"; shift
    if [ ! -f "$file_" ]; then
        if [ -n "$create_" ]; then
            touch "$file_"
        else
            # fichier inexistant
            ewarn "Fichier de propriété inexistant: $file_"
            return 1
        fi
    fi

    # récupérer les noms des propriétés et leur valeur
    local var_ arg_ sh_var_ awkre_var_ value_
    local -a vars_ values_
    value_=vars_
    for arg_ in "$@"; do
        array_add "$value_" "$arg_"
        if [ "$value_" == "vars_" ]; then
            value_=values_
        else
            value_=vars_
        fi
    done

    # créer le script qui va parcourir le fichier pour mettre à jour les valeurs
    script_="BEGIN {"
    for var_ in "${vars_[@]}"; do
        sh_var_="${var_//./_}"    # nom de la variable shell traduite ("." --> "_")
        sh_var_="${sh_var_//-/_}" #                                   ("-" --> "_")
        script_="$script_
    ${sh_var_}_done = 0"
    done
    script_="$script_"'
}
function write_all_remaining_vars() {'
    local i=0
    while [ $i -lt ${#vars_[*]} ]; do
        var_="${vars_[$i]}"
        sh_var_="${var_//./_}"    # nom de la variable shell traduite ("." --> "_")
        sh_var_="${sh_var_//-/_}" #                                   ("-" --> "_")
        awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk
        value_="${values_[$i]}"

        script_="$script_"'
    if (! '"$sh_var_"'_done) {
        print "'"$var_"'=" "'"${value_//\"/\\\"}"'"
        '"$sh_var_"'_done = 1
    }'
        i=$((i + 1))
    done
    script_="$script_
}"

    local i=0
    while [ $i -lt ${#vars_[*]} ]; do
        var_="${vars_[$i]}"
        sh_var_="${var_//./_}"    # nom de la variable shell traduite ("." --> "_")
        sh_var_="${sh_var_//-/_}" #                                   ("-" --> "_")
        awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk
        value_="${values_[$i]}"
        script_="$script_"'
! '"$sh_var_"'_done && $0 ~ "^[ \t]*'"$awkre_var_"'=" {
    #name = gensub("^([ \t]*'"$awkre_var_"'=).*$", "\\1", 1)
    match($0, "^[ \t]*'"$awkre_var_"'=")
    name = substr($0, RSTART, RLENGTH)
    value = substr($0, length(name) + 1)

    while (substr(value, length(value), 1) == "\\") {
        getline
        value = value $0
    }
    line = name "'"${value_//\"/\\\"}"'"
    prefix = ""
    max_len = 75
    if (length(line) > max_len) {
        do {
            print prefix substr(line, 1, max_len) "\\"
            line = substr(line, max_len + 1)
            prefix = "    "
            max_len = 71
        } while (length(line) > max_len)
    }
    print prefix line
    '"$sh_var_"'_done = 1
    next
}'
        i=$((i + 1))
    done

    script_="$script_
{
    print
}
END {
    write_all_remaining_vars()
}"

    awk "$script_" <"$file_" >"$file_.$$" &&
    /bin/mv "$file_.$$" "$file_"
}

function file_get_xml_properties() {
    # lire les propriétés d'un fichier de propriétés xml. Limitation: les
    # propriétés ne doivent pas être continuées sur plusieurs lignes. Les
    # propriétés doivent être écrites sous la forme::
    #    <propname>propvalue</propname>

    # note: les noms de propriété java peuvent contenir le caractère "." mais
    # pas les noms de variable bash. La conversion est faite
    # automatiquement. Par exemple::
    #    file_get_properties build.properties path.to.package "default value"
    # charge la valeur de la propriété dans la variable path_to_package

    # $1=nom du fichier de propriété
    # $2..n=propriétés qu'il faut lire et valeurs par défaut de ces propriétés
    #   $__2*i  __=nom de la propriété
    #   $__2*i+1__=valeur par défaut de la propriété si la valeur n'existe pas
    #              dans le fichier
    local OENC="$UTF8"
    local file_="$1"; shift
    if [ ! -f "$file_" ]; then
        # fichier inexistant
        ewarn "Fichier de propriété inexistant: $file_"
        return 1
    fi

    local var_ sh_var_ awk_var_
    local script
    while [ -n "$1" ]; do
        # pour chacune des variables...
        var_="$1"; shift
        sh_var_="${var_//./_}"    # nom de la variable shell traduite ("." --> "_")
        sh_var_="${sh_var_//-/_}" #                                   ("-" --> "_")
        awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk

        # initialiser la valeur par défaut
        set_var "$sh_var_" "$1"; shift

        # et créer le script qui affichera sa valeur
        script="$script"'
$0 ~ /^[ \t]*<'"$awkre_var_"'>.*<\/'"$awkre_var_"'>/ {
    sub(/^[ \t]*<'"$awkre_var_"'>/, "")
    sub(/<\/'"$awkre_var_"'>.*$/, "")
    gsub("\"", "\\\"", $0)
    print "'"$sh_var_"'=\"" $0 "\""
}
'
    done

    eval "$(awk "$script" <"$file_")"
}

function file_set_xml_properties() {
    # écrire des propriétés dans un fichier de propriétés java.

    # $1=nom du fichier de propriété
    # $2..n=propriétés qu'il faut écrire et valeurs de ces propriétés
    #   $__2*i  __=nom de la propriété
    #   $__2*i+1__=valeur de la propriété
    # traiter les arguments
    local OENC="$UTF8"
    local done_ create_
    while [ -z "$done_" ]; do
        case "$1" in
        --create|-c)
            # créer le fichier s'il n'existe pas
            create_=1
            shift
            ;;
        *)
            done_=1
            ;;
        esac
    done

    local file_="$1"; shift
    if [ ! -f "$file_" ]; then
        if [ -n "$create_" ]; then
            touch "$file_"
        else
            # fichier inexistant
            ewarn "Fichier de propriété inexistant: $file_"
            return 1
        fi
    fi

    # récupérer les noms des propriétés et leur valeur
    local var_ arg_ sh_var_ awkre_var_ value_
    local -a vars_ values_
    value_=vars_
    for arg_ in "$@"; do
        array_add "$value_" "$arg_"
        if [ "$value_" == "vars_" ]; then
            value_=values_
        else
            value_=vars_
        fi
    done

    # créer le script qui va parcourir le fichier pour mettre à jour les valeurs
    script_='BEGIN {
    rootElement = ""'
    for var_ in "${vars_[@]}"; do
        sh_var_="${var_//./_}"    # nom de la variable shell traduite ("." --> "_")
        sh_var_="${sh_var_//-/_}" #                                   ("-" --> "_")
        script_="$script_
    ${sh_var_}_done = 0"
    done
    script_="$script_"'
}
function write_all_remaining_vars() {'
    local i=0
    while [ $i -lt ${#vars_[*]} ]; do
        var_="${vars_[$i]}"
        sh_var_="${var_//./_}"    # nom de la variable shell traduite ("." --> "_")
        sh_var_="${sh_var_//-/_}" #                                   ("-" --> "_")
        awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk
        value_="${values_[$i]}"

        script_="$script_"'
    if (! '"$sh_var_"'_done) {
        print "<'"$var_"'>'"${value_//\"/\\\"}"'</'"$var_"'>"
        '"$sh_var_"'_done = 1
    }'
        i=$((i + 1))
    done
    script_="$script_
}"'
rootElement == "" {
    match($0, /<.*>/)
    element = substr($0, RSTART + 1, RLENGTH - 2)
    firstChar = substr(element, 1, 1)
    if (firstChar != "?" && firstChar != "!") {
        rootElement = element
    }
}'

    local i=0
    while [ $i -lt ${#vars_[*]} ]; do
        var_="${vars_[$i]}"
        sh_var_="${var_//./_}"    # nom de la variable shell traduite ("." --> "_")
        sh_var_="${sh_var_//-/_}" #                                   ("-" --> "_")
        awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk
        value_="${values_[$i]}"
        script_="$script_"'
rootElement != "" && ! '"$sh_var_"'_done && $0 ~ /^[ \t]*<'"$awkre_var_"'>.*<\/'"$awkre_var_"'>/ {
    match($0, /^[ \t]*<'"$awkre_var_"'>/)
    first = substr($0, RSTART, RLENGTH)
    value = substr($0, length(first) + 1)
    match(value, /<\/'"$awkre_var_"'>.*$/)
    last = substr(value, RSTART, RLENGTH)
    value = substr(value, 1, RSTART)

    print first "'"${value_//\"/\\\"}"'" last
    '"$sh_var_"'_done = 1
    next
}'
        i=$((i + 1))
    done

    script_="$script_"'
rootElement != "" && $0 ~ "</" rootElement ">" {
    rootElement = ""
    write_all_remaining_vars()
}
{
    print
}
END {
    write_all_remaining_vars()
}'

    awk "$script_" <"$file_" >"$file_.$$" &&
    /bin/mv "$file_.$$" "$file_"
}

##################################################
# date: fonction pour afficher la date en français et au format RFC-822

function get_date_rfc822() {
    if [ -n "$__legacy_date__" ]; then
        LC_TIME=C date +"%a, %d %b %Y %H:%M:%S %Z"
    else
        date -R
    fi
}

function get_date_fr() {
    date +"%d/%m/%Y"
}

function get_time_fr() {
    date +"%Hh%M"
}

function parse_date() {
    local value="$1" type="${2:-date}"
    local now="$(awk 'BEGIN { print mktime(strftime("%Y %m %d 00 00 00 +0400")) }')"
    case "$value" in
    +*)
        value="$(($now + ${value#+} * 86400))"
        ;;
    *)
        value="$(<<<"$value" awk -F/ '{
            nd = strftime("%d"); nm = strftime("%m"); ny = strftime("%Y")
            d = $1 + 0; if (d < 1 || d > 31) d = nd;
            if ($2 == "") m = nm;
            else { m = $2 + 0; if (m < 1 || m > 12) m = nm; }
            if ($3 == "") y = ny;
            else { y = $3 + 0; if (y < 100) y = y + 2000; }
            print mktime(sprintf("%04i %02i %02i 00 00 00 +0400", y, m, d));
        }')"
    esac
    case "$type" in
    d|date)
        awk '{ print strftime("%d/%m/%Y", $0 + 0) }' <<<"$value"
        ;;
    l|ldap)
        awk '{ print strftime("%Y%m%d%H%M%S+0400", $0 + 0) }' <<<"$value"
        ;;
    *)
        echo "$value"
        ;;
    esac
}

##################################################
# path: fonctions pour gérer les chemins

function abspath() {
    # Retourner un chemin absolu vers $1. Si $2 est non nul et si $1 est un
    # chemin relatif, alors $1 est exprimé par rapport à $2, sinon il est
    # exprimé par rapport au répertoire courant.
    local pwd="$(pwd)"
    if [ -n "$2" ]; then
        # tout d'abord, calculer le répertoire à partir duquel on exprime les
        # chemin relatifs
        if [ -d "$2" ]; then
            pwd="$(cd "$2"; pwd)"
        else
            pwd="$(abspath "$2")"
        fi
    fi

    if [ -e "$1" ]; then
        local dn="$(dirname "$1")" bn="$(basename "$1")"
        if [ "$bn" == "." ]; then
            echo "$(cd "$pwd"; cd "$dn"; pwd)"
        elif [ "$bn" == ".." ]; then
            echo "$(cd "$pwd"; cd "$dn/.."; pwd)"
        elif [ "$dn" == "/" ]; then
            echo "/$bn"
        else
            echo "$(cd "$pwd"; cd "$dn"; pwd)/$bn"
        fi
    else
        if first_char_is "$1" "/"; then
            echo "$1"
        else
            echo "$pwd/$1"
        fi
    fi
}

function relpath() {
    # Retourner le chemin relatif de $1 par rapport à $2. Si $2 n'est pas
    # spécifié, on prend le répertoire courant. Si $1 ou $2 ne sont pas des
    # répertoires absolus, il sont transformés en chemins absolus par rapport à
    # $3.
    # Si $1==$2, retourner une chaine vide
    local p="$(abspath "$1" "$3")" cwd="$2"
    if [ -z "$cwd" ]; then
        cwd="$(pwd)"
    else
        cwd="$(abspath "$cwd" "$3")"
    fi
    if [ "$p" == "$cwd" ]; then
        echo ""
    elif [ "${p#$cwd/}" != "$p" ]; then
        echo "${p#$cwd/}"
    else
        local rp
        while [ -n "$cwd" -a "${p#$cwd/}" == "$p" ]; do
            rp="${rp:+$rp/}.."
            cwd="${cwd%/*}"
        done
        rp="$rp/${p#$cwd/}"
        # ${rp%//} traite le cas $1==/
        echo "${rp%//}"
    fi
}

function deref() {
    # Retourner un chemin absolu vers le fichier $1, dans lequel
    # toutes les composantes "lien symbolique" ont été supprimées.
    local OENC="$UTF8"

    local max_deref=50
    local file="$1"
    while [ -L "$file" ]; do
        basedir="$(dirname "$file")"
        link="$(readlink "$file")"
        if first_char_is "$link" "/"; then
            # lien absolu
            file="$link"
        else
            # lien relatif
            file="$basedir/$link"
        fi

        max_deref=$(($max_deref - 1))
        [ $max_deref -eq 0 ] && die "Plus de 50 indirection. Le lien $file est-il récursif?"
    done
    abspath "$file"
}

function path_if_test() {
    # afficher un chemin si le fichier $2 existe (en utilisant l'opérateur $1)
    # dans l'un des chemins absolus $4..n. si $3==relative, afficher le chemin
    # relatif, sinon le chemin absolu. note: $3 peut être de la forme
    # relative:path, auquel cas le chemin affiché est exprimé relativement à
    # path
    local op_="$1"; shift
    local file_="$1"; shift
    local rel_="$1" reldir_=; shift
    if beginswith "$rel_" relative; then
        reldir_="${rel_#relative}"
        if beginswith "$reldir_" :; then
            # on a un argument de la forme relative:path
            reldir_="${reldir_#:}"
            if [ -n "$reldir_" ]; then
                reldir_="${reldir_}/"
            fi
        else
            # argument vide ou format non valide
            reldir_=
        fi
    else
        rel_=
    fi

    while [ -n "$1" ]; do
        local basedir_="$1"
        if [ $op_ "$basedir_/$file_" ]; then
            if [ -n "$rel_" ]; then
                echo "$reldir_$file_"
            else
                echo "$basedir_/$file_"
            fi
            break
        fi
        shift
    done
}

function is_archive() {
    # tester si l'extension d'un fichier indique que c'est une archive
    local name="${1%.zip}"
    name="${name%.tgz}"
    name="${name%.tar.gz}"
    name="${name%.tar}"
    name="${name%.tar.bz2}"
    name="${name%.jar}"
    name="${name%.war}"
    name="${name%.ear}"
    [ "$name" != "$1" ]
}
function extract_archive() {
    # Extraire le contenu de l'archive $1 dans le répertoire ${2:-.}
    # Les autres arguments indiquent les fichiers à extraire
    local arch="$1" destdir="${2:-.}"
    shift; shift
    if endswith "$arch" .zip; then
        unzip -d "$destdir" "$arch" "$@" || return
    elif endswith "$arch" .tgz || endswith "$arch" .tar.gz; then
        tar xzf "$arch" -C "$destdir" "$@" || return
    elif endswith "$arch" .tbz2 || endswith "$arch" .tar.bz2; then
        tar xjf "$arch" -C "$destdir" "$@" || return
    elif endswith "$arch" .tar; then
        tar xf "$arch" -C "$destdir" "$@" || return
    elif endswith "$arch" .jar || endswith "$arch" .war || endswith "$arch" .ear; then
        jar xf "$arch" -C "$destdir" "$@" || return
    else
        return 1
    fi
}


##################################################
# array: fonctions pour gérer des tableaux

# Afficher la commande permettant d'initialiser le tableau $1 avec les valeurs:
# soit du tableau $2, soit de $3..$n si $2=="@"
# S'il n'y a que l'argument $1, alors afficher la commande permettant de
# recréer le tableau $1
function set_array_cmd() {
    [ $# -eq 1 ] && set -- "$1" "$1"
    local s_ v_ f_
    s_="$1=("; shift
    if [ "$1" == "@" ]; then
        shift
    else
        eval "set -- \"\${$1[@]}\""
    fi
    f_=1
    for v_ in "$@"; do
        [ -n "$f_" ] && f_= || s_="$s_ "
        s_="$s_\"$(quote_arg "$v_")\""
    done
    s_="$s_)"
    echo "$s_"
}
# Soit $1 un tableau à créer. Si $2=="@", créer le tableau $1 avec les valeurs
# $3..$n. Sinon, créer le tableau $1 avec les valeurs du tableau $2.
# Cette fonction n'existe que comme un pendant de set_var(), mais le véritable
# intérêt est la fonction set_array_cmd(). cf array_copy() pour une version plus
# efficace de la copie de tableaux
function set_array() {
    eval "$(set_array_cmd "$@")"
}

function array_count() {
    # retourner le nombre d'éléments du tableau $1
    eval "echo \${#$1[*]}"
}

function array_isempty() {
    # tester si un tableau est vide
    test $(array_count "$1") -eq 0
}

function array_new() {
    # créer un tableau vide dont le nom est $1
    eval "$1=()"
}

function array_add() {
    # ajouter la valeur $2 au tableau dont le nom est $1
    eval "$1=(\"\${$1[@]}\" \"$(quote_arg "$2")\")"
}

# insérer la valeur $2 au début du tableau dont le nom est $1
function array_ins() { eval "$1=(\"$(quote_arg "$2")\" \"\${$1[@]}\")"; }

function array_add_values() {
    # Ajouter les valeurs $2 au tableau dont le nom est $1
    # $2 est une liste de valeurs séparées par des espaces ou le caractère ':'
    local array_="$1" value_
    if [[ "$2" == *:* ]]; then
        local IFS=:
        set -- $2
        unset IFS
    else
        set -- $2
    fi
    for value_ in "$@"; do
        array_add "$array_" "$value_"
    done
}

function array_del() {
    # supprimer les valeurs $2 du tableau dont le nom est $1
    local arg_="$(quote_arg "$2")" value_
    local -a array_
    eval 'for value_ in "${'"$1"'[@]}"; do if [ "$value_" != "'"$arg_"'" ]; then array_add array_ "$value_"; fi; done'
    array_copy "$1" array_
}

function array_set() {
    # ajouter la valeur $2 au tableau dont le nom est $1, si la valeur n'y est
    # pas déjà. Retourner vrai si la valeur a été ajoutée
    local value_="$(quote_arg "$2")" i_
    eval "for i_ in \"\${$1[@]}\"; do if [ \"\$i_\" == \"$value_\" ]; then return 1; fi; done"
    array_add "$1" "$2"
    return 0
}

function array_contains() {
    # tester si le tableau dont le nom est $1 contient la valeur $2
    local value_="$(quote_arg "$2")" i_
    eval "for i_ in \"\${$1[@]}\"; do if [ \"\$i_\" == \"$value_\" ]; then return; fi; done"
    return 1
}

function array_copy() {
    # copier le contenu du tableau $2 dans le tableau $1
    eval "$1=(\"\${$2[@]}\")"
}

function array_extend() {
    # Ajouter le contenu du tableau $2 au tableau $1
    eval "$1=(\"\${$1[@]}\" \"\${$2[@]}\")"
}
# dans le tableau $1, remplacer toutes les occurences de $2 par $3..*
function array_replace() {
    local srcname_="$1"; shift
    local from_="$1"; shift
    local -a dest_
    local src_ v_
    src_="$srcname_[@]"
    for value_ in "${!src_}"; do
        if [ "$value_" == "$from_" ]; then
            dest_=("${dest_[@]}" "$@")
        else
            dest_=("${dest_[@]}" "$value_")
        fi
    done
    array_copy "$srcname_" dest_
}
# Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les
# arguments '$v $3..$n'
function array_each() {
    local an_="$1"; shift
    local f_="$1"; shift
    local a_="$an_[@]" v
    for v_ in "${!a_}"; do
        "$f_" "$v_" "$@"
    done
}
# retourner la première valeur du tableau $1
function first_value() { eval "echo \"\${$1[@]:0:1}\""; }
# retourner la dernière valeur du tableau $1
function last_value() { eval "echo \"\${$1[@]:\$((-1)):1}\""; }
# copier le contenu du tableau $2 dans le tableau $1
function array_copy() { eval "$1=(\"\${$2[@]}\")"; }
# copier tous 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_del_last() { array_copy_firsts "$1"; }
# copier tous 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_del_first() { array_copy_lasts "$1"; }
# ajouter le contenu du tableau $2 au tableau $1
function array_extend() { eval "$1=(\"\${$1[@]}\" \"\${$2[@]}\")"; }
# 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))}\")"; }
# ajouter toutes les valeurs du tableau $2 dans le tableau $1, excepté la première
function array_extend_lasts() { eval "$1=(\"\${$1[@]}\" \"\${$2[@]:1}\")"; }
# Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les
# arguments '$v $3..$n', et remplacer la valeur par le résultat de la fonction
function array_map() {
    local an_="$1"; shift
    local f_="$1"; shift
    local a_="$an_[@]" v
    local -a vs_
    for v_ in "${!a_}"; do
        vs_=("${vs_[@]}" "$("$f_" "$v_" "$@")")
    done
    array_copy "$an_" vs_
}

function array_from_args() {
    # créer le tableau dont le nom est $1 avec les arguments à partir de $2
    local array_="$1"; shift
    eval "$array_"'=("$@")'
}

function array_from_file() {
    # créer le tableau dont le nom est $1 avec les lignes du fichier $2
    # si $3=all, ne pas supprimer les lignes vide ou de commentaire
    eval "$(<"$2" uawk -v name="$1" -v all="$3" '
BEGIN {
    print name "=("
}
all != "all" && $0 ~ /^$/ { next }
all != "all" && $0 ~ /^#/ { next }
{
    gsub(/'\''/, "'\'\\\\\'\''")
    print "'\''" $0 "'\''"
}
END {
    print ")"
}')" #"
}

function array_from_lines() {
    # créer le tableau $1 avec chaque ligne de $2. Les lignes vides sont
    # ignorés.
    eval "$(<<<"$2" uawk -v name="$1" '
BEGIN {
    print name "=("
}
/^$/ { next }
{
    gsub(/'\''/, "'\'\\\\\'\''")
    print "'\''" $0 "'\''"
}
END {
    print ")"
}')" #"
}

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"
    eval "$(<<<"$2" uawk -v RS="" -v ORS="" '{ gsub("\r*\n$", ""); print }' |
uawk -v name="$1" -v RS="${3:-:}" '
BEGIN {
    print name "=("
}
/^$/ { next }
{
    gsub(/'\''/, "'\'\\\\\'\''")
    print "'\''" $0 "'\''"
}
END {
    print ")"
}')" #"
}

function array_from_path() {
    array_split "$1" "$2" ":"
}

function array_join() {
    # afficher le contenu du tableau dont le nom est $1 sous forme d'une liste
    # de chemins séparés par $2
    # 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
    local array_ line_ joined_ sep_="${2:-,}" pfix_ sfix_
    if [ "$1" == "@" ]; then
        array_="\$@"
        shift; shift
    else
        array_="\${$1[@]}"
        pfix_="$4"
        sfix_="$5"
    fi
    eval 'for line_ in "'"$array_"'"; do joined_="${joined_:+$joined_'"$sep_"'}$pfix_$line_$sfix_"; done'
    if [ -n "$joined_" ]; then
        echo "$joined_"
    elif [ "$array_" != "\$@" -a -n "$3" ]; then
        echo "$3"
    fi
}

function array_to_lines() {
    # afficher le tableau dont le nom est $1 sous forme de lignes
    array_join "$1" "
" "$2" "$3" "$4"
}

function array_to_path() {
    # afficher le tableau dont le nom est $1 sous forme d'une liste de chemins
    # séparés par ':')
    array_join "$1" ":" "$2" "$3" "$4"
}

function array_enum_start() {
    # Commencer l'énumération du tableau $1
    [ -z "$1" ] && return 1
    set_var_literal "${1}_enum_index" 0
}

function array_enum_next() {
    # Soit la variable array dont le nom est donné dans $1, et la variable next
    # dont le nom est donné dans $2. Mettre à jour next avec l'élément
    # $1_enum_index du tableau array, supprimer cet élément, et incrémenter
    # $1_enum_index. Retourner faux s'il n'y a plus d'éléments dans le tableau

    local arrayname_="$1" varname_="$2"
    [ -z "$arrayname_" -o -z "$varname_" ] && return 1

    eval "local count_=\${#$arrayname_[*]}"
    [ $count_ -eq 0 ] && return 1

    # calculer l'index de départ
    local index_="${arrayname_}_enum_index"
    [ -z "${!index_}" ] && set_var_literal "$index_" 0
    # lire la valeur
    local item_="$arrayname_[${!index_}]"
    set_var "$varname_" "${!item_}"
    # la supprimer du tableau
    unset "$item_"
    # incrémenter le compteur
    set_var_literal "$index_" $((${!index_} + 1))
}

function __array_ls() {
    # Lister les fichiers avec `list_$1 $3 $4...`, et les mettre dans le tableau $2
    # Le tableau contient les chemins complets, par seulement les noms comme avec list_$1
    local list_="list_${1:-all}"; shift
    local arrayname_="$1"; shift
    local basedir_="${1:-.}"; shift
    local -a files_
    array_from_lines files_ "$("$list_" "$basedir_" "$@")"
    local file_
    array_new "$arrayname_"
    for file_ in "${files_[@]}"; do
        array_add "$arrayname_" "$basedir_/$file_"
    done
}

function array_lsall() {
    # Lister les fichiers avec `list_all $2 $3...`, et les mettre dans le tableau $1
    # Le tableau contient les chemins complets, par seulement les noms comme avec list_all
    __array_ls all "$@"
}

function array_lsdirs() {
    # Lister les fichiers avec `list_dirs $2 $3...`, et les mettre dans le tableau $1
    # Le tableau contient les chemins complets, par seulement les noms comme avec list_dirs
    __array_ls dirs "$@"
}

function array_lsfiles() {
    # Lister les fichiers avec `list_files $2 $3...`, et les mettre dans le tableau $1
    # Le tableau contient les chemins complets, par seulement les noms comme avec list_files
    __array_ls files "$@"
}

##################################################
# Accès à une resource web

function _dumpurl_method_available() {
    # $1=dumpurl_method
    local m="$1"
    if [ "$m" == "curl" ]; then
        progexists curl || [ -n "$__curl_EXISTS__" ]
    elif [ "$m" == "wget" ]; then
        if progexists wget || [ -n "$__wget_EXISTS__" ]; then
            # vérifier la version ce doit être >= 1.9
            wget --version | grep "^GNU Wget" | awk '{
    v = $0
    major = 0
    minor = 0
    if (match(v, /[0-9][0-9]*/) != 0) {
        major = substr(v, RSTART, RLENGTH) + 0
        v = substr(v, RSTART + RLENGTH + 1)
    }
    if (match(v, /[0-9][0-9]*/) != 0) {
        minor = substr(v, RSTART, RLENGTH) + 0
        v = substr(v, RSTART + RLENGTH + 1)
    }

    if (major >= 1 && minor >= 9) {
        exit 0
    } else {
        exit 1
    }
}'
        fi
    else
        return 1
    fi
}

function dumpurl_method() {
    if [ -z "$DUMPURL_METHOD" ]; then
        local DUMPURL_METHOD
        if _dumpurl_method_available curl; then
            DUMPURL_METHOD=curl
        elif _dumpurl_method_available wget; then
            DUMPURL_METHOD=wget
        ## XXX telnet non supporté pour le moment
        #elif progexists telnet; then
        #    DUMPURL_METHOD=telnet
        fi
    fi
    echo "$DUMPURL_METHOD"
}

function dumpurl_available() {
    # retourner vrai si dumpurl est disponible (si wget ou curl sont trouvés)
    _dumpurl_method_available "$(dumpurl_method)"
}

function dumpurl() {
    # afficher le résultat du téléchargement de l'url $1, ou une chaine vide si
    # une erreur s'est produite.
    # l'option -m choisit la méthode: GET ou POST (suivi de la chaine à envoyer)
    # l'option -H permet d'ajouter des en-têtes

    local done= http_method=GET postdata headers header url no_proxy
    local -a headers
    while [ -n "$1" ]; do
        case "$1" in
        --)
            shift
            done=1
            ;;

        -m)
            shift
            http_method="$1"
            if [ "$http_method" == "POST" ]; then
                postdata="$2"
                shift
            fi
            ;;

        -H)
            shift
            array_add headers "$1: $2"
            shift
            ;;

        -X)
            no_proxy=1
            ;;

        -*)
            #ewarn "option non reconnue: $1"
            ;;

        *)
            done=1
            ;;
        esac
        [ -n "$done" ] && break
        shift
    done
    url="$1"

    dumpurl_available || return 1
    local dumpurl_method="$(dumpurl_method)"

    if [ "$dumpurl_method" == "curl" ]; then
        curl="curl${no_proxy:+ -x ''}"
        for header in "${headers[@]}"; do
            curl="$curl --header \"$(quote_arg "$header")\""
        done
        if [ "$http_method" == "GET" ]; then
            :
        elif [ "$http_method" == "POST" ]; then
            curl="$curl --data-binary \"$(quote_arg "$postdata")\""
        else
            # méthode inconnue
            return 1
        fi
        curl="$curl -f -s \"$(quote_arg "$url")\""

        eval "$curl"

    elif [ "$dumpurl_method" == "wget" ]; then
        wget="wget${no_proxy:+ --no-proxy}"
        for header in "${headers[@]}"; do
            wget="$wget --header=\"$(quote_arg "$header")\""
        done
        if [ "$http_method" == "GET" ]; then
            :
        elif [ "$http_method" == "POST" ]; then
            wget="$wget --post-data=\"$(quote_arg "$postdata")\""
        else
            # méthode inconnue
            return 1
        fi
        wget="$wget -q -O - \"$(quote_arg "$url")\""

        eval "$wget"

    # XXX pas encore implémenté
    #elif [ "$dumpurl_method" == "telnet" ]; then
    #    return 1

    else
        return 1
    fi
}
