nutools/lib/ulib/multiconf

385 lines
15 KiB
Plaintext
Raw Normal View History

2017-09-13 08:13:11 +04:00
##@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
2017-09-14 08:28:40 +04:00
uprovide multiconf
urequire install
2017-09-13 08:13:11 +04:00
function conf_local() {
# afficher les commandes pour définir comme locales les variables utilisées
# par les fonctions conf_*
2017-09-14 21:51:23 +04:00
# 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"
2017-09-13 08:13:11 +04:00
}
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
2017-09-14 22:21:34 +04:00
# conf_load_files "${__CONF_FILES[@]}"
2017-09-14 08:28:40 +04:00
# 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...
2017-09-14 22:21:34 +04:00
# conf_find_files __CONF_FILES PATH/TO/NAME.conf PATH/TO/NAME.d/*.conf
# conf_upgrade PATH/TO/NAME.conf
2017-09-14 22:21:34 +04:00
# conf_load_files "${__CONF_FILES[@]}"
2017-09-13 08:13:11 +04:00
local __name="$1"; shift
[ -n "$__name" ] || return 1
[ $# -gt 0 ] && conf_init "$@"
local -a __CONF_FILES
2017-09-14 21:51:23 +04:00
if [[ "$__name" == */* ]]; then
conf_find_files __CONFS_FILES "$__name.conf" "$__name.d/*.conf"
[ $# -gt 0 ] && conf_upgrade "$__name.conf"
2017-09-14 21:51:23 +04:00
else
conf_find_files __CONFS_FILES "$HOME/etc/default/$__name" "$HOME/etc/$__name.d/*.conf"
[ $# -gt 0 ] && conf_upgrade "$HOME/etc/default/$__name"
2017-09-14 21:51:23 +04:00
fi
conf_load_files "${__CONFS_FILES[@]}"
2017-09-13 08:13:11 +04:00
}
function conf_init() {
2017-09-14 21:51:23 +04:00
# 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é
2017-09-13 08:13:11 +04:00
# 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 ':'
2017-09-13 08:13:11 +04:00
# 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
2018-02-01 14:56:22 +04:00
# 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
2017-09-13 08:13:11 +04:00
# 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=()
2017-09-13 08:13:11 +04:00
__CONF_ARRAY_VARS=()
__CONF_PATH_VARS=()
local __type=scalar __initial=1 __prefix __var __desc
[ $# -eq 0 ] && is_array CONFIG && set -- "${CONFIG[@]}"
2017-09-13 08:13:11 +04:00
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
2017-09-13 08:13:11 +04:00
case "$1" in
-a|--array) __type=array;;
-p|--path) __type=path;;
-s|--scalar) __type=scalar;;
2017-09-13 08:13:11 +04:00
*)
array_add __CONF_DESCS "$1"
splitfsep "$1" // __var __desc
case "$__type" in
array)
2018-02-01 14:56:22 +04:00
[ "${__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
2017-09-13 08:13:11 +04:00
;;
esac
shift
done
}
2017-09-14 22:21:34 +04:00
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() {
2017-09-13 08:13:11 +04:00
# 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é
2018-02-01 14:56:22 +04:00
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"
2017-09-14 21:51:23 +04:00
continue
2018-02-01 14:56:22 +04:00
elif [ -d "$__spec" ]; then
__spec="$__spec/*.conf"
2017-09-14 21:51:23 +04:00
fi
2018-02-01 14:56:22 +04:00
splitwcs "$__spec" __dir __wc
array_lsfiles __files "${__dir:-.}" "$__wc"
array_extend "$__dest" __files
2017-09-14 21:51:23 +04:00
done
2017-09-13 08:13:11 +04:00
}
2017-09-14 22:21:34 +04:00
function conf_load_files() {
2017-09-13 08:13:11 +04:00
# 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.
2018-02-01 14:56:22 +04:00
local -a __backups __values
local __file __name __i __backup __bn __bv
for __file in "$@"; do
2017-09-14 21:51:23 +04:00
# faire une copie de sauvegarde puis supprimer les variables tableaux
2018-02-01 14:56:22 +04:00
__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
2017-09-19 08:19:12 +04:00
# 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")
2018-02-01 14:56:22 +04:00
__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
2018-02-01 14:56:22 +04:00
__backup="$__bn=$__bv"
fi
else
2018-02-01 14:56:22 +04:00
__backup="$__bv"
fi
fi
2018-02-01 14:56:22 +04:00
__backups=("${__backups[@]}" "$__backup")
unset "$__name"
2017-09-14 21:51:23 +04:00
done
# charger le fichier
2018-02-01 14:56:22 +04:00
source "$__file"
2017-09-14 21:51:23 +04:00
# puis restaurer les variables ou les fusionner avec une éventuelle nouvelle valeur
2018-02-01 14:56:22 +04:00
__i=0
for __name in "${__CONF_ARRAY_VARS[@]}" "${__CONF_PATH_VARS[@]}"; do
__backup="${__backups[$__i]}"
if [ -n "$(declare -p "$__name" 2>/dev/null)" ]; then
2017-09-14 21:51:23 +04:00
# la variable a été redéfinie, la fusionner avec la précédente valeur
2018-02-01 14:56:22 +04:00
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
2017-09-14 21:51:23 +04:00
else
# la variable n'a pas été redéfinie, restaurer la précédente valeur
2018-02-01 14:56:22 +04:00
eval "$__backup"
2017-09-14 21:51:23 +04:00
fi
2018-02-01 14:56:22 +04:00
__i=$(($__i + 1))
2017-09-14 21:51:23 +04:00
done
done
2017-09-13 08:13:11 +04:00
}
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() {
2018-02-01 14:56:22 +04:00
# 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
2018-02-01 14:56:22 +04:00
if [ ! -f "$__dest" ]; then
local __prefix
for __desc in "${__CONF_DESCS[@]}"; do
[ "${__desc:0:1}" == "#" ] && __prefix="$__desc"
break
done
2018-02-01 14:56:22 +04:00
[ ${#__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
2018-02-01 14:56:22 +04:00
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
2018-02-01 14:56:22 +04:00
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
2018-02-01 14:56:22 +04:00
cat "$destf" >"$dest"
ac_clean "$destf" "$destt"
return 0
}