328 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			328 lines
		
	
	
		
			13 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 toujours initialisées à la valeur vide
 | 
						|
    # 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)
 | 
						|
                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 __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. 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")
 | 
						|
                __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=$(eval "echo $__conf_bv")"
 | 
						|
                    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 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
 | 
						|
    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
 | 
						|
    if [ $# -gt 0 ]; then
 | 
						|
        eval "$(conf_local)"
 | 
						|
        conf_init "$@"
 | 
						|
    fi
 | 
						|
    local desc namevalue name value
 | 
						|
    # 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"
 | 
						|
            echo_setv "$name" "$value" >>"$dest"
 | 
						|
        fi
 | 
						|
    done
 | 
						|
}
 |