diff --git a/lib/default/ufile b/lib/default/ufile index cb065f1..755d24a 100644 --- a/lib/default/ufile +++ b/lib/default/ufile @@ -1,9 +1,5 @@ # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -# Fichiers externes à inclure. Chacun de ces fichiers peut contenir des -# définitions de fonctions et de la variables RULES -INCLUDES=() - # Règles pour le classement des fichiers. Chaque règle est de la forme # pattern:destdir[:renamef] RULES=() diff --git a/lib/ulib/install b/lib/ulib/install index 89de2fc..014bb33 100644 --- a/lib/ulib/install +++ b/lib/ulib/install @@ -111,7 +111,15 @@ function copy_update_ask() { # Copier ou mettre à jour le fichier $1 vers le fichier $2. # Si le fichier existe déjà, la différence est affichée, et une confirmation # est demandée pour l'écrasement du fichier. + # si $1 commence par '-' alors on s'en sert comme option pour configurer le + # niveau d'interaction pour demander la confirmation. les paramètres sont + # alors décalés # Retourner vrai si le fichier a été copié sans erreur. + local interopt=-c + if [[ "$1" == -* ]]; then + interopt="$1" + shift + fi local src="$1" dest="$2" [ -d "$dest" ] && dest="$dest/$(basename -- "$src")" @@ -119,10 +127,14 @@ function copy_update_ask() { [ -f "$dest" ] || copy_replace "$src" "$dest" if testdiff "$src" "$dest"; then - diff -u "$dest" "$src" - if ask_yesno -c "Voulez-vous remplacer $(ppath "$dest") par la nouvelle version?" C; then + check_interaction "$interopt" && diff -u "$dest" "$src" + if ask_yesno "$interopt" "Voulez-vous remplacer $(ppath "$dest") par la nouvelle version?" C; then copy_replace "$src" "$dest" "$3" return $? + elif ! check_interaction "$interopt"; then + ewarn "Les mises à jours suivantes sont disponibles:" + diff -u "$dest" "$src" + ewarn "Le fichier $(ppath "$dest") n'a pas été mis à jour" fi fi return 1 diff --git a/lib/ulib/multiconf b/lib/ulib/multiconf new file mode 100644 index 0000000..0fda462 --- /dev/null +++ b/lib/ulib/multiconf @@ -0,0 +1,182 @@ +##@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" +} + +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. + # dans l'exemple suivant: + # conf_init NAME VALUE -a SRCDIRS DESTDIRS + # NAME et VALUE sont scalaires alors que SRCDIRS et DESTDIRS sont tableaux + # Les variables scalaires sont initialisées à la valeur vide ou à la valeur + # spécifiée e.g.: + # conf_init VAR=value + # Les variables tableaux sont toujours initialisées à la valeur vide + # L'option -s permet de revenir au mode scalaire + __CONF_ARRAY_VARS=() + local __var __array + while [ $# -gt 0 ]; do + case "$1" in + -a|--array) __array=1;; + -s|--scalar) __array=;; + *) + if [ -n "$__array" ]; then + eval "${1%%=*}=()" + array_addu __CONF_ARRAY_VARS "${1%%=*}" + else + setv "$1" + fi + ;; + 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 + for __conf_file in "$@"; do + # faire une copie de sauvegarde puis supprimer les variables tableaux + __conf_backups=() + for __conf_name in "${__CONF_ARRAY_VARS[@]}"; do + __conf_backups=("${__conf_backups[@]}" "$(declare -p "$__conf_name")") + 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[@]}"; do + __conf_backup="${__conf_backups[$__conf_i]}" + __conf_backup="${__conf_backup#declare * }" + if [ -n "$(declare -p "$__conf_name" 2>/dev/null)" ]; then + # la variable a été redéfinie, la fusionner avec la précédente valeur + array_copy __conf_values "$__conf_name" + eval "$__conf_backup" + array_extend "$__conf_name" __conf_values + 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 +} diff --git a/ufile b/ufile index b87b156..9640f61 100755 --- a/ufile +++ b/ufile @@ -1,7 +1,7 @@ #!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 source "$(dirname "$0")/lib/ulib/ulib" || exit 1 -urequire DEFAULTS +urequire DEFAULTS multiconf function display_help() { uecho "$scriptname: classer des fichiers selon certains règles @@ -126,8 +126,7 @@ parse_args "$@"; set -- "${args[@]}" ## charger toutes les règles -RULES=() -INCLUDES=() +conf_init -a RULES if [ -n "$config" ]; then if [ "$action" != edit ]; then # le fichier doit exister, sauf en mode édition où il sera créé s'il @@ -140,22 +139,11 @@ if [ -n "$config" ]; then else set_defaults ufile fi -array_copy rules RULES -for include in "${INCLUDES[@]}"; do - if [ -f "$include" ]; then - RULES=() - source "$include" - array_extend rules RULES - else - ewarn "$include: fichier introuvable" - fi -done -array_copy RULES rules +conf_load "$HOME/etc/ufile.d/*.conf" ## actions particulières if [ "$action" == list ]; then - echo "# $(echo_seta2 INCLUDES)" array_to_lines RULES exit 0 elif [ "$action" == edit ]; then @@ -180,7 +168,7 @@ else fi [ $# -gt 0 ] || die "Vous devez spécifier des fichiers à classer" -[ ${#RULES[*]} -gt 0 ] || die "Vous devez spécifier des règles pour le classement des fichiers dans ~/etc/default/ufile" +[ ${#RULES[*]} -gt 0 ] || die "Vous devez spécifier des règles pour le classement des fichiers dans ~/etc/default/ufile ou ~/etc/ufile.d/*.conf" # vérifier les règles for rule in "${RULES[@]}"; do