##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## Gestion de fichiers modèles
##@cooked nocomments
##@require base
uprovide template
urequire base

function __template_prel_abspath() {
    # afficher le chemin absolu du fichier $1. Si $1 est un chemin relatif, le
    # répertoire de référence pour le calcul du chemin absolu dépend du
    # répertoire courant: si l'on est dans un des sous-répertoires du répertoire
    # de destination $2, calculer le chemin absolu par rapport au répertoire
    # courant. Sinon, l'exprimer par rapport à $2.
    if withinpath "$2" "$(pwd)"; then
        abspath "$1"
    else
        abspath "$1" "$2"
    fi
}
function __template_check_srcdir() {
    [ -n "$1" ] || {
        eerror "Vous devez spécifier le répertoire source"
        return 1
    }
    [ -d "$1" ] || {
        eerror "$1: répertoire source introuvable"
        return 1
    }
    return 0
}
function __template_check_destdir() {
    [ -n "$1" ] || {
        eerror "Vous devez spécifier le répertoire de destination"
        return 1
    }
    [ -d "$1" ] || {
        eerror "$1: répertoire destination introuvable"
        return 1
    }
    return 0
}
function __template_plsort() {
    # Trier par taille du chemin
    awk '{print length($0) ":" $0}' |
    sort -n "$@" |
    awk '{sub(/[0-9]+:/, ""); print}'
}
function __template_search_destdir() {
    # Chercher à partir du répertoire courant si un des répertoires parents
    # s'appelle $1. Retourner vrai si le répertoire a été trouvé.
    local dir="$(pwd)" dirname

    while true; do
        setx dirname=basename -- "$dir"
        if [ "$dir" == / ]; then
            # s'arrêter à la racine
            return 1
        elif [ "$dir" == "$HOME" ]; then
            # s'arrêter au répertoire HOME
            return 1
        elif [ "$dirname" == "$1" ]; then
            echo "$dir"
            return 0
        elif [ "$dirname" == ".$1" ]; then
            echo "$dir"
            return 0
        fi
        setx dir=dirname -- "$dir"
    done
}
function __template_set_destdir() {
    local __destdir="${1:-destdir}" __autocreate="${2:-autocreate}" __name="${3:-template}"
    setv "$__autocreate"
    if [ -z "${!__destdir}" ]; then
        if [ -d "$__name" ]; then
            setv "$__destdir" "$__name"
        elif [ -d ".$__name" ]; then
            setv "$__destdir" ".$__name"
        elif setx "$__destdir" __template_search_destdir "$__name"; then
            estepn "Sélection automatique de $(ppath "${!__destdir}")"
        elif [ -e "$__name" ]; then
            eerror "Vous devez spécifier le répertoire de destination avec -d"
            return 1
        else
            setv "$__destdir" "$__name"
            setv "$__autocreate" 1
        fi
    fi
    setx "$__destdir" abspath "${!__destdir}"
    return 0
}
function __template_set_var() {
    # Mettre à jour la variable $1 avec la valeur $2 en tenant compte de
    # certaines dépendances. Par exemple, si on modifie host, il faut mettre à
    # jour hostname.
    # La variable __TEMPLATE_DEFAULTF_var contient le nom d'une fonction qui
    # retourne la valeur par défaut de la variable. Cette fonction est appelée
    # avec le nom de la variable comme premier argument.
    # La variable __TEMPLATE_UPDATEF_var contient le nom d'une fonction qui met
    # à jour les variables dépendantes. Cette fonction est appelée avec le nom
    # de la variable comme premier argument et sa nouvelle valeur en deuxième
    # argument.
    # Retourner vrai si la valeur a été changée
    local __orig_value="${!1}"
    local __defaultf="__TEMPLATE_DEFAULTF_$1" __updatef="__TEMPLATE_UPDATEF_$1"

    array_contains TEMPLATE_DYNAMIC_VARS "$1" || array_addu TEMPLATE_STATIC_VARS "$1"
    [ "$3" == writable ] && array_del TEMPLATE_NOWRITE_VARS

    [ -z "$2" -a -n "${!__defaultf}" ] && set -- "$1" "$("${!__defaultf}" "$1")"
    setv "$1" "$2"
    [ -n "${!__updatef}" ] && "${!__updatef}" "$1" "$2"
    [ "$2" != "$__orig_value" ]
}

# liste des variables qu'il faut remplacer dans les fichiers sources
TEMPLATE_STATIC_VARS=()
function __template_setup_tmpfile() {
    if [ ${#TEMPLATE_STATIC_VARS[*]} -gt 0 ]; then
        # S'il y a des variables à remplacer, préparer un fichier temporaire
        ac_set_tmpfile tmpfile
    fi
}
function __template_clean_tmpfile() {
    if [ ${#TEMPLATE_STATIC_VARS[*]} -gt 0 ]; then
        ac_clean "$tmpfile"
    fi
}
function __template_fillvars() {
    # Pour chacune des variables VAR de TEMPLATE_STATIC_VARS, remplacer dans le
    # fichier $1 les occurences de @@VAR@@ par la valeur $VAR
    # Afficher le chemin vers le fichier temporaire qui contient le fichier
    # modifié. Si $2 est spécifié, prendre ce chemin comme fichier temporaire.
    # Sinon, créer un nouveau fichier temporaire. Si le fichier ne contient
    # aucune occurence de variable, afficher le chemin original $1.
    [ ${#TEMPLATE_STATIC_VARS[*]} -eq 0 ] && { echo "$1"; return; }

    # chercher si le fichier contient au moins un des tags de
    # TEMPLATE_STATIC_VARS
    local __var __tag __found
    for __var in "${TEMPLATE_STATIC_VARS[@]}"; do
        __tag="@@${__var}@@"
        if quietgrep "$__tag" "$1"; then
            __found=1
            break
        fi
    done
    [ -n "$__found" ] || { echo "$1"; return; }

    # construire le script sed pour le remplacement des variables
    local __script __first=1 __repl
    for __var in "${TEMPLATE_STATIC_VARS[@]}"; do
        [ -n "$__first" ] || __script="$__script"$'\n'
        __first=
        __script="${__script}s/@@${__var}@@/$(qseds "${!__var}")/g"
    done

    sed "$__script" <"$1" >"$2"
    echo "$2"
}

function template_list() {
# Soit $N le séparateur --, lister les fichiers des répertoires sources
# $2..$(N-1) qui seraient fusionnés avec template_merge() ou supprimés avec
# template_unmerge() du répertoire destination $1. Si des chemins sont spécifiés
# avec les arguments $(N+1)..@, ne traiter que les fichiers qui correspondent à
# ces spécifications. Exemple:
#     template_list destdir srcdirs... -- specs...
    local destdir="${1:-.}"; shift
    __template_check_destdir "$destdir" || return 1
    setx destdir=abspath "$destdir"

    local -a srcdirs; local srcdir
    while [ $# -gt 0 ]; do
        srcdir="$1"; shift
        [ "$srcdir" == -- ] && break
        __template_check_srcdir "$srcdir" || return 1
        setx srcdir=abspath "$srcdir"
        array_add srcdirs "$srcdir"
    done

    local rel2pwd
    withinpath "$destdir" "$(pwd)" && rel2pwd=1

    if [ $# -eq 0 ]; then
        [ -n "$rel2pwd" ] && set -- . || set -- "$destdir"
    fi

    local tmpfile; __template_setup_tmpfile

    local spec srcspec src content dest list
    local -a srcfiles
    for spec in "$@"; do
        setx srcspec=__template_prel_abspath "$spec" "$destdir"
        withinpath "$destdir" "$srcspec" || continue
        srcspec="${srcspec#$destdir}"

        for srcdir in "${srcdirs[@]}"; do
            [ -e "$srcdir$srcspec" ] || continue
            array_from_lines srcfiles "$(find "$srcdir$srcspec" -type f)"
            for src in "${srcfiles[@]}"; do
                setx content=__template_fillvars "$src" "$tmpfile"
                dest="$destdir/${src#$srcdir/}"

                list=
                if [ -L "$dest" ]; then
                    :
                elif [ -f "$dest" ]; then
                    testsame "$content" "$dest" && list=1
                else
                    list=1
                fi
                [ -n "$list" ] || continue

                if [ -n "$rel2pwd" ]; then
                    relpath "$dest"
                else
                    echo "${src#$srcdir/}"
                fi
            done
        done
    done | csort -u

    __template_clean_tmpfile
}

function template_merge() {
# Soit $N le séparateur --, copier dans le répertoire destination $1 les
# fichiers des répertoires sources $2..$(N-1) correspondant aux spécifications
# $(N+1)..@, si ces fichiers n'ont pas été modifiés dans le répertoire de
# destination.
# Les fichiers sources ayant l'extension .template sont ignorés par défaut, sauf
# s'ils sonts demandés explicitement. Exemple:
#     template_merge destdir srcdirs... -- specs...
    local destdir="${1:-.}"; shift
    __template_check_destdir "$destdir" || return 1
    setx destdir=abspath "$destdir"

    local -a srcdirs; local srcdir
    while [ $# -gt 0 ]; do
        srcdir="$1"; shift
        [ "$srcdir" == -- ] && break
        __template_check_srcdir "$srcdir" || return 1
        setx srcdir=abspath "$srcdir"
        array_add srcdirs "$srcdir"
    done

    [ $# -gt 0 ] || set -- "$destdir"

    local tmpfile; __template_setup_tmpfile

    local spec template srcspec src content dest
    local srcfiles
    for spec in "$@"; do
        setb template=[ "${spec%.template}" != "$spec" ]
        setx srcspec=__template_prel_abspath "$spec"
        if ! withinpath "$destdir" "$srcspec"; then
            ewarn "$spec: fichier ignoré"
            continue
        fi
        srcspec="${srcspec#$destdir}"

        for srcdir in "${srcdirs[@]}"; do
            if [ -z "$template" -a ! -e "$srcdir$srcspec" -a -e "$srcdir$srcspec.template" ]; then
                srcspec="$srcspec.template"
                template=1
            fi
            [ -e "$srcdir$srcspec" ] || continue
            ebegin "$(basename -- "$srcdir") --> $(ppath "$destdir$srcspec")"
            s=0
            if [ -n "$template" ]; then
                array_from_lines srcfiles "$(find "$srcdir$srcspec" -type f)"
            else
                array_from_lines srcfiles "$(find "$srcdir$srcspec" -type f | grep -v '\.template$')"
            fi
            for src in "${srcfiles[@]}"; do
                setx content=__template_fillvars "$src" "$tmpfile"
                dest="$destdir/${src#$srcdir/}"
                [ -n "$template" ] && dest="${dest%.template}"

                if [ -L "$dest" ]; then
                    edotw 0 "LOCALLY MODIFIED: $(ppath "$dest")"
                elif [ -f "$dest" ]; then
                    if testsame "$content" "$dest"; then
                        show_debug && edot 0 "ALREADY COPIED: $(ppath "$dest")"
                    else
                        edotw 0 "LOCALLY MODIFIED: $(ppath "$dest")"
                    fi
                else
                    mkdirof "$dest"
                    cp "$content" "$dest"; r=$?
                    edot $r "COPY: $(ppath "$dest")"
                    [ $r -eq 0 ] || s=$r
                fi
            done
            eend $s
        done
    done

    __template_clean_tmpfile
}

function template_unmerge() {
# Soit $N le séparateur --, supprimer du répertoire destination $1 les fichiers
# provenant des répertoires sources $2..$(N-1) et qui n'ont pas été modifiés. Si
# des chemins sont spécifiés avec les arguments $(N+1)..@, ne traiter que les
# fichiers qui correspondent à ces spécifications. Exemple:
#     template_unmerge destdir srcdirs... -- specs...
    local destdir="${1:-.}"; shift
    __template_check_destdir "$destdir" || return 1
    setx destdir=abspath "$destdir"

    local -a srcdirs; local srcdir
    while [ $# -gt 0 ]; do
        srcdir="$1"; shift
        [ "$srcdir" == -- ] && break
        __template_check_srcdir "$srcdir" || return 1
        setx srcdir=abspath "$srcdir"
        array_add srcdirs "$srcdir"
    done

    [ $# -gt 0 ] || set -- "$destdir"

    local tmpfile; __template_setup_tmpfile

    local spec srcspec src content dest
    local srcfiles
    for spec in "$@"; do
        setx srcspec=__template_prel_abspath "$spec"
        if ! withinpath "$destdir" "$srcspec"; then
            ewarn "$spec: fichier ignoré"
            continue
        fi
        srcspec="${srcspec#$destdir}"

        for srcdir in "${srcdirs[@]}"; do
            [ -e "$srcdir$srcspec" ] || continue
            s=0
            ebegin "$(ppath "$destdir$srcspec")"
            array_from_lines files "$(find "$srcdir$srcspec" -type f)"
            for src in "${files[@]}"; do
                setx content=__template_fillvars "$src" "$tmpfile"
                dest="$destdir/${src#$srcdir/}"

                if [ -L "$dest" ]; then
                    edotw 0 "LOCALLY MODIFIED: $(ppath "$dest")"
                elif [ -f "$dest" ]; then
                    if testsame "$content" "$dest"; then
                        rm -f "$dest"; r=$?
                        edot $r "REMOVE: $(ppath "$dest")"
                        [ $r -eq 0 ] || s=$r
                    else
                        edotw 0 "LOCALLY MODIFIED: $(ppath "$dest")"
                    fi
                else
                    show_debug && edot 0 "ALREADY REMOVED: $(ppath "$dest")"
                fi
            done
            eend $s
        done
    done

    __template_clean_tmpfile
}

function template_cleandest() {
# Supprimer dans le répertoire de destination $1 tous les répertoires vides.
# Cette fonction est habituellement utilisée après template_unmerge()
# Ignorer les chemins qui contiennent .git/ et .svn/
    local -a dirs
    [ -d "$1" ] || return 1
    array_from_lines dirs "$(cd "$1"; find . -type d | grep -v .git/ | grep -v .svn/ | __template_plsort -r)"
    array_del dirs .
    (cd "$1"; rmdir "${dirs[@]}" 2>/dev/null)
}

function template_diff() {
# Afficher les différences entre les fichiers du répertoire de destination $1 et
# les fichiers des répertoires sources $2..@
    local destdir="${1:-.}"; shift
    __template_check_destdir "$destdir" || return 1
    setx destdir=abspath "$destdir"

    local -a srcdirs; local srcdir
    while [ $# -gt 0 ]; do
        srcdir="$1"; shift
        [ "$srcdir" == -- ] && break
        __template_check_srcdir "$srcdir" || return 1
        setx srcdir=abspath "$srcdir"
        array_add srcdirs "$srcdir"
    done

    local tmpfile; __template_setup_tmpfile

    local src content dest
    local -a srcfiles
    for srcdir in "${srcdirs[@]}"; do
        array_from_lines srcfiles "$(find "$srcdir" -type f)"
        for src in "${srcfiles[@]}"; do
            setx content=__template_fillvars "$src" "$tmpfile"
            dest="$destdir/${src#$srcdir/}"
            if [ -f "$dest" ] && testdiff "$content" "$dest"; then
                diff -uwB "$content" "$dest"
            fi
        done
    done | page_maybe -S

    __template_clean_tmpfile
}

function template_srcdir() {
# Obtenir le chemin vers le répertoire source de templates $1, situé dans
# ULIBDIR/templates
    urequire ulib
    if [ -n "$ULIBDIR" ]; then
        echo "$ULIBDIR/templates/$1"
    else
        abspath "templates/$1"
    fi
}

function template_build_vars() {
# Initialiser les tableaux $1 et $2 avec la description des variables $3..@
# les descriptions sont de la forme var[:depvars,...]=desc
# $1 reçoit les noms (depvars... var) parce que les variables dépendantes
# doivent toujours être placées AVANT la variable maitre
# $2 reçoit les noms (depvars...)
# pour chaque description, une variable __TEMPLATE_DESC_var est créée avec
# la valeur desc
    local __t_destvs="$1"; shift
    local __t_destnw="$1"; shift
    local -a __t_depvars
    local __t_vardesc __t_var __t_depvar __t_desc
    for __t_vardesc in "$@"; do
        splitvar "$__t_vardesc" __t_depvar __t_desc
        splitpair "$__t_depvar" __t_var __t_depvar
        array_split __t_depvars "$__t_depvar" ,
        for __t_depvar in "${__t_depvars[@]}"; do
            array_addu "$__t_destvs" "$__t_depvar"
            [ -n "$__t_destnw" ] && array_addu "$__t_destnw" "$__t_depvar"
        done
        array_del "$__t_destvs" "$__t_var"
        array_add "$__t_destvs" "$__t_var"
        eval "__TEMPLATE_DESC_$__t_var=\"\$__t_desc\""
    done
}

function templatectl_config() {
# Obtenir le chemin vers le fichier de configuration pour le répertoire $1 Si
# l'un des fichiers CONFIG.conf ou .CONFIG existent déjà, prendre ceux-là.
# Sinon, si $2==nohideconfig, utiliser le nom CONFIG.conf, sinon utiliser
# .CONFIG
    local config="$(basename -- "$TEMPLATECTL_CONFIG")"
    if [ -f "$1/$config.conf" ]; then echo "$1/$config.conf"
    elif [ -f "$1/.$config" ]; then echo "$1/.$config"
    elif [ "$2" == nohideconfig ]; then echo "$1/$config.conf"
    else echo "$1/.$config"
    fi
}

function templatectl_loadvars() {
# Charger les valeurs des variables depuis le fichier $1
# Les variables suivantes doivent être définies:
# - Le tableau TEMPLATECTL_DEFAULTS permet de donner une valeur par défaut aux
#   variables mentionnées dans TEMPLATE_STATIC_VARS. C'est une liste de valeurs
#   de la forme 'name=value'
# - Le tableau TEMPLATECTL_VARS contient des variables supplémentaires
#   spécifiées par l'utilisateur. C'est une liste de valeurs de la forme
#   'name=value'
# - TEMPLATE_STATIC_VARS doit contenir une liste de noms de variables qui
#   peuvent être remplacés dans les fichiers de template.
# - TEMPLATE_DYNAMIC_VARS contient une liste de noms de variables valides, mais
#   qui ne doivent pas être remplacés, en effet, ils sont utilisés pour le
#   déploiement des fichiers.
# - TEMPLATE_NOWRITE_VARS contient une liste de noms de variables qui ne
#   devraient pas être écrits dans le fichier des variables, sauf si elles
#   reçoivent une valeur explicite de la part de l'utilisateur. Ce tableau est
#   mis à jour lors de l'analyse du tableau TEMPLATECTL_VARS
# - TEMPLATE_USER_VARS contient une liste de noms de tableaux qui sont définis
#   en plus et qui peuvent être utilisés par des scripts annexes
    local -a __template_vars __dynamic_vars
    local __t_var __t_name __t_value

    array_copy __template_vars TEMPLATECTL_DEFAULTS
    configdir="$(dirname -- "$1")"
    [ -f "$1" ] && source "$1"

    for __t_var in "${__template_vars[@]}"; do
        splitvar "$__t_var" __t_name __t_value
        __template_set_var "$__t_name" "$__t_value"
    done
    array_contains TEMPLATE_STATIC_VARS configdir && __template_set_var configdir "$configdir"
    for __t_var in "${__dynamic_vars[@]}"; do
        splitvar "$__t_var" __t_name __t_value
        array_del TEMPLATE_STATIC_VARS "$__t_name"
        array_addu TEMPLATE_DYNAMIC_VARS "$__t_name"
        __template_set_var "$__t_name" "$__t_value"
    done
    array_contains TEMPLATE_DYNAMIC_VARS configdir && __template_set_var configdir "$configdir"

    is_defined TEMPLATE_USER_VARS || TEMPLATE_USER_VARS=()
    for __t_var in "${TEMPLATE_USER_VARS[@]}"; do
        is_defined "$__t_var" || array_new "$__t_var"
    done

    local __t_modified=1
    for __t_var in "${TEMPLATECTL_VARS[@]}"; do
        splitvar "$__t_var" __t_name __t_value
        __template_set_var "$__t_name" "$__t_value" writable && __t_modified=0
    done
    return $__t_modified
}

function templatectl_writevars() {
# Ecrire les variables dans le fichier $1
    local __t_var __t_desc
    echo "# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8" >"$1"
    for __t_var in "${TEMPLATE_USER_VARS[@]}"; do
        __t_desc="__TEMPLATE_DESC_$__t_var"
        [ -n "${!__t_desc}" ] && echo "# ${!__t_desc}" >>"$1"
        if is_array "$__t_var"; then
            set_array_cmd "$__t_var" >>"$1"
        else
            echo_setv "$__t_var" "${!__t_var}" >>"$1"
        fi
    done
    if [ ${#TEMPLATE_DYNAMIC_VARS[*]} -gt 0 ]; then
        echo "__dynamic_vars=(" >>"$1"
        for __t_var in "${TEMPLATE_DYNAMIC_VARS[@]}"; do
            array_contains TEMPLATE_NOWRITE_VARS "$__t_var" && continue
            __t_desc="__TEMPLATE_DESC_$__t_var"
            [ -n "${!__t_desc}" ] && echo "# ${!__t_desc}" >>"$1"
            echo_setv "$__t_var" "${!__t_var}" >>"$1"
        done
        echo ")" >>"$1"
    fi
    echo "__template_vars=(# ne pas modifier" >>"$1"
    for __t_var in "${TEMPLATE_STATIC_VARS[@]}"; do
        array_contains TEMPLATE_NOWRITE_VARS "$__t_var" && continue
        __t_desc="__TEMPLATE_DESC_$__t_var"
        [ -n "${!__t_desc}" ] && echo "# ${!__t_desc}" >>"$1"
        echo_setv "$__t_var" "${!__t_var}" >>"$1"
    done
    echo ")" >>"$1"
}

function templatectl_list_vars() {
# Afficher les valeurs des variables
    local __var
    echo "# template vars"
    for __var in "${TEMPLATE_STATIC_VARS[@]}"; do
        echo_setv "$__var=${!__var}"
    done
    if [ ${#TEMPLATE_DYNAMIC_VARS[*]} -gt 0 ]; then
        echo "# dynamic vars"
        for __var in "${TEMPLATE_DYNAMIC_VARS[@]}"; do
            echo_setv "$__var=${!__var}"
        done
    fi
}

__TEMPLATECTL_HELP="\
-d, --destdir DESTDIR
    Spécifier le répertoire local dans lequel copier les fichiers templates
-s, --srcdir SRCDIR
    Ajouter un répertoire à la liste des répertoires sources pour les templates.
    Si ce n'est pas un chemin, c'est le nom d'un répertoire dans le répertoires
    des templates par défaut.
-l, --list
    Lister les templates disponibles.
-m, --merge
    Copier les templates spécifiés dans le répertoire local s'il n'y existent
    pas déjà. Les templates ayant l'extension '.template' doivent être demandés
    explicitement. Sinon, ils sont ignorés.
-z, --unmerge
    Supprimer les fichiers du répertoire local s'ils n'ont pas été modifiés par
    rapport aux templates.
-C, --clean
    Supprimer les répertoires vides dans le répertoire local. Peut être utile
    après -z
-g, --diff
    Afficher les différences entre les templates et les fichiers du répertoire
    local.
-L, --list-vars
    Afficher pour information les valeurs par défaut des variables de template.
-v, --var NAME=VALUE
    Spécifier la valeur d'une variable. Toutes les variables sont autorisées,
    sauf celles qui figurent dans la liste des variables dynamiques.
-w, --write-vars
    Ecrire dans le fichier $TEMPLATECTL_CONFIG les valeurs des variables, ce qui
    permet après édition du fichier d'éviter de les spécifier à chaque fois avec
    l'option -v
    Le fichier n'est pas écrasé s'il existe déjà."
function __display_templatectl_help() { uecho "$__TEMPLATECTL_HELP"; }
function templatectl() {
# Fonction de haut niveau qui facilite l'utilisation des fonctions template_*
# définir la fonction __display_templatectl_help() pour l'affichage de l'aide
# - Le tableau TEMPLATECTL_SRCDIRS doit contenir la liste des répertoires
#   sources pour les templates. Alternativement, il est possible de définir la
#   variable TEMPLATECTL_SRCDIR s'il n'y a qu'un seul répertoire source pour le
#   template
# - TEMPLATECTL_CONFIG est le nom de base du fichier à partir duquel sont
#   chargées les variables et dans lequel sont écrites les variables avec
#   l'option --write-vars
#   Si le nom de base est CONFIG, le fichier s'appelera .CONFIG si l'option
#   --hide-config est utilisée (par défaut) ou CONFIG.conf si l'option
#   --no-hide-config est utilisée
# Les variables de template_loadvars() sont aussi prises en compte
    local -a __tc_srcdirs; local __tc_srcdir
    if [ ${#TEMPLATECTL_SRCDIRS[*]} -gt 0 ]; then
        :
    elif [ -n "$TEMPLATECTL_SRCDIR" ]; then
        local -a TEMPLATECTL_SRCDIRS
        TEMPLATECTL_SRCDIRS=("$TEMPLATECTL_SRCDIR")
    fi
    for __tc_srcdir in "${TEMPLATECTL_SRCDIRS[@]}"; do
        if ! withpath "$__tc_srcdir"; then
            __tc_srcdir="$(template_srcdir "$__tc_srcdir")"
        fi
        array_add __tc_srcdirs "$__tc_srcdir"
    done

    local -a __tc_args
    local __tc_auto=1 __tc_load_vars=1
    local __tc_destdir __tc_config __tc_nohideconfig
    local __tc_list __tc_merge __tc_unmerge __tc_clean __tc_diff __tc_list_vars __tc_write_vars
    parse_opts "${PRETTYOPTS[@]}" \
        --help '$exit_with __display_templatectl_help' \
        -d:,--destdir: __tc_destdir= \
        -s:,--srcdir: __tc_srcdirs \
        --config: __tc_config= \
        --no-hide-config __tc_nohideconfig=1 \
        --hide-config __tc_nohideconfig= \
        --load-vars __tc_load_vars=1 \
        --no-load-vars __tc_load_vars= \
        --noop __tc_auto= \
        -l,--list '$__tc_list=1; __tc_auto=' \
        -m,--merge '$__tc_merge=1; __tc_auto=' \
        -z,--unmerge '$__tc_unmerge=1; __tc_auto=' \
        -C,--clean '$__tc_clean=1; __tc_auto=' \
        -g,--diff '$__tc_diff=1; __tc_auto=' \
        -L,--list-vars '$__tc_list_vars=1; __tc_auto=' \
        -v:,--var: TEMPLATECTL_VARS \
        -w,--write-vars __tc_write_vars=1 \
        @ __tc_args -- "$@" && set -- "${__tc_args[@]}" || { eerror "$__tc_args"; return 1; }

    [ -n "$__tc_destdir" ] || __tc_destdir=.
    __template_check_destdir "$__tc_destdir" || return 1

    array_isempty __tc_srcdirs && return 1
    for __tc_srcdir in "${__tc_srcdirs[@]}"; do
        __template_check_srcdir "$__tc_srcdir" || return 1
    done

    [ -n "$__tc_config" ] || __tc_config="$(templatectl_config "$destdir" ${__tc_nohideconfig:+nohideconfig})"
    if [ -n "$__tc_load_vars" ]; then
        templatectl_loadvars "$__tc_config"
    fi
    if [ -n "$__tc_write_vars" ]; then
        if [ -f "$__tc_config" ]; then
            ewarn "Refus d'écraser le fichier existant $(ppath "$__tc_config")"
        else
            templatectl_writevars "$__tc_config"
        fi
    fi

    [ -n "$__tc_auto" ] && __tc_list=1
    [ -n "$__tc_list_vars" ] && templatectl_list_vars
    [ -n "$__tc_list" ] && template_list "$__tc_destdir" "${__tc_srcdirs[@]}" -- "$@"
    [ -n "$__tc_merge" ] && template_merge "$__tc_destdir" "${__tc_srcdirs[@]}" -- "$@"
    [ -n "$__tc_unmerge" ] && template_unmerge "$__tc_destdir" "${__tc_srcdirs[@]}" -- "$@"
    [ -n "$__tc_clean" ] && template_cleandest "$__tc_destdir" "$@"
    [ -n "$__tc_diff" ] && template_diff "$__tc_destdir" "${__tc_srcdirs[@]}" -- "$@"
}
