##@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}' } 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 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 [ -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() 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 $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 templatectl_config() { # Obtenir le chemin vers le fichier de configuration pour le répertoire $1 echo "$1/$(basename -- "$TEMPLATECTL_CONFIG")" } 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 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" 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 echo "# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8" >"$1" echo "__template_vars=(" >>"$1" for __t_var in "${TEMPLATE_STATIC_VARS[@]}"; do array_contains TEMPLATE_NOWRITE_VARS "$__t_var" && continue echo_setv "$__t_var=${!__t_var}" >>"$1" done echo ")" >>"$1" echo "__dynamic_vars=(" >>"$1" for __t_var in "${TEMPLATE_DYNAMIC_VARS[@]}"; do array_contains TEMPLATE_NOWRITE_VARS "$__t_var" && continue 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 # 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_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 \ --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 local __tc_config="$__tc_destdir/$(basename -- "$TEMPLATECTL_CONFIG")" 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[@]}" -- "$@" }