385 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			385 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
| ##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
 | |
| ## Gestion de fichiers de configuration et de répertoires de configuration
 | |
| ##@cooked nocomments
 | |
| uprovide multiconf
 | |
| urequire install
 | |
| 
 | |
| function conf_local() {
 | |
|     # afficher les commandes pour définir comme locales les variables utilisées
 | |
|     # par les fonctions conf_*
 | |
|     # cela permet d'utiliser ces fonctions à l'intérieur d'autres fonctions sans
 | |
|     # polluer l'espace de nom
 | |
|     echo "local -a __CONF_DESCS __CONF_ARRAY_VARS __CONF_PATH_VARS"
 | |
| }
 | |
| 
 | |
| function conf_auto() {
 | |
|     # charger la configuration pour l'outil $1 avec les variables $2..@
 | |
|     # conf_init n'est appelé que si des variables sont spécifiées, ce qui permet
 | |
|     # d'appeler conf_init au préalable si une configuration spécifique doit être
 | |
|     # faite.
 | |
|     # Ainsi:
 | |
|     #     conf_auto NAME VARS...
 | |
|     # est équivalent à:
 | |
|     #     conf_init VARS...
 | |
|     #     conf_auto NAME
 | |
|     # est équivalent à:
 | |
|     #     conf_init VARS...
 | |
|     #     conf_find_files __CONF_FILES ~/etc/default/NAME ~/etc/NAME.d/*.conf
 | |
|     #     conf_upgrade ~/etc/default/NAME
 | |
|     #     conf_load_files "${__CONF_FILES[@]}"
 | |
|     # Pour supporter les scénarii où les fichiers de configuration sont ailleurs
 | |
|     # que dans ~/etc/default, l'argument NAME peut être un chemin:
 | |
|     #    conf_auto PATH/TO/NAME VARS...
 | |
|     # est équivalent à:
 | |
|     #     conf_init VARS...
 | |
|     #     conf_find_files __CONF_FILES PATH/TO/NAME.conf PATH/TO/NAME.d/*.conf
 | |
|     #     conf_upgrade PATH/TO/NAME.conf
 | |
|     #     conf_load_files "${__CONF_FILES[@]}"
 | |
|     local __name="$1"; shift
 | |
|     [ -n "$__name" ] || return 1
 | |
|     [ $# -gt 0 ] && conf_init "$@"
 | |
|     local -a __CONF_FILES
 | |
|     if [[ "$__name" == */* ]]; then
 | |
|         conf_find_files __CONFS_FILES "$__name.conf" "$__name.d/*.conf"
 | |
|         [ $# -gt 0 ] && conf_upgrade "$__name.conf"
 | |
|     else
 | |
|         conf_find_files __CONFS_FILES "$HOME/etc/default/$__name" "$HOME/etc/$__name.d/*.conf"
 | |
|         [ $# -gt 0 ] && conf_upgrade "$HOME/etc/default/$__name"
 | |
|     fi
 | |
|     conf_load_files "${__CONFS_FILES[@]}"
 | |
| }
 | |
| 
 | |
| function conf_init() {
 | |
|     # définir les variables attendues lors du chargement des fichiers de
 | |
|     # configuration par conf_load_files()
 | |
|     # Si cette fonction n'a pas d'argument, le contenu du tableau CONFIG s'il
 | |
|     # est existe est utilisé
 | |
|     # par défaut, les variables sont en mode scalaire: la définition d'une
 | |
|     # variable écrase la valeur précédente. Avec l'option -a les variables sont
 | |
|     # en mode tableau: les nouvelles valeurs sont rajoutées à la fin du tableau.
 | |
|     # Avec l'option -p les variables sont en mode chemin: les nouvelles valeurs
 | |
|     # sont ajoutées si elles n'existe pas déjà avec le séparateur ':'
 | |
|     # dans l'exemple suivant:
 | |
|     #     conf_init NAME VALUE -a SRCDIRS DESTDIRS -p LIBPATH
 | |
|     # NAME et VALUE sont scalaires, SRCDIRS et DESTDIRS sont des tableaux et
 | |
|     # LIBPATH est de type chemin
 | |
|     # Les variables scalaires et chemin sont initialisées à la valeur vide ou à
 | |
|     # la valeur spécifiée e.g.:
 | |
|     #     conf_init VAR=value MYPATH=a:b:c
 | |
|     # Les variables tableaux sont initialisées à la valeur vide sauf si le nom
 | |
|     # est suivi de '=' auquel cas la valeur actuelle est gardée, e.g.
 | |
|     #     VS=(a b c); WS=(x y z)
 | |
|     #     conf_init -a VS WS=
 | |
|     #     echo_seta2 VS        # VS=()
 | |
|     #     echo_seta2 WS        # WS=(x y z)
 | |
|     # Dans le cas des variables tableaux, la valeur après '=' est toujours
 | |
|     # ignorée. Pour simplifier la lecture, on peut rajouter une valeur marqueur
 | |
|     # comme 'current' ou 'actual', e.g
 | |
|     #     conf_init -a VS=CurrentValues
 | |
|     # L'option -s permet de revenir au mode scalaire
 | |
| 
 | |
|     # Note: il est possible d'associer une description à chaque variable ainsi
 | |
|     # qu'un en-tête, ce qui permet de construire le fichier de configuration ou
 | |
|     # de mettre à jour un fichier existant avec conf_upgrade(). Par exemple, les
 | |
|     # commandes suivantes:
 | |
|     #     CONFIG=(
 | |
|     #         "# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8"
 | |
|     #         "# configurer l'application"
 | |
|     #         -s
 | |
|     #         "NAME=payet//nom de l'administrateur"
 | |
|     #         "MAIL=admin@host.tld//mail de contact"
 | |
|     #         -a
 | |
|     #         "HOSTS//hôtes autorisés à se connecter"
 | |
|     #     )
 | |
|     #     conf_init
 | |
|     # permettent de générer automatiquement le fichier de configuration suivant:
 | |
|     #     # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
 | |
|     #     # configurer l'application
 | |
|     #
 | |
|     #     # nom de l'administrateur
 | |
|     #     #NAME=payet
 | |
|     #
 | |
|     #     # mail de contact
 | |
|     #     #MAIL=admin@host.tld
 | |
|     #
 | |
|     #     # hôtes autorisés à se connecter
 | |
|     #     #HOSTS=()
 | |
|     __CONF_DESCS=()
 | |
|     __CONF_ARRAY_VARS=()
 | |
|     __CONF_PATH_VARS=()
 | |
|     local __type=scalar __initial=1 __prefix __var __desc
 | |
|     [ $# -eq 0 ] && is_array CONFIG && set -- "${CONFIG[@]}"
 | |
|     while [ $# -gt 0 ]; do
 | |
|         if [ -n "$__initial" ]; then
 | |
|             if [ "${1:0:1}" == "#" ]; then
 | |
|                 [ ${#__prefix} -gt 0 ] && __prefix="$__prefix"$'\n'
 | |
|                 __prefix="$__prefix$1"
 | |
|                 shift
 | |
|                 continue
 | |
|             else
 | |
|                 [ -n "$__prefix" ] && array_add __CONF_DESCS "$__prefix"
 | |
|                 __initial=
 | |
|             fi
 | |
|         fi
 | |
|         case "$1" in
 | |
|         -a|--array) __type=array;;
 | |
|         -p|--path) __type=path;;
 | |
|         -s|--scalar) __type=scalar;;
 | |
|         *)
 | |
|             array_add __CONF_DESCS "$1"
 | |
|             splitfsep "$1" // __var __desc
 | |
|             case "$__type" in
 | |
|             array)
 | |
|                 [ "${__var%%=*}" == "$__var" ] && eval "${__var%%=*}=()"
 | |
|                 array_addu __CONF_ARRAY_VARS "${__var%%=*}"
 | |
|                 array_del __CONF_PATH_VARS "${__var%%=*}"
 | |
|                 ;;
 | |
|             path)
 | |
|                 setv "$__var"
 | |
|                 array_addu __CONF_PATH_VARS "${__var%%=*}"
 | |
|                 array_del __CONF_ARRAY_VARS "${__var%%=*}"
 | |
|                 ;;
 | |
|             scalar)
 | |
|                 setv "$__var"
 | |
|                 ;;
 | |
|             esac
 | |
|             ;;
 | |
|         esac
 | |
|         shift
 | |
|     done
 | |
| }
 | |
| 
 | |
| function conf_load() {
 | |
|     # charger les fichiers de configuration spécifiés
 | |
|     #     conf_load SPECS...
 | |
|     # est équivalent à:
 | |
|     #     conf_find_files __CONF_FILES SPECS...
 | |
|     #     conf_load_files "${__CONF_FILES[@]}"
 | |
|     local -a __CONF_FILES
 | |
|     conf_find_files __CONFS_FILES "$@"
 | |
|     conf_load_files "${__CONFS_FILES[@]}"
 | |
| }
 | |
| 
 | |
| function conf_find_files() {
 | |
|     # initialiser le tableau $1 avec les fichiers de configuration correspondant
 | |
|     # aux arguments $2..@
 | |
|     # - si on spécifie un fichier, il est pris tel quel s'il existe
 | |
|     # - si on spécifie un répertoire, tous les fichiers *.conf de ce répertoire
 | |
|     #   sont pris
 | |
|     # - si on spécifie un pattern e.g path/to/*.conf alors tous les fichiers
 | |
|     #   correspondant au pattern sont pris
 | |
|     # - sinon l'argument est ignoré
 | |
|     local __dest="$1"; shift
 | |
|     local -a __files
 | |
|     local __spec __dir __wc
 | |
|     array_new "$__dest"
 | |
|     for __spec in "$@"; do
 | |
|         if [ -f "$__spec" ]; then
 | |
|             array_add "$__dest" "$__spec"
 | |
|             continue
 | |
|         elif [ -d "$__spec" ]; then
 | |
|             __spec="$__spec/*.conf"
 | |
|         fi
 | |
|         splitwcs "$__spec" __dir __wc
 | |
|         array_lsfiles __files "${__dir:-.}" "$__wc"
 | |
|         array_extend "$__dest" __files
 | |
|     done
 | |
| }
 | |
| 
 | |
| function conf_load_files() {
 | |
|     # sourcer les fichiers spécifiés en faisant ce qui est nécessaire pour que
 | |
|     # les variables de __CONF_ARRAY_VARS soient correctement traitées.
 | |
|     local -a __backups __values
 | |
|     local __file __name __i __backup __bn __bv
 | |
|     for __file in "$@"; do
 | |
|         # faire une copie de sauvegarde puis supprimer les variables tableaux
 | |
|         __backups=()
 | |
|         for __name in "${__CONF_ARRAY_VARS[@]}" "${__CONF_PATH_VARS[@]}"; do
 | |
|             __backup="$(declare -p "$__name" 2>/dev/null)"
 | |
|             if [ -z "$__backup" ]; then
 | |
|                 __backup="$__name="
 | |
|             else
 | |
|                 # faire une correction de l'expression parce que la commande
 | |
|                 # affichée par declare -p est différente entre bash 4.3 et bash
 | |
|                 # 4.4 pour les tableaux. soit le tableau array=(a b)
 | |
|                 # - bash 4.3 affiche      declare -a array='([0]="a" [1]="b")'
 | |
|                 # - bash 4.4 affiche      declare -a array=([0]="a" [1]="b")
 | |
|                 __backup="${__backup#declare }"
 | |
|                 __bn="${__backup%% *}"
 | |
|                 __bv="${__backup#* }"
 | |
|                 if [[ "$__bn" == -*a* ]]; then
 | |
|                     __bn="${__bv%%=*}"
 | |
|                     __bv="${__bv#*=}"
 | |
|                     if [ "${__bv:0:2}" == "'(" -a "${__bv: -2:2}" == ")'" ]; then
 | |
|                         __backup="$__bn=$(eval "echo $__bv")"
 | |
|                     else
 | |
|                         __backup="$__bn=$__bv"
 | |
|                     fi
 | |
|                 else
 | |
|                     __backup="$__bv"
 | |
|                 fi
 | |
|             fi
 | |
|             __backups=("${__backups[@]}" "$__backup")
 | |
|             unset "$__name"
 | |
|         done
 | |
|         # charger le fichier
 | |
|         source "$__file"
 | |
|         # puis restaurer les variables ou les fusionner avec une éventuelle nouvelle valeur
 | |
|         __i=0
 | |
|         for __name in "${__CONF_ARRAY_VARS[@]}" "${__CONF_PATH_VARS[@]}"; do
 | |
|             __backup="${__backups[$__i]}"
 | |
|             if [ -n "$(declare -p "$__name" 2>/dev/null)" ]; then
 | |
|                 # la variable a été redéfinie, la fusionner avec la précédente valeur
 | |
|                 if array_contains __CONF_ARRAY_VARS "$__name"; then
 | |
|                     array_copy __values "$__name"
 | |
|                     eval "$__backup"
 | |
|                     array_extend "$__name" __values
 | |
|                 elif array_contains __CONF_PATH_VARS "$__name"; then
 | |
|                     __values="${!__name}"
 | |
|                     eval "$__backup"
 | |
|                     uaddpath "$__values" "$__name"
 | |
|                 fi
 | |
|             else
 | |
|                 # la variable n'a pas été redéfinie, restaurer la précédente valeur
 | |
|                 eval "$__backup"
 | |
|             fi
 | |
|             __i=$(($__i + 1))
 | |
|         done
 | |
|     done
 | |
| }
 | |
| 
 | |
| CONF_INSTALL_ASK_DEFAULT=
 | |
| function conf_install() {
 | |
|     # USAGE: conf_install DEST PREFIX SRCS...
 | |
|     # installer les fichiers de SRCS dans le répertoire standardisé DEST avec le
 | |
|     # préfixe PREFIX
 | |
|     # ## destination
 | |
|     # - si DEST est un nom sans chemin, e.g NAME, alors la destination est
 | |
|     #   ~/etc/NAME.d
 | |
|     # - si DEST est un nom avec chemin, alors la valeur est prise telle quelle
 | |
|     #   comme destination, et le répertoire est créé le cas échéant.
 | |
|     # Si un fichier existe déjà dans la destination, afficher une demande de
 | |
|     # confirmation avant de l'écraser
 | |
|     # ## source
 | |
|     # - si SRC est un fichier, le prendre tel quel
 | |
|     # - si SRC est un répertoire, prendre tous les fichiers SRC/*.conf
 | |
|     # - si SRC est un pattern, prendre tous les fichiers correspondant
 | |
|     # s'il n'y a qu'une seule source, la destination sera DEST/PREFIX.conf
 | |
|     # sinon, la destination sera DEST/PREFIX-SRCNAME où SRCNAME est le nom du
 | |
|     # fichier source
 | |
|     # si PREFIX est vide, alors les fichiers sont copiés avec leur nom sans
 | |
|     # modification.
 | |
|     local -a tmpsrcs srcs
 | |
|     local src dir wc
 | |
|     local dest="$1"; shift
 | |
|     local prefix="$1"; shift
 | |
|     [[ "$dest" == */* ]] || dest="$HOME/etc/$dest.d"
 | |
|     mkdir -p "$dest" || return 1
 | |
|     for src in "$@"; do
 | |
|         if [ -f "$src" ]; then
 | |
|             array_add srcs "$src"
 | |
|         elif [ -d "$src" ]; then
 | |
|             array_lsfiles tmpsrcs "$src" "*.conf"
 | |
|             array_extend srcs tmpsrcs
 | |
|         else
 | |
|             splitwcs "$src" dir wc
 | |
|             array_lsfiles tmpsrcs "$dir" "$wc"
 | |
|             array_extend srcs tmpsrcs
 | |
|         fi
 | |
|     done
 | |
|     [ ${#srcs[*]} -gt 0 ] || return 0
 | |
|     local COPY_UPDATE_ASK_DEFAULT="${CONF_INSTALL_ASK_DEFAULT:-$COPY_UPDATE_ASK_DEFAULT}"
 | |
|     if [ -n "$prefix" ]; then
 | |
|         if [ ${#srcs[*]} -eq 1 ]; then
 | |
|             copy_update_ask -y "$src" "$dest/$prefix.conf"
 | |
|         else
 | |
|             for src in "${srcs[@]}"; do
 | |
|                 copy_update_ask -y "$src" "$dest/$prefix-$(basename -- "$srcname")"
 | |
|             done
 | |
|         fi
 | |
|     else
 | |
|         for src in "${srcs[@]}"; do
 | |
|             copy_update_ask -y "$src" "$dest"
 | |
|         done
 | |
|     fi
 | |
| }
 | |
| 
 | |
| function conf_upgrade() {
 | |
|     # USAGE: conf_upgrade DEST [VARS...]
 | |
|     # Si les variables VARS... sont spécifiées, on appelle au préalable
 | |
|     # conf_init()
 | |
|     local __dest="$1"; shift
 | |
|     local __desc __namevalue __name __value
 | |
| 
 | |
|     [ $# -gt 0 ] && conf_init "$@"
 | |
|     # calculer le préfixe et initialiser le fichier le cas échéant
 | |
|     if [ ! -f "$__dest" ]; then
 | |
|         local __prefix
 | |
|         for __desc in "${__CONF_DESCS[@]}"; do
 | |
|             [ "${__desc:0:1}" == "#" ] && __prefix="$__desc"
 | |
|             break
 | |
|         done
 | |
|         [ ${#__prefix} -gt 0 ] || __prefix="# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8"
 | |
|         echo "$__prefix" >"$__dest"
 | |
|     fi
 | |
|     # vérifier la présence de chaque variable
 | |
|     for __desc in "${__CONF_DESCS[@]}"; do
 | |
|         [ "${__desc:0:1}" == "#" ] && continue
 | |
|         splitfsep "$__desc" // __namevalue __desc
 | |
|         splitvar "$__namevalue" __name __value
 | |
|         if ! grep -qE "^\s*#*(\s*export)?\s*$__name=" "$__dest"; then
 | |
|             echo >>"$__dest"
 | |
|             [ -n "$__desc" ] && echo "# $__desc" >>"$__dest"
 | |
|             echo -n "#" >>"$__dest"
 | |
|             if array_contains __CONF_ARRAY_VARS "$__name"; then
 | |
|                 echo_seta2 "$__name" >>"$__dest"
 | |
|             else
 | |
|                 echo_setv "$__name" "$__value" >>"$__dest"
 | |
|             fi
 | |
|         fi
 | |
|     done
 | |
| }
 | |
| 
 | |
| function conf_update() {
 | |
|     # USAGE: conf_update [-n] DEST VARS...
 | |
|     # Pour chaque variable mentionnée, le fichier DEST est mis à jour avec sa
 | |
|     # valeur actuelle. Le fichier doit exister, et conf_init() *doit* avoir été
 | |
|     # appelé au préalable.
 | |
|     # Avec l'option -n, ne pas modifier l'état activé/désactivé des variables
 | |
|     # dans le fichier de configuration. En d'autres termes, si la variable était
 | |
|     # commentée dans le fichier, la laisser commentée, mais mettre quand même à
 | |
|     # jour la valeur
 | |
|     local enable=1
 | |
|     if [ "$1" == -n ]; then
 | |
|         enable=
 | |
|         shift
 | |
|     fi
 | |
|     local dest="$1"; shift
 | |
|     [ -f "$dest" ] || return 1
 | |
|     local from to
 | |
|     ac_set_tmpfile destf
 | |
|     ac_set_tmpfile destt
 | |
|     cat "$dest" >"$destf"
 | |
| 
 | |
|     local name setvar
 | |
|     for name in "$@"; do
 | |
|         if array_contains __CONF_ARRAY_VARS "$name"; then
 | |
|             setx setvar=echo_seta2 "$name"
 | |
|         else
 | |
|             setx setvar=echo_setv2 "$name"
 | |
|         fi
 | |
|         awkrun <"$destf" >"$destt" name="$name" setvar="$setvar" enable:int="$enable" '
 | |
| $0 ~ "^\\s*#*(\\s*export)?\\s*" name "=" {
 | |
|   match($0, "^(\\s*#*)((\\s*export)?\\s*)" name "=", vs)
 | |
|   if (enable) print vs[2] setvar
 | |
|   else print vs[1] vs[2] setvar
 | |
|   next
 | |
| }
 | |
| { print }'
 | |
|         cat "$destt" >"$destf"
 | |
|     done
 | |
|     cat "$destf" >"$dest"
 | |
|     ac_clean "$destf" "$destt"
 | |
|     return 0
 | |
| }
 |