##@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 on est dans le répertoire de
    # destination $2, sinon l'exprimer par rapport au répertoire de destination
    # $2 si c'est un chemin relatif.
    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}'
}

# liste des variables qu'il faut remplacer dans les fichiers sources
TEMPLATE_VARS=()
function __template_setup_tmpfile() {
    if [ ${#TEMPLATE_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_VARS[*]} -gt 0 ]; then
        ac_clean "$tmpfile"
    fi
}
function __template_fillvars() {
    # Pour chacune des variables VAR de TEMPLATE_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_VARS[*]} -eq 0 ] && { echo "$1"; return; }

    # chercher si le fichier contient au moins un des tags de TEMPLATE_VARS
    local __var __tag __found
    for __var in "${TEMPLATE_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
    for __var in "${TEMPLATE_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() {
# Lister les fichiers du répertoire source $1 qui seraient fusionnées avec
# template_merge() ou supprimés avec template_unmerge() du répertoire
# destination $2. Si des chemins sont spécifiés avec les arguments $3..@, ne
# traiter que les fichiers qui correspondent à ces spécifications
    local srcdir="$1"; shift
    __template_check_srcdir "$srcdir" || return 1
    setx srcdir=abspath "$srcdir"

    local destdir="${1:-.}"; shift
    __template_check_destdir "$destdir" || return 1
    setx destdir=abspath "$destdir"

    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}"

        [ -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 | csort -u

    __template_clean_tmpfile
}

function template_merge() {
# Copier dans le répertoire destination $2 tous les fichiers provenant du
# répertoire source $1 correspondant aux spécifications $3..@, 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.
    local srcdir="$1"; shift
    __template_check_srcdir "$srcdir" || return 1
    setx srcdir=abspath "$srcdir"

    local destdir="${1:-.}"; shift
    __template_check_destdir "$destdir" || return 1
    setx destdir=abspath "$destdir"

    [ $# -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}"

        [ -e "$srcdir$srcspec" ] || continue
        ebegin "$(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

    __template_clean_tmpfile
}

function template_unmerge() {
# Supprimer du répertoire de destination $2 tous les fichiers provenant du
# répertoire source $1 et qui n'ont pas été modifiés. Si des chemins sont
# spécifiés avec les arguments $3..@, ne traiter que les fichiers qui
# correspondent à ces spécifications.
    local srcdir="$1"; shift
    __template_check_srcdir "$srcdir" || return 1
    setx srcdir=abspath "$srcdir"

    local destdir="${1:-.}"; shift
    __template_check_destdir "$destdir" || return 1
    setx destdir=abspath "$destdir"

    [ $# -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}"

        [ -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

    __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()
    local -a dirs
    [ -d "$1" ] || return 1
    array_from_lines dirs "$(cd "$1"; find . -type d | __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 $2 et
# les fichiers du répertoire source $1
    local srcdir="$1"; shift
    __template_check_srcdir "$srcdir" || return 1
    setx srcdir=abspath "$srcdir"

    local destdir="${1:-.}"; shift
    __template_check_destdir "$destdir" || return 1
    setx destdir=abspath "$destdir"

    local tmpfile; __template_setup_tmpfile

    local src content dest
    local -a srcfiles
    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 | page_maybe -S

    __template_clean_tmpfile
}

function templatesrc() {
    # 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 __templatectl_display_help() { :; }
__TEMPLATECTL_SUPPLOPTS=()
function __templatectl_parseopts() {
    parse_opts "${PRETTYOPTS[@]}" \
        --help '$exit_with __templatectl_display_help' \
        -s:,--srcdir: __templatectl_srcdir= \
        -d:,--destdir: __templatectl_destdir= \
        -l,--list '$__templatectl_list=1; __templatectl_opt=1' \
        -m,--merge '$__templatectl_merge=1; __templatectl_opt=1' \
        -z,--unmerge '$__templatectl_unmerge=1; __templatectl_opt=1' \
        -C,--clean '$__templatectl_clean=1; __templatectl_opt=1' \
        -g,--diff '$__templatectl_diff=1; __templatectl_opt=1' \
        "${__TEMPLATECTL_SUPPLOPTS[@]}" \
        @ args -- "$@"
}

function __templatectl_do() {
    __template_check_srcdir "$__templatectl_srcdir" || return 1
    [ -n "$__templatectl_destdir" ] || __templatectl_destdir=.
    __template_check_destdir "$__templatectl_destdir" || return 1
    [ -n "$__templatectl_auto" ] && __templatectl_list=1

    [ -n "$__templatectl_list" ] && template_list "$__templatectl_srcdir" "$__templatectl_destdir" "$@"
    [ -n "$__templatectl_merge" ] && template_merge "$__templatectl_srcdir" "$__templatectl_destdir" "$@"
    [ -n "$__templatectl_unmerge" ] && template_unmerge "$__templatectl_srcdir" "$__templatectl_destdir" "$@"
    [ -n "$__templatectl_clean" ] && template_cleandest "$__templatectl_destdir" "$@"
    [ -n "$__templatectl_diff" ] && template_diff "$__templatectl_srcdir" "$__templatectl_destdir" "$@"
    return 0
}

function templatectl() {
# fonction de haut niveau qui facilite l'utilisation des fonctions template_*
# définir la fonction __templatectl_display_help() pour l'affichage de l'aide
    local -a args
    local __templatectl_list __templatectl_merge __templatectl_unmerge __templatectl_clean __templatectl_diff __templatectl_opt __templatectl_auto
    local __templatectl_destdir

    __templatectl_parseopts "$@" && set -- "${args[@]}" || { eerror "$args"; return 1; }
    [ -z "$__templatectl_opt" ] && __templatectl_auto=1
    __templatectl_do "$@"
}
