##@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_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.conf ~/etc/NAME.d/*.conf # 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_load_files "${__CONF_FILES[@]}" local __name="$1"; shift [ -n "$__name" ] || return 1 [ $# -gt 0 ] && conf_init "$@" local -a __CONF_FILES if [[ "$__name" == */* ]]; then conf_load "$__name.conf" "$__name.d/*.conf" else conf_load "$HOME/etc/default/$__name.conf" "$HOME/etc/$__name.d/*.conf" fi } function conf_init() { # définir les variables attendues lors du chargement des fichiers de # configuration par conf_load_files # 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 toujours initialisées à la valeur vide # L'option -s permet de revenir au mode scalaire __CONF_ARRAY_VARS=() __CONF_PATH_VARS=() local __var __type=scalar while [ $# -gt 0 ]; do case "$1" in -a|--array) __type=array;; -p|--path) __type=path;; -s|--scalar) __type=scalar;; *) case "$__type" in array) eval "${1%%=*}=()" array_addu __CONF_ARRAY_VARS "${1%%=*}" array_del __CONF_PATH_VARS "${1%%=*}" ;; path) setv "$1" array_addu __CONF_PATH_VARS "${1%%=*}" array_del __CONF_ARRAY_VARS "${1%%=*}" ;; scalar) setv "$1" ;; 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 __conf_dest="$1"; shift local -a __conf_files local __conf_spec __conf_dir __conf_wc array_new "$__conf_dest" for __conf_spec in "$@"; do if [ -f "$__conf_spec" ]; then array_add "$__conf_dest" "$__conf_spec" continue elif [ -d "$__conf_spec" ]; then __conf_spec="$__conf_spec/*.conf" fi splitwcs "$__conf_spec" __conf_dir __conf_wc array_lsfiles __conf_files "${__conf_dir:-.}" "$__conf_wc" array_extend "$__conf_dest" __conf_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 __conf_backups __conf_values local __conf_file __conf_name __conf_i __conf_backup __conf_bn __conf_bv for __conf_file in "$@"; do # faire une copie de sauvegarde puis supprimer les variables tableaux __conf_backups=() for __conf_name in "${__CONF_ARRAY_VARS[@]}" "${__CONF_PATH_VARS[@]}"; do __conf_backup="$(declare -p "$__conf_name" 2>/dev/null)" if [ -z "$__conf_backup" ]; then __conf_backup="$__conf_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. pour le tableau array=(a b) on aura: # - bash 4.3 affiche declare -a array='([0]="a" [1]="b")' # - bash 4.4 affiche declare -a array=([0]="a" [1]="b") __conf_backup="${__conf_backup#declare }" __conf_bn="${__conf_backup%% *}" __conf_bv="${__conf_backup#* }" if [[ "$__conf_bn" == -*a* ]]; then __conf_bn="${__conf_bv%%=*}" __conf_bv="${__conf_bv#*=}" if [ "${__conf_bv:0:2}" == "'(" -a "${__conf_bv: -2:2}" == ")'" ]; then __conf_backup="$__conf_bn=${__conf_bv:1: -1}" else __conf_backup="$__conf_bn=$__conf_bv" fi else __conf_backup="$__conf_bv" fi fi __conf_backups=("${__conf_backups[@]}" "$__conf_backup") unset "$__conf_name" done # charger le fichier source "$__conf_file" # puis restaurer les variables ou les fusionner avec une éventuelle nouvelle valeur __conf_i=0 for __conf_name in "${__CONF_ARRAY_VARS[@]}" "${__CONF_PATH_VARS[@]}"; do __conf_backup="${__conf_backups[$__conf_i]}" if [ -n "$(declare -p "$__conf_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 "$__conf_name"; then array_copy __conf_values "$__conf_name" eval "$__conf_backup" array_extend "$__conf_name" __conf_values elif array_contains __CONF_PATH_VARS "$__conf_name"; then __conf_values="${!__conf_name}" eval "$__conf_backup" uaddpath "$__conf_values" "$__conf_name" fi else # la variable n'a pas été redéfinie, restaurer la précédente valeur eval "$__conf_backup" fi __conf_i=$(($__conf_i + 1)) done done } function conf_install() { # USAGE: conf_install DEST SRCS... # installer les fichiers de SRCS dans le répertoire standardisé DEST # ## 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 local -a srcs local src dir wc local dest="$1"; shift [[ "$dest" == */* ]] || dest="$HOME/etc/$dest.d" mkdir -p "$dest" || return 1 for src in "$@"; do if [ -f "$src" ]; then srcs=("$src") elif [ -d "$src" ]; then array_lsfiles srcs "$src" "*.conf" else splitwcs "$src" dir wc array_lsfiles srcs "$dir" "$wc" fi for src in "${srcs[@]}"; do copy_update_ask -y "$src" "$dest" done done }