From 94feff75dbc92fb27e4309d22c80234f48970d86 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 20 Aug 2025 13:01:26 +0400 Subject: [PATCH 01/91] calcul de MYTRUESELF --- bash/src/base.init.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bash/src/base.init.sh b/bash/src/base.init.sh index de5ae8c..2104e56 100644 --- a/bash/src/base.init.sh +++ b/bash/src/base.init.sh @@ -21,6 +21,12 @@ if [ -z "$NULIB_NO_INIT_ENV" ]; then fi [ -n "$NULIBDIR" ] || NULIBDIR="$MYDIR" + # Si le script courant est un lien, calculer le répertoire destination + if [ -n "$MYTRUESELF" -a -n "$MYSELF" ]; then + MYTRUESELF="$(readlink -f "$MYSELF")" + MYTRUEDIR="$(dirname -- "$MYTRUESELF")" + fi + # Repertoire temporaire [ -z "$TMPDIR" -a -d "$HOME/tmp" ] && TMPDIR="$HOME/tmp" [ -z "$TMPDIR" ] && TMPDIR="${TMP:-${TEMP:-/tmp}}" From 26821f2a1ac6b20c5a0f4f78e1297c22e49c4f41 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 20 Aug 2025 13:03:14 +0400 Subject: [PATCH 02/91] renommer TRUE en REAL --- bash/src/base.init.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bash/src/base.init.sh b/bash/src/base.init.sh index 2104e56..bd2c1cb 100644 --- a/bash/src/base.init.sh +++ b/bash/src/base.init.sh @@ -22,9 +22,9 @@ if [ -z "$NULIB_NO_INIT_ENV" ]; then [ -n "$NULIBDIR" ] || NULIBDIR="$MYDIR" # Si le script courant est un lien, calculer le répertoire destination - if [ -n "$MYTRUESELF" -a -n "$MYSELF" ]; then - MYTRUESELF="$(readlink -f "$MYSELF")" - MYTRUEDIR="$(dirname -- "$MYTRUESELF")" + if [ -n "$MYREALSELF" -a -n "$MYSELF" ]; then + MYREALSELF="$(readlink -f "$MYSELF")" + MYREALDIR="$(dirname -- "$MYREALSELF")" fi # Repertoire temporaire From 7d4c385492cad29e600a7feea1f0f3cda345869b Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 20 Aug 2025 13:22:10 +0400 Subject: [PATCH 03/91] afficher correctement une option facultative --- bash/src/base.args.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bash/src/base.args.sh b/bash/src/base.args.sh index 43e63ae..c70cfa8 100644 --- a/bash/src/base.args.sh +++ b/bash/src/base.args.sh @@ -184,7 +184,7 @@ function __nulib_args_parse_args() { *) die "Invalid arg definition: expected option, got '$1'" || return;; esac # est-ce que l'option prend un argument? - local __def __longdef __witharg __valdesc + local __def __longdef __witharg __valdesc __defvaldesc __witharg= for __def in "${__defs[@]}"; do if [ "${__def%::*}" != "$__def" ]; then @@ -346,16 +346,19 @@ $prefix$usage" fi # est-ce que l'option prend un argument? __witharg= - __valdesc=value + __valdesc= + __defvaldesc=value for __def in "${__defs[@]}"; do if [ "${__def%::*}" != "$__def" ]; then [ "$__witharg" != : ] && __witharg=:: [ -n "${__def#*::}" ] && __valdesc="[${__def#*::}]" + __defvaldesc="[value]" elif [ "${__def%:*}" != "$__def" ]; then __witharg=: [ -n "${__def#*:}" ] && __valdesc="${__def#*:}" fi done + [ -n "$__valdesc" ] || __valdesc="$__defvaldesc" # description de l'option local first=1 thelp tdesc for __def in "${__defs[@]}"; do From 529633e3ba8955a2d1a63cb64a2cf4940ec654b0 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 21 Aug 2025 18:39:46 +0400 Subject: [PATCH 04/91] modifs.mineures sans commentaires --- bash/src/base.init.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bash/src/base.init.sh b/bash/src/base.init.sh index bd2c1cb..671bb33 100644 --- a/bash/src/base.init.sh +++ b/bash/src/base.init.sh @@ -25,6 +25,7 @@ if [ -z "$NULIB_NO_INIT_ENV" ]; then if [ -n "$MYREALSELF" -a -n "$MYSELF" ]; then MYREALSELF="$(readlink -f "$MYSELF")" MYREALDIR="$(dirname -- "$MYREALSELF")" + MYREALNAME="$(basename -- "$MYREALSELF")" fi # Repertoire temporaire From 3c8ef572467847cb4089ed1a0ff3cf23a9338f79 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 22 Aug 2025 12:27:34 +0400 Subject: [PATCH 05/91] ajout install.sh --- bash/src/_output_color.sh | 3 +- bash/src/_output_vanilla.sh | 3 +- bash/src/install.sh | 147 ++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 bash/src/install.sh diff --git a/bash/src/_output_color.sh b/bash/src/_output_color.sh index afe6428..2cc5cc8 100644 --- a/bash/src/_output_color.sh +++ b/bash/src/_output_color.sh @@ -6,7 +6,8 @@ function __esection() { local length="${COLUMNS:-80}" setx lsep=__complete "$prefix" "$length" - - recho "$COULEUR_BLEUE$lsep$COULEUR_NORMALE" + recho " +$COULEUR_BLEUE$lsep$COULEUR_NORMALE" [ -n "$*" ] || return 0 length=$((length - 1)) setx -a lines=echo "$1" diff --git a/bash/src/_output_vanilla.sh b/bash/src/_output_vanilla.sh index cbd466f..165f1d7 100644 --- a/bash/src/_output_vanilla.sh +++ b/bash/src/_output_vanilla.sh @@ -6,7 +6,8 @@ function __esection() { local length="${COLUMNS:-80}" setx lsep=__complete "$prefix" "$length" - - recho "$lsep" + recho " +$lsep" [ -n "$*" ] || return 0 length=$((length - 1)) setx -a lines=echo "$1" diff --git a/bash/src/install.sh b/bash/src/install.sh new file mode 100644 index 0000000..fc4dc51 --- /dev/null +++ b/bash/src/install.sh @@ -0,0 +1,147 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@cooked nocomments +module: install "Outils de haut niveau pour installer des fichiers de configuration" +require: base + +if [ -z "$NULIB_INSTALL_CONFIGURED" ]; then + # Faut-il afficher le nom des fichiers copié/créés? + export NULIB_INSTALL_VERBOSE=1 + # Faut-il afficher les destinations avec ppath? + export NULIB_INSTALL_USES_PPATH= +fi +export NULIB_INSTALL_CONFIGURED=1 + +function ensure_exists() { + # Créer le fichier vide "$1" s'il n'existe pas déjà, avec les permissions + # $2(=644). retourner vrai si le fichier a été créé sans erreur + [ -f "$1" ] || { + if [ -n "$NULIB_INSTALL_VERBOSE" ]; then + if [ -n "$NULIB_INSTALL_USES_PPATH" ]; then + estep "$(ppath "$1")" + else + estep "$1" + fi + fi + mkdirof "$1" + local r=0 + touch "$1" && chmod "${2:-644}" "$1" || r=$? + return $r + } + return 1 +} + +function __nulib_install_show_args() { + if [ -z "$NULIB_INSTALL_VERBOSE" ]; then + : + elif [ -n "$NULIB_INSTALL_USES_PPATH" ]; then + estep "$1 --> $(ppath "$2")${3:+/}" + else + estep "$1 --> $2${3:+/}" + fi +} + +function copy_replace() { + # Copier de façon inconditionnelle le fichier $1 vers le fichier $2, en + # réinitialisation les permissions à la valeur $3 + local src="$1" dest="$2" + local srcname="$(basename -- "$src")" + + [ -d "$dest" ] && dest="$dest/$srcname" + mkdirof "$dest" || return 1 + + if [ -n "$NULIB_INSTALL_VERBOSE" ]; then + local destarg destname slash + destarg="$(dirname -- "$dest")" + destname="$(basename -- "$dest")" + if [ "$srcname" == "$destname" ]; then + slash=1 + else + destarg="$destarg/$destname" + fi + __nulib_install_show_args "$srcname" "$destarg" "$slash" + fi + local r=0 + if cp "$src" "$dest"; then + if [ -n "$3" ]; then + chmod "$3" "$dest" || r=$? + fi + fi + return $r +} + +function copy_new() { + # Copier le fichier "$1" vers le fichier "$2", avec les permissions $3(=644) + # Ne pas écraser le fichier destination s'il existe déjà + # Retourner vrai si le fichier a été copié sans erreur + local src="$1" dest="$2" + + [ -d "$dest" ] && dest="$dest/$(basename -- "$src")" + mkdirof "$dest" || return 1 + + if [ ! -e "$dest" ]; then + copy_replace "$src" "$dest" "$3" + else + return 1 + fi +} + +function copy_update() { + # Copier le fichier "$1" vers le fichier "$2", si $2 n'existe pas, ou si $1 + # a été modifié par rapport à $2. Réinitialiser le cas échéant les + # permissions à la valeur $3 + # Retourner vrai si le fichier a été copié sans erreur. + local src="$1" dest="$2" + + [ -d "$dest" ] && dest="$dest/$(basename -- "$src")" + mkdirof "$dest" || return 1 + + if [ ! -e "$dest" ]; then + copy_replace "$src" "$dest" "$3" + elif testdiff "$src" "$dest"; then + copy_replace "$src" "$dest" "$3" + else + return 1 + fi +} + +COPY_UPDATE_ASK_DEFAULT= +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")" + mkdirof "$dest" || return 1 + + [ -f "$dest" ] || copy_replace "$src" "$dest" + if testdiff "$src" "$dest"; then + check_interaction "$interopt" && diff -u "$dest" "$src" + if ask_yesno "$interopt" "Voulez-vous remplacer $(ppath "$dest") par la nouvelle version?" "${COPY_UPDATE_ASK_DEFAULT:-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 +} + +function link_new() { + # Si $2 n'existe pas, créer le lien symbolique $2 pointant vers $1 + [ -e "$2" ] && return 0 + + __nulib_install_show_args "$(basename -- "$2")" "$(dirname -- "$1")" + ln -s "$1" "$2" +} From afaf12497b750857468fc4c939a47ad123993eb9 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sat, 30 Aug 2025 17:52:22 +0400 Subject: [PATCH 06/91] modifs.mineures sans commentaires --- php/src/db/TODO.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/php/src/db/TODO.md b/php/src/db/TODO.md index c7004a6..1f03e6d 100644 --- a/php/src/db/TODO.md +++ b/php/src/db/TODO.md @@ -1,7 +1,29 @@ # db/Capacitor -* charge() permet de spécifier la clé associée avec la valeur chargée, et - discharge() retourne les valeurs avec la clé primaire -* chargeAll() (ou peut-être chargeFrom()) permet de charger depuis un iterable +charge() permet de spécifier la clé associée avec la valeur chargée, et +discharge() retourne les valeurs avec la clé primaire + +--- + +chargeAll() (ou peut-être chargeFrom()) permet de charger depuis un iterable + +--- + +rendre obsolète la classe Capacitor: ne garder que CapacitorChannel et +CapacitorStorage + +--- + +constante de classe AUTO_MIGRATE valant par défaut null + +false: ne jamais faire de migration: assumer que la table existe avec les bonnes +colonnes + +true: toujours chercher à faire la migration + +null: calculer la valeur en fonction du profil courant: true pour devel, false +sinon + + -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file From 01c93ec71e1c3b8d8b894141544ed1b86af5268a Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 2 Sep 2025 08:22:39 +0400 Subject: [PATCH 07/91] maj todo --- php/src/output/TODO.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/php/src/output/TODO.md b/php/src/output/TODO.md index fc100bf..28ae6ac 100644 --- a/php/src/output/TODO.md +++ b/php/src/output/TODO.md @@ -38,4 +38,13 @@ peut-être rajouter `ui` (ou `web`?) en plus de say, log, debuglog? * `msg::section()` et/ou `msg::title()` appellent automatiquement `app::action()` * `msg::estep()` appelle automatiquement `app::step()` +* support des logs structurés: log:: ajoute des objets json, un par ligne, dans + le fichier destination. msg::, say:: mettent en forme l'information structurée. + * clés standards: timestamp, user, tech, exception + * string ou list deviennent ["user" => string|list] + ne pas faire cette transformation si le tableau est associatif + * un trait Tlogger permet de spécifier le cas échéant comment mettre en forme + une donnée structurée --> il permet de calculer les valeurs de user_message + et tech_message + -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file From 90f92c74a330e10d39d52d92aa1a79a84edf41f1 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 5 Sep 2025 09:32:34 +0400 Subject: [PATCH 08/91] =?UTF-8?q?mettre=20=C3=A0=20jour=20CHANGES.md=20au?= =?UTF-8?q?=20lieu=20de=20le=20r=C3=A9=C3=A9crire=20compl=C3=A8tement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bash/src/pman.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bash/src/pman.sh b/bash/src/pman.sh index 094b269..2d26133 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -409,10 +409,6 @@ function _rscript_create_release_branch() { ## Release $Tag du $date " _list_commits | _filter_changes | _format_md >>"$changelog" - if [ -s CHANGES.md ]; then - echo >>"$changelog" - cat CHANGES.md >>"$changelog" - fi "${EDITOR:-nano}" +7 "$changelog" [ -s "$changelog" ] || exit_with ewarn "Création de la release annulée" @@ -423,11 +419,18 @@ EOF # créer le changelog _scripta "update CHANGES.md" <CHANGES.md +')") >"\$tmpchanges" +if [ -s CHANGES.md ]; then + echo >>"\$tmpchanges" + cat CHANGES.md >>"\$tmpchanges" +fi +cat "\$tmpchanges" >CHANGES.md +rm -f "\$tmpchanges" git add CHANGES.md EOF From 88e6b9b92fb8b77857b017dc0b13b87e2d4d1f9e Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 8 Sep 2025 13:08:55 +0400 Subject: [PATCH 09/91] ajouter filterf --- bin/filterf | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ wip/TEMPLATE | 10 ++++++++ 2 files changed, 76 insertions(+) create mode 100755 bin/filterf create mode 100755 wip/TEMPLATE diff --git a/bin/filterf b/bin/filterf new file mode 100755 index 0000000..e8f602a --- /dev/null +++ b/bin/filterf @@ -0,0 +1,66 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname -- "$0")/../load.sh" || exit 1 + +function filter_arg() { + local inverse="$1"; shift + while read arg; do + if [ "$inverse" == 0 ]; then + [ $1 "$arg" ] && echo "$arg" + else + [ $1 "$arg" ] || echo "$arg" + fi + done +} + +chdir= +lsdirs= +lsfiles= +dir= +file= +link= +exists= +nonzero= +inverse=0 +args=( + "filtrer une liste de fichiers" + "[filter]" + -C:,--chdir chdir= "changer le répertoire courant avant de lister les fichiers" + --lsdirs . "n'afficher que les répertoires du répertoire courant" + --lsfiles . "n'afficher que les fichiers du répertoire courant" + -d,--dir . "garder uniquement les répertoires" + -f,--file . "garder uniquement les fichiers" + -L,--link . "garder uniquement les liens symboliques" + -e,--exists . "garder uniquement les fichiers/répertoires qui existent" + -s,--nonzero . "garder uniquement les fichiers ayant une taille non nulle" + -n,--inverse inverse=1 "inverser le sens du filtre" +) +parse_args "$@"; set -- "${args[@]}" + +if [ -n "$chdir" ]; then + cd "$chdir" || die +fi + +if in_isatty; then + # lister les fichiers + [ -n "$lsdirs" -o -n "$lsfiles" ] || { + lsdirs=1 + lsfiles=1 + } + if [ -n "$lsdirs" -a -n "$lsfiles" ]; then + cmd="{ $(qvals ls_dirs . "$@"); $(qvals ls_files . "$@"); }" + elif [ -n "$lsdirs" ]; then + cmd="$(qvals ls_dirs . "$@")" + elif [ -n "$lsfiles" ]; then + cmd="$(qvals ls_files . "$@")" + fi +else + cmd="cat" +fi + +[ -n "$dir" ] && cmd="$cmd | filter_arg $inverse -d" +[ -n "$file" ] && cmd="$cmd | filter_arg $inverse -f" +[ -n "$link" ] && cmd="$cmd | filter_arg $inverse -L" +[ -n "$exists" ] && cmd="$cmd | filter_arg $inverse -e" +[ -n "$nonzero" ] && cmd="$cmd | filter_arg $inverse -s" +eval "$cmd" diff --git a/wip/TEMPLATE b/wip/TEMPLATE new file mode 100755 index 0000000..c46b7c5 --- /dev/null +++ b/wip/TEMPLATE @@ -0,0 +1,10 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname -- "$0")/../load.sh" || exit 1 + +args=( + "description" + "usage" +) +parse_args "$@"; set -- "${args[@]}" + From 28487e0240b5208180280a3331e8e9f4cc856044 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 9 Sep 2025 11:15:12 +0400 Subject: [PATCH 10/91] renommer filterf en ff --- bin/{filterf => ff} | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) rename bin/{filterf => ff} (66%) diff --git a/bin/filterf b/bin/ff similarity index 66% rename from bin/filterf rename to bin/ff index e8f602a..42a0d43 100755 --- a/bin/filterf +++ b/bin/ff @@ -12,13 +12,26 @@ function filter_arg() { fi done } +function filter_broken() { + local inverse="$1"; shift + while read arg; do + [ -L "$arg" ] || continue + if [ "$inverse" == 0 ]; then + [ -e "$arg" ] || echo "$arg" + else + [ -e "$arg" ] && echo "$arg" + fi + done +} chdir= lsdirs= lsfiles= +lsnames= dir= file= link= +broken= exists= nonzero= inverse=0 @@ -28,9 +41,11 @@ args=( -C:,--chdir chdir= "changer le répertoire courant avant de lister les fichiers" --lsdirs . "n'afficher que les répertoires du répertoire courant" --lsfiles . "n'afficher que les fichiers du répertoire courant" + --lsnames . "n'afficher que les noms de fichier" -d,--dir . "garder uniquement les répertoires" -f,--file . "garder uniquement les fichiers" -L,--link . "garder uniquement les liens symboliques" + -b,--broken . "garder uniquement les liens symboliques cassés" -e,--exists . "garder uniquement les fichiers/répertoires qui existent" -s,--nonzero . "garder uniquement les fichiers ayant une taille non nulle" -n,--inverse inverse=1 "inverser le sens du filtre" @@ -43,16 +58,19 @@ fi if in_isatty; then # lister les fichiers - [ -n "$lsdirs" -o -n "$lsfiles" ] || { - lsdirs=1 - lsfiles=1 - } + setx cwd=pwd + [ -n "$lsnames" ] && withpath= || withpath=1 if [ -n "$lsdirs" -a -n "$lsfiles" ]; then - cmd="{ $(qvals ls_dirs . "$@"); $(qvals ls_files . "$@"); }" + cmd="{ +$(qvals ls_dirs ${withpath:+-p} "$cwd" "$@") +$(qvals ls_files ${withpath:+-p} "$cwd" "$@") +}" elif [ -n "$lsdirs" ]; then - cmd="$(qvals ls_dirs . "$@")" + cmd="$(qvals ls_dirs ${withpath:+-p} "$cwd" "$@")" elif [ -n "$lsfiles" ]; then - cmd="$(qvals ls_files . "$@")" + cmd="$(qvals ls_files ${withpath:+-p} "$cwd" "$@")" + else + cmd="$(qvals ls_all ${withpath:+-p} "$cwd" "$@")" fi else cmd="cat" @@ -61,6 +79,7 @@ fi [ -n "$dir" ] && cmd="$cmd | filter_arg $inverse -d" [ -n "$file" ] && cmd="$cmd | filter_arg $inverse -f" [ -n "$link" ] && cmd="$cmd | filter_arg $inverse -L" +[ -n "$broken" ] && cmd="$cmd | filter_broken $inverse" [ -n "$exists" ] && cmd="$cmd | filter_arg $inverse -e" [ -n "$nonzero" ] && cmd="$cmd | filter_arg $inverse -s" eval "$cmd" From ee336996084b89560b389bc2ad3bac9805f17113 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 10 Sep 2025 11:31:45 +0400 Subject: [PATCH 11/91] =?UTF-8?q?template:=20pallier=20les=20probl=C3=A8me?= =?UTF-8?q?s=20d'I/O?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bash/src/template.sh | 94 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 11 deletions(-) diff --git a/bash/src/template.sh b/bash/src/template.sh index c3d3376..f97506c 100644 --- a/bash/src/template.sh +++ b/bash/src/template.sh @@ -22,7 +22,12 @@ et \$2 vaudra alors 'file' si un fichier \${2#.}.local existe (e.g 'file.ext.local'), prendre ce fichier à la place comme source -Ajouter file au tableau userfiles" +Ajouter file au tableau userfiles + +retourner: +- 0 en cas de copie avec succès +- 2 si la source n'existe pas +- 3 si une erreur I/O s'est produite lors de la copie" function template_copy_replace() { local src="$1" dest="$2" local srcdir srcname lsrcname @@ -37,8 +42,28 @@ function template_copy_replace() { lsrcname="${srcname#.}.local" [ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname" + [ -e "$src" ] || return 2 + userfiles+=("$dest") - cp -P "$src" "$dest" + local have_backup + if [ -e "$dest" ]; then + # copie de sauvegarde avant + if ! cp -P --preserve=all "$dest" "$dest.bck.$$"; then + rm "$dest.bck.$$" + return 3 + fi + have_backup=1 + fi + if ! cp -P "$src" "$dest"; then + rm "$dest" + if [ -n "$have_backup" ]; then + # restaurer la sauvegarde en cas d'erreur + cp -P --preserve=all "$dest.bck.$$" "$dest" && + rm "$dest.bck.$$" + fi + return 3 + fi + [ -n "$have_backup" ] && rm "$dest.bck.$$" return 0 } @@ -51,7 +76,13 @@ et \$2 vaudra alors 'file' si un fichier \${1#.}.local existe (e.g 'file.ext.local'), prendre ce fichier à la place comme source -Ajouter file au tableau userfiles" +Ajouter file au tableau userfiles + +retourner: +- 0 en cas de copie avec succès +- 1 si le fichier existait déjà +- 2 si la source n'existe pas +- 3 si une erreur I/O s'est produite lors de la copie" function template_copy_missing() { local src="$1" dest="$2" local srcdir srcname lsrcname @@ -63,15 +94,33 @@ function template_copy_missing() { dest="$srcdir/$dest" fi - userfiles+=("$dest") - if [ ! -e "$dest" ]; then - lsrcname="${srcname#.}.local" - [ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname" + lsrcname="${srcname#.}.local" + [ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname" - cp -P "$src" "$dest" - return 0 + [ -e "$src" ] || return 2 + + userfiles+=("$dest") + [ -e "$dest" ] && return 1 + + if ! cp -P "$src" "$dest"; then + # ne pas garder le fichier en cas d'erreur de copie + rm "$dest" + return 3 fi - return 1 + return 0 +} + +function: template_ioerror "\ +tester si une erreur de copie s'est produite lors de l'appel à +template_copy_missing() ou template_copy_replace(), par exemple en cas de +dépassement de capacité du disque ou si le fichier source n'existe pas + +il faut appeler cette fonction avec la valeur de retour de ces fonctions, e.g + template_copy_missing file + template_ioerror $? && die" +function template_ioerror() { + local r="${1:-$?}" + [ $r -ge 2 ] } function: template_dump_vars "\ @@ -219,8 +268,13 @@ function _template_can_process() { esac } +function: template_process_userfiles "\ +retourner: +- 0 en cas de traitement avec succès des fichiers +- 3 si une erreur I/O s'est produite lors du traitement d'un des fichiers" function template_process_userfiles() { local awkscript sedscript workfile userfile + local have_backup ac_set_tmpfile awkscript ac_set_tmpfile sedscript template_generate_scripts "$awkscript" "$sedscript" "$@" @@ -231,10 +285,28 @@ function template_process_userfiles() { if cat "$userfile" | awk -f "$awkscript" | sed -rf "$sedscript" >"$workfile"; then if testdiff "$workfile" "$userfile"; then # n'écrire le fichier que s'il a changé - cat "$workfile" >"$userfile" + if [ -e "$userfile" ]; then + # copie de sauvegarde avant + if ! cp -P --preserve=all "$userfile" "$userfile.bck.$$"; then + rm "$userfile.bck.$$" + return 3 + fi + have_backup=1 + fi + if ! cat "$workfile" >"$userfile"; then + rm "$userfile" + if [ -n "$have_backup" ]; then + # restaurer la sauvegarde en cas d'erreur + cp -P --preserve=all "$userfile.bck.$$" "$userfile" && + rm "$userfile.bck.$$" + fi + return 3 + fi + [ -n "$have_backup" ] && rm "$userfile.bck.$$" fi fi done ac_clean "$awkscript" "$sedscript" "$workfile" + return 0 } From 6b7d4b683d8f1648a609a2ab704aef7bfd1729e7 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 10 Sep 2025 22:00:43 +0400 Subject: [PATCH 12/91] =?UTF-8?q?inclure=20une=20version=20simplifi=C3=A9e?= =?UTF-8?q?=20de=20nulib/mail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/php.xml | 17 + composer.json | 3 + composer.lock | 1377 ++++++++++++++++++++++++++- php/src/mail/MailTemplate.php | 120 +++ php/src/mail/MailTemplateHelper.php | 22 + php/src/mail/MailerException.php | 7 + php/src/mail/mailer.php | 188 ++++ php/src/mail/mdc.php | 22 + php/tbin/mail.php | 26 + php/tbin/test_mail.php | 19 + php/tests/mail/MailTemplateTest.php | 34 + 11 files changed, 1791 insertions(+), 44 deletions(-) create mode 100644 php/src/mail/MailTemplate.php create mode 100644 php/src/mail/MailTemplateHelper.php create mode 100644 php/src/mail/MailerException.php create mode 100644 php/src/mail/mailer.php create mode 100644 php/src/mail/mdc.php create mode 100755 php/tbin/mail.php create mode 100644 php/tbin/test_mail.php create mode 100644 php/tests/mail/MailTemplateTest.php diff --git a/.idea/php.xml b/.idea/php.xml index 7e6be21..4114efb 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -55,6 +55,23 @@ + + + + + + + + + + + + + + + + + diff --git a/composer.json b/composer.json index 72c693e..76a20f7 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,9 @@ }, "require": { "symfony/yaml": "^5.0", + "phpmailer/phpmailer": "^6.8", + "symfony/expression-language": "^5.4", + "league/commonmark": "^2.4", "ext-json": "*", "php": "^7.4" }, diff --git a/composer.lock b/composer.lock index eb7f2b1..c69c6c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,874 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "71744d15224f445d1aeefe16ec7d1099", + "content-hash": "424dc194faea590269d136c8ffaf2505", "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "league/commonmark", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2025-07-20T12:47:49+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.5", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": "7.1 - 8.3" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.5" + }, + "time": "2023-10-05T20:37:59+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.10", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2", + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.4" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.10" + }, + "time": "2023-07-30T15:38:18+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.10.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144", + "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.10.0" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2025-04-24T15:19:31+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "symfony/cache", + "version": "v5.4.46", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/0fe08ee32cec2748fbfea10c52d3ee02049e0f6b", + "reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "provide": { + "psr/cache-implementation": "1.0|2.0", + "psr/simple-cache-implementation": "1.0|2.0", + "symfony/cache-implementation": "1.0|2.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "^1.6|^2.0", + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v5.4.46" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-04T11:43:55+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/517c3a3619dadfa6952c4651767fcadffb4df65e", + "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0|^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v2.5.4", @@ -73,9 +939,72 @@ ], "time": "2024-09-25T14:11:13+00:00" }, + { + "name": "symfony/expression-language", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "a784b66edc4c151eb05076d04707906ee2c209a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/a784b66edc4c151eb05076d04707906ee2c209a9", + "reference": "a784b66edc4c151eb05076d04707906ee2c209a9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an engine that can compile and evaluate expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/expression-language/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-04T14:55:40+00:00" + }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -134,7 +1063,258 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" }, "funding": [ { @@ -150,7 +1330,80 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "862700068db0ddfd8c5b850671e029a90246ec75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/862700068db0ddfd8c5b850671e029a90246ec75", + "reference": "862700068db0ddfd8c5b850671e029a90246ec75", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/yaml", @@ -301,16 +1554,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.3", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -349,7 +1602,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -357,20 +1610,20 @@ "type": "tidelift" } ], - "time": "2025-07-05T12:25:42+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -389,7 +1642,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -413,9 +1666,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "nulib/tests", @@ -894,16 +2147,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.23", + "version": "9.6.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" + "reference": "049c011e01be805202d8eebedef49f769a8ec7b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", - "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/049c011e01be805202d8eebedef49f769a8ec7b7", + "reference": "049c011e01be805202d8eebedef49f769a8ec7b7", "shasum": "" }, "require": { @@ -914,7 +2167,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", @@ -925,11 +2178,11 @@ "phpunit/php-timer": "^5.0.3", "sebastian/cli-parser": "^1.0.2", "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.8", + "sebastian/comparator": "^4.0.9", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", "sebastian/exporter": "^4.0.6", - "sebastian/global-state": "^5.0.7", + "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", "sebastian/type": "^3.2.1", @@ -977,7 +2230,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.25" }, "funding": [ { @@ -1001,7 +2254,7 @@ "type": "tidelift" } ], - "time": "2025-05-02T06:40:34+00:00" + "time": "2025-08-20T14:38:31+00:00" }, { "name": "sebastian/cli-parser", @@ -1172,16 +2425,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "4.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", "shasum": "" }, "require": { @@ -1234,15 +2487,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2025-08-10T06:51:50+00:00" }, { "name": "sebastian/complexity", @@ -1509,16 +2774,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "5.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", "shasum": "" }, "require": { @@ -1561,15 +2826,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2025-08-10T07:10:35+00:00" }, { "name": "sebastian/lines-of-code", @@ -1742,16 +3019,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", "shasum": "" }, "require": { @@ -1793,15 +3070,27 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2025-08-10T06:57:39+00:00" }, { "name": "sebastian/resource-operations", diff --git a/php/src/mail/MailTemplate.php b/php/src/mail/MailTemplate.php new file mode 100644 index 0000000..ee1739d --- /dev/null +++ b/php/src/mail/MailTemplate.php @@ -0,0 +1,120 @@ + "string", + "body" => "string", + "exprs" => "array", + ]; + + function __construct(array $mail) { + $tsubject = $mail["subject"] ?? null; + $tbody = $mail["body"] ?? null; + $texprs = $mail["exprs"] ?? []; + + $this->el = new ExpressionLanguage(); + ValueException::check_null($this->subject = $tsubject, "subject"); + ValueException::check_null($this->body = $tbody, "body"); + $exprs = []; + # Commencer par extraire les expressions de la forme {name} + if (preg_match_all('/\{([a-zA-Z_][a-zA-Z0-9_.-]*)}/', $this->body, $mss, PREG_SET_ORDER)) { + foreach ($mss as $ms) { + $key = $ms[0]; + $expr = str_replace("'", "\\'", $ms[1]); + $expr = "_helper.value('$expr')"; + $exprs[$key] = $expr; + } + } + $index = 0; + foreach ($texprs as $key => $expr) { + $prefix = null; + $orig = $expr; + if (preg_match('/^\[([^]]*)]/', $expr, $ms)) { + # un préfixe spécifié de la forme [prefix]expr permet de reconnaitre les + # formes spéciales de expr (+, *, .) qui sont précédées de prefix + # exemple: [https://]+app.url permettra d'utiliser un texte markdown + # de la forme qui est correctement reconnu comme un + # url + $prefix = $ms[1]; + $expr = substr($expr, strlen($ms[0])); + } + $mapKey = false; + if (str::del_prefix($expr, "+")) { + # config + $mapKey = "$prefix+$expr"; + $expr = str_replace("'", "\\'", $expr); + $expr = "_helper.config('$expr')"; + } elseif (str::del_prefix($expr, "*")) { + # session + $mapKey = "$prefix*$expr"; + $expr = str_replace("'", "\\'", $expr); + $expr = "_helper.session('$expr')"; + } elseif (str::del_prefix($expr, ".")) { + # session + $mapKey = "$prefix.$expr"; + $expr = str_replace("'", "\\'", $expr); + $expr = "_helper.value('$expr')"; + } elseif ($prefix !== null) { + # sinon remettre le préfixe + $expr = $orig; + } + + if ($key === $index) { + $index++; + if ($mapKey !== false) { + $exprs[$mapKey] = $expr; + } else { + # clé normale: la correspondance est en minuscule + $exprs[$expr] = strtolower($expr); + } + } else { + $exprs[$key] = $expr; + } + } + uksort($exprs, function ($a, $b) { + return -cv::complen($a, $b); + }); + $this->exprs = $exprs; + } + + /** @var ExpressionLanguage */ + protected $el; + + protected $subject; + + protected $body; + + protected $exprs; + + protected function _eval(string $template, ?array $data): string { + if ($data === null) return $template; + $el = $this->el; + foreach ($this->exprs as $key => $expr) { + $value = $el->evaluate($expr, $data); + if (is_array($value)) $value = str::join(" ", $value); + elseif (!is_string($value)) $value = strval($value); + $template = str_replace($key, $value, $template); + } + return $template; + } + + function eval(?array $data, $convertMd=true): array { + if ($data !== null) { + $data["_helper"] = new MailTemplateHelper($data); + } + $subject = $this->_eval($this->subject, $data); + $body = $this->body; + if ($convertMd) $body = mdc::convert($body); + $body = $this->_eval($body, $data); + return [ + "subject" => $subject, + "body" => $body, + ]; + } +} diff --git a/php/src/mail/MailTemplateHelper.php b/php/src/mail/MailTemplateHelper.php new file mode 100644 index 0000000..0633749 --- /dev/null +++ b/php/src/mail/MailTemplateHelper.php @@ -0,0 +1,22 @@ +data = $data; + } + + function value(string $pkey) { + return cl::pget($this->data, $pkey); + } + + function config(string $pkey) { + return config::get($pkey); + } + + function session(string $pkey) { + return session::pget($pkey); + } +} diff --git a/php/src/mail/MailerException.php b/php/src/mail/MailerException.php new file mode 100644 index 0000000..79e9261 --- /dev/null +++ b/php/src/mail/MailerException.php @@ -0,0 +1,7 @@ + ["string", "smtp"], + "debug" => ["int", SMTP::DEBUG_OFF], + "host" => ["?string", "smtp.univ.run"], + "port" => ["?int", 25], + "auth" => "?bool", + "username" => "?string", + "password" => "?string", + "secure" => "?string", + ]; + + static function get(?array $params=null, ?bool $exceptions=null): PHPMailer { + $mailer = new PHPMailer($exceptions); + $mailer->setLanguage("fr"); + $mailer->CharSet = PHPMailer::CHARSET_UTF8; + # backend + $backend = $params["backend"] ?? null; + $backend ??= cv::vn(getenv("NULIB_MAIL_BACKEND")); + $backend ??= "smtp"; + switch ($backend) { + case "smtp": + # host + $host = $params["host"] ?? null; + $host ??= cv::vn(getenv("NULIB_MAIL_HOST")); + # port + $port = $params["port"] ?? null; + $port ??= cv::vn(getenv("NULIB_MAIL_PORT")); + $port ??= 25; + if ($host === null) { + throw new ValueException("mail host is required"); + } + msg::debug("new PHPMailer using SMTP to $host:$port"); + $mailer->isSMTP(); + $mailer->Host = $host; + $mailer->Port = $port; + break; + case "phpmail": + msg::debug("new PHPMailer using PHPmail"); + $mailer->isMail(); + break; + case "sendmail": + msg::debug("new PHPMailer using sendmail"); + $mailer->isSendmail(); + break; + default: + throw ValueException::invalid_value($backend, "mailer backend"); + } + # debug + $debug = $params["debug"] ?? null; + $debug ??= cv::vn(getenv("NULIB_MAIL_DEBUG")); + $debug ??= SMTP::DEBUG_OFF; + if (is_int($debug)) { + if ($debug < SMTP::DEBUG_OFF) $debug = SMTP::DEBUG_OFF; + elseif ($debug > SMTP::DEBUG_LOWLEVEL) $debug = SMTP::DEBUG_LOWLEVEL; + } elseif (!self::is_bool($debug)) { + throw ValueException::invalid_value($debug, "debug mode"); + } + $mailer->SMTPDebug = $debug; + # auth, username, password + $username = $params["username"] ?? null; + $username ??= cv::vn(getenv("NULIB_MAIL_USERNAME")); + $password = $params["password"] ?? null; + $password ??= cv::vn(getenv("NULIB_MAIL_PASSWORD")); + $auth = $params["auth"] ?? null; + $auth ??= cv::vn(getenv("NULIB_MAIL_AUTH")); + $auth ??= $username !== null && $password !== null; + $mailer->SMTPAuth = self::get_bool($auth); + $mailer->Username = $username; + $mailer->Password = $password; + # secure + $secure = $params["secure"] ?? null; + $secure ??= cv::vn(getenv("NULIB_MAIL_SECURE")); + $secure ??= false; + if (self::is_bool($secure)) { + if (!$secure) { + $mailer->SMTPSecure = ""; + $mailer->SMTPAutoTLS = false; + } + } else { + switch ($secure) { + case PHPMailer::ENCRYPTION_SMTPS: + case PHPMailer::ENCRYPTION_STARTTLS: + $mailer->SMTPSecure = $secure; + break; + default: + throw ValueException::invalid_value($secure, "encryption mode"); + } + } + + return $mailer; + } + + static function build($to, string $subject, string $body, $cc=null, $bcc=null, ?string $from=null, ?PHPMailer $mailer=null): PHPMailer { + if ($mailer === null) $mailer = self::get(); + $mailer->clearAllRecipients(); + + if ($from === null) $from = static::FROM; + $mailer->setFrom($from); + foreach (cl::with($to) as $tos) { + foreach (preg_split('/\s*[,;]\s*/', trim($tos)) as $to) { + $mailer->addAddress($to); + } + } + foreach (cl::with($cc) as $ccs) { + foreach (preg_split('/\s*[,;]\s*/', trim($ccs)) as $cc) { + $mailer->addCC($cc); + } + } + foreach (cl::with($bcc) as $bccs) { + foreach (preg_split('/\s*[,;]\s*/', trim($bccs)) as $bcc) { + $mailer->addBCC($bcc); + } + } + $mailer->isHTML(); + $mailer->Subject = $subject; + $mailer->Body = $body; + return $mailer; + } + + static function _send(PHPMailer $mailer): void { + $tos = []; + foreach ($mailer->getToAddresses() as $to) { + $tos[] = $to[0]; + } + $tos = str::join(",", $tos); + msg::debug("Sending to $tos"); + if (!$mailer->send()) { + throw new MailerException("Une erreur s'est produite pendant l'envoi du mail", $mailer->ErrorInfo); + } + } + + static function send($to, string $subject, string $body, $cc=null, $bcc=null, ?string $from=null, ?PHPMailer $mailer=null): void { + self::_send(self::build($to, $subject, $body, $cc, $bcc, $from, $mailer)); + } + + static function tsend(array $template, array $data, $to, $cc=null, $bcc=null, ?string $from=null): void { + $template = new MailTemplate($template); + $mail = $template->eval($data); + self::send($to, $mail["subject"], $mail["body"], $cc, $bcc, $from); + } +} diff --git a/php/src/mail/mdc.php b/php/src/mail/mdc.php new file mode 100644 index 0000000..204264e --- /dev/null +++ b/php/src/mail/mdc.php @@ -0,0 +1,22 @@ + false, + ]); + } + return self::$mdc; + } + + static function convert(string $text): string { + return self::mdc()->convert($text); + } +} diff --git a/php/tbin/mail.php b/php/tbin/mail.php new file mode 100755 index 0000000..eda2794 --- /dev/null +++ b/php/tbin/mail.php @@ -0,0 +1,26 @@ +#!/usr/bin/php + parent::ARGS, + ["-t", "--to", "args" => 1, "action" => "--add", "name" => "to"], + ["-c", "--cc", "args" => 1, "action" => "--add", "name" => "cc"], + ["-b", "--bcc", "args" => 1, "action" => "--add", "name" => "bcc"], + ["-F", "--from", "args" => 1, "name" => "from"], + ["args" => 2, "name" => "args"], + ]; + + protected $to, $cc, $bcc, $from; + protected $args; + + function main() { + $subject = $this->args[0]; + $body = $this->args[1]; + mailer::send($this->to, $subject, $body, $this->cc, $this->bcc, $this->from); + } +}); diff --git a/php/tbin/test_mail.php b/php/tbin/test_mail.php new file mode 100644 index 0000000..d37b25b --- /dev/null +++ b/php/tbin/test_mail.php @@ -0,0 +1,19 @@ + "test de mail", + "body" => << "moi même", +]; +mailer::tsend($template, $data, "jephte.clain@gmail.com"); diff --git a/php/tests/mail/MailTemplateTest.php b/php/tests/mail/MailTemplateTest.php new file mode 100644 index 0000000..0a462f6 --- /dev/null +++ b/php/tests/mail/MailTemplateTest.php @@ -0,0 +1,34 @@ + "infos pour NOM PRENOM", + "body" => << [ + "PRENOM" => "prenom", + "NOM" => "nom", + "AGE" => "age", + ], + ]; + + $tpl = new MailTemplate($mail); + [ + "subject" => $subject, + "body" => $body, + ] = $tpl->eval([ + "nom" => "Clain", + "prenom" => "Jephté", + "age" => 47, + ]); + self::assertSame("infos pour Clain Jephté", $subject); + self::assertSame("

bonjour Jephté Clain,

\n

vous avez 47 ans

\n", $body); + } +} From c6ffb456f21a109e9ffe97de0834caa23df8660e Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 12 Sep 2025 16:36:23 +0400 Subject: [PATCH 13/91] maj projet --- .idea/nulib-base.iml | 1 - 1 file changed, 1 deletion(-) diff --git a/.idea/nulib-base.iml b/.idea/nulib-base.iml index 55df4ce..c88c3c8 100644 --- a/.idea/nulib-base.iml +++ b/.idea/nulib-base.iml @@ -4,7 +4,6 @@ - From 328169f6c5aef7ee43d2370baa9c8b1e4a720d7f Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 15 Sep 2025 22:51:20 +0400 Subject: [PATCH 14/91] ajout cl::split_assoc --- php/src/cl.php | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/php/src/cl.php b/php/src/cl.php index f0919ea..b15ed55 100644 --- a/php/src/cl.php +++ b/php/src/cl.php @@ -923,4 +923,48 @@ class cl { A::usort($array, $keys, $assoc); return $array; } + + ############################################################################# + + /** + * Extraire d'un tableau les clés séquentielles et les clés associatives + * + * Retourner une liste [$list, $assoc] où $list est un tableau avec uniquement + * les valeurs des clés séquentielles et $assoc est un tableau avec uniquement + * les valeurs des clés associatives. S'il n'existe aucune clé séquentielle + * (resp. aucune clé associative), $list (resp. $assoc) vaut null. + * + * Par exemple: split_assoc(["a", "b" => "c"]) retourne [["a"], ["b" => "c"]] + */ + static final function split_assoc(?array $array): array { + $list = null; + $assoc = null; + if ($array !== null) { + $i = 0; + foreach ($array as $key => $value) { + if ($key === $i) { + $list[] = $value; + $i++; + } else { + $assoc[$key] = $value; + } + } + } + return [$list, $assoc]; + } + + /** + * Joindre en un seul tableau un tableau avec des clés séquentielles et un + * tableau avec des clés associatives. + * + * Si $list_first==true, les clés séquentielles arrivent d'abord, ensuite les + * clés associatives. Sinon, ce sont les clés associatives qui arrivent d'abord + */ + static final function merge_assoc(?array &$array, ?array $list, ?array $assoc, bool $list_first=false): void { + if ($list === null && $assoc === null) $array = []; + elseif ($list === null) $array = $assoc; + elseif ($assoc === null) $array = $list; + elseif ($list_first) $array = array_merge($list, $assoc); + else $array = array_merge($assoc, $list); + } } From 07b9324de8522b86f2629305be439193d133d664 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 29 Sep 2025 11:46:40 +0400 Subject: [PATCH 15/91] ajout str::indent() --- php/src/str.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/php/src/str.php b/php/src/str.php index 6e09c51..689c9d0 100644 --- a/php/src/str.php +++ b/php/src/str.php @@ -392,6 +392,18 @@ class str { return implode($glue, $pieces); } + /** + * indenter chaque ligne de $text + */ + static function indent(?string $text, string $indent=" "): ?string { + if ($text === null) return null; + $indented = []; + foreach (explode("\n", $text) as $line) { + $indented[] = "$indent$line"; + } + return implode("\n", $indented); + } + const CAMEL_PATTERN0 = '/([A-Z0-9]+)$/A'; const CAMEL_PATTERN1 = '/([A-Z0-9]+)[A-Z]/A'; const CAMEL_PATTERN2 = '/([A-Z]?[^A-Z]+)/A'; From 7e228fc3e62aee575eb1a8909e4833ac75670100 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 29 Sep 2025 22:53:12 +0400 Subject: [PATCH 16/91] modifs.mineures sans commentaires --- TODO.md | 4 ++++ wip/{pman.md => TODO.md} | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) rename wip/{pman.md => TODO.md} (95%) diff --git a/TODO.md b/TODO.md index 8eea214..4531d55 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,7 @@ +# nulib + +* [wip](wip/TODO.md) + # nulib/bash * [nulib/bash](bash/TODO.md) diff --git a/wip/pman.md b/wip/TODO.md similarity index 95% rename from wip/pman.md rename to wip/TODO.md index 50460ec..d2bc928 100644 --- a/wip/pman.md +++ b/wip/TODO.md @@ -1,13 +1,6 @@ -# pman +# TODO -outil pour gérer les projets PHP -* p, pci, pp, pu: gestion courante git. - ces outils peuvent agir sur un ensemble de projets, notamment tous les - projets dépendants du projet courant -* pver: gestion des versions. - calculer la prochaine version en respectant semver - -## scripts de gestion de projet +## pman définir précisément le rôle des scripts * pdist: créer la branche DIST, basculer dessus, merger MAIN dans DIST @@ -24,4 +17,14 @@ il faudra supprimer * pman: fonctionnalités réparties dans les autres scripts spécialisés * pmer: fonctionnalités réperties dans les autres scripts spécialisés +### divers + +outil pour gérer les projets PHP +* p, pci, pp, pu: gestion courante git. + ces outils peuvent agir sur un ensemble de projets, notamment tous les + projets dépendants du projet courant +* pver: gestion des versions. + calculer la prochaine version en respectant semver + + -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file From f458fbb3e7376e98a994d17590095fb02b5b2cfb Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 30 Sep 2025 08:18:42 +0400 Subject: [PATCH 17/91] modifs.mineures sans commentaires --- .idea/php-docker-settings.xml | 15 +++++++++++++++ php/src/output/std/StdMessenger.php | 1 + 2 files changed, 16 insertions(+) diff --git a/.idea/php-docker-settings.xml b/.idea/php-docker-settings.xml index bd786be..047d43d 100644 --- a/.idea/php-docker-settings.xml +++ b/.idea/php-docker-settings.xml @@ -17,6 +17,21 @@ + + + + + + +
diff --git a/php/src/output/std/StdMessenger.php b/php/src/output/std/StdMessenger.php index 3892a0d..c163aeb 100644 --- a/php/src/output/std/StdMessenger.php +++ b/php/src/output/std/StdMessenger.php @@ -424,6 +424,7 @@ class StdMessenger implements _IMessenger { # tout d'abord userMessage if ($exception instanceof UserException) { $userMessage = UserException::get_user_message($exception); + $userMessage ??= "Une erreur technique s'est produite"; $showSummary = true; } else { $userMessage = UserException::get_summary($exception); From 8cfcaae5b86f8499dd6a3f12a07f21a662809c81 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 30 Sep 2025 17:25:16 +0400 Subject: [PATCH 18/91] maj package nulib/app --- php/src/app/RunFile.php | 2 +- php/src/app/cli/include-launcher.php | 2 +- php/src/os/sh.php | 2 +- runphp/phpwrapper-_bg_launcher.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/php/src/app/RunFile.php b/php/src/app/RunFile.php index bd34357..b336c34 100644 --- a/php/src/app/RunFile.php +++ b/php/src/app/RunFile.php @@ -2,8 +2,8 @@ namespace nulib\app; use nulib\A; -use nulib\app; use nulib\cl; +use nulib\app\app; use nulib\file\SharedFile; use nulib\os\path; use nulib\os\sh; diff --git a/php/src/app/cli/include-launcher.php b/php/src/app/cli/include-launcher.php index 0958cb7..d0d04b5 100644 --- a/php/src/app/cli/include-launcher.php +++ b/php/src/app/cli/include-launcher.php @@ -3,7 +3,7 @@ # les constantes suivantes doivent être définies AVANT de chager ce script: # - NULIB_APP_app_params : paramètres du projet -use nulib\app; +use nulib\app\app; use nulib\os\path; if ($argc <= 1) die("invalid arguments"); diff --git a/php/src/os/sh.php b/php/src/os/sh.php index a18f012..802ca36 100644 --- a/php/src/os/sh.php +++ b/php/src/os/sh.php @@ -1,7 +1,7 @@ Date: Thu, 2 Oct 2025 19:12:23 +0400 Subject: [PATCH 19/91] ajout func::get_all(), call_all() --- php/src/php/func.php | 90 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/php/src/php/func.php b/php/src/php/func.php index fcec77b..1c0123c 100644 --- a/php/src/php/func.php +++ b/php/src/php/func.php @@ -545,6 +545,92 @@ class func { else return cl::withn($value); } + const MASK_PS = ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC; + const MASK_P = ReflectionMethod::IS_PUBLIC; + const METHOD_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC; + const METHOD_P = ReflectionMethod::IS_PUBLIC; + + private static function match_name(string $name, array $includes, array $excludes): bool { + if ($includes) { + $matches = false; + foreach ($includes as $include) { + if (substr($include, 0, 1) == "/") { + # expression régulière + if (preg_match($include, $name)) { + $matches = true; + break; + } + } else { + # tester la présence de la sous-chaine + if (strpos($name, $include) !== false) { + $matches = true; + break; + } + } + } + if (!$matches) return false; + } + foreach ($excludes as $exclude) { + if (substr($exclude, 0, 1) == "/") { + # expression régulière + if (preg_match($exclude, $name)) return false; + } else { + # tester la présence de la sous-chaine + if (strpos($name, $exclude) !== false) return false; + } + } + return true; + } + + /** + * @return self[] + */ + static function get_all($class_or_object, ?array $params=null): array { + $prefix = $params["prefix"] ?? ""; + $length = strlen($prefix); + $args = $params["args"] ?? null; + $staticOnly = $params["static_only"] ?? false; + $includes = cl::with($params["include"] ?? null); + $excludes = cl::with($params["exclude"] ?? null); + + if (is_string($class_or_object)) { + # lister les méthodes publiques statiques de la classe + $mask = self::MASK_PS; + $expected = self::METHOD_PS; + $c = new ReflectionClass($class_or_object); + } elseif (is_object($class_or_object)) { + # lister les méthodes publiques de la classe + $c = new ReflectionClass($class_or_object); + $mask = $staticOnly? self::MASK_PS: self::MASK_P; + $expected = $staticOnly? self::METHOD_PS: self::METHOD_P; + } else { + throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet"); + } + $methods = []; + foreach ($c->getMethods() as $m) { + if (($m->getModifiers() & $mask) != $expected) continue; + $name = $m->getName(); + if (substr($name, 0, $length) != $prefix) continue; + if (!self::match_name($name, $includes, $excludes)) continue; + $method = [$class_or_object, $name]; + $methods[] = self::with($method, $args); + } + return $methods; + } + + /** + * Appeler toutes les méthodes publiques de $object_or_class et retourner un + * tableau [$method_name => $return_value] des valeurs de retour. + */ + static function call_all($class_or_object, ?array $params=null) { + $methods = self::get_all($class_or_object, $params); + $values = []; + foreach ($methods as $method) { + $values[$method->getName()] = $method->invoke(); + } + return $values; + } + ############################################################################# protected function __construct(int $type, $func, ?array $args=null, bool $bound=false, ?string $reason=null) { @@ -598,6 +684,10 @@ class func { protected ?array $func; + function getName(): ?string { + return $this->func[1] ?? null; + } + protected bool $bound; protected ?string $reason; From efb7901498e7d998af96ebef7fedb9b74990934e Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 01:44:49 +0400 Subject: [PATCH 20/91] migration d'outils depuis nur-ture --- .idea/nulib-base.iml | 1 + bin/._pman-composer_local_deps.php | 4 +- bin/._pman-composer_select_profile.php | 4 +- bin/.dumpser.php | 1 + bin/.json2yml.php | 1 + bin/.mysql.capacitor.php | 1 + bin/.pgsql.capacitor.php | 1 + bin/.sqlite.capacitor.php | 1 + bin/.yml2json.php | 1 + bin/dumpser.php | 1 + bin/json2yml.php | 1 + bin/mysql.capacitor.php | 1 + bin/pgsql.capacitor.php | 1 + bin/sqlite.capacitor.php | 1 + bin/yml2json.php | 1 + composer.json | 11 +- composer.lock | 51 +- php/bin/dumpser.php | 7 + php/bin/json2yml.php | 7 + php/bin/mysql.capacitor.php | 7 + php/bin/pgsql.capacitor.php | 7 + php/bin/sqlite.capacitor.php | 7 + php/bin/yml2json.php | 7 + php/cli/AbstractCapacitorApp.php | 111 ++++ php/cli/BgLauncherApp.php | 122 ++++ php/cli/DumpserApp.php | 31 + php/cli/Json2yamlApp.php | 21 + php/cli/MysqlCapacitorApp.php | 45 ++ php/cli/PgsqlCapacitorApp.php | 45 ++ php/cli/SqliteCapacitorApp.php | 43 ++ php/cli/SteamTrainApp.php | 53 ++ php/cli/Yaml2jsonApp.php | 21 + php/{src/tools => cli}/pman/ComposerFile.php | 2 +- .../tools => cli}/pman/ComposerPmanFile.php | 2 +- php/src/app/app.php | 614 +++++++++++++++++ php/src/app/args/AbstractArgsParser.php | 109 +++ php/src/app/args/Aodef.php | 623 ++++++++++++++++++ php/src/app/args/Aogroup.php | 38 ++ php/src/app/args/Aolist.php | 271 ++++++++ php/src/app/args/Aosection.php | 47 ++ php/src/app/args/ArgsException.php | 20 + php/src/app/args/SimpleAolist.php | 189 ++++++ php/src/app/args/SimpleArgsParser.php | 250 +++++++ php/src/app/args/TODO.md | 21 + php/src/app/cli/Application.php | 383 +++++++++++ php/src/app/config.php | 41 ++ php/src/app/config/ArrayConfig.php | 50 ++ php/src/app/config/ConfigManager.php | 150 +++++ php/src/app/config/EnvConfig.php | 112 ++++ php/src/app/config/IConfig.php | 24 + php/src/app/config/JsonConfig.php | 13 + php/src/app/config/ProfileManager.php | 142 ++++ php/src/app/config/YamlConfig.php | 13 + php/src/php/types/varray.php | 24 + php/src/php/types/vbool.php | 54 ++ php/src/php/types/vcontent.php | 25 + php/src/php/types/vfloat.php | 22 + php/src/php/types/vfunc.php | 22 + php/src/php/types/vint.php | 22 + php/src/php/types/vkey.php | 26 + php/src/php/types/vpkey.php | 32 + php/src/php/types/vrawstring.php | 31 + php/src/php/types/vstring.php | 6 + php/src/php/types/vtext.php | 7 + php/tests/app/appTest.php | 132 ++++ php/tests/app/cli/AodefTest.php | 159 +++++ php/tests/app/cli/AolistTest.php | 63 ++ php/tests/app/cli/SimpleAolistTest.php | 59 ++ php/tests/app/cli/SimpleArgsParserTest.php | 175 +++++ php/tests/app/config/ConfigManagerTest.php | 124 ++++ 70 files changed, 4687 insertions(+), 27 deletions(-) create mode 120000 bin/.dumpser.php create mode 120000 bin/.json2yml.php create mode 120000 bin/.mysql.capacitor.php create mode 120000 bin/.pgsql.capacitor.php create mode 120000 bin/.sqlite.capacitor.php create mode 120000 bin/.yml2json.php create mode 120000 bin/dumpser.php create mode 120000 bin/json2yml.php create mode 120000 bin/mysql.capacitor.php create mode 120000 bin/pgsql.capacitor.php create mode 120000 bin/sqlite.capacitor.php create mode 120000 bin/yml2json.php create mode 100755 php/bin/dumpser.php create mode 100755 php/bin/json2yml.php create mode 100755 php/bin/mysql.capacitor.php create mode 100755 php/bin/pgsql.capacitor.php create mode 100755 php/bin/sqlite.capacitor.php create mode 100755 php/bin/yml2json.php create mode 100644 php/cli/AbstractCapacitorApp.php create mode 100644 php/cli/BgLauncherApp.php create mode 100644 php/cli/DumpserApp.php create mode 100644 php/cli/Json2yamlApp.php create mode 100644 php/cli/MysqlCapacitorApp.php create mode 100644 php/cli/PgsqlCapacitorApp.php create mode 100644 php/cli/SqliteCapacitorApp.php create mode 100644 php/cli/SteamTrainApp.php create mode 100644 php/cli/Yaml2jsonApp.php rename php/{src/tools => cli}/pman/ComposerFile.php (99%) rename php/{src/tools => cli}/pman/ComposerPmanFile.php (99%) create mode 100644 php/src/app/app.php create mode 100644 php/src/app/args/AbstractArgsParser.php create mode 100644 php/src/app/args/Aodef.php create mode 100644 php/src/app/args/Aogroup.php create mode 100644 php/src/app/args/Aolist.php create mode 100644 php/src/app/args/Aosection.php create mode 100644 php/src/app/args/ArgsException.php create mode 100644 php/src/app/args/SimpleAolist.php create mode 100644 php/src/app/args/SimpleArgsParser.php create mode 100644 php/src/app/args/TODO.md create mode 100644 php/src/app/cli/Application.php create mode 100644 php/src/app/config.php create mode 100644 php/src/app/config/ArrayConfig.php create mode 100644 php/src/app/config/ConfigManager.php create mode 100644 php/src/app/config/EnvConfig.php create mode 100644 php/src/app/config/IConfig.php create mode 100644 php/src/app/config/JsonConfig.php create mode 100644 php/src/app/config/ProfileManager.php create mode 100644 php/src/app/config/YamlConfig.php create mode 100644 php/src/php/types/varray.php create mode 100644 php/src/php/types/vbool.php create mode 100644 php/src/php/types/vcontent.php create mode 100644 php/src/php/types/vfloat.php create mode 100644 php/src/php/types/vfunc.php create mode 100644 php/src/php/types/vint.php create mode 100644 php/src/php/types/vkey.php create mode 100644 php/src/php/types/vpkey.php create mode 100644 php/src/php/types/vrawstring.php create mode 100644 php/src/php/types/vstring.php create mode 100644 php/src/php/types/vtext.php create mode 100644 php/tests/app/appTest.php create mode 100644 php/tests/app/cli/AodefTest.php create mode 100644 php/tests/app/cli/AolistTest.php create mode 100644 php/tests/app/cli/SimpleAolistTest.php create mode 100644 php/tests/app/cli/SimpleArgsParserTest.php create mode 100644 php/tests/app/config/ConfigManagerTest.php diff --git a/.idea/nulib-base.iml b/.idea/nulib-base.iml index c88c3c8..419ada4 100644 --- a/.idea/nulib-base.iml +++ b/.idea/nulib-base.iml @@ -4,6 +4,7 @@ + diff --git a/bin/._pman-composer_local_deps.php b/bin/._pman-composer_local_deps.php index 92aeda8..c99d615 100755 --- a/bin/._pman-composer_local_deps.php +++ b/bin/._pman-composer_local_deps.php @@ -2,9 +2,7 @@ |<|>|<=|>=|(?:is\s+)?null|(?:is\s+)?not\s+null)\s*(.*)$/', $arg, $ms); + } + + protected function storageCtl(CapacitorStorage $storage): void { + $args = $this->args; + + $channelClass = $this->channelClass; + $tableName = $this->tableName; + if ($channelClass === null && $tableName === null) { + $name = A::shift($args); + if ($name !== null) { + if (!$storage->channelExists($name, $row)) { + self::die("$name: nom de canal de données introuvable"); + } + if ($row["class_name"] !== "class@anonymous") $channelClass = $row["class_name"]; + else $tableName = $row["table_name"]; + } + } + if ($channelClass !== null) { + $channelClass = str_replace("/", "\\", $channelClass); + $channel = new $channelClass; + } elseif ($tableName !== null) { + $channel = new class($tableName) extends CapacitorChannel { + function __construct(?string $name=null) { + parent::__construct($name); + $this->tableName = $name; + } + }; + } else { + $found = false; + foreach ($storage->getChannels() as $row) { + msg::print($row["name"]); + $found = true; + } + if ($found) self::exit(); + self::die("Vous devez spécifier le canal de données"); + } + $capacitor = new Capacitor($storage, $channel); + + switch ($this->action) { + case self::ACTION_RESET: + $capacitor->reset($this->recreate); + break; + case self::ACTION_QUERY: + if (!$args) { + # lister les id + $out = new Stream(STDOUT); + $primaryKeys = $storage->getPrimaryKeys($channel); + $rows = $storage->db()->all([ + "select", + "cols" => $primaryKeys, + "from" => $channel->getTableName(), + ]); + $out->fputcsv($primaryKeys); + foreach ($rows as $row) { + $rowIds = $storage->getRowIds($channel, $row); + $out->fputcsv($rowIds); + } + } else { + # afficher les lignes correspondantes + if (count($args) == 1 && !self::isa_cond($args[0])) { + $filter = $args[0]; + } else { + $filter = []; + $ms = null; + foreach ($args as $arg) { + if (self::isa_cond($arg, $ms)) { + $filter[$ms[1]] = [$ms[2], $ms[3]]; + } else { + $filter[$arg] = ["not null"]; + } + } + } + $first = true; + $capacitor->each($filter, function ($row) use (&$first) { + if ($first) $first = false; + else echo "---\n"; + yaml::dump($row); + }); + } + break; + case self::ACTION_SQL: + echo $capacitor->getCreateSql()."\n"; + break; + } + } +} diff --git a/php/cli/BgLauncherApp.php b/php/cli/BgLauncherApp.php new file mode 100644 index 0000000..3f965fd --- /dev/null +++ b/php/cli/BgLauncherApp.php @@ -0,0 +1,122 @@ + "lancer un script en tâche de fond", + "usage" => "ApplicationClass args...", + + "sections" => [ + parent::VERBOSITY_SECTION, + ], + + ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS, + "help" => "Afficher des informations sur la tâche", + ], + ["-s", "--start", "name" => "action", "value" => self::ACTION_START, + "help" => "Démarrer la tâche", + ], + ["-k", "--stop", "name" => "action", "value" => self::ACTION_STOP, + "help" => "Arrêter la tâche", + ], + ]; + + protected int $action = self::ACTION_START; + + static function show_infos(RunFile $runfile, ?int $level=null): void { + msg::print($runfile->getDesc(), $level); + msg::print(yaml::with(["data" => $runfile->read()]), ($level ?? 0) - 1); + } + + function main() { + $args = $this->args; + + $appClass = $args[0] ?? null; + if ($appClass === null) { + self::die("Vous devez spécifier la classe de l'application"); + } + $appClass = $args[0] = str_replace("/", "\\", $appClass); + if (!class_exists($appClass)) { + self::die("$appClass: classe non trouvée"); + } + + $useRunfile = constant("$appClass::USE_RUNFILE"); + if (!$useRunfile) { + self::die("Cette application ne supporte le lancement en tâche de fond"); + } + + $runfile = app::with($appClass)->getRunfile(); + switch ($this->action) { + case self::ACTION_START: + $argc = count($args); + $appClass::_manage_runfile($argc, $args, $runfile); + if ($runfile->warnIfLocked()) self::exit(app::EC_LOCKED); + array_splice($args, 0, 0, [ + PHP_BINARY, + path::abspath(NULIB_APP_app_launcher), + ]); + app::params_putenv(); + self::_start($args, $runfile); + break; + case self::ACTION_STOP: + self::_stop($runfile); + self::show_infos($runfile, -1); + break; + case self::ACTION_INFOS: + self::show_infos($runfile); + break; + } + } + + public static function _start(array $args, Runfile $runfile): void { + $pid = pcntl_fork(); + if ($pid == -1) { + # parent, impossible de forker + throw new ExitError(app::EC_FORK_PARENT, "Unable to fork"); + } elseif (!$pid) { + # child, fork ok + $runfile->wfPrepare($pid); + $outfile = $runfile->getOutfile() ?? "/tmp/NULIB_APP_app_console.out"; + $exitcode = app::EC_FORK_CHILD; + try { + # rediriger STDIN, STDOUT et STDERR + fclose(fopen($outfile, "wb")); // vider le fichier + fclose(STDIN); $in = fopen("/dev/null", "rb"); + fclose(STDOUT); $out = fopen($outfile, "ab"); + fclose(STDERR); $err = fopen($outfile, "ab"); + # puis lancer la commande + $cmd = new Cmd($args); + $cmd->addSource("/g/init.env"); + $cmd->addRedir("both", $outfile, true); + $cmd->fork_exec($exitcode, false); + sh::_waitpid(-$pid, $exitcode); + } finally { + $runfile->wfReaped($exitcode); + } + } + } + + public static function _stop(Runfile $runfile): bool { + $data = $runfile->read(); + $pid = $runfile->_getCid($data); + msg::action("stop $pid"); + if ($runfile->wfKill($reason)) { + msg::asuccess(); + return true; + } else { + msg::afailure($reason); + return false; + } + } +} diff --git a/php/cli/DumpserApp.php b/php/cli/DumpserApp.php new file mode 100644 index 0000000..61c4aa7 --- /dev/null +++ b/php/cli/DumpserApp.php @@ -0,0 +1,31 @@ + parent::ARGS, + "purpose" => "afficher des données sérialisées", + ]; + + function main() { + $files = []; + foreach ($this->args as $arg) { + if (is_file($arg)) { + $files[] = $arg; + } else { + msg::warning("$arg: fichier invalide ou introuvable"); + } + } + $showSection = count($files) > 1; + foreach ($files as $file) { + if ($showSection) msg::section($file); + $sfile = new SharedFile($file); + yaml::dump($sfile->unserialize()); + } + } +} diff --git a/php/cli/Json2yamlApp.php b/php/cli/Json2yamlApp.php new file mode 100644 index 0000000..138f184 --- /dev/null +++ b/php/cli/Json2yamlApp.php @@ -0,0 +1,21 @@ +args[0] ?? null; + if ($input === null || $input === "-") { + $output = null; + } else { + $output = path::ensure_ext($input, ".yml", ".json"); + } + + $data = json::load($input); + yaml::dump($data, $output); + } +} \ No newline at end of file diff --git a/php/cli/MysqlCapacitorApp.php b/php/cli/MysqlCapacitorApp.php new file mode 100644 index 0000000..a188782 --- /dev/null +++ b/php/cli/MysqlCapacitorApp.php @@ -0,0 +1,45 @@ + parent::ARGS, + "purpose" => "gestion d'un capacitor mysql", + "usage" => [ + "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] [--query] key=value...", + "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] --sql-create", + ], + ["-t", "--table-name", "args" => 1, + "help" => "nom de la table porteuse du canal de données", + ], + ["-c", "--channel-class", "args" => 1, + "help" => "nom de la classe dérivée de CapacitorChannel", + ], + ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, + "help" => "réinitialiser le canal", + ], + ["-n", "--no-recreate", "name" => "recreate", "value" => false, + "help" => "ne pas recréer la table correspondant au canal" + ], + ["--query", "name" => "action", "value" => self::ACTION_QUERY, + "help" => "lister les lignes correspondant aux valeurs spécifiées. c'est l'action par défaut", + ], + ["-s", "--sql-create", "name" => "action", "value" => self::ACTION_SQL, + "help" => "afficher la requête pour créer la table", + ], + ]; + + function main() { + $dbconn = A::shift($this->args); + if ($dbconn === null) self::die("Vous devez spécifier la base de données"); + $tmp = config::db($dbconn); + if ($tmp === null) self::die("$dbconn: base de données invalide"); + $storage = new MysqlStorage($tmp); + + $this->storageCtl($storage); + } +} diff --git a/php/cli/PgsqlCapacitorApp.php b/php/cli/PgsqlCapacitorApp.php new file mode 100644 index 0000000..978ee51 --- /dev/null +++ b/php/cli/PgsqlCapacitorApp.php @@ -0,0 +1,45 @@ + parent::ARGS, + "purpose" => "gestion d'un capacitor pgsql", + "usage" => [ + "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] [--query] key=value...", + "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] --sql-create", + ], + ["-t", "--table-name", "args" => 1, + "help" => "nom de la table porteuse du canal de données", + ], + ["-c", "--channel-class", "args" => 1, + "help" => "nom de la classe dérivée de CapacitorChannel", + ], + ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, + "help" => "réinitialiser le canal", + ], + ["-n", "--no-recreate", "name" => "recreate", "value" => false, + "help" => "ne pas recréer la table correspondant au canal" + ], + ["--query", "name" => "action", "value" => self::ACTION_QUERY, + "help" => "lister les lignes correspondant aux valeurs spécifiées. c'est l'action par défaut", + ], + ["-s", "--sql-create", "name" => "action", "value" => self::ACTION_SQL, + "help" => "afficher la requête pour créer la table", + ], + ]; + + function main() { + $dbconn = A::shift($this->args); + if ($dbconn === null) self::die("Vous devez spécifier la base de données"); + $tmp = config::db($dbconn); + if ($tmp === null) self::die("$dbconn: base de données invalide"); + $storage = new PgsqlStorage($tmp); + + $this->storageCtl($storage); + } +} diff --git a/php/cli/SqliteCapacitorApp.php b/php/cli/SqliteCapacitorApp.php new file mode 100644 index 0000000..daef081 --- /dev/null +++ b/php/cli/SqliteCapacitorApp.php @@ -0,0 +1,43 @@ + parent::ARGS, + "purpose" => "gestion d'un capacitor sqlite", + "usage" => [ + "DBFILE [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] [--query] key=value...", + "DBFILE [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] --sql-create", + ], + ["-t", "--table-name", "args" => 1, + "help" => "nom de la table porteuse du canal de données", + ], + ["-c", "--channel-class", "args" => 1, + "help" => "nom de la classe dérivée de CapacitorChannel", + ], + ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, + "help" => "réinitialiser le canal", + ], + ["-n", "--no-recreate", "name" => "recreate", "value" => false, + "help" => "ne pas recréer la table correspondant au canal" + ], + ["--query", "name" => "action", "value" => self::ACTION_QUERY, + "help" => "lister les lignes correspondant aux valeurs spécifiées. c'est l'action par défaut", + ], + ["-s", "--sql-create", "name" => "action", "value" => self::ACTION_SQL, + "help" => "afficher la requête pour créer la table", + ], + ]; + + function main() { + $dbfile = A::shift($this->args); + if ($dbfile === null) self::die("Vous devez spécifier la base de données"); + if (!file_exists($dbfile)) self::die("$dbfile: fichier introuvable"); + $storage = new SqliteStorage($dbfile); + + $this->storageCtl($storage); + } +} diff --git a/php/cli/SteamTrainApp.php b/php/cli/SteamTrainApp.php new file mode 100644 index 0000000..6406cbf --- /dev/null +++ b/php/cli/SteamTrainApp.php @@ -0,0 +1,53 @@ + self::TITLE, + "description" => << 1, + "help" => "spécifier le nombre d'étapes", + ], + ["-f", "--force-enabled", "value" => true, + "help" => "lancer la commande même si les tâches planifiées sont désactivées", + ], + ["-n", "--no-install-signal-handler", "value" => false, + "help" => "ne pas installer le gestionnaire de signaux", + ], + ]; + + protected $count = 100; + + protected bool $forceEnabled = false; + + protected bool $installSignalHandler = true; + + function main() { + app::check_bgapplication_enabled($this->forceEnabled); + if ($this->installSignalHandler) app::install_signal_handler(); + $count = intval($this->count); + msg::info("Starting train for ".words::q($count, "step#s")); + app::action("Running train...", $count); + for ($i = 1; $i <= $count; $i++) { + msg::print("Tchou-tchou! x $i"); + app::step(); + sleep(1); + } + msg::info("Stopping train at ".new DateTime()); + } +} diff --git a/php/cli/Yaml2jsonApp.php b/php/cli/Yaml2jsonApp.php new file mode 100644 index 0000000..fb3f96f --- /dev/null +++ b/php/cli/Yaml2jsonApp.php @@ -0,0 +1,21 @@ +args[0] ?? null; + if ($input === null || $input === "-") { + $output = null; + } else { + $output = path::ensure_ext($input, ".json", [".yml", ".yaml"]); + } + + $data = yaml::load($input); + json::dump($data, $output); + } +} \ No newline at end of file diff --git a/php/src/tools/pman/ComposerFile.php b/php/cli/pman/ComposerFile.php similarity index 99% rename from php/src/tools/pman/ComposerFile.php rename to php/cli/pman/ComposerFile.php index c3dd7a1..1e72de2 100644 --- a/php/src/tools/pman/ComposerFile.php +++ b/php/cli/pman/ComposerFile.php @@ -1,5 +1,5 @@ getParams(); + } elseif ($app instanceof Application) { + $class = get_class($app); + $params = [ + "class" => $class, + "projdir" => $app::PROJDIR, + "vendor" => $app::VENDOR, + "appcode" => $app::APPCODE, + "datadir" => $app::DATADIR, + "etcdir" => $app::ETCDIR, + "vardir" => $app::VARDIR, + "logdir" => $app::LOGDIR, + "appgroup" => $app::APPGROUP, + "name" => $app::NAME, + "title" => $app::TITLE, + ]; + } elseif (self::isa_Application($app)) { + $class = $app; + $params = [ + "class" => $class, + "projdir" => constant("$app::PROJDIR"), + "vendor" => constant("$app::VENDOR"), + "appcode" => constant("$app::APPCODE"), + "datadir" => constant("$app::DATADIR"), + "etcdir" => constant("$app::ETCDIR"), + "vardir" => constant("$app::VARDIR"), + "logdir" => constant("$app::LOGDIR"), + "appgroup" => constant("$app::APPGROUP"), + "name" => constant("$app::NAME"), + "title" => constant("$app::TITLE"), + ]; + } elseif (is_array($app)) { + $params = $app; + } else { + throw ValueException::invalid_type($app, Application::class); + } + return $params; + } + + protected static ?self $app = null; + + /** + * @param Application|string|array $app + * @param Application|string|array|null $proj + */ + static function with($app, $proj=null): self { + $params = self::get_params($app); + $proj ??= self::params_getenv(); + $proj ??= self::$app; + $proj_params = $proj !== null? self::get_params($proj): null; + if ($proj_params !== null) { + A::merge($params, cl::select($proj_params, [ + "projdir", + "vendor", + "appcode", + "cwd", + "datadir", + "etcdir", + "vardir", + "logdir", + "profile", + "facts", + "debug", + ])); + } + return new static($params, $proj_params !== null); + } + + static function init($app, $proj=null): void { + self::$app = static::with($app, $proj); + } + + static function get(): self { + return self::$app ??= new static(null); + } + + static function params_putenv(): void { + $params = serialize(self::get()->getParams()); + putenv("NULIB_APP_app_params=$params"); + } + + static function params_getenv(): ?array { + $params = getenv("NULIB_APP_app_params"); + if ($params === false) return null; + return unserialize($params); + } + + static function get_profile(?bool &$productionMode=null): string { + return self::get()->getProfile($productionMode); + } + + static function is_prod(): bool { + return self::get_profile() === "prod"; + } + + static function is_devel(): bool { + return self::get_profile() === "devel"; + } + + static function set_profile(?string $profile=null, ?bool $productionMode=null): void { + self::get()->setProfile($profile, $productionMode); + } + + const FACT_WEB_APP = "web-app"; + const FACT_CLI_APP = "cli-app"; + + static final function is_fact(string $fact, $value=true): bool { + return self::get()->isFact($fact, $value); + } + + static final function set_fact(string $fact, $value=true): void { + self::get()->setFact($fact, $value); + } + + static function is_debug(): bool { + return self::get()->isDebug(); + } + + static function set_debug(?bool $debug=true): void { + self::get()->setDebug($debug); + } + + /** + * @var array répertoires vendor exprimés relativement à PROJDIR + */ + const DEFAULT_VENDOR = [ + "bindir" => "vendor/bin", + "autoload" => "vendor/autoload.php", + ]; + + function __construct(?array $params, bool $useProjParams=false) { + if ($useProjParams) { + [ + "projdir" => $projdir, + "vendor" => $vendor, + "appcode" => $appcode, + "datadir" => $datadir, + "etcdir" => $etcdir, + "vardir" => $vardir, + "logdir" => $logdir, + ] = $params; + $cwd = $params["cwd"] ?? null; + $datadirIsDefined = true; + } else { + # projdir + $projdir = $params["projdir"] ?? null; + if ($projdir === null) { + global $_composer_autoload_path, $_composer_bin_dir; + $autoload = $_composer_autoload_path ?? null; + $bindir = $_composer_bin_dir ?? null; + if ($autoload !== null) { + $vendor = preg_replace('/\/[^\/]+\.php$/', "", $autoload); + $bindir ??= "$vendor/bin"; + $projdir = preg_replace('/\/[^\/]+$/', "", $vendor); + $params["vendor"] = [ + "autoload" => $autoload, + "bindir" => $bindir, + ]; + } + } + if ($projdir === null) $projdir = "."; + $projdir = path::abspath($projdir); + # vendor + $vendor = $params["vendor"] ?? self::DEFAULT_VENDOR; + $vendor["bindir"] = path::reljoin($projdir, $vendor["bindir"]); + $vendor["autoload"] = path::reljoin($projdir, $vendor["autoload"]); + # appcode + $appcode = $params["appcode"] ?? null; + if ($appcode === null) { + $appcode = str::without_suffix("-app", path::basename($projdir)); + } + $APPCODE = str_replace("-", "_", strtoupper($appcode)); + # cwd + $cwd = $params["cwd"] ?? null; + # datadir + $datadir = getenv("${APPCODE}_DATADIR"); + $datadirIsDefined = $datadir !== false; + if ($datadir === false) $datadir = $params["datadir"] ?? null; + if ($datadir === null) $datadir = "devel"; + $datadir = path::reljoin($projdir, $datadir); + # etcdir + $etcdir = getenv("${APPCODE}_ETCDIR"); + if ($etcdir === false) $etcdir = $params["etcdir"] ?? null; + if ($etcdir === null) $etcdir = "etc"; + $etcdir = path::reljoin($datadir, $etcdir); + # vardir + $vardir = getenv("${APPCODE}_VARDIR"); + if ($vardir === false) $vardir = $params["vardir"] ?? null; + if ($vardir === null) $vardir = "var"; + $vardir = path::reljoin($datadir, $vardir); + # logdir + $logdir = getenv("${APPCODE}_LOGDIR"); + if ($logdir === false) $logdir = $params["logdir"] ?? null; + if ($logdir === null) $logdir = "log"; + $logdir = path::reljoin($datadir, $logdir); + } + # cwd + $cwd ??= getcwd(); + # profile + $this->profileManager = new ProfileManager([ + "app" => true, + "name" => $appcode, + "default_profile" => $datadirIsDefined? "prod": "devel", + "profile" => $params["profile"] ?? null, + ]); + # $facts + $this->facts = $params["facts"] ?? null; + # debug + $this->debug = $params["debug"] ?? null; + + $this->projdir = $projdir; + $this->vendor = $vendor; + $this->appcode = $appcode; + $this->cwd = $cwd; + $this->datadir = $datadir; + $this->etcdir = $etcdir; + $this->vardir = $vardir; + $this->logdir = $logdir; + + # name, title + $appgroup = $params["appgroup"] ?? null; + $name = $params["name"] ?? $params["class"] ?? null; + if ($name === null) { + $name = $appcode; + } else { + # si $name est une classe, enlever le package et normaliser i.e + # my\package\MyApplication --> my-application + $name = preg_replace('/.*\\\\/', "", $name); + $name = str::camel2us($name, false, "-"); + $name = str::without_suffix("-app", $name); + } + $this->appgroup = $appgroup; + $this->name = $name; + $this->title = $params["title"] ?? null; + } + + ############################################################################# + # Paramètres partagés par tous les scripts d'un projet (et les scripts lancés + # à partir d'une application de ce projet) + + protected string $projdir; + + function getProjdir(): string { + return $this->projdir; + } + + protected array $vendor; + + function getVendorBindir(): string { + return $this->vendor["bindir"]; + } + + function getVendorAutoload(): string { + return $this->vendor["autoload"]; + } + + protected string $appcode; + + function getAppcode(): string { + return $this->appcode; + } + + protected string $cwd; + + function getCwd(): string { + return $this->cwd; + } + + protected string $datadir; + + function getDatadir(): string { + return $this->datadir; + } + + protected string $etcdir; + + function getEtcdir(): string { + return $this->etcdir; + } + + protected string $vardir; + + function getVardir(): string { + return $this->vardir; + } + + protected string $logdir; + + function getLogdir(): string { + return $this->logdir; + } + + protected ProfileManager $profileManager; + + function getProfile(?bool &$productionMode=null): string { + return $this->profileManager->getProfile($productionMode); + } + + function isProductionMode(): bool { + return $this->profileManager->isProductionMode(); + } + + function setProfile(?string $profile, ?bool $productionMode=null): void { + $this->profileManager->setProfile($profile, $productionMode); + } + + protected ?array $facts; + + function isFact(string $fact, $value=true): bool { + return ($this->facts[$fact] ?? false) === $value; + } + + function setFact(string $fact, $value=true): void { + $this->facts[$fact] = $value; + } + + protected ?bool $debug; + + function isDebug(): bool { + $debug = $this->debug; + if ($debug === null) { + $debug = defined("DEBUG")? DEBUG: null; + $DEBUG = getenv("DEBUG"); + $debug ??= $DEBUG !== false? $DEBUG: null; + $debug ??= config::k("debug"); + $debug ??= false; + $this->debug = $debug; + } + return $debug; + } + + function setDebug(bool $debug=true): void { + $this->debug = $debug; + } + + /** + * @param ?string|false $profile + * + * false === pas de profil + * null === profil par défaut + */ + function withProfile(string $file, $profile): string { + if ($profile !== false) { + $profile ??= $this->getProfile(); + [$dir, $filename] = path::split($file); + $basename = path::basename($filename); + $ext = path::ext($file); + $file = path::join($dir, "$basename.$profile$ext"); + } + return $file; + } + + function findFile(array $dirs, array $names, $profile=null): string { + # d'abord chercher avec le profil + if ($profile !== false) { + foreach ($dirs as $dir) { + foreach ($names as $name) { + $file = path::join($dir, $name); + $file = $this->withProfile($file, $profile); + if (file_exists($file)) return $file; + } + } + } + # puis sans profil + foreach ($dirs as $dir) { + foreach ($names as $name) { + $file = path::join($dir, $name); + if (file_exists($file)) return $file; + } + } + # la valeur par défaut est avec profil + return $this->withProfile(path::join($dirs[0], $names[0]), $profile); + } + + function fencedJoin(string $basedir, ?string ...$paths): string { + $path = path::reljoin($basedir, ...$paths); + if (!path::is_within($path, $basedir)) { + throw ValueException::invalid_value($path, "path"); + } + return $path; + } + + ############################################################################# + # Paramètres spécifiques à cette application + + protected ?string $appgroup; + + function getAppgroup(): ?string { + return $this->appgroup; + } + + protected string $name; + + function getName(): ?string { + return $this->name; + } + + protected ?string $title; + + function getTitle(): ?string { + return $this->title; + } + + ############################################################################# + # Méthodes outils + + /** recréer le tableau des paramètres */ + function getParams(): array { + return [ + "projdir" => $this->projdir, + "vendor" => $this->vendor, + "appcode" => $this->appcode, + "cwd" => $this->cwd, + "datadir" => $this->datadir, + "etcdir" => $this->etcdir, + "vardir" => $this->vardir, + "logdir" => $this->logdir, + "profile" => $this->getProfile(), + "facts" => $this->facts, + "debug" => $this->debug, + "appgroup" => $this->appgroup, + "name" => $this->name, + "title" => $this->title, + ]; + } + + /** + * obtenir le chemin vers le fichier de configuration. par défaut, retourner + * une valeur de la forme "$ETCDIR/$name[.$profile].conf" + */ + function getEtcfile(?string $name=null, $profile=null): string { + if ($name === null) $name = "{$this->name}.conf"; + return $this->findFile([$this->etcdir], [$name], $profile); + } + + /** + * obtenir le chemin vers le fichier de travail. par défaut, retourner une + * valeur de la forme "$VARDIR/$appgroup/$name[.$profile].tmp" + */ + function getVarfile(?string $name=null, $profile=null): string { + if ($name === null) $name = "{$this->name}.tmp"; + $file = $this->fencedJoin($this->vardir, $this->appgroup, $name); + $file = $this->withProfile($file, $profile); + sh::mkdirof($file); + return $file; + } + + /** + * obtenir le chemin vers le fichier de log. par défaut, retourner une + * valeur de la forme "$LOGDIR/$appgroup/$name.log" (sans le profil, parce + * qu'il s'agit du fichier de log par défaut) + * + * Si $name est spécifié, la valeur retournée sera de la forme + * "$LOGDIR/$appgroup/$basename[.$profile].$ext" + */ + function getLogfile(?string $name=null, $profile=null): string { + if ($name === null) { + $name = "{$this->name}.log"; + $profile ??= false; + } + $file = $this->fencedJoin($this->logdir, $this->appgroup, $name); + $file = $this->withProfile($file, $profile); + sh::mkdirof($file); + return $file; + } + + /** + * obtenir le chemin absolu vers un fichier de travail + * - si le chemin est absolu, il est inchangé + * - sinon le chemin est exprimé par rapport à $vardir/$appgroup + * + * is $ensureDir, créer le répertoire du fichier s'il n'existe pas déjà + * + * la différence avec {@link self::getVarfile()} est que le fichier peut + * au final être situé ailleurs que dans $vardir. de plus, il n'y a pas de + * valeur par défaut pour $file + */ + function getWorkfile(string $file, $profile=null, bool $ensureDir=true): string { + $file = path::reljoin($this->vardir, $this->appgroup, $file); + $file = $this->withProfile($file, $profile); + if ($ensureDir) sh::mkdirof($file); + return $file; + } + + /** + * obtenir le chemin absolu vers un fichier spécifié par l'utilisateur. + * - si le chemin commence par /, il est laissé en l'état + * - si le chemin commence par ./ ou ../, il est exprimé par rapport à $cwd + * - sinon le chemin est exprimé par rapport à $vardir/$appgroup + * + * la différence est avec {@link self::getVarfile()} est que le fichier peut + * au final être situé ailleurs que dans $vardir. de plus, il n'y a pas de + * valeur par défaut pour $file + */ + function getUserfile(string $file): string { + if (path::is_qualified($file)) { + return path::reljoin($this->cwd, $file); + } else { + return path::reljoin($this->vardir, $this->appgroup, $file); + } + } + + protected ?RunFile $runfile = null; + + function getRunfile(): RunFile { + $name = $this->name; + $runfile = $this->getWorkfile($name); + $logfile = $this->getLogfile("$name.out", false); + return $this->runfile ??= new RunFile($name, $runfile, $logfile); + } + + protected ?array $lockFiles = null; + + function getLockfile(?string $name=null): LockFile { + $this->lockFiles[$name] ??= $this->getRunfile()->getLockFile($name, $this->title); + return $this->lockFiles[$name]; + } + + ############################################################################# + + const EC_FORK_CHILD = 250; + const EC_FORK_PARENT = 251; + const EC_DISABLED = 252; + const EC_LOCKED = 253; + const EC_BAD_COMMAND = 254; + const EC_UNEXPECTED = 255; + + ############################################################################# + + static bool $dispach_signals = false; + + static function install_signal_handler(bool $allow=true): void { + if (!$allow) return; + $signalHandler = function(int $signo, $siginfo) { + throw new ExitError(128 + $signo); + }; + pcntl_signal(SIGHUP, $signalHandler); + pcntl_signal(SIGINT, $signalHandler); + pcntl_signal(SIGQUIT, $signalHandler); + pcntl_signal(SIGTERM, $signalHandler); + self::$dispach_signals = true; + } + + static function _dispatch_signals() { + if (self::$dispach_signals) pcntl_signal_dispatch(); + } + + ############################################################################# + + static ?func $bgapplication_enabled = null; + + /** + * spécifier la fonction permettant de vérifier si l'exécution de tâches + * de fond est autorisée. Si cette méthode n'est pas utilisée, par défaut, + * les tâches planifiées sont autorisées + * + * si $func===true, spécifier une fonction qui retourne toujours vrai + * si $func===false, spécifiée une fonction qui retourne toujours faux + * sinon, $func doit être une fonction valide + */ + static function set_bgapplication_enabled($func): void { + if (is_bool($func)) { + $enabled = $func; + $func = function () use ($enabled) { + return $enabled; + }; + } + self::$bgapplication_enabled = func::with($func); + } + + /** + * Si les exécutions en tâche de fond sont autorisée, retourner. Sinon + * afficher une erreur et quitter l'application + */ + static function check_bgapplication_enabled(bool $forceEnabled=false): void { + if (self::$bgapplication_enabled === null || $forceEnabled) return; + if (!self::$bgapplication_enabled->invoke()) { + throw new ExitError(self::EC_DISABLED, "Planifications désactivées. La tâche n'a pas été lancée"); + } + } + + ############################################################################# + + static function action(?string $title, ?int $maxSteps=null): void { + self::get()->getRunfile()->action($title, $maxSteps); + } + + static function step(int $nbSteps=1): void { + self::get()->getRunfile()->step($nbSteps); + } +} diff --git a/php/src/app/args/AbstractArgsParser.php b/php/src/app/args/AbstractArgsParser.php new file mode 100644 index 0000000..1a50037 --- /dev/null +++ b/php/src/app/args/AbstractArgsParser.php @@ -0,0 +1,109 @@ + 0) throw $this->notEnoughArgs($count, $option); + } + + protected function tooManyArgs(int $count, int $expected, ?string $arg=null): ArgsException { + if ($arg !== null) $arg .= ": "; + return new ArgsException("${arg}trop d'arguments (attendu $expected, reçu $count)"); + } + + protected function invalidArg(string $arg): ArgsException { + return new ArgsException("$arg: argument invalide"); + } + + protected function ambiguousArg(string $arg, array $candidates): ArgsException { + $candidates = implode(", ", $candidates); + return new ArgsException("$arg: argument ambigû (les valeurs possibles sont $candidates)"); + } + + /** + * consommer les arguments de $src en avançant l'index $srci et provisionner + * $dest à partir de $desti. si $desti est plus grand que 0, celà veut dire + * que $dest a déjà commencé à être provisionné, et qu'il faut continuer. + * + * $destmin est le nombre minimum d'arguments à consommer. $destmax est le + * nombre maximum d'arguments à consommer. + * + * $srci est la position de l'élément courant à consommer le cas échéant + * retourner le nombre d'arguments qui manquent (ou 0 si tous les arguments + * ont été consommés) + * + * pour les arguments optionnels, ils sont consommés tant qu'il y en a de + * disponible, ou jusqu'à la présence de '--'. Si $keepsep, l'argument '--' + * est gardé dans la liste des arguments optionnels. + */ + protected static function consume_args($src, &$srci, &$dest, $desti, $destmin, $destmax, bool $keepsep): int { + $srcmax = count($src); + # arguments obligatoires + while ($desti < $destmin) { + if ($srci < $srcmax) { + $dest[] = $src[$srci]; + } else { + # pas assez d'arguments + return $destmin - $desti; + } + $srci++; + $desti++; + } + # arguments facultatifs + $eoo = false; // l'option a-t-elle été terminée? + while ($desti < $destmax && $srci < $srcmax) { + $opt = $src[$srci]; + $srci++; + $desti++; + if ($opt === "--") { + # fin des arguments facultatifs en entrée + $eoo = true; + if ($keepsep) $dest[] = $opt; + break; + } + $dest[] = $opt; + } + if (!$eoo && $desti < $destmax) { + # pas assez d'arguments en entrée, terminer avec "--" + $dest[] = "--"; + } + return 0; + } + + abstract function normalize(array $args): array; + + /** @var object|array objet destination */ + protected $dest; + + protected function setDest(&$dest): void { + $this->dest =& $dest; + } + + protected function unsetDest(): void { + unset($this->dest); + } + + abstract function process(array $args); + + function parse(&$dest, array $args=null): void { + if ($args === null) { + global $argv; + $args = array_slice($argv, 1); + } + $args = $this->normalize($args); + $dest ??= new stdClass(); + $this->setDest($dest); + $this->process($args); + $this->unsetDest(); + } + + abstract function actionPrintHelp(string $arg): void; +} diff --git a/php/src/app/args/Aodef.php b/php/src/app/args/Aodef.php new file mode 100644 index 0000000..58b3a73 --- /dev/null +++ b/php/src/app/args/Aodef.php @@ -0,0 +1,623 @@ +origDef = $def; + $this->mergeParse($def); + //$this->debugTrace("construct"); + } + + protected array $origDef; + + public bool $show = true; + public ?bool $disabled = null; + public ?bool $isRemains = null; + public ?string $extends = null; + + protected ?array $_removes = null; + protected ?array $_adds = null; + + protected ?array $_args = null; + public ?string $argsdesc = null; + + public ?bool $ensureArray = null; + public $action = null; + public ?func $func = null; + public ?bool $inverse = null; + public $value = null; + public ?string $name = null; + public ?string $property = null; + public ?string $key = null; + + public ?string $help = null; + + protected ?array $_options = []; + + public bool $haveShortOptions = false; + public bool $haveLongOptions = false; + public bool $isCommand = false; + public bool $isHelp = false; + + public bool $haveArgs = false; + public ?int $minArgs = null; + public ?int $maxArgs = null; + + protected function mergeParse(array $def): void { + $merges = $defs["merges"] ?? null; + $merge = $defs["merge"] ?? null; + if ($merge !== null) $merges[] = $merge; + if ($merges !== null) { + foreach ($merges as $merge) { + if ($merge !== null) $this->mergeParse($merge); + } + } + + $this->parse($def); + + $merge = $defs["merge_after"] ?? null; + if ($merge !== null) $this->mergeParse($merge); + } + + protected function parse(array $def): void { + [$options, $params] = cl::split_assoc($def); + + $this->show ??= $params["show"] ?? true; + $this->extends ??= $params["extends"] ?? null; + + $this->disabled = vbool::withn($params["disabled"] ?? null); + $removes = varray::withn($params["remove"] ?? null); + A::merge($this->_removes, $removes); + $adds = varray::withn($params["add"] ?? null); + A::merge($this->_adds, $adds); + A::merge($this->_adds, $options); + + $args = $params["args"] ?? null; + $args ??= $params["arg"] ?? null; + if ($args === true) $args = 1; + elseif ($args === "*") $args = [null]; + elseif ($args === "+") $args = ["value", null]; + if (is_int($args)) $args = array_fill(0, $args, "value"); + $this->_args ??= cl::withn($args); + + $this->argsdesc ??= $params["argsdesc"] ?? null; + + $this->ensureArray ??= $params["ensure_array"] ?? null; + $this->action = $params["action"] ?? null; + $this->inverse ??= $params["inverse"] ?? null; + $this->value ??= $params["value"] ?? null; + $this->name ??= $params["name"] ?? null; + $this->property ??= $params["property"] ?? null; + $this->key ??= $params["key"] ?? null; + + $this->help ??= $params["help"] ?? null; + } + + function isExtends(): bool { + return $this->extends !== null; + } + + function setup1(bool $extends=false, ?Aolist $aolist=null): void { + if (!$extends && !$this->isExtends()) { + $this->processOptions(); + } elseif ($extends && $this->isExtends()) { + $this->processExtends($aolist); + } + $this->initRemains(); + //$this->debugTrace("setup1"); + } + + protected function processExtends(Aolist $argdefs): void { + $option = $this->extends; + if ($option === null) { + throw ArgsException::missing("extends", "destination arg"); + } + $dest = $argdefs->get($option); + if ($dest === null) { + throw ArgsException::invalid($option, "destination arg"); + } + + if ($this->ensureArray !== null) $dest->ensureArray = $this->ensureArray; + if ($this->action !== null) $dest->action = $this->action; + if ($this->inverse !== null) $dest->inverse = $this->inverse; + if ($this->value !== null) $dest->value = $this->value; + if ($this->name !== null) $dest->name = $this->name; + if ($this->property !== null) $dest->property = $this->property; + if ($this->key !== null) $dest->key = $this->key; + + A::merge($dest->_removes, $this->_removes); + A::merge($dest->_adds, $this->_adds); + $dest->processOptions(); + } + + function buildOptions(?array $options): array { + $result = []; + if ($options !== null) { + foreach ($options as $option) { + if (substr($option, 0, 2) === "--") { + $type = self::TYPE_LONG; + if (preg_match('/^--([^:-][^:]*)(::?)?$/', $option, $ms)) { + $name = $ms[1]; + $args = $ms[2] ?? null; + $option = "--$name"; + } else { + throw ArgsException::invalid($option, "long option"); + } + } elseif (substr($option, 0, 1) === "-") { + $type = self::TYPE_SHORT; + if (preg_match('/^-([^:-])(::?)?$/', $option, $ms)) { + $name = $ms[1]; + $args = $ms[2] ?? null; + $option = "-$name"; + } else { + throw ArgsException::invalid($option, "short option"); + } + } else { + $type = self::TYPE_COMMAND; + if (preg_match('/^([^:-][^:]*)$/', $option, $ms)) { + $name = $ms[1]; + $args = null; + $option = "$name"; + } else { + throw ArgsException::invalid($option, "command"); + } + } + if ($args === ":") { + $argsType = self::ARGS_MANDATORY; + } elseif ($args === "::") { + $argsType = self::ARGS_OPTIONAL; + } else { + $argsType = self::ARGS_NONE; + } + $result[$option] = [ + "name" => $name, + "option" => $option, + "type" => $type, + "args_type" => $argsType, + ]; + } + } + return $result; + } + + protected function initRemains(): void { + if ($this->isRemains === null) { + $options = array_fill_keys(array_keys($this->_options), true); + foreach (array_keys($this->buildOptions($this->_removes)) as $option) { + unset($options[$option]); + } + foreach (array_keys($this->buildOptions($this->_adds)) as $option) { + unset($options[$option]); + } + if (!$options) $this->isRemains = true; + } + } + + /** traiter le paramètre parent */ + protected function processOptions(): void { + $this->removeOptions($this->_removes); + $this->_removes = null; + $this->addOptions($this->_adds); + $this->_adds = null; + } + + function addOptions(?array $options): void { + A::merge($this->_options, $this->buildOptions($options)); + $this->updateType(); + } + + function removeOptions(?array $options): void { + foreach ($this->buildOptions($options) as $option) { + unset($this->_options[$option["option"]]); + } + $this->updateType(); + } + + function removeOption(string $option): void { + unset($this->_options[$option]); + } + + /** mettre à jour le type d'option */ + protected function updateType(): void { + $haveShortOptions = false; + $haveLongOptions = false; + $isCommand = false; + $isHelp = false; + foreach ($this->_options as $option) { + switch ($option["type"]) { + case self::TYPE_SHORT: + $haveShortOptions = true; + break; + case self::TYPE_LONG: + $haveLongOptions = true; + break; + case self::TYPE_COMMAND: + $isCommand = true; + break; + } + switch ($option["option"]) { + case "--help": + case "--help++": + $isHelp = true; + break; + } + } + $this->haveShortOptions = $haveShortOptions; + $this->haveLongOptions = $haveLongOptions; + $this->isCommand = $isCommand; + $this->isHelp = $isHelp; + } + + function setup2(): void { + $this->processArgs(); + $this->processAction(); + $this->afterSetup(); + //$this->debugTrace("setup2"); + } + + /** + * traiter les informations concernant les arguments puis calculer les nombres + * minimum et maximum d'arguments que prend l'option + */ + protected function processArgs(): void { + $args = $this->_args; + $haveArgs = boolval($args); + if ($this->isRemains) { + $haveArgs = true; + $args = [null]; + } elseif ($args === null) { + $optionalArgs = null; + foreach ($this->_options as $option) { + switch ($option["args_type"]) { + case self::ARGS_NONE: + break; + case self::ARGS_MANDATORY: + $haveArgs = true; + $optionalArgs = false; + break; + case self::ARGS_OPTIONAL: + $haveArgs = true; + $optionalArgs ??= true; + break; + } + } + $optionalArgs ??= false; + if ($haveArgs) { + $args = ["value"]; + if ($optionalArgs) $args = [$args]; + } + } + + if ($this->isRemains) $desc = "remaining args"; + else $desc = cl::first($this->_options)["option"]; + + $args ??= []; + $argsdesc = []; + $reqs = []; + $haveNull = false; + $optArgs = null; + foreach ($args as $arg) { + if (is_string($arg)) { + $reqs[] = $arg; + $argsdesc[] = strtoupper($arg); + } elseif (is_array($arg)) { + $optArgs = $arg; + break; + } elseif ($arg === null) { + $haveNull = true; + break; + } else { + throw ArgsException::invalid("$desc: $arg", "option arg"); + } + } + + $opts = []; + $optArgsdesc = null; + $lastarg = "VALUE"; + if ($optArgs !== null) { + $haveOpt = false; + foreach ($optArgs as $arg) { + if (is_string($arg)) { + $haveOpt = true; + $opts[] = $arg; + $lastarg = strtoupper($arg); + $optArgsdesc[] = $lastarg; + } elseif ($arg === null) { + $haveNull = true; + break; + } else { + throw ArgsException::invalid("$desc: $arg", "option arg"); + } + } + if (!$haveOpt) $haveNull = true; + } + if ($haveNull) $optArgsdesc[] = "${lastarg}s..."; + if ($optArgsdesc !== null) { + $argsdesc[] = "[".implode(" ", $optArgsdesc)."]"; + } + + $minArgs = count($reqs); + if ($haveNull) $maxArgs = PHP_INT_MAX; + else $maxArgs = $minArgs + count($opts); + + $this->haveArgs = $haveArgs; + $this->minArgs = $minArgs; + $this->maxArgs = $maxArgs; + $this->argsdesc ??= implode(" ", $argsdesc); + } + + private static function get_longest(array $options, int $type): ?string { + $longest = null; + $maxlen = 0; + foreach ($options as $option) { + if ($option["type"] !== $type) continue; + $name = $option["name"]; + $len = strlen($name); + if ($len > $maxlen) { + $longest = $name; + $maxlen = $len; + } + } + return $longest; + } + + protected function processAction(): void { + $this->ensureArray ??= $this->isRemains || $this->maxArgs > 1; + + $action = $this->action; + $func = $this->func; + if ($action === null) { + if ($this->isCommand) $action = "--set-command"; + elseif ($this->isRemains) $action = "--set-args"; + elseif ($this->isHelp) $action = "--show-help"; + elseif ($this->haveArgs) $action = "--set"; + elseif ($this->value !== null) $action = "--set"; + else $action = "--inc"; + } + if (is_string($action) && substr($action, 0, 2) === "--") { + # fonction interne + } else { + $func = func::with($action); + $action = "--func"; + } + $this->action = $action; + $this->func = $func; + + $name = $this->name; + $property = $this->property; + $key = $this->key; + if ($action !== "--func" && !$this->isRemains && + $name === null && $property === null && $key === null + ) { + # si on ne précise pas le nom de la propriété, la dériver à partir du + # nom de l'option la plus longue + $longest = self::get_longest($this->_options, self::TYPE_LONG); + $longest ??= self::get_longest($this->_options, self::TYPE_COMMAND); + $longest ??= self::get_longest($this->_options, self::TYPE_SHORT); + if ($longest !== null) { + $longest = preg_replace('/[^A-Za-z0-9]+/', "_", $longest); + if (preg_match('/^[0-9]/', $longest)) { + # le nom de la propriété ne doit pas commencer par un chiffre + $longest = "p$longest"; + } + $name = $longest; + } + } elseif ($name === null && $property !== null) { + $name = $property; + } elseif ($name === null && $key !== null) { + $name = $key; + } + $this->name = $name; + } + + protected function afterSetup(): void { + $this->disabled ??= false; + $this->ensureArray ??= false; + $this->inverse ??= false; + if (str::del_prefix($this->help, "++")) { + $this->show = false; + } + } + + function getOptions(): array { + if ($this->disabled) return []; + else return array_keys($this->_options); + } + + function isEmpty(): bool { + return $this->disabled || !$this->_options; + } + + function printHelp(?array $what=null): void { + $showDef = $what["show"] ?? $this->show; + if (!$showDef) return; + + $prefix = $what["prefix"] ?? null; + if ($prefix !== null) echo $prefix; + + $showOptions = $what["options"] ?? true; + if ($showOptions) { + echo " "; + echo implode(", ", array_keys($this->_options)); + if ($this->haveArgs) { + echo " "; + echo $this->argsdesc; + } + echo "\n"; + } + + $showHelp = $what["help"] ?? true; + if ($this->help && $showHelp) { + echo str::indent($this->help, " "); + echo "\n"; + } + } + + function action(&$dest, $value, ?string $arg, AbstractArgsParser $parser): void { + if ($this->ensureArray) { + varray::ensure($value); + } elseif (is_array($value)) { + $count = count($value); + if ($count == 0) $value = null; + elseif ($count == 1) $value = $value[0]; + } + + switch ($this->action) { + case "--set": $this->actionSet($dest, $value); break; + case "--inc": $this->actionInc($dest); break; + case "--dec": $this->actionDec($dest); break; + case "--add": $this->actionAdd($dest, $value); break; + case "--adds": $this->actionAdds($dest, $value); break; + case "--merge": $this->actionMerge($dest, $value); break; + case "--merges": $this->actionMerges($dest, $value); break; + case "--func": $this->func->bind($dest)->invoke([$value, $arg, $this]); break; + case "--set-args": $this->actionSetArgs($dest, $value); break; + case "--set-command": $this->actionSetCommand($dest, $value); break; + case "--show-help": $parser->actionPrintHelp($arg); break; + default: throw ArgsException::invalid($this->action, "arg action"); + } + } + + function actionSet(&$dest, $value): void { + if ($this->property !== null) { + oprop::set($dest, $this->property, $value); + } elseif ($this->key !== null) { + akey::set($dest, $this->key, $value); + } elseif ($this->name !== null) { + valx::set($dest, $this->name, $value); + } + } + + function actionInc(&$dest): void { + if ($this->property !== null) { + if ($this->inverse) oprop::dec($dest, $this->property); + else oprop::inc($dest, $this->property); + } elseif ($this->key !== null) { + if ($this->inverse) akey::dec($dest, $this->key); + else akey::inc($dest, $this->key); + } elseif ($this->name !== null) { + if ($this->inverse) valx::dec($dest, $this->name); + else valx::inc($dest, $this->name); + } + } + + function actionDec(&$dest): void { + if ($this->property !== null) { + if ($this->inverse) oprop::inc($dest, $this->property); + else oprop::dec($dest, $this->property); + } elseif ($this->key !== null) { + if ($this->inverse) akey::inc($dest, $this->key); + else akey::dec($dest, $this->key); + } elseif ($this->name !== null) { + if ($this->inverse) valx::inc($dest, $this->name); + else valx::dec($dest, $this->name); + } + } + + function actionAdd(&$dest, $value): void { + if ($this->property !== null) { + oprop::append($dest, $this->property, $value); + } elseif ($this->key !== null) { + akey::append($dest, $this->key, $value); + } elseif ($this->name !== null) { + valx::append($dest, $this->name, $value); + } + } + + function actionAdds(&$dest, $value): void { + if ($this->property !== null) { + foreach (cl::with($value) as $value) { + oprop::append($dest, $this->property, $value); + } + } elseif ($this->key !== null) { + foreach (cl::with($value) as $value) { + akey::append($dest, $this->key, $value); + } + } elseif ($this->name !== null) { + foreach (cl::with($value) as $value) { + valx::append($dest, $this->name, $value); + } + } + } + + function actionMerge(&$dest, $value): void { + if ($this->property !== null) { + oprop::merge($dest, $this->property, $value); + } elseif ($this->key !== null) { + akey::merge($dest, $this->key, $value); + } elseif ($this->name !== null) { + valx::merge($dest, $this->name, $value); + } + } + + function actionMerges(&$dest, $value): void { + if ($this->property !== null) { + foreach (cl::with($value) as $value) { + oprop::merge($dest, $this->property, $value); + } + } elseif ($this->key !== null) { + foreach (cl::with($value) as $value) { + akey::merge($dest, $this->key, $value); + } + } elseif ($this->name !== null) { + foreach (cl::with($value) as $value) { + valx::merge($dest, $this->name, $value); + } + } + } + + function actionSetArgs(&$dest, $value): void { + if ($this->property !== null) { + oprop::set($dest, $this->property, $value); + } elseif ($this->key !== null) { + akey::set($dest, $this->key, $value); + } elseif ($this->name !== null) { + valx::set($dest, $this->name, $value); + } + } + + function actionSetCommand(&$dest, $value): void { + if ($this->property !== null) { + oprop::set($dest, $this->property, $value); + } elseif ($this->key !== null) { + akey::set($dest, $this->key, $value); + } elseif ($this->name !== null) { + valx::set($dest, $this->name, $value); + } + } + + function __toString(): string { + $options = implode(",", $this->getOptions()); + $args = $this->haveArgs? " ({$this->minArgs}-{$this->maxArgs})": false; + return "$options$args"; + } + private function debugTrace(string $message): void { + $options = implode(",", cl::split_assoc($this->origDef)[0] ?? []); + echo "$options $message\n"; + } +} diff --git a/php/src/app/args/Aogroup.php b/php/src/app/args/Aogroup.php new file mode 100644 index 0000000..7873a70 --- /dev/null +++ b/php/src/app/args/Aogroup.php @@ -0,0 +1,38 @@ +all() as $aodef) { + $firstAodef ??= $aodef; + $aodef->printHelp(["help" => false]); + } + if ($firstAodef !== null) { + $firstAodef->printHelp(["options" => false]); + } + } +} diff --git a/php/src/app/args/Aolist.php b/php/src/app/args/Aolist.php new file mode 100644 index 0000000..504de50 --- /dev/null +++ b/php/src/app/args/Aolist.php @@ -0,0 +1,271 @@ +origDefs = $defs; + $this->initDefs($defs, $setup); + } + + protected array $origDefs; + + protected ?array $aomain; + protected ?array $aosections; + protected ?array $aospecials; + + public ?Aodef $remainsArgdef = null; + + function initDefs(array $defs, bool $setup=true): void { + $this->mergeParse($defs, $aobjects); + $this->aomain = $aobjects["main"] ?? null; + $this->aosections = $aobjects["sections"] ?? null; + $this->aospecials = $aobjects["specials"] ?? null; + if ($setup) $this->setup(); + } + + protected function mergeParse(array $defs, ?array &$aobjects, bool $parse=true): void { + $aobjects ??= []; + + $merges = $defs["merges"] ?? null; + $merge = $defs["merge"] ?? null; + if ($merge !== null) $merges[] = $merge; + if ($merges !== null) { + foreach ($merges as $merge) { + $this->mergeParse($merge, $aobjects, false); + $this->parse($merge, $aobjects); + } + } + + if ($parse) $this->parse($defs, $aobjects); + + $merge = $defs["merge_after"] ?? null; + if ($merge !== null) { + $this->mergeParse($merge, $aobjects, false); + $this->parse($merge, $aobjects); + } + } + + protected function parse(array $defs, array &$aobjects): void { + [$defs, $params] = cl::split_assoc($defs); + if ($defs !== null) { + $aomain =& $aobjects["main"]; + foreach ($defs as $def) { + $first = $def[0] ?? null; + if ($first === "group") { + $aobject = new Aogroup($def); + } else { + $aobject = new Aodef($def); + } + $aomain[] = $aobject; + } + } + $sections = $params["sections"] ?? null; + if ($sections !== null) { + $aosections =& $aobjects["sections"]; + $index = 0; + foreach ($sections as $key => $section) { + if ($key === $index) { + $index++; + $aosections[] = new Aosection($section); + } else { + /** @var Aosection $aosection */ + $aosection = $aosections[$key] ?? null; + if ($aosection === null) { + $aosections[$key] = new Aosection($section); + } else { + #XXX il faut implémenter la fusion en cas de section existante + # pour le moment, la liste existante est écrasée + $aosection->initDefs($section); + } + } + } + } + $this->parseParams($params); + } + + protected function parseParams(?array $params): void { + } + + function all(?array $what=null): iterable { + $returnsAodef = $what["aodef"] ?? true; + $returnsAolist = $what["aolist"] ?? false; + $returnExtends = $what["extends"] ?? false; + $withSpecials = $what["aospecials"] ?? true; + # lister les sections avant, pour que les options de la section principale + # soient prioritaires + $aosections = $this->aosections; + if ($aosections !== null) { + /** @var Aosection $aobject */ + foreach ($aosections as $aosection) { + if ($returnsAolist) { + yield $aosection; + } elseif ($returnsAodef) { + yield from $aosection->all($what); + } + } + } + + $aomain = $this->aomain; + if ($aomain !== null) { + /** @var Aodef $aobject */ + foreach ($aomain as $aobject) { + if ($aobject instanceof Aodef) { + if ($returnsAodef) { + if ($returnExtends) { + if ($aobject->isExtends()) yield $aobject; + } else { + if (!$aobject->isExtends()) yield $aobject; + } + } + } elseif ($aobject instanceof Aolist) { + if ($returnsAolist) { + yield $aobject; + } elseif ($returnsAodef) { + yield from $aobject->all($what); + } + } + } + } + + $aospecials = $this->aospecials; + if ($withSpecials && $aospecials !== null) { + /** @var Aodef $aobject */ + foreach ($aospecials as $aobject) { + yield $aobject; + } + } + } + + protected function filter(callable $callback): void { + $aomain = $this->aomain; + if ($aomain !== null) { + $filtered = []; + /** @var Aodef $aobject */ + foreach ($aomain as $aobject) { + if ($aobject instanceof Aolist) { + $aobject->filter($callback); + } + if (call_user_func($callback, $aobject)) { + $filtered[] = $aobject; + } + } + $this->aomain = $filtered; + } + $aosections = $this->aosections; + if ($aosections !== null) { + $filtered = []; + /** @var Aosection $aosection */ + foreach ($aosections as $aosection) { + $aosection->filter($callback); + if (call_user_func($callback, $aosection)) { + $filtered[] = $aosection; + } + } + $this->aosections = $filtered; + } + } + + protected function setup(): void { + # calculer les options + foreach ($this->all() as $aodef) { + $aodef->setup1(); + } + /** @var Aodef $aodef */ + foreach ($this->all(["extends" => true]) as $aodef) { + $aodef->setup1(true, $this); + } + # ne garder que les objets non vides + $this->filter(function($aobject): bool { + if ($aobject instanceof Aodef) { + return !$aobject->isEmpty(); + } elseif ($aobject instanceof Aolist) { + return !$aobject->isEmpty(); + } else { + return false; + } + }); + # puis calculer nombre d'arguments et actions + foreach ($this->all() as $aodef) { + $aodef->setup2(); + } + } + + function isEmpty(): bool { + foreach ($this->all() as $aobject) { + return false; + } + return true; + } + + function get(string $option): ?Aodef { + return null; + } + + function actionPrintHelp(string $arg): void { + $this->printHelp([ + "show_all" => $arg === "--help++", + ]); + } + + function printHelp(?array $what=null): void { + $show = $what["show_all"] ?? false; + if (!$show) $show = null; + + $aosections = $this->aosections; + if ($aosections !== null) { + /** @var Aosection $aosection */ + foreach ($aosections as $aosection) { + $aosection->printHelp(cl::merge($what, [ + "show" => $show, + "prefix" => "\n", + ])); + } + } + + $aomain = $this->aomain; + if ($aomain !== null) { + echo "\nOPTIONS\n"; + foreach ($aomain as $aobject) { + $aobject->printHelp(cl::merge($what, [ + "show" => $show, + ])); + } + } + } + + function __toString(): string { + $items = []; + $what = [ + "aodef" => true, + "aolist" => true, + ]; + foreach ($this->all($what) as $aobject) { + if ($aobject instanceof Aodef) { + $items[] = strval($aobject); + } elseif ($aobject instanceof Aogroup) { + $items[] = implode("\n", [ + "group", + str::indent(strval($aobject)), + ]); + } elseif ($aobject instanceof Aosection) { + $items[] = implode("\n", [ + "section", + str::indent(strval($aobject)), + ]); + } else { + $items[] = false; + } + } + return implode("\n", $items); + } +} diff --git a/php/src/app/args/Aosection.php b/php/src/app/args/Aosection.php new file mode 100644 index 0000000..b5d478c --- /dev/null +++ b/php/src/app/args/Aosection.php @@ -0,0 +1,47 @@ +show = vbool::with($params["show"] ?? true); + $this->prefix ??= $params["prefix"] ?? null; + $this->title ??= $params["title"] ?? null; + $this->description ??= $params["description"] ?? null; + $this->suffix ??= $params["suffix"] ?? null; + } + + function printHelp(?array $what=null): void { + $showSection = $what["show"] ?? $this->show; + if (!$showSection) return; + + $prefix = $what["prefix"] ?? null; + if ($prefix !== null) echo $prefix; + + if ($this->prefix) echo "{$this->prefix}\n"; + if ($this->title) echo "{$this->title}\n"; + if ($this->description) echo "\n{$this->description}\n"; + /** @var Aodef|Aolist $aobject */ + foreach ($this->all(["aolist" => true]) as $aobject) { + $aobject->printHelp(); + } + if ($this->suffix) echo "{$this->suffix}\n"; + } +} diff --git a/php/src/app/args/ArgsException.php b/php/src/app/args/ArgsException.php new file mode 100644 index 0000000..db2cfff --- /dev/null +++ b/php/src/app/args/ArgsException.php @@ -0,0 +1,20 @@ +prefix ??= $params["prefix"] ?? null; + $this->name ??= $params["name"] ?? null; + $this->purpose ??= $params["purpose"] ?? null; + $this->usage ??= $params["usage"] ?? null; + $this->description ??= $params["description"] ?? null; + $this->suffix ??= $params["suffix"] ?? null; + + $this->commandname ??= $params["commandname"] ?? null; + $this->commandproperty ??= $params["commandproperty"] ?? null; + $this->commandkey ??= $params["commandkey"] ?? null; + + $this->argsname ??= $params["argsname"] ?? null; + $this->argsproperty ??= $params["argsproperty"] ?? null; + $this->argskey ??= $params["argskey"] ?? null; + + $this->autohelp ??= vbool::withn($params["autohelp"] ?? null); + $this->autoremains ??= vbool::withn($params["autoremains"] ?? null); + } + + /** @return string[] */ + function getOptions(): array { + return array_keys($this->index); + } + + protected function indexAodefs(): void { + $this->index = []; + foreach ($this->all() as $aodef) { + $options = $aodef->getOptions(); + foreach ($options as $option) { + /** @var Aodef $prevAodef */ + $prevAodef = $this->index[$option] ?? null; + if ($prevAodef !== null) $prevAodef->removeOption($option); + $this->index[$option] = $aodef; + } + } + } + + protected function setup(): void { + # calculer les options pour les objets déjà fusionnés + /** @var Aodef $aodef */ + foreach ($this->all() as $aodef) { + $aodef->setup1(); + } + + # puis traiter les extensions d'objets et calculer les options pour ces + # objets sur la base de l'index que l'on crée une première fois + $this->indexAodefs(); + /** @var Aodef $aodef */ + foreach ($this->all(["extends" => true]) as $aodef) { + $aodef->setup1(true, $this); + } + + # ne garder que les objets non vides + $this->filter(function($aobject) { + if ($aobject instanceof Aodef) { + return !$aobject->isEmpty(); + } elseif ($aobject instanceof Aolist) { + return !$aobject->isEmpty(); + } else { + return false; + } + }); + + # rajouter remains et help si nécessaire + $this->aospecials = []; + $helpArgdef = null; + $remainsArgdef = null; + /** @var Aodef $aodef */ + foreach ($this->all() as $aodef) { + if ($aodef->isHelp) $helpArgdef = $aodef; + if ($aodef->isRemains) $remainsArgdef = $aodef; + } + + $this->autohelp ??= true; + if ($helpArgdef === null && $this->autohelp) { + $helpArgdef = new Aodef([ + "--help", "--help++", + "action" => "--show-help", + "help" => "Afficher l'aide", + ]); + $helpArgdef->setup1(); + } + if ($helpArgdef !== null) $this->aospecials[] = $helpArgdef; + + $this->autoremains ??= true; + if ($remainsArgdef === null && $this->autoremains) { + $remainsArgdef = new Aodef([ + "args" => [null], + "action" => "--set-args", + "name" => $this->argsname ?? "args", + "property" => $this->argsproperty, + "key" => $this->argskey, + ]); + $remainsArgdef->setup1(); + } + if ($remainsArgdef !== null) { + $this->remainsArgdef = $remainsArgdef; + $this->aospecials[] = $remainsArgdef; + } + + # puis calculer nombre d'arguments et actions + $this->indexAodefs(); + /** @var Aodef $aodef */ + foreach ($this->all() as $aodef) { + $aodef->setup2(); + } + } + + function get(string $option): ?Aodef { + return $this->index[$option] ?? null; + } + + function printHelp(?array $what = null): void { + $showList = $what["show"] ?? true; + if (!$showList) return; + + $prefix = $what["prefix"] ?? null; + if ($prefix !== null) echo $prefix; + + if ($this->prefix) echo "{$this->prefix}\n"; + if ($this->purpose) { + echo "{$this->name}: {$this->purpose}\n"; + } elseif (!$this->prefix) { + # s'il y a un préfixe sans purpose, il remplace purpose + echo "{$this->name}\n"; + } + if ($this->usage) { + echo "\nUSAGE\n"; + foreach (cl::with($this->usage) as $usage) { + echo " {$this->name} $usage\n"; + } + } + if ($this->description) echo "\n{$this->description}\n"; + parent::printHelp($what); + if ($this->suffix) echo "{$this->suffix}\n"; + } + + function __toString(): string { + return implode("\n", [ + "objects:", + str::indent(parent::__toString()), + "index:", + str::indent(implode("\n", array_keys($this->index))), + ]); + } +} diff --git a/php/src/app/args/SimpleArgsParser.php b/php/src/app/args/SimpleArgsParser.php new file mode 100644 index 0000000..e514dc1 --- /dev/null +++ b/php/src/app/args/SimpleArgsParser.php @@ -0,0 +1,250 @@ +aolist = new SimpleAolist($defs); + } + + protected SimpleAolist $aolist; + + protected function getArgdef(string $option): ?Aodef { + return $this->aolist->get($option); + } + + protected function getOptions(): array { + return $this->aolist->getOptions(); + } + + function normalize(array $args): array { + $i = 0; + $max = count($args); + $options = []; + $remains = []; + $parseOpts = true; + while ($i < $max) { + $arg = $args[$i++]; + if (!$parseOpts) { + # le reste n'est que des arguments + $remains[] = $arg; + continue; + } + if ($arg === "--") { + # fin des options + $parseOpts = false; + continue; + } + + if (substr($arg, 0, 2) === "--") { + ####################################################################### + # option longue + $pos = strpos($arg, "="); + if ($pos !== false) { + # option avec valeur + $option = substr($arg, 0, $pos); + $value = substr($arg, $pos + 1); + } else { + # option sans valeur + $option = $arg; + $value = null; + } + $argdef = $this->getArgdef($option); + if ($argdef === null) { + # chercher une correspondance + $len = strlen($option); + $candidates = []; + foreach ($this->getOptions() as $candidate) { + if (substr($candidate, 0, $len) === $option) { + $candidates[] = $candidate; + } + } + switch (count($candidates)) { + case 0: throw $this->invalidArg($option); + case 1: $option = $candidates[0]; break; + default: throw $this->ambiguousArg($option, $candidates); + } + $argdef = $this->getArgdef($option); + } + + if ($argdef->haveArgs) { + $minArgs = $argdef->minArgs; + $maxArgs = $argdef->maxArgs; + $values = []; + if ($value !== null) { + $values[] = $value; + $offset = 1; + } elseif ($minArgs == 0) { + # cas particulier: la première valeur doit être collée à l'option + # si $maxArgs == 1 + $offset = $maxArgs == 1 ? 1 : 0; + } else { + $offset = 0; + } + $this->checkEnoughArgs($option, + self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true)); + + if ($minArgs == 0 && $maxArgs == 1) { + # cas particulier: la première valeur doit être collée à l'option + if (count($values) > 0) { + $options[] = "$option=$values[0]"; + $values = array_slice($values, 1); + } else { + $options[] = $option; + } + } else { + $options[] = $option; + } + $options = array_merge($options, $values); + } elseif ($value !== null) { + throw $this->tooManyArgs(1, 0, $option); + } else { + $options[] = $option; + } + + } elseif (substr($arg, 0, 1) === "-") { + ####################################################################### + # option courte + $pos = 1; + $len = strlen($arg); + while ($pos < $len) { + $option = "-".substr($arg, $pos, 1); + $argdef = $this->getArgdef($option); + if ($argdef === null) throw $this->invalidArg($option); + if ($argdef->haveArgs) { + $minArgs = $argdef->minArgs; + $maxArgs = $argdef->maxArgs; + $values = []; + if ($len > $pos + 1) { + $values[] = substr($arg, $pos + 1); + $offset = 1; + $pos = $len; + } elseif ($minArgs == 0) { + # cas particulier: la première valeur doit être collée à l'option + # si $maxArgs == 1 + $offset = $maxArgs == 1 ? 1 : 0; + } else { + $offset = 0; + } + $this->checkEnoughArgs($option, + self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true)); + + if ($minArgs == 0 && $maxArgs == 1) { + # cas particulier: la première valeur doit être collée à l'option + if (count($values) > 0) { + $options[] = "$option$values[0]"; + $values = array_slice($values, 1); + } else { + $options[] = $option; + } + } else { + $options[] = $option; + } + $options = array_merge($options, $values); + } else { + $options[] = $option; + } + $pos++; + } + } else { + #XXX implémenter les commandes + + ####################################################################### + # argument + $remains[] = $arg; + } + } + return array_merge($options, ["--"], $remains); + } + + function process(array $args) { + $i = 0; + $max = count($args); + # d'abord traiter les options + while ($i < $max) { + $arg = $args[$i++]; + if ($arg === "--") { + # fin des options + break; + } + + if (preg_match('/^(--[^=]+)(?:=(.*))?/', $arg, $ms)) { + # option longue + } elseif (preg_match('/^(-.)(.+)?/', $arg, $ms)) { + # option courte + } else { + # commande + throw StateException::unexpected_state("commands are not supported"); + } + $option = $ms[1]; + $ovalue = $ms[2] ?? null; + $argdef = $this->getArgdef($option); + if ($argdef === null) throw StateException::unexpected_state(); + $defvalue = $argdef->value; + if ($argdef->haveArgs) { + $minArgs = $argdef->minArgs; + $maxArgs = $argdef->maxArgs; + if ($minArgs == 0 && $maxArgs == 1) { + # argument facultatif + if ($ovalue !== null) $value = [$ovalue]; + else $value = cl::with($defvalue); + $offset = 1; + } else { + $value = []; + $offset = 0; + } + self::consume_args($args, $i, $value, $offset, $minArgs, $maxArgs, false); + } else { + $value = $defvalue; + } + + $this->action($value, $arg, $argdef); + } + + # construire la liste des arguments qui restent + $args = array_slice($args, $i); + $i = 0; + $max = count($args); + $argdef = $this->aolist->remainsArgdef; + if ($argdef !== null && $argdef->haveArgs) { + $minArgs = $argdef->minArgs; + $maxArgs = $argdef->maxArgs; + if ($maxArgs == PHP_INT_MAX) { + # cas particulier: si le nombre d'arguments restants est non borné, + # les prendre tous sans distinction ni traitement de '--' + $value = $args; + # mais tester tout de même s'il y a le minimum requis d'arguments + $this->checkEnoughArgs(null, $minArgs - $max); + } else { + $value = []; + $this->checkEnoughArgs(null, + self::consume_args($args, $i, $value, 0, $minArgs, $maxArgs, false)); + if ($i <= $max - 1) throw $this->tooManyArgs($max, $i); + } + $this->action($value, null, $argdef); + } elseif ($i <= $max - 1) { + throw $this->tooManyArgs($max, $i); + } + } + + function action($value, ?string $arg, Aodef $argdef) { + $argdef->action($this->dest, $value, $arg, $this); + } + + public function actionPrintHelp(string $arg): void { + $this->aolist->actionPrintHelp($arg); + throw new ExitError(0); + } + + function showDebugInfos() { + echo $this->aolist."\n"; #XXX + } +} diff --git a/php/src/app/args/TODO.md b/php/src/app/args/TODO.md new file mode 100644 index 0000000..d19db42 --- /dev/null +++ b/php/src/app/args/TODO.md @@ -0,0 +1,21 @@ +# nulib\app\args + +* [ ] dans la section "profils", rajouter une option pour spécifier un fichier de configuration +* [ ] transformer un schéma en définition d'arguments, un tableau en liste d'arguments, et vice-versa +* [ ] faire une implémentation ArgsParser qui supporte les commandes, et les options dynamiques + * commandes: + `program [options] command [options]` + * multi-commandes: + `program [options] command [options] // command [options] // ...` + * dynamique: la liste des options et des commandes supportées est calculée dynamiquement + +## support des commandes + +faire une interface Runnable qui représente un composant pouvant être exécuté. +Application implémente Runnable, mais l'analyse des arguments peut retourner une +autre instance de runnable pour faciliter l'implémentation de différents +sous-outils + +## BUGS + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/php/src/app/cli/Application.php b/php/src/app/cli/Application.php new file mode 100644 index 0000000..5a947aa --- /dev/null +++ b/php/src/app/cli/Application.php @@ -0,0 +1,383 @@ +getDesc(); + echo implode("\n", $desc["message"])."\n"; + $ec = $desc["exitcode"] ?? 0; + break; + case "dump": + case "d": + yaml::dump($runfile->read()); + break; + case "reset": + case "z": + if (!$runfile->isRunning()) $runfile->reset(); + else $ec = self::_error("cannot reset while running"); + break; + case "release": + case "rl": + $runfile->release(); + break; + case "start": + case "s": + array_splice($argv, 1, 1); $argc--; + return; + case "kill": + case "k": + if ($runfile->isRunning()) $runfile->wfKill(); + else $ec = self::_error("not running"); + break; + default: + $ec = self::_error("$argv[1]: unexpected command", app::EC_BAD_COMMAND); + } + exit($ec); + } + + static function run(?Application $app=null): void { + $unlock = false; + $stop = false; + $shutdown = function () use (&$unlock, &$stop) { + if ($unlock) { + app::get()->getRunfile()->release(); + $unlock = false; + } + if ($stop) { + app::get()->getRunfile()->wfStop(); + $stop = false; + } + }; + register_shutdown_function($shutdown); + app::install_signal_handler(static::INSTALL_SIGNAL_HANDLER); + try { + static::_initialize_app(); + $useRunfile = static::USE_RUNFILE; + $useRunlock = static::USE_RUNLOCK; + if ($useRunfile) { + $runfile = app::get()->getRunfile(); + + global $argc, $argv; + self::_manage_runfile($argc, $argv, $runfile); + if ($useRunlock && $runfile->warnIfLocked()) exit(app::EC_LOCKED); + + $runfile->wfStart(); + $stop = true; + if ($useRunlock) { + $runfile->lock(); + $unlock = true; + } + } + if ($app === null) $app = new static(); + static::_configure_app($app); + static::_start_app($app); + } catch (ExitError $e) { + if ($e->haveUserMessage()) msg::error($e->getUserMessage()); + exit($e->getCode()); + } catch (Exception $e) { + msg::error($e); + exit(app::EC_UNEXPECTED); + } + } + + protected static function _initialize_app(): void { + app::init(static::class); + app::set_fact(app::FACT_CLI_APP); + msg::set_messenger(new StdMessenger([ + "min_level" => msg::DEBUG, + ])); + } + + protected static function _configure_app(Application $app): void { + config::configure(config::CONFIGURE_INITIAL_ONLY); + + $msgs = null; + $msgs["console"] = new StdMessenger([ + "min_level" => msg::NORMAL, + ]); + if (static::USE_LOGFILE) { + $msgs["log"] = new StdMessenger([ + "output" => app::get()->getLogfile(), + "min_level" => msg::MINOR, + "add_date" => true, + ]); + } + msg::init($msgs); + + $app->parseArgs(); + config::configure(); + } + + protected static function _start_app(Application $app): void { + $retcode = $app->main(); + if (is_int($retcode)) exit($retcode); + elseif (is_bool($retcode)) exit($retcode? 0: 1); + elseif ($retcode !== null) exit(strval($retcode)); + } + + /** + * sortir de l'application avec un code d'erreur, qui est 0 par défaut (i.e + * pas d'erreur) + * + * équivalent à lancer l'exception {@link ExitError} + */ + protected static final function exit(int $exitcode=0, $message=null) { + throw new ExitError($exitcode, $message); + } + + /** + * sortir de l'application avec un code d'erreur, qui vaut 1 par défaut (i.e + * une erreur s'est produite) + * + * équivalent à lancer l'exception {@link ExitError} + */ + protected static final function die($message=null, int $exitcode=1) { + throw new ExitError($exitcode, $message); + } + + const PROFILE_SECTION = [ + "title" => "PROFILS D'EXECUTION", + "show" => false, + ["group", + ["-p", "--profile", "--app-profile", + "args" => "profile", + "action" => [app::class, "set_profile"], + "help" => "spécifier le profil d'exécution", + ], + ["-P", "--prod", "action" => [app::class, "set_profile", "prod"]], + ["-T", "--test", "action" => [app::class, "set_profile", "test"]], + ["--devel", "action" => [app::class, "set_profile", "devel"]], + ], + ]; + + const VERBOSITY_SECTION = [ + "title" => "NIVEAU D'INFORMATION", + "show" => false, + ["group", + ["--verbosity", + "args" => "verbosity", "argsdesc" => "silent|quiet|verbose|debug", + "action" => [null, "set_application_verbosity"], + "help" => "spécifier le niveau d'informations affiché", + ], + ["-q", "--quiet", "action" => [null, "set_application_verbosity", "quiet"]], + ["-v", "--verbose", "action" => [null, "set_application_verbosity", "verbose"]], + ["-D", "--debug", "action" => [null, "set_application_verbosity", "debug"]], + ], + ["-L", "--logfile", + "args" => "output", + "action" => [null, "set_application_log_output"], + "help" => "Logger les messages de l'application dans le fichier spécifié", + ], + ["group", + ["--color", + "action" => [null, "set_application_color", true], + "help" => "Afficher (resp. ne pas afficher) la sortie en couleur par défaut", + ], + ["--no-color", "action" => [null, "set_application_color", false]], + ], + ]; + + static function set_application_verbosity(string $verbosity): void { + $console = console::get(); + switch ($verbosity) { + case "Q": + case "silent": + $console->resetParams([ + "min_level" => msg::NONE, + ]); + break; + case "q": + case "quiet": + $console->resetParams([ + "min_level" => msg::MAJOR, + ]); + break; + case "n": + case "normal": + $console->resetParams([ + "min_level" => msg::NORMAL, + ]); + break; + case "v": + case "verbose": + $console->resetParams([ + "min_level" => msg::MINOR, + ]); + break; + case "D": + case "debug": + app::set_debug(); + $console->resetParams([ + "min_level" => msg::DEBUG, + ]); + break; + default: + throw ValueException::invalid_value($verbosity, "verbosity"); + } + } + + static function set_application_log_output(string $logfile): void { + log::create_or_reset_params([ + "output" => $logfile, + ], StdMessenger::class, [ + "add_date" => true, + "min_level" => log::MINOR, + ]); + } + static function set_application_color(bool $color): void { + console::reset_params([ + "color" => $color, + ]); + } + const ARGS = [ + "sections" => [ + self::PROFILE_SECTION, + self::VERBOSITY_SECTION, + ], + ]; + + protected function getArgsParser(): AbstractArgsParser { + return new SimpleArgsParser(static::ARGS); + } + + /** @throws ArgsException */ + function parseArgs(array $args=null): void { + $this->getArgsParser()->parse($this, $args); + } + + const PROFILE_COLORS = [ + "prod" => "@r", + "test" => "@g", + "devel" => "@w", + ]; + const DEFAULT_PROFILE_COLOR = "y"; + + /** retourner le profil courant en couleur */ + static function get_profile(?string $profile=null): string { + if ($profile === null) $profile = app::get_profile(); + foreach (static::PROFILE_COLORS as $text => $color) { + if (strpos($profile, $text) !== false) { + return $color? "$profile": $profile; + } + } + $color = static::DEFAULT_PROFILE_COLOR; + return $color? "$profile": $profile; + } + + protected ?array $args = null; + + abstract function main(); + + static function runfile(): RunFile { + return app::with(static::class)->getRunfile(); + } +} diff --git a/php/src/app/config.php b/php/src/app/config.php new file mode 100644 index 0000000..08699f9 --- /dev/null +++ b/php/src/app/config.php @@ -0,0 +1,41 @@ +addConfigurator($configurators); + } + + # certains types de configurations sont normalisés + /** ne configurer que le minimum pour que l'application puisse s'initialiser */ + const CONFIGURE_INITIAL_ONLY = ["include" => "initial"]; + /** ne configurer que les routes */ + const CONFIGURE_ROUTES_ONLY = ["include" => "routes"]; + /** configurer uniquement ce qui ne nécessite pas d'avoir une session */ + const CONFIGURE_NO_SESSION = ["exclude" => "session"]; + + static function configure(?array $params=null): void { + self::$config->configure($params); + } + + static final function add($config, string ...$profiles): void { self::$config->addConfig($config, $profiles); } + static final function get(string $pkey, $default=null, ?string $profile=null) { return self::$config->getValue($pkey, $default, $profile); } + static final function k(string $pkey, $default=null) { return self::$config->getValue("app.$pkey", $default); } + static final function db(string $pkey, $default=null) { return self::$config->getValue("dbs.$pkey", $default); } + static final function m(string $pkey, $default=null) { return self::$config->getValue("msgs.$pkey", $default); } + static final function l(string $pkey, $default=null) { return self::$config->getValue("mails.$pkey", $default); } +} + +new class extends config { + function __construct() { + self::$config = new ConfigManager(); + } +}; \ No newline at end of file diff --git a/php/src/app/config/ArrayConfig.php b/php/src/app/config/ArrayConfig.php new file mode 100644 index 0000000..6a02c8e --- /dev/null +++ b/php/src/app/config/ArrayConfig.php @@ -0,0 +1,50 @@ +APP(); break; + case "dbs": $default = $this->DBS(); break; + case "msgs": $default = $this->MSGS(); break; + case "mails": $default = $this->MAILS(); break; + default: $default = []; + } + $config[$key] ??= $default; + } + $this->config = $config; + } + + protected array $config; + + function has(string $pkey, string $profile): bool { + return cl::phas($this->config, $pkey); + } + + function get(string $pkey, string $profile) { + return cl::pget($this->config, $pkey); + } + + function set(string $pkey, $value, string $profile): void { + cl::pset($this->config, $pkey, $value); + } +} diff --git a/php/src/app/config/ConfigManager.php b/php/src/app/config/ConfigManager.php new file mode 100644 index 0000000..ef10881 --- /dev/null +++ b/php/src/app/config/ConfigManager.php @@ -0,0 +1,150 @@ +configurators, cl::with($configurators)); + } + + protected array $configured = []; + + /** + * configurer les objets et les classes qui ne l'ont pas encore été. la liste + * des objets et des classes à configurer est fournie en appelant la méthode + * {@link addConfigurator()} + * + * par défaut, la configuration se fait en appelant toutes les méthodes + * publiques des objets et toutes les méthodes statiques des classes qui + * commencent par 'configure', e.g 'configureThis()' ou 'configure_db()', + * si elles n'ont pas déjà été appelées + * + * Il est possible de modifier la liste des méthodes appelées avec le tableau + * $params, qui doit être conforme au schema de {@link func::CALL_ALL_SCHEMA} + */ + function configure(?array $params=null): void { + $params["prefix"] ??= "configure"; + foreach ($this->configurators as $key => $configurator) { + $configured =& $this->configured[$key]; + /** @var func[] $methods */ + $methods = func::get_all($configurator, $params); + foreach ($methods as $method) { + $name = $method->getName() ?? "(no name)"; + $done = $configured[$name] ?? false; + if (!$done) { + $method->invoke(); + $configured[$name] = true; + } + } + } + } + + ############################################################################# + + protected $cache = []; + + protected function resetCache(): void { + $this->cache = []; + } + + protected function cacheHas(string $pkey, string $profile) { + return array_key_exists("$profile.$pkey", $this->cache); + } + + protected function cacheGet(string $pkey, string $profile) { + return cl::get($this->cache, "$profile.$pkey"); + } + + protected function cacheSet(string $pkey, $value, string $profile): void { + $this->cache["$profile.$pkey"] = $value; + } + + protected array $profileConfigs = []; + + /** + * Ajouter une configuration valide pour le(s) profil(s) spécifié(s) + * + * $config est un objet ou une classe qui définit une ou plusieurs des + * constantes APP, DBS, MSGS, MAILS + * + * si $inProfiles===null, la configuration est valide dans tous les profils + */ + function addConfig($config, ?array $inProfiles=null): void { + if (is_string($config)) { + $c = new ReflectionClass($config); + if ($c->implementsInterface(IConfig::class)) { + $config = $c->newInstance(); + } else { + $config = []; + foreach (IConfig::CONFIG_KEYS as $key) { + $config[$key] = cl::with($c->getConstant(strtoupper($key))); + } + $config = new ArrayConfig($config); + } + } elseif (is_array($config)) { + $config = new ArrayConfig($config); + } elseif (!($config instanceof IConfig)) { + throw ValueException::invalid_type($config, "array|IConfig"); + } + + $inProfiles ??= [IConfig::PROFILE_ALL]; + foreach ($inProfiles as $profile) { + $this->profileConfigs[$profile][] = $config; + } + + $this->resetCache(); + } + + function _getValue(string $pkey, $default, string $inProfile) { + $profiles = [$inProfile]; + if ($inProfile !== IConfig::PROFILE_ALL) $profiles[] = IConfig::PROFILE_ALL; + $value = $default; + foreach ($profiles as $profile) { + /** @var IConfig[] $configs */ + $configs = $this->profileConfigs[$profile] ?? []; + foreach (array_reverse($configs) as $config) { + if ($config->has($pkey, $profile)) { + $value = $config->get($pkey, $profile); + break; + } + } + } + return $value; + } + + /** + * obtenir la valeur au chemin de clé $pkey dans le profil spécifié + * + * le $inProfile===null, prendre le profil par défaut. + */ + function getValue(string $pkey, $default=null, ?string $inProfile=null) { + $inProfile ??= app::get_profile(); + + if ($this->cacheHas($pkey, $inProfile)) { + return $this->cacheGet($pkey, $inProfile); + } + + $value = $this->_getValue($pkey, $default, $inProfile); + $this->cacheSet($pkey, $default, $inProfile); + return $value; + } + + function setValue(string $pkey, $value, ?string $inProfile=null): void { + $inProfile ??= app::get_profile(); + /** @var IConfig[] $configs */ + $configs =& $this->profileConfigs[$inProfile]; + if ($configs === null) $key = 0; + else $key = array_key_last($configs); + $configs[$key] ??= new ArrayConfig([]); + $configs[$key]->set($pkey, $value, $inProfile); + } +} diff --git a/php/src/app/config/EnvConfig.php b/php/src/app/config/EnvConfig.php new file mode 100644 index 0000000..b07bf47 --- /dev/null +++ b/php/src/app/config/EnvConfig.php @@ -0,0 +1,112 @@ + "mysql", "name" => "mysql:host=authdb;dbname=auth;charset=utf8", + * "user" => "auth_int", "pass" => "auth" ] + * situé au chemin de clé dbs.auth dans le profil prod, on peut par exemple + * définir les variables suivantes: + * CONFIG_prod_dbs__auth__type="mysql" + * CONFIG_prod_dbs__auth__name="mysql:host=authdb;dbname=auth;charset=utf8" + * CONFIG_prod_dbs__auth__user="auth_int" + * CONFIG_prod_dbs__auth__pass="auth" + * ou alternativement: + * JSON_CONFIG_prod_dbs__auth='{"type":"mysql","name":"mysql:host=authdb;dbname=auth;charset=utf8","user":"auth_int","pass":"auth"}' + * + * Les préfixes supportés sont, dans l'ordre de précédence: + * - JSON_FILE_CONFIG -- une valeur au format JSON inscrite dans un fichier + * - JSON_CONFIG -- une valeur au format JSON + * - FILE_CONFIG -- une valeur inscrite dans un fichier + * - CONFIG -- une valeur scalaire + */ +class EnvConfig implements IConfig{ + protected ?array $profileConfigs = null; + + /** analyser $name et retourner [$pkey, $profile] */ + private static function parse_pkey_profile($name): array { + $i = strpos($name, "_"); + if ($i === false) return [false, false]; + $profile = substr($name, 0, $i); + if ($profile === "ALL") $profile = IConfig::PROFILE_ALL; + $name = substr($name, $i + 1); + $pkey = str_replace("__", ".", $name); + return [$pkey, $profile]; + } + + function loadEnvConfig(): void { + if ($this->profileConfigs !== null) return; + $json_files = []; + $jsons = []; + $files = []; + $vars = []; + foreach (getenv() as $name => $value) { + if (str::starts_with("JSON_FILE_CONFIG_", $name)) { + $json_files[str::without_prefix("JSON_FILE_CONFIG_", $name)] = $value; + } elseif (str::starts_with("JSON_CONFIG_", $name)) { + $jsons[str::without_prefix("JSON_CONFIG_", $name)] = $value; + } elseif (str::starts_with("FILE_CONFIG_", $name)) { + $files[str::without_prefix("FILE_CONFIG_", $name)] = $value; + } elseif (str::starts_with("CONFIG_", $name)) { + $vars[str::without_prefix("CONFIG_", $name)] = $value; + } + } + $profileConfigs = []; + foreach ($json_files as $name => $file) { + [$pkey, $profile] = self::parse_pkey_profile($name); + $value = json::load($file); + cl::pset($profileConfigs, "$profile.$pkey", $value); + } + foreach ($jsons as $name => $json) { + [$pkey, $profile] = self::parse_pkey_profile($name); + $value = json::decode($json); + cl::pset($profileConfigs, "$profile.$pkey", $value); + } + foreach ($files as $name => $file) { + [$pkey, $profile] = self::parse_pkey_profile($name); + $value = file::reader($file)->getContents(); + cl::pset($profileConfigs, "$profile.$pkey", $value); + } + foreach ($vars as $name => $value) { + [$pkey, $profile] = self::parse_pkey_profile($name); + cl::pset($profileConfigs, "$profile.$pkey", $value); + } + $this->profileConfigs = $profileConfigs; + } + + function has(string $pkey, string $profile): bool { + $this->loadEnvConfig(); + $config = $this->profileConfigs[$profile] ?? null; + return cl::phas($config, $pkey); + } + + function get(string $pkey, string $profile) { + $this->loadEnvConfig(); + $config = $this->profileConfigs[$profile] ?? null; + return cl::pget($config, $pkey); + } + + function set(string $pkey, $value, string $profile): void { + $this->loadEnvConfig(); + $config =& $this->profileConfigs[$profile]; + cl::pset($config, $pkey, $value); + } +} diff --git a/php/src/app/config/IConfig.php b/php/src/app/config/IConfig.php new file mode 100644 index 0000000..dcba89f --- /dev/null +++ b/php/src/app/config/IConfig.php @@ -0,0 +1,24 @@ + true, + "test" => true, + ]; + + /** + * @var array mapping profil d'application --> profil effectif + * + * ce mapping est utilisé quand il faut calculer le profil courant s'il n'a + * pas été spécifié par l'utilisateur. il permet de faire correspondre le + * profil courant de l'application avec le profil effectif à sélectionner + */ + const PROFILE_MAP = null; + + function __construct(?array $params=null) { + $this->isAppProfile = $params["app"] ?? false; + $this->profiles = static::PROFILES; + $this->productionModes = static::PRODUCTION_MODES; + $this->profileMap = static::PROFILE_MAP; + $name = $params["name"] ?? static::NAME; + if ($name === null) { + $this->configKey = null; + $this->envKeys = ["APP_PROFILE"]; + } else { + $configKey = "${name}_profile"; + $envKey = strtoupper($configKey); + if ($this->isAppProfile) { + $this->configKey = null; + $this->envKeys = [$envKey, "APP_PROFILE"]; + } else { + $this->configKey = $configKey; + $this->envKeys = [$envKey]; + } + } + $this->defaultProfile = $params["default_profile"] ?? null; + $profile = $params["profile"] ?? null; + $productionMode = $params["production_mode"] ?? null; + $productionMode ??= $this->productionModes[$profile] ?? false; + $this->profile = $profile; + $this->productionMode = $productionMode; + } + + /** + * @var bool cet objet est-il utilisé pour gérer le profil de l'application? + */ + protected bool $isAppProfile; + + protected ?array $profiles; + + protected ?array $productionModes; + + protected ?array $profileMap; + + protected function mapProfile(?string $profile): ?string { + return $this->profileMap[$profile] ?? $profile; + } + + protected ?string $configKey; + + function getConfigProfile(): ?string { + if ($this->configKey === null) return null; + return config::k($this->configKey); + } + + protected array $envKeys; + + function getEnvProfile(): ?string { + foreach ($this->envKeys as $envKey) { + $profile = getenv($envKey); + if ($profile !== false) return $profile; + } + return null; + } + + protected ?string $defaultProfile; + + function getDefaultProfile(): ?string { + return $this->defaultProfile; + } + + function setDefaultProfile(?string $profile): void { + $this->defaultProfile = $profile; + } + + protected ?string $profile; + + protected bool $productionMode; + + protected function resolveProfile(): void { + $profile ??= $this->getenvProfile(); + $profile ??= $this->getConfigProfile(); + $profile ??= $this->getDefaultProfile(); + if ($this->isAppProfile) { + $profile ??= $this->profiles[0] ?? "prod"; + } else { + $profile ??= $this->mapProfile(app::get_profile()); + } + $this->profile = $profile; + $this->productionMode = $this->productionModes[$profile] ?? false; + } + + function getProfile(?bool &$productionMode=null): string { + if ($this->profile === null) $this->resolveProfile(); + $productionMode = $this->productionMode; + return $this->profile; + } + + function isProductionMode(): bool { + return $this->productionMode; + } + + function setProfile(?string $profile=null, ?bool $productionMode=null): void { + if ($profile === null) $this->profile = null; + $profile ??= $this->getProfile($productionMode); + $productionMode ??= $this->productionModes[$profile] ?? false; + $this->profile = $profile; + $this->productionMode = $productionMode; + } +} diff --git a/php/src/app/config/YamlConfig.php b/php/src/app/config/YamlConfig.php new file mode 100644 index 0000000..e248c9c --- /dev/null +++ b/php/src/app/config/YamlConfig.php @@ -0,0 +1,13 @@ + $projdir, + "vendor" => [ + "bindir" => "$projdir/vendor/bin", + "autoload" => "$projdir/vendor/autoload.php", + ], + "appcode" => "nur-ture", + "cwd" => $cwd, + "datadir" => "$projdir/devel", + "etcdir" => "$projdir/devel/etc", + "vardir" => "$projdir/devel/var", + "logdir" => "$projdir/devel/log", + "profile" => "devel", + "appgroup" => null, + "name" => "my-application1", + "title" => null, + ], $app1->getParams()); + + $app2 = myapp::with(MyApplication2::class, $app1); + self::assertSame([ + "projdir" => $projdir, + "vendor" => [ + "bindir" => "$projdir/vendor/bin", + "autoload" => "$projdir/vendor/autoload.php", + ], + "appcode" => "nur-ture", + "cwd" => $cwd, + "datadir" => "$projdir/devel", + "etcdir" => "$projdir/devel/etc", + "vardir" => "$projdir/devel/var", + "logdir" => "$projdir/devel/log", + "profile" => "devel", + "appgroup" => null, + "name" => "my-application2", + "title" => null, + ], $app2->getParams()); + } + + function testInit() { + $projdir = config::get_projdir(); + $cwd = getcwd(); + + myapp::reset(); + myapp::init(MyApplication1::class); + self::assertSame([ + "projdir" => $projdir, + "vendor" => [ + "bindir" => "$projdir/vendor/bin", + "autoload" => "$projdir/vendor/autoload.php", + ], + "appcode" => "nur-ture", + "cwd" => $cwd, + "datadir" => "$projdir/devel", + "etcdir" => "$projdir/devel/etc", + "vardir" => "$projdir/devel/var", + "logdir" => "$projdir/devel/log", + "profile" => "devel", + "appgroup" => null, + "name" => "my-application1", + "title" => null, + ], myapp::get()->getParams()); + + myapp::init(MyApplication2::class); + self::assertSame([ + "projdir" => $projdir, + "vendor" => [ + "bindir" => "$projdir/vendor/bin", + "autoload" => "$projdir/vendor/autoload.php", + ], + "appcode" => "nur-ture", + "cwd" => $cwd, + "datadir" => "$projdir/devel", + "etcdir" => "$projdir/devel/etc", + "vardir" => "$projdir/devel/var", + "logdir" => "$projdir/devel/log", + "profile" => "devel", + "appgroup" => null, + "name" => "my-application2", + "title" => null, + ], myapp::get()->getParams()); + } + } +} + +namespace nulib\app\impl { + + use nulib\app\cli\Application; + use nulib\os\path; + use nulib\app\app; + + class config { + const PROJDIR = __DIR__.'/../..'; + + static function get_projdir(): string { + return path::abspath(self::PROJDIR); + } + } + + class myapp extends app { + static function reset(): void { + self::$app = null; + } + } + + class MyApplication1 extends Application { + const PROJDIR = config::PROJDIR; + + function main() { + } + } + class MyApplication2 extends Application { + const PROJDIR = null; + + function main() { + } + } +} diff --git a/php/tests/app/cli/AodefTest.php b/php/tests/app/cli/AodefTest.php new file mode 100644 index 0000000..7d1c828 --- /dev/null +++ b/php/tests/app/cli/AodefTest.php @@ -0,0 +1,159 @@ +setup1(); + $aodef->setup2(); + #var_export($aodef->debugInfos()); #XXX + self::assertSame($options, $aodef->getOptions()); + self::assertSame($haveShortOptions, $aodef->haveShortOptions, "haveShortOptions"); + self::assertSame($haveLongOptions, $aodef->haveLongOptions, "haveLongOptions"); + self::assertSame($isCommand, $aodef->isCommand, "isCommand"); + self::assertSame($haveArgs, $aodef->haveArgs, "haveArgs"); + self::assertSame($minArgs, $aodef->minArgs, "minArgs"); + self::assertSame($maxArgs, $aodef->maxArgs, "maxArgs"); + self::assertSame($argsdesc, $aodef->argsdesc, "argsdesc"); + } + + function testArgsNone() { + $aodef = new Aodef(["-o"]); + self::assertArg($aodef, + ["-o"], + true, false, false, + false, 0, 0, ""); + + $aodef = new Aodef(["--longo"]); + self::assertArg($aodef, + ["--longo"], + false, true, false, + false, 0, 0, ""); + + $aodef = new Aodef(["-o", "--longo"]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + false, 0, 0, ""); + } + + function testArgsMandatory() { + $aodef = new Aodef(["-o:", "--longo"]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 1, 1, "VALUE"); + + $aodef = new Aodef(["-a:", "-b:"]); + self::assertArg($aodef, + ["-a", "-b"], + true, false, false, + true, 1, 1, "VALUE"); + + $aodef = new Aodef(["-a:", "-b::"]); + self::assertArg($aodef, + ["-a", "-b"], + true, false, false, + true, 1, 1, "VALUE"); + + $aodef = new Aodef(["-a::", "-b:"]); + self::assertArg($aodef, + ["-a", "-b"], + true, false, false, + true, 1, 1, "VALUE"); + + $aodef = new Aodef(["-o", "--longo", "args" => true]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 1, 1, "VALUE"); + + $aodef = new Aodef(["-o", "--longo", "args" => 1]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 1, 1, "VALUE"); + + $aodef = new Aodef(["-o", "--longo", "args" => "value"]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 1, 1, "VALUE"); + + $aodef = new Aodef(["-o", "--longo", "args" => ["value"]]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 1, 1, "VALUE"); + } + + function testArgsOptional() { + $aodef = new Aodef(["-o::", "--longo"]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 0, 1, "[VALUE]"); + + $aodef = new Aodef(["-o", "--longo", "args" => [["value"]]]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 0, 1, "[VALUE]"); + + $aodef = new Aodef(["-o", "--longo", "args" => [[null]]]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 0, PHP_INT_MAX, "[VALUEs...]"); + + $aodef = new Aodef(["-o", "--longo", "args" => ["value", null]]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 1, PHP_INT_MAX, "VALUE [VALUEs...]"); + + $aodef = new Aodef(["-o", "--longo", "args" => "*"]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 0, PHP_INT_MAX, "[VALUEs...]"); + + $aodef = new Aodef(["-o", "--longo", "args" => "+"]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 1, PHP_INT_MAX, "VALUE [VALUEs...]"); + } + + function testMerge() { + $BASE = ["-o:", "--longo"]; + + $aodef = new Aodef([ + "merge" => $BASE, + "add" => ["-a", "--longa"], + "remove" => ["-o", "--longo"], + ]); + self::assertArg($aodef, + ["-a", "--longa"], + true, true, false, + false, 0, 0, ""); + + $aodef = new Aodef([ + "merge" => $BASE, + "add" => ["-a", "--longa"], + "remove" => ["-o", "--longo"], + "-x", + ]); + self::assertArg($aodef, + ["-a", "--longa", "-x"], + true, true, false, + false, 0, 0, ""); + } +} diff --git a/php/tests/app/cli/AolistTest.php b/php/tests/app/cli/AolistTest.php new file mode 100644 index 0000000..4b60541 --- /dev/null +++ b/php/tests/app/cli/AolistTest.php @@ -0,0 +1,63 @@ + "value", + ["--opt"], + ["group", + ["--gopt1"], + ["--gopt2"], + ], + "sections" => [ + [ + ["--s0opt"], + ["group", + ["--s0gopt1"], + ["--s0gopt2"], + ], + ], + "ns" => [ + ["--nsopt"], + ["group", + ["--nsgopt1"], + ["--nsgopt2"], + ], + ], + ], + ]) extends Aolist {}; + + echo "$aolist\n"; + self::assertTrue(true); + } +} diff --git a/php/tests/app/cli/SimpleAolistTest.php b/php/tests/app/cli/SimpleAolistTest.php new file mode 100644 index 0000000..e216f8d --- /dev/null +++ b/php/tests/app/cli/SimpleAolistTest.php @@ -0,0 +1,59 @@ + [ + ["-o", "--longo"], + ], + ]); + echo "$aolist\n"; #XXX + + $aolist = new SimpleAolist([ + ["-o", "--longo"], + ["-o", "--longx"], + ]); + echo "$aolist\n"; #XXX + + $aolist = new SimpleAolist([ + ["-o", "--longo"], + ["-o"], + ["--longo"], + ]); + echo "$aolist\n"; #XXX + + self::assertTrue(true); + } + + function testExtends() { + $ARGS0 = [ + ["-o:", "--longo", + "name" => "desto", + "help" => "help longo" + ], + ["-a:", "--longa", + "name" => "desta", + "help" => "help longa" + ], + ]; + $ARGS = [ + "merge" => $ARGS0, + ["extends" => "-a", + "remove" => ["--longa"], + "add" => ["--desta"], + "help" => "help desta" + ], + ]; + //$aolist0 = new SimpleArgDefs($ARGS0); + //echo "$aolist0\n"; #XXX + $aolist = new SimpleAolist($ARGS); + echo "$aolist\n"; #XXX + + self::assertTrue(true); + } +} diff --git a/php/tests/app/cli/SimpleArgsParserTest.php b/php/tests/app/cli/SimpleArgsParserTest.php new file mode 100644 index 0000000..6dd73e7 --- /dev/null +++ b/php/tests/app/cli/SimpleArgsParserTest.php @@ -0,0 +1,175 @@ + [["value", "value"]]], + ["--mo12:", "args" => ["value", ["value"]]], + ["--mo22:", "args" => ["value", "value"]], + ]; + const NORMALIZE_TESTS = [ + [], ["--"], + ["--"], ["--"], + ["--", "--"], ["--", "--"], + ["-aa"], ["-a", "-a", "--"], + ["a", "b"], ["--", "a", "b"], + ["-a", "--ma", "x", "a", "--ma=y", "b"], ["-a", "--mandatory", "x", "--mandatory", "y", "--", "a", "b"], + ["-mx", "-m", "y"], ["-m", "x", "-m", "y", "--"], + ["-ox", "-o", "y"], ["-ox", "-o", "--", "y"], + ["-a", "--", "-a", "-c"], ["-a", "--", "-a", "-c"], + + # -a et -b doivent être considérés comme arguments, -n comme option + ["--mo02"], ["--mo02", "--", "--"], + ["--mo02", "-a"], ["--mo02", "-a", "--", "--"], + ["--mo02", "--"], ["--mo02", "--", "--"], + ["--mo02", "--", "-n"], ["--mo02", "--", "-n", "--"], + ["--mo02", "--", "--", "-b"], ["--mo02", "--", "--", "-b"], + # + ["--mo02", "-a"], ["--mo02", "-a", "--", "--"], + ["--mo02", "-a", "-a"], ["--mo02", "-a", "-a", "--"], + ["--mo02", "-a", "--"], ["--mo02", "-a", "--", "--"], + ["--mo02", "-a", "--", "-n"], ["--mo02", "-a", "--", "-n", "--"], + ["--mo02", "-a", "--", "--", "-b"], ["--mo02", "-a", "--", "--", "-b"], + + [ + "--mo02", "--", + "--mo02", "x", "--", + "--mo02", "x", "y", + "--mo12", "x", "--", + "--mo12", "x", "y", + "--mo22", "x", "y", + "z", + ], [ + "--mo02", "--", + "--mo02", "x", "--", + "--mo02", "x", "y", + "--mo12", "x", "--", + "--mo12", "x", "y", + "--mo22", "x", "y", + "--", + "z", + ], + ]; + + function testNormalize() { + $parser = new SimpleArgsParser(self::NORMALIZE_ARGS); + $count = count(self::NORMALIZE_TESTS); + for ($i = 0; $i < $count; $i += 2) { + $args = self::NORMALIZE_TESTS[$i]; + $expected = self::NORMALIZE_TESTS[$i + 1]; + $normalized = $parser->normalize($args); + self::assertSame($expected, $normalized + , "for ".var_export($args, true) + .", normalized is ".var_export($normalized, true) + ); + } + } + + function testArgsNone() { + $parser = new SimpleArgsParser([ + ["-z"], + ["-a"], + ["-b"], + ["-c",], + ["-d", "value" => 42], + ]); + + $dest = []; $parser->parse($dest, ["-a", "-bb", "-ccc", "-dddd"]); + self::assertSame(null, $dest["z"] ?? null); + self::assertSame(1, $dest["a"] ?? null); + self::assertSame(2, $dest["b"] ?? null); + self::assertSame(3, $dest["c"] ?? null); + self::assertSame(42, $dest["d"] ?? null); + + self::assertTrue(true); + } + + function testArgsMandatory() { + $parser = new SimpleArgsParser([ + ["-z:"], + ["-a:"], + ["-b:"], + ["-c:", "value" => 42], + ]); + + $dest = []; $parser->parse($dest, [ + "-a", + "-bb", + "-c", + "-c15", + "-c30", + "-c45", + ]); + self::assertSame(null, $dest["z"] ?? null); + self::assertSame("-bb", $dest["a"] ?? null); + self::assertSame(null, $dest["b"] ?? null); + self::assertSame("45", $dest["c"] ?? null); + + self::assertTrue(true); + } + + function testArgsOptional() { + $parser = new SimpleArgsParser([ + ["-z::"], + ["-a::"], + ["-b::"], + ["-c::", "value" => 42], + ["-d::", "value" => 42], + ]); + + $dest = []; $parser->parse($dest, [ + "-a", + "-bb", + "-c", + "-d15", + "-d30", + ]); + self::assertSame(null, $dest["z"] ?? null); + self::assertSame(null, $dest["a"] ?? null); + self::assertSame("b", $dest["b"] ?? null); + self::assertSame(42, $dest["c"] ?? null); + self::assertSame("30", $dest["d"] ?? null); + + self::assertTrue(true); + } + + function testRemains() { + $parser = new SimpleArgsParser([]); + $dest = []; $parser->parse($dest, ["x", "y"]); + self::assertSame(["x", "y"], $dest["args"] ?? null); + } + + function test() { + $parser = new SimpleArgsParser([ + ["-n", "--none"], + ["-m:", "--mandatory"], + ["-o::", "--optional"], + ["--mo02:", "args" => [["value", "value"]]], + ["--mo12:", "args" => ["value", ["value"]]], + ["--mo22:", "args" => ["value", "value"]], + ]); + $parser->parse($dest, [ + "--mo02", "--", + "--mo02", "x", "--", + "--mo02", "x", "y", + "--mo12", "x", "--", + "--mo12", "x", "y", + "--mo22", "x", "y", + "z", + ]); + + self::assertTrue(true); + } +} diff --git a/php/tests/app/config/ConfigManagerTest.php b/php/tests/app/config/ConfigManagerTest.php new file mode 100644 index 0000000..69ba5f8 --- /dev/null +++ b/php/tests/app/config/ConfigManagerTest.php @@ -0,0 +1,124 @@ +addConfigurator(config1::class); + $config->configure(); + self::assertSame([ + "config1::static configure1", + ], impl\result::$configured); + + result::reset(); + $config->addConfigurator(config1::class); + $config->configure(); + $config->configure(); + $config->configure(); + self::assertSame([ + "config1::static configure1", + ], impl\result::$configured); + + result::reset(); + $config->addConfigurator(new config1()); + $config->configure(); + self::assertSame([ + "config1::static configure1", + "config1::configure2", + ], impl\result::$configured); + + result::reset(); + $config->addConfigurator(new config1()); + $config->configure(["include" => "2"]); + self::assertSame([ + "config1::configure2", + ], impl\result::$configured); + $config->configure(["include" => "1"]); + self::assertSame([ + "config1::configure2", + "config1::static configure1", + ], impl\result::$configured); + + result::reset(); + $config->addConfigurator([ + config1::class, + new config2(), + ]); + $config->configure(); + self::assertSame([ + "config1::static configure1", + "config2::static configure1", + "config2::configure2", + ], impl\result::$configured); + } + + function testConfig() { + $config = new ConfigManager(); + + $config->addConfig([ + "app" => [ + "var" => "array", + ] + ]); + self::assertSame("array", $config->getValue("app.var")); + + $config->addConfig(new ArrayConfig([ + "app" => [ + "var" => "instance", + ] + ])); + self::assertSame("instance", $config->getValue("app.var")); + + $config->addConfig(config1::class); + self::assertSame("class1", $config->getValue("app.var")); + + $config->addConfig(config2::class); + self::assertSame("class2", $config->getValue("app.var")); + } + } +} + +namespace nulib\app\config\impl { + class result { + static array $configured = []; + + static function reset() { + self::$configured = []; + } + } + + class config1 { + const APP = [ + "var" => "class1", + ]; + + static function configure1() { + result::$configured[] = "config1::static configure1"; + } + + function configure2() { + result::$configured[] = "config1::configure2"; + } + } + + class config2 { + const APP = [ + "var" => "class2", + ]; + + static function configure1() { + result::$configured[] = "config2::static configure1"; + } + + function configure2() { + result::$configured[] = "config2::configure2"; + } + } +} From 58af45cc996470df195659395e2ad7c30d9eebbb Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 02:08:29 +0400 Subject: [PATCH 21/91] modifs.mineures sans commentaires --- php/src/app/app.php | 43 +++++++++-------- php/src/app/cli/Application.php | 75 +++++------------------------ php/src/output/console.php | 48 ++++++++++++++++++ php/src/output/log.php | 10 ++++ php/tbin/{mail.php => sendmail.php} | 21 ++++---- 5 files changed, 103 insertions(+), 94 deletions(-) rename php/tbin/{mail.php => sendmail.php} (53%) diff --git a/php/src/app/app.php b/php/src/app/app.php index 9242de1..9781202 100644 --- a/php/src/app/app.php +++ b/php/src/app/app.php @@ -28,7 +28,7 @@ class app { "class" => $class, "projdir" => $app::PROJDIR, "vendor" => $app::VENDOR, - "appcode" => $app::APPCODE, + "projcode" => $app::PROJCODE, "datadir" => $app::DATADIR, "etcdir" => $app::ETCDIR, "vardir" => $app::VARDIR, @@ -43,7 +43,7 @@ class app { "class" => $class, "projdir" => constant("$app::PROJDIR"), "vendor" => constant("$app::VENDOR"), - "appcode" => constant("$app::APPCODE"), + "projcode" => constant("$app::PROJCODE"), "datadir" => constant("$app::DATADIR"), "etcdir" => constant("$app::ETCDIR"), "vardir" => constant("$app::VARDIR"), @@ -75,7 +75,7 @@ class app { A::merge($params, cl::select($proj_params, [ "projdir", "vendor", - "appcode", + "projcode", "cwd", "datadir", "etcdir", @@ -156,7 +156,7 @@ class app { [ "projdir" => $projdir, "vendor" => $vendor, - "appcode" => $appcode, + "projcode" => $projcode, "datadir" => $datadir, "etcdir" => $etcdir, "vardir" => $vardir, @@ -187,32 +187,32 @@ class app { $vendor = $params["vendor"] ?? self::DEFAULT_VENDOR; $vendor["bindir"] = path::reljoin($projdir, $vendor["bindir"]); $vendor["autoload"] = path::reljoin($projdir, $vendor["autoload"]); - # appcode - $appcode = $params["appcode"] ?? null; - if ($appcode === null) { - $appcode = str::without_suffix("-app", path::basename($projdir)); + # projcode + $projcode = $params["projcode"] ?? null; + if ($projcode === null) { + $projcode = str::without_suffix("-app", path::basename($projdir)); } - $APPCODE = str_replace("-", "_", strtoupper($appcode)); + $PROJCODE = str_replace("-", "_", strtoupper($projcode)); # cwd $cwd = $params["cwd"] ?? null; # datadir - $datadir = getenv("${APPCODE}_DATADIR"); + $datadir = getenv("${PROJCODE}_DATADIR"); $datadirIsDefined = $datadir !== false; if ($datadir === false) $datadir = $params["datadir"] ?? null; if ($datadir === null) $datadir = "devel"; $datadir = path::reljoin($projdir, $datadir); # etcdir - $etcdir = getenv("${APPCODE}_ETCDIR"); + $etcdir = getenv("${PROJCODE}_ETCDIR"); if ($etcdir === false) $etcdir = $params["etcdir"] ?? null; if ($etcdir === null) $etcdir = "etc"; $etcdir = path::reljoin($datadir, $etcdir); # vardir - $vardir = getenv("${APPCODE}_VARDIR"); + $vardir = getenv("${PROJCODE}_VARDIR"); if ($vardir === false) $vardir = $params["vardir"] ?? null; if ($vardir === null) $vardir = "var"; $vardir = path::reljoin($datadir, $vardir); # logdir - $logdir = getenv("${APPCODE}_LOGDIR"); + $logdir = getenv("${PROJCODE}_LOGDIR"); if ($logdir === false) $logdir = $params["logdir"] ?? null; if ($logdir === null) $logdir = "log"; $logdir = path::reljoin($datadir, $logdir); @@ -222,7 +222,7 @@ class app { # profile $this->profileManager = new ProfileManager([ "app" => true, - "name" => $appcode, + "name" => $projcode, "default_profile" => $datadirIsDefined? "prod": "devel", "profile" => $params["profile"] ?? null, ]); @@ -233,7 +233,7 @@ class app { $this->projdir = $projdir; $this->vendor = $vendor; - $this->appcode = $appcode; + $this->projcode = $projcode; $this->cwd = $cwd; $this->datadir = $datadir; $this->etcdir = $etcdir; @@ -244,13 +244,14 @@ class app { $appgroup = $params["appgroup"] ?? null; $name = $params["name"] ?? $params["class"] ?? null; if ($name === null) { - $name = $appcode; + $name = $projcode; } else { # si $name est une classe, enlever le package et normaliser i.e - # my\package\MyApplication --> my-application + # my\package\MyApplication --> my-application.php $name = preg_replace('/.*\\\\/', "", $name); $name = str::camel2us($name, false, "-"); $name = str::without_suffix("-app", $name); + $name .= ".php"; } $this->appgroup = $appgroup; $this->name = $name; @@ -277,10 +278,10 @@ class app { return $this->vendor["autoload"]; } - protected string $appcode; + protected string $projcode; - function getAppcode(): string { - return $this->appcode; + function getProjcode(): string { + return $this->projcode; } protected string $cwd; @@ -432,7 +433,7 @@ class app { return [ "projdir" => $this->projdir, "vendor" => $this->vendor, - "appcode" => $this->appcode, + "projcode" => $this->projcode, "cwd" => $this->cwd, "datadir" => $this->datadir, "etcdir" => $this->etcdir, diff --git a/php/src/app/cli/Application.php b/php/src/app/cli/Application.php index 5a947aa..679eb09 100644 --- a/php/src/app/cli/Application.php +++ b/php/src/app/cli/Application.php @@ -14,7 +14,6 @@ use nulib\output\console; use nulib\output\log; use nulib\output\msg; use nulib\output\std\StdMessenger; -use nulib\ValueException; /** * Class Application: application de base @@ -34,13 +33,13 @@ abstract class Application { /** * @var string code du projet, utilisé pour dériver le noms de certains des - * paramètres extraits de l'environnement, e.g XXX_YYY_DATADIR si le projet a - * pour code xxx-yyy + * paramètres extraits de l'environnement, e.g MY_APP_DATADIR si le projet a + * pour code my-app * * si non définie, cette valeur est calculée automatiquement à partir de * self::PROJDIR sans le suffixe "-app" */ - const APPCODE = null; + const PROJCODE = null; /** * @var string|null identifiant d'un groupe auquel l'application appartient. @@ -245,7 +244,7 @@ EOT); } const PROFILE_SECTION = [ - "title" => "PROFILS D'EXECUTION", + "title" => "PROFIL D'EXECUTION", "show" => false, ["group", ["-p", "--profile", "--app-profile", @@ -265,79 +264,27 @@ EOT); ["group", ["--verbosity", "args" => "verbosity", "argsdesc" => "silent|quiet|verbose|debug", - "action" => [null, "set_application_verbosity"], + "action" => [console::class, "set_verbosity"], "help" => "spécifier le niveau d'informations affiché", ], - ["-q", "--quiet", "action" => [null, "set_application_verbosity", "quiet"]], - ["-v", "--verbose", "action" => [null, "set_application_verbosity", "verbose"]], - ["-D", "--debug", "action" => [null, "set_application_verbosity", "debug"]], + ["-q", "--quiet", "action" => [console::class, "set_verbosity", "quiet"]], + ["-v", "--verbose", "action" => [console::class, "set_verbosity", "verbose"]], + ["-D", "--debug", "action" => [console::class, "set_verbosity", "debug"]], ], ["-L", "--logfile", "args" => "output", - "action" => [null, "set_application_log_output"], + "action" => [log::class, "set_output"], "help" => "Logger les messages de l'application dans le fichier spécifié", ], ["group", ["--color", - "action" => [null, "set_application_color", true], + "action" => [console::class, "set_color", true], "help" => "Afficher (resp. ne pas afficher) la sortie en couleur par défaut", ], - ["--no-color", "action" => [null, "set_application_color", false]], + ["--no-color", "action" => [console::class, "set_color", false]], ], ]; - static function set_application_verbosity(string $verbosity): void { - $console = console::get(); - switch ($verbosity) { - case "Q": - case "silent": - $console->resetParams([ - "min_level" => msg::NONE, - ]); - break; - case "q": - case "quiet": - $console->resetParams([ - "min_level" => msg::MAJOR, - ]); - break; - case "n": - case "normal": - $console->resetParams([ - "min_level" => msg::NORMAL, - ]); - break; - case "v": - case "verbose": - $console->resetParams([ - "min_level" => msg::MINOR, - ]); - break; - case "D": - case "debug": - app::set_debug(); - $console->resetParams([ - "min_level" => msg::DEBUG, - ]); - break; - default: - throw ValueException::invalid_value($verbosity, "verbosity"); - } - } - - static function set_application_log_output(string $logfile): void { - log::create_or_reset_params([ - "output" => $logfile, - ], StdMessenger::class, [ - "add_date" => true, - "min_level" => log::MINOR, - ]); - } - static function set_application_color(bool $color): void { - console::reset_params([ - "color" => $color, - ]); - } const ARGS = [ "sections" => [ self::PROFILE_SECTION, diff --git a/php/src/output/console.php b/php/src/output/console.php index 0ef8c81..f836620 100644 --- a/php/src/output/console.php +++ b/php/src/output/console.php @@ -1,7 +1,10 @@ resetParams([ + "min_level" => msg::NONE, + ]); + break; + case "q": + case "quiet": + $console->resetParams([ + "min_level" => msg::MAJOR, + ]); + break; + case "n": + case "normal": + $console->resetParams([ + "min_level" => msg::NORMAL, + ]); + break; + case "v": + case "verbose": + $console->resetParams([ + "min_level" => msg::MINOR, + ]); + break; + case "D": + case "debug": + app::set_debug(); + $console->resetParams([ + "min_level" => msg::DEBUG, + ]); + break; + default: + throw ValueException::invalid_value($verbosity, "verbosity"); + } + } + + static function set_color(bool $color=true): void { + console::reset_params([ + "color" => $color, + ]); + } } diff --git a/php/src/output/log.php b/php/src/output/log.php index 103727f..2e1d3ec 100644 --- a/php/src/output/log.php +++ b/php/src/output/log.php @@ -2,6 +2,7 @@ namespace nulib\output; use nulib\output\std\ProxyMessenger; +use nulib\output\std\StdMessenger; /** * Class log: inscrire un message dans les logs uniquement @@ -25,4 +26,13 @@ class log extends _messenger { static function get(): IMessenger { return self::$msg ??= new ProxyMessenger(); } + + static function set_output(string $logfile): void { + self::create_or_reset_params([ + "output" => $logfile, + ], StdMessenger::class, [ + "add_date" => true, + "min_level" => self::MINOR, + ]); + } } diff --git a/php/tbin/mail.php b/php/tbin/sendmail.php similarity index 53% rename from php/tbin/mail.php rename to php/tbin/sendmail.php index eda2794..89f1c01 100755 --- a/php/tbin/mail.php +++ b/php/tbin/sendmail.php @@ -2,25 +2,28 @@ "SUBJECT BODY -t RECIPIENT", "merge" => parent::ARGS, - ["-t", "--to", "args" => 1, "action" => "--add", "name" => "to"], - ["-c", "--cc", "args" => 1, "action" => "--add", "name" => "cc"], - ["-b", "--bcc", "args" => 1, "action" => "--add", "name" => "bcc"], - ["-F", "--from", "args" => 1, "name" => "from"], + ["-F", "--from", "args" => 1, "name" => "from", "argsdesc" => "MAILFROM"], + ["-t", "--to", "args" => 1, "action" => "--add", "name" => "to", "argsdesc" => "RECIPIENT"], + ["-c", "--cc", "args" => 1, "action" => "--add", "name" => "cc", "argsdesc" => "RECIPIENT"], + ["-b", "--bcc", "args" => 1, "action" => "--add", "name" => "bcc", "argsdesc" => "RECIPIENT"], ["args" => 2, "name" => "args"], ]; protected $to, $cc, $bcc, $from; - protected $args; function main() { - $subject = $this->args[0]; - $body = $this->args[1]; + $subject = $this->args[0] ?? null; + ValueException::check_null($subject, "subject"); + $body = $this->args[1] ?? null; + ValueException::check_null($body, "body"); mailer::send($this->to, $subject, $body, $this->cc, $this->bcc, $this->from); } }); From 31692fd63700fabb79ef8849e7fa2a95bfdd085a Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 02:30:28 +0400 Subject: [PATCH 22/91] maj phpwrappers --- .../{SteamTrainApp.php => _SteamTrainApp.php} | 2 +- runphp/phpwrapper-.launcher.php | 8 ++++---- runphp/phpwrapper-_bg_launcher.php | 6 +++--- runphp/phpwrapper-_wrapper.sh | 7 +++---- runphp/update-runphp.sh | 16 +++++++++++++++- 5 files changed, 26 insertions(+), 13 deletions(-) rename php/cli/{SteamTrainApp.php => _SteamTrainApp.php} (97%) diff --git a/php/cli/SteamTrainApp.php b/php/cli/_SteamTrainApp.php similarity index 97% rename from php/cli/SteamTrainApp.php rename to php/cli/_SteamTrainApp.php index 6406cbf..7113daf 100644 --- a/php/cli/SteamTrainApp.php +++ b/php/cli/_SteamTrainApp.php @@ -7,7 +7,7 @@ use nulib\output\msg; use nulib\php\time\DateTime; use nulib\text\words; -class SteamTrainApp extends Application { +class _SteamTrainApp extends Application { const PROJDIR = __DIR__.'/../..'; const TITLE = "Train à vapeur"; const USE_LOGFILE = true; diff --git a/runphp/phpwrapper-.launcher.php b/runphp/phpwrapper-.launcher.php index adabe3d..0fb509c 100644 --- a/runphp/phpwrapper-.launcher.php +++ b/runphp/phpwrapper-.launcher.php @@ -1,12 +1,12 @@ # TODO Faire une copie de ce script dans un répertoire de l'application web -# (dans le répertoire cli_config/ par défaut) et modifier les paramètres si nécessaire +# (dans le répertoire cli/config/ par défaut) et modifier les paramètres si nécessaire #------------------------------------------------------------------------------- __DIR__.'/..', + "projdir" => __DIR__.'/@@CLI2PROJ@@', "appcode" => \app\config\bootstrap::APPCODE, ]; -require __DIR__.'/../vendor/nulib/base/php/src/app/cli/include-launcher.php'; +require __DIR__.'/@@CLI2PROJ@@/vendor/nulib/base/php/src/app/cli/include-launcher.php'; diff --git a/runphp/phpwrapper-_bg_launcher.php b/runphp/phpwrapper-_bg_launcher.php index 5b9a74b..13affdc 100644 --- a/runphp/phpwrapper-_bg_launcher.php +++ b/runphp/phpwrapper-_bg_launcher.php @@ -2,17 +2,17 @@ # (dans le répertoire sbin/ par défaut) et modifier les paramètres si nécessaire #------------------------------------------------------------------------------- __DIR__.'/..', + "projdir" => __DIR__.'/@@SBIN2PROJ@@', "appcode" => \app\config\bootstrap::APPCODE, ]); BgLauncherApp::run(); diff --git a/runphp/phpwrapper-_wrapper.sh b/runphp/phpwrapper-_wrapper.sh index 05569e8..5320701 100644 --- a/runphp/phpwrapper-_wrapper.sh +++ b/runphp/phpwrapper-_wrapper.sh @@ -1,5 +1,5 @@ # TODO Faire une copie de ce script dans un répertoire de l'application web -# (dans le répertoire cli_config/ par défaut) et modifier les paramétres si nécessaire +# (dans le répertoire cli/config/ par défaut) et modifier les paramétres si nécessaire #------------------------------------------------------------------------------- #!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 @@ -7,7 +7,7 @@ # Tous les chemins suivants sont relatifs au répertoire qui contient ce script # Chemin relatif de la racine du projet -PROJPATH=.. +PROJPATH=@@CLI2PROJ@@ # Chemin relatif vers le lanceur PHP LAUNCHERPATH=.launcher.php @@ -100,10 +100,9 @@ if [ "$RUNPHP_MODE" == host ]; then args+=( --workdir "$cwd" "$COMPOSE_SERVICE" - exec "$MYNAME" + exec "$0" "$@" ) - cd "$PROJDIR" exec "${args[@]}" fi diff --git a/runphp/update-runphp.sh b/runphp/update-runphp.sh index b4555f5..2975e6b 100755 --- a/runphp/update-runphp.sh +++ b/runphp/update-runphp.sh @@ -157,19 +157,31 @@ if [ -n "$projdir" ]; then fi sbin_path=sbin - cli_path=cli_config + sbin2proj=.. + cli_path=cli/config + cli2proj=../.. if [ "$install_phpwrappers" == auto ]; then if [ ! -f "$PROJDIR/$COMPOSERDIR/composer.json" ]; then # ce doit être un projet PHP install_phpwrappers= + elif [ -d "$projdir/cli/config" ]; then + install_phpwrappers=1 + sbin_path=sbin + sbin2proj=.. + cli_path=cli/config + cli2proj=../.. elif [ -d "$projdir/cli_config" ]; then install_phpwrappers=1 sbin_path=sbin + sbin2proj=.. cli_path=cli_config + cli2proj=../.. elif [ -d "$projdir/_cli" ]; then install_phpwrappers=1 sbin_path=sbin + sbin2proj=.. cli_path=_cli + cli2proj=../.. else install_phpwrappers= fi @@ -193,7 +205,9 @@ if [ -n "$projdir" ]; then mkdir -p "$destdir" tail -n+4 "$MYDIR/$phpwrapper" | sed " s|/@@SBIN@@/|/$sbin_path/| +s|@@SBIN2PROJ@@|$sbin2proj| s|/@@CLI@@/|/$cli_path/| +s|@@CLI2PROJ@@|$cli2proj| " >"$destdir/$destname" [ -n "$mode" ] && chmod "$mode" "$destdir/$destname" done From e1d30984a476fb6905ab4a18f0ab6e2b21813109 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 02:33:04 +0400 Subject: [PATCH 23/91] modifs.mineures sans commentaires --- composer.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index f982da1..61f5643 100644 --- a/composer.json +++ b/composer.json @@ -47,14 +47,14 @@ "nulib\\": "php/tests" } }, - "bin": [ - "php/bin/dumpser.php", - "php/bin/json2yml.php", - "php/bin/yml2json.php", - "php/bin/sqlite.capacitor.php", - "php/bin/mysql.capacitor.php", - "php/bin/pgsql.capacitor.php" - ], + "bin": [ + "php/bin/dumpser.php", + "php/bin/json2yml.php", + "php/bin/yml2json.php", + "php/bin/sqlite.capacitor.php", + "php/bin/mysql.capacitor.php", + "php/bin/pgsql.capacitor.php" + ], "config": { "vendor-dir": "php/vendor" }, From d7f82a2fd986ae9046b9fc8f3829072a49fb7586 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 02:46:07 +0400 Subject: [PATCH 24/91] modifs.mineures sans commentaires --- runphp/phpwrapper-.launcher.php | 2 +- runphp/phpwrapper-_bg_launcher.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runphp/phpwrapper-.launcher.php b/runphp/phpwrapper-.launcher.php index 0fb509c..802d9cd 100644 --- a/runphp/phpwrapper-.launcher.php +++ b/runphp/phpwrapper-.launcher.php @@ -7,6 +7,6 @@ require __DIR__.'/@@CLI2PROJ@@/vendor/autoload.php'; const NULIB_APP_app_params = [ "projdir" => __DIR__.'/@@CLI2PROJ@@', - "appcode" => \app\config\bootstrap::APPCODE, + "projcode" => \app\config\bootstrap::PROJCODE, ]; require __DIR__.'/@@CLI2PROJ@@/vendor/nulib/base/php/src/app/cli/include-launcher.php'; diff --git a/runphp/phpwrapper-_bg_launcher.php b/runphp/phpwrapper-_bg_launcher.php index 13affdc..86e28aa 100644 --- a/runphp/phpwrapper-_bg_launcher.php +++ b/runphp/phpwrapper-_bg_launcher.php @@ -5,14 +5,14 @@ # Lancer une application en tâche de fond require __DIR__.'/@@SBIN2PROJ@@/vendor/autoload.php'; +use cli\BgLauncherApp; use nulib\app\app; -use nulib\cli\BgLauncherApp; # chemin vers le lanceur PHP const NULIB_APP_app_launcher = __DIR__.'/@@SBIN2PROJ@@/@@CLI@@/.launcher.php'; app::init([ "projdir" => __DIR__.'/@@SBIN2PROJ@@', - "appcode" => \app\config\bootstrap::APPCODE, + "projcode" => \app\config\bootstrap::PROJCODE, ]); BgLauncherApp::run(); From 8c72c5c3ebfcc5590d2a86491bccc107b5763b7a Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 03:28:52 +0400 Subject: [PATCH 25/91] modifs.mineures sans commentaires --- php/src/app/app.php | 1 - php/src/app/cli/Application.php | 9 +++++++-- php/src/app/config.php | 17 +++++++++++++++++ php/src/app/config/ConfigManager.php | 10 ++++------ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/php/src/app/app.php b/php/src/app/app.php index 9781202..b46595a 100644 --- a/php/src/app/app.php +++ b/php/src/app/app.php @@ -251,7 +251,6 @@ class app { $name = preg_replace('/.*\\\\/', "", $name); $name = str::camel2us($name, false, "-"); $name = str::without_suffix("-app", $name); - $name .= ".php"; } $this->appgroup = $appgroup; $this->name = $name; diff --git a/php/src/app/cli/Application.php b/php/src/app/cli/Application.php index 679eb09..330efdf 100644 --- a/php/src/app/cli/Application.php +++ b/php/src/app/cli/Application.php @@ -246,9 +246,14 @@ EOT); const PROFILE_SECTION = [ "title" => "PROFIL D'EXECUTION", "show" => false, + ["-c", "--config", "--app-config", + "args" => "file", "argsdesc" => "CONFIG.yml", + "action" => [config::class, "load_config"], + "help" => "spécifier un fichier de configuration", + ], ["group", - ["-p", "--profile", "--app-profile", - "args" => "profile", + ["-g", "--profile", "--app-profile", + "args" => 1, "argsdesc" => "PROFILE", "action" => [app::class, "set_profile"], "help" => "spécifier le profil d'exécution", ], diff --git a/php/src/app/config.php b/php/src/app/config.php index 08699f9..27469c4 100644 --- a/php/src/app/config.php +++ b/php/src/app/config.php @@ -2,7 +2,12 @@ namespace nulib\app; use nulib\app\config\ConfigManager; +use nulib\app\config\JsonConfig; +use nulib\app\config\YamlConfig; use nulib\cl; +use nulib\os\path; +use nulib\str; +use nulib\ValueException; /** * Class config: gestion de la configuration de l'application @@ -27,6 +32,18 @@ class config { } static final function add($config, string ...$profiles): void { self::$config->addConfig($config, $profiles); } + static final function load_config($file): void { + $ext = path::ext($file); + if ($ext === ".yml" || $ext === ".yaml") { + $config = new YamlConfig($file); + } elseif ($ext === ".json") { + $config = new JsonConfig($file); + } else { + throw ValueException::invalid_value($file, "config file"); + } + self::add($config); + } + static final function get(string $pkey, $default=null, ?string $profile=null) { return self::$config->getValue($pkey, $default, $profile); } static final function k(string $pkey, $default=null) { return self::$config->getValue("app.$pkey", $default); } static final function db(string $pkey, $default=null) { return self::$config->getValue("dbs.$pkey", $default); } diff --git a/php/src/app/config/ConfigManager.php b/php/src/app/config/ConfigManager.php index ef10881..c3ca147 100644 --- a/php/src/app/config/ConfigManager.php +++ b/php/src/app/config/ConfigManager.php @@ -76,7 +76,7 @@ class ConfigManager { * $config est un objet ou une classe qui définit une ou plusieurs des * constantes APP, DBS, MSGS, MAILS * - * si $inProfiles===null, la configuration est valide dans tous les profils + * si !$inProfiles, la configuration est valide dans tous les profils */ function addConfig($config, ?array $inProfiles=null): void { if (is_string($config)) { @@ -96,7 +96,7 @@ class ConfigManager { throw ValueException::invalid_type($config, "array|IConfig"); } - $inProfiles ??= [IConfig::PROFILE_ALL]; + if (!$inProfiles) $inProfiles = [IConfig::PROFILE_ALL]; foreach ($inProfiles as $profile) { $this->profileConfigs[$profile][] = $config; } @@ -107,18 +107,16 @@ class ConfigManager { function _getValue(string $pkey, $default, string $inProfile) { $profiles = [$inProfile]; if ($inProfile !== IConfig::PROFILE_ALL) $profiles[] = IConfig::PROFILE_ALL; - $value = $default; foreach ($profiles as $profile) { /** @var IConfig[] $configs */ $configs = $this->profileConfigs[$profile] ?? []; foreach (array_reverse($configs) as $config) { if ($config->has($pkey, $profile)) { - $value = $config->get($pkey, $profile); - break; + return $config->get($pkey, $profile); } } } - return $value; + return $default; } /** From ef5a0ee7306f472daf96919bac1b706d8152713e Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 07:53:15 +0400 Subject: [PATCH 26/91] modifs.mineures sans commentaires --- php/src/app/args/TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/php/src/app/args/TODO.md b/php/src/app/args/TODO.md index d19db42..98582c3 100644 --- a/php/src/app/args/TODO.md +++ b/php/src/app/args/TODO.md @@ -1,5 +1,7 @@ # nulib\app\args +* [ ] supporter "-o:args" +* [ ] vérifier que remainArgs honore les bornes, notamment si spécifié par l'utilisateur * [ ] dans la section "profils", rajouter une option pour spécifier un fichier de configuration * [ ] transformer un schéma en définition d'arguments, un tableau en liste d'arguments, et vice-versa * [ ] faire une implémentation ArgsParser qui supporte les commandes, et les options dynamiques From c37816e56d462d1f372a3bc06d0eba216921c155 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 11:04:40 +0400 Subject: [PATCH 27/91] modifs.mineures sans commentaires --- php/cli/MysqlCapacitorApp.php | 8 +- php/cli/PgsqlCapacitorApp.php | 8 +- php/cli/SqliteCapacitorApp.php | 8 +- php/cli/_SteamTrainApp.php | 2 +- php/run-tests | 2 +- php/src/A.php | 1 - php/src/app/RunFile.php | 1 - php/src/app/args/AbstractArgsParser.php | 1 - php/src/app/args/Aodef.php | 48 ++-- php/src/app/args/Aogroup.php | 2 - php/src/app/args/Aolist.php | 3 - php/src/app/args/Aosection.php | 2 - php/src/app/args/SimpleAolist.php | 8 +- php/src/app/args/SimpleArgsParser.php | 3 - php/src/app/args/TODO.md | 2 - php/src/app/config.php | 2 - php/src/db/Capacitor.php | 1 - php/src/file/csv/CsvReader.php | 1 - php/src/output/console.php | 1 - php/src/php/time/Delay.php | 1 - php/tbin/output-forever.log | 0 php/tbin/test-application.php | 69 +++++ php/tbin/test-console.php | 278 +++++++++++++++++++++ php/tbin/test-output-forever.php | 18 ++ php/tests/app/appTest.php | 23 +- php/tests/app/argsTest.php | 1 - php/tests/app/cli/AodefTest.php | 16 +- php/tests/app/cli/AolistTest.php | 2 +- php/tests/app/cli/SimpleAolistTest.php | 19 +- php/tests/app/cli/SimpleArgsParserTest.php | 2 +- php/tests/app/config/ConfigManagerTest.php | 4 +- php/tests/db/sqlite/SqliteStorageTest.php | 2 +- php/tests/mail/MailTemplateTest.php | 2 +- php/tests/php/funcTest.php | 5 +- 34 files changed, 470 insertions(+), 76 deletions(-) create mode 100644 php/tbin/output-forever.log create mode 100755 php/tbin/test-application.php create mode 100755 php/tbin/test-console.php create mode 100755 php/tbin/test-output-forever.php diff --git a/php/cli/MysqlCapacitorApp.php b/php/cli/MysqlCapacitorApp.php index a188782..2587d93 100644 --- a/php/cli/MysqlCapacitorApp.php +++ b/php/cli/MysqlCapacitorApp.php @@ -10,13 +10,13 @@ class MysqlCapacitorApp extends AbstractCapacitorApp { "merge" => parent::ARGS, "purpose" => "gestion d'un capacitor mysql", "usage" => [ - "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] [--query] key=value...", - "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] --sql-create", + "DBCONN [channelName | -t table | -c ChannelClass] [--query] key=value...", + "DBCONN [channelName | -t table | -c ChannelClass] --sql-create", ], - ["-t", "--table-name", "args" => 1, + ["-t:table", "--table-name", "help" => "nom de la table porteuse du canal de données", ], - ["-c", "--channel-class", "args" => 1, + ["-c:class", "--channel-class", "help" => "nom de la classe dérivée de CapacitorChannel", ], ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, diff --git a/php/cli/PgsqlCapacitorApp.php b/php/cli/PgsqlCapacitorApp.php index 978ee51..73e10a2 100644 --- a/php/cli/PgsqlCapacitorApp.php +++ b/php/cli/PgsqlCapacitorApp.php @@ -10,13 +10,13 @@ class PgsqlCapacitorApp extends AbstractCapacitorApp { "merge" => parent::ARGS, "purpose" => "gestion d'un capacitor pgsql", "usage" => [ - "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] [--query] key=value...", - "DBCONN [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] --sql-create", + "DBCONN [channelName | -t table | -c ChannelClass] [--query] key=value...", + "DBCONN [channelName | -t table | -c ChannelClass] --sql-create", ], - ["-t", "--table-name", "args" => 1, + ["-t:table", "--table-name", "help" => "nom de la table porteuse du canal de données", ], - ["-c", "--channel-class", "args" => 1, + ["-c:class", "--channel-class", "help" => "nom de la classe dérivée de CapacitorChannel", ], ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, diff --git a/php/cli/SqliteCapacitorApp.php b/php/cli/SqliteCapacitorApp.php index daef081..5f9985a 100644 --- a/php/cli/SqliteCapacitorApp.php +++ b/php/cli/SqliteCapacitorApp.php @@ -9,13 +9,13 @@ class SqliteCapacitorApp extends AbstractCapacitorApp { "merge" => parent::ARGS, "purpose" => "gestion d'un capacitor sqlite", "usage" => [ - "DBFILE [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] [--query] key=value...", - "DBFILE [CHANNEL_NAME | -t TABLE | -c CHANNEL_CLASS] --sql-create", + "DBFILE [channelName | -t table | -c ChannelClass] [--query] key=value...", + "DBFILE [channelName | -t table | -c ChannelClass] --sql-create", ], - ["-t", "--table-name", "args" => 1, + ["-t:table", "--table-name", "help" => "nom de la table porteuse du canal de données", ], - ["-c", "--channel-class", "args" => 1, + ["-c:class", "--channel-class", "help" => "nom de la classe dérivée de CapacitorChannel", ], ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, diff --git a/php/cli/_SteamTrainApp.php b/php/cli/_SteamTrainApp.php index 7113daf..9ccf13d 100644 --- a/php/cli/_SteamTrainApp.php +++ b/php/cli/_SteamTrainApp.php @@ -20,7 +20,7 @@ class _SteamTrainApp extends Application { Cette application peut être utilisée pour tester le lancement des tâches de fond EOT, - ["-c", "--count", "args" => 1, + ["-c:count", "--count", "help" => "spécifier le nombre d'étapes", ], ["-f", "--force-enabled", "value" => true, diff --git a/php/run-tests b/php/run-tests index 1b0c5a1..75d58d8 100755 --- a/php/run-tests +++ b/php/run-tests @@ -1,5 +1,5 @@ #!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 MYDIR="$(dirname -- "$0")" -VENDOR="$MYDIR/../vendor" +VENDOR="$MYDIR/vendor" "$VENDOR/bin/phpunit" --bootstrap "$VENDOR/autoload.php" "$@" "$MYDIR/tests" diff --git a/php/src/A.php b/php/src/A.php index dffd3d2..b1e7cc6 100644 --- a/php/src/A.php +++ b/php/src/A.php @@ -1,7 +1,6 @@ mergeParse($merge); } + private static function verifix_args(?array &$options): ?array { + $args = null; + if ($options !== null) { + foreach ($options as &$option) { + if (preg_match('/^(.*:)([^:].*)$/', $option, $ms)) { + $option = $ms[1]; + $args ??= explode(",", $ms[2]); + } + }; unset($option); + } + return $args; + } + protected function parse(array $def): void { [$options, $params] = cl::split_assoc($def); $this->show ??= $params["show"] ?? true; $this->extends ??= $params["extends"] ?? null; - $this->disabled = vbool::withn($params["disabled"] ?? null); - $removes = varray::withn($params["remove"] ?? null); - A::merge($this->_removes, $removes); - $adds = varray::withn($params["add"] ?? null); - A::merge($this->_adds, $adds); - A::merge($this->_adds, $options); - - $args = $params["args"] ?? null; + $args ??= $params["args"] ?? null; $args ??= $params["arg"] ?? null; if ($args === true) $args = 1; elseif ($args === "*") $args = [null]; elseif ($args === "+") $args = ["value", null]; if (is_int($args)) $args = array_fill(0, $args, "value"); - $this->_args ??= cl::withn($args); + $this->disabled = vbool::withn($params["disabled"] ?? null); + $adds = varray::withn($params["add"] ?? null); + A::merge($this->_adds, $adds); + A::merge($this->_adds, $options); + $args ??= self::verifix_args($this->_adds); + $removes = varray::withn($params["remove"] ?? null); + A::merge($this->_removes, $removes); + self::verifix_args($this->_adds); + + $this->_args ??= cl::withn($args); $this->argsdesc ??= $params["argsdesc"] ?? null; $this->ensureArray ??= $params["ensure_array"] ?? null; @@ -286,11 +298,11 @@ class Aodef { */ protected function processArgs(): void { $args = $this->_args; - $haveArgs = boolval($args); if ($this->isRemains) { - $haveArgs = true; - $args = [null]; + $args ??= [null]; + $haveArgs = boolval($args); } elseif ($args === null) { + $haveArgs = false; $optionalArgs = null; foreach ($this->_options as $option) { switch ($option["args_type"]) { @@ -311,6 +323,8 @@ class Aodef { $args = ["value"]; if ($optionalArgs) $args = [$args]; } + } else { + $haveArgs = boolval($args); } if ($this->isRemains) $desc = "remaining args"; @@ -450,12 +464,12 @@ class Aodef { } function isEmpty(): bool { - return $this->disabled || !$this->_options; + return $this->disabled || (!$this->_options && !$this->isRemains); } function printHelp(?array $what=null): void { $showDef = $what["show"] ?? $this->show; - if (!$showDef) return; + if (!$showDef || $this->isRemains) return; $prefix = $what["prefix"] ?? null; if ($prefix !== null) echo $prefix; diff --git a/php/src/app/args/Aogroup.php b/php/src/app/args/Aogroup.php index 7873a70..b9858f3 100644 --- a/php/src/app/args/Aogroup.php +++ b/php/src/app/args/Aogroup.php @@ -2,8 +2,6 @@ namespace nulib\app\args; use nulib\A; -use nulib\app\args\Aolist; -use nulib\app\args\ArgsException; /** * Class Aogroup: groupe d'arguments fonctionnant ensemble diff --git a/php/src/app/args/Aolist.php b/php/src/app/args/Aolist.php index 504de50..0862615 100644 --- a/php/src/app/args/Aolist.php +++ b/php/src/app/args/Aolist.php @@ -1,9 +1,6 @@ "Afficher l'aide", ]); $helpArgdef->setup1(); + $this->aospecials[] = $helpArgdef; } - if ($helpArgdef !== null) $this->aospecials[] = $helpArgdef; $this->autoremains ??= true; if ($remainsArgdef === null && $this->autoremains) { @@ -135,11 +133,9 @@ class SimpleAolist extends Aolist { "key" => $this->argskey, ]); $remainsArgdef->setup1(); - } - if ($remainsArgdef !== null) { - $this->remainsArgdef = $remainsArgdef; $this->aospecials[] = $remainsArgdef; } + $this->remainsArgdef = $remainsArgdef; # puis calculer nombre d'arguments et actions $this->indexAodefs(); diff --git a/php/src/app/args/SimpleArgsParser.php b/php/src/app/args/SimpleArgsParser.php index e514dc1..cdb181a 100644 --- a/php/src/app/args/SimpleArgsParser.php +++ b/php/src/app/args/SimpleArgsParser.php @@ -1,9 +1,6 @@ "tester la gestion des arguments", + "usage" => "-A|-a|-b", + + "merge" => parent::ARGS, + ["group", + ["-A:", "--seta", "args" => "int", "name" => "a", + "help" => "spécifier a", + ], + ["--seta10", "name" => "a", "value" => 10], + ["--seta20", "name" => "a", "value" => 20], + ], + ["-a", "--inca", "name" => "a", + "help" => "incrémenter a", + ], + ["-b", "--deca", "name" => "a", "inverse" => true, + "help" => "décrémenter a", + ], + ["-D::", "--override", + "help" => "++remplace celui de la section principale", + ], + "sections" => [ + [ + "title" => "Section X", + "show" => false, + ["group", + ["-X:", "--setx", "args" => "int", "name" => "x", + "help" => "spécifier x", + ], + ["--setx10", "name" => "x", "value" => 10], + ["--setx20", "name" => "x", "value" => 20], + ], + ["-x", "--incx", "name" => "x"], + ["-y", "--decx", "name" => "x", "inverse" => true], + ], + ], + ["args" => [["value", "value"]], "name" => "args"], + ]; + + private ?int $a = null; + private ?int $x = null; + private ?string $override = null; + + function main() { + $profile = app::get_profile($productionMode); + $profile = self::get_profile($profile); + $productionMode = $productionMode? "production": "development"; + msg::info("profile=$profile ($productionMode)"); + $debug = app::is_debug()? "DEBUG": "non"; + msg::info("debug=$debug"); + + msg::info([ + "variables:", + "\na=", var_export($this->a, true), + "\nx=", var_export($this->x, true), + "\noverride=", var_export($this->override, true), + "\nargs=", var_export($this->args, true), + ]); + } +}); diff --git a/php/tbin/test-console.php b/php/tbin/test-console.php new file mode 100755 index 0000000..bc50074 --- /dev/null +++ b/php/tbin/test-console.php @@ -0,0 +1,278 @@ +#!/usr/bin/php +title("title0"); +$msg->title("title1"); +$msg->print("print under title1"); +$msg->end(); +$msg->print("print under title0"); +$msg->end(); + +$msg->desc("action avec step"); +$msg->action("action avec step"); +$msg->step("step"); +$msg->asuccess("action success"); + +$msg->action("action avec step"); +$msg->step("step"); +$msg->afailure("action failure"); + +$msg->action("action avec step"); +$msg->step("step"); +$msg->adone("action neutral"); + +$msg->desc("actions sans step"); +$msg->action("action sans step"); +$msg->asuccess("action success"); + +$msg->action("action sans step"); +$msg->afailure("action failure"); + +$msg->action("action sans step"); +$msg->adone("action neutral"); + +$msg->desc("actions imbriquées"); +$msg->action("action0"); +$msg->action("action1"); +$msg->action("action2"); +$msg->asuccess("action2 success"); +$msg->asuccess("action1 success"); +$msg->asuccess("action0 success"); + +$msg->desc("action avec step, sans messages"); +$msg->action("action avec step, sans messages, success"); +$msg->step("step"); +$msg->asuccess(); + +$msg->action("action avec step, sans messages, failure"); +$msg->step("step"); +$msg->afailure(); + +$msg->action("action avec step, sans messages, done"); +$msg->step("step"); +$msg->adone(); + +$msg->desc("action sans step, sans messages"); +$msg->action("action sans step, sans messages, success"); +$msg->asuccess(); + +$msg->action("action sans step, sans messages, failure"); +$msg->afailure(); + +$msg->action("action sans step, sans messages, done"); +$msg->adone(); + +$msg->desc("actions imbriquées, sans messages"); +$msg->action("action0"); +$msg->action("action1"); +$msg->action("action2"); +$msg->asuccess(); +$msg->asuccess(); +$msg->asuccess(); + +$msg->info("info"); +$msg->note("note"); +$msg->warning("warning"); +$msg->error("error"); + +$msg->section("section", function ($msg) { + $msg->title("title", function ($msg) { + $msg->desc("desc"); + $msg->print("print"); + + $msg->desc("action avec step"); + $msg->action("action avec step", function ($msg) { + $msg->step("step"); + $msg->asuccess("action success"); + }); + + $msg->action("action avec step", function ($msg) { + $msg->step("step"); + $msg->afailure("action failure"); + }); + + $msg->action("action avec step", function ($msg) { + $msg->step("step"); + $msg->adone("action done"); + }); + + $msg->desc("actions sans step"); + $msg->action("action sans step", function ($msg) { + $msg->asuccess("action success"); + }); + + $msg->action("action sans step", function ($msg) { + $msg->afailure("action failure"); + }); + + $msg->action("action sans step", function ($msg) { + $msg->adone("action done"); + }); + + $msg->desc("actions imbriquées"); + $msg->action("action0", function ($msg) { + $msg->action("action1", function ($msg) { + $msg->action("action2", function ($msg) { + $msg->asuccess("action2 success"); + }); + $msg->asuccess("action1 success"); + }); + $msg->asuccess("action0 success"); + }); + + $msg->desc("action avec step, sans messages"); + $msg->action("action avec step, sans messages, success", function ($msg) { + $msg->step("step"); + $msg->asuccess(); + }); + + $msg->action("action avec step, sans messages, failure", function ($msg) { + $msg->step("step"); + $msg->afailure(); + }); + + $msg->action("action avec step, sans messages, done", function ($msg) { + $msg->step("step"); + $msg->adone(); + }); + + $msg->desc("action sans step, sans messages"); + $msg->action("action sans step, sans messages, success", function ($msg) { + $msg->asuccess(); + }); + + $msg->action("action sans step, sans messages, failure", function ($msg) { + $msg->afailure(); + }); + + $msg->action("action sans step, sans messages, done", function ($msg) { + $msg->adone(); + }); + + $msg->desc("actions imbriquées, sans messages"); + $msg->action("action0", function ($msg) { + $msg->action("action1", function ($msg) { + $msg->action("action2", function ($msg) { + $msg->asuccess(); + }); + $msg->asuccess(); + }); + $msg->asuccess(); + }); + + $msg->desc("action avec step, avec code de retour"); + $msg->action("action avec step, avec code de retour true", function ($msg) { + $msg->step("step"); + return true; + }); + + $msg->action("action avec step, avec code de retour false", function ($msg) { + $msg->step("step"); + return false; + }); + + $msg->action("action avec step, avec code de retour autre", function ($msg) { + $msg->step("step"); + return "autre"; + }); + + $msg->action("action avec step, avec code de retour null", function ($msg) { + $msg->step("step"); + }); + + $msg->desc("action sans step, avec code de retour"); + $msg->action("action sans step, avec code de retour true", function ($msg) { + return true; + }); + + $msg->action("action sans step, avec code de retour false", function ($msg) { + return false; + }); + + $msg->action("action sans step, avec code de retour autre", function ($msg) { + return "autre"; + }); + + # ici, il n'y aura pas de message du tout + $msg->action("action sans step, avec code de retour null", function ($msg) { + }); + + $msg->info("info"); + $msg->note("note"); + $msg->warning("warning"); + $msg->error("error"); + }); +}); + +$msg->section("multi-line\nsection", function ($msg) { + $msg->title("multi-line\ntitle"); + $msg->title("another\ntitle"); + + $msg->print("multi-line\nprint"); + $msg->info("multi-line\ninfo"); + $msg->action("multi-line\naction"); + $msg->asuccess(); + $msg->action("multi-line\naction"); + $msg->step("multi-line\nstep"); + $msg->afailure(); + $msg->action("multi-line\naction"); + $msg->step("multi-line\nstep"); + $msg->asuccess("multi-line\nsuccess"); + $msg->action("multi-line\naction"); + $msg->step("multi-line\nstep"); + $msg->adone("multi-line\ndone"); + + $msg->end(); + $msg->end(); +}); + +$msg->section("Exceptions", function ($msg) { + $e = new Exception("message"); + $u1 = new UserException("userMessage"); + $u2 = new UserException("userMessage", "techMessage"); + $msg->title("avec message", function ($msg) use ($e, $u1, $u2) { + $msg->info(["exception", $e]); + $msg->info(["userException1", $u1]); + $msg->info(["userException2", $u2]); + }); + $msg->title("sans message", function ($msg) use ($e, $u1, $u2) { + $msg->info($e); + $msg->info($u1); + $msg->info($u2); + }); +}); diff --git a/php/tbin/test-output-forever.php b/php/tbin/test-output-forever.php new file mode 100755 index 0000000..42d014d --- /dev/null +++ b/php/tbin/test-output-forever.php @@ -0,0 +1,18 @@ +#!/usr/bin/php + "output-forever.log", +])); + +$index = 1; +while (true) { + msg::info("info $index"); + $index++; + sleep(1); +} diff --git a/php/tests/app/appTest.php b/php/tests/app/appTest.php index 03ae6f0..fe19a9a 100644 --- a/php/tests/app/appTest.php +++ b/php/tests/app/appTest.php @@ -1,10 +1,11 @@ "$projdir/vendor/bin", "autoload" => "$projdir/vendor/autoload.php", ], - "appcode" => "nur-ture", + "projcode" => "nulib-base", "cwd" => $cwd, "datadir" => "$projdir/devel", "etcdir" => "$projdir/devel/etc", "vardir" => "$projdir/devel/var", "logdir" => "$projdir/devel/log", "profile" => "devel", + "facts" => null, + "debug" => null, "appgroup" => null, "name" => "my-application1", "title" => null, @@ -38,13 +41,15 @@ namespace nulib\app { "bindir" => "$projdir/vendor/bin", "autoload" => "$projdir/vendor/autoload.php", ], - "appcode" => "nur-ture", + "projcode" => "nulib-base", "cwd" => $cwd, "datadir" => "$projdir/devel", "etcdir" => "$projdir/devel/etc", "vardir" => "$projdir/devel/var", "logdir" => "$projdir/devel/log", "profile" => "devel", + "facts" => null, + "debug" => null, "appgroup" => null, "name" => "my-application2", "title" => null, @@ -63,13 +68,15 @@ namespace nulib\app { "bindir" => "$projdir/vendor/bin", "autoload" => "$projdir/vendor/autoload.php", ], - "appcode" => "nur-ture", + "projcode" => "nulib-base", "cwd" => $cwd, "datadir" => "$projdir/devel", "etcdir" => "$projdir/devel/etc", "vardir" => "$projdir/devel/var", "logdir" => "$projdir/devel/log", "profile" => "devel", + "facts" => null, + "debug" => null, "appgroup" => null, "name" => "my-application1", "title" => null, @@ -82,13 +89,15 @@ namespace nulib\app { "bindir" => "$projdir/vendor/bin", "autoload" => "$projdir/vendor/autoload.php", ], - "appcode" => "nur-ture", + "projcode" => "nulib-base", "cwd" => $cwd, "datadir" => "$projdir/devel", "etcdir" => "$projdir/devel/etc", "vardir" => "$projdir/devel/var", "logdir" => "$projdir/devel/log", "profile" => "devel", + "facts" => null, + "debug" => null, "appgroup" => null, "name" => "my-application2", "title" => null, @@ -99,12 +108,12 @@ namespace nulib\app { namespace nulib\app\impl { + use nulib\app\app; use nulib\app\cli\Application; use nulib\os\path; - use nulib\app\app; class config { - const PROJDIR = __DIR__.'/../..'; + const PROJDIR = __DIR__.'/../../..'; static function get_projdir(): string { return path::abspath(self::PROJDIR); diff --git a/php/tests/app/argsTest.php b/php/tests/app/argsTest.php index c0a894c..3404d23 100644 --- a/php/tests/app/argsTest.php +++ b/php/tests/app/argsTest.php @@ -3,7 +3,6 @@ namespace nulib\app; use nulib\tests\TestCase; -use nulib\app\args; class argsTest extends TestCase { function testFrom_array() { diff --git a/php/tests/app/cli/AodefTest.php b/php/tests/app/cli/AodefTest.php index 7d1c828..c27d6c1 100644 --- a/php/tests/app/cli/AodefTest.php +++ b/php/tests/app/cli/AodefTest.php @@ -2,7 +2,7 @@ namespace nulib\app\cli; use nulib\app\args\Aodef; -use nur\t\TestCase; +use nulib\tests\TestCase; class AodefTest extends TestCase { protected static function assertArg( @@ -156,4 +156,18 @@ class AodefTest extends TestCase { true, true, false, false, 0, 0, ""); } + + function testArgsdesc() { + $aodef = new Aodef(["-o:value", "--longo"]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 1, 1, "VALUE"); + + $aodef = new Aodef(["-o:file,suffix", "--longo"]); + self::assertArg($aodef, + ["-o", "--longo"], + true, true, false, + true, 2, 2, "FILE SUFFIX"); + } } diff --git a/php/tests/app/cli/AolistTest.php b/php/tests/app/cli/AolistTest.php index 4b60541..6f7e44c 100644 --- a/php/tests/app/cli/AolistTest.php +++ b/php/tests/app/cli/AolistTest.php @@ -4,7 +4,7 @@ namespace nulib\app\cli; use nulib\app\args\Aogroup; use nulib\app\args\Aolist; use nulib\app\args\Aosection; -use nur\t\TestCase; +use nulib\tests\TestCase; class AolistTest extends TestCase { function testGroup() { diff --git a/php/tests/app/cli/SimpleAolistTest.php b/php/tests/app/cli/SimpleAolistTest.php index e216f8d..3cc5ea5 100644 --- a/php/tests/app/cli/SimpleAolistTest.php +++ b/php/tests/app/cli/SimpleAolistTest.php @@ -2,7 +2,7 @@ namespace nulib\app\cli; use nulib\app\args\SimpleAolist; -use nur\t\TestCase; +use nulib\tests\TestCase; class SimpleAolistTest extends TestCase { function testOverride() { @@ -56,4 +56,21 @@ class SimpleAolistTest extends TestCase { self::assertTrue(true); } + + function testRemainingArgs() { + $aolist = new SimpleAolist([]); + echo "$aolist\n"; #XXX + + $aolist = new SimpleAolist([ + ["name" => "args"], + ]); + echo "$aolist\n"; #XXX + + $aolist = new SimpleAolist([ + ["args" => 2, "name" => "args"], + ]); + echo "$aolist\n"; #XXX + + self::assertTrue(true); + } } diff --git a/php/tests/app/cli/SimpleArgsParserTest.php b/php/tests/app/cli/SimpleArgsParserTest.php index 6dd73e7..3328702 100644 --- a/php/tests/app/cli/SimpleArgsParserTest.php +++ b/php/tests/app/cli/SimpleArgsParserTest.php @@ -2,7 +2,7 @@ namespace nulib\app\cli; use nulib\app\args\SimpleArgsParser; -use nur\t\TestCase; +use nulib\tests\TestCase; class SimpleArgsParserTest extends TestCase { const NORMALIZE_ARGS = [ diff --git a/php/tests/app/config/ConfigManagerTest.php b/php/tests/app/config/ConfigManagerTest.php index 69ba5f8..415a6bd 100644 --- a/php/tests/app/config/ConfigManagerTest.php +++ b/php/tests/app/config/ConfigManagerTest.php @@ -1,10 +1,10 @@ Date: Fri, 3 Oct 2025 12:22:02 +0400 Subject: [PATCH 28/91] modifs.mineures sans commentaires --- php/src/app/app.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/php/src/app/app.php b/php/src/app/app.php index b46595a..e7cb4c7 100644 --- a/php/src/app/app.php +++ b/php/src/app/app.php @@ -15,6 +15,9 @@ use nulib\ValueException; class app { private static function isa_Application($app): bool { if (!is_string($app)) return false; + #XXX support legacy + $legacyApplication = 'nur\cli\Application'; + if ($app === $legacyApplication || is_subclass_of($app, $legacyApplication)) return true; return $app === Application::class || is_subclass_of($app, Application::class); } From e24c298cd81161c3b457cbb2df0a659aa519bfa5 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 13:35:43 +0400 Subject: [PATCH 29/91] modifs.mineures sans commentaires --- .idea/php.xml | 80 +++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/.idea/php.xml b/.idea/php.xml index 4114efb..40a93eb 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -22,56 +22,56 @@ - - - - - - - + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + + From 7979dc5e6970d7eecd98069dd7a4ea89f2ce07d7 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 17:38:32 +0400 Subject: [PATCH 30/91] modifs.mineures sans commentaires --- php/src/app/args/AbstractArgsParser.php | 4 +- php/src/app/args/Aodef.php | 3 +- php/tbin/test-application.php | 41 +++++++++++-------- php/tbin/{test_mail.php => test-mail.php} | 0 php/tbin/{test_mysql.php => test-mysql.php} | 0 php/tbin/{test_pgsql.php => test-pgsql.php} | 0 php/tbin/{test_sqlite.php => test-sqlite.php} | 0 7 files changed, 29 insertions(+), 19 deletions(-) rename php/tbin/{test_mail.php => test-mail.php} (100%) rename php/tbin/{test_mysql.php => test-mysql.php} (100%) rename php/tbin/{test_pgsql.php => test-pgsql.php} (100%) rename php/tbin/{test_sqlite.php => test-sqlite.php} (100%) diff --git a/php/src/app/args/AbstractArgsParser.php b/php/src/app/args/AbstractArgsParser.php index fdd76f1..92df216 100644 --- a/php/src/app/args/AbstractArgsParser.php +++ b/php/src/app/args/AbstractArgsParser.php @@ -65,14 +65,14 @@ abstract class AbstractArgsParser { if ($opt === "--") { # fin des arguments facultatifs en entrée $eoo = true; - if ($keepsep) $dest[] = $opt; + if ($keepsep) $dest[] = "--"; break; } $dest[] = $opt; } if (!$eoo && $desti < $destmax) { # pas assez d'arguments en entrée, terminer avec "--" - $dest[] = "--"; + if ($keepsep) $dest[] = "--"; } return 0; } diff --git a/php/src/app/args/Aodef.php b/php/src/app/args/Aodef.php index d7904f7..89d9da2 100644 --- a/php/src/app/args/Aodef.php +++ b/php/src/app/args/Aodef.php @@ -239,7 +239,8 @@ class Aodef { } function addOptions(?array $options): void { - A::merge($this->_options, $this->buildOptions($options)); + // les options pouvant être numériques (e.g "-1"), utiliser A::merge2 + A::merge2($this->_options, $this->buildOptions($options)); $this->updateType(); } diff --git a/php/tbin/test-application.php b/php/tbin/test-application.php index bd2ff67..a196a27 100755 --- a/php/tbin/test-application.php +++ b/php/tbin/test-application.php @@ -12,6 +12,21 @@ Application::run(new class extends Application { "usage" => "-A|-a|-b", "merge" => parent::ARGS, + "sections" => [ + [ + "title" => "Section X", + "show" => false, + ["group", + ["-X:", "--setx", "args" => "int", "name" => "x", + "help" => "spécifier x", + ], + ["--setx10", "name" => "x", "value" => 10], + ["--setx20", "name" => "x", "value" => 20], + ], + ["-x", "--incx", "name" => "x"], + ["-y", "--decx", "name" => "x", "inverse" => true], + ], + ], ["group", ["-A:", "--seta", "args" => "int", "name" => "a", "help" => "spécifier a", @@ -28,27 +43,19 @@ Application::run(new class extends Application { ["-D::", "--override", "help" => "++remplace celui de la section principale", ], - "sections" => [ - [ - "title" => "Section X", - "show" => false, - ["group", - ["-X:", "--setx", "args" => "int", "name" => "x", - "help" => "spécifier x", - ], - ["--setx10", "name" => "x", "value" => 10], - ["--setx20", "name" => "x", "value" => 20], - ], - ["-x", "--incx", "name" => "x"], - ["-y", "--decx", "name" => "x", "inverse" => true], - ], - ], - ["args" => [["value", "value"]], "name" => "args"], + ["-1:first", "--one", "help" => "un argument"], + ["-2:first,second", "--two", "help" => "deux arguments"], + ["-3", "args" => ""], + //["args" => [["value", "value"]], "name" => "args"], + //["args" => ["value", ["value"]], "name" => "args"], + //["args" => ["value", "value"], "name" => "args"], ]; private ?int $a = null; private ?int $x = null; private ?string $override = null; + private ?string $one = null; + private ?array $two = null; function main() { $profile = app::get_profile($productionMode); @@ -63,6 +70,8 @@ Application::run(new class extends Application { "\na=", var_export($this->a, true), "\nx=", var_export($this->x, true), "\noverride=", var_export($this->override, true), + "\none=", var_export($this->one, true), + "\ntwo=", var_export($this->two, true), "\nargs=", var_export($this->args, true), ]); } diff --git a/php/tbin/test_mail.php b/php/tbin/test-mail.php similarity index 100% rename from php/tbin/test_mail.php rename to php/tbin/test-mail.php diff --git a/php/tbin/test_mysql.php b/php/tbin/test-mysql.php similarity index 100% rename from php/tbin/test_mysql.php rename to php/tbin/test-mysql.php diff --git a/php/tbin/test_pgsql.php b/php/tbin/test-pgsql.php similarity index 100% rename from php/tbin/test_pgsql.php rename to php/tbin/test-pgsql.php diff --git a/php/tbin/test_sqlite.php b/php/tbin/test-sqlite.php similarity index 100% rename from php/tbin/test_sqlite.php rename to php/tbin/test-sqlite.php From c76dc98c3195b483fa9a8b900884515ceb0669e7 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 3 Oct 2025 17:40:07 +0400 Subject: [PATCH 31/91] modifs.mineures sans commentaires --- php/src/app/args/TODO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/php/src/app/args/TODO.md b/php/src/app/args/TODO.md index d19db42..da6b1ad 100644 --- a/php/src/app/args/TODO.md +++ b/php/src/app/args/TODO.md @@ -1,6 +1,5 @@ # nulib\app\args -* [ ] dans la section "profils", rajouter une option pour spécifier un fichier de configuration * [ ] transformer un schéma en définition d'arguments, un tableau en liste d'arguments, et vice-versa * [ ] faire une implémentation ArgsParser qui supporte les commandes, et les options dynamiques * commandes: From 014825f09d4cad8f413f836ef87711cdff715a61 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sat, 4 Oct 2025 09:59:02 +0400 Subject: [PATCH 32/91] modifs.mineures sans commentaires --- php/src/app/app.php | 13 +++++++++++-- php/src/app/cli/Application.php | 13 +++++++------ php/src/app/config/ProfileManager.php | 8 +++----- php/src/file/Stream.php | 2 +- php/src/file/csv/csv_flavours.php | 2 +- php/src/ref/ref_cache.php | 11 +++++++++++ php/src/ref/{file/csv => }/ref_csv.php | 2 +- php/src/ref/{php => }/ref_func.php | 2 +- php/src/ref/ref_jquery.php | 15 +++++++++++++++ php/src/ref/{web => }/ref_mimetypes.php | 2 +- php/src/ref/ref_profiles.php | 22 ++++++++++++++++++++++ 11 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 php/src/ref/ref_cache.php rename php/src/ref/{file/csv => }/ref_csv.php (96%) rename php/src/ref/{php => }/ref_func.php (96%) create mode 100644 php/src/ref/ref_jquery.php rename php/src/ref/{web => }/ref_mimetypes.php (92%) create mode 100644 php/src/ref/ref_profiles.php diff --git a/php/src/app/app.php b/php/src/app/app.php index e7cb4c7..13aa994 100644 --- a/php/src/app/app.php +++ b/php/src/app/app.php @@ -9,6 +9,7 @@ use nulib\ExitError; use nulib\os\path; use nulib\os\sh; use nulib\php\func; +use nulib\ref\ref_profiles; use nulib\str; use nulib\ValueException; @@ -114,13 +115,21 @@ class app { static function get_profile(?bool &$productionMode=null): string { return self::get()->getProfile($productionMode); } + + static function is_production_mode(): bool { + return self::get()->isProductionMode(); + } static function is_prod(): bool { - return self::get_profile() === "prod"; + return self::get_profile() === ref_profiles::PROD; + } + + static function is_test(): bool { + return self::get_profile() === ref_profiles::TEST; } static function is_devel(): bool { - return self::get_profile() === "devel"; + return self::get_profile() === ref_profiles::DEVEL; } static function set_profile(?string $profile=null, ?bool $productionMode=null): void { diff --git a/php/src/app/cli/Application.php b/php/src/app/cli/Application.php index 330efdf..02f0901 100644 --- a/php/src/app/cli/Application.php +++ b/php/src/app/cli/Application.php @@ -14,6 +14,7 @@ use nulib\output\console; use nulib\output\log; use nulib\output\msg; use nulib\output\std\StdMessenger; +use nulib\ref\ref_profiles; /** * Class Application: application de base @@ -257,9 +258,9 @@ EOT); "action" => [app::class, "set_profile"], "help" => "spécifier le profil d'exécution", ], - ["-P", "--prod", "action" => [app::class, "set_profile", "prod"]], - ["-T", "--test", "action" => [app::class, "set_profile", "test"]], - ["--devel", "action" => [app::class, "set_profile", "devel"]], + ["-P", "--prod", "action" => [app::class, "set_profile", ref_profiles::PROD]], + ["-T", "--test", "action" => [app::class, "set_profile", ref_profiles::TEST]], + ["--devel", "action" => [app::class, "set_profile", ref_profiles::DEVEL]], ], ]; @@ -307,9 +308,9 @@ EOT); } const PROFILE_COLORS = [ - "prod" => "@r", - "test" => "@g", - "devel" => "@w", + ref_profiles::PROD => "@r", + ref_profiles::TEST => "@g", + ref_profiles::DEVEL => "@w", ]; const DEFAULT_PROFILE_COLOR = "y"; diff --git a/php/src/app/config/ProfileManager.php b/php/src/app/config/ProfileManager.php index 6645906..9e66c5b 100644 --- a/php/src/app/config/ProfileManager.php +++ b/php/src/app/config/ProfileManager.php @@ -3,6 +3,7 @@ namespace nulib\app\config; use nulib\app\app; use nulib\app\config; +use nulib\ref\ref_profiles; /** * Class ProfileManager: gestionnaire de profils @@ -21,10 +22,7 @@ class ProfileManager { const PROFILES = null; /** @var array profils dont le mode production doit être actif */ - const PRODUCTION_MODES = [ - "prod" => true, - "test" => true, - ]; + const PRODUCTION_MODES = ref_profiles::PRODUCTION_MODES; /** * @var array mapping profil d'application --> profil effectif @@ -114,7 +112,7 @@ class ProfileManager { $profile ??= $this->getConfigProfile(); $profile ??= $this->getDefaultProfile(); if ($this->isAppProfile) { - $profile ??= $this->profiles[0] ?? "prod"; + $profile ??= $this->profiles[0] ?? ref_profiles::PROD; } else { $profile ??= $this->mapProfile(app::get_profile()); } diff --git a/php/src/file/Stream.php b/php/src/file/Stream.php index c1d7496..89b3c7c 100644 --- a/php/src/file/Stream.php +++ b/php/src/file/Stream.php @@ -6,7 +6,7 @@ use nulib\NoMoreDataException; use nulib\os\EOFException; use nulib\os\IOException; use nulib\php\iter\AbstractIterator; -use nulib\ref\file\csv\ref_csv; +use nulib\ref\ref_csv; use nulib\str; use nulib\ValueException; diff --git a/php/src/file/csv/csv_flavours.php b/php/src/file/csv/csv_flavours.php index 4bc7bd9..2a782e6 100644 --- a/php/src/file/csv/csv_flavours.php +++ b/php/src/file/csv/csv_flavours.php @@ -2,7 +2,7 @@ namespace nulib\file\csv; use nulib\cl; -use nulib\ref\file\csv\ref_csv; +use nulib\ref\ref_csv; use nulib\str; class csv_flavours { diff --git a/php/src/ref/ref_cache.php b/php/src/ref/ref_cache.php new file mode 100644 index 0000000..d1dbf0a --- /dev/null +++ b/php/src/ref/ref_cache.php @@ -0,0 +1,11 @@ + + + true, + self::TEST => true, + ]; +} From 6cedfe94936f89601fc887da334d262996b637bd Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sat, 4 Oct 2025 10:12:45 +0400 Subject: [PATCH 33/91] modifs.mineures sans commentaires --- php/src/ref/cli/ref_args.php | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/php/src/ref/cli/ref_args.php b/php/src/ref/cli/ref_args.php index 98f5515..962da26 100644 --- a/php/src/ref/cli/ref_args.php +++ b/php/src/ref/cli/ref_args.php @@ -6,11 +6,11 @@ namespace nulib\ref\cli; */ class ref_args { const DEFS_SCHEMA = [ - "set_defaults" => [null, null, "tableau contenant des paramètres et des options par défaut"], - "merge_arrays" => [null, null, "liste de tableaux à merger à celui-ci avant de calculer la liste effective des options"], - "merge" => [null, null, "tableau à merger à celui-ci avant de calculer la liste effective des options", - # si merge_arrays et merge sont spécifiés tous les deux, "merge" est mergé après "merge_arrays" + "merges" => ["?array", null, "liste de tableaux contenant des paramètres et des options par défaut"], + "merge" => ["?array", null, "tableau contenant des paramètres et des options par défaut", + # si merges et merge sont spécifiés tous les deux, "merge" est mergé après "merges" ], + "merge_after" => ["?array", null, "tableau contenant des paramètres et des options supplémentaires"], "prefix" => [null, null, "texte à afficher avant l'aide générée automatiquement"], "name" => [null, null, "nom du programme, utilisé pour l'affichage de l'aide"], "purpose" => [null, null, "courte description de l'objet de ce programme"], @@ -51,34 +51,34 @@ class ref_args { ]; const DEF_SCHEMA = [ - "set_defaults" => [null, null, "tableau contenant des paramètres par défaut"], - "merge_arrays" => [null, null, "liste de tableaux à merger à celui-ci"], - "merge" => [null, null, "tableau à merger à celui-ci", - # si merge_arrays et merge sont spécifiés tous les deux, "merge" est mergé après "merge_arrays" + "merges" => ["array", null, "liste de tableaux contenant des paramètres et des options par défaut"], + "merge" => ["array", null, "tableau contenant des paramètres et des options par défaut", + # si merges et merge sont spécifiés tous les deux, "merge" est mergé après "merges" ], - "kind" => [null, null, "type de définition: 'option' ou 'command'"], - "arg" => [null, null, "type de l'argument attendu par l'option"], - "args" => [null, null, "type des arguments attendus par l'option", + "merge_after" => ["array", null, "tableau contenant des paramètres et des options supplémentaires"], + "extends" => ["string", null, "option que cette définition étend"], + "add" => ["array", null, "options à rajouter"], + "remove" => ["array", null, "options à enlever"], + "show" => ["bool", true, "faut-il afficher cette option par défaut?"], + "disabled" => ["bool", false, "cette option est-elle désactivée?"], + "arg" => ["?string|int|bool", null, "type de l'argument attendu par l'option"], + "args" => ["?array", null, "type des arguments attendus par l'option", # si args est spécifié, arg est ignoré ], - "argsdesc" => [null, null, "description textuelle des arguments, utilisé pour l'affichage de l'aide"], - "type" => [null, null, "types dans lesquels convertir les arguments avant de les fournir à l'utilisateur"], - "action" => [null, null, "fonction à appeler quand cette option est utilisée", + "argsdesc" => ["?string", null, "description textuelle des arguments, utilisé pour l'affichage de l'aide"], + "type" => ["schema", null, "type dans lequel convertir les arguments avant de les fournir à l'utilisateur"], + "ensure_array" => ["bool", false, "forcer la destination à être un tableau"], + "action" => ["callable", null, "fonction à appeler quand cette option est utilisée", # la signature de la fonction est ($value, $name, $arg, $dest, $def) ], - "name" => [null, null, "propriété ou clé à initialiser en réponse à l'utilisation de cette option", + "inverse" => ["bool", false, "décrémenter la destination au lieu de l'incrémenter pour une option sans argument"], + "name" => ["?string", null, "propriété ou clé à initialiser en réponse à l'utilisation de cette option", # le nom à spécifier est au format under_score, qui est transformée en camelCase si la destination est un objet ], - "property" => [null, null, "comme name mais force l'utilisation d'une propriété"], - "key" => [null, null, "comme name mais force l'utilisation d'une clé"], - "inverse" => ["bool", false, "décrémenter la destination au lieu de l'incrémenter pour une option sans argument"], + "property" => ["?string", null, "comme name mais force l'utilisation d'une propriété"], + "key" => ["?key", null, "comme name mais force l'utilisation d'une clé"], "value" => ["mixed", null, "valeur à forcer au lieu d'incrémenter la destination"], - "ensure_array" => [null, null, "forcer la destination à être un tableau"], "help" => [null, null, "description de cette option, utilisé pour l'affichage de l'aide"], - "cmd_args" => [null, null, "définition des sous-options pour une commande"], - - # ces valeurs sont calculées - "cmd_defs" => [null, null, "(interne) liste des définitions correspondant au paramètre options"], ]; const ARGS_ALLOWED_VALUES = ["value", "path", "dir", "file", "host"]; From 651ba8c5534ca796f847fce55a0f93f22c646edd Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 5 Oct 2025 16:48:18 +0400 Subject: [PATCH 34/91] =?UTF-8?q?int=C3=A9gration=20de=20nulib/cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 1 + php/bin/cachectl.php | 7 + php/cli/CachectlApp.php | 127 +++++++++ php/src/cache/CacheData.php | 46 ++++ php/src/cache/CacheFile.php | 360 ++++++++++++++++++++++++++ php/src/cache/CacheManager.php | 68 +++++ php/src/cache/CursorCacheData.php | 36 +++ php/src/cache/CursorChannel.php | 127 +++++++++ php/src/cache/DataCacheData.php | 59 +++++ php/src/cache/TODO.md | 6 + php/src/cache/cache.php | 93 +++++++ php/tbin/.gitignore | 1 + php/tbin/test-cache.php | 73 ++++++ php/tests/cache/CursorChannelTest.php | 38 +++ php/tests/cache/SourceDb.php | 22 ++ php/tests/cache/_TestCase.php | 23 ++ php/tests/cache/cacheTest.php | 108 ++++++++ 17 files changed, 1195 insertions(+) create mode 100755 php/bin/cachectl.php create mode 100644 php/cli/CachectlApp.php create mode 100644 php/src/cache/CacheData.php create mode 100644 php/src/cache/CacheFile.php create mode 100644 php/src/cache/CacheManager.php create mode 100644 php/src/cache/CursorCacheData.php create mode 100644 php/src/cache/CursorChannel.php create mode 100644 php/src/cache/DataCacheData.php create mode 100644 php/src/cache/TODO.md create mode 100644 php/src/cache/cache.php create mode 100755 php/tbin/test-cache.php create mode 100644 php/tests/cache/CursorChannelTest.php create mode 100644 php/tests/cache/SourceDb.php create mode 100644 php/tests/cache/_TestCase.php create mode 100644 php/tests/cache/cacheTest.php diff --git a/composer.json b/composer.json index 61f5643..f17024c 100644 --- a/composer.json +++ b/composer.json @@ -48,6 +48,7 @@ } }, "bin": [ + "php/bin/cachectl.php", "php/bin/dumpser.php", "php/bin/json2yml.php", "php/bin/yml2json.php", diff --git a/php/bin/cachectl.php b/php/bin/cachectl.php new file mode 100755 index 0000000..48de2bc --- /dev/null +++ b/php/bin/cachectl.php @@ -0,0 +1,7 @@ +#!/usr/bin/php + parent::ARGS, + "purpose" => "gestion de fichiers cache", + ["-r", "--read", "name" => "action", "value" => self::ACTION_READ, + "help" => "Afficher le contenu d'un fichier cache", + ], + ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS, + "help" => "Afficher des informations sur le fichier cache", + ], + ["-k", "--clean", "name" => "action", "value" => self::ACTION_CLEAN, + "help" => "Supprimer le fichier cache s'il a expiré", + ], + ["-a", "--add-duration", "args" => 1, + "action" => [null, "->setActionUpdate", self::ACTION_UPDATE_ADD], + "help" => "Ajouter le nombre de secondes spécifié à la durée du cache", + ], + ["-b", "--sub-duration", "args" => 1, + "action" => [null, "->setActionUpdate", self::ACTION_UPDATE_SUB], + "help" => "Enlever le nombre de secondes spécifié à la durée du cache", + ], + #XXX pas encore implémenté + //["-s", "--set-duration", "args" => 1, + // "action" => [null, "->setActionUpdate", self::ACTION_UPDATE_SET], + // "help" => "Mettre à jour la durée du cache à la valeur spécifiée", + //], + ]; + + protected $action = self::ACTION_READ; + + protected $updateAction, $updateDuration; + + function setActionUpdate(int $action, $updateDuration): void { + $this->action = self::ACTION_UPDATE; + switch ($action) { + case self::ACTION_UPDATE_SUB: + $this->updateAction = CacheFile::UPDATE_SUB; + break; + case self::ACTION_UPDATE_SET: + $this->updateAction = CacheFile::UPDATE_SET; + break; + case self::ACTION_UPDATE_ADD: + $this->updateAction = CacheFile::UPDATE_ADD; + break; + } + $this->updateDuration = $updateDuration; + } + + protected function findCaches(string $dir, ?array &$files): void { + foreach (glob("$dir/*") as $file) { + if (is_dir($file)) { + $this->findCaches($file, $files); + } elseif (is_file($file) && fnmatch("*.cache", $file)) { + $files[] = $file; + } + } + } + + function main() { + $files = []; + foreach ($this->args as $arg) { + if (is_dir($arg)) { + $this->findCaches($arg, $files); + } elseif (is_file($arg)) { + $files[] = $arg; + } else { + msg::warning("$arg: fichier introuvable"); + } + } + $showSection = count($files) > 1; + foreach ($files as $file) { + switch ($this->action) { + case self::ACTION_READ: + if ($showSection) msg::section($file); + $cache = new CacheFile($file, null, [ + "readonly" => true, + "duration" => "INF", + "override_duration" => true, + ]); + yaml::dump($cache->get()); + break; + case self::ACTION_INFOS: + if ($showSection) msg::section($file); + $cache = new CacheFile($file, null, [ + "readonly" => true, + ]); + yaml::dump($cache->getInfos()); + break; + case self::ACTION_CLEAN: + msg::action(path::ppath($file)); + $cache = new CacheFile($file); + try { + if ($cache->deleteExpired()) msg::asuccess("fichier supprimé"); + else msg::adone("fichier non expiré"); + } catch (Exception $e) { + msg::afailure($e); + } + break; + case self::ACTION_UPDATE: + msg::action(path::ppath($file)); + $cache = new CacheFile($file); + try { + $cache->updateDuration($this->updateDuration, $this->updateAction); + msg::asuccess("fichier mis à jour"); + } catch (Exception $e) { + msg::afailure($e); + } + break; + default: + self::die("$this->action: action non implémentée"); + } + } + } +} diff --git a/php/src/cache/CacheData.php b/php/src/cache/CacheData.php new file mode 100644 index 0000000..5ce5b83 --- /dev/null +++ b/php/src/cache/CacheData.php @@ -0,0 +1,46 @@ +name = $name ?? ""; + $this->compute = func::withn($compute ?? static::COMPUTE); + } + + protected string $name; + + function getName() : string { + return $this->name; + } + + protected ?func $compute; + + /** calculer la donnée */ + function compute() { + $compute = $this->compute; + $data = $compute !== null? $compute->invoke(): null; + return $data; + } + + /** spécifier le chemin du cache à partir du fichier de base */ + abstract function setDatafile(?string $basefile): void; + + /** indiquer si le cache existe */ + abstract function exists(): bool; + + /** charger la donnée depuis le cache */ + abstract function load(); + + /** sauvegarder la donnée dans le cache et la retourner */ + abstract function save($data); + + /** supprimer le cache */ + abstract function delete(); +} diff --git a/php/src/cache/CacheFile.php b/php/src/cache/CacheFile.php new file mode 100644 index 0000000..f40ed80 --- /dev/null +++ b/php/src/cache/CacheFile.php @@ -0,0 +1,360 @@ +initialDuration = Delay::with($params["duration"] ?? static::DURATION); + $this->overrideDuration = $params["override_duration"] ?? false; + $this->readonly = $params["readonly"] ?? false; + $this->cacheNull = $params["cache_null"] ?? false; + $data ??= $params["data"] ?? null; + $this->sources = null; + if (self::ensure_source($data, $source)) { + if ($source !== null) $source->setDatafile($basefile); + $this->sources = ["" => $source]; + } else { + $sources = []; + $index = 0; + foreach ($data as $key => $source) { + self::ensure_source($source, $source, false); + if ($source !== null) { + $source->setDatafile($basefile); + if ($key === $index) { + $index++; + $key = $source->getName(); + } + } elseif ($key === $index) { + $index++; + } + $sources[$key] = $source; + } + $this->sources = $sources; + } + parent::__construct($file); + } + + protected Delay $initialDuration; + + protected bool $overrideDuration; + + protected bool $readonly; + + protected bool $cacheNull; + + /** @var ?CacheData[] */ + protected ?array $sources; + + /** + * vérifier si le fichier est valide. s'il est invalide, il faut le recréer. + * + * on assume que le fichier existe, vu qu'il a été ouvert en c+b + */ + function isValid(): bool { + # considèrer que le fichier est invalide s'il est de taille nulle + return $this->getSize() > 0; + } + + protected ?DateTime $start; + + protected ?Delay $duration; + + protected $data; + + /** charger les données. le fichier a déjà été verrouillé en lecture */ + protected function loadMetadata(): void { + if ($this->isValid()) { + $this->rewind(); + [ + "start" => $start, + "duration" => $duration, + "data" => $data, + ] = $this->unserialize(null, false, true); + if ($this->overrideDuration) { + $duration = Delay::with($this->initialDuration, $start); + } + } else { + $start = null; + $duration = null; + $data = null; + } + $this->start = $start; + $this->duration = $duration; + $this->data = $data; + } + + /** + * tester s'il faut mettre les données à jour. le fichier a déjà été + * verrouillé en lecture + */ + protected function shouldUpdate(bool $noCache=false): bool { + if ($this->isValid()) { + $expired = $this->duration->isElapsed(); + } else { + $expired = false; + $noCache = true; + } + return $noCache || $expired; + } + + /** sauvegarder les données. le fichier a déjà été verrouillé en écriture */ + protected function saveMetadata(): void { + $this->duration ??= $this->initialDuration; + if ($this->start === null) { + $this->start = new DateTime(); + $this->duration = Delay::with($this->duration, $this->start); + } + $this->ftruncate(); + $this->serialize([ + "start" => $this->start, + "duration" => $this->duration, + "data" => $this->data, + ], false, true); + } + + protected function unlinkFiles(bool $datafilesOnly=false): void { + foreach ($this->sources as $source) { + if ($source !== null) $source->delete(); + } + if (!$datafilesOnly) @unlink($this->file); + } + + /** tester si $value peut être mis en cache */ + protected function shouldCache($value): bool { + return $this->cacheNull || $value !== null; + } + + protected ?DateTime $ostart; + + protected ?Delay $oduration; + + protected $odata; + + protected function beforeAction() { + $this->loadMetadata(); + $this->ostart = cv::clone($this->start); + $this->oduration = cv::clone($this->duration); + $this->odata = cv::clone($this->data); + } + + protected function afterAction() { + $modified = false; + if ($this->start != $this->ostart) $modified = true; + $duration = $this->duration; + $oduration = $this->oduration; + if ($duration === null || $oduration === null) $modified = true; + elseif ($duration->getDest() != $oduration->getDest()) $modified = true; + # égalité stricte uniquement pour $data et $datafiles + if ($this->data !== $this->odata) $modified = true; + if ($modified && !$this->readonly) { + $this->lockWrite(); + $this->saveMetadata(); + } + } + + protected function action(callable $callback, bool $willWrite=false) { + if ($willWrite && !$this->readonly) $this->lockWrite(); + else $this->lockRead(); + try { + $this->beforeAction(); + $result = $callback(); + $this->afterAction(); + return $result; + } finally { + $this->ostart = null; + $this->oduration = null; + $this->odata = null; + $this->start = null; + $this->duration = null; + $this->data = null; + $this->unlock(true); + } + } + + protected function compute() { + return null; + } + + protected function refreshData($data, bool $noCache) { + $source = $this->sources[$data] ?? null; + $updateMetadata = $this->shouldUpdate($noCache); + if ($source === null) $updateData = $this->data === null; + else $updateData = !$source->exists(); + if (!$this->readonly && ($updateMetadata || $updateData)) { + $this->lockWrite(); + if ($updateMetadata) { + # il faut refaire tout le cache + $this->unlinkFiles(true); + $this->start = null; + $this->duration = null; + $this->data = null; + $updateData = true; + } + if ($source === null) { + if ($updateData) { + # calculer la valeur + try { + $data = $this->compute(); + } catch (Exception $e) { + # le fichier n'est pas mis à jour, mais ce n'est pas gênant: lors + # des futurs appels, l'exception continuera d'être lancée ou la + # valeur sera finalement mise à jour + throw $e; + } + } else { + $data = $this->data; + } + if ($this->shouldCache($data)) $this->data = $data; + else $this->data = $data = null; + } else { + if ($updateData) { + # calculer la valeur + try { + $data = $source->compute(); + } catch (Exception $e) { + # le fichier n'est pas mis à jour, mais ce n'est pas gênant: lors + # des futurs appels, l'exception continuera d'être lancée ou la + # valeur sera finalement mise à jour + throw $e; + } + } else { + $data = $source->load(); + } + if ($this->shouldCache($data)) { + $data = $source->save($data); + } else { + # ne pas garder le fichier s'il ne faut pas mettre en cache + $source->delete(); + $data = null; + } + } + } elseif ($source === null) { + $data = $this->data; + } elseif ($source->exists()) { + $data = $source->load(); + } else { + $data = null; + } + return $data; + } + + /** + * s'assurer que le cache est à jour avec les données les plus récentes. si + * les données sont déjà présentes dans le cache et n'ont pas encore expirées + * cette méthode est un NOP + */ + function refresh(bool $noCache=false): self { + $this->action(function() use ($noCache) { + foreach (array_keys($this->sources) as $data) { + $this->refreshData($data, $noCache); + } + }); + return $this; + } + + function get($data=null, bool $noCache=false) { + return $this->action(function () use ($data, $noCache) { + return $this->refreshData($data, $noCache); + }); + } + + function all($data=null, bool $noCache=false): ?iterable { + $data = $this->get($data, $noCache); + if ($data !== null && !is_iterable($data)) $data = [$data]; + return $data; + } + + function delete($data=null): void { + $source = $this->sources[$data] ?? null; + if ($source !== null) $source->delete(); + } + + /** obtenir les informations sur le fichier */ + function getInfos(): array { + return $this->action(function () { + if (!$this->isValid()) { + return ["valid" => false]; + } + $start = $this->start; + $duration = $this->duration; + return [ + "valid" => true, + "start" => $start, + "duration" => strval($duration), + "date_start" => $start->format(), + "date_end" => $duration->getDest()->format(), + ]; + }); + } + + const UPDATE_SUB = -1, UPDATE_SET = 0, UPDATE_ADD = 1; + + /** + * mettre à jour la durée de validité du fichier + * + * XXX UPDATE_SET n'est pas implémenté + */ + function updateDuration($nduration, int $action=self::UPDATE_ADD): void { + if ($this->readonly) return; + $this->action(function () use ($nduration, $action) { + if (!$this->isValid()) return; + $duration = $this->duration; + if ($action < 0) $duration->subDuration($nduration); + elseif ($action > 0) $duration->addDuration($nduration); + }, true); + } + + /** supprimer les fichiers s'ils ont expiré */ + function deleteExpired(bool $force=false): bool { + if ($this->readonly) return false; + return $this->action(function () use ($force) { + if ($force || $this->shouldUpdate()) { + $this->unlinkFiles(); + return true; + } + return false; + }, true); + } +} diff --git a/php/src/cache/CacheManager.php b/php/src/cache/CacheManager.php new file mode 100644 index 0000000..2b134c9 --- /dev/null +++ b/php/src/cache/CacheManager.php @@ -0,0 +1,68 @@ +shouldCaches = []; + $this->defaultCache = true; + $this->includes = $includes; + $this->excludes = $excludes; + } + + /** + * @var array tableau {id => shouldCache} indiquant si l'élément id doit être + * mis en cache + */ + protected array $shouldCaches; + + /** + * @var bool valeur par défaut de shouldCache si la valeur n'est pas trouvée + * dans $shouldCache + */ + protected bool $defaultCache; + + /** + * @var array|null groupes à toujours inclure dans le cache. pour les + * identifiants de ces groupe, {@link self::shouldCache()} retourne toujours + * true. + * + * $excludes est prioritaire par rapport à $includes + */ + protected ?array $includes; + + /** + * @var array|null groupes à exclure de la mise en cache. la mise en cache est + * toujours calculée pour les identifiants de ces groupes. + */ + protected ?array $excludes; + + function setNoCache(bool $noCache=true, bool $reset=true): self { + if ($reset) $this->shouldCaches = []; + $this->defaultCache = !$noCache; + return $this; + } + + function shouldCache(string $id, ?string $groupId=null, bool $reset=true): bool { + if ($groupId !== null) { + $includes = $this->includes; + $shouldInclude = $includes !== null && in_array($groupId, $includes); + $excludes = $this->excludes; + $shouldExclude = $excludes !== null && in_array($groupId, $excludes); + if ($shouldInclude && !$shouldExclude) return true; + } + $cacheId = "$groupId-$id"; + $shouldCache = cl::get($this->shouldCaches, $cacheId, $this->defaultCache); + $this->shouldCaches[$cacheId] = $reset?: $shouldCache; + return $shouldCache; + } +} diff --git a/php/src/cache/CursorCacheData.php b/php/src/cache/CursorCacheData.php new file mode 100644 index 0000000..bcfdd33 --- /dev/null +++ b/php/src/cache/CursorCacheData.php @@ -0,0 +1,36 @@ +initStorage(cache::storage()); + $this->channel = $channel; + } + + function setDatafile(?string $basefile): void { + } + + protected CursorChannel $channel; + + function exists(): bool { + return $this->channel->count() > 0; + } + + function load() { + return $this->channel; + } + + function save($data) { + if (!is_iterable($data)) $data = [$data]; + $this->channel->rechargeAll($data); + return $this->channel; + } + + function delete() { + $this->channel->delete(null); + } +} diff --git a/php/src/cache/CursorChannel.php b/php/src/cache/CursorChannel.php new file mode 100644 index 0000000..1af52b1 --- /dev/null +++ b/php/src/cache/CursorChannel.php @@ -0,0 +1,127 @@ +initStorage($storage); + if ($rows !== null) $channel->rechargeAll($rows); + return $channel; + } + + const NAME = "cursor"; + const TABLE_NAME = "cursor"; + + const COLUMN_DEFINITIONS = [ + "group_id_" => "varchar(32) not null", // groupe de curseur + "id_" => "varchar(128) not null", // nom du curseur + "key_index_" => "integer not null", + "key_" => "varchar(128) not null", + "search_" => "varchar(255)", + + "primary key (group_id_, id_, key_index_)", + ]; + + const ADD_COLUMNS = null; + + protected function COLUMN_DEFINITIONS(): ?array { + return cl::merge(self::COLUMN_DEFINITIONS, static::ADD_COLUMNS); + } + + /** + * @param array|string $cursorId + */ + function __construct($cursorId) { + parent::__construct(); + cache::verifix_id($cursorId); + [ + "group_id" => $this->groupId, + "id" => $this->id, + ] = $cursorId; + } + + protected string $groupId; + + protected string $id; + + function getCursorId(): array { + return [ + "group_id" => $this->groupId, + "id" => $this->id, + ]; + } + + function getBaseFilter(): ?array { + return [ + "group_id_" => $this->groupId, + "id_" => $this->id, + ]; + } + + protected int $index = 0; + + protected function getSearch($item): ?string { + $search = cl::filter_n(cl::with($item)); + $search = implode(" ", $search); + return substr($search, 0, 255); + } + + function getItemValues($item, $key=null): ?array { + $index = $this->index++; + $key = $key ?? $index; + $key = substr(strval($key), 0, 128); + $addColumns = static::ADD_COLUMNS ?? []; + $addColumns = cl::select($item, + array_filter(array_keys($addColumns), function ($key) { + return is_string($key); + })); + return cl::merge($addColumns, [ + "group_id_" => $this->groupId, + "id_" => $this->id, + "key_index_" => $index, + "key_" => $key, + "search_" => $this->getSearch($item), + ]); + } + + function reset(bool $recreate=false): void { + $this->index = 0; + parent::reset($recreate); + } + + function chargeAll(?iterable $items, $func=null, ?array $args=null): int { + if ($items === null) return 0; + $count = 0; + if ($func !== null) $func = func::with($func, $args)->bind($this); + foreach ($items as $key => $item) { + $count += $this->charge($item, $func, [$key]); + } + return $count; + } + + function rechargeAll(?iterable $items): self { + $this->delete(null); + $this->index = 0; + $this->chargeAll($items); + return $this; + } + + function getIterator(): Traversable { + $rows = $this->dbAll([ + "cols" => ["key_", "item__"], + "where" => $this->getBaseFilter(), + ]); + foreach ($rows as $row) { + $key = $row["key_"]; + $item = $this->unserialize($row["item__"]); + yield $key => $item; + } + } +} diff --git a/php/src/cache/DataCacheData.php b/php/src/cache/DataCacheData.php new file mode 100644 index 0000000..7ada552 --- /dev/null +++ b/php/src/cache/DataCacheData.php @@ -0,0 +1,59 @@ +setDatafile($basefile); + } + + function compute() { + $data = parent::compute(); + if ($data instanceof Traversable) $data = cl::all($data); + return $data; + } + + protected string $datafile; + + function setDatafile(?string $basefile): void { + if ($basefile === null) { + $basedir = "."; + $basename = ""; + } else { + $basedir = path::dirname($basefile); + $basename = path::filename($basefile); + } + $this->datafile = "$basedir/.$basename.{$this->name}".cache::EXT; + } + + function exists(): bool { + return file_exists($this->datafile); + } + + function load() { + return file::reader($this->datafile)->unserialize(); + } + + function save($data) { + file::writer($this->datafile)->serialize($data); + return $data; + } + + function delete(): void { + @unlink($this->datafile); + } +} diff --git a/php/src/cache/TODO.md b/php/src/cache/TODO.md new file mode 100644 index 0000000..e8c41c0 --- /dev/null +++ b/php/src/cache/TODO.md @@ -0,0 +1,6 @@ +# nulib\cache + +* [ ] CacheChannel: stocker aussi la clé primaire, ce qui permet de récupérer + la donnée correspondante dans la source? + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/php/src/cache/cache.php b/php/src/cache/cache.php new file mode 100644 index 0000000..ed599fa --- /dev/null +++ b/php/src/cache/cache.php @@ -0,0 +1,93 @@ +getVarfile("cache.db"); + } + + protected static ?CapacitorStorage $storage = null; + + static function storage(): CapacitorStorage { + return self::$storage ??= new SqliteStorage(self::dbfile()); + } + + static function set_storage(CapacitorStorage $storage): CapacitorStorage { + return self::$storage = $storage; + } + + protected static ?CacheManager $manager = null; + + static function manager(): CacheManager { + return self::$manager ??= new CacheManager(); + } + + static function set_manager(CacheManager $manager): CacheManager { + return self::$manager = $manager; + } + + static function nc(bool $noCache=true, bool $reset=false): void { + self::manager()->setNoCache($noCache, $reset); + } + + protected static function should_cache(string $id, ?string $groupId=null, bool $reset=true): bool { + return self::manager()->shouldCache($id, $groupId, $reset); + } + + static function verifix_id(&$cacheId): void { + $cacheId ??= utils::uuidgen(); + if (is_array($cacheId)) { + $keys = array_keys($cacheId); + if (array_key_exists("id", $cacheId)) $idKey = "id"; + else $idKey = $keys[0] ?? null; + $id = strval($cacheId[$idKey] ?? ""); + if (array_key_exists("group_id", $cacheId)) $groupIdKey = "group_id"; + else $groupIdKey = $keys[1] ?? null; + $groupId = strval($cacheId[$groupIdKey] ?? ""); + } else { + $id = strval($cacheId); + $groupId = ""; + } + # si le groupe ou le nom sont trop grand, en faire un hash + if (strlen($groupId) > 32) $groupId = md5($groupId); + if (strlen($id) > 128) $id = substr($id, 0, 128 - 32).md5($id); + $cacheId = ["group_id" => $groupId, "id" => $id]; + } + + private static function new(array $cacheId, ?string $suffix, $data, ?array $params=null): CacheFile { + $file = $cacheId["group_id"]; + if ($file) $file .= "_"; + $file .= $cacheId["id"]; + $file .= $suffix; + return new CacheFile($file, $data, $params); + } + + static function cache($dataId, $data, ?array $params=null): CacheFile { + self::verifix_id($dataId); + return self::new($dataId, null, $data, $params); + } + + static function get($dataId, $data, ?array $params=null) { + self::verifix_id($dataId); + $noCache = !self::should_cache($dataId["id"], $dataId["group_id"]); + $cache = self::new($dataId, null, $data, $params); + return $cache->get(null, $noCache); + } + + static function all($cursorId, $rows, ?array $params=null): ?iterable { + self::verifix_id($cursorId); + $noCache = !self::should_cache($cursorId["id"], $cursorId["group_id"]); + $cache = self::new($cursorId, "_rows", new CursorCacheData($cursorId, $rows), $params); + return $cache->get(null, $noCache); + } +} diff --git a/php/tbin/.gitignore b/php/tbin/.gitignore index f3b938a..c44eb09 100644 --- a/php/tbin/.gitignore +++ b/php/tbin/.gitignore @@ -1 +1,2 @@ /*.db +/*.cache diff --git a/php/tbin/test-cache.php b/php/tbin/test-cache.php new file mode 100755 index 0000000..f3590c0 --- /dev/null +++ b/php/tbin/test-cache.php @@ -0,0 +1,73 @@ +#!/usr/bin/php +get()); + if ($dumpInfos) { + yaml::dump($cache->getInfos()); + } +} + +//system("rm -f *.cache .*.cache"); + +$what = [ + "null", + "one", + "two", + "three", +]; +$duration = 10; + +if (in_array("null", $what)) { + $null = new CacheFile("null", null, [ + "duration" => $duration, + ]); + show("null", $null); +} + +if (in_array("one", $what)) { + $one = new class("one", null, [ + "duration" => $duration, + ]) extends CacheFile { + protected function compute() { + return 1; + } + }; + show("one", $one); +} + +if (in_array("two", $what)) { + $two = new CacheFile("two", new DataCacheData(null, function () { + return 2; + }), [ + "duration" => $duration, + ]); + show("two", $two); +} + +if (in_array("three", $what)) { + $data31 = new DataCacheData("data31name", function () { + return 31; + }); + + $data32 = new DataCacheData(null, function () { + return 32; + }); + + $three = new CacheFile("three", [ + "data31" => $data31, + $data31, # name=data31name + "data32" => $data32, + $data32, # name="" + ]); + Txx("three.0=", $three->get("data31")); + Txx("three.1=", $three->get("data31name")); + Txx("three.2=", $three->get("data32")); + Txx("three.3=", $three->get("")); +} diff --git a/php/tests/cache/CursorChannelTest.php b/php/tests/cache/CursorChannelTest.php new file mode 100644 index 0000000..70e7343 --- /dev/null +++ b/php/tests/cache/CursorChannelTest.php @@ -0,0 +1,38 @@ + ["a" => "un", "b" => "deux"], + "eng" => ["a" => "one", "b" => "two"], + ["a" => 1, "b" => 2], + ]; + + function testUsage() { + $channel = CursorChannel::with("numbers", self::DATA, self::$storage); + $count = 0; + foreach ($channel as $key => $item) { + msg::info("one: $key => {$item["a"]}"); + $count++; + } + self::assertSame(3, $count); + } + + function testAddColumns() { + $channel = (new class("numbers") extends CursorChannel { + const NAME = "numbersac"; + const TABLE_NAME = self::NAME; + const ADD_COLUMNS = [ + "a" => "varchar(30)", + ]; + })->initStorage(self::$storage)->rechargeAll(self::DATA); + $count = 0; + foreach ($channel as $key => $item) { + msg::info("one: $key => {$item["a"]}"); + $count++; + } + self::assertSame(3, $count); + } +} diff --git a/php/tests/cache/SourceDb.php b/php/tests/cache/SourceDb.php new file mode 100644 index 0000000..31dc119 --- /dev/null +++ b/php/tests/cache/SourceDb.php @@ -0,0 +1,22 @@ +exec("insert into source (s, i, b) values (null, null, null)"); + $db->exec("insert into source (s, i, b) values ('false', 0, 0)"); + $db->exec("insert into source (s, i, b) values ('first', 1, 1)"); + $db->exec("insert into source (s, i, b) values ('second', 2, 1)"); + } + + public function __construct() { + parent::__construct(__DIR__."/source.db"); + } +} diff --git a/php/tests/cache/_TestCase.php b/php/tests/cache/_TestCase.php new file mode 100644 index 0000000..f05875c --- /dev/null +++ b/php/tests/cache/_TestCase.php @@ -0,0 +1,23 @@ +close(); + } +} diff --git a/php/tests/cache/cacheTest.php b/php/tests/cache/cacheTest.php new file mode 100644 index 0000000..3a16cb0 --- /dev/null +++ b/php/tests/cache/cacheTest.php @@ -0,0 +1,108 @@ + ["a" => "un", "b" => "deux"], + "eng" => ["a" => "one", "b" => "two"], + ["a" => 1, "b" => 2], + ]; + + function gendata() { + msg::note("gendata"); + foreach (self::DATA as $key => $item) { + msg::info("yield $key"); + yield $key => $item; + sleep(2); + } + msg::note("fin gendata"); + } + + function _testRows(iterable $rows, int $expectedCount) { + $count = 0; + foreach ($rows as $key => $row) { + $parts = ["got $key => {"]; + $i = 0; + foreach ($row as $k => $v) { + if ($i++ > 0) $parts[] = ", "; + $parts[] = "$k=$v"; + } + $parts[] = "}"; + msg::info(implode("", $parts)); + $count++; + } + self::assertSame($expectedCount, $count); + } + + function _testGet(string $dataId, int $expectedCount, callable $gencompute) { + msg::section($dataId); + cache::nc(true, true); + + msg::step("premier"); + $rows = cache::get($dataId, $gencompute()); + $this->_testRows($rows, $expectedCount); + msg::step("deuxième"); + $rows = cache::get($dataId, $gencompute()); + $this->_testRows($rows, $expectedCount); + + msg::step("vider le cache"); + cache::nc(true, true); + + msg::step("premier"); + $rows = cache::get($dataId, $gencompute()); + $this->_testRows($rows, $expectedCount); + msg::step("deuxième"); + $rows = cache::get($dataId, $gencompute()); + $this->_testRows($rows, $expectedCount); + } + + function testGetStatic() { + $this->_testGet("getStatic", 3, function () { + return static function () { + msg::note("getdata"); + return self::DATA; + }; + }); + } + + function testGetGenerator() { + $this->_testGet("getGenerator", 3, function () { + return $this->gendata(); + }); + } + + function _testAll(string $cursorId, int $expectedCount, callable $gencompute) { + msg::section($cursorId); + cache::nc(true, true); + + msg::step("premier"); + $rows = cache::all($cursorId, $gencompute()); + $this->_testRows($rows, $expectedCount); + msg::step("deuxième"); + $rows = cache::all($cursorId, $gencompute()); + $this->_testRows($rows, $expectedCount); + + msg::step("vider le cache"); + cache::nc(true, true); + + msg::step("premier"); + $rows = cache::all($cursorId, $gencompute()); + $this->_testRows($rows, $expectedCount); + msg::step("deuxième"); + $rows = cache::all($cursorId, $gencompute()); + $this->_testRows($rows, $expectedCount); + } + + function testAllGenerator() { + $this->_testAll("allGenerator", 4, function() { + return static function() { + $db = new SourceDb(); + msg::note("query source"); + yield from $db->all("select * from source"); + }; + }); + } +} From 291db941b95527d5f2b228f30ef09318b7ec10dc Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 5 Oct 2025 16:56:03 +0400 Subject: [PATCH 35/91] modifs.mineures sans commentaires --- bin/.cachectl.php | 1 + bin/cachectl.php | 1 + 2 files changed, 2 insertions(+) create mode 120000 bin/.cachectl.php create mode 120000 bin/cachectl.php diff --git a/bin/.cachectl.php b/bin/.cachectl.php new file mode 120000 index 0000000..c9604f8 --- /dev/null +++ b/bin/.cachectl.php @@ -0,0 +1 @@ +../php/bin/cachectl.php \ No newline at end of file diff --git a/bin/cachectl.php b/bin/cachectl.php new file mode 120000 index 0000000..42fbf67 --- /dev/null +++ b/bin/cachectl.php @@ -0,0 +1 @@ +runphp \ No newline at end of file From d8b70d7ee0c2c8e7a5706ad6f63372c76ef31108 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 5 Oct 2025 17:27:12 +0400 Subject: [PATCH 36/91] optimiser la destination du cache --- php/cli/CachectlApp.php | 7 ++++++- php/src/cache/CacheData.php | 8 +++++++- php/src/cache/CacheFile.php | 15 +++++++++------ php/src/cache/CursorCacheData.php | 4 ++++ php/src/cache/DataCacheData.php | 4 ++++ 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/php/cli/CachectlApp.php b/php/cli/CachectlApp.php index 2773cee..91f8636 100644 --- a/php/cli/CachectlApp.php +++ b/php/cli/CachectlApp.php @@ -18,6 +18,9 @@ class CachectlApp extends Application { ["-r", "--read", "name" => "action", "value" => self::ACTION_READ, "help" => "Afficher le contenu d'un fichier cache", ], + ["-d::", "--data", + "help" => "Identifiant de la donnée à afficher", + ], ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS, "help" => "Afficher des informations sur le fichier cache", ], @@ -43,6 +46,8 @@ class CachectlApp extends Application { protected $updateAction, $updateDuration; + protected $data = null; + function setActionUpdate(int $action, $updateDuration): void { $this->action = self::ACTION_UPDATE; switch ($action) { @@ -90,7 +95,7 @@ class CachectlApp extends Application { "duration" => "INF", "override_duration" => true, ]); - yaml::dump($cache->get()); + yaml::dump($cache->get($this->data)); break; case self::ACTION_INFOS: if ($showSection) msg::section($file); diff --git a/php/src/cache/CacheData.php b/php/src/cache/CacheData.php index 5ce5b83..5ad37c0 100644 --- a/php/src/cache/CacheData.php +++ b/php/src/cache/CacheData.php @@ -6,7 +6,7 @@ use nulib\php\func; use Traversable; /** - * Interface CacheData: gestion d'une donnée mise en cache + * Class CacheData: gestion d'une donnée mise en cache */ abstract class CacheData { function __construct(?string $name, $compute) { @@ -29,6 +29,12 @@ abstract class CacheData { return $data; } + /** + * le cache est-il externe? si non, utiliser {@link setDatafile()} pour + * spécifier le fichier destination de la valeur + */ + abstract function isExternal(): bool; + /** spécifier le chemin du cache à partir du fichier de base */ abstract function setDatafile(?string $basefile): void; diff --git a/php/src/cache/CacheFile.php b/php/src/cache/CacheFile.php index f40ed80..6107f9f 100644 --- a/php/src/cache/CacheFile.php +++ b/php/src/cache/CacheFile.php @@ -217,10 +217,12 @@ class CacheFile extends SharedFile { return null; } - protected function refreshData($data, bool $noCache) { - $source = $this->sources[$data] ?? null; + protected function refreshData($key, bool $noCache) { + $source = $this->sources[$key] ?? null; + $external = $source !== null && $source->isExternal(); + $updateMetadata = $this->shouldUpdate($noCache); - if ($source === null) $updateData = $this->data === null; + if (!$key && !$external) $updateData = $this->data === null; else $updateData = !$source->exists(); if (!$this->readonly && ($updateMetadata || $updateData)) { $this->lockWrite(); @@ -232,11 +234,12 @@ class CacheFile extends SharedFile { $this->data = null; $updateData = true; } - if ($source === null) { + if (!$key && !$external) { if ($updateData) { # calculer la valeur try { - $data = $this->compute(); + if ($source !== null) $data = $source->compute(); + else $data = $this->compute(); } catch (Exception $e) { # le fichier n'est pas mis à jour, mais ce n'est pas gênant: lors # des futurs appels, l'exception continuera d'être lancée ou la @@ -270,7 +273,7 @@ class CacheFile extends SharedFile { $data = null; } } - } elseif ($source === null) { + } elseif (!$key && !$external) { $data = $this->data; } elseif ($source->exists()) { $data = $source->load(); diff --git a/php/src/cache/CursorCacheData.php b/php/src/cache/CursorCacheData.php index bcfdd33..c0b402d 100644 --- a/php/src/cache/CursorCacheData.php +++ b/php/src/cache/CursorCacheData.php @@ -11,6 +11,10 @@ class CursorCacheData extends CacheData { $this->channel = $channel; } + function isExternal(): bool { + return true; + } + function setDatafile(?string $basefile): void { } diff --git a/php/src/cache/DataCacheData.php b/php/src/cache/DataCacheData.php index 7ada552..f3b4e26 100644 --- a/php/src/cache/DataCacheData.php +++ b/php/src/cache/DataCacheData.php @@ -27,6 +27,10 @@ class DataCacheData extends CacheData { return $data; } + function isExternal(): bool { + return false; + } + protected string $datafile; function setDatafile(?string $basefile): void { From f52da16f447edd5c58ee5104f2fe3422b3db1c91 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 5 Oct 2025 20:07:30 +0400 Subject: [PATCH 37/91] mailer prend la configuration dans config aussi --- php/src/mail/mailer.php | 38 ++++++++++++++++++++++++++------------ php/tbin/mailer-devel.yml | 5 +++++ php/tbin/mailer-test.yml | 7 +++++++ 3 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 php/tbin/mailer-devel.yml create mode 100644 php/tbin/mailer-test.yml diff --git a/php/src/mail/mailer.php b/php/src/mail/mailer.php index f9510dc..3f92202 100644 --- a/php/src/mail/mailer.php +++ b/php/src/mail/mailer.php @@ -1,6 +1,7 @@ "?string", ]; + static function resolve_params(?array $params=null): array { + $envParams = [ + "backend" => cv::vn(getenv("NULIB_MAIL_BACKEND")), + "debug" => cv::vn(getenv("NULIB_MAIL_DEBUG")), + "host" => cv::vn(getenv("NULIB_MAIL_HOST")), + "port" => cv::vn(getenv("NULIB_MAIL_PORT")), + "auth" => cv::vn(getenv("NULIB_MAIL_AUTH")), + "username" => cv::vn(getenv("NULIB_MAIL_USERNAME")), + "password" => cv::vn(getenv("NULIB_MAIL_PASSWORD")), + "secure" => cv::vn(getenv("NULIB_MAIL_SECURE")), + ]; + $configParams = config::k("mailer"); + foreach (array_keys(self::SCHEMA) as $key) { + $params[$key] ??= $envParams[$key] ?? null; + $params[$key] ??= $configParams[$key] ?? null; + } + return $params; + } + static function get(?array $params=null, ?bool $exceptions=null): PHPMailer { + $params = self::resolve_params($params); + $mailer = new PHPMailer($exceptions); $mailer->setLanguage("fr"); $mailer->CharSet = PHPMailer::CHARSET_UTF8; # backend - $backend = $params["backend"] ?? null; - $backend ??= cv::vn(getenv("NULIB_MAIL_BACKEND")); - $backend ??= "smtp"; + $backend = $params["backend"] ?? "smtp"; switch ($backend) { case "smtp": - # host + # host, port $host = $params["host"] ?? null; - $host ??= cv::vn(getenv("NULIB_MAIL_HOST")); - # port - $port = $params["port"] ?? null; - $port ??= cv::vn(getenv("NULIB_MAIL_PORT")); - $port ??= 25; + $port = $params["port"] ?? 25; if ($host === null) { throw new ValueException("mail host is required"); } @@ -93,9 +109,7 @@ class mailer { throw ValueException::invalid_value($backend, "mailer backend"); } # debug - $debug = $params["debug"] ?? null; - $debug ??= cv::vn(getenv("NULIB_MAIL_DEBUG")); - $debug ??= SMTP::DEBUG_OFF; + $debug = $params["debug"] ?? SMTP::DEBUG_OFF; if (is_int($debug)) { if ($debug < SMTP::DEBUG_OFF) $debug = SMTP::DEBUG_OFF; elseif ($debug > SMTP::DEBUG_LOWLEVEL) $debug = SMTP::DEBUG_LOWLEVEL; diff --git a/php/tbin/mailer-devel.yml b/php/tbin/mailer-devel.yml new file mode 100644 index 0000000..1190d9f --- /dev/null +++ b/php/tbin/mailer-devel.yml @@ -0,0 +1,5 @@ +# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8 + +app: + mailer: + host: maildev.devel.self diff --git a/php/tbin/mailer-test.yml b/php/tbin/mailer-test.yml new file mode 100644 index 0000000..6a3f17e --- /dev/null +++ b/php/tbin/mailer-test.yml @@ -0,0 +1,7 @@ +# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8 + +app: + mailer: + host: maildev.univ-reunion.fr + username: mel + password: gibson From ff4ef3403764d33a92eb3f8b2004196569575873 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 5 Oct 2025 20:52:32 +0400 Subject: [PATCH 38/91] =?UTF-8?q?par=20d=C3=A9faut,=20v=C3=A9rifier=20la?= =?UTF-8?q?=20connexion=20avant=20chaque=20transaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/src/db/CapacitorStorage.php | 2 +- php/src/db/IDatabase.php | 2 +- php/src/db/pdo/Pdo.php | 19 +++++++++++++++---- php/src/db/pgsql/Pgsql.php | 19 ++++++++++++++----- php/src/db/sqlite/Sqlite.php | 18 ++++++++++++++---- 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/php/src/db/CapacitorStorage.php b/php/src/db/CapacitorStorage.php index dbc7f87..4c399c4 100644 --- a/php/src/db/CapacitorStorage.php +++ b/php/src/db/CapacitorStorage.php @@ -17,7 +17,7 @@ abstract class CapacitorStorage { abstract function db(): IDatabase; function ensureLive(): self { - $this->db()->ensure(); + $this->db()->ensureLive(); return $this; } diff --git a/php/src/db/IDatabase.php b/php/src/db/IDatabase.php index 32a0013..5ca2e54 100644 --- a/php/src/db/IDatabase.php +++ b/php/src/db/IDatabase.php @@ -17,7 +17,7 @@ interface IDatabase extends ITransactor { * transactions en cours sont perdues. cette méthode est donc prévue pour * vérifier la validité de la connexion avant de lancer une transaction */ - function ensure(): self; + function ensureLive(): self; /** * - si c'est un insert, retourner l'identifiant autogénéré de la ligne diff --git a/php/src/db/pdo/Pdo.php b/php/src/db/pdo/Pdo.php index 10da1c6..500d385 100644 --- a/php/src/db/pdo/Pdo.php +++ b/php/src/db/pdo/Pdo.php @@ -28,6 +28,7 @@ class Pdo implements IDatabase { "options" => $pdo->options, "config" => $pdo->config, "migration" => $pdo->migration, + "autocheck" => $pdo->autocheck, ], $params)); } else { return new static($pdo, $params); @@ -52,6 +53,10 @@ class Pdo implements IDatabase { protected const MIGRATION = null; + protected const AUTOCHECK = true; + + protected const AUTOOPEN = true; + const dbconn_SCHEMA = [ "name" => "string", "user" => "?string", @@ -64,7 +69,8 @@ class Pdo implements IDatabase { "replace_config" => ["?array|callable"], "config" => ["?array|callable"], "migration" => ["?array|string|callable"], - "auto_open" => ["bool", true], + "autocheck" => ["bool", self::AUTOCHECK], + "autoopen" => ["bool", self::AUTOOPEN], ]; function __construct($dbconn=null, ?array $params=null) { @@ -96,8 +102,8 @@ class Pdo implements IDatabase { # migrations $this->migration = $params["migration"] ?? static::MIGRATION; # - $defaultAutoOpen = self::params_SCHEMA["auto_open"][1]; - if ($params["auto_open"] ?? $defaultAutoOpen) { + $this->autocheck = $params["autocheck"] ?? static::AUTOCHECK; + if ($params["autoopen"] ?? static::AUTOOPEN) { $this->open(); } } @@ -113,6 +119,8 @@ class Pdo implements IDatabase { /** @var array|string|callable */ protected $migration; + protected bool $autocheck; + protected ?\PDO $db = null; function getSql($query, ?array $params=null): string { @@ -163,7 +171,7 @@ class Pdo implements IDatabase { const SQL_CHECK_LIVE = "select 1"; - function ensure(): self { + function ensureLive(): self { try { $this->_query(static::SQL_CHECK_LIVE); } catch (\PDOException $e) { @@ -206,6 +214,9 @@ class Pdo implements IDatabase { } function beginTransaction(?callable $func=null, bool $commit=true): void { + # s'assurer que la connexion à la BDD est active avant de commencer une + # transaction + if ($this->autocheck) $this->ensureLive(); $this->db()->beginTransaction(); if ($this->transactors !== null) { foreach ($this->transactors as $transactor) { diff --git a/php/src/db/pgsql/Pgsql.php b/php/src/db/pgsql/Pgsql.php index ca9b7e9..bda2423 100644 --- a/php/src/db/pgsql/Pgsql.php +++ b/php/src/db/pgsql/Pgsql.php @@ -34,7 +34,6 @@ class Pgsql implements IDatabase { } } - protected const OPTIONS = [ # XXX désactiver les connexions persistantes par défaut # pour réactiver par défaut, il faudrait vérifier la connexion à chaque fois @@ -49,13 +48,18 @@ class Pgsql implements IDatabase { const MIGRATION = null; + protected const AUTOCHECK = true; + + protected const AUTOOPEN = true; + const params_SCHEMA = [ "dbconn" => ["array"], "options" => ["?array|callable"], "replace_config" => ["?array|callable"], "config" => ["?array|callable"], "migration" => ["?array|string|callable"], - "auto_open" => ["bool", true], + "autocheck" => ["bool", self::AUTOCHECK], + "autoopen" => ["bool", self::AUTOOPEN], ]; const dbconn_SCHEMA = [ @@ -113,8 +117,8 @@ class Pgsql implements IDatabase { # migrations $this->migration = $params["migration"] ?? static::MIGRATION; # - $defaultAutoOpen = self::params_SCHEMA["auto_open"][1]; - if ($params["auto_open"] ?? $defaultAutoOpen) { + $this->autocheck = $params["autocheck"] ?? static::AUTOCHECK; + if ($params["autoopen"] ?? static::AUTOOPEN) { $this->open(); } } @@ -130,6 +134,8 @@ class Pgsql implements IDatabase { /** @var array|string|callable */ protected $migration; + protected bool $autocheck; + /** @var resource */ protected $db = null; @@ -209,7 +215,7 @@ class Pgsql implements IDatabase { const SQL_CHECK_LIVE = "select 1"; - function ensure(): self { + function ensureLive(): self { try { $this->_query(static::SQL_CHECK_LIVE); } catch (\PDOException $e) { @@ -267,6 +273,9 @@ class Pgsql implements IDatabase { } function beginTransaction(?callable $func=null, bool $commit=true): void { + # s'assurer que la connexion à la BDD est active avant de commencer une + # transaction + if ($this->autocheck) $this->ensureLive(); $this->_exec("begin"); if ($this->transactors !== null) { foreach ($this->transactors as $transactor) { diff --git a/php/src/db/sqlite/Sqlite.php b/php/src/db/sqlite/Sqlite.php index 1d52f2c..c82ca43 100644 --- a/php/src/db/sqlite/Sqlite.php +++ b/php/src/db/sqlite/Sqlite.php @@ -80,6 +80,10 @@ class Sqlite implements IDatabase { const MIGRATION = null; + protected const AUTOCHECK = true; + + protected const AUTOOPEN = true; + const params_SCHEMA = [ "file" => ["string", ""], "flags" => ["int", SQLITE3_OPEN_READWRITE + SQLITE3_OPEN_CREATE], @@ -88,7 +92,8 @@ class Sqlite implements IDatabase { "replace_config" => ["?array|callable"], "config" => ["?array|callable"], "migration" => ["?array|string|callable"], - "auto_open" => ["bool", true], + "autocheck" => ["bool", self::AUTOCHECK], + "autoopen" => ["bool", self::AUTOOPEN], ]; function __construct(?string $file=null, ?array $params=null) { @@ -117,9 +122,9 @@ class Sqlite implements IDatabase { # migrations $this->migration = $params["migration"] ?? static::MIGRATION; # - $defaultAutoOpen = self::params_SCHEMA["auto_open"][1]; $this->inTransaction = false; - if ($params["auto_open"] ?? $defaultAutoOpen) { + $this->autocheck = $params["autocheck"] ?? static::AUTOCHECK; + if ($params["autoopen"] ?? static::AUTOOPEN) { $this->open(); } } @@ -147,6 +152,8 @@ class Sqlite implements IDatabase { /** @var array|string|callable */ protected $migration; + protected bool $autocheck; + /** @var SQLite3 */ protected $db; @@ -208,7 +215,7 @@ class Sqlite implements IDatabase { const SQL_CHECK_LIVE = "select 1"; - function ensure(): self { + function ensureLive(): self { try { $this->_query(static::SQL_CHECK_LIVE); } catch (\PDOException $e) { @@ -259,6 +266,9 @@ class Sqlite implements IDatabase { } function beginTransaction(?callable $func=null, bool $commit=true): void { + # s'assurer que la connexion à la BDD est active avant de commencer une + # transaction + if ($this->autocheck) $this->ensureLive(); $this->db()->exec("begin"); $this->inTransaction = true; if ($this->transactors !== null) { From 526a693ead7315c552eb3ea74ba08b9097aa9816 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 6 Oct 2025 08:04:16 +0400 Subject: [PATCH 39/91] =?UTF-8?q?changer=20la=20configuration=20par=20d?= =?UTF-8?q?=C3=A9faut=20pour=20mysql?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/src/db/mysql/Mysql.php | 11 +++++++++++ php/src/db/pdo/Pdo.php | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/php/src/db/mysql/Mysql.php b/php/src/db/mysql/Mysql.php index f0a0e75..52b93d6 100644 --- a/php/src/db/mysql/Mysql.php +++ b/php/src/db/mysql/Mysql.php @@ -6,11 +6,22 @@ use nulib\db\pdo\Pdo; class Mysql extends Pdo { const PREFIX = "mysql"; + static function config_setTimeout(self $pdo): void { + $pdo->_exec("SET session wait_timeout=28800"); + $pdo->_exec("SET session interactive_timeout=28800"); + } + const CONFIG_setTimeout = [self::class, "config_setTimeout"]; + static function config_unbufferedQueries(self $mysql): void { $mysql->db->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); } const CONFIG_unbufferedQueries = [self::class, "config_unbufferedQueries"]; + const DEFAULT_CONFIG = [ + ...parent::DEFAULT_CONFIG, + self::CONFIG_setTimeout, + ]; + function getDbname(): ?string { $url = $this->dbconn["name"] ?? null; if ($url !== null && preg_match('/^mysql(?::|.*;)dbname=([^;]+)/i', $url, $ms)) { diff --git a/php/src/db/pdo/Pdo.php b/php/src/db/pdo/Pdo.php index 500d385..ba92e99 100644 --- a/php/src/db/pdo/Pdo.php +++ b/php/src/db/pdo/Pdo.php @@ -42,7 +42,7 @@ class Pdo implements IDatabase { const CONFIG_errmodeException_lowerCase = [self::class, "config_errmodeException_lowerCase"]; protected const OPTIONS = [ - \PDO::ATTR_PERSISTENT => true, + \PDO::ATTR_PERSISTENT => false, ]; protected const DEFAULT_CONFIG = [ From f55c66e1f34bb13513f848feb1d8a5ef9896c1cc Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 6 Oct 2025 08:59:29 +0400 Subject: [PATCH 40/91] support --no-arg --- php/src/app/args/Aodef.php | 5 ++++ php/tests/app/{cli => args}/AodefTest.php | 3 +-- php/tests/app/{cli => args}/AolistTest.php | 5 +--- .../app/{cli => args}/SimpleAolistTest.php | 2 +- .../{cli => args}/SimpleArgsParserTest.php | 26 ++++++++++++++++++- 5 files changed, 33 insertions(+), 8 deletions(-) rename php/tests/app/{cli => args}/AodefTest.php (98%) rename php/tests/app/{cli => args}/AolistTest.php (90%) rename php/tests/app/{cli => args}/SimpleAolistTest.php (98%) rename php/tests/app/{cli => args}/SimpleArgsParserTest.php (85%) diff --git a/php/src/app/args/Aodef.php b/php/src/app/args/Aodef.php index 89d9da2..4486eb8 100644 --- a/php/src/app/args/Aodef.php +++ b/php/src/app/args/Aodef.php @@ -436,6 +436,11 @@ class Aodef { $longest ??= self::get_longest($this->_options, self::TYPE_SHORT); if ($longest !== null) { $longest = preg_replace('/[^A-Za-z0-9]+/', "_", $longest); + # les options --no-name mettent à jour la valeur $name et inversent + # le traitement + if ($longest !== "no_" && str::del_prefix($longest, "no_")) { + $this->inverse ??= true; + } if (preg_match('/^[0-9]/', $longest)) { # le nom de la propriété ne doit pas commencer par un chiffre $longest = "p$longest"; diff --git a/php/tests/app/cli/AodefTest.php b/php/tests/app/args/AodefTest.php similarity index 98% rename from php/tests/app/cli/AodefTest.php rename to php/tests/app/args/AodefTest.php index c27d6c1..db6b6d1 100644 --- a/php/tests/app/cli/AodefTest.php +++ b/php/tests/app/args/AodefTest.php @@ -1,7 +1,6 @@ parse($dest, ["-aabb"]); + self::assertSame(["plouf" => 0, "args" => []], $dest); + + $parser = new SimpleArgsParser([ + ["-a", "--plouf", "value" => true], + ["-b", "--no-plouf", "value" => false], + ]); + $dest = ["plouf" => null]; + $parser->parse($dest, []); + self::assertSame(["plouf" => null, "args" => []], $dest); + $dest = ["plouf" => null]; + $parser->parse($dest, ["-a"]); + self::assertSame(["plouf" => true, "args" => []], $dest); + $dest = ["plouf" => null]; + $parser->parse($dest, ["-b"]); + self::assertSame(["plouf" => false, "args" => []], $dest); + } } From 90ca62984dd4708e10e8acd3e8ad4f8e1c876976 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 6 Oct 2025 11:46:07 +0400 Subject: [PATCH 41/91] maj mounts --- runphp/runphp | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/runphp/runphp b/runphp/runphp index 30f3056..10486e3 100755 --- a/runphp/runphp +++ b/runphp/runphp @@ -536,41 +536,36 @@ OPTIONS done # monter le répertoire qui contient $PROJDIR - mount_composer= + Cwd="$(pwd)" + mount_homes=1 mount_standalone=1 mount_mount=1 + mount_cwd=1 if [ -z "$PROJDIR" -o "${PROJDIR#$HOME/}" != "$PROJDIR" -o "$PROJDIR" == "$HOME" ]; then # bind mount $HOME args+=(-v "$HOME:$HOME${UseRslave:+:rslave}") - if [ -n "$RUNPHP_STANDALONE" -a "${RUNPHP_STANDALONE#$HOME/}" != "$RUNPHP_STANDALONE" ]; then - mount_standalone= - fi - if [ -n "$RUNPHP_MOUNT" -a "${RUNPHP_MOUNT#$HOME/}" != "$RUNPHP_MOUNT" ]; then - mount_mount= - fi - elif [ -n "$PROJDIR" ]; then - # bind mount uniquement le répertoire du projet + [ "${HOME#/home/}" != "$HOME" ] && mount_homes= + [ -n "$RUNPHP_STANDALONE" -a "${RUNPHP_STANDALONE#$HOME/}" != "$RUNPHP_STANDALONE" ] && mount_standalone= + [ -n "$RUNPHP_MOUNT" -a "${RUNPHP_MOUNT#$HOME/}" != "$RUNPHP_MOUNT" ] && mount_mount= + [ "${Cwd#$HOME/}" != "$Cwd" ] && mount_cwd= + elif [ -n "$PROJDIR" -a "${PROJDIR#/home/}" == "$PROJDIR" ]; then + # bind mount le répertoire du projet s'il n'est pas dans /home (qui est + # monté par défaut si $HOME n'est pas monté) args+=(-v "$PROJDIR:$PROJDIR${UseRslave:+:rslave}") - mount_composer=1 - [ "$RUNPHP_STANDALONE" == "$PROJDIR" ] && mount_standalone= - [ "$RUNPHP_MOUNT" == "$PROJDIR" ] && mount_mount= + [ "$RUNPHP_STANDALONE" == "$PROJDIR" -o "${RUNPHP_STANDALONE#$PROJDIR/}" != "$PROJDIR"] && mount_standalone= + [ "$RUNPHP_MOUNT" == "$PROJDIR" -o "${RUNPHP_MOUNT#$PROJDIR/}" != "$PROJDIR" ] && mount_mount= + [ "$Cwd" == "$PROJDIR" -o "${Cwd#$PROJDIR/}" != "$PROJDIR" ] && mount_cwd= fi - if [ -n "$mount_composer" -a -d "$HOME/.composer" ]; then - # monter la configuration de composer - args+=(-v "$HOME/.composer:$HOME/.composer") - fi - if [ -n "$RUNPHP_STANDALONE" -a -n "$mount_standalone" ]; then - args+=(-v "$RUNPHP_STANDALONE:$RUNPHP_STANDALONE") - fi - if [ -n "$RUNPHP_MOUNT" -a -n "$mount_mount" ]; then - args+=(-v "$RUNPHP_MOUNT:$RUNPHP_MOUNT") - fi - args+=(-w "$(pwd)") + [ -n "$mount_homes" ] && args+=(-v "/home:/home${UseRslave:+:rslave}") + [ -n "$RUNPHP_STANDALONE" -a -n "$mount_standalone" ] && args+=(-v "$RUNPHP_STANDALONE:$RUNPHP_STANDALONE${UseRslave:+:rslave}") + [ -n "$RUNPHP_MOUNT" -a -n "$mount_mount" ] && args+=(-v "$RUNPHP_MOUNT:$RUNPHP_MOUNT${UseRslave:+:rslave}") + [ -n "$mount_cwd" ] && args+=(-v "$Cwd:$Cwd${UseRslave:+:rslave}") + args+=(-w "$Cwd") # lancer avec l'utilisateur courant if [ $(id -u) -ne 0 ]; then # si c'est un utilisateur lambda, il faut monter les informations - # nécessaires. composer est déjà monté via $HOME + # nécessaires. composer est déjà monté via $HOME ou /home args+=( -e DEVUSER_USERENT="$(getent passwd "$(id -un)")" -e DEVUSER_GROUPENT="$(getent group "$(id -gn)")" From c1c369f554a431fadaf2edc445f7d34e9d46b010 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 6 Oct 2025 13:09:02 +0400 Subject: [PATCH 42/91] maj chemin pour phpwrappers --- runphp/update-runphp.sh | 57 +++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/runphp/update-runphp.sh b/runphp/update-runphp.sh index 2975e6b..07f30e4 100755 --- a/runphp/update-runphp.sh +++ b/runphp/update-runphp.sh @@ -52,7 +52,7 @@ declare -A PHPWRAPPER_MODES=( ) projdir= -install_phpwrappers=auto +install_phpwrappers=1 args=( "Mettre à jour le script runphp" "[path/to/runphp]" @@ -63,7 +63,6 @@ Copier les fichiers pour un projet de l'université de la Réunion: - le script build est mis à jour - les wrappers PHP pour la gestion des tâches de fond sont mis à jour le cas échéant" - -p,--phpwrappers-install install_phpwrappers=1 "forcer l'installation des wrappers PHP" --np,--no-phpwrappers-install install_phpwrappers= "ne pas installer les wrappers PHP" ) parse_args "$@"; set -- "${args[@]}" @@ -156,35 +155,31 @@ if [ -n "$projdir" ]; then ' fi - sbin_path=sbin - sbin2proj=.. - cli_path=cli/config - cli2proj=../.. - if [ "$install_phpwrappers" == auto ]; then - if [ ! -f "$PROJDIR/$COMPOSERDIR/composer.json" ]; then - # ce doit être un projet PHP - install_phpwrappers= - elif [ -d "$projdir/cli/config" ]; then - install_phpwrappers=1 - sbin_path=sbin - sbin2proj=.. - cli_path=cli/config - cli2proj=../.. - elif [ -d "$projdir/cli_config" ]; then - install_phpwrappers=1 - sbin_path=sbin - sbin2proj=.. - cli_path=cli_config - cli2proj=../.. - elif [ -d "$projdir/_cli" ]; then - install_phpwrappers=1 - sbin_path=sbin - sbin2proj=.. - cli_path=_cli - cli2proj=../.. - else - install_phpwrappers= - fi + if [ -z "$install_phpwrappers" ]; then + install_phpwrappers= + elif [ ! -f "$PROJDIR/$COMPOSERDIR/composer.json" ]; then + # ce doit être un projet PHP + install_phpwrappers= + elif [ -d "$projdir/cli/config" ]; then + install_phpwrappers=1 + sbin_path=sbin + sbin2proj=.. + cli_path=cli/config + cli2proj=../.. + elif [ -d "$projdir/cli_config" ]; then + install_phpwrappers=1 + sbin_path=sbin + sbin2proj=.. + cli_path=cli_config + cli2proj=.. + elif [ -d "$projdir/_cli" ]; then + install_phpwrappers=1 + sbin_path=sbin + sbin2proj=.. + cli_path=_cli + cli2proj=.. + else + install_phpwrappers= fi if [ -n "$install_phpwrappers" ]; then From 95b0263969c8916490968bfa1b6321796c900f5f Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 6 Oct 2025 21:05:57 +0400 Subject: [PATCH 43/91] modifs.mineures sans commentaires --- bash/src/pman.sh | 14 +- bash/src/pman.tool.pdev.sh | 10 + bash/src/pman.tool.pdist.sh | 10 + bash/src/pman.tool.pmain.sh | 10 + bin/nlshell | 4 +- wip/_pman.tool | 372 ++++++++++++++++++++++++++++++++++++ wip/pdev | 1 + wip/pdist | 1 + wip/pmain | 1 + wip/pman | 372 ++++++++++++++++++++++++++++++++++++ wip/pmer | 262 +++++++++++++++++++++++++ wip/prel | 292 ++++++++++++++++++++++++++++ wip/pwip | 60 ++++++ 13 files changed, 1404 insertions(+), 5 deletions(-) create mode 100644 bash/src/pman.tool.pdev.sh create mode 100644 bash/src/pman.tool.pdist.sh create mode 100644 bash/src/pman.tool.pmain.sh create mode 100755 wip/_pman.tool create mode 120000 wip/pdev create mode 120000 wip/pdist create mode 120000 wip/pmain create mode 100755 wip/pman create mode 100755 wip/pmer create mode 100755 wip/prel create mode 100755 wip/pwip diff --git a/bash/src/pman.sh b/bash/src/pman.sh index 2d26133..917969c 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -147,18 +147,23 @@ EOF ################################################################################ # Config -function ensure_gitdir() { +function check_gitdir() { # commencer dans le répertoire indiqué local chdir="$1" if [ -n "$chdir" ]; then - cd "$chdir" || die || return + cd "$chdir" || return 1 fi # se mettre à la racine du dépôt git local gitdir git_ensure_gitvcs setx gitdir=git_get_toplevel - cd "$gitdir" || die || return + cd "$gitdir" || return 1 +} + +function ensure_gitdir() { + # commencer dans le répertoire indiqué + check_gitdir "$@" || die || return } function load_branches() { @@ -245,6 +250,9 @@ function load_config() { ConfigFile="$(pwd)/.pman.conf" source "$ConfigFile" elif [ -n "$1" -a -n "${MYNAME#$1}" ]; then + # $1 est le nom de base de l'outil e.g "pdev", et le suffixe est la + # configuration à charger par défaut. i.e pdev74 chargera par défaut la + # configuration pman74.conf ConfigFile="$NULIBDIR/bash/src/pman${MYNAME#$1}.conf.sh" source "$ConfigFile" else diff --git a/bash/src/pman.tool.pdev.sh b/bash/src/pman.tool.pdev.sh new file mode 100644 index 0000000..a913374 --- /dev/null +++ b/bash/src/pman.tool.pdev.sh @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +PMAN_TOOLS=pdev +SRC_TYPE=DEVELOP +SRC_BRANCH="${SRC_TYPE,,}"; SRC_BRANCH="${SRC_BRANCH^}Branch" +DEST_TYPE=MAIN +DEST_BRANCH="${DEST_TYPE,,}"; DEST_BRANCH="${DEST_BRANCH^}Branch" +ALLOW_MERGE=1 +MERGE_PREL=1 +ALLOW_DELETE= diff --git a/bash/src/pman.tool.pdist.sh b/bash/src/pman.tool.pdist.sh new file mode 100644 index 0000000..433e3bc --- /dev/null +++ b/bash/src/pman.tool.pdist.sh @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +PMAN_TOOL=pdist +SRC_TYPE=DIST +SRC_BRANCH="${SRC_TYPE,,}"; SRC_BRANCH="${SRC_BRANCH^}Branch" +DEST_TYPE= +DEST_BRANCH= +ALLOW_MERGE= +MERGE_PREL= +ALLOW_DELETE= diff --git a/bash/src/pman.tool.pmain.sh b/bash/src/pman.tool.pmain.sh new file mode 100644 index 0000000..3e6a4a0 --- /dev/null +++ b/bash/src/pman.tool.pmain.sh @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +PMAN_TOOL=pmain +SRC_TYPE=MAIN +SRC_BRANCH="${SRC_TYPE,,}"; SRC_BRANCH="${SRC_BRANCH^}Branch" +DEST_TYPE=DIST +DEST_BRANCH="${DEST_TYPE,,}"; DEST_BRANCH="${DEST_BRANCH^}Branch" +ALLOW_MERGE=1 +MERGE_PREL= +ALLOW_DELETE= diff --git a/bin/nlshell b/bin/nlshell index f5f64f6..6a628cd 100755 --- a/bin/nlshell +++ b/bin/nlshell @@ -20,8 +20,8 @@ fi [ -f /etc/profile ] && source /etc/profile [ -f ~/.bash_profile ] && source ~/.bash_profile -# Modifier le PATH. Ajouter aussi le chemin vers les uapps python -PATH=$(qval "$NULIBDIR/bin:$PATH") +# Modifier le PATH +PATH=$(qval "$NULIBDIR/wip:$NULIBDIR/bin:$PATH") if [ -n '$DEFAULT_PS1' ]; then DEFAULT_PS1=$(qval "[nlshell] $DEFAULT_PS1") diff --git a/wip/_pman.tool b/wip/_pman.tool new file mode 100755 index 0000000..3781dd6 --- /dev/null +++ b/wip/_pman.tool @@ -0,0 +1,372 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname -- "$0")/../load.sh" || exit 1 +require: git pman pman.conf "pman.tool.$MYNAME" + +git_cleancheckout_DIRTY="\ +Vous avez des modifications locales. +Enregistrez ces modifications avant de fusionner la branche" + +function dump_action() { + echo -n "\ +SRC_TYPE=$SRC_TYPE +SRC_BRANCH=$SRC_BRANCH +DEST_TYPE=$DEST_TYPE +DEST_BRANCH=$DEST_BRANCH + +CurrentBranch=$CurrentBranch +LocalBranches=${LocalBranches[*]} +RemoteBranches=${RemoteBranches[*]} +AllBranches=${AllBranches[*]} + +SrcType=$SrcType +SrcBranch=$SrcBranch +DestType=$DestType +DestBranch=$DestBranch + +UpstreamBranch=$UpstreamBranch +FeatureBranches=${FeatureBranches[*]} +DevelopBranch=$DevelopBranch +ReleaseBranch=$ReleaseBranch +HotfixBranch=$HotfixBranch +MainBranch=$MainBranch +DistBranch=$DistBranch +" +} + +function _ensure_src_branch() { + [ -n "$SrcBranch" ] || die "La branche $SRC_TYPE n'a pas été définie" + [ "$1" == init -o -n "${!SRC_BRANCH}" ] || die "$SrcBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +} + +function _ensure_dest_branch() { + [ -n "$DestBranch" ] || die "La branche $DEST_TYPE n'a pas été définie" + [ "$1" == init -o -n "${!DEST_BRANCH}" ] || die "$DestBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +} + +function checkout_action() { + local -a push_branches + + if [ -z "${!SRC_BRANCH}" ]; then + array_contains AllBranches "$SrcBranch" && exit_with enote "\ +$SrcBranch: une branche du même nom existe dans l'origine + git checkout $SrcBranch" + _ensure_dest_branch + _ensure_src_branch init + + resolve_should_push + + enote "Vous allez créer la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}" + ask_yesno "Voulez-vous continuer?" O || die + + einfo "Création de la branche $SrcBranch" + git checkout -b "$SrcBranch" "$DestBranch" || die + push_branches+=("$SrcBranch") + + _push_branches + fi + git checkout "$SrcBranch" +} + +function ensure_branches() { + [ -n "${!SRC_BRANCH}" -a -n "${!DEST_BRANCH}" ] || + die "${!SRC_BRANCH}: Aucune configuration de fusion trouvée pour cette branche" + + array_contains LocalBranches "${!SRC_BRANCH}" || die "${!SRC_BRANCH}: branche source introuvable" + array_contains LocalBranches "${!DEST_BRANCH}" || die "${!DEST_BRANCH}: branche destination introuvable" +} + +function _show_action() { + local commits + setx commits=_list_commits + if [ -n "$commits" ]; then + if [ $ShowLevel -ge 2 ]; then + { + echo "\ +# Commits à fusionner ${!SRC_BRANCH} --> ${!DEST_BRANCH} + +$commits +" + _sd_COLOR=always _show_diff + } | less -eRF + else + einfo "Commits à fusionner ${!SRC_BRANCH} --> ${!DEST_BRANCH}" + eecho "$commits" + fi + fi +} +function show_action() { + git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" + ensure_branches + _show_action "$@" +} + +function _merge_action() { + enote "\ +Ce script va +- fusionner la branche ${COULEUR_BLEUE}${!SRC_BRANCH}${COULEUR_NORMALE} dans ${COULEUR_ROUGE}${!DEST_BRANCH}${COULEUR_NORMALE}${Push:+ +- pousser les branches modifiées}" + ask_yesno "Voulez-vous continuer?" O || die + + local script=".git/pman-merge.sh" + local -a push_branches delete_branches + local hook + local comment= + local or_die=" || exit 1" + + _mscript_start + _scripta <.gitignore "\ +.~lock*# +.*.swp" + git add .gitignore + fi + return 0 +} + +function init_repo_action() { + local -a push_branches; local config + + [ ${#LocalBranches[*]} -eq 0 ] || die "Ce dépôt a déjà été initialisé" + + _init_config || exit_with ewarn "Initialisation du dépôt annulée" + + einfo "Création de la branche $MAIN" + git symbolic-ref HEAD "refs/heads/$MAIN" + git commit -m "commit initial" + push_branches+=("$MAIN") + + einfo "Création de la branche $DEVELOP" + git checkout -b "$DEVELOP" + push_branches+=("$DEVELOP") + + _push_branches +} + +function init_config_action() { + local -a push_branches; local config + + [ -f .pman.conf -a -z "$ForceCreate" ] && die "La configuration pman a déjà été initialisée" + + resolve_should_push + + _init_config || exit_with ewarn "Initialisation de la configuration annulée" + git commit -m "configuration pman" + push_branches+=("$CurrentBranch") + + _push_branches +} + +function _init_composer() { + if [ ! -f .composer.pman.yml -o -n "$ForceCreate" ]; then + ac_set_tmpfile config + cat >"$config" <Intégration initiale de la branche $UPSTREAM" \ + -srecursive -Xours --allow-unrelated-histories \ + "$UPSTREAM" + push_branches+=("$DEVELOP") + + _push_branches + fi + git checkout -q "$UPSTREAM" +} + +function _ensure_dist_branch() { + [ -n "$DIST" ] || die "La branche DIST n'a pas été définie" + [ "$1" == init -o -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +} + +function init_dist_action() { + local -a push_branches + + if [ -z "$DistBranch" ]; then + array_contains AllBranches "$DIST" && exit_with enote "\ +$DIST: une branche du même nom existe dans l'origine + git checkout $DIST" + _ensure_main_branch + _ensure_dist_branch init + + resolve_should_push + + enote "Vous allez créer la branche ${COULEUR_VERTE}$DIST${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MAIN${COULEUR_NORMALE}" + ask_yesno "Voulez-vous continuer?" O || die + + einfo "Création de la branche $DIST" + git checkout -b "$DIST" "$MAIN" || die + push_branches+=("$DIST") + + _push_branches + fi + git checkout -q "$DIST" +} + +function init_feature_action() { + local -a push_branches; local branch + + [ -n "$FEATURE" ] || die "La branche FEATURE n'a pas été définie" + branch="${1#$FEATURE}" + [ -n "$branch" ] || die "Vous devez spécifier le nom de la branche" + branch="$FEATURE$branch" + + if ! array_contains LocalBranches "$branch"; then + array_contains AllBranches "$branch" && exit_with enote "\ +$branch: une branche du même nom existe dans l'origine + git checkout $branch" + _ensure_develop_branch + + resolve_should_push + + enote "Vous allez créer la branche ${COULEUR_VERTE}$branch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$DEVELOP${COULEUR_NORMALE}" + ask_yesno "Voulez-vous continuer?" O || die + + einfo "Création de la branche $branch" + git checkout -b "$branch" "$DEVELOP" || die + push_branches+=("$branch") + + _push_branches + fi + git checkout -q "$branch" +} + +function init_action() { + local what="${1:-develop}"; shift + case "$what" in + init|repo|r) init_repo_action "$@";; + config) init_config_action "$@";; + composer) init_composer_action "$@";; + main|m) checkout_main_action;; + develop|dev|d) init_develop_action "$@";; + upstream|up|u) init_upstream_action "$@";; + dist|x) init_dist_action "$@";; + *) init_feature_action "$what" "$@";; + esac +} + +################################################################################ +# Programme principal +################################################################################ + +chdir= +ConfigBranch= +ConfigFile= +action=init +Origin= +[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push= +ForceCreate= +args=( + "gérer un projet git" + "\ +repo|config|composer +develop|upstream|dist + +INITIALISATION + +Par défaut, le script agit en mode initialisation qui permet de créer et/ou +configurer certaines branches du dépôt si elles n'existent pas déjà + + repo + initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP + + develop + créer la branche $DEVELOP + upstream + créer la branche ${UPSTREAM:-UPSTREAM} en tant que source de la branche $DEVELOP + dist + créer la branche ${DIST:-DIST} en tant que destination de la branche $MAIN + anything + créer la branche ${FEATURE}anything à partir de la branche $DEVELOP" + -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" + -B:,--config-branch ConfigBranch= "++\ +branche à partir de laquelle charger la configuration" + -c:,--config-file:CONFIG ConfigFile= "++\ +fichier de configuration des branches. cette option est prioritaire sur --config-branch +par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" + -w,--show-config action=show "++\ +afficher la configuration chargée" + --composer-select-profile action=composer_select_profile "\ +sélectionner le profil composer spécifié en argument" + -O:,--origin Origin= "++\ +origine vers laquelle pousser les branches" + -n,--no-push Push= "\ +ne pas pousser les branches vers leur origine après leur création" + --push Push=1 "++\ +pousser les branches vers leur origine après leur création. +c'est l'option par défaut" + -f,--force-create ForceCreate=1 "\ +Avec config, forcer la (re)création du fichier .pman.conf" +) +parse_args "$@"; set -- "${args[@]}" + +# charger la configuration +ensure_gitdir "$chdir" +load_branches all +load_config "$MYNAME" +load_branches current + +# puis faire l'action que l'on nous demande +case "$action" in +show) + show_action "$@" + ;; +init) + git_ensure_cleancheckout + init_action "$@" + ;; +composer_select_profile) + exec "$MYDIR/_pman-$action.php" "$@" + ;; +*) + die "$action: action non implémentée" + ;; +esac diff --git a/wip/pmer b/wip/pmer new file mode 100755 index 0000000..9a7daf8 --- /dev/null +++ b/wip/pmer @@ -0,0 +1,262 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname -- "$0")/../load.sh" || exit 1 +require: git pman pman.conf + +git_cleancheckout_DIRTY="\ +Vous avez des modifications locales. +Enregistrez ces modifications avant de fusionner la branche" + +function show_action() { + local commits + setx commits=_list_commits + if [ -n "$commits" ]; then + if [ $ShowLevel -ge 2 ]; then + { + echo "\ +# Commits à fusionner $SrcBranch --> $DestBranch + +$commits +" + _sd_COLOR=always _show_diff + } | less -eRF + else + einfo "Commits à fusionner $SrcBranch --> $DestBranch" + eecho "$commits" + fi + fi +} + +function ensure_branches() { + [ -n "$SrcBranch" -a -n "$DestBranch" ] || + die "$SrcBranch: Aucune configuration de fusion trouvée pour cette branche" + + array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche source introuvable" + array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche destination introuvable" +} + +function merge_action() { + [ -z "$ShouldPush" ] && enote "\ +L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine" + + enote "\ +Ce script va +- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+ +- pousser les branches modifiées}" + ask_yesno "Voulez-vous continuer?" O || die + + local script=".git/pman-merge.sh" + local -a push_branches delete_branches + local hook + local comment= + local or_die=" || exit 1" + + _mscript_start + _scripta < + AFTER_MERGE_ + AFTER_DELETE_ + BEFORE_PUSH_ + AFTER_PUSH_ +srcType et destType pouvant valoir UPSTREAM, DEVELOP, FEATURE, RELEASE, MAIN, HOTFIX, DIST" + -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" + -O:,--origin Origin= "++\ +origine à partir de laquelle les branches distantes sont considérées" + -B:,--config-branch ConfigBranch= "++\ +branche à partir de laquelle charger la configuration" + -c:,--config-file:CONFIG ConfigFile= "++\ +fichier de configuration des branches. cette option est prioritaire sur --config-branch +par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" + --fake _Fake=1 "++option non documentée" + --keep-script _KeepScript=1 "++option non documentée" + -w,--show '$action=show; inc@ ShowLevel' "\ +lister les modifications qui seraient fusionnées dans la branche destination" + -b,--rebase action=rebase "\ +lancer git rebase -i sur la branche source. cela permet de réordonner les +commits pour nettoyer l'historique avant la fusion" + --merge action=merge "++\ +fusionner la branche source dans la branche destination correspondante. +c'est l'action par défaut" + --tech-merge TechMerge=1 "++option non documentée" + -s:,--squash:COMMIT_MSG SquashMsg= "\ +fusionner les modifications de la branche comme un seul commit. +cette option ne devrait pas être utilisée avec --no-delete" + -n,--no-push Push= "\ +ne pas pousser les branches vers leur origine après la fusion" + --push Push=1 "++\ +pousser les branches vers leur origine après la fusion. +c'est l'option par défaut" + -k,--no-delete Delete= "\ +ne pas supprimer la branche après la fusion dans la destination" + --delete Delete=1 "++\ +supprimer la branche après la fusion dans la destination. +c'est l'option par défaut" + -f,--force-merge ForceMerge=1 "++\ +forcer la fusion pour une branche qui devrait être traitée par prel" + -a:,--after-merge AfterMerge= "\ +évaluer le script spécifié après une fusion *réussie*" +) +parse_args "$@"; set -- "${args[@]}" + +# charger la configuration +ensure_gitdir "$chdir" +load_branches all +load_config "$MYNAME" +load_branches current "$1" + +resolve_should_push quiet + +# puis faire l'action que l'on nous demande +case "$action" in +show) + git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" + ensure_branches + show_action "$@" + ;; +merge) + ShouldDelete=1 + no_merge_msg="$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel" + if [ "$SrcType" == develop ]; then + [ -z "$ForceMerge" ] && die "$no_merge_msg" + [ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$SrcBranch" + elif [ "$SrcType" == release -o "$SrcType" == hotfix ]; then + die "$no_merge_msg" + fi + # n'autoriser la suppression que pour feature + [ "$SrcType" == feature ] || ShouldDelete= + [ -z "$ShouldDelete" ] && Delete= + [ -z "$_Fake" ] && git_ensure_cleancheckout + if array_contains LocalBranches "$SrcBranch"; then + ensure_branches + merge_action "$@" + elif array_contains AllBranches "$SrcBranch"; then + enote "$SrcBranch: une branche du même nom existe dans l'origine" + die "$SrcBranch: branche locale introuvable" + else + die "$SrcBranch: branche introuvable" + fi + ;; +*) + die "$action: action non implémentée" + ;; +esac diff --git a/wip/prel b/wip/prel new file mode 100755 index 0000000..2ec3a31 --- /dev/null +++ b/wip/prel @@ -0,0 +1,292 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname -- "$0")/../load.sh" || exit 1 +require: git pman pman.conf + +git_cleancheckout_DIRTY="\ +Vous avez des modifications locales. +Enregistrez ces modifications avant de créer une release" + +function show_action() { + local commits + setx commits=_list_commits + if [ -n "$commits" ]; then + if [ $ShowLevel -ge 2 ]; then + { + echo "\ +# Commits à fusionner $SrcBranch --> $DestBranch + +$commits +" + _sd_COLOR=always _show_diff + } | less -eRF + else + einfo "Commits à fusionner $SrcBranch --> $DestBranch" + eecho "$commits" + fi + fi +} + +function ensure_branches() { + [ -n "$SrcBranch" -a -n "$DestBranch" ] || + die "$SrcBranch: Aucune configuration de fusion trouvée pour cette branche" + + array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche source introuvable" + array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche destination introuvable" + + Tag="$TAG_PREFIX$Version$TAG_SUFFIX" + local -a tags + setx -a tags=git tag -l "${TAG_PREFIX}*${TAG_SUFFIX}" + if [ -z "$ForceCreate" ]; then + array_contains tags "$Tag" && die "$Tag: le tag correspondant à la version existe déjà" + fi +} + +function create_release_action() { + if [ -n "$ReleaseBranch" ]; then + Version="${ReleaseBranch#$RELEASE}" + Tag="$TAG_PREFIX$Version$TAG_SUFFIX" + merge_release_action "$@"; return $? + elif [ -n "$HotfixBranch" ]; then + Version="${HotfixBranch#$HOTFIX}" + Tag="$TAG_PREFIX$Version$TAG_SUFFIX" + merge_hotfix_action "$@"; return $? + fi + + [ -n "$ManualRelease" ] && ewarn "\ +L'option --no-merge a été forcée puisque ce dépôt ne supporte pas les releases automatiques" + [ -z "$ShouldPush" ] && enote "\ +L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine" + + if [ -z "$Version" -a -n "$CurrentVersion" -a -f VERSION.txt ]; then + Version="$(" + -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" + -O:,--origin Origin= "++\ +origine à partir de laquelle les branches distantes sont considérées" + -B:,--config-branch ConfigBranch= "++\ +branche à partir de laquelle charger la configuration" + -c:,--config-file:CONFIG ConfigFile= "++\ +fichier de configuration des branches. cette option est prioritaire sur --config-branch +par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" + -n,--no-push Push= "\ +ne pas pousser les branches vers leur origine après la fusion" + --push Push=1 "++\ +pousser les branches vers leur origine après la fusion. +c'est l'option par défaut" +) +parse_args "$@"; set -- "${args[@]}" + +# charger la configuration +ensure_gitdir "$chdir" +load_branches all +load_config "$MYNAME" +load_branches current + +branch="$1" +if [ -z "$branch" -a ${#FeatureBranches[*]} -eq 1 ]; then + branch="${FeatureBranches[0]}" +fi +[ -n "$branch" ] || die "Vous devez spécifier la branche à créer" +branch="$FEATURE${branch#$FEATURE}" + +resolve_should_push +git_ensure_cleancheckout + +if array_contains AllBranches "$branch"; then + git checkout -q "$branch" +else + # si la branche source n'existe pas, la créer + args=(--origin "$Origin") + if [ -n "$ConfigFile" ]; then args+=(--config-file "$ConfigFile") + elif [ -n "$ConfigBranch" ]; then args+=(--config-branch "$ConfigBranch") + fi + [ -z "$Push" ] && args+=(--no-push) + exec "$MYDIR/pman" "${args[@]}" "$branch" +fi From 810ead58d634b3630098b7e400e5356520e11aab Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 7 Oct 2025 02:00:52 +0400 Subject: [PATCH 44/91] modifs.mineures sans commentaires --- php/src/cache/CacheFile.php | 49 +++++++++++++++-------------------- php/tests/cache/cacheTest.php | 20 +++++++------- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/php/src/cache/CacheFile.php b/php/src/cache/CacheFile.php index 6107f9f..035fd05 100644 --- a/php/src/cache/CacheFile.php +++ b/php/src/cache/CacheFile.php @@ -232,38 +232,29 @@ class CacheFile extends SharedFile { $this->start = null; $this->duration = null; $this->data = null; - $updateData = true; } if (!$key && !$external) { - if ($updateData) { - # calculer la valeur - try { - if ($source !== null) $data = $source->compute(); - else $data = $this->compute(); - } catch (Exception $e) { - # le fichier n'est pas mis à jour, mais ce n'est pas gênant: lors - # des futurs appels, l'exception continuera d'être lancée ou la - # valeur sera finalement mise à jour - throw $e; - } - } else { - $data = $this->data; + # calculer la valeur + try { + if ($source !== null) $data = $source->compute(); + else $data = $this->compute(); + } catch (Exception $e) { + # le fichier n'est pas mis à jour, mais ce n'est pas gênant: lors + # des futurs appels, l'exception continuera d'être lancée ou la + # valeur sera finalement mise à jour + throw $e; } if ($this->shouldCache($data)) $this->data = $data; else $this->data = $data = null; - } else { - if ($updateData) { - # calculer la valeur - try { - $data = $source->compute(); - } catch (Exception $e) { - # le fichier n'est pas mis à jour, mais ce n'est pas gênant: lors - # des futurs appels, l'exception continuera d'être lancée ou la - # valeur sera finalement mise à jour - throw $e; - } - } else { - $data = $source->load(); + } elseif ($source !== null) { + # calculer la valeur + try { + $data = $source->compute(); + } catch (Exception $e) { + # le fichier n'est pas mis à jour, mais ce n'est pas gênant: lors + # des futurs appels, l'exception continuera d'être lancée ou la + # valeur sera finalement mise à jour + throw $e; } if ($this->shouldCache($data)) { $data = $source->save($data); @@ -272,10 +263,12 @@ class CacheFile extends SharedFile { $source->delete(); $data = null; } + } else { + $data = null; } } elseif (!$key && !$external) { $data = $this->data; - } elseif ($source->exists()) { + } elseif ($source !== null && $source->exists()) { $data = $source->load(); } else { $data = null; diff --git a/php/tests/cache/cacheTest.php b/php/tests/cache/cacheTest.php index 3a16cb0..7c31f37 100644 --- a/php/tests/cache/cacheTest.php +++ b/php/tests/cache/cacheTest.php @@ -11,16 +11,6 @@ class cacheTest extends _TestCase { ["a" => 1, "b" => 2], ]; - function gendata() { - msg::note("gendata"); - foreach (self::DATA as $key => $item) { - msg::info("yield $key"); - yield $key => $item; - sleep(2); - } - msg::note("fin gendata"); - } - function _testRows(iterable $rows, int $expectedCount) { $count = 0; foreach ($rows as $key => $row) { @@ -70,7 +60,15 @@ class cacheTest extends _TestCase { function testGetGenerator() { $this->_testGet("getGenerator", 3, function () { - return $this->gendata(); + return static function () { + msg::note("gendata"); + foreach (self::DATA as $key => $item) { + msg::info("yield $key"); + yield $key => $item; + sleep(2); + } + msg::note("fin gendata"); + }; }); } From c748fed3881f39bfb70bf4d01b10fdb0ae1d3c3d Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 7 Oct 2025 05:34:58 +0400 Subject: [PATCH 45/91] Date et DateTime sont immutable --- php/src/php/time/Date.php | 13 +- php/src/php/time/DateTime.php | 345 ++++----------------- php/src/php/time/Delay.php | 13 +- php/src/php/time/Elapsed.php | 8 +- php/src/php/time/MutableDate.php | 24 ++ php/src/php/time/MutableDateTime.php | 168 ++++++++++ php/src/php/time/_TDateTime.php | 103 ++++++ php/src/php/time/_utils.php | 147 +++++++++ php/tests/php/time/DateTest.php | 8 +- php/tests/php/time/DateTimeTest.php | 36 ++- php/tests/php/time/DelayTest.php | 46 ++- php/tests/php/time/MutableDateTest.php | 87 ++++++ php/tests/php/time/MutableDateTimeTest.php | 121 ++++++++ 13 files changed, 783 insertions(+), 336 deletions(-) create mode 100644 php/src/php/time/MutableDate.php create mode 100644 php/src/php/time/MutableDateTime.php create mode 100644 php/src/php/time/_TDateTime.php create mode 100644 php/src/php/time/_utils.php create mode 100644 php/tests/php/time/MutableDateTest.php create mode 100644 php/tests/php/time/MutableDateTimeTest.php diff --git a/php/src/php/time/Date.php b/php/src/php/time/Date.php index c681e68..69a14c8 100644 --- a/php/src/php/time/Date.php +++ b/php/src/php/time/Date.php @@ -1,7 +1,7 @@ setTime(0, 0); + protected function fix(DateTimeInterface $datetime): \DateTimeInterface { + return $datetime->setTime(0, 0); + } + + /** @return MutableDate|self */ + function clone(bool $mutable=false): DateTimeInterface { + if ($mutable) return new MutableDate($this); + else return clone $this; } function format($format=self::DEFAULT_FORMAT): string { diff --git a/php/src/php/time/DateTime.php b/php/src/php/time/DateTime.php index 438b614..264e945 100644 --- a/php/src/php/time/DateTime.php +++ b/php/src/php/time/DateTime.php @@ -1,10 +1,10 @@ format("H,i,s")); - return $h * 3600 + $m * 60 + $s; - } - - static function _YmdHMSZ_format(\DateTime $datetime): string { - $YmdHMS = $datetime->format("Ymd\\THis"); - $Z = $datetime->format("P"); - if ($Z === "+00:00") $Z = "Z"; - return "$YmdHMS$Z"; - } +class DateTime extends \DateTimeImmutable { + use _TDateTime; const DEFAULT_FORMAT = "d/m/Y H:i:s"; - const INT_FORMATS = [ - "year" => "Y", - "month" => "m", - "day" => "d", - "hour" => "H", - "minute" => "i", - "second" => "s", - "wday" => "N", - "wnum" => "W", - "nbsecs" => [self::class, "_nbsecs_format"], - ]; - const STRING_FORMATS = [ - "timezone" => "P", - "datetime" => "d/m/Y H:i:s", - "date" => "d/m/Y", - "Ymd" => "Ymd", - "YmdHMS" => "Ymd\\THis", - "YmdHMSZ" => [self::class, "_YmdHMSZ_format"], - ]; /** - * corriger une année à deux chiffres qui est située dans le passé et - * retourner l'année à 4 chiffres. + * $datetime est une spécification de date, avec ou sans fuseau horaire * - * par exemple, si l'année courante est 2019, alors: - * - fix_past_year('18') === '2018' - * - fix_past_year('19') === '1919' - * - fix_past_year('20') === '1920' - */ - static function fix_past_year(int $year): int { - if ($year < 100) { - $y = getdate(); $y = $y["year"]; - $r = $y % 100; - $c = $y - $r; - if ($year >= $r) $year += $c - 100; - else $year += $c; - } - return $year; - } - - /** - * corriger une année à deux chiffres et retourner l'année à 4 chiffres. - * l'année charnière entre année passée et année future est 70 + * si $datetime ne contient pas de fuseau horaire, elle est réputée être dans + * le fuseau $timezone, qui est le fuseau local par défaut * - * par exemple, si l'année courante est 2019, alors: - * - fix_past_year('18') === '2018' - * - fix_past_year('19') === '2019' - * - fix_past_year('20') === '2020' - * - fix_past_year('69') === '2069' - * - fix_past_year('70') === '1970' - * - fix_past_year('71') === '1971' + * si $datetime contient un fuseau horaire et si $forceTimezone est vrai, + * alors $datetime est réexprimée dans le fuseau $timezone. + * si $timezone est null alors $forceTimezone vaut vrai par défaut. + * + * datetime | timezone | forceTimezone | résultat + * -----------------|----------|---------------|--------- + * datetime | any | any | datetime+localtz + * datetime+origtz | null | null | datetime+localtz + * datetime+origtz | null | true | datetime+localtz + * datetime+origtz | null | false | datetime+origtz + * datetime+origtz | newtz | null | datetime+origtz + * datetime+origtz | newtz | false | datetime+origtz + * datetime+origtz | newtz | true | datetime+newtz */ - static function fix_any_year(int $year): int { - if ($year < 100) { - $y = intval(date("Y")); - $r = $y % 100; - $c = $y - $r; - if ($year >= 70) $year += $c - 100; - else $year += $c; - } - return $year; - } - - static function fix_z(?string $Z): ?string { - $Z = strtoupper($Z); - str::del_prefix($Z, "+"); - if (preg_match('/^\d{4}$/', $Z)) { - $Z = substr($Z, 0, 2).":".substr($Z, 2); - } - if ($Z === "Z" || $Z === "UTC" || $Z === "00:00") return "UTC"; - return "GMT+$Z"; - } - - function __construct($datetime="now", DateTimeZone $timezone=null, ?bool $forceLocalTimezone=null) { - $forceLocalTimezone ??= $timezone === null; - if ($forceLocalTimezone) { - $setTimezone = $timezone; + function __construct($datetime=null, DateTimeZone $timezone=null, ?bool $forceTimezone=null) { + $resetTimezone = null; + $forceTimezone ??= $timezone === null; + if ($forceTimezone) { + $resetTimezone = $timezone ?? new DateTimeZone(date_default_timezone_get()); $timezone = null; } $datetime ??= "now"; - if ($datetime instanceof \DateTimeInterface) { - $timezone ??= $datetime->getTimezone(); - parent::__construct(); - $this->setTimestamp($datetime->getTimestamp()); - $this->setTimezone($timezone); + if ($datetime instanceof DateTimeImmutable) { + $datetime = \DateTime::createFromImmutable($datetime); + } elseif ($datetime instanceof \DateTime) { + $datetime = clone $datetime; + #XXX sous PHP 8, remplacer les deux commandes ci-dessus par + # DateTime::createFromInterface } elseif (is_int($datetime)) { - parent::__construct("now", $timezone); - $this->setTimestamp($datetime); + $timestamp = $datetime; + $datetime = new \DateTime("now", $timezone); + $datetime->setTimestamp($timestamp); } elseif (is_string($datetime)) { $Y = $H = $Z = null; - if (preg_match(self::DMY_PATTERN, $datetime, $ms)) { + if (preg_match(_utils::DMY_PATTERN, $datetime, $ms)) { $Y = $ms[3] ?? null; - if ($Y !== null) $Y = self::fix_any_year(intval($Y)); + if ($Y !== null) $Y = _utils::fix_any_year(intval($Y)); else $Y = intval(date("Y")); $m = intval($ms[2]); $d = intval($ms[1]); - } elseif (preg_match(self::YMD_PATTERN, $datetime, $ms)) { + } elseif (preg_match(_utils::YMD_PATTERN, $datetime, $ms)) { $Y = $ms[1]; - if (strlen($Y) == 2) $Y = self::fix_any_year(intval($ms[1])); + if (strlen($Y) == 2) $Y = _utils::fix_any_year(intval($ms[1])); else $Y = intval($Y); $m = intval($ms[2]); $d = intval($ms[3]); - } elseif (preg_match(self::DMYHIS_PATTERN, $datetime, $ms)) { + } elseif (preg_match(_utils::DMYHIS_PATTERN, $datetime, $ms)) { $Y = $ms[3]; - if ($Y !== null) $Y = self::fix_any_year(intval($Y)); + if ($Y !== null) $Y = _utils::fix_any_year(intval($Y)); else $Y = intval(date("Y")); $m = intval($ms[2]); $d = intval($ms[1]); $H = intval($ms[4]); $M = intval($ms[5]); $S = intval($ms[6] ?? 0); - } elseif (preg_match(self::YMDHISZ_PATTERN, $datetime, $ms)) { + } elseif (preg_match(_utils::YMDHISZ_PATTERN, $datetime, $ms)) { $Y = $ms[1]; - if (strlen($Y) == 2) $Y = self::fix_any_year(intval($ms[1])); + if (strlen($Y) == 2) $Y = _utils::fix_any_year(intval($ms[1])); else $Y = intval($Y); $m = intval($ms[2]); $d = intval($ms[3]); @@ -281,73 +107,61 @@ class DateTime extends \DateTime { if ($Y !== null) { if ($H === null) $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d); else $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H, $M, $S); - if ($Z !== null) $timezone ??= new DateTimeZone(self::fix_z($Z)); + if ($Z !== null) $timezone = new DateTimeZone(_utils::fix_z($Z)); } - parent::__construct($datetime, $timezone); + $datetime = new \DateTime($datetime, $timezone); - } elseif (is_array($datetime) && ($datetime = self::parse_array($datetime)) !== null) { - $setTimezone = $timezone; - $timezone = null; + } elseif (is_array($datetime) && ($datetime = _utils::parse_array($datetime)) !== null) { [$Y, $m, $d, $H, $M, $S, $Z] = $datetime; if ($H === null && $M === null && $S === null) { $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d); } else { $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H ?? 0, $M ?? 0, $S ?? 0); } - if ($Z !== null) $timezone ??= new DateTimeZone(self::fix_z($Z)); - parent::__construct($datetime, $timezone); + if ($Z !== null) $timezone = new DateTimeZone(_utils::fix_z($Z)); + $datetime = new \DateTime($datetime, $timezone); + } - } else { + if ($datetime instanceof DateTimeInterface) { + if ($resetTimezone !== null) $datetime->setTimezone($resetTimezone); + $datetime = $this->fix($datetime); + parent::__construct($datetime->format("Y-m-d\\TH:i:s.uP")); + } else { throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface"); } - - if ($forceLocalTimezone) { - $setTimezone ??= new DateTimeZone(date_default_timezone_get()); - $this->setTimezone($setTimezone); - } } - function clone(): self { - return clone $this; + protected function fix(DateTimeInterface $datetime): DateTimeInterface { + return $datetime; } - function diff($target, $absolute=false): DateInterval { - return new DateInterval(parent::diff($target, $absolute)); - } - - function format($format=self::DEFAULT_FORMAT): string { - if (array_key_exists($format, self::INT_FORMATS)) { - $format = self::INT_FORMATS[$format]; - } elseif (array_key_exists($format, self::STRING_FORMATS)) { - $format = self::STRING_FORMATS[$format]; - } - if (is_callable($format)) return $format($this); - else return \DateTime::format($format); + /** @return MutableDateTime|self */ + function clone(bool $mutable=false): DateTimeInterface { + if ($mutable) return new MutableDateTime($this); + else return clone $this; } /** * modifier cet objet pour que l'heure soit à 00:00:00 ce qui le rend propice * à l'utilisation comme borne inférieure d'une période */ - function wrapStartOfDay(): self { - $this->setTime(0, 0); - return $this; + function getStartOfDay(): self { + return new static($this->setTime(0, 0)); } /** * modifier cet objet pour que l'heure soit à 23:59:59.999999 ce qui le rend * propice à l'utilisation comme borne supérieure d'une période */ - function wrapEndOfDay(): self { - $this->setTime(23, 59, 59, 999999); - return $this; + function getEndOfDay(): self { + return new static($this->setTime(23, 59, 59, 999999)); } function getPrevDay(int $nbDays=1, bool $skipWeekend=false): self { if ($nbDays == 1 && $skipWeekend && $this->wday == 1) { - $nbdays = 3; + $nbDays = 3; } - return static::with($this->sub(new \DateInterval("P${nbDays}D"))); + return new static($this->sub(new \DateInterval("P${nbDays}D"))); } function getNextDay(int $nbDays=1, bool $skipWeekend=false): self { @@ -355,35 +169,6 @@ class DateTime extends \DateTime { $wday = $this->wday; if ($wday > 5) $nbDays = 8 - $this->wday; } - return static::with($this->add(new \DateInterval("P${nbDays}D"))); - } - - function getElapsedAt(?DateTime $now=null, ?int $resolution=null): string { - return Elapsed::format_at($this, $now, $resolution); - } - - function getElapsedSince(?DateTime $now=null, ?int $resolution=null): string { - return Elapsed::format_since($this, $now, $resolution); - } - - function getElapsedDelay(?DateTime $now=null, ?int $resolution=null): string { - return Elapsed::format_delay($this, $now, $resolution); - } - - function __toString(): string { - return $this->format(); - } - - function __get($name) { - if (array_key_exists($name, self::INT_FORMATS)) { - $format = self::INT_FORMATS[$name]; - if (is_callable($format)) return intval($format($this)); - else return intval($this->format($format)); - } elseif (array_key_exists($name, self::STRING_FORMATS)) { - $format = self::STRING_FORMATS[$name]; - if (is_callable($format)) return $format($this); - else return $this->format($format); - } - throw new InvalidArgumentException("Unknown property $name"); + return new static($this->add(new \DateInterval("P${nbDays}D"))); } } diff --git a/php/src/php/time/Delay.php b/php/src/php/time/Delay.php index c8f52f7..1d11b72 100644 --- a/php/src/php/time/Delay.php +++ b/php/src/php/time/Delay.php @@ -39,8 +39,7 @@ class Delay { "s" => [1, 0], ]; - static function compute_dest(int $x, string $u, ?int $y, ?DateTimeInterface $from): array { - $dest = DateTime::with($from)->clone(); + protected static function compute_dest(int $x, string $u, ?int $y, MutableDateTime $dest): array { $yu = null; switch ($u) { case "w": @@ -89,9 +88,9 @@ class Delay { } function __construct($delay, ?DateTimeInterface $from=null) { - if ($from === null) $from = new DateTime(); + $from = MutableDateTime::with($from)->clone(true); if ($delay === "INF") { - $dest = DateTime::with($from)->clone(); + $dest = $from; $dest->add(new DateInterval("P9999Y")); $repr = "INF"; } elseif (is_int($delay)) { @@ -126,11 +125,11 @@ class Delay { [$this->dest, $this->repr] = $data; } - /** @var DateTime */ + /** @var MutableDateTime */ protected $dest; function getDest(): DateTime { - return $this->dest; + return $this->dest->clone(); } function addDuration($duration) { @@ -157,7 +156,7 @@ class Delay { } protected function _getDiff(?DateTimeInterface $now=null): \DateInterval { - if ($now === null) $now = new DateTime(); + $now ??= new \DateTime(); return $this->dest->diff($now); } diff --git a/php/src/php/time/Elapsed.php b/php/src/php/time/Elapsed.php index 37f22c6..8bd2f8d 100644 --- a/php/src/php/time/Elapsed.php +++ b/php/src/php/time/Elapsed.php @@ -1,6 +1,8 @@ getTimestamp() - $start->getTimestamp(); return (new self($seconds, $resolution))->formatAt(); } - static function format_since(DateTime $start, ?DateTime $now=null, ?int $resolution=null): string { + static function format_since(DateTimeInterface $start, ?DateTimeInterface $now=null, ?int $resolution=null): string { $now ??= new DateTime(); $seconds = $now->getTimestamp() - $start->getTimestamp(); return (new self($seconds, $resolution))->formatSince(); } - static function format_delay(DateTime $start, ?DateTime $now=null, ?int $resolution=null): string { + static function format_delay(DateTimeInterface $start, ?DateTimeInterface $now=null, ?int $resolution=null): string { $now ??= new DateTime(); $seconds = $now->getTimestamp() - $start->getTimestamp(); return (new self($seconds, $resolution))->formatDelay(); diff --git a/php/src/php/time/MutableDate.php b/php/src/php/time/MutableDate.php new file mode 100644 index 0000000..1f01428 --- /dev/null +++ b/php/src/php/time/MutableDate.php @@ -0,0 +1,24 @@ +setTime(0, 0); + } + + /** @return Date|self */ + function clone(bool $mutable=false): DateTimeInterface { + if ($mutable) return clone $this; + else return new Date($this); + } + + function format($format=self::DEFAULT_FORMAT): string { + return parent::format($format); + } +} diff --git a/php/src/php/time/MutableDateTime.php b/php/src/php/time/MutableDateTime.php new file mode 100644 index 0000000..c7e69a7 --- /dev/null +++ b/php/src/php/time/MutableDateTime.php @@ -0,0 +1,168 @@ +getTimezone(); + parent::__construct(); + $this->setTimestamp($datetime->getTimestamp()); + $this->setTimezone($timezone); + + } elseif (is_int($datetime)) { + parent::__construct("now", $timezone); + $this->setTimestamp($datetime); + + } elseif (is_string($datetime)) { + $Y = $H = $Z = null; + if (preg_match(_utils::DMY_PATTERN, $datetime, $ms)) { + $Y = $ms[3] ?? null; + if ($Y !== null) $Y = _utils::fix_any_year(intval($Y)); + else $Y = intval(date("Y")); + $m = intval($ms[2]); + $d = intval($ms[1]); + } elseif (preg_match(_utils::YMD_PATTERN, $datetime, $ms)) { + $Y = $ms[1]; + if (strlen($Y) == 2) $Y = _utils::fix_any_year(intval($ms[1])); + else $Y = intval($Y); + $m = intval($ms[2]); + $d = intval($ms[3]); + } elseif (preg_match(_utils::DMYHIS_PATTERN, $datetime, $ms)) { + $Y = $ms[3]; + if ($Y !== null) $Y = _utils::fix_any_year(intval($Y)); + else $Y = intval(date("Y")); + $m = intval($ms[2]); + $d = intval($ms[1]); + $H = intval($ms[4]); + $M = intval($ms[5]); + $S = intval($ms[6] ?? 0); + } elseif (preg_match(_utils::YMDHISZ_PATTERN, $datetime, $ms)) { + $Y = $ms[1]; + if (strlen($Y) == 2) $Y = _utils::fix_any_year(intval($ms[1])); + else $Y = intval($Y); + $m = intval($ms[2]); + $d = intval($ms[3]); + $H = intval($ms[4]); + $M = intval($ms[5]); + $S = intval($ms[6] ?? 0); + $Z = $ms[7] ?? null; + } + if ($Y !== null) { + if ($H === null) $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d); + else $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H, $M, $S); + if ($Z !== null) $timezone = new DateTimeZone(_utils::fix_z($Z)); + } + parent::__construct($datetime, $timezone); + + } elseif (is_array($datetime) && ($datetime = _utils::parse_array($datetime)) !== null) { + [$Y, $m, $d, $H, $M, $S, $Z] = $datetime; + if ($H === null && $M === null && $S === null) { + $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d); + } else { + $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H ?? 0, $M ?? 0, $S ?? 0); + } + if ($Z !== null) $timezone = new DateTimeZone(_utils::fix_z($Z)); + parent::__construct($datetime, $timezone); + + } else { + throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface"); + } + + if ($resetTimezone !== null) $this->setTimezone($resetTimezone); + } + + /** @return DateTime|self */ + function clone(bool $mutable=false): DateTimeInterface { + if ($mutable) return clone $this; + else return new DateTime($this); + } + + /** + * modifier cet objet pour que l'heure soit à 00:00:00 ce qui le rend propice + * à l'utilisation comme borne inférieure d'une période + */ + function setStartOfDay(): self { + $this->setTime(0, 0); + return $this; + } + + /** + * modifier cet objet pour que l'heure soit à 23:59:59.999999 ce qui le rend + * propice à l'utilisation comme borne supérieure d'une période + */ + function setEndOfDay(): self { + $this->setTime(23, 59, 59, 999999); + return $this; + } + + function setPrevDay(int $nbDays=1, bool $skipWeekend=false): self { + if ($nbDays == 1 && $skipWeekend && $this->wday == 1) { + $nbDays = 3; + } + $this->sub(new \DateInterval("P${nbDays}D")); + return $this; + } + + function setNextDay(int $nbDays=1, bool $skipWeekend=false): self { + if ($nbDays == 1 && $skipWeekend) { + $wday = $this->wday; + if ($wday > 5) $nbDays = 8 - $this->wday; + } + $this->add(new \DateInterval("P${nbDays}D")); + return $this; + } +} diff --git a/php/src/php/time/_TDateTime.php b/php/src/php/time/_TDateTime.php new file mode 100644 index 0000000..755ec31 --- /dev/null +++ b/php/src/php/time/_TDateTime.php @@ -0,0 +1,103 @@ +format(); + } + + function __get($name) { + if (array_key_exists($name, _utils::INT_FORMATS)) { + $format = _utils::INT_FORMATS[$name]; + if (is_callable($format)) return intval($format($this)); + else return intval($this->format($format)); + } elseif (array_key_exists($name, _utils::STRING_FORMATS)) { + $format = _utils::STRING_FORMATS[$name]; + if (is_callable($format)) return $format($this); + else return $this->format($format); + } + throw new InvalidArgumentException("Unknown property $name"); + } + + function getElapsedAt(?DateTimeInterface $now=null, ?int $resolution=null): string { + return Elapsed::format_at($this, $now, $resolution); + } + + function getElapsedSince(?DateTimeInterface $now=null, ?int $resolution=null): string { + return Elapsed::format_since($this, $now, $resolution); + } + + function getElapsedDelay(?DateTimeInterface $now=null, ?int $resolution=null): string { + return Elapsed::format_delay($this, $now, $resolution); + } +} diff --git a/php/src/php/time/_utils.php b/php/src/php/time/_utils.php new file mode 100644 index 0000000..aeec99e --- /dev/null +++ b/php/src/php/time/_utils.php @@ -0,0 +1,147 @@ +format("H,i,s")); + return $h * 3600 + $m * 60 + $s; + } + + static function _YmdHMSZ_format(\DateTimeInterface $datetime): string { + $YmdHMS = $datetime->format("Ymd\\THis"); + $Z = $datetime->format("P"); + if ($Z === "+00:00") $Z = "Z"; + return "$YmdHMS$Z"; + } + + const INT_FORMATS = [ + "year" => "Y", + "month" => "m", + "day" => "d", + "hour" => "H", + "minute" => "i", + "second" => "s", + "wday" => "N", + "wnum" => "W", + "nbsecs" => [self::class, "_nbsecs_format"], + ]; + const STRING_FORMATS = [ + "timezone" => "P", + "datetime" => "d/m/Y H:i:s", + "date" => "d/m/Y", + "Ymd" => "Ymd", + "YmdHMS" => "Ymd\\THis", + "YmdHMSZ" => [self::class, "_YmdHMSZ_format"], + ]; + + /** + * corriger une année à deux chiffres qui est située dans le passé et + * retourner l'année à 4 chiffres. + * + * par exemple, si l'année courante est 2019, alors: + * - fix_past_year('18') === '2018' + * - fix_past_year('19') === '1919' + * - fix_past_year('20') === '1920' + */ + static function fix_past_year(int $year): int { + if ($year < 100) { + $y = getdate(); + $y = $y["year"]; + $r = $y % 100; + $c = $y - $r; + if ($year >= $r) $year += $c - 100; + else $year += $c; + } + return $year; + } + + /** + * corriger une année à deux chiffres et retourner l'année à 4 chiffres. + * l'année charnière entre année passée et année future est 70 + * + * par exemple, si l'année courante est 2019, alors: + * - fix_past_year('18') === '2018' + * - fix_past_year('19') === '2019' + * - fix_past_year('20') === '2020' + * - fix_past_year('69') === '2069' + * - fix_past_year('70') === '1970' + * - fix_past_year('71') === '1971' + */ + static function fix_any_year(int $year): int { + if ($year < 100) { + $y = intval(date("Y")); + $r = $y % 100; + $c = $y - $r; + if ($year >= 70) $year += $c - 100; + else $year += $c; + } + return $year; + } + + static function fix_z(?string $Z): ?string { + $Z = strtoupper($Z); + str::del_prefix($Z, "+"); + if (preg_match('/^\d{4}$/', $Z)) { + $Z = substr($Z, 0, 2) . ":" . substr($Z, 2); + } + if ($Z === "Z" || $Z === "UTC" || $Z === "00:00") return "UTC"; + return "GMT+$Z"; + } + + static function get_value(array $datetime, ?string $key, ?string $k, ?int $index) { + $value = null; + if ($value === null && $key !== null) $value = $datetime[$key] ?? null; + if ($value === null && $k !== null) $value = $datetime[$k] ?? null; + if ($value === null && $index !== null) $value = $datetime[$index] ?? null; + return $value; + } + + static function parse_int(array $datetime, ?string $key, ?string $k, ?int $index, ?int &$part, bool $required = true, ?int $default = null): bool { + $part = null; + $value = self::get_value($datetime, $key, $k, $index); + if ($value === null) { + if ($required && $default === null) return false; + $part = $default; + return true; + } + if (is_numeric($value)) { + $part = intval($value); + return true; + } + return false; + } + + static function parse_str(array $datetime, ?string $key, ?string $k, ?int $index, ?string &$part, bool $required = true, ?string $default = null): bool { + $part = null; + $value = self::get_value($datetime, $key, $k, $index); + if ($value === null) { + if ($required && $default === null) return false; + $part = $default; + return true; + } + if (is_string($value)) { + $part = $value; + return true; + } + return false; + } + + static function parse_array(array $datetime): ?array { + if (!self::parse_int($datetime, "year", "Y", 0, $year)) return null; + if (!self::parse_int($datetime, "month", "m", 1, $month)) return null; + if (!self::parse_int($datetime, "day", "d", 2, $day)) return null; + self::parse_int($datetime, "hour", "H", 3, $hour, false); + self::parse_int($datetime, "minute", "M", 4, $minute, false); + self::parse_int($datetime, "second", "S", 5, $second, false); + self::parse_str($datetime, "tz", null, 6, $tz, false); + return [$year, $month, $day, $hour, $minute, $second, $tz]; + } +} diff --git a/php/tests/php/time/DateTest.php b/php/tests/php/time/DateTest.php index 458e42b..4656fc5 100644 --- a/php/tests/php/time/DateTest.php +++ b/php/tests/php/time/DateTest.php @@ -29,13 +29,15 @@ class DateTest extends TestCase { function testClone() { $date = self::dt("now"); + $clone = $date->clone(true); + self::assertInstanceOf(MutableDate::class, $clone); $clone = $date->clone(); - self::assertInstanceOf(DateTime::class, $clone); + self::assertInstanceOf(Date::class, $clone); } function testConstruct() { - $y = date("Y"); - self::assertSame("05/04/$y", strval(new Date("5/4"))); + $Y = date("Y"); + self::assertSame("05/04/$Y", strval(new Date("5/4"))); self::assertSame("05/04/2024", strval(new Date("5/4/24"))); self::assertSame("05/04/2024", strval(new Date("5/4/2024"))); self::assertSame("05/04/2024", strval(new Date("05/04/2024"))); diff --git a/php/tests/php/time/DateTimeTest.php b/php/tests/php/time/DateTimeTest.php index 67cc9de..0880c67 100644 --- a/php/tests/php/time/DateTimeTest.php +++ b/php/tests/php/time/DateTimeTest.php @@ -5,12 +5,8 @@ use DateTimeZone; use nulib\tests\TestCase; class DateTimeTest extends TestCase { - protected static function dt(string $datetime): DateTime { - return new DateTime($datetime, new DateTimeZone("Indian/Reunion")); - } - function testDateTime() { - $date = self::dt("2024-04-05 09:15:23"); + $date = new DateTime("2024-04-05 09:15:23"); self::assertEquals("05/04/2024 09:15:23", $date->format()); self::assertEquals("05/04/2024 09:15:23", strval($date)); @@ -31,24 +27,36 @@ class DateTimeTest extends TestCase { } function testDateTimeZ() { - $date = new DateTime("20240405T091523Z"); - self::assertSame("20240405T131523", $date->YmdHMS); - self::assertSame("20240405T131523+04:00", $date->YmdHMSZ); - # comme on spécifie la timezone, la valeur Z est ignorée - $date = new DateTime("20240405T091523Z", new DateTimeZone("Indian/Reunion")); - self::assertSame("20240405T091523", $date->YmdHMS); + $date = new DateTime("20240405T091523"); self::assertSame("20240405T091523+04:00", $date->YmdHMSZ); + + $date = new DateTime("20240405T091523+02:00", null, null); + self::assertSame("20240405T111523+04:00", $date->YmdHMSZ); + $date = new DateTime("20240405T091523+02:00", null, true); + self::assertSame("20240405T111523+04:00", $date->YmdHMSZ); + $date = new DateTime("20240405T091523+02:00", null, false); + self::assertSame("20240405T091523+02:00", $date->YmdHMSZ); + + $newtz = new DateTimeZone("+06:00"); + $date = new DateTime("20240405T091523+02:00", $newtz, null); + self::assertSame("20240405T091523+02:00", $date->YmdHMSZ); + $date = new DateTime("20240405T091523+02:00", $newtz, false); + self::assertSame("20240405T091523+02:00", $date->YmdHMSZ); + $date = new DateTime("20240405T091523+02:00", $newtz, true); + self::assertSame("20240405T131523+06:00", $date->YmdHMSZ); } function testClone() { - $date = self::dt("now"); + $date = new DateTime("now"); + $clone = $date->clone(true); + self::assertInstanceOf(MutableDateTime::class, $clone); $clone = $date->clone(); self::assertInstanceOf(DateTime::class, $clone); } function testConstruct() { - $y = date("Y"); - self::assertSame("05/04/$y 00:00:00", strval(new DateTime("5/4"))); + $Y = date("Y"); + self::assertSame("05/04/$Y 00:00:00", strval(new DateTime("5/4"))); self::assertSame("05/04/2024 00:00:00", strval(new DateTime("5/4/24"))); self::assertSame("05/04/2024 00:00:00", strval(new DateTime("5/4/2024"))); self::assertSame("05/04/2024 00:00:00", strval(new DateTime("05/04/2024"))); diff --git a/php/tests/php/time/DelayTest.php b/php/tests/php/time/DelayTest.php index 132bc4d..b86f6af 100644 --- a/php/tests/php/time/DelayTest.php +++ b/php/tests/php/time/DelayTest.php @@ -5,72 +5,68 @@ use DateTimeZone; use nulib\tests\TestCase; class DelayTest extends TestCase { - protected static function dt(string $datetime): DateTime { - return new DateTime($datetime, new DateTimeZone("Indian/Reunion")); - } - function testDelay() { - $from = self::dt("2024-04-05 09:15:23"); + $from = new MutableDateTime("2024-04-05 09:15:23"); $delay = new Delay(10, $from); - self::assertEquals(self::dt("2024-04-05 09:15:33"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 09:15:33"), $delay->getDest()); $delay = new Delay("10", $from); - self::assertEquals(self::dt("2024-04-05 09:15:33"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 09:15:33"), $delay->getDest()); $delay = new Delay("10s", $from); - self::assertEquals(self::dt("2024-04-05 09:15:33"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 09:15:33"), $delay->getDest()); $delay = new Delay("s", $from); - self::assertEquals(self::dt("2024-04-05 09:15:24"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 09:15:24"), $delay->getDest()); $delay = new Delay("5m", $from); - self::assertEquals(self::dt("2024-04-05 09:20:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 09:20:00"), $delay->getDest()); $delay = new Delay("5m0", $from); - self::assertEquals(self::dt("2024-04-05 09:20:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 09:20:00"), $delay->getDest()); $delay = new Delay("5m2", $from); - self::assertEquals(self::dt("2024-04-05 09:20:02"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 09:20:02"), $delay->getDest()); $delay = new Delay("m", $from); - self::assertEquals(self::dt("2024-04-05 09:16:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 09:16:00"), $delay->getDest()); $delay = new Delay("5h", $from); - self::assertEquals(self::dt("2024-04-05 14:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 14:00:00"), $delay->getDest()); $delay = new Delay("5h0", $from); - self::assertEquals(self::dt("2024-04-05 14:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 14:00:00"), $delay->getDest()); $delay = new Delay("5h2", $from); - self::assertEquals(self::dt("2024-04-05 14:02:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 14:02:00"), $delay->getDest()); $delay = new Delay("h", $from); - self::assertEquals(self::dt("2024-04-05 10:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-05 10:00:00"), $delay->getDest()); $delay = new Delay("5d", $from); - self::assertEquals(self::dt("2024-04-10 05:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-10 05:00:00"), $delay->getDest()); $delay = new Delay("5d2", $from); - self::assertEquals(self::dt("2024-04-10 02:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-10 02:00:00"), $delay->getDest()); $delay = new Delay("5d0", $from); - self::assertEquals(self::dt("2024-04-10 00:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-10 00:00:00"), $delay->getDest()); $delay = new Delay("d", $from); - self::assertEquals(self::dt("2024-04-06 05:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-06 05:00:00"), $delay->getDest()); $delay = new Delay("2w", $from); - self::assertEquals(self::dt("2024-04-21 05:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-21 05:00:00"), $delay->getDest()); $delay = new Delay("2w2", $from); - self::assertEquals(self::dt("2024-04-21 02:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-21 02:00:00"), $delay->getDest()); $delay = new Delay("2w0", $from); - self::assertEquals(self::dt("2024-04-21 00:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-21 00:00:00"), $delay->getDest()); $delay = new Delay("w", $from); - self::assertEquals(self::dt("2024-04-07 05:00:00"), $delay->getDest()); + self::assertEquals(new MutableDateTime("2024-04-07 05:00:00"), $delay->getDest()); } function testElapsed() { diff --git a/php/tests/php/time/MutableDateTest.php b/php/tests/php/time/MutableDateTest.php new file mode 100644 index 0000000..26f425d --- /dev/null +++ b/php/tests/php/time/MutableDateTest.php @@ -0,0 +1,87 @@ +format()); + self::assertSame("05/04/2024", strval($date)); + self::assertSame(2024, $date->year); + self::assertSame(4, $date->month); + self::assertSame(5, $date->day); + self::assertSame(0, $date->hour); + self::assertSame(0, $date->minute); + self::assertSame(0, $date->second); + self::assertSame(5, $date->wday); + self::assertSame(14, $date->wnum); + self::assertSame("+04:00", $date->timezone); + self::assertSame("05/04/2024 00:00:00", $date->datetime); + self::assertSame("05/04/2024", $date->date); + } + + function testClone() { + $date = self::dt("now"); + $clone = $date->clone(true); + self::assertInstanceOf(MutableDate::class, $clone); + $clone = $date->clone(); + self::assertInstanceOf(Date::class, $clone); + } + + function testConstruct() { + $Y = date("Y"); + self::assertSame("05/04/$Y", strval(new MutableDate("5/4"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/24"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024"))); + self::assertSame("05/04/2024", strval(new MutableDate("05/04/2024"))); + self::assertSame("05/04/2024", strval(new MutableDate("20240405"))); + self::assertSame("05/04/2024", strval(new MutableDate("240405"))); + self::assertSame("05/04/2024", strval(new MutableDate("20240405T091523"))); + self::assertSame("05/04/2024", strval(new MutableDate("20240405T091523Z"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9:15:23"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9.15.23"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9:15"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9.15"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 9h15"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 09:15:23"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 09:15"))); + self::assertSame("05/04/2024", strval(new MutableDate("5/4/2024 09h15"))); + } + + function testCompare() { + $a = new MutableDate("10/02/2024"); + $b = new MutableDate("15/02/2024"); + $c = new MutableDate("20/02/2024"); + $a2 = new MutableDate("10/02/2024"); + $b2 = new MutableDate("15/02/2024"); + $c2 = new MutableDate("20/02/2024"); + + self::assertTrue($a == $a2); + self::assertFalse($a === $a2); + self::assertTrue($b == $b2); + self::assertTrue($c == $c2); + + self::assertFalse($a < $a); + self::assertTrue($a < $b); + self::assertTrue($a < $c); + + self::assertTrue($a <= $a); + self::assertTrue($a <= $b); + self::assertTrue($a <= $c); + + self::assertFalse($c > $c); + self::assertTrue($c > $b); + self::assertTrue($c > $a); + + self::assertTrue($c >= $c); + self::assertTrue($c >= $b); + self::assertTrue($c >= $a); + } +} diff --git a/php/tests/php/time/MutableDateTimeTest.php b/php/tests/php/time/MutableDateTimeTest.php new file mode 100644 index 0000000..a83f129 --- /dev/null +++ b/php/tests/php/time/MutableDateTimeTest.php @@ -0,0 +1,121 @@ +format()); + self::assertEquals("05/04/2024 09:15:23", strval($date)); + self::assertSame(2024, $date->year); + self::assertSame(4, $date->month); + self::assertSame(5, $date->day); + self::assertSame(9, $date->hour); + self::assertSame(15, $date->minute); + self::assertSame(23, $date->second); + self::assertSame(5, $date->wday); + self::assertSame(14, $date->wnum); + self::assertEquals("+04:00", $date->timezone); + self::assertSame("05/04/2024 09:15:23", $date->datetime); + self::assertSame("05/04/2024", $date->date); + self::assertSame("20240405", $date->Ymd); + self::assertSame("20240405T091523", $date->YmdHMS); + self::assertSame("20240405T091523+04:00", $date->YmdHMSZ); + } + + function testDateTimeZ() { + $date = new MutableDateTime("20240405T091523"); + self::assertSame("20240405T091523+04:00", $date->YmdHMSZ); + + $date = new MutableDateTime("20240405T091523+02:00", null, null); + self::assertSame("20240405T111523+04:00", $date->YmdHMSZ); + $date = new MutableDateTime("20240405T091523+02:00", null, true); + self::assertSame("20240405T111523+04:00", $date->YmdHMSZ); + $date = new MutableDateTime("20240405T091523+02:00", null, false); + self::assertSame("20240405T091523+02:00", $date->YmdHMSZ); + + $newtz = new DateTimeZone("+06:00"); + $date = new MutableDateTime("20240405T091523+02:00", $newtz, null); + self::assertSame("20240405T091523+02:00", $date->YmdHMSZ); + $date = new MutableDateTime("20240405T091523+02:00", $newtz, false); + self::assertSame("20240405T091523+02:00", $date->YmdHMSZ); + $date = new MutableDateTime("20240405T091523+02:00", $newtz, true); + self::assertSame("20240405T131523+06:00", $date->YmdHMSZ); + } + + function testClone() { + $date = new MutableDateTime("now"); + $clone = $date->clone(true); + self::assertInstanceOf(MutableDateTime::class, $clone); + $clone = $date->clone(); + self::assertInstanceOf(DateTime::class, $clone); + } + + function testConstruct() { + $Y = date("Y"); + self::assertSame("05/04/$Y 00:00:00", strval(new MutableDateTime("5/4"))); + self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("5/4/24"))); + self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("5/4/2024"))); + self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("05/04/2024"))); + self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("20240405"))); + self::assertSame("05/04/2024 00:00:00", strval(new MutableDateTime("240405"))); + self::assertSame("05/04/2024 09:15:23", strval(new MutableDateTime("20240405T091523"))); + self::assertSame("05/04/2024 13:15:23", strval(new MutableDateTime("20240405T091523Z"))); + self::assertSame("05/04/2024 09:15:23", strval(new MutableDateTime("5/4/2024 9:15:23"))); + self::assertSame("05/04/2024 09:15:23", strval(new MutableDateTime("5/4/2024 9.15.23"))); + self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 9:15"))); + self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 9.15"))); + self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 9h15"))); + self::assertSame("05/04/2024 09:15:23", strval(new MutableDateTime("5/4/2024 09:15:23"))); + self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 09:15"))); + self::assertSame("05/04/2024 09:15:00", strval(new MutableDateTime("5/4/2024 09h15"))); + } + + function testCompare() { + $a = new MutableDateTime("10/02/2024"); + $a2 = new MutableDateTime("10/02/2024 8:30"); + $a3 = new MutableDateTime("10/02/2024 15:45"); + $b = new MutableDateTime("15/02/2024"); + $b2 = new MutableDateTime("15/02/2024 8:30"); + $b3 = new MutableDateTime("15/02/2024 15:45"); + $x = new MutableDateTime("10/02/2024"); + $x2 = new MutableDateTime("10/02/2024 8:30"); + $x3 = new MutableDateTime("10/02/2024 15:45"); + + self::assertTrue($a == $x); + self::assertFalse($a === $x); + self::assertTrue($a2 == $x2); + self::assertTrue($a3 == $x3); + + self::assertFalse($a < $a); + self::assertTrue($a < $a2); + self::assertTrue($a < $a3); + self::assertTrue($a < $b); + self::assertTrue($a < $b2); + self::assertTrue($a < $b3); + + self::assertTrue($a <= $a); + self::assertTrue($a <= $a2); + self::assertTrue($a <= $a3); + self::assertTrue($a <= $b); + self::assertTrue($a <= $b2); + self::assertTrue($a <= $b3); + + self::assertTrue($b > $a); + self::assertTrue($b > $a2); + self::assertTrue($b > $a3); + self::assertFalse($b > $b); + self::assertFalse($b > $b2); + self::assertFalse($b > $b3); + + self::assertTrue($b >= $a); + self::assertTrue($b >= $a2); + self::assertTrue($b >= $a3); + self::assertTrue($b >= $b); + self::assertFalse($b >= $b2); + self::assertFalse($b >= $b3); + } +} From ee058e00cdcf3be71d9067243004f24e114681a1 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 7 Oct 2025 08:20:59 +0400 Subject: [PATCH 46/91] modifs.mineures sans commentaires --- php/src/php/time/MutableDateTime.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/php/src/php/time/MutableDateTime.php b/php/src/php/time/MutableDateTime.php index c7e69a7..1083444 100644 --- a/php/src/php/time/MutableDateTime.php +++ b/php/src/php/time/MutableDateTime.php @@ -139,6 +139,9 @@ class MutableDateTime extends \DateTime { $this->setTime(0, 0); return $this; } + function getStartOfDay(): self { + return $this->clone(true)->setStartOfDay(); + } /** * modifier cet objet pour que l'heure soit à 23:59:59.999999 ce qui le rend @@ -148,6 +151,9 @@ class MutableDateTime extends \DateTime { $this->setTime(23, 59, 59, 999999); return $this; } + function getEndOfDay(): self { + return $this->clone(true)->setEndOfDay(); + } function setPrevDay(int $nbDays=1, bool $skipWeekend=false): self { if ($nbDays == 1 && $skipWeekend && $this->wday == 1) { @@ -156,6 +162,9 @@ class MutableDateTime extends \DateTime { $this->sub(new \DateInterval("P${nbDays}D")); return $this; } + function getPrevDay(int $nbDays=1, bool $skipWeekend=false): self { + return $this->clone(true)->setPrevDay($nbDays, $skipWeekend); + } function setNextDay(int $nbDays=1, bool $skipWeekend=false): self { if ($nbDays == 1 && $skipWeekend) { @@ -165,4 +174,7 @@ class MutableDateTime extends \DateTime { $this->add(new \DateInterval("P${nbDays}D")); return $this; } + function getNextDay(int $nbDays=1, bool $skipWeekend=false): self { + return $this->clone(true)->setNextDay($nbDays, $skipWeekend); + } } From 75c06e7038dd613ffcd4fe67242f773ccc89aaa0 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 8 Oct 2025 09:04:31 +0400 Subject: [PATCH 47/91] ajouter le support des articles --- php/src/text/Word.php | 77 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/php/src/text/Word.php b/php/src/text/Word.php index a91b03b..fb5851c 100644 --- a/php/src/text/Word.php +++ b/php/src/text/Word.php @@ -28,17 +28,23 @@ use nulib\ValueException; */ class Word { /** @var bool le mot est-il féminin? */ - private $fem; + private bool $fem; + /** @var string article "aucun", "aucune" */ + private ?string $aucun; + /** @var string article "un", "une" */ + private ?string $un; /** @var string article "le", "la", "l'" */ - private $le; + private ?string $le; /** @var string article "ce", "cet", "cette" */ - private $ce; + private ?string $ce; + /** @var string article "de", "d'" */ + private ?string $de; /** @var string article "du", "de la", "de l'" */ - private $du; + private ?string $du; /** @var string article "au", "à la", "à l'" */ - private $au; + private ?string $au; /** @var string le mot sans article */ - private $w; + private string $w; function __construct(string $spec, bool $adjective=false) { if (preg_match('/^f([eé]m(inin)?)?\s*:\s*/iu', $spec, $ms)) { @@ -57,28 +63,40 @@ class Word { $fem = null; } if (preg_match('/^l\'\s*/i', $spec, $ms) && $fem !== null) { + $aucun = $fem? "aucune ": "aucun "; + $un = $fem? "une ": "un "; $le = "l'"; $ce = "cet "; + $de = "d'"; $du = "de l'"; $au = "à l'"; $spec = substr($spec, strlen($ms[0])); } elseif (preg_match('/^la\s+/i', $spec, $ms)) { $fem = true; + $aucun = "aucune "; + $un = "une "; $le = "la "; $ce = "cette "; + $de = "de "; $du = "de la "; $au = "à la "; $spec = substr($spec, strlen($ms[0])); } elseif (preg_match('/^le\s+/i', $spec, $ms)) { $fem = false; + $aucun = "aucun "; + $un = "un "; $le = "le "; $ce = "ce "; + $de = "de "; $du = "du "; $au = "au "; $spec = substr($spec, strlen($ms[0])); } else { + $aucun = null; + $un = null; $le = null; $ce = null; + $de = null; $du = null; $au = null; } @@ -86,13 +104,16 @@ class Word { # si c'est un nom, il faut l'article et le genre if ($fem === null) { throw new ValueException("Vous devez spécifier le genre du nom"); - } elseif ($le === null || $du === null || $au === null) { + } elseif ($le === null) { throw new ValueException("Vous devez spécifier l'article du nom"); } } $this->fem = $fem; + $this->aucun = $aucun; + $this->un = $un; $this->le = $le; $this->ce = $ce; + $this->de = $de; $this->du = $du; $this->au = $au; $this->w = $spec; @@ -168,15 +189,25 @@ class Word { return "$amount/$max ".$this->w($amount); } + function pronom(): string { + return $this->fem? "elle": "il"; + } + + function articleAucun(): ?string { + return $this->un; + } + + function articleUn(): ?string { + return $this->un; + } + /** retourner le mot avec l'article indéfini et la quantité */ function un(int $amount=1): string { $abs_amount = abs($amount); if ($abs_amount == 0) { - $aucun = $this->fem? "aucune ": "aucun "; - return $aucun.$this->w($amount); + return $this->aucun.$this->w($amount); } elseif ($abs_amount == 1) { - $un = $this->fem? "une ": "un "; - return $un.$this->w($amount); + return $this->un.$this->w($amount); } else { return "les $amount ".$this->w($amount); } @@ -193,6 +224,10 @@ class Word { } } + function articleLe(): ?string { + return $this->le; + } + function le(int $amount=1): string { $abs_amount = abs($amount); if ($abs_amount == 0) { @@ -214,6 +249,10 @@ class Word { } } + function articleCe(): string { + return $this->ce; + } + function ce(int $amount=1): string { $abs_amount = abs($amount); if ($abs_amount == 0) { @@ -235,6 +274,18 @@ class Word { } } + function articleDe(): string { + return $this->de; + } + + function _de(int $amount=1): string { + return $this->de.$this->w($amount); + } + + function articleDu(): string { + return $this->du; + } + function du(int $amount=1): string { $abs_amount = abs($amount); if ($abs_amount == 0) { @@ -256,6 +307,10 @@ class Word { } } + function articleAu(): string { + return $this->au; + } + function au(int $amount=1): string { $abs_amount = abs($amount); if ($abs_amount == 0) { From cf30ff638650d137a2d0048e3d63b0c6fb93210e Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 8 Oct 2025 09:06:03 +0400 Subject: [PATCH 48/91] =?UTF-8?q?exceptions=20normalis=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/src/exceptions.php | 105 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 php/src/exceptions.php diff --git a/php/src/exceptions.php b/php/src/exceptions.php new file mode 100644 index 0000000..afad896 --- /dev/null +++ b/php/src/exceptions.php @@ -0,0 +1,105 @@ +_ce(); + $msg .= " est invalide"; + if ($reason) $msg .= ": $reason"; + return new UserException($msg); + } + + /** + * spécialisation de {@link self::invalid_value()} qui permet d'indiquer les + * types attendus + */ + static function invalid_type($value, $expectedTypes=null): UserException { + $pronom = self::word()->pronom(); + $expectedTypes = cl::withn($expectedTypes); + if (!$expectedTypes) { + $reason = null; + } elseif (count($expectedTypes) == 1) { + $reason = "$pronom doit être du type suivant: "; + } else { + $reason = "$pronom doit être d'un des types suivants: "; + } + $reason .= implode(", ", $expectedTypes); + return self::invalid_value($value, $reason); + } + + static function invalid_format($value, $expectedFormats=null): UserException { + $pronom = self::word()->pronom(); + $expectedFormats = cl::withn($expectedFormats); + if (!$expectedFormats) { + $reason = null; + } elseif (count($expectedFormats) == 1) { + $reason = "$pronom doit être au format suivant: "; + } else { + $reason = "$pronom doit être dans l'un des formats suivants: "; + } + $reason .= implode(", ", $expectedFormats); + return self::invalid_value($value, $reason); + } + + static function out_of_range($value, ?int $min=null, ?int $max=null): UserException { + $pronom = self::word()->pronom(); + if ($min !== null && $max !== null) { + $reason = "$pronom doit être compris entre $min et $max"; + } else if ($min !== null) { + $reason = "$pronom doit être supérieur à $min"; + } elseif ($max !== null) { + $reason = "$pronom doit être inférieur à $max"; + } else { + $reason = null; + } + return self::invalid_value($value, $reason); + } + + static function null_value(?string $reason=null): UserException { + $msg = self::word()->_ce(); + $msg .= " ne doit pas être nulle"; + if ($reason) $msg .= ": $reason"; + return new UserException($msg); + } + + /** + * indiquer qu'une valeur est manquante + */ + static function missing_value(?int $amout=null, ?string $reason=null): UserException { + $msg = "il manque "; + if ($amout !== null) { + $msg .= self::word()->q($amout); + } else { + $msg .= self::word()->_le(); + } + if ($reason) $msg .= ": $reason"; + return new UserException($msg); + } + + /** + * indiquer qu'une valeur est en trop + */ + static function unexpected_value(?string $reason=null): UserException { + $msg = "il y a trop "; + $msg .= self::word()->_de(2); + if ($reason) $msg .= ": $reason"; + return new UserException($msg); + } +} From 42992c84d48df716a819ffb1435e5a1636a981ba Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 8 Oct 2025 12:42:51 +0400 Subject: [PATCH 49/91] =?UTF-8?q?r=C3=A9organiser=20les=20exceptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/cli/pman/ComposerFile.php | 5 +- php/cli/pman/ComposerPmanFile.php | 9 +- php/src/AccessException.php | 12 +- php/src/ExceptionShadow.php | 37 ++-- php/src/ExitError.php | 3 +- php/src/StateException.php | 4 +- php/src/UserException.php | 95 +++-------- php/src/ValueException.php | 68 -------- php/src/app/app.php | 6 +- php/src/app/args/AbstractArgsParser.php | 10 +- php/src/app/args/Aodef.php | 16 +- php/src/app/args/Aogroup.php | 2 +- php/src/app/args/ArgsException.php | 17 +- php/src/app/args/_exceptions.php | 10 ++ php/src/app/config.php | 4 +- php/src/app/config/ConfigManager.php | 4 +- php/src/cache/CacheFile.php | 2 +- php/src/cv.php | 10 +- php/src/db/Capacitor.php | 4 +- php/src/db/CapacitorStorage.php | 4 +- php/src/db/_private/_base.php | 6 +- php/src/db/_private/_common.php | 4 +- php/src/db/_private/_insert.php | 4 +- php/src/db/_private/_select.php | 4 +- php/src/db/pdo/Pdo.php | 4 +- php/src/db/pgsql/Pgsql.php | 4 +- php/src/db/sqlite/Sqlite.php | 4 +- php/src/exceptions.php | 216 ++++++++++++++++++++---- php/src/file/SharedFile.php | 3 +- php/src/file/Stream.php | 3 +- php/src/file/tab/TAbstractBuilder.php | 3 +- php/src/file/tab/TAbstractReader.php | 3 +- php/src/mail/MailTemplate.php | 4 +- php/src/mail/mailer.php | 14 +- php/src/output/_messenger.php | 3 +- php/src/output/console.php | 3 +- php/src/output/std/StdMessenger.php | 56 +++--- php/src/php/func.php | 12 +- php/src/str.php | 2 +- php/src/text/Word.php | 8 + php/src/web/curl/CurlException.php | 8 +- php/src/web/curl/curl.php | 4 +- php/tbin/sendmail.php | 7 +- php/tbin/test-exceptions.php | 65 +++++++ 44 files changed, 442 insertions(+), 324 deletions(-) create mode 100644 php/src/app/args/_exceptions.php create mode 100755 php/tbin/test-exceptions.php diff --git a/php/cli/pman/ComposerFile.php b/php/cli/pman/ComposerFile.php index 1e72de2..e40a0df 100644 --- a/php/cli/pman/ComposerFile.php +++ b/php/cli/pman/ComposerFile.php @@ -2,17 +2,16 @@ namespace cli\pman; use nulib\cl; +use nulib\exceptions; use nulib\ext\json; use nulib\file; use nulib\os\path; -use nulib\ValueException; class ComposerFile { function __construct(string $composerFile=".", bool $ensureExists=true) { if (is_dir($composerFile)) $composerFile = path::join($composerFile, 'composer.json'); if ($ensureExists && !file_exists($composerFile)) { - $message = path::ppath($composerFile).": fichier introuvable"; - throw new ValueException($message); + throw exceptions::invalid_value(path::ppath($composerFile), "ce fichier", "il est introuvable"); } $this->composerFile = $composerFile; $this->load(); diff --git a/php/cli/pman/ComposerPmanFile.php b/php/cli/pman/ComposerPmanFile.php index 4587e16..1ab44c7 100644 --- a/php/cli/pman/ComposerPmanFile.php +++ b/php/cli/pman/ComposerPmanFile.php @@ -2,10 +2,10 @@ namespace cli\pman; use nulib\A; +use nulib\exceptions; use nulib\ext\yaml; use nulib\os\path; use nulib\str; -use nulib\ValueException; class ComposerPmanFile { const NAMES = [".composer.pman", ".pman"]; @@ -29,8 +29,7 @@ class ComposerPmanFile { } } if ($ensureExists && !file_exists($configFile)) { - $message = path::ppath($configFile).": fichier introuvable"; - throw new ValueException($message); + throw exceptions::invalid_value(path::ppath($configFile), "ce fichier", "il est introuvable"); } $this->configFile = $configFile; $this->load(); @@ -66,9 +65,7 @@ class ComposerPmanFile { function getProfileConfig(string $profile, ?array $composerRequires=null, ?array $composerRequireDevs=null): array { $config = $this->data["composer"][$profile] ?? null; - if ($config === null) { - throw new ValueException("$profile: profil invalide"); - } + if ($config === null) throw exceptions::invalid_value($profile, "ce profil"); if ($composerRequires !== null) { $matchRequires = $this->data["composer"]["match_require"]; foreach ($composerRequires as $dep => $version) { diff --git a/php/src/AccessException.php b/php/src/AccessException.php index 6996667..629ec39 100644 --- a/php/src/AccessException.php +++ b/php/src/AccessException.php @@ -1,36 +1,38 @@ trace = self::extract_trace($exception->getTrace()); $previous = $exception->getPrevious(); if ($previous !== null) $this->previous = new static($previous); + if ($exception instanceof UserException) { + $this->userMessage = $exception->getUserMessage(); + $this->techMessage = $exception->getTechMessage(); + } else { + $this->userMessage = null; + $this->techMessage = null; + } } - /** @var string */ - protected $class; + protected string $class; function getClass(): string { return $this->class; } - /** @var string */ - protected $message; + protected string $message; function getMessage(): string { return $this->message; @@ -61,22 +66,19 @@ class ExceptionShadow { return $this->code; } - /** @var string */ - protected $file; + protected string $file; function getFile(): string { return $this->file; } - /** @var int */ - protected $line; + protected int $line; function getLine(): int { return $this->line; } - /** @var array */ - protected $trace; + protected array $trace; function getTrace(): array { return $this->trace; @@ -92,10 +94,21 @@ class ExceptionShadow { return implode("\n", $lines); } - /** @var ExceptionShadow */ - protected $previous; + protected ?ExceptionShadow $previous; function getPrevious(): ?ExceptionShadow { return $this->previous; } + + protected ?array $userMessage; + + function getUserMessage(): ?array { + return $this->userMessage; + } + + protected ?array $techMessage; + + function getTechMessage(): ?array { + return $this->techMessage; + } } diff --git a/php/src/ExitError.php b/php/src/ExitError.php index a14c3a8..de8501b 100644 --- a/php/src/ExitError.php +++ b/php/src/ExitError.php @@ -18,8 +18,7 @@ class ExitError extends Error { return $this->getCode() !== 0; } - /** @var ?string */ - protected $userMessage; + protected ?string $userMessage; function haveUserMessage(): bool { return $this->userMessage !== null; diff --git a/php/src/StateException.php b/php/src/StateException.php index 3eadf1d..a2f6bfe 100644 --- a/php/src/StateException.php +++ b/php/src/StateException.php @@ -12,12 +12,12 @@ class StateException extends LogicException { if ($method === null) $method = "this method"; $message = "$method is not implemented"; if ($prefix) $prefix = "$prefix: "; - return new static($prefix.$message); + return new static("$prefix$message"); } static final function unexpected_state(?string $suffix=null): self { $message = "unexpected state"; if ($suffix) $suffix = ": $suffix"; - return new static($message.$suffix); + return new static("$message$suffix"); } } diff --git a/php/src/UserException.php b/php/src/UserException.php index 1bef745..7578093 100644 --- a/php/src/UserException.php +++ b/php/src/UserException.php @@ -1,90 +1,35 @@ getUserMessage(); - else return null; + function __construct($userMessage, $code=0, ?Throwable $previous=null) { + $this->userMessage = $userMessage = c::resolve($userMessage); + parent::__construct(c::to_string($userMessage), $code, $previous); } - /** @param Throwable|ExceptionShadow $e */ - static final function get_user_summary($e): string { - $parts = []; - $first = true; - while ($e !== null) { - $message = self::get_user_message($e); - if (!$message) $message = "(no message)"; - if ($first) $first = false; - else $parts[] = "caused by "; - $parts[] = get_class($e) . ": " . $message; - $e = $e->getPrevious(); - } - return implode(", ", $parts); - } + protected ?array $userMessage; - /** @param Throwable|ExceptionShadow $e */ - static function get_message($e): ?string { - $message = $e->getMessage(); - if (!$message && $e instanceof self) $message = $e->getUserMessage(); - return $message; - } - - /** @param Throwable|ExceptionShadow $e */ - static final function get_summary($e): string { - $parts = []; - $first = true; - while ($e !== null) { - $message = self::get_message($e); - if (!$message) $message = "(no message)"; - if ($first) $first = false; - else $parts[] = "caused by "; - if ($e instanceof ExceptionShadow) $class = $e->getClass(); - else $class = get_class($e); - $parts[] = "$class: $message"; - $e = $e->getPrevious(); - } - return implode(", ", $parts); - } - - /** @param Throwable|ExceptionShadow $e */ - static final function get_traceback($e): string { - $tbs = []; - $previous = false; - while ($e !== null) { - if (!$previous) { - $efile = $e->getFile(); - $eline = $e->getLine(); - $tbs[] = "at $efile($eline)"; - } else { - $tbs[] = "~~ caused by: " . self::get_summary($e); - } - $tbs[] = $e->getTraceAsString(); - $e = $e->getPrevious(); - $previous = true; - #XXX il faudrait ne pas réinclure les lignes communes aux exceptions qui - # ont déjà été affichées - } - return implode("\n", $tbs); - } - - function __construct($userMessage, $techMessage=null, $code=0, ?Throwable $previous=null) { - $this->userMessage = $userMessage; - if ($techMessage === null) $techMessage = $userMessage; - parent::__construct($techMessage, $code, $previous); - } - - /** @var ?string */ - protected $userMessage; - - function getUserMessage(): ?string { + function getUserMessage(): ?array { return $this->userMessage; } + + protected ?array $techMessage = null; + + function getTechMessage(): ?array { + return $this->techMessage; + } + + function setTechMessage($techMessage): self { + $techMessage ??= c::resolve($techMessage); + $this->techMessage = $techMessage; + return $this; + } } diff --git a/php/src/ValueException.php b/php/src/ValueException.php index 12813d2..b321866 100644 --- a/php/src/ValueException.php +++ b/php/src/ValueException.php @@ -5,72 +5,4 @@ namespace nulib; * Class ValueException: indiquer qu'une valeur est invalide */ class ValueException extends UserException { - private static function value($value): string { - if (is_object($value)) { - return "<".get_class($value).">"; - } elseif (is_array($value)) { - $values = $value; - $parts = []; - $index = 0; - foreach ($values as $key => $value) { - if ($key === $index) { - $index++; - $parts[] = self::value($value); - } else { - $parts[] = "$key=>".self::value($value); - } - } - return "[" . implode(", ", $parts) . "]"; - } elseif (is_string($value)) { - return $value; - } else { - return var_export($value, true); - } - } - - private static function message($value, ?string $message, ?string $kind, ?string $prefix, ?string $suffix): string { - if ($kind === null) $kind = "value"; - if ($message === null) $message = "$kind$suffix"; - if ($value !== null) { - $value = self::value($value); - if ($prefix) $prefix = "$prefix: $value"; - else $prefix = $value; - } - if ($prefix) $prefix = "$prefix: "; - return $prefix.$message; - } - - static final function null(?string $kind=null, ?string $prefix=null, ?string $message=null): self { - return new static(self::message(null, $message, $kind, $prefix, " should not be null")); - } - - static final function check_null($value, ?string $kind=null, ?string $prefix=null, ?string $message=null) { - if ($value === null) throw static::null($kind, $prefix, $message); - return $value; - } - - static final function invalid_kind($value=null, ?string $kind=null, ?string $prefix=null, ?string $message=null): self { - return new static(self::message($value, $message, $kind, $prefix, " is invalid")); - } - - static final function invalid_key($value, ?string $prefix=null, ?string $message=null): self { - return self::invalid_kind($value, "key", $prefix, $message); - } - - static final function invalid_value($value, ?string $prefix=null, ?string $message=null): self { - return self::invalid_kind($value, "value", $prefix, $message); - } - - static final function invalid_type($value, string $expected_type): self { - return new static(self::message($value, null, "type", null, " is invalid, expected $expected_type")); - } - - static final function invalid_class($class, string $expected_class): self { - if (is_object($class)) $class = get_class($class); - return new static(self::message($class, null, "class", null, " is invalid, expected $expected_class")); - } - - static final function forbidden($value=null, ?string $kind=null, ?string $prefix=null, ?string $message=null): self { - return new static(self::message($value, $message, $kind, $prefix, " is forbidden")); - } } diff --git a/php/src/app/app.php b/php/src/app/app.php index 13aa994..dc01e45 100644 --- a/php/src/app/app.php +++ b/php/src/app/app.php @@ -5,13 +5,13 @@ use nulib\A; use nulib\app\cli\Application; use nulib\app\config\ProfileManager; use nulib\cl; +use nulib\exceptions; use nulib\ExitError; use nulib\os\path; use nulib\os\sh; use nulib\php\func; use nulib\ref\ref_profiles; use nulib\str; -use nulib\ValueException; class app { private static function isa_Application($app): bool { @@ -59,7 +59,7 @@ class app { } elseif (is_array($app)) { $params = $app; } else { - throw ValueException::invalid_type($app, Application::class); + throw exceptions::invalid_type($app, "app", Application::class); } return $params; } @@ -410,7 +410,7 @@ class app { function fencedJoin(string $basedir, ?string ...$paths): string { $path = path::reljoin($basedir, ...$paths); if (!path::is_within($path, $basedir)) { - throw ValueException::invalid_value($path, "path"); + throw exceptions::invalid_type($path, $kind, "path"); } return $path; } diff --git a/php/src/app/args/AbstractArgsParser.php b/php/src/app/args/AbstractArgsParser.php index 92df216..f1fc241 100644 --- a/php/src/app/args/AbstractArgsParser.php +++ b/php/src/app/args/AbstractArgsParser.php @@ -6,7 +6,8 @@ use stdClass; abstract class AbstractArgsParser { protected function notEnoughArgs(int $needed, ?string $arg=null): ArgsException { if ($arg !== null) $arg .= ": "; - return new ArgsException("${arg}nécessite $needed argument(s) supplémentaires"); + $reason = $arg._exceptions::missing_value_message($needed); + return _exceptions::missing_value(null, null, $reason); } protected function checkEnoughArgs(?string $option, int $count): void { @@ -15,16 +16,17 @@ abstract class AbstractArgsParser { protected function tooManyArgs(int $count, int $expected, ?string $arg=null): ArgsException { if ($arg !== null) $arg .= ": "; - return new ArgsException("${arg}trop d'arguments (attendu $expected, reçu $count)"); + $reason = $arg._exceptions::unexpected_value_message($count - $expected); + return _exceptions::unexpected_value(null, null, $reason); } protected function invalidArg(string $arg): ArgsException { - return new ArgsException("$arg: argument invalide"); + return _exceptions::invalid_value($arg); } protected function ambiguousArg(string $arg, array $candidates): ArgsException { $candidates = implode(", ", $candidates); - return new ArgsException("$arg: argument ambigû (les valeurs possibles sont $candidates)"); + return new ArgsException("$arg: cet argument est ambigû (les valeurs possibles sont $candidates)"); } /** diff --git a/php/src/app/args/Aodef.php b/php/src/app/args/Aodef.php index 4486eb8..fd0c726 100644 --- a/php/src/app/args/Aodef.php +++ b/php/src/app/args/Aodef.php @@ -147,11 +147,11 @@ class Aodef { protected function processExtends(Aolist $argdefs): void { $option = $this->extends; if ($option === null) { - throw ArgsException::missing("extends", "destination arg"); + throw _exceptions::null_value("extends", "il doit spécifier l'argument destination"); } $dest = $argdefs->get($option); if ($dest === null) { - throw ArgsException::invalid($option, "destination arg"); + throw _exceptions::invalid_value($option, "extends", "il doit spécifier un argument valide"); } if ($this->ensureArray !== null) $dest->ensureArray = $this->ensureArray; @@ -178,7 +178,7 @@ class Aodef { $args = $ms[2] ?? null; $option = "--$name"; } else { - throw ArgsException::invalid($option, "long option"); + throw _exceptions::invalid_value($option, "cette option longue"); } } elseif (substr($option, 0, 1) === "-") { $type = self::TYPE_SHORT; @@ -187,7 +187,7 @@ class Aodef { $args = $ms[2] ?? null; $option = "-$name"; } else { - throw ArgsException::invalid($option, "short option"); + throw _exceptions::invalid_value($option, " cette option courte"); } } else { $type = self::TYPE_COMMAND; @@ -196,7 +196,7 @@ class Aodef { $args = null; $option = "$name"; } else { - throw ArgsException::invalid($option, "command"); + throw _exceptions::invalid_value($option, "cette commande"); } } if ($args === ":") { @@ -347,7 +347,7 @@ class Aodef { $haveNull = true; break; } else { - throw ArgsException::invalid("$desc: $arg", "option arg"); + throw _exceptions::invalid_value("$desc: $arg", $kind, "ce n'est pas un argument valide"); } } @@ -366,7 +366,7 @@ class Aodef { $haveNull = true; break; } else { - throw ArgsException::invalid("$desc: $arg", "option arg"); + throw _exceptions::invalid_value("$desc: $arg", $kind, "ce n'est pas un argument valide"); } } if (!$haveOpt) $haveNull = true; @@ -519,7 +519,7 @@ class Aodef { case "--set-args": $this->actionSetArgs($dest, $value); break; case "--set-command": $this->actionSetCommand($dest, $value); break; case "--show-help": $parser->actionPrintHelp($arg); break; - default: throw ArgsException::invalid($this->action, "arg action"); + default: throw _exceptions::invalid_value($this->action, $kind, "action non supportée"); } } diff --git a/php/src/app/args/Aogroup.php b/php/src/app/args/Aogroup.php index b9858f3..06e2df2 100644 --- a/php/src/app/args/Aogroup.php +++ b/php/src/app/args/Aogroup.php @@ -10,7 +10,7 @@ class Aogroup extends Aolist { function __construct(array $defs, bool $setup=false) { $marker = A::pop($defs, 0); if ($marker !== "group") { - throw ArgsException::invalid(null, "group"); + throw _exceptions::missing_value(null, $kind, "ce n'est pas un groupe valide"); } # réordonner les clés numériques $defs = array_merge($defs); diff --git a/php/src/app/args/ArgsException.php b/php/src/app/args/ArgsException.php index db2cfff..fe814e8 100644 --- a/php/src/app/args/ArgsException.php +++ b/php/src/app/args/ArgsException.php @@ -1,20 +1,7 @@ subChannels[] = $channel; } else { - throw ValueException::invalid_type($channel, CapacitorChannel::class); + throw exceptions::invalid_type($channel, "channel", CapacitorChannel::class); } } } diff --git a/php/src/db/CapacitorStorage.php b/php/src/db/CapacitorStorage.php index 4c399c4..b812408 100644 --- a/php/src/db/CapacitorStorage.php +++ b/php/src/db/CapacitorStorage.php @@ -5,8 +5,8 @@ use nulib\A; use nulib\cl; use nulib\cv; use nulib\db\_private\_migration; +use nulib\exceptions; use nulib\php\func; -use nulib\ValueException; use Traversable; /** @@ -596,7 +596,7 @@ abstract class CapacitorStorage { * si $filter n'est pas un tableau, il est transformé en ["id_" => $filter] */ function _one(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): ?array { - if ($filter === null) throw ValueException::null("filter"); + if ($filter === null) throw exceptions::null_value("filter"); $this->_create($channel); $this->verifixFilter($channel, $filter); $raw = $this->db()->one(cl::merge([ diff --git a/php/src/db/_private/_base.php b/php/src/db/_private/_base.php index 2ca42f9..c554174 100644 --- a/php/src/db/_private/_base.php +++ b/php/src/db/_private/_base.php @@ -1,14 +1,14 @@ "create", "type" => "ddl"]; @@ -28,7 +28,7 @@ abstract class _base extends _common { $sql = _generic::parse($sql, $bindings); $meta = ["isa" => "generic", "type" => null]; } else { - throw ValueException::invalid_kind($sql, "query"); + throw exceptions::invalid_type($sql, "cette requête sql"); } } else { if (!is_string($sql)) $sql = strval($sql); diff --git a/php/src/db/_private/_common.php b/php/src/db/_private/_common.php index 575a53b..69b93ab 100644 --- a/php/src/db/_private/_common.php +++ b/php/src/db/_private/_common.php @@ -2,8 +2,8 @@ namespace nulib\db\_private; use nulib\cl; +use nulib\exceptions; use nulib\str; -use nulib\ValueException; class _common { protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool { @@ -249,7 +249,7 @@ class _common { protected static function check_eof(string $tmpsql, string $usersql): void { self::consume(';\s*', $tmpsql); if ($tmpsql) { - throw new ValueException("unexpected value at end: $usersql"); + throw exceptions::invalid_value($usersql, "cette requête sql"); } } } diff --git a/php/src/db/_private/_insert.php b/php/src/db/_private/_insert.php index eb54980..a4fe118 100644 --- a/php/src/db/_private/_insert.php +++ b/php/src/db/_private/_insert.php @@ -2,7 +2,7 @@ namespace nulib\db\_private; use nulib\cl; -use nulib\ValueException; +use nulib\exceptions; class _insert extends _common { const SCHEMA = [ @@ -44,7 +44,7 @@ class _insert extends _common { } elseif ($into !== null) { $sql[] = $into; } else { - throw new ValueException("expected table name: $usersql"); + throw exceptions::invalid_value($usersql, "cette requête sql", "il faut spécifier la table"); } ## cols & values diff --git a/php/src/db/_private/_select.php b/php/src/db/_private/_select.php index 0dc0f0b..4fdc3ff 100644 --- a/php/src/db/_private/_select.php +++ b/php/src/db/_private/_select.php @@ -2,8 +2,8 @@ namespace nulib\db\_private; use nulib\cl; +use nulib\exceptions; use nulib\str; -use nulib\ValueException; class _select extends _common { const SCHEMA = [ @@ -101,7 +101,7 @@ class _select extends _common { $sql[] = "from"; $sql[] = $from; } else { - throw new ValueException("expected table name: $usersql"); + throw exceptions::invalid_value($usersql, "cette requête sql", "il faut spécifier la table"); } ## where diff --git a/php/src/db/pdo/Pdo.php b/php/src/db/pdo/Pdo.php index ba92e99..a12be17 100644 --- a/php/src/db/pdo/Pdo.php +++ b/php/src/db/pdo/Pdo.php @@ -6,8 +6,8 @@ use nulib\db\_private\_config; use nulib\db\_private\Tvalues; use nulib\db\IDatabase; use nulib\db\ITransactor; +use nulib\exceptions; use nulib\php\func; -use nulib\ValueException; class Pdo implements IDatabase { use Tvalues; @@ -203,7 +203,7 @@ class Pdo implements IDatabase { $this->transactors[] = $transactor; $transactor->willUpdate(); } else { - throw ValueException::invalid_type($transactor, ITransactor::class); + throw exceptions::invalid_type($transactor, "transactor", ITransactor::class); } } return $this; diff --git a/php/src/db/pgsql/Pgsql.php b/php/src/db/pgsql/Pgsql.php index bda2423..72e2ef8 100644 --- a/php/src/db/pgsql/Pgsql.php +++ b/php/src/db/pgsql/Pgsql.php @@ -6,8 +6,8 @@ use nulib\db\_private\_config; use nulib\db\_private\Tvalues; use nulib\db\IDatabase; use nulib\db\ITransactor; +use nulib\exceptions; use nulib\php\func; -use nulib\ValueException; class Pgsql implements IDatabase { use Tvalues; @@ -253,7 +253,7 @@ class Pgsql implements IDatabase { $this->transactors[] = $transactor; $transactor->willUpdate(); } else { - throw ValueException::invalid_type($transactor, ITransactor::class); + throw exceptions::invalid_type($transactor, "transactor", ITransactor::class); } } return $this; diff --git a/php/src/db/sqlite/Sqlite.php b/php/src/db/sqlite/Sqlite.php index c82ca43..7876c35 100644 --- a/php/src/db/sqlite/Sqlite.php +++ b/php/src/db/sqlite/Sqlite.php @@ -7,8 +7,8 @@ use nulib\db\_private\_config; use nulib\db\_private\Tvalues; use nulib\db\IDatabase; use nulib\db\ITransactor; +use nulib\exceptions; use nulib\php\func; -use nulib\ValueException; use SQLite3; use SQLite3Result; use SQLite3Stmt; @@ -254,7 +254,7 @@ class Sqlite implements IDatabase { $this->transactors[] = $transactor; $transactor->willUpdate(); } else { - throw ValueException::invalid_type($transactor, ITransactor::class); + throw exceptions::invalid_type($transactor, "transactor", ITransactor::class); } } return $this; diff --git a/php/src/exceptions.php b/php/src/exceptions.php index afad896..8bfcb9f 100644 --- a/php/src/exceptions.php +++ b/php/src/exceptions.php @@ -1,12 +1,82 @@ getUserMessage(); + elseif ($e instanceof ExceptionShadow) $userMessage = $e->getUserMessage(); + else return null; + return c::to_string($userMessage); + } + + /** @param Throwable|ExceptionShadow $e */ + public static function get_tech_message($e): ?string { + if ($e instanceof UserException) $techMessage = $e->getTechMessage(); + elseif ($e instanceof ExceptionShadow) $techMessage = $e->getTechMessage(); + else return null; + return c::to_string($techMessage); + } + + /** @param Throwable|ExceptionShadow $e */ + public static function get_message($e): string { + if ($e instanceof UserException) $userMessage = $e->getUserMessage(); + elseif ($e instanceof ExceptionShadow) $userMessage = $e->getUserMessage(); + else return $e->getMessage(); + return c::to_string($userMessage); + } + + /** @param Throwable|ExceptionShadow $e */ + public static final function get_summary($e, bool $includePrevious = true): string { + $parts = []; + $first = true; + while ($e !== null) { + $message = self::get_message($e); + if (!$message) $message = "(no message)"; + $techMessage = self::get_tech_message($e); + if ($techMessage) $message .= " |$techMessage|"; + if ($first) $first = false; + else $parts[] = ", caused by "; + if ($e instanceof ExceptionShadow) $class = $e->getClass(); + else $class = get_class($e); + $parts[] = "$class: $message"; + $e = $includePrevious ? $e->getPrevious() : null; + } + return implode("", $parts); + } + + /** @param Throwable|ExceptionShadow $e */ + public static final function get_traceback($e): string { + $tbs = []; + $previous = false; + while ($e !== null) { + if (!$previous) { + $efile = $e->getFile(); + $eline = $e->getLine(); + $tbs[] = "at $efile($eline)"; + } else { + $tbs[] = "~~ caused by: " . self::get_summary($e, false); + } + $tbs[] = $e->getTraceAsString(); + $e = $e->getPrevious(); + $previous = true; + #XXX il faudrait ne pas réinclure les lignes communes aux exceptions qui + # ont déjà été affichées + } + return implode("\n", $tbs); + } + + ############################################################################# + + const EXCEPTION = ValueException::class; + const WORD = "la valeur#s"; protected static Word $word; @@ -15,23 +85,59 @@ class exceptions { return self::$word ??= new Word(static::WORD); } + static function value($value): string { + if (is_object($value)) { + return "<".get_class($value).">"; + } elseif (is_array($value)) { + $values = $value; + $parts = []; + $index = 0; + foreach ($values as $key => $value) { + if ($key === $index) { + $index++; + $parts[] = self::value($value); + } else { + $parts[] = "$key=>".self::value($value); + } + } + return "[".implode(", ", $parts)."]"; + } elseif (is_string($value)) { + return $value; + } else { + return var_export($value, true); + } + } + + static function generic($value, ?string $kind, ?string $cause, ?string $reason=null, ?Throwable $previous=null): UserException { + $msg = ""; + if ($value !== null) { + $msg .= self::value($value); + $msg .= ": "; + } + $kind ??= self::word()->_ce(); + $msg .= $kind; + $cause ??= "est invalide"; + if ($cause) $msg .= " $cause"; + if ($reason) $msg .= ": $reason"; + $code = $previous !== null? $previous->getCode(): 0; + $class = static::EXCEPTION; + return new $class($msg, $code, $previous); + } + /** * indiquer qu'une valeur est invalide pour une raison générique */ - static function invalid_value($value, ?string $reason=null): UserException { - $msg = var_export($value, true); - $msg .= self::word()->_ce(); - $msg .= " est invalide"; - if ($reason) $msg .= ": $reason"; - return new UserException($msg); + static function invalid_value($value, ?string $kind=null, ?string $reason=null, ?Throwable $previous=null): UserException { + return self::generic($value, $kind, null, $reason, $previous); } /** * spécialisation de {@link self::invalid_value()} qui permet d'indiquer les * types attendus */ - static function invalid_type($value, $expectedTypes=null): UserException { - $pronom = self::word()->pronom(); + static function invalid_type($value, ?string $kind=null, $expectedTypes=null, ?Throwable $previous=null): UserException { + if ($kind !== null) $pronom = "il"; + else $pronom = self::word()->pronom(); $expectedTypes = cl::withn($expectedTypes); if (!$expectedTypes) { $reason = null; @@ -41,11 +147,12 @@ class exceptions { $reason = "$pronom doit être d'un des types suivants: "; } $reason .= implode(", ", $expectedTypes); - return self::invalid_value($value, $reason); + return self::invalid_value($value, $kind, $reason, $previous); } - static function invalid_format($value, $expectedFormats=null): UserException { - $pronom = self::word()->pronom(); + static function invalid_format($value, ?string $kind=null, $expectedFormats=null, ?Throwable $previous=null): UserException { + if ($kind !== null) $pronom = "il"; + else $pronom = self::word()->pronom(); $expectedFormats = cl::withn($expectedFormats); if (!$expectedFormats) { $reason = null; @@ -55,51 +162,90 @@ class exceptions { $reason = "$pronom doit être dans l'un des formats suivants: "; } $reason .= implode(", ", $expectedFormats); - return self::invalid_value($value, $reason); + return self::invalid_value($value, $kind, $reason, $previous); } - static function out_of_range($value, ?int $min=null, ?int $max=null): UserException { - $pronom = self::word()->pronom(); + static function forbidden_value($value, ?string $kind=null, $allowedValues=null, ?Throwable $previous=null): UserException { + if ($kind !== null) $pronom = "il"; + else $pronom = self::word()->pronom(); + $allowedValues = cl::withn($allowedValues); + if (!$allowedValues) $reason = null; + else $reason = "$pronom doit faire partie de cette liste: "; + $reason .= implode(", ", $allowedValues); + return self::invalid_value($value, $kind, $reason, $previous); + } + + static function out_of_range($value, ?string $kind=null, ?int $min=null, ?int $max=null, ?Throwable $previous=null): UserException { + if ($kind !== null) { + $pronom = "il"; + $compris = "compris"; + $superieur = "supérieur"; + $inferieur = "inférieur"; + } else { + $word = self::word(); + $pronom = $word->pronom(); + $compris = $word->isFeminin()? "comprise": "compris"; + $superieur = $word->isFeminin()? "supérieure": "supérieur"; + $inferieur = $word->isFeminin()? "inférieure": "inférieur"; + } if ($min !== null && $max !== null) { - $reason = "$pronom doit être compris entre $min et $max"; + $reason = "$pronom doit être $compris entre $min et $max"; } else if ($min !== null) { - $reason = "$pronom doit être supérieur à $min"; + $reason = "$pronom doit être $superieur à $min"; } elseif ($max !== null) { - $reason = "$pronom doit être inférieur à $max"; + $reason = "$pronom doit être $inferieur à $max"; } else { $reason = null; } - return self::invalid_value($value, $reason); + return self::invalid_value($value, $kind, $reason, $previous); } - static function null_value(?string $reason=null): UserException { - $msg = self::word()->_ce(); - $msg .= " ne doit pas être nulle"; - if ($reason) $msg .= ": $reason"; - return new UserException($msg); + static function null_value(?string $kind=null, ?string $reason=null, ?Throwable $previous=null): UserException { + if ($kind !== null) $nul = "null"; + else $nul = self::word()->isFeminin()? "nulle": "nul"; + return self::generic(null, $kind, "ne doit pas être $nul", $reason, $previous); + } + + static function missing_value_message(?int $amount=null, ?string $kind=null): string { + $message = "il manque "; + if ($kind !== null) { + if ($amount !== null) $message = "$amount $kind"; + else $message = $kind; + } else { + if ($amount !== null) $message .= self::word()->q($amount); + else $message .= self::word()->_un(); + } + return $message; } /** * indiquer qu'une valeur est manquante */ - static function missing_value(?int $amout=null, ?string $reason=null): UserException { - $msg = "il manque "; - if ($amout !== null) { - $msg .= self::word()->q($amout); + static function missing_value(?int $amount=null, ?string $kind=null, ?string $reason=null, ?Throwable $previous=null): UserException { + $reason ??= self::missing_value_message($amount, $kind); + $class = static::EXCEPTION; + return new $class($reason, null, $previous); + } + + static function unexpected_value_message(?int $amount=null, ?string $kind=null): string { + if ($amount !== null) { + if ($kind !== null) $kind = "$amount $kind"; + else $kind = self::word()->q($amount); + $message = "il y a $kind en trop"; } else { - $msg .= self::word()->_le(); + if ($kind !== null) $kind = "de $kind"; + else $kind = self::word()->_de(2); + $message = "il y a trop $kind"; } - if ($reason) $msg .= ": $reason"; - return new UserException($msg); + return $message; } /** * indiquer qu'une valeur est en trop */ - static function unexpected_value(?string $reason=null): UserException { - $msg = "il y a trop "; - $msg .= self::word()->_de(2); - if ($reason) $msg .= ": $reason"; - return new UserException($msg); + static function unexpected_value(?int $amount=null, ?string $kind=null, ?string $reason=null, ?Throwable $previous=null): UserException { + $reason ??= self::unexpected_value_message($amount, $kind); + $class = static::EXCEPTION; + return new $class($reason, null, $previous); } } diff --git a/php/src/file/SharedFile.php b/php/src/file/SharedFile.php index c14001e..62fae81 100644 --- a/php/src/file/SharedFile.php +++ b/php/src/file/SharedFile.php @@ -1,6 +1,7 @@ fd = $fd; $this->close = $close; $this->throwOnError = $throwOnError ?? static::THROW_ON_ERROR; diff --git a/php/src/file/tab/TAbstractBuilder.php b/php/src/file/tab/TAbstractBuilder.php index f12e1e0..e4d50c3 100644 --- a/php/src/file/tab/TAbstractBuilder.php +++ b/php/src/file/tab/TAbstractBuilder.php @@ -2,6 +2,7 @@ namespace nulib\file\tab; use nulib\cl; +use nulib\exceptions; use nulib\file\csv\CsvBuilder; use nulib\file\web\Upload; use nulib\os\path; @@ -32,7 +33,7 @@ trait TAbstractBuilder { } elseif (is_array($builder)) { $params = cl::merge($builder, $params); } elseif ($builder !== null) { - throw ValueException::invalid_type($builder, self::class); + throw exceptions::invalid_type($builder, "builder", self::class); } $output = $params["output"] ?? null; diff --git a/php/src/file/tab/TAbstractReader.php b/php/src/file/tab/TAbstractReader.php index f6037c7..2bdb538 100644 --- a/php/src/file/tab/TAbstractReader.php +++ b/php/src/file/tab/TAbstractReader.php @@ -2,6 +2,7 @@ namespace nulib\file\tab; use nulib\cl; +use nulib\exceptions; use nulib\file\csv\CsvReader; use nulib\file\web\Upload; use nulib\os\path; @@ -31,7 +32,7 @@ trait TAbstractReader { } elseif (is_array($reader)) { $params = cl::merge($reader, $params); } elseif ($reader !== null) { - throw ValueException::invalid_type($reader, self::class); + throw exceptions::invalid_type($reader, "reader", self::class); } $input = $params["input"] ?? null; diff --git a/php/src/mail/MailTemplate.php b/php/src/mail/MailTemplate.php index ee1739d..d007879 100644 --- a/php/src/mail/MailTemplate.php +++ b/php/src/mail/MailTemplate.php @@ -19,8 +19,8 @@ class MailTemplate { $texprs = $mail["exprs"] ?? []; $this->el = new ExpressionLanguage(); - ValueException::check_null($this->subject = $tsubject, "subject"); - ValueException::check_null($this->body = $tbody, "body"); + $this->subject = cv::not_null($tsubject, "subject"); + $this->body = cv::not_null($tbody, "body"); $exprs = []; # Commencer par extraire les expressions de la forme {name} if (preg_match_all('/\{([a-zA-Z_][a-zA-Z0-9_.-]*)}/', $this->body, $mss, PREG_SET_ORDER)) { diff --git a/php/src/mail/mailer.php b/php/src/mail/mailer.php index 3f92202..8f695d9 100644 --- a/php/src/mail/mailer.php +++ b/php/src/mail/mailer.php @@ -4,6 +4,7 @@ namespace nulib\mail; use nulib\app\config; use nulib\cl; use nulib\cv; +use nulib\exceptions; use nulib\output\msg; use nulib\str; use nulib\ValueException; @@ -90,7 +91,7 @@ class mailer { $host = $params["host"] ?? null; $port = $params["port"] ?? 25; if ($host === null) { - throw new ValueException("mail host is required"); + throw exceptions::null_value("host"); } msg::debug("new PHPMailer using SMTP to $host:$port"); $mailer->isSMTP(); @@ -106,7 +107,7 @@ class mailer { $mailer->isSendmail(); break; default: - throw ValueException::invalid_value($backend, "mailer backend"); + throw exceptions::forbidden_value($backend, "backend", ["smtp", "phpmail", "sendmail"]); } # debug $debug = $params["debug"] ?? SMTP::DEBUG_OFF; @@ -114,7 +115,7 @@ class mailer { if ($debug < SMTP::DEBUG_OFF) $debug = SMTP::DEBUG_OFF; elseif ($debug > SMTP::DEBUG_LOWLEVEL) $debug = SMTP::DEBUG_LOWLEVEL; } elseif (!self::is_bool($debug)) { - throw ValueException::invalid_value($debug, "debug mode"); + throw exceptions::invalid_type($debug, "debug", ["int", "bool"]); } $mailer->SMTPDebug = $debug; # auth, username, password @@ -144,7 +145,10 @@ class mailer { $mailer->SMTPSecure = $secure; break; default: - throw ValueException::invalid_value($secure, "encryption mode"); + throw exceptions::forbidden_value($secure, "secure", [ + PHPMailer::ENCRYPTION_SMTPS, + PHPMailer::ENCRYPTION_STARTTLS, + ]); } } @@ -186,7 +190,7 @@ class mailer { $tos = str::join(",", $tos); msg::debug("Sending to $tos"); if (!$mailer->send()) { - throw new MailerException("Une erreur s'est produite pendant l'envoi du mail", $mailer->ErrorInfo); + throw new MailerException("erreur d'envoi du mail", $mailer->ErrorInfo); } } diff --git a/php/src/output/_messenger.php b/php/src/output/_messenger.php index 1226c24..883b460 100644 --- a/php/src/output/_messenger.php +++ b/php/src/output/_messenger.php @@ -2,6 +2,7 @@ namespace nulib\output; use nulib\cl; +use nulib\exceptions; use nulib\str; use nulib\ValueException; @@ -15,7 +16,7 @@ abstract class _messenger { static function set_messenger_class(string $msg_class, ?array $params=null) { if (!is_subclass_of($msg_class, IMessenger::class)) { - throw ValueException::invalid_class($msg_class, IMessenger::class); + throw exceptions::invalid_type($msg_class, $kind, IMessenger::class); } static::set_messenger(new $msg_class($params)); } diff --git a/php/src/output/console.php b/php/src/output/console.php index a5ca3a9..9eb6b52 100644 --- a/php/src/output/console.php +++ b/php/src/output/console.php @@ -2,6 +2,7 @@ namespace nulib\output; use nulib\app\app; +use nulib\exceptions; use nulib\output\std\ProxyMessenger; use nulib\ValueException; @@ -63,7 +64,7 @@ class console extends _messenger { ]); break; default: - throw ValueException::invalid_value($verbosity, "verbosity"); + throw exceptions::forbidden_value($verbosity, $kind, ["silent", "quiet", "normal", "verbose", "debug"]); } } diff --git a/php/src/output/std/StdMessenger.php b/php/src/output/std/StdMessenger.php index c163aeb..4d80891 100644 --- a/php/src/output/std/StdMessenger.php +++ b/php/src/output/std/StdMessenger.php @@ -4,6 +4,7 @@ namespace nulib\output\std; use Exception; use nulib\A; use nulib\cl; +use nulib\exceptions; use nulib\ExceptionShadow; use nulib\output\IMessenger; use nulib\UserException; @@ -236,9 +237,11 @@ class StdMessenger implements _IMessenger { return $indentLevel; } - protected function _printTitle(?string $linePrefix, int $level, - string $type, $content, - int $indentLevel, StdOutput $out): void { + protected function _printTitle( + ?string $linePrefix, int $level, + string $type, $content, + int $indentLevel, StdOutput $out + ): void { $prefixes = self::GENERIC_PREFIXES[$level][$type]; if ($prefixes[0]) $out->print(); $content = cl::with($content); @@ -284,10 +287,12 @@ class StdMessenger implements _IMessenger { } } - protected function _printAction(?string $linePrefix, int $level, - bool $printContent, $content, - bool $printResult, ?bool $rsuccess, $rcontent, - int $indentLevel, StdOutput $out): void { + protected function _printAction( + ?string $linePrefix, int $level, + bool $printContent, $content, + bool $printResult, ?bool $rsuccess, $rcontent, + int $indentLevel, StdOutput $out + ): void { $color = $out->isColor(); if ($rsuccess === true) $type = "success"; elseif ($rsuccess === false) $type = "failure"; @@ -357,9 +362,11 @@ class StdMessenger implements _IMessenger { } } - protected function _printGeneric(?string $linePrefix, int $level, - string $type, $content, - int $indentLevel, StdOutput $out): void { + protected function _printGeneric( + ?string $linePrefix, int $level, + string $type, $content, + int $indentLevel, StdOutput $out + ): void { $prefixes = self::GENERIC_PREFIXES[$level][$type]; $content = cl::with($content); if ($out->isColor()) { @@ -390,7 +397,11 @@ class StdMessenger implements _IMessenger { } } - protected function _printGenericOrException(?int $level, string $type, $content, int $indentLevel, StdOutput $out): void { + protected function _printGenericOrException( + ?int $level, + string $type, $content, + int $indentLevel, StdOutput $out + ): void { $linePrefix = $this->getLinePrefix(); # si $content contient des exceptions, les afficher avec un level moindre $exceptions = null; @@ -421,27 +432,18 @@ class StdMessenger implements _IMessenger { $level1 = $this->decrLevel($level); $showTraceback = $this->checkLevel($level1); foreach ($exceptions as $exception) { - # tout d'abord userMessage - if ($exception instanceof UserException) { - $userMessage = UserException::get_user_message($exception); - $userMessage ??= "Une erreur technique s'est produite"; - $showSummary = true; - } else { - $userMessage = UserException::get_summary($exception); - $showSummary = false; - } - if ($userMessage !== null && $showContent) { + # tout d'abord message + $message = exceptions::get_message($exception); + if ($showContent) { if ($printActions) { $this->printActions(); $printActions = false; } - $this->_printGeneric($linePrefix, $level, $type, $userMessage, $indentLevel, $out); + $this->_printGeneric($linePrefix, $level, $type, $message, $indentLevel, $out); } # puis summary et traceback if ($showTraceback) { if ($printActions) { $this->printActions(); $printActions = false; } - if ($showSummary) { - $summary = UserException::get_summary($exception); - $this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out); - } - $traceback = UserException::get_traceback($exception); + $summary = exceptions::get_summary($exception, false); + $this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out); + $traceback = exceptions::get_traceback($exception); $this->_printGeneric($linePrefix, $level1, $type, $traceback, $indentLevel, $out); } } diff --git a/php/src/php/func.php b/php/src/php/func.php index 1c0123c..976a761 100644 --- a/php/src/php/func.php +++ b/php/src/php/func.php @@ -6,8 +6,8 @@ use Exception; use nulib\A; use nulib\cl; use nulib\cv; +use nulib\exceptions; use nulib\StateException; -use nulib\ValueException; use ReflectionClass; use ReflectionFunction; use ReflectionMethod; @@ -446,11 +446,7 @@ class func { const TYPE_STATIC = self::TYPE_METHOD | self::FLAG_STATIC; protected static function not_a_callable($func, ?string $reason) { - if ($reason === null) { - $msg = var_export($func, true); - $reason = "$msg: not a callable"; - } - return new ValueException($reason); + throw exceptions::invalid_type($func, null, "callable"); } private static function _with($func, ?array $args=null, bool $strict=true, ?string &$reason=null): ?self { @@ -604,7 +600,7 @@ class func { $mask = $staticOnly? self::MASK_PS: self::MASK_P; $expected = $staticOnly? self::METHOD_PS: self::METHOD_P; } else { - throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet"); + throw exceptions::invalid_type($class_or_object, null, ["class", "object"]); } $methods = []; foreach ($c->getMethods() as $m) { @@ -777,7 +773,7 @@ class func { if (is_object($object) && !($this->flags & self::FLAG_STATIC)) { if (is_object($c)) $c = get_class($c); if (is_string($c) && !($object instanceof $c)) { - throw ValueException::invalid_type($object, $c); + throw exceptions::invalid_type($object, "object", $c); } $this->object = $object; $this->bound = true; diff --git a/php/src/str.php b/php/src/str.php index 689c9d0..c330b37 100644 --- a/php/src/str.php +++ b/php/src/str.php @@ -438,7 +438,7 @@ class str { } elseif (preg_match(self::CAMEL_PATTERN2, $camel, $ms, PREG_OFFSET_CAPTURE)) { # préfixe en minuscule } else { - throw ValueException::invalid_kind($camel, "camel string"); + throw exceptions::invalid_type($camel, $kind, "camel string"); } $parts[] = strtolower($ms[1][0]); $index = intval($ms[1][1]) + strlen($ms[1][0]); diff --git a/php/src/text/Word.php b/php/src/text/Word.php index fb5851c..b25cfe3 100644 --- a/php/src/text/Word.php +++ b/php/src/text/Word.php @@ -119,6 +119,14 @@ class Word { $this->w = $spec; } + function isMasculin(): bool { + return !$this->fem; + } + + function isFeminin(): bool { + return $this->fem; + } + /** * retourner le mot sans article * diff --git a/php/src/web/curl/CurlException.php b/php/src/web/curl/CurlException.php index 53fda92..52e87a0 100644 --- a/php/src/web/curl/CurlException.php +++ b/php/src/web/curl/CurlException.php @@ -5,9 +5,8 @@ use nulib\UserException; use Throwable; class CurlException extends UserException { - function __construct($ch, ?string $message=null, $code=0, ?Throwable $previous=null) { - if ($message === null) $message = "(unknown error)"; - $userMessage = $message; + function __construct($ch, $userMessage=null, $code=0, ?Throwable $previous=null) { + $userMessage ??= "erreur curl inconnue"; $techMessage = null; if ($ch !== null) { $parts = []; @@ -17,6 +16,7 @@ class CurlException extends UserException { if ($error != "") $parts[] = "error: $error"; if ($parts) $techMessage = implode(", ", $parts); } - parent::__construct($userMessage, $techMessage, $code, $previous); + parent::__construct($userMessage, $code, $previous); + $this->setTechMessage($techMessage); } } diff --git a/php/src/web/curl/curl.php b/php/src/web/curl/curl.php index 34f0677..fcc7530 100644 --- a/php/src/web/curl/curl.php +++ b/php/src/web/curl/curl.php @@ -12,11 +12,11 @@ class curl { if (!isset($curlOptions[CURLOPT_RETURNTRANSFER])) $curlOptions[CURLOPT_RETURNTRANSFER] = true; $extractHeaders = isset($curlOptions[CURLOPT_HEADER]) && $curlOptions[CURLOPT_HEADER]; $ch = curl_init(); - if ($ch === false) throw new CurlException(null, "init"); + if ($ch === false) throw new CurlException(null, "erreur curl lors de l'initialisation"); curl_setopt_array($ch, $curlOptions); try { $result = curl_exec($ch); - if ($result === false) throw new CurlException($ch); + if ($result === false) throw new CurlException($ch, "erreur curl lors du téléchargement"); if ($extractHeaders) { $info = curl_getinfo($ch); $headersSize = $info["header_size"]; diff --git a/php/tbin/sendmail.php b/php/tbin/sendmail.php index 89f1c01..0b20a6a 100755 --- a/php/tbin/sendmail.php +++ b/php/tbin/sendmail.php @@ -3,6 +3,7 @@ require __DIR__.'/../vendor/autoload.php'; use nulib\app\cli\Application; +use nulib\cv; use nulib\mail\mailer; use nulib\ValueException; @@ -20,10 +21,8 @@ Application::run(new class extends Application { protected $to, $cc, $bcc, $from; function main() { - $subject = $this->args[0] ?? null; - ValueException::check_null($subject, "subject"); - $body = $this->args[1] ?? null; - ValueException::check_null($body, "body"); + $subject = cv::not_null($this->args[0] ?? null, "subject"); + $body = cv::not_null($this->args[1] ?? null, "body"); mailer::send($this->to, $subject, $body, $this->cc, $this->bcc, $this->from); } }); diff --git a/php/tbin/test-exceptions.php b/php/tbin/test-exceptions.php new file mode 100755 index 0000000..ad17757 --- /dev/null +++ b/php/tbin/test-exceptions.php @@ -0,0 +1,65 @@ +#!/usr/bin/php + "tester l'affichage des exception", + + "merge" => parent::ARGS, + ]; + + function fart(): void { + throw new RuntimeException("fart"); + } + + function prout(): void { + try { + $this->fart(); + } catch (Exception $e) { + throw new RuntimeException("prout", $e->getCode(), $e); + } + } + + function main() { + try { + throw new Exception("exception normale"); + } catch (Exception $e) { + msg::info("summary: ". exceptions::get_summary($e)); + msg::error($e); + } + try { + try { + $this->prout(); + } catch (Exception $e) { + throw new Exception("exception normale", $e->getCode(), $e); + } + } catch (Exception $e) { + msg::info("summary: ". exceptions::get_summary($e)); + msg::error($e); + } + try { + throw exceptions::invalid_value("valeur", $kind) + ->setTechMessage("message technique"); + } catch (Exception $e) { + msg::info("summary: ". exceptions::get_summary($e)); + msg::error($e); + } + try { + try { + $this->prout(); + } catch (Exception $e) { + throw exceptions::invalid_value("valeur", $kind, null, $e) + ->setTechMessage("message technique"); + } + } catch (Exception $e) { + msg::info("summary: ". exceptions::get_summary($e)); + msg::error($e); + } + } +}); From 23fe2859b5d589b9847688d1cbbe268115305945 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 8 Oct 2025 12:45:04 +0400 Subject: [PATCH 50/91] modifs.mineures sans commentaires --- php/src/AccessException.php | 4 ++-- php/src/cache/CacheData.php | 2 -- php/src/cache/CacheFile.php | 2 -- php/src/cache/DataCacheData.php | 1 - php/src/file/SharedFile.php | 1 - php/src/file/Stream.php | 1 - php/src/file/tab/TAbstractBuilder.php | 1 - php/src/file/tab/TAbstractReader.php | 1 - php/src/mail/MailTemplate.php | 1 - php/src/mail/mailer.php | 1 - php/src/output/_messenger.php | 1 - php/src/output/console.php | 1 - php/src/output/std/StdMessenger.php | 1 - 13 files changed, 2 insertions(+), 16 deletions(-) diff --git a/php/src/AccessException.php b/php/src/AccessException.php index 629ec39..bacf080 100644 --- a/php/src/AccessException.php +++ b/php/src/AccessException.php @@ -1,13 +1,13 @@ Date: Wed, 8 Oct 2025 14:10:13 +0400 Subject: [PATCH 51/91] modifs.mineures sans commentaires --- php/src/mail/mailer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/php/src/mail/mailer.php b/php/src/mail/mailer.php index b19e2bc..d11eb34 100644 --- a/php/src/mail/mailer.php +++ b/php/src/mail/mailer.php @@ -21,6 +21,7 @@ class mailer { return true; } else { switch (strval($value)) { + case "": case "0": case "no": case "off": From c37748b6f4337607e969d0c8201a8679ef5d746c Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 8 Oct 2025 15:44:34 +0400 Subject: [PATCH 52/91] modifs.mineures sans commentaires --- php/src/output/IMessenger.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/php/src/output/IMessenger.php b/php/src/output/IMessenger.php index 7b8fdec..6bd5aa4 100644 --- a/php/src/output/IMessenger.php +++ b/php/src/output/IMessenger.php @@ -100,8 +100,8 @@ interface IMessenger { * terminer le chapitre en cours. toutes les actions en cours sont terminées * avec un résultat neutre. * - * @param bool $all faut-il terminer *tous* les chapitres ainsi que la section - * en cours? + * @param bool $all terminer *tous* les chapitres ainsi que la section en + * cours */ function end(bool $all=false): void; } From 8974cf09a1c1e24219159f569c9b458d65e86c73 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 8 Oct 2025 15:48:46 +0400 Subject: [PATCH 53/91] modifs.mineures sans commentaires --- php/src/exceptions.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/php/src/exceptions.php b/php/src/exceptions.php index 8bfcb9f..a6b1e88 100644 --- a/php/src/exceptions.php +++ b/php/src/exceptions.php @@ -14,7 +14,8 @@ class exceptions { if ($e instanceof UserException) $userMessage = $e->getUserMessage(); elseif ($e instanceof ExceptionShadow) $userMessage = $e->getUserMessage(); else return null; - return c::to_string($userMessage); + if ($userMessage === null) return null; + else return c::to_string($userMessage); } /** @param Throwable|ExceptionShadow $e */ @@ -22,7 +23,8 @@ class exceptions { if ($e instanceof UserException) $techMessage = $e->getTechMessage(); elseif ($e instanceof ExceptionShadow) $techMessage = $e->getTechMessage(); else return null; - return c::to_string($techMessage); + if ($techMessage === null) return null; + else return c::to_string($techMessage); } /** @param Throwable|ExceptionShadow $e */ From 5035d5522ab61368f0ad2fd4e75f7f507d0f066a Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 13 Oct 2025 11:52:41 +0400 Subject: [PATCH 54/91] =?UTF-8?q?mise=20=C3=A0=20jour=20de=20la=20gestion?= =?UTF-8?q?=20des=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/src/UserException.php | 2 +- php/src/app/app.php | 6 +- php/src/app/args/Aodef.php | 6 +- php/src/app/args/Aogroup.php | 2 +- php/src/app/cli/Application.php | 61 +- php/src/app/config.php | 4 +- php/src/app/config/ConfigManager.php | 4 +- php/src/cl.php | 2 +- php/src/cv.php | 14 +- php/src/db/_private/_base.php | 4 +- php/src/file.php | 2 +- php/src/file/csv/csv_flavours.php | 6 +- php/src/output/TODO.md | 16 +- php/src/output/_TMessenger.php | 62 ++ php/src/output/_messenger.php | 32 +- php/src/output/con.php | 27 + php/src/output/console.php | 75 -- php/src/output/log.php | 43 +- php/src/output/msg.php | 69 +- php/src/output/out.php | 34 - php/src/output/say.php | 29 +- php/src/output/std/AbstractMessenger.php | 296 ++++++++ php/src/output/std/ConsoleMessenger.php | 432 +++++++++++ php/src/output/std/LogMessenger.php | 306 ++++++++ php/src/output/std/NullMessenger.php | 58 ++ php/src/output/std/ProxyMessenger.php | 111 ++- php/src/output/std/StdMessenger.php | 724 ------------------- php/src/output/std/StdOutput.php | 28 +- php/src/output/std/_IMessenger.php | 71 ++ php/src/output/web.php | 15 + php/src/php/time/Elapsed.php | 6 +- php/src/str.php | 4 +- php/src/txt.php | 2 +- php/tbin/.gitignore | 1 + php/tbin/test-console.php | 278 ------- php/tbin/test-mysql.php | 4 +- php/tbin/test-output-forever.php | 4 +- php/tbin/test-output.php | 458 ++++++++++++ php/tests/cache/_TestCase.php | 4 +- php/tests/cache/cacheTest.php | 2 +- php/tests/db/sqlite/ChannelMigrationTest.php | 4 +- php/tests/php/funcTest.php | 2 +- 42 files changed, 1966 insertions(+), 1344 deletions(-) create mode 100644 php/src/output/_TMessenger.php create mode 100644 php/src/output/con.php delete mode 100644 php/src/output/console.php delete mode 100644 php/src/output/out.php create mode 100644 php/src/output/std/AbstractMessenger.php create mode 100644 php/src/output/std/ConsoleMessenger.php create mode 100644 php/src/output/std/LogMessenger.php create mode 100644 php/src/output/std/NullMessenger.php delete mode 100644 php/src/output/std/StdMessenger.php create mode 100644 php/src/output/web.php delete mode 100755 php/tbin/test-console.php create mode 100755 php/tbin/test-output.php diff --git a/php/src/UserException.php b/php/src/UserException.php index 7578093..e41dc2c 100644 --- a/php/src/UserException.php +++ b/php/src/UserException.php @@ -28,7 +28,7 @@ class UserException extends RuntimeException { } function setTechMessage($techMessage): self { - $techMessage ??= c::resolve($techMessage); + if ($techMessage !== null) $techMessage = c::resolve($techMessage); $this->techMessage = $techMessage; return $this; } diff --git a/php/src/app/app.php b/php/src/app/app.php index dc01e45..c3fcdd4 100644 --- a/php/src/app/app.php +++ b/php/src/app/app.php @@ -119,7 +119,7 @@ class app { static function is_production_mode(): bool { return self::get()->isProductionMode(); } - + static function is_prod(): bool { return self::get_profile() === ref_profiles::PROD; } @@ -146,7 +146,7 @@ class app { static final function set_fact(string $fact, $value=true): void { self::get()->setFact($fact, $value); } - + static function is_debug(): bool { return self::get()->isDebug(); } @@ -410,7 +410,7 @@ class app { function fencedJoin(string $basedir, ?string ...$paths): string { $path = path::reljoin($basedir, ...$paths); if (!path::is_within($path, $basedir)) { - throw exceptions::invalid_type($path, $kind, "path"); + throw exceptions::invalid_value($path, "path"); } return $path; } diff --git a/php/src/app/args/Aodef.php b/php/src/app/args/Aodef.php index fd0c726..921a197 100644 --- a/php/src/app/args/Aodef.php +++ b/php/src/app/args/Aodef.php @@ -347,7 +347,7 @@ class Aodef { $haveNull = true; break; } else { - throw _exceptions::invalid_value("$desc: $arg", $kind, "ce n'est pas un argument valide"); + throw _exceptions::invalid_value("$desc: $arg"); } } @@ -366,7 +366,7 @@ class Aodef { $haveNull = true; break; } else { - throw _exceptions::invalid_value("$desc: $arg", $kind, "ce n'est pas un argument valide"); + throw _exceptions::invalid_value("$desc: $arg"); } } if (!$haveOpt) $haveNull = true; @@ -519,7 +519,7 @@ class Aodef { case "--set-args": $this->actionSetArgs($dest, $value); break; case "--set-command": $this->actionSetCommand($dest, $value); break; case "--show-help": $parser->actionPrintHelp($arg); break; - default: throw _exceptions::invalid_value($this->action, $kind, "action non supportée"); + default: throw _exceptions::invalid_value($this->action, null, "action non supportée"); } } diff --git a/php/src/app/args/Aogroup.php b/php/src/app/args/Aogroup.php index 06e2df2..d293a75 100644 --- a/php/src/app/args/Aogroup.php +++ b/php/src/app/args/Aogroup.php @@ -10,7 +10,7 @@ class Aogroup extends Aolist { function __construct(array $defs, bool $setup=false) { $marker = A::pop($defs, 0); if ($marker !== "group") { - throw _exceptions::missing_value(null, $kind, "ce n'est pas un groupe valide"); + throw _exceptions::missing_value(null, null, "ce n'est pas un groupe valide"); } # réordonner les clés numériques $defs = array_merge($defs); diff --git a/php/src/app/cli/Application.php b/php/src/app/cli/Application.php index 02f0901..85c8d13 100644 --- a/php/src/app/cli/Application.php +++ b/php/src/app/cli/Application.php @@ -10,10 +10,13 @@ use nulib\app\config; use nulib\app\RunFile; use nulib\ExitError; use nulib\ext\yaml; -use nulib\output\console; +use nulib\output\con; use nulib\output\log; use nulib\output\msg; -use nulib\output\std\StdMessenger; +use nulib\output\say; +use nulib\output\std\ConsoleMessenger; +use nulib\output\std\LogMessenger; +use nulib\output\std\ProxyMessenger; use nulib\ref\ref_profiles; /** @@ -192,7 +195,7 @@ EOT); protected static function _initialize_app(): void { app::init(static::class); app::set_fact(app::FACT_CLI_APP); - msg::set_messenger(new StdMessenger([ + msg::set_messenger(new ConsoleMessenger([ "min_level" => msg::DEBUG, ])); } @@ -200,18 +203,20 @@ EOT); protected static function _configure_app(Application $app): void { config::configure(config::CONFIGURE_INITIAL_ONLY); - $msgs = null; - $msgs["console"] = new StdMessenger([ + $con = con::set_messenger(new ConsoleMessenger([ "min_level" => msg::NORMAL, - ]); + ])); + say::set_messenger($con); + msg::set_messenger($con); if (static::USE_LOGFILE) { - $msgs["log"] = new StdMessenger([ + $log = log::set_messenger(new LogMessenger([ "output" => app::get()->getLogfile(), "min_level" => msg::MINOR, - "add_date" => true, - ]); + ])); + } else { + $log = log::set_messenger(new ProxyMessenger()); } - msg::init($msgs); + msg::set_messenger($log); $app->parseArgs(); config::configure(); @@ -268,26 +273,36 @@ EOT); "title" => "NIVEAU D'INFORMATION", "show" => false, ["group", - ["--verbosity", + ["-V", "--verbosity", "args" => "verbosity", "argsdesc" => "silent|quiet|verbose|debug", - "action" => [console::class, "set_verbosity"], - "help" => "spécifier le niveau d'informations affiché", + "action" => [con::class, "set_verbosity"], + "help" => "Spécifier le niveau d'informations affiché sur la console", ], - ["-q", "--quiet", "action" => [console::class, "set_verbosity", "quiet"]], - ["-v", "--verbose", "action" => [console::class, "set_verbosity", "verbose"]], - ["-D", "--debug", "action" => [console::class, "set_verbosity", "debug"]], - ], - ["-L", "--logfile", - "args" => "output", - "action" => [log::class, "set_output"], - "help" => "Logger les messages de l'application dans le fichier spécifié", + ["-q", "--quiet", "action" => [con::class, "set_verbosity", "quiet"]], + ["-v", "--verbose", "action" => [con::class, "set_verbosity", "verbose"]], + ["-D", "--debug", "action" => [con::class, "set_verbosity", "debug"]], ], ["group", ["--color", - "action" => [console::class, "set_color", true], + "action" => [con::class, "set_color", true], "help" => "Afficher (resp. ne pas afficher) la sortie en couleur par défaut", ], - ["--no-color", "action" => [console::class, "set_color", false]], + ["--no-color", "action" => [con::class, "set_color", false]], + ], + ["group", + ["-L", "--logfile", + "args" => "output", + "action" => [log::class, "set_output"], + "help" => "Logger les messages de l'application dans le fichier spécifié", + ], + ["--lV", "--lverbosity", + "args" => "verbosity", "argsdesc" => "silent|quiet|verbose|debug", + "action" => [log::class, "set_verbosity"], + "help" => "Spécifier le niveau des informations ajoutées dans les logs", + ], + ["--lq", "--lquiet", "action" => [log::class, "set_verbosity", "quiet"]], + ["--lv", "--lverbose", "action" => [log::class, "set_verbosity", "verbose"]], + ["--lD", "--ldebug", "action" => [log::class, "set_verbosity", "debug"]], ], ]; diff --git a/php/src/app/config.php b/php/src/app/config.php index 8c1581b..6fd270a 100644 --- a/php/src/app/config.php +++ b/php/src/app/config.php @@ -28,7 +28,7 @@ class config { static function configure(?array $params=null): void { self::$config->configure($params); } - + static final function add($config, string ...$profiles): void { self::$config->addConfig($config, $profiles); } static final function load_config($file): void { $ext = path::ext($file); @@ -37,7 +37,7 @@ class config { } elseif ($ext === ".json") { $config = new JsonConfig($file); } else { - throw exceptions::invalid_type($file, $kind, "config file"); + throw exceptions::invalid_value($file, "config file"); } self::add($config); } diff --git a/php/src/app/config/ConfigManager.php b/php/src/app/config/ConfigManager.php index ee3f63a..abcef01 100644 --- a/php/src/app/config/ConfigManager.php +++ b/php/src/app/config/ConfigManager.php @@ -59,11 +59,11 @@ class ConfigManager { protected function cacheHas(string $pkey, string $profile) { return array_key_exists("$profile.$pkey", $this->cache); } - + protected function cacheGet(string $pkey, string $profile) { return cl::get($this->cache, "$profile.$pkey"); } - + protected function cacheSet(string $pkey, $value, string $profile): void { $this->cache["$profile.$pkey"] = $value; } diff --git a/php/src/cl.php b/php/src/cl.php index b15ed55..2e57a87 100644 --- a/php/src/cl.php +++ b/php/src/cl.php @@ -848,7 +848,7 @@ class cl { static final function any_not_same(?array $array, $value): bool { return self::any_if($array, cv::Fnot_same($value)); } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - + static final function filter_if(?array $array, callable $cond): ?array { if ($array === null) return null; $filtered = []; diff --git a/php/src/cv.php b/php/src/cv.php index f1ffc85..89b81ac 100644 --- a/php/src/cv.php +++ b/php/src/cv.php @@ -29,7 +29,7 @@ class cv { static final function t($value): bool { return $value || $value === "0"; } - + /** tester si $value est fausse (cela n'inclue pas la chaine "0") */ static final function f($value): bool { return !$value && $value !== "0"; @@ -198,11 +198,11 @@ class cv { * * lever une exception si $value n'est d'aucun de ces types */ - static final function check_key($value, ?string $prefix=null, bool $throw_exception=true): array { + static final function check_key($value, ?string $kind=null, bool $throwException=true): array { $index = is_int($value)? $value : null; $key = is_string($value)? $value : null; - if ($index === null && $key === null && $throw_exception) { - throw exceptions::invalid_type($value, $kind, "key", $prefix); + if ($index === null && $key === null && $throwException) { + throw exceptions::invalid_type($value, $kind, "key"); } else { return [$index, $key]; } @@ -214,12 +214,12 @@ class cv { * * @throws ValueException si $value n'est d'aucun de ces types */ - static final function check_bsa($value, ?string $prefix=null, bool $throw_exception=true): array { + static final function check_bsa($value, ?string $kind=null, bool $throwException=true): array { $bool = is_bool($value)? $value : null; $scalar = !is_bool($value) && is_scalar($value)? $value : null; $array = is_array($value)? $value : null; - if ($bool === null && $scalar === null && $array === null && $throw_exception) { - throw exceptions::invalid_type($value, $kind, ["bool", "scalar", "array"], $prefix); + if ($bool === null && $scalar === null && $array === null && $throwException) { + throw exceptions::invalid_type($value, $kind, ["bool", "scalar", "array"]); } else { return [$bool, $scalar, $array]; } diff --git a/php/src/db/_private/_base.php b/php/src/db/_private/_base.php index c554174..8325bae 100644 --- a/php/src/db/_private/_base.php +++ b/php/src/db/_private/_base.php @@ -8,7 +8,7 @@ abstract class _base extends _common { if (is_array($sql)) { $prefix = $sql[0] ?? null; if ($prefix === null) { - throw exceptions::invalid_type($sql, "cette requête sql"); + throw exceptions::invalid_value($sql, "cette requête sql"); } elseif (_create::isa($prefix)) { $sql = _create::parse($sql, $bindings); $meta = ["isa" => "create", "type" => "ddl"]; @@ -28,7 +28,7 @@ abstract class _base extends _common { $sql = _generic::parse($sql, $bindings); $meta = ["isa" => "generic", "type" => null]; } else { - throw exceptions::invalid_type($sql, "cette requête sql"); + throw exceptions::invalid_value($sql, "cette requête sql"); } } else { if (!is_string($sql)) $sql = strval($sql); diff --git a/php/src/file.php b/php/src/file.php index a8175d4..4c84f58 100644 --- a/php/src/file.php +++ b/php/src/file.php @@ -57,7 +57,7 @@ class file { } return $file; } - + static function writer($output, ?string $mode="w+b", ?callable $func=null): FileWriter { $file = new FileWriter(self::fix_dash($output), $mode); if ($func !== null) { diff --git a/php/src/file/csv/csv_flavours.php b/php/src/file/csv/csv_flavours.php index 2a782e6..bb00150 100644 --- a/php/src/file/csv/csv_flavours.php +++ b/php/src/file/csv/csv_flavours.php @@ -17,13 +17,13 @@ class csv_flavours { "dumb," => ref_csv::DUMB_OO_FLAVOUR, "dumb" => ref_csv::DUMB_FLAVOUR, ]; - + const ENCODINGS = [ ref_csv::OO_FLAVOUR => ref_csv::OO_ENCODING, ref_csv::XL_FLAVOUR => ref_csv::XL_ENCODING, ref_csv::DUMB_FLAVOUR => ref_csv::DUMB_ENCODING, ]; - + static final function verifix(?string $flavour): ?string { if ($flavour === null) return null; $lflavour = strtolower($flavour); @@ -41,7 +41,7 @@ class csv_flavours { elseif ($flavour == ref_csv::XL_FLAVOUR) return ref_csv::MSEXCEL; else return $flavour; } - + static final function get_params(string $flavour): array { return [$flavour[0], $flavour[1], $flavour[2]]; } diff --git a/php/src/output/TODO.md b/php/src/output/TODO.md index 28ae6ac..0164f84 100644 --- a/php/src/output/TODO.md +++ b/php/src/output/TODO.md @@ -1,8 +1,13 @@ # nulib\output -* dans msg::action($m, function() {}), *bloquer* la marque pour empêcher d'aller - plus bas que prévu. comme ça s'il y a plusieurs success ou failure dans la - fonction, c'est affiché correctement. +* log:: permet d'ajouter autant d'instance de LogMessenger que nécessaire + * on pourrait qualifier un logger avec par exemple la classe qui l'appelle + ou le nom d'un sous-système. + * pour un log structuré, un attribut donnerai le qualificatif, ce qui ne + serait pris en compte que par le logger approprié (e.g un logger qui est + responsable de nulib/io logguera les message de nulib/io/ClassA mais pas + les messages de nulib/args/ClassB + * un trait permet d'ajouter un logger à une classe * [ ] possibilité de paramétrer le nom du fichier destination pour faire une rotation des logs @@ -13,11 +18,6 @@ * [ ] dans `StdMessenger::resetParams()`, `[output]` peut être une instance de StdOutput pour mettre à jour $out ET $err, ou un tableau de deux éléments pour mettre à jour séparément $out et $err -* [ ] vérifier que la date affichée pour un TITLE est celle à laquelle l'appel - a été fait, même si le premier événement en dessous arrive bien plus tard -* [ ] pareil pour action: sauf si c'est une seule ligne, la date de action est - la date du premier appel, alors que la date de $result est celui du result si - c'est affiché sur une autre ligne * réorganiser pour que msg:: attaque un proxy dans lequel est configuré un ensemble standard de sorties: say, log, debuglog * `--aD, --av, --aq, --asilent` pour les valeurs d'ajustement qui sont un diff --git a/php/src/output/_TMessenger.php b/php/src/output/_TMessenger.php new file mode 100644 index 0000000..26de8a5 --- /dev/null +++ b/php/src/output/_TMessenger.php @@ -0,0 +1,62 @@ +addMessenger($msg); + else self::$msg = new ProxyMessenger(self::$msg); + return $msg; + } + + static function get(): IMessenger { + return self::$msg ??= new NullMessenger(); + } + + static function set_verbosity(string $verbosity): void { + $msg = self::get(); + switch ($verbosity) { + case "Q": + case "silent": + $msg->resetParams([ + "min_level" => self::NONE, + ]); + break; + case "q": + case "quiet": + $msg->resetParams([ + "min_level" => self::MAJOR, + ]); + break; + case "n": + case "normal": + $msg->resetParams([ + "min_level" => self::NORMAL, + ]); + break; + case "v": + case "verbose": + $msg->resetParams([ + "min_level" => self::MINOR, + ]); + break; + case "D": + case "debug": + app::set_debug(); + $msg->resetParams([ + "min_level" => self::DEBUG, + ]); + break; + default: + throw exceptions::forbidden_value($verbosity, "verbosity", ["silent", "quiet", "normal", "verbose", "debug"]); + } + } +} diff --git a/php/src/output/_messenger.php b/php/src/output/_messenger.php index 702c4b7..bf5d4ca 100644 --- a/php/src/output/_messenger.php +++ b/php/src/output/_messenger.php @@ -1,44 +1,16 @@ clone($params); } - static final function __callStatic($name, $args) { - $name = str::us2camel($name); - call_user_func_array([static::get(), $name], $args); - } - ############################################################################# const DEBUG = IMessenger::DEBUG; diff --git a/php/src/output/con.php b/php/src/output/con.php new file mode 100644 index 0000000..b9127ef --- /dev/null +++ b/php/src/output/con.php @@ -0,0 +1,27 @@ +resetParams([ + "color" => $color, + ]); + } +} diff --git a/php/src/output/console.php b/php/src/output/console.php deleted file mode 100644 index 205f105..0000000 --- a/php/src/output/console.php +++ /dev/null @@ -1,75 +0,0 @@ -resetParams([ - "min_level" => msg::NONE, - ]); - break; - case "q": - case "quiet": - $console->resetParams([ - "min_level" => msg::MAJOR, - ]); - break; - case "n": - case "normal": - $console->resetParams([ - "min_level" => msg::NORMAL, - ]); - break; - case "v": - case "verbose": - $console->resetParams([ - "min_level" => msg::MINOR, - ]); - break; - case "D": - case "debug": - app::set_debug(); - $console->resetParams([ - "min_level" => msg::DEBUG, - ]); - break; - default: - throw exceptions::forbidden_value($verbosity, $kind, ["silent", "quiet", "normal", "verbose", "debug"]); - } - } - - static function set_color(bool $color=true): void { - console::reset_params([ - "color" => $color, - ]); - } -} diff --git a/php/src/output/log.php b/php/src/output/log.php index 2e1d3ec..967de5c 100644 --- a/php/src/output/log.php +++ b/php/src/output/log.php @@ -1,38 +1,41 @@ isEmpty()) { + $msg->addMessenger(new LogMessenger([ + "min_level" => msg::MINOR, + ])); + } + return $msg; } static function set_output(string $logfile): void { - self::create_or_reset_params([ + self::ensure_log()->resetParams([ "output" => $logfile, - ], StdMessenger::class, [ - "add_date" => true, - "min_level" => self::MINOR, ]); } } diff --git a/php/src/output/msg.php b/php/src/output/msg.php index d180d18..9bd4eee 100644 --- a/php/src/output/msg.php +++ b/php/src/output/msg.php @@ -1,67 +1,18 @@ resetParams($params); - return self::$out; - } - - static function write(...$values): void { self::$out->write(...$values); } - static function print(...$values): void { self::$out->print(...$values); } - - static function iwrite(int $indentLevel, ...$values): void { self::$out->iwrite($indentLevel, ...$values); } - static function iprint(int $indentLevel, ...$values): void { self::$out->iprint($indentLevel, ...$values); } -} -out::reset(); diff --git a/php/src/output/say.php b/php/src/output/say.php index 9d8b6d0..f5d5583 100644 --- a/php/src/output/say.php +++ b/php/src/output/say.php @@ -1,28 +1,17 @@ $max_level) { + throw new Exception("$level: level not allowed here"); + } + return $level; + } + + /** @var StdOutput la sortie standard */ + protected StdOutput $out; + + /** @var int level par défaut dans lequel les messages sont affichés */ + protected int $defaultLevel; + + /** @var int level minimum que doivent avoir les messages pour être affichés */ + protected int $minLevel; + + /** @var bool faut-il ajouter la date à chaque ligne? */ + protected bool $addDate; + + /** @var string format de la date */ + protected string $dateFormat; + + /** @var bool faut-il afficher les ids (p=id t=id a=id) */ + protected bool $showIds; + + /** @var ?string identifiant de ce messenger, à ajouter à chaque ligne */ + protected ?string $id; + + protected int $lastTitleId = 1; + + protected int $lastActionId = 1; + + protected function getLinePrefix(): ?string { + $linePrefix = null; + if ($this->addDate) { + $date = date_create()->format($this->dateFormat); + $linePrefix .= "$date "; + } + if ($this->showIds) { + if ($this->id !== null) $linePrefix .= "p=$this->id "; + $titleId = $this->_getTitleId(); + if ($titleId !== null) $linePrefix .= "t=$titleId "; + $actionId = $this->_getActionId(); + if ($actionId !== null) $linePrefix .= "a=$actionId "; + } + return $linePrefix; + } + + protected function decrLevel(int $level, int $amount=-1): int { + $level += $amount; + if ($level < self::MIN_LEVEL) $level = self::MIN_LEVEL; + return $level; + } + + protected function checkLevel(?int &$level): bool { + if ($level === null) $level = $this->defaultLevel; + elseif ($level < 0) $level = $this->decrLevel($this->defaultLevel, $level); + return $level >= $this->minLevel; + } + + protected function _printTitle( + ?string $linePrefix, int $level, + string $type, $content, + int $indentLevel, StdOutput $out + ): void { + $prefixes = self::GENERIC_PREFIXES[$level][$type]; + if ($prefixes[0]) $out->print(); + $content = cl::with($content); + if ($out->isColor()) { + $before = $prefixes[2]; + $prefix = $prefixes[3]; + $prefix2 = $prefix !== null? "$prefix ": null; + $suffix = $prefixes[4]; + $suffix2 = $suffix !== null? " $suffix": null; + $after = $prefixes[5]; + + $lines = $out->getLines(false, ...$content); + $maxlen = 0; + foreach ($lines as &$content) { + $line = $out->filterColors($content); + $len = mb_strlen($line); + if ($len > $maxlen) $maxlen = $len; + $content = [$content, $len]; + }; unset($content); + if ($before !== null) { + if ($linePrefix !== null) $out->write($linePrefix); + $out->iprint($indentLevel, $prefix, substr($before, 1), str_repeat($before[0], $maxlen), $suffix); + } + foreach ($lines as [$content, $len]) { + if ($linePrefix !== null) $out->write($linePrefix); + $padding = $len < $maxlen? str_repeat(" ", $maxlen - $len): null; + $out->iprint($indentLevel, $prefix2, $content, $padding, $suffix2); + } + if ($after !== null) { + if ($linePrefix !== null) $out->write($linePrefix); + $out->iprint($indentLevel, $prefix, substr($after, 1), str_repeat($after[0], $maxlen), $suffix); + } + } else { + $prefix = $prefixes[1]; + if ($prefix !== null) $prefix .= " "; + $prefix2 = str_repeat(" ", mb_strlen($prefix)); + $lines = $out->getLines(false, ...$content); + foreach ($lines as $content) { + if ($linePrefix !== null) $out->write($linePrefix); + $out->iprint($indentLevel, $prefix, $content); + $prefix = $prefix2; + } + } + } + + protected abstract function flushActions(bool $endAction=false, ?int $overrideLevel=null): void; + + protected function _printAction( + ?string $linePrefix, int $level, + bool $printContent, $content, + bool $printResult, ?bool $rsuccess, $rcontent, + int $indentLevel, StdOutput $out + ): void { + $color = $out->isColor(); + if ($rsuccess === true) $type = "success"; + elseif ($rsuccess === false) $type = "failure"; + else $type = "done"; + $rprefixes = self::RESULT_PREFIXES[$type]; + if ($color) { + $rprefix = $rprefixes[1]; + $rprefix2 = null; + if ($rprefix !== null) { + $rprefix .= " "; + $rprefix2 = $out->filterColors($out->filterContent($rprefix)); + $rprefix2 = str_repeat(" ", mb_strlen($rprefix2)); + } + } else { + $rprefix = $rprefixes[0]; + if ($rprefix !== null) $rprefix .= " "; + $rprefix2 = str_repeat(" ", mb_strlen($rprefix)); + } + if ($printContent && $printResult) { + A::ensure_array($content); + if ($rcontent) { + $content[] = ": "; + $content[] = $rcontent; + } + $lines = $out->getLines(false, ...$content); + foreach ($lines as $content) { + if ($linePrefix !== null) $out->write($linePrefix); + $out->iprint($indentLevel, $rprefix, $content); + $rprefix = $rprefix2; + } + } elseif ($printContent) { + $prefixes = self::GENERIC_PREFIXES[$level]["step"]; + if ($color) { + $prefix = $prefixes[1]; + if ($prefix !== null) $prefix .= " "; + $prefix2 = $out->filterColors($out->filterContent($prefix)); + $prefix2 = str_repeat(" ", mb_strlen($prefix2)); + $suffix = $prefixes[2]; + } else { + $prefix = $prefixes[0]; + if ($prefix !== null) $prefix .= " "; + $prefix2 = str_repeat(" ", mb_strlen($prefix)); + $suffix = null; + } + A::ensure_array($content); + $content[] = ":"; + $lines = $out->getLines(false, ...$content); + foreach ($lines as $content) { + if ($linePrefix !== null) $out->write($linePrefix); + $out->iprint($indentLevel, $prefix, $content, $suffix); + $prefix = $prefix2; + } + } elseif ($printResult) { + if (!$rcontent) { + if ($type === "success") $rcontent = $color? "succès": ""; + elseif ($type === "failure") $rcontent = $color? "échec": ""; + elseif ($type === "done") $rcontent = "fait"; + } + $rprefix = " $rprefix"; + $rprefix2 = " $rprefix2"; + $lines = $out->getLines(false, $rcontent); + foreach ($lines as $rcontent) { + if ($linePrefix !== null) $out->write($linePrefix); + $out->iprint($indentLevel, $rprefix, $rcontent); + $rprefix = $rprefix2; + } + } + } + + protected function _printGeneric( + ?string $linePrefix, int $level, + string $type, $content, + int $indentLevel, StdOutput $out + ): void { + $prefixes = self::GENERIC_PREFIXES[$level][$type]; + $content = cl::with($content); + if ($out->isColor()) { + $prefix = $prefixes[1]; + $prefix2 = null; + if ($prefix !== null) { + $prefix .= " "; + $prefix2 = $out->filterColors($out->filterContent($prefix)); + $prefix2 = str_repeat(" ", mb_strlen($prefix2)); + } + $suffix = $prefixes[2]; + $lines = $out->getLines(false, ...$content); + foreach ($lines as $content) { + if ($linePrefix !== null) $out->write($linePrefix); + $out->iprint($indentLevel, $prefix, $content, $suffix); + $prefix = $prefix2; + } + } else { + $prefix = $prefixes[0]; + if ($prefix !== null) $prefix .= " "; + $prefix2 = str_repeat(" ", mb_strlen($prefix)); + $lines = $out->getLines(false, ...$content); + foreach ($lines as $content) { + if ($linePrefix !== null) $out->write($linePrefix); + $out->iprint($indentLevel, $prefix, $content); + $prefix = $prefix2; + } + } + } + + protected function _printGenericOrException( + ?int $level, + string $type, $content, + int $indentLevel, StdOutput $out + ): void { + $linePrefix = $this->getLinePrefix(); + # si $content contient des exceptions, les afficher avec un level moindre + $exceptions = null; + if (is_array($content)) { + $valueContent = null; + foreach ($content as $value) { + if ($value instanceof Throwable || $value instanceof ExceptionShadow) { + $exceptions[] = $value; + } else { + $valueContent[] = $value; + } + } + if ($valueContent === null) $content = null; + elseif (count($valueContent) == 1) $content = $valueContent[0]; + else $content = $valueContent; + } elseif ($content instanceof Throwable || $content instanceof ExceptionShadow) { + $exceptions[] = $content; + $content = null; + } + + $flushActions = true; + $showContent = $this->checkLevel($level); + if ($content !== null && $showContent) { + $this->flushActions(); $flushActions = false; + $this->_printGeneric($linePrefix, $level, $type, $content, $indentLevel, $out); + } + if ($exceptions !== null) { + $level1 = $this->decrLevel($level); + $showTraceback = $this->checkLevel($level1); + foreach ($exceptions as $exception) { + # tout d'abord message + $message = exceptions::get_message($exception); + if ($showContent) { + if ($flushActions) { $this->flushActions(); $flushActions = false; } + $this->_printGeneric($linePrefix, $level, $type, $message, $indentLevel, $out); + } + # puis summary et traceback + if ($showTraceback) { + if ($flushActions) { $this->flushActions(); $flushActions = false; } + $summary = exceptions::get_summary($exception, false); + $this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out); + $traceback = exceptions::get_traceback($exception); + $this->_printGeneric($linePrefix, $level1, $type, $traceback, $indentLevel, $out); + } + } + } + } +} diff --git a/php/src/output/std/ConsoleMessenger.php b/php/src/output/std/ConsoleMessenger.php new file mode 100644 index 0000000..fd3d716 --- /dev/null +++ b/php/src/output/std/ConsoleMessenger.php @@ -0,0 +1,432 @@ + $color, + "indent" => $indent, + ]; + if ($output !== null) { + $this->err = $this->out = new StdOutput($output, $params); + } else { + $this->out = new StdOutput(STDOUT, $params); + $this->err = new StdOutput(STDERR, $params); + } + $this->defaultLevel = $defaultLevel; + $this->minLevel = $minLevel; + $this->addDate = $addDate; + $this->dateFormat = $dateFormat; + $this->id = $id; + $this->showIds = $showIds; + $this->inSection = false; + $this->section = null; + $this->titles = []; + $this->actions = []; + } + + function resetParams(?array $params=null): void { + $output = $params["output"] ?? null; + $color = $params["color"] ?? null; + $indent = $params["indent"] ?? null; + + $defaultLevel = $params["default_level"] ?? null; + if ($defaultLevel !== null) $defaultLevel = self::verifix_level($defaultLevel); + + $debug = $params["debug"] ?? null; + $minLevel = $params["min_level"] ?? null; + if ($debug !== null) $minLevel ??= self::DEBUG; + $minLevel ??= $params["verbosity"] ?? null; # alias + if ($minLevel !== null) $minLevel = self::verifix_level($minLevel, self::NONE); + + $addDate = $params["add_date"] ?? null; + $dateFormat = $params["date_format"] ?? null; + $id = $params["id"] ?? null; + + $params = [ + "output" => $output, + "color" => $color, + "indent" => $indent, + ]; + if ($this->out === $this->err) { + $this->out->resetParams($params); + } else { + # NB: si initialement [output] était null, et qu'on spécifie une valeur + # [output], alors les deux instances $out et $err sont mis à jour + # séparément avec la même valeur de output + # de plus, on ne peut plus revenir à la situation initiale avec une + # destination différente pour $out et $err + $this->out->resetParams($params); + $this->err->resetParams($params); + } + if ($defaultLevel !== null) $this->defaultLevel = $defaultLevel; + if ($minLevel !== null) $this->minLevel = $minLevel; + if ($addDate !== null) $this->addDate = boolval($addDate); + if ($dateFormat !== null) $this->dateFormat = $dateFormat; + if ($id !== null) $this->id = $id; + } + + function clone(?array $params=null): IMessenger { + $clone = clone $this; + if ($params !== null) $clone->resetParams($params); + #XXX faut-il marquer la section et les titres du clone à "print" => false? + # ou en faire des références au parent? + # dans tous les cas, on considère qu'il n'y a pas d'actions en cours, et on + # ne doit pas dépiler avec end() plus que l'état que l'on a eu lors du clone + return $clone; + } + + /** @var StdOutput la sortie d'erreur */ + protected StdOutput $err; + + /** @var bool est-on dans une section? */ + protected bool $inSection; + + /** @var array section qui est en attente d'affichage */ + protected ?array $section; + + function section($content, ?callable $func=null, ?int $level=null): void { + $this->_endSection(); + $this->inSection = true; + if (!$this->checkLevel($level)) return; + $this->section = [ + "line_prefix" => $this->getLinePrefix(), + "level" => $level, + "content" => $content, + "print_content" => true, + ]; + if ($func !== null) { + try { + $func($this); + } finally { + $this->_endSection(); + } + } + } + + protected function printSection() { + $section =& $this->section; + if ($section !== null && $section["print_content"]) { + $this->_printTitle( + $section["line_prefix"], $section["level"], + "section", $section["content"], + 0, $this->err); + $section["print_content"] = false; + } + } + + function _endSection(): void { + while ($this->actions) $this->adone(); + while ($this->titles) $this->_endTitle(); + $this->inSection = false; + $this->section = null; + } + + protected function getIndentLevel(bool $withActions=true): int { + $indentLevel = count($this->titles) - 1; + if ($indentLevel < 0) $indentLevel = 0; + if ($withActions) { + foreach ($this->actions as $action) { + if ($action["level"] < $this->minLevel) continue; + $indentLevel++; + } + } + return $indentLevel; + } + + protected array $titles; + + function _getTitleMark(): int { + return count($this->titles); + } + + function _getTitleId(): ?int { + return end($this->titles)["id"] ?? null; + } + + function title($content, ?callable $func=null, ?int $level=null): void { + if (!$this->checkLevel($level)) return; + $titleLevel = $this->_getTitleMark(); + // faire en deux temps pour linePrefix soit à jour + $this->titles[] = ["id" => $this->lastTitleId++]; + A::merge($this->titles[array_key_last($this->titles)], [ + "title_level" => $titleLevel, + "max_title_level" => null, + "line_prefix" => $this->getLinePrefix(), + "level" => $level, + "content" => $content, + "print_content" => true, + "descs" => [], + "print_descs" => false, + ]); + if ($func !== null) { + try { + $title =& $this->titles[array_key_last($this->titles)]; + $title["max_title_level"] = $titleLevel + 1; + $func($this); + } finally { + $title["max_title_level"] = null; + $this->_endTitle($titleLevel); + } + } + } + + function desc($content, ?int $level=null): void { + if (!$this->checkLevel($level)) return; + $desc = [ + "line_prefix" => $this->getLinePrefix(), + "level" => $level, + "content" => $content, + ]; + $key = array_key_last($this->titles); + if ($key !== null) { + $title =& $this->titles[$key]; + $title["descs"][] = $desc; + $title["print_descs"] = true; + } else { + # pas de titre en cours + $this->_printGeneric( + $desc["line_prefix"], $desc["level"], + "desc", $desc["content"], + 0, $this->err); + } + } + + protected function flushTitles(): void { + $this->printSection(); + $err = $this->err; + $indentLevel = 0; + foreach ($this->titles as &$title) { + if ($title["print_content"]) { + $this->_printTitle( + $title["line_prefix"], $title["level"], + "title", $title["content"], + $indentLevel, $err); + $title["print_content"] = false; + } + if ($title["print_descs"]) { + foreach ($title["descs"] as $desc) { + $this->_printGeneric( + $desc["line_prefix"], $desc["level"], + "desc", $desc["content"], + $indentLevel, $err); + } + $title["descs"] = []; + $title["print_descs"] = false; + } + $indentLevel++; + }; unset($title); + } + + function _endTitle(?int $until=null): void { + $title = $this->titles[array_key_last($this->titles)]; + $until ??= $title["max_title_level"]; + $until ??= $this->_getTitleMark() - 1; + while (count($this->titles) > $until) { + array_pop($this->titles); + } + } + + protected array $actions; + + function _getActionMark(): int { + return count($this->actions); + } + + function _getActionId(): ?int { + return end($this->actions)["id"] ?? null; + } + + protected function flushActions(bool $endAction=false, ?int $overrideLevel=null): void { + $this->flushTitles(); + $err = $this->err; + $indentLevel = $this->getIndentLevel(false); + $lastIndex = array_key_last($this->actions); + $index = 0; + foreach ($this->actions as &$action) { + $mergeResult = $index++ == $lastIndex && $endAction; + $linePrefix = $action["line_prefix"]; + $level = $overrideLevel?? $action["level"]; + $content = $action["content"]; + $printContent = $action["print_content"]; + $rsuccess = $action["result_success"]; + $rcontent = $action["result_content"]; + if ($level < $this->minLevel) continue; + if ($mergeResult) { + if (time() - $action["timestamp"] <= 2) { + $this->_printAction( + $linePrefix, $level, + $printContent, $content, + true, $rsuccess, $rcontent, + $indentLevel, $err); + } else { + # si l'action a pris plus de 2 secondes, ne pas fusionner pour que + # l'on voit le temps que ça a pris + $this->_printAction( + $linePrefix, $level, + $printContent, $content, + false, null, null, + $indentLevel, $err); + # recalculer une nouvelle ligne de préfixe pour le résultat + $linePrefix = $this->getLinePrefix(); + $this->_printAction( + $linePrefix, $level, + false, null, + true, $rsuccess, $rcontent, + $indentLevel, $err); + + } + } elseif ($printContent) { + $this->_printAction( + $linePrefix, $level, + $printContent, $content, + false, $rsuccess, $rcontent, + $indentLevel, $err); + $action["print_content"] = false; + } + $indentLevel++; + }; unset($action); + if ($endAction) $this->_endAction(); + } + + function action($content, ?callable $func=null, ?int $level=null): void { + $this->checkLevel($level); + $actionLevel = $this->_getActionMark(); + // faire en deux temps pour linePrefix soit à jour + $this->actions[] = ["id" => $this->lastActionId++]; + A::merge($this->actions[array_key_last($this->actions)], [ + "action_level" => $actionLevel, + "max_action_level" => null, + "timestamp" => time(), + "line_prefix" => $this->getLinePrefix(), + "level" => $level, + "content" => $content, + "print_content" => true, + "result_success" => null, + "result_content" => null, + ]); + if ($func !== null) { + try { + $action =& $this->actions[array_key_last($this->actions)]; + $action["max_action_level"] = $actionLevel + 1; + $result = $func($this); + if ($this->_getActionMark() > $actionLevel) { + $this->aresult($result); + } + } catch (Exception $e) { + $this->afailure($e); + throw $e; + } finally { + $action["max_action_level"] = null; + $this->_endAction($actionLevel); + } + } + } + + function step($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "step", $content, + $this->getIndentLevel(), $this->err); + } + + function asuccess($content=null, ?int $overrideLevel=null): void { + if (!$this->actions) $this->action(null); + $action =& $this->actions[array_key_last($this->actions)]; + $action["result_success"] = true; + $action["result_content"] = $content; + $this->flushActions(true, $overrideLevel); + } + + function afailure($content=null, ?int $overrideLevel=null): void { + if (!$this->actions) $this->action(null); + $action =& $this->actions[array_key_last($this->actions)]; + $action["result_success"] = false; + $action["result_content"] = $content; + $this->flushActions(true, $overrideLevel); + } + + function adone($content=null, ?int $overrideLevel=null): void { + if (!$this->actions) $this->action(null); + $action =& $this->actions[array_key_last($this->actions)]; + $action["result_success"] = null; + $action["result_content"] = $content; + $this->flushActions(true, $overrideLevel); + } + + function aresult($result=null, ?int $overrideLevel=null): void { + if (!$this->actions) $this->action(null); + if ($result === true) $this->asuccess(null, $overrideLevel); + elseif ($result === false) $this->afailure(null, $overrideLevel); + elseif ($result instanceof Exception) $this->afailure($result, $overrideLevel); + else $this->adone($result, $overrideLevel); + } + + function _endAction(?int $until=null): void { + $action = $this->actions[array_key_last($this->actions)]; + $until ??= $action["max_action_level"]; + $until ??= $this->_getActionMark() - 1; + while (count($this->actions) > $until) { + array_pop($this->actions); + } + } + + function print($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "print", $content, + $this->getIndentLevel(), $this->out); + } + + function info($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "info", $content, + $this->getIndentLevel(), $this->err); + } + + function note($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "note", $content, + $this->getIndentLevel(), $this->err); + } + + function warning($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "warning", $content, + $this->getIndentLevel(), $this->err); + } + + function error($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "error", $content, + $this->getIndentLevel(), $this->err); + } + + function end(bool $all=false): void { + if ($all) $this->_endSection(); + elseif ($this->actions) $this->_endAction(); + elseif ($this->titles) $this->_endTitle(); + else $this->_endSection(); + } +} diff --git a/php/src/output/std/LogMessenger.php b/php/src/output/std/LogMessenger.php new file mode 100644 index 0000000..bc7d978 --- /dev/null +++ b/php/src/output/std/LogMessenger.php @@ -0,0 +1,306 @@ +out = new StdOutput($output ?? STDERR, [ + "color" => $color, + "indent" => $indent, + ]); + $this->defaultLevel = $defaultLevel; + $this->minLevel = $minLevel; + $this->addDate = $addDate; + $this->dateFormat = $dateFormat; + $this->id = $id; + $this->showIds = $showIds; + $this->titles = []; + $this->actions = []; + } + + function resetParams(?array $params=null): void { + $output = $params["output"] ?? null; + $color = $params["color"] ?? null; + $indent = $params["indent"] ?? null; + + $defaultLevel = $params["default_level"] ?? null; + if ($defaultLevel !== null) $defaultLevel = self::verifix_level($defaultLevel); + + $debug = $params["debug"] ?? null; + $minLevel = $params["min_level"] ?? null; + if ($debug !== null) $minLevel ??= self::DEBUG; + $minLevel ??= $params["verbosity"] ?? null; # alias + if ($minLevel !== null) $minLevel = self::verifix_level($minLevel, self::NONE); + + $addDate = $params["add_date"] ?? null; + $dateFormat = $params["date_format"] ?? null; + $id = $params["id"] ?? null; + + $this->out->resetParams([ + "output" => $output, + "color" => $color, + "indent" => $indent, + ]); + if ($defaultLevel !== null) $this->defaultLevel = $defaultLevel; + if ($minLevel !== null) $this->minLevel = $minLevel; + if ($addDate !== null) $this->addDate = boolval($addDate); + if ($dateFormat !== null) $this->dateFormat = $dateFormat; + if ($id !== null) $this->id = $id; + } + + function clone(?array $params=null): IMessenger { + $clone = clone $this; + if ($params !== null) $clone->resetParams($params); + return $clone; + } + + function section($content, ?callable $func=null, ?int $level=null): void { + $this->_endSection(); + if (!$this->checkLevel($level)) return; + $this->_printTitle( + $this->getLinePrefix(), $level, + "section", $content, + 0, $this->out); + if ($func !== null) { + try { + $func($this); + } finally { + $this->_endSection(); + } + } + } + + function _endSection(): void { + $this->end(true); + } + + protected array $titles; + + function _getTitleMark(): int { + return count($this->titles); + } + + function _getTitleId(): ?int { + return end($this->titles)["id"] ?? null; + } + + function title($content, ?callable $func=null, ?int $level=null): void { + if (!$this->checkLevel($level)) return; + $titleLevel = $this->_getTitleMark(); + $this->titles[] = [ + "id" => $this->lastTitleId++, + "title_level" => $titleLevel, + "max_title_level" => null, + ]; + $this->_printTitle( + $this->getLinePrefix(), $level, + "title", $content, + $titleLevel, $this->out); + if ($func !== null) { + try { + $title =& $this->titles[array_key_last($this->titles)]; + $title["max_title_level"] = $titleLevel + 1; + $func($this); + } finally { + $title["max_title_level"] = null; + $this->_endTitle($titleLevel); + } + } + } + + function desc($content, ?int $level=null): void { + if (!$this->checkLevel($level)) return; + $titleLevel = end($this->titles)["title_level"] ?? 0; + $this->_printGeneric( + $this->getLinePrefix(), $level, + "desc", $content, + $titleLevel, $this->out); + + } + + function _endTitle(?int $until=null): void { + $title = $this->titles[array_key_last($this->titles)]; + $until ??= $title["max_title_level"]; + $until ??= $this->_getTitleMark() - 1; + while (count($this->titles) > $until) { + array_pop($this->titles); + } + } + + protected array $actions; + + function _getActionMark(): int { + return count($this->actions); + } + + function _getActionId(): ?int { + return end($this->actions)["id"] ?? null; + } + + function action($content, ?callable $func=null, ?int $level=null): void { + $this->checkLevel($level); + $actionLevel = $this->_getActionMark(); + $this->actions[] = [ + "id" => $this->lastActionId++, + "action_level" => $actionLevel, + "max_action_level" => null, + "level" => $level + ]; + $this->_printAction( + $this->getLinePrefix(), $level, + true, $content, + false, null, null, + $actionLevel, $this->out); + if ($func !== null) { + try { + $action =& $this->actions[array_key_last($this->actions)]; + $action["max_action_level"] = $actionLevel + 1; + $result = $func($this); + if ($this->_getActionMark() > $actionLevel) { + $this->aresult($result); + } + } catch (Exception $e) { + $this->afailure($e); + throw $e; + } finally { + $action["max_action_level"] = null; + $this->_endAction($actionLevel); + } + } + } + + protected function flushActions(bool $endAction=false, ?int $overrideLevel=null): void { + } + + function step($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "step", $content, + $this->getIndentLevel(), $this->out); + } + + function asuccess($content=null, ?int $overrideLevel=null): void { + if ($this->_getActionMark() == 0) $this->action(null); + $action = end($this->actions); + $level = $overrideLevel ?? $action["level"]; + $this->_printAction( + $this->getLinePrefix(), $level, + false, null, + true, true, $content, + $action["action_level"], $this->out); + $this->_endAction(); + } + + function afailure($content=null, ?int $overrideLevel=null): void { + if ($this->_getActionMark() == 0) $this->action(null); + $action = end($this->actions); + $level = $overrideLevel ?? $action["level"]; + $this->_printAction( + $this->getLinePrefix(), $level, + false, null, + true, false, $content, + $action["action_level"], $this->out); + $this->_endAction(); + } + + function adone($content=null, ?int $overrideLevel=null): void { + if ($this->_getActionMark() == 0) $this->action(null); + $action = end($this->actions); + $level = $overrideLevel ?? $action["level"]; + $this->_printAction( + $this->getLinePrefix(), $level, + false, null, + true, null, $content, + $action["action_level"], $this->out); + $this->_endAction(); + } + + function aresult($result=null, ?int $overrideLevel=null): void { + if ($this->_getActionMark() == 0) $this->action(null); + if ($result === true) $this->asuccess(null, $overrideLevel); + elseif ($result === false) $this->afailure(null, $overrideLevel); + elseif ($result instanceof Exception) $this->afailure($result, $overrideLevel); + else $this->adone($result, $overrideLevel); + } + + function _endAction(?int $until=null): void { + $action = $this->actions[array_key_last($this->actions)]; + $until ??= $action["max_action_level"]; + $until ??= $this->_getActionMark() - 1; + while (count($this->actions) > $until) { + array_pop($this->actions); + } + } + + protected function getIndentLevel(bool $withActions=true): int { + $indentLevel = count($this->titles) - 1; + if ($indentLevel < 0) $indentLevel = 0; + if ($withActions) $indentLevel += count($this->actions); + return $indentLevel; + } + + function print($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "print", $content, + $this->getIndentLevel(), $this->out); + } + + function info($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "info", $content, + $this->getIndentLevel(), $this->out); + } + + function note($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "note", $content, + $this->getIndentLevel(), $this->out); + } + + function warning($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "warning", $content, + $this->getIndentLevel(), $this->out); + } + + function error($content, ?int $level=null): void { + $this->_printGenericOrException( + $level, "error", $content, + $this->getIndentLevel(), $this->out); + } + + function end(bool $all=false): void { + if ($all) { + while ($this->actions) $this->adone(); + while ($this->titles) $this->_endTitle(); + } elseif ($this->actions) { + $this->_endAction(); + } elseif ($this->titles) { + $this->_endTitle(); + } + } +} diff --git a/php/src/output/std/NullMessenger.php b/php/src/output/std/NullMessenger.php new file mode 100644 index 0000000..ca4f816 --- /dev/null +++ b/php/src/output/std/NullMessenger.php @@ -0,0 +1,58 @@ +msgs = []; foreach ($msgs as $msg) { if ($msg !== null) $this->msgs[] = $msg; } } /** @var IMessenger[] */ - protected $msgs; + protected ?array $msgs = []; + + function isEmpty(): bool { + return !$this->msgs; + } + + function addMessenger(IMessenger $msg): self { + $this->msgs[] = $msg; + return $this; + } + + function resetParams(?array $params=null): void { + foreach ($this->msgs as $msg) { + $msg->resetParams($params); + } + } - function resetParams(?array $params=null): void { foreach ($this->msgs as $msg) { $msg->resetParams($params); } } function clone(?array $params=null): self { $clone = clone $this; foreach ($clone->msgs as &$msg) { @@ -30,6 +42,7 @@ class ProxyMessenger implements IMessenger { }; unset($msg); return $clone; } + function section($content, ?callable $func=null, ?int $level=null): void { $useFunc = false; foreach ($this->msgs as $msg) { @@ -47,6 +60,7 @@ class ProxyMessenger implements IMessenger { } } } + function title($content, ?callable $func=null, ?int $level=null): void { $useFunc = false; $untils = []; @@ -71,7 +85,13 @@ class ProxyMessenger implements IMessenger { } } } - function desc($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->desc($content, $level); } } + + function desc($content, ?int $level=null): void { + foreach ($this->msgs as $msg) { + $msg->desc($content, $level); + } + } + function action($content, ?callable $func=null, ?int $level=null): void { $useFunc = false; $untils = []; @@ -107,15 +127,70 @@ class ProxyMessenger implements IMessenger { } } } - function step($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->step($content, $level); } } - function asuccess($content=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->asuccess($content, $overrideLevel); } } - function afailure($content=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->afailure($content, $overrideLevel); } } - function adone($content=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->adone($content, $overrideLevel); } } - function aresult($result=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->aresult($result, $overrideLevel); } } - function print($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->print($content, $level); } } - function info($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->info($content, $level); } } - function note($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->note($content, $level); } } - function warning($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->warning($content, $level); } } - function error($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->error($content, $level); } } - function end(bool $all=false): void { foreach ($this->msgs as $msg) { $msg->end($all); } } + + function step($content, ?int $level=null): void { + foreach ($this->msgs as $msg) { + $msg->step($content, $level); + } + } + + function asuccess($content=null, ?int $overrideLevel=null): void { + foreach ($this->msgs as $msg) { + $msg->asuccess($content, $overrideLevel); + } + } + + function afailure($content=null, ?int $overrideLevel=null): void { + foreach ($this->msgs as $msg) { + $msg->afailure($content, $overrideLevel); + } + } + + function adone($content=null, ?int $overrideLevel=null): void { + foreach ($this->msgs as $msg) { + $msg->adone($content, $overrideLevel); + } + } + + function aresult($result=null, ?int $overrideLevel=null): void { + foreach ($this->msgs as $msg) { + $msg->aresult($result, $overrideLevel); + } + } + + function print($content, ?int $level=null): void { + foreach ($this->msgs as $msg) { + $msg->print($content, $level); + } + } + + function info($content, ?int $level=null): void { + foreach ($this->msgs as $msg) { + $msg->info($content, $level); + } + } + + function note($content, ?int $level=null): void { + foreach ($this->msgs as $msg) { + $msg->note($content, $level); + } + } + + function warning($content, ?int $level=null): void { + foreach ($this->msgs as $msg) { + $msg->warning($content, $level); + } + } + + function error($content, ?int $level=null): void { + foreach ($this->msgs as $msg) { + $msg->error($content, $level); + } + } + + function end(bool $all=false): void { + foreach ($this->msgs as $msg) { + $msg->end($all); + } + } } diff --git a/php/src/output/std/StdMessenger.php b/php/src/output/std/StdMessenger.php deleted file mode 100644 index 4ac8d83..0000000 --- a/php/src/output/std/StdMessenger.php +++ /dev/null @@ -1,724 +0,0 @@ - self::DEBUG, - "minor" => self::MINOR, "verbose" => self::MINOR, - "normal" => self::NORMAL, - "major" => self::MAJOR, "quiet" => self::MAJOR, - "none" => self::NONE, "silent" => self::NONE, - ]; - - protected static function verifix_level($level, int $max_level=self::MAX_LEVEL): int { - if (!in_array($level, self::VALID_LEVELS, true)) { - $level = cl::get(self::LEVEL_MAP, $level, $level); - } - if (!in_array($level, self::VALID_LEVELS, true)) { - throw new Exception("$level: invalid level"); - } - if ($level > $max_level) { - throw new Exception("$level: level not allowed here"); - } - return $level; - } - - const GENERIC_PREFIXES = [ - self::MAJOR => [ - "section" => [true, "SECTION!", "===", "=", "=", "==="], - "title" => [false, "TITLE!", null, "T", "", "==="], - "desc" => ["DESC!", ">", ""], - "error" => ["CRIT.ERROR!", "E!", ""], - "warning" => ["CRIT.WARNING!", "W!", ""], - "note" => ["ATTENTION!", "N!", ""], - "info" => ["IMPORTANT!", "N!", ""], - "step" => ["*", ".", ""], - "print" => [null, null, null], - ], - self::NORMAL => [ - "section" => [true, "SECTION:", "---", "-", "-", "---"], - "title" => [false, "TITLE:", null, "T", "", "---"], - "desc" => ["DESC:", ">", ""], - "error" => ["ERROR:", "E", ""], - "warning" => ["WARNING:", "W", ""], - "note" => ["NOTE:", "N", ""], - "info" => ["INFO:", "I", ""], - "step" => ["*", ".", ""], - "print" => [null, null, null], - ], - self::MINOR => [ - "section" => [true, "section", null, ">>", "<<", null], - "title" => [false, "title", null, "t", "", null], - "desc" => ["desc", ">", ""], - "error" => ["error", "E", ""], - "warning" => ["warning", "W", ""], - "note" => ["note", "N", ""], - "info" => ["info", "I", ""], - "step" => ["*", ".", ""], - "print" => [null, null, null], - ], - self::DEBUG => [ - "section" => [true, "section", null, ">>", "<<", null], - "title" => [false, "title", null, "t", "", null], - "desc" => ["desc", ">", ""], - "error" => ["debugE", "e", ""], - "warning" => ["debugW", "w", ""], - "note" => ["debugN", "i", ""], - "info" => ["debug", "D", ""], - "step" => ["*", ".", ""], - "print" => [null, null, null], - ], - ]; - - const RESULT_PREFIXES = [ - "failure" => ["(FAILURE)", ""], - "success" => ["(SUCCESS)", ""], - "done" => [null, null], - ]; - - function __construct(?array $params=null) { - $output = cl::get($params, "output"); - $color = cl::get($params, "color"); - $indent = cl::get($params, "indent", static::INDENT); - - $defaultLevel = cl::get($params, "default_level"); - if ($defaultLevel === null) $defaultLevel = self::NORMAL; - $defaultLevel = self::verifix_level($defaultLevel); - - $debug = boolval(cl::get($params, "debug")); - $minLevel = cl::get($params, "min_level"); - if ($minLevel === null && $debug) $minLevel = self::DEBUG; - if ($minLevel === null) $minLevel = cl::get($params, "verbosity"); # alias - if ($minLevel === null) $minLevel = self::NORMAL; - $minLevel = self::verifix_level($minLevel, self::NONE); - - $addDate = boolval(cl::get($params, "add_date")); - $dateFormat = cl::get($params, "date_format", static::DATE_FORMAT); - $id = cl::get($params, "id"); - - $params = [ - "color" => $color, - "indent" => $indent, - ]; - if ($output !== null) { - $this->err = $this->out = new StdOutput($output, $params); - } else { - $this->out = new StdOutput(STDOUT, $params); - $this->err = new StdOutput(STDERR, $params); - } - $this->defaultLevel = $defaultLevel; - $this->minLevel = $minLevel; - $this->addDate = $addDate; - $this->dateFormat = $dateFormat; - $this->id = $id; - $this->inSection = false; - $this->titles = []; - $this->actions = []; - } - - function resetParams(?array $params=null): void { - $output = cl::get($params, "output"); - $color = cl::get($params, "color"); - $indent = cl::get($params, "indent"); - - $defaultLevel = cl::get($params, "default_level"); - if ($defaultLevel !== null) $defaultLevel = self::verifix_level($defaultLevel); - - $debug = cl::get($params, "debug"); - $minLevel = cl::get($params, "min_level"); - if ($minLevel === null && $debug !== null) $minLevel = $debug? self::DEBUG: self::NORMAL; - if ($minLevel === null) $minLevel = cl::get($params, "verbosity"); # alias - if ($minLevel !== null) $minLevel = self::verifix_level($minLevel, self::NONE); - - $addDate = cl::get($params, "add_date"); - $dateFormat = cl::get($params, "date_format"); - $id = cl::get($params, "id"); - - $params = [ - "output" => $output, - "color" => $color, - "indent" => $indent, - ]; - if ($this->out === $this->err) { - $this->out->resetParams($params); - } else { - # NB: si initialement [output] était null, et qu'on spécifie une valeur - # [output], alors les deux instances $out et $err sont mis à jour - # séparément avec la même valeur de output - # de plus, on ne peut plus revenir à la situation initiale avec une - # destination différente pour $out et $err - $this->out->resetParams($params); - $this->err->resetParams($params); - } - if ($defaultLevel !== null) $this->defaultLevel = $defaultLevel; - if ($minLevel !== null) $this->minLevel = $minLevel; - if ($addDate !== null) $this->addDate = boolval($addDate); - if ($dateFormat !== null) $this->dateFormat = $dateFormat; - if ($id !== null) $this->id = $id; - } - - function clone(?array $params=null): IMessenger { - $clone = clone $this; - if ($params !== null) $clone->resetParams($params); - #XXX faut-il marquer la section et les titres du clone à "print" => false? - # ou en faire des références au parent? - # dans tous les cas, on considère qu'il n'y a pas d'actions en cours, et on - # ne doit pas dépiler avec end() plus que l'état que l'on a eu lors du clone - return $clone; - } - - /** @var StdOutput la sortie standard */ - protected $out; - - /** @var StdOutput la sortie d'erreur */ - protected $err; - - /** @var int level par défaut dans lequel les messages sont affichés */ - protected $defaultLevel; - - /** @var int level minimum que doivent avoir les messages pour être affichés */ - protected $minLevel; - - /** @var bool faut-il ajouter la date à chaque ligne? */ - protected $addDate; - - /** @var string format de la date */ - protected $dateFormat; - - /** @var ?string identifiant de ce messenger, à ajouter à chaque ligne */ - protected $id; - - protected function getLinePrefix(): ?string { - $linePrefix = null; - if ($this->addDate) { - $date = date_create()->format($this->dateFormat); - $linePrefix .= "$date "; - } - if ($this->id !== null) { - $linePrefix .= "$this->id "; - } - return $linePrefix; - } - - protected function decrLevel(int $level, int $amount=-1): int { - $level += $amount; - if ($level < self::MIN_LEVEL) $level = self::MIN_LEVEL; - return $level; - } - - protected function checkLevel(?int &$level): bool { - if ($level === null) $level = $this->defaultLevel; - elseif ($level < 0) $level = $this->decrLevel($this->defaultLevel, $level); - return $level >= $this->minLevel; - } - - protected function getIndentLevel(bool $withActions=true): int { - $indentLevel = count($this->titles) - 1; - if ($indentLevel < 0) $indentLevel = 0; - if ($withActions) { - foreach ($this->actions as $action) { - if ($action["level"] < $this->minLevel) continue; - $indentLevel++; - } - } - return $indentLevel; - } - - protected function _printTitle( - ?string $linePrefix, int $level, - string $type, $content, - int $indentLevel, StdOutput $out - ): void { - $prefixes = self::GENERIC_PREFIXES[$level][$type]; - if ($prefixes[0]) $out->print(); - $content = cl::with($content); - if ($out->isColor()) { - $before = $prefixes[2]; - $prefix = $prefixes[3]; - $prefix2 = $prefix !== null? "$prefix ": null; - $suffix = $prefixes[4]; - $suffix2 = $suffix !== null? " $suffix": null; - $after = $prefixes[5]; - - $lines = $out->getLines(false, ...$content); - $maxlen = 0; - foreach ($lines as &$content) { - $line = $out->filterColors($content); - $len = mb_strlen($line); - if ($len > $maxlen) $maxlen = $len; - $content = [$content, $len]; - }; unset($content); - if ($before !== null) { - if ($linePrefix !== null) $out->write($linePrefix); - $out->iprint($indentLevel, $prefix, substr($before, 1), str_repeat($before[0], $maxlen), $suffix); - } - foreach ($lines as [$content, $len]) { - if ($linePrefix !== null) $out->write($linePrefix); - $padding = $len < $maxlen? str_repeat(" ", $maxlen - $len): null; - $out->iprint($indentLevel, $prefix2, $content, $padding, $suffix2); - } - if ($after !== null) { - if ($linePrefix !== null) $out->write($linePrefix); - $out->iprint($indentLevel, $prefix, substr($after, 1), str_repeat($after[0], $maxlen), $suffix); - } - } else { - $prefix = $prefixes[1]; - if ($prefix !== null) $prefix .= " "; - $prefix2 = str_repeat(" ", mb_strlen($prefix)); - $lines = $out->getLines(false, ...$content); - foreach ($lines as $content) { - if ($linePrefix !== null) $out->write($linePrefix); - $out->iprint($indentLevel, $prefix, $content); - $prefix = $prefix2; - } - } - } - - protected function _printAction( - ?string $linePrefix, int $level, - bool $printContent, $content, - bool $printResult, ?bool $rsuccess, $rcontent, - int $indentLevel, StdOutput $out - ): void { - $color = $out->isColor(); - if ($rsuccess === true) $type = "success"; - elseif ($rsuccess === false) $type = "failure"; - else $type = "done"; - $rprefixes = self::RESULT_PREFIXES[$type]; - if ($color) { - $rprefix = $rprefixes[1]; - $rprefix2 = null; - if ($rprefix !== null) { - $rprefix .= " "; - $rprefix2 = $out->filterColors($out->filterContent($rprefix)); - $rprefix2 = str_repeat(" ", mb_strlen($rprefix2)); - } - } else { - $rprefix = $rprefixes[0]; - if ($rprefix !== null) $rprefix .= " "; - $rprefix2 = str_repeat(" ", mb_strlen($rprefix)); - } - if ($printContent && $printResult) { - A::ensure_array($content); - if ($rcontent) { - $content[] = ": "; - $content[] = $rcontent; - } - $lines = $out->getLines(false, ...$content); - foreach ($lines as $content) { - if ($linePrefix !== null) $out->write($linePrefix); - $out->iprint($indentLevel, $rprefix, $content); - $rprefix = $rprefix2; - } - } elseif ($printContent) { - $prefixes = self::GENERIC_PREFIXES[$level]["step"]; - if ($color) { - $prefix = $prefixes[1]; - if ($prefix !== null) $prefix .= " "; - $prefix2 = $out->filterColors($out->filterContent($prefix)); - $prefix2 = str_repeat(" ", mb_strlen($prefix2)); - $suffix = $prefixes[2]; - } else { - $prefix = $prefixes[0]; - if ($prefix !== null) $prefix .= " "; - $prefix2 = str_repeat(" ", mb_strlen($prefix)); - $suffix = null; - } - A::ensure_array($content); - $content[] = ":"; - $lines = $out->getLines(false, ...$content); - foreach ($lines as $content) { - if ($linePrefix !== null) $out->write($linePrefix); - $out->iprint($indentLevel, $prefix, $content, $suffix); - $prefix = $prefix2; - } - } elseif ($printResult) { - if (!$rcontent) { - if ($type === "success") $rcontent = $color? "succès": ""; - elseif ($type === "failure") $rcontent = $color? "échec": ""; - elseif ($type === "done") $rcontent = "fait"; - } - $rprefix = " $rprefix"; - $rprefix2 = " $rprefix2"; - $lines = $out->getLines(false, $rcontent); - foreach ($lines as $rcontent) { - if ($linePrefix !== null) $out->write($linePrefix); - $out->iprint($indentLevel, $rprefix, $rcontent); - $rprefix = $rprefix2; - } - } - } - - protected function _printGeneric( - ?string $linePrefix, int $level, - string $type, $content, - int $indentLevel, StdOutput $out - ): void { - $prefixes = self::GENERIC_PREFIXES[$level][$type]; - $content = cl::with($content); - if ($out->isColor()) { - $prefix = $prefixes[1]; - $prefix2 = null; - if ($prefix !== null) { - $prefix .= " "; - $prefix2 = $out->filterColors($out->filterContent($prefix)); - $prefix2 = str_repeat(" ", mb_strlen($prefix2)); - } - $suffix = $prefixes[2]; - $lines = $out->getLines(false, ...$content); - foreach ($lines as $content) { - if ($linePrefix !== null) $out->write($linePrefix); - $out->iprint($indentLevel, $prefix, $content, $suffix); - $prefix = $prefix2; - } - } else { - $prefix = $prefixes[0]; - if ($prefix !== null) $prefix .= " "; - $prefix2 = str_repeat(" ", mb_strlen($prefix)); - $lines = $out->getLines(false, ...$content); - foreach ($lines as $content) { - if ($linePrefix !== null) $out->write($linePrefix); - $out->iprint($indentLevel, $prefix, $content); - $prefix = $prefix2; - } - } - } - - protected function _printGenericOrException( - ?int $level, - string $type, $content, - int $indentLevel, StdOutput $out - ): void { - $linePrefix = $this->getLinePrefix(); - # si $content contient des exceptions, les afficher avec un level moindre - $exceptions = null; - if (is_array($content)) { - $valueContent = null; - foreach ($content as $value) { - if ($value instanceof Throwable || $value instanceof ExceptionShadow) { - $exceptions[] = $value; - } else { - $valueContent[] = $value; - } - } - if ($valueContent === null) $content = null; - elseif (count($valueContent) == 1) $content = $valueContent[0]; - else $content = $valueContent; - } elseif ($content instanceof Throwable || $content instanceof ExceptionShadow) { - $exceptions[] = $content; - $content = null; - } - - $printActions = true; - $showContent = $this->checkLevel($level); - if ($content !== null && $showContent) { - $this->printActions(); $printActions = false; - $this->_printGeneric($linePrefix, $level, $type, $content, $indentLevel, $out); - } - if ($exceptions !== null) { - $level1 = $this->decrLevel($level); - $showTraceback = $this->checkLevel($level1); - foreach ($exceptions as $exception) { - # tout d'abord message - $message = exceptions::get_message($exception); - if ($showContent) { - if ($printActions) { $this->printActions(); $printActions = false; } - $this->_printGeneric($linePrefix, $level, $type, $message, $indentLevel, $out); - } - # puis summary et traceback - if ($showTraceback) { - if ($printActions) { $this->printActions(); $printActions = false; } - $summary = exceptions::get_summary($exception, false); - $this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out); - $traceback = exceptions::get_traceback($exception); - $this->_printGeneric($linePrefix, $level1, $type, $traceback, $indentLevel, $out); - } - } - } - } - - /** @var bool est-on dans une section? */ - protected $inSection; - - /** @var array section qui est en attente d'affichage */ - protected $section; - - function section($content, ?callable $func=null, ?int $level=null): void { - $this->_endSection(); - $this->inSection = true; - if (!$this->checkLevel($level)) return; - $this->section = [ - "line_prefix" => $this->getLinePrefix(), - "level" => $level, - "content" => $content, - "print_content" => true, - ]; - if ($func !== null) { - try { - $func($this); - } finally { - $this->_endSection(); - } - } - } - - protected function printSection() { - $section =& $this->section; - if ($section !== null && $section["print_content"]) { - $this->_printTitle( - $section["line_prefix"], $section["level"], - "section", $section["content"], - 0, $this->err); - $section["print_content"] = false; - } - } - - function _endSection(): void { - $this->inSection = false; - $this->section = null; - } - - /** @var array */ - protected $titles; - - /** @var array */ - protected $title; - - function _getTitleMark(): int { - return count($this->titles); - } - - function title($content, ?callable $func=null, ?int $level=null): void { - if (!$this->checkLevel($level)) return; - $until = $this->_getTitleMark(); - $this->titles[] = [ - "line_prefix" => $this->getLinePrefix(), - "level" => $level, - "content" => $content, - "print_content" => true, - "descs" => [], - "print_descs" => false, - ]; - $this->title =& $this->titles[$until]; - if ($func !== null) { - try { - $func($this); - } finally { - $this->_endTitle($until); - } - } - } - - function desc($content, ?int $level=null): void { - if (!$this->checkLevel($level)) return; - $title =& $this->title; - $title["descs"][] = [ - "line_prefix" => $this->getLinePrefix(), - "level" => $level, - "content" => $content, - ]; - $title["print_descs"] = true; - } - - protected function printTitles(): void { - $this->printSection(); - $err = $this->err; - $indentLevel = 0; - foreach ($this->titles as &$title) { - if ($title["print_content"]) { - $this->_printTitle( - $title["line_prefix"], $title["level"], - "title", $title["content"], - $indentLevel, $err); - $title["print_content"] = false; - } - if ($title["print_descs"]) { - foreach ($title["descs"] as $desc) { - $this->_printGeneric( - $desc["line_prefix"], $desc["level"], - "desc", $desc["content"], - $indentLevel, $err); - } - $title["descs"] = []; - $title["print_descs"] = false; - } - $indentLevel++; - }; unset($title); - } - - function _endTitle(?int $until=null): void { - if ($until === null) $until = $this->_getTitleMark() - 1; - while (count($this->titles) > $until) { - array_pop($this->titles); - } - if ($this->titles) { - $this->title =& $this->titles[count($this->titles) - 1]; - } else { - $this->titles = []; - unset($this->title); - } - } - - /** @var array */ - protected $actions; - - /** @var array */ - protected $action; - - function _getActionMark(): int { - return count($this->actions); - } - - function action($content, ?callable $func=null, ?int $level=null): void { - $this->checkLevel($level); - $until = $this->_getActionMark(); - $this->actions[] = [ - "line_prefix" => $this->getLinePrefix(), - "level" => $level, - "content" => $content, - "print_content" => true, - "result_success" => null, - "result_content" => null, - ]; - $this->action =& $this->actions[$until]; - if ($func !== null) { - try { - $result = $func($this); - if ($this->_getActionMark() > $until) { - $this->aresult($result); - } - } catch (Exception $e) { - $this->afailure($e); - throw $e; - } finally { - $this->_endAction($until); - } - } - } - - function printActions(bool $endAction=false, ?int $overrideLevel=null): void { - $this->printTitles(); - $err = $this->err; - $indentLevel = $this->getIndentLevel(false); - $lastIndex = count($this->actions) - 1; - $index = 0; - foreach ($this->actions as &$action) { - $mergeResult = $index++ == $lastIndex && $endAction; - $linePrefix = $action["line_prefix"]; - $level = $overrideLevel?? $action["level"]; - $content = $action["content"]; - $printContent = $action["print_content"]; - $rsuccess = $action["result_success"]; - $rcontent = $action["result_content"]; - if ($level < $this->minLevel) continue; - if ($mergeResult) { - $this->_printAction( - $linePrefix, $level, - $printContent, $content, - true, $rsuccess, $rcontent, - $indentLevel, $err); - } elseif ($printContent) { - $this->_printAction( - $linePrefix, $level, - $printContent, $content, - false, $rsuccess, $rcontent, - $indentLevel, $err); - $action["print_content"] = false; - } - $indentLevel++; - }; unset($action); - if ($endAction) $this->_endAction(); - } - - function step($content, ?int $level=null): void { - $this->_printGenericOrException($level, "step", $content, $this->getIndentLevel(), $this->err); - } - - function asuccess($content=null, ?int $overrideLevel=null): void { - if (!$this->actions) $this->action(null); - $this->action["result_success"] = true; - $this->action["result_content"] = $content; - $this->printActions(true, $overrideLevel); - } - - function afailure($content=null, ?int $overrideLevel=null): void { - if (!$this->actions) $this->action(null); - $this->action["result_success"] = false; - $this->action["result_content"] = $content; - $this->printActions(true, $overrideLevel); - } - - function adone($content=null, ?int $overrideLevel=null): void { - if (!$this->actions) $this->action(null); - $this->action["result_success"] = null; - $this->action["result_content"] = $content; - $this->printActions(true, $overrideLevel); - } - - function aresult($result=null, ?int $overrideLevel=null): void { - if (!$this->actions) $this->action(null); - if ($result === true) $this->asuccess(null, $overrideLevel); - elseif ($result === false) $this->afailure(null, $overrideLevel); - elseif ($result instanceof Exception) $this->afailure($result, $overrideLevel); - else $this->adone($result, $overrideLevel); - } - - function _endAction(?int $until=null): void { - if ($until === null) $until = $this->_getActionMark() - 1; - while (count($this->actions) > $until) { - array_pop($this->actions); - } - if ($this->actions) { - $this->action =& $this->actions[count($this->actions) - 1]; - } else { - $this->actions = []; - unset($this->action); - } - } - - function print($content, ?int $level=null): void { - $this->_printGenericOrException($level, "print", $content, $this->getIndentLevel(), $this->out); - } - - function info($content, ?int $level=null): void { - $this->_printGenericOrException($level, "info", $content, $this->getIndentLevel(), $this->err); - } - - function note($content, ?int $level=null): void { - $this->_printGenericOrException($level, "note", $content, $this->getIndentLevel(), $this->err); - } - - function warning($content, ?int $level=null): void { - $this->_printGenericOrException($level, "warning", $content, $this->getIndentLevel(), $this->err); - } - - function error($content, ?int $level=null): void { - $this->_printGenericOrException($level, "error", $content, $this->getIndentLevel(), $this->err); - } - - function end(bool $all=false): void { - if ($all) { - while ($this->actions) $this->adone(); - while ($this->titles) $this->_endTitle(); - $this->_endSection(); - } elseif ($this->actions) { - $this->_endAction(); - } elseif ($this->titles) { - $this->_endTitle(); - } else { - $this->_endSection(); - } - } -} diff --git a/php/src/output/std/StdOutput.php b/php/src/output/std/StdOutput.php index c30c466..945b9b3 100644 --- a/php/src/output/std/StdOutput.php +++ b/php/src/output/std/StdOutput.php @@ -79,12 +79,12 @@ class StdOutput { } function resetParams(?array $params=null): void { - $output = cl::get($params, "output"); + $output = $params["output"] ?? null; $maskErrors = null; - $color = cl::get($params, "color"); - $filterTags = cl::get($params, "filter_tags"); - $indent = cl::get($params, "indent"); - $flush = cl::get($params, "flush"); + $color = $params["color"] ?? null; + $filterTags = $params["filter_tags"] ?? null; + $indent = $params["indent"] ?? null; + $flush = $params["flush"] ?? null; if ($output instanceof Stream) $output = $output->getResource(); if ($output !== null) { @@ -105,14 +105,14 @@ class StdOutput { else $message = "$output: open error"; throw new Exception($message); } - if ($flush === null) $flush = true; + $flush ??= true; } else { $outf = $output; } $this->outf = $outf; $this->maskErrors = $maskErrors; - if ($color === null) $color = stream_isatty($outf); - if ($flush === null) $flush = false; + $color ??= stream_isatty($outf); + $flush ??= false; } if ($color !== null) $this->color = boolval($color); if ($filterTags !== null) $this->filterTags = boolval($filterTags); @@ -124,23 +124,23 @@ class StdOutput { protected $outf; /** @var bool faut-il masquer les erreurs d'écriture? */ - protected $maskErrors; + protected ?bool $maskErrors; /** @var bool faut-il autoriser la sortie en couleur? */ - protected $color; + protected bool $color = false; function isColor(): bool { return $this->color; } /** @var bool faut-il enlever les tags dans la sortie? */ - protected $filterTags; + protected bool $filterTags = true; /** @var string indentation unitaire */ - protected $indent; + protected string $indent = " "; /** @var bool faut-il flush le fichier après l'écriture de chaque ligne */ - protected $flush; + protected bool $flush = true; function isatty(): bool { return stream_isatty($this->outf); @@ -167,6 +167,7 @@ class StdOutput { $text .= "m"; return $text; } + function filterContent(string $text): string { # couleur au début $text = preg_replace_callback('/]*)>/', [self::class, "replace_colors"], $text); @@ -178,6 +179,7 @@ class StdOutput { } return $text; } + function filterColors(string $text): string { return preg_replace('/\x1B\[.*?m/', "", $text); } diff --git a/php/src/output/std/_IMessenger.php b/php/src/output/std/_IMessenger.php index 9b54b59..ed1c70f 100644 --- a/php/src/output/std/_IMessenger.php +++ b/php/src/output/std/_IMessenger.php @@ -7,13 +7,84 @@ use nulib\output\IMessenger; * Interface _IMessenger: méthodes privées de IMessenger */ interface _IMessenger extends IMessenger { + const INDENT = " "; + + const DATE_FORMAT = 'Y-m-d\TH:i:s.u'; + + const VALID_LEVELS = [self::DEBUG, self::MINOR, self::NORMAL, self::MAJOR, self::NONE]; + + const LEVEL_MAP = [ + "debug" => self::DEBUG, + "minor" => self::MINOR, "verbose" => self::MINOR, + "normal" => self::NORMAL, + "major" => self::MAJOR, "quiet" => self::MAJOR, + "none" => self::NONE, "silent" => self::NONE, + ]; + + const GENERIC_PREFIXES = [ + self::MAJOR => [ + "section" => [true, "SECTION!", "===", "=", "=", "==="], + "title" => [false, "TITLE!", null, "T", "", "==="], + "desc" => ["DESC!", ">", ""], + "error" => ["CRIT.ERROR!", "E!", ""], + "warning" => ["CRIT.WARNING!", "W!", ""], + "note" => ["ATTENTION!", "N!", ""], + "info" => ["IMPORTANT!", "N!", ""], + "step" => ["*", ".", ""], + "print" => [null, null, null], + ], + self::NORMAL => [ + "section" => [true, "SECTION:", "---", "-", "-", "---"], + "title" => [false, "TITLE:", null, "T", "", "---"], + "desc" => ["DESC:", ">", ""], + "error" => ["ERROR:", "E", ""], + "warning" => ["WARNING:", "W", ""], + "note" => ["NOTE:", "N", ""], + "info" => ["INFO:", "I", ""], + "step" => ["*", ".", ""], + "print" => [null, null, null], + ], + self::MINOR => [ + "section" => [true, "section", null, ">>", "<<", null], + "title" => [false, "title", null, "t", "", null], + "desc" => ["desc", ">", ""], + "error" => ["error", "E", ""], + "warning" => ["warning", "W", ""], + "note" => ["note", "N", ""], + "info" => ["info", "I", ""], + "step" => ["*", ".", ""], + "print" => [null, null, null], + ], + self::DEBUG => [ + "section" => [true, "section", null, ">>", "<<", null], + "title" => [false, "title", null, "t", "", null], + "desc" => ["desc", ">", ""], + "error" => ["debugE", "e", ""], + "warning" => ["debugW", "w", ""], + "note" => ["debugN", "i", ""], + "info" => ["debug", "D", ""], + "step" => ["*", ".", ""], + "print" => [null, null, null], + ], + ]; + + const RESULT_PREFIXES = [ + "failure" => ["(FAILURE)", ""], + "success" => ["(SUCCESS)", ""], + "done" => [null, null], + ]; + function _endSection(): void; function _getTitleMark(): int; + function _getTitleId(): ?int; + function _endTitle(?int $until=null): void; function _getActionMark(): int; + function _getActionId(): ?int; + function _endAction(?int $until=null): void; } diff --git a/php/src/output/web.php b/php/src/output/web.php new file mode 100644 index 0000000..c3b6e4b --- /dev/null +++ b/php/src/output/web.php @@ -0,0 +1,15 @@ +getTimestamp() - $start->getTimestamp(); return (new self($seconds, $resolution))->formatAt(); } - + static function format_since(DateTimeInterface $start, ?DateTimeInterface $now=null, ?int $resolution=null): string { $now ??= new DateTime(); $seconds = $now->getTimestamp() - $start->getTimestamp(); return (new self($seconds, $resolution))->formatSince(); } - + static function format_delay(DateTimeInterface $start, ?DateTimeInterface $now=null, ?int $resolution=null): string { $now ??= new DateTime(); $seconds = $now->getTimestamp() - $start->getTimestamp(); diff --git a/php/src/str.php b/php/src/str.php index c330b37..0614ad1 100644 --- a/php/src/str.php +++ b/php/src/str.php @@ -110,7 +110,7 @@ class str { if ($s === null) return null; else return ucfirst($s); } - + static final function upperw(?string $s, ?string $delimiters=null): ?string { if ($s === null) return null; if ($delimiters !== null) return ucwords($s, $delimiters); @@ -438,7 +438,7 @@ class str { } elseif (preg_match(self::CAMEL_PATTERN2, $camel, $ms, PREG_OFFSET_CAPTURE)) { # préfixe en minuscule } else { - throw exceptions::invalid_type($camel, $kind, "camel string"); + throw exceptions::invalid_value($camel, "camel string"); } $parts[] = strtolower($ms[1][0]); $index = intval($ms[1][1]) + strlen($ms[1][0]); diff --git a/php/src/txt.php b/php/src/txt.php index 2c5ef53..68a2794 100644 --- a/php/src/txt.php +++ b/php/src/txt.php @@ -105,7 +105,7 @@ class txt { if ($s === null) return null; return mb_strtoupper(mb_substr($s, 0, 1)).mb_substr($s, 1); } - + static final function upperw(?string $s, ?string $delimiters=null): ?string { if ($s === null) return null; if ($delimiters === null) $delimiters = " _-\t\r\n\f\v"; diff --git a/php/tbin/.gitignore b/php/tbin/.gitignore index c44eb09..4e0bb30 100644 --- a/php/tbin/.gitignore +++ b/php/tbin/.gitignore @@ -1,2 +1,3 @@ /*.db /*.cache +/*.log diff --git a/php/tbin/test-console.php b/php/tbin/test-console.php deleted file mode 100755 index bc50074..0000000 --- a/php/tbin/test-console.php +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/php -title("title0"); -$msg->title("title1"); -$msg->print("print under title1"); -$msg->end(); -$msg->print("print under title0"); -$msg->end(); - -$msg->desc("action avec step"); -$msg->action("action avec step"); -$msg->step("step"); -$msg->asuccess("action success"); - -$msg->action("action avec step"); -$msg->step("step"); -$msg->afailure("action failure"); - -$msg->action("action avec step"); -$msg->step("step"); -$msg->adone("action neutral"); - -$msg->desc("actions sans step"); -$msg->action("action sans step"); -$msg->asuccess("action success"); - -$msg->action("action sans step"); -$msg->afailure("action failure"); - -$msg->action("action sans step"); -$msg->adone("action neutral"); - -$msg->desc("actions imbriquées"); -$msg->action("action0"); -$msg->action("action1"); -$msg->action("action2"); -$msg->asuccess("action2 success"); -$msg->asuccess("action1 success"); -$msg->asuccess("action0 success"); - -$msg->desc("action avec step, sans messages"); -$msg->action("action avec step, sans messages, success"); -$msg->step("step"); -$msg->asuccess(); - -$msg->action("action avec step, sans messages, failure"); -$msg->step("step"); -$msg->afailure(); - -$msg->action("action avec step, sans messages, done"); -$msg->step("step"); -$msg->adone(); - -$msg->desc("action sans step, sans messages"); -$msg->action("action sans step, sans messages, success"); -$msg->asuccess(); - -$msg->action("action sans step, sans messages, failure"); -$msg->afailure(); - -$msg->action("action sans step, sans messages, done"); -$msg->adone(); - -$msg->desc("actions imbriquées, sans messages"); -$msg->action("action0"); -$msg->action("action1"); -$msg->action("action2"); -$msg->asuccess(); -$msg->asuccess(); -$msg->asuccess(); - -$msg->info("info"); -$msg->note("note"); -$msg->warning("warning"); -$msg->error("error"); - -$msg->section("section", function ($msg) { - $msg->title("title", function ($msg) { - $msg->desc("desc"); - $msg->print("print"); - - $msg->desc("action avec step"); - $msg->action("action avec step", function ($msg) { - $msg->step("step"); - $msg->asuccess("action success"); - }); - - $msg->action("action avec step", function ($msg) { - $msg->step("step"); - $msg->afailure("action failure"); - }); - - $msg->action("action avec step", function ($msg) { - $msg->step("step"); - $msg->adone("action done"); - }); - - $msg->desc("actions sans step"); - $msg->action("action sans step", function ($msg) { - $msg->asuccess("action success"); - }); - - $msg->action("action sans step", function ($msg) { - $msg->afailure("action failure"); - }); - - $msg->action("action sans step", function ($msg) { - $msg->adone("action done"); - }); - - $msg->desc("actions imbriquées"); - $msg->action("action0", function ($msg) { - $msg->action("action1", function ($msg) { - $msg->action("action2", function ($msg) { - $msg->asuccess("action2 success"); - }); - $msg->asuccess("action1 success"); - }); - $msg->asuccess("action0 success"); - }); - - $msg->desc("action avec step, sans messages"); - $msg->action("action avec step, sans messages, success", function ($msg) { - $msg->step("step"); - $msg->asuccess(); - }); - - $msg->action("action avec step, sans messages, failure", function ($msg) { - $msg->step("step"); - $msg->afailure(); - }); - - $msg->action("action avec step, sans messages, done", function ($msg) { - $msg->step("step"); - $msg->adone(); - }); - - $msg->desc("action sans step, sans messages"); - $msg->action("action sans step, sans messages, success", function ($msg) { - $msg->asuccess(); - }); - - $msg->action("action sans step, sans messages, failure", function ($msg) { - $msg->afailure(); - }); - - $msg->action("action sans step, sans messages, done", function ($msg) { - $msg->adone(); - }); - - $msg->desc("actions imbriquées, sans messages"); - $msg->action("action0", function ($msg) { - $msg->action("action1", function ($msg) { - $msg->action("action2", function ($msg) { - $msg->asuccess(); - }); - $msg->asuccess(); - }); - $msg->asuccess(); - }); - - $msg->desc("action avec step, avec code de retour"); - $msg->action("action avec step, avec code de retour true", function ($msg) { - $msg->step("step"); - return true; - }); - - $msg->action("action avec step, avec code de retour false", function ($msg) { - $msg->step("step"); - return false; - }); - - $msg->action("action avec step, avec code de retour autre", function ($msg) { - $msg->step("step"); - return "autre"; - }); - - $msg->action("action avec step, avec code de retour null", function ($msg) { - $msg->step("step"); - }); - - $msg->desc("action sans step, avec code de retour"); - $msg->action("action sans step, avec code de retour true", function ($msg) { - return true; - }); - - $msg->action("action sans step, avec code de retour false", function ($msg) { - return false; - }); - - $msg->action("action sans step, avec code de retour autre", function ($msg) { - return "autre"; - }); - - # ici, il n'y aura pas de message du tout - $msg->action("action sans step, avec code de retour null", function ($msg) { - }); - - $msg->info("info"); - $msg->note("note"); - $msg->warning("warning"); - $msg->error("error"); - }); -}); - -$msg->section("multi-line\nsection", function ($msg) { - $msg->title("multi-line\ntitle"); - $msg->title("another\ntitle"); - - $msg->print("multi-line\nprint"); - $msg->info("multi-line\ninfo"); - $msg->action("multi-line\naction"); - $msg->asuccess(); - $msg->action("multi-line\naction"); - $msg->step("multi-line\nstep"); - $msg->afailure(); - $msg->action("multi-line\naction"); - $msg->step("multi-line\nstep"); - $msg->asuccess("multi-line\nsuccess"); - $msg->action("multi-line\naction"); - $msg->step("multi-line\nstep"); - $msg->adone("multi-line\ndone"); - - $msg->end(); - $msg->end(); -}); - -$msg->section("Exceptions", function ($msg) { - $e = new Exception("message"); - $u1 = new UserException("userMessage"); - $u2 = new UserException("userMessage", "techMessage"); - $msg->title("avec message", function ($msg) use ($e, $u1, $u2) { - $msg->info(["exception", $e]); - $msg->info(["userException1", $u1]); - $msg->info(["userException2", $u2]); - }); - $msg->title("sans message", function ($msg) use ($e, $u1, $u2) { - $msg->info($e); - $msg->info($u1); - $msg->info($u2); - }); -}); diff --git a/php/tbin/test-mysql.php b/php/tbin/test-mysql.php index e2eb555..043d924 100644 --- a/php/tbin/test-mysql.php +++ b/php/tbin/test-mysql.php @@ -7,9 +7,9 @@ use nulib\db\CapacitorChannel; use nulib\db\mysql\Mysql; use nulib\db\mysql\MysqlStorage; use nulib\output\msg; -use nulib\output\std\StdMessenger; +use nulib\output\std\ConsoleMessenger; -msg::set_messenger_class(StdMessenger::class); +msg::set_messenger_class(ConsoleMessenger::class); $db = new Mysql([ "type" => "mysql", diff --git a/php/tbin/test-output-forever.php b/php/tbin/test-output-forever.php index 42d014d..6bee7b2 100755 --- a/php/tbin/test-output-forever.php +++ b/php/tbin/test-output-forever.php @@ -3,10 +3,10 @@ require(__DIR__.'/../vendor/autoload.php'); use nulib\UserException; -use nulib\output\std\StdMessenger; +use nulib\output\std\ConsoleMessenger; use nulib\output\msg; -msg::set_messenger(new StdMessenger(), new StdMessenger([ +msg::set_messenger(new ConsoleMessenger(), new ConsoleMessenger([ "output" => "output-forever.log", ])); diff --git a/php/tbin/test-output.php b/php/tbin/test-output.php new file mode 100755 index 0000000..78aa8d0 --- /dev/null +++ b/php/tbin/test-output.php @@ -0,0 +1,458 @@ +#!/usr/bin/php +title("title"); + sleep(5); + $msg->info("info"); + sleep(5); + $msg->info("info"); + $msg->end(); + + echo date("Y-m-d\\TH:i:s.u")."\n"; + $msg->action("action"); + sleep(5); + $msg->info("info"); + sleep(5); + $msg->info("info"); + $msg->adone(); + + echo date("Y-m-d\\TH:i:s.u")."\n"; + $msg->action("action"); + sleep(5); + $msg->asuccess(); + + echo date("Y-m-d\\TH:i:s.u")."\n"; + $msg->action("action"); + $msg->asuccess("plouf1"); + + echo date("Y-m-d\\TH:i:s.u")."\n"; + $msg->action("action"); + sleep(5); + $msg->asuccess("plouf2"); +} + +if ($titles) { + $msg->title("title0"); + $msg->desc("desc0"); + $msg->title("title1"); + $msg->desc("desc1"); + $msg->print("print under title1"); + $msg->end(); + $msg->print("print under title0"); + $msg->end(); + $msg->print("print out of title"); +} + +if ($maxTitleLevel) { + $msg->info("test maxTitleLevel"); + $msg->title("1first", function(IMessenger $msg) { + $msg->info("1one"); + $msg->end(); + $msg->info("1two"); + $msg->end(); + $msg->info("1three"); + }); + $msg->info("0one"); + $msg->end(); + $msg->info("0two"); + $msg->end(); + $msg->info("0three"); + + $msg->title("2first", function(IMessenger $msg) { + $msg->title("3second", function(IMessenger $msg) { + $msg->title("4third", function(IMessenger $msg) { + $msg->info("4one"); + $msg->end(); + $msg->info("4two"); + $msg->end(); + $msg->info("4three"); + }); + $msg->info("3four"); + $msg->end(); + $msg->info("3five"); + $msg->end(); + $msg->info("3six"); + }); + $msg->info("2seven"); + $msg->end(); + $msg->info("2eight"); + $msg->end(); + $msg->info("2nine"); + }); + $msg->info("1one"); + $msg->end(); + $msg->info("1two"); + $msg->end(); + $msg->info("1three"); +} + +if ($actions) { + $msg->desc("action avec step"); + $msg->action("action avec step"); + $msg->step("step"); + $msg->asuccess("action success"); + + $msg->action("action avec step"); + $msg->step("step"); + $msg->afailure("action failure"); + + $msg->action("action avec step"); + $msg->step("step"); + $msg->adone("action neutral"); + + $msg->desc("actions sans step"); + $msg->action("action sans step"); + $msg->asuccess("action success"); + + $msg->action("action sans step"); + $msg->afailure("action failure"); + + $msg->action("action sans step"); + $msg->adone("action neutral"); + + $msg->desc("actions imbriquées"); + $msg->action("action0"); + $msg->action("action1"); + $msg->action("action2"); + $msg->asuccess("action2 success"); + $msg->asuccess("action1 success"); + $msg->asuccess("action0 success"); + + $msg->desc("action avec step, sans messages"); + $msg->action("action avec step, sans messages, success"); + $msg->step("step"); + $msg->asuccess(); + + $msg->action("action avec step, sans messages, failure"); + $msg->step("step"); + $msg->afailure(); + + $msg->action("action avec step, sans messages, done"); + $msg->step("step"); + $msg->adone(); + + $msg->desc("action sans step, sans messages"); + $msg->action("action sans step, sans messages, success"); + $msg->asuccess(); + + $msg->action("action sans step, sans messages, failure"); + $msg->afailure(); + + $msg->action("action sans step, sans messages, done"); + $msg->adone(); + + $msg->desc("actions imbriquées, sans messages"); + $msg->action("action0"); + $msg->action("action1"); + $msg->action("action2"); + $msg->asuccess(); + $msg->asuccess(); + $msg->asuccess(); +} + +if ($maxActionLevel) { + $msg->info("test maxActionLevel"); + $msg->action("first1", function (IMessenger $msg) { + $msg->info("one"); + $msg->end(); + $msg->info("two"); + $msg->end(); + $msg->info("three"); + }); + $msg->action("first2", function (IMessenger $msg) { + $msg->info("one"); + $msg->adone(); + $msg->info("two"); + $msg->adone(); + $msg->info("three"); + }); + $msg->action("first3", function (IMessenger $msg) { + $msg->action("second", function (IMessenger $msg) { + $msg->action("third", function (IMessenger $msg) { + $msg->info("one"); + $msg->end(); + $msg->info("two"); + $msg->end(); + $msg->info("three"); + }); + $msg->info("four"); + $msg->end(); + $msg->info("five"); + $msg->end(); + $msg->info("six"); + }); + $msg->info("seven"); + $msg->end(); + $msg->info("eight"); + $msg->end(); + $msg->info("nine"); + }); + $msg->info("ten"); + $msg->end(); + $msg->info("eleven"); + $msg->end(); + $msg->info("twelve"); + + $msg->action("first4", function (IMessenger $msg) { + $msg->action("second", function (IMessenger $msg) { + $msg->action("third", function (IMessenger $msg) { + $msg->info("one"); + $msg->adone(); + $msg->info("two"); + $msg->adone(); + $msg->info("three"); + }); + $msg->info("four"); + $msg->adone(); + $msg->info("five"); + $msg->adone(); + $msg->info("six"); + }); + $msg->info("seven"); + $msg->adone(); + $msg->info("eight"); + $msg->adone(); + $msg->info("nine"); + }); + $msg->info("ten"); + $msg->adone(); + $msg->info("eleven"); + $msg->adone(); + $msg->info("twelve"); +} + +if ($levels) { + $msg->info("info"); + $msg->note("note"); + $msg->warning("warning"); + $msg->error("error"); +} + +if ($complete) { + $msg->section("section", function (IMessenger $msg) { + $msg->title("title", function (IMessenger $msg) { + $msg->desc("desc"); + $msg->print("print"); + + $msg->desc("action avec step"); + $msg->action("action avec step", function (IMessenger $msg) { + $msg->step("step"); + $msg->asuccess("action success"); + }); + + $msg->action("action avec step", function (IMessenger $msg) { + $msg->step("step"); + $msg->afailure("action failure"); + }); + + $msg->action("action avec step", function (IMessenger $msg) { + $msg->step("step"); + $msg->adone("action done"); + }); + + $msg->desc("actions sans step"); + $msg->action("action sans step", function (IMessenger $msg) { + $msg->asuccess("action success"); + }); + + $msg->action("action sans step", function (IMessenger $msg) { + $msg->afailure("action failure"); + }); + + $msg->action("action sans step", function (IMessenger $msg) { + $msg->adone("action done"); + }); + + $msg->desc("actions imbriquées"); + $msg->action("action0", function (IMessenger $msg) { + $msg->action("action1", function (IMessenger $msg) { + $msg->action("action2", function (IMessenger $msg) { + $msg->asuccess("action2 success"); + }); + $msg->asuccess("action1 success"); + }); + $msg->asuccess("action0 success"); + }); + + $msg->desc("action avec step, sans messages"); + $msg->action("action avec step, sans messages, success", function (IMessenger $msg) { + $msg->step("step"); + $msg->asuccess(); + }); + + $msg->action("action avec step, sans messages, failure", function (IMessenger $msg) { + $msg->step("step"); + $msg->afailure(); + }); + + $msg->action("action avec step, sans messages, done", function (IMessenger $msg) { + $msg->step("step"); + $msg->adone(); + }); + + $msg->desc("action sans step, sans messages"); + $msg->action("action sans step, sans messages, success", function (IMessenger $msg) { + $msg->asuccess(); + }); + + $msg->action("action sans step, sans messages, failure", function (IMessenger $msg) { + $msg->afailure(); + }); + + $msg->action("action sans step, sans messages, done", function (IMessenger $msg) { + $msg->adone(); + }); + + $msg->desc("actions imbriquées, sans messages"); + $msg->action("action0", function (IMessenger $msg) { + $msg->action("action1", function (IMessenger $msg) { + $msg->action("action2", function (IMessenger $msg) { + $msg->asuccess(); + }); + $msg->asuccess(); + }); + $msg->asuccess(); + }); + + $msg->desc("action avec step, avec code de retour"); + $msg->action("action avec step, avec code de retour true", function (IMessenger $msg) { + $msg->step("step"); + return true; + }); + + $msg->action("action avec step, avec code de retour false", function (IMessenger $msg) { + $msg->step("step"); + return false; + }); + + $msg->action("action avec step, avec code de retour autre", function (IMessenger $msg) { + $msg->step("step"); + return "autre"; + }); + + $msg->action("action avec step, avec code de retour null", function (IMessenger $msg) { + $msg->step("step"); + }); + + $msg->desc("action sans step, avec code de retour"); + $msg->action("action sans step, avec code de retour true", function (IMessenger $msg) { + return true; + }); + + $msg->action("action sans step, avec code de retour false", function (IMessenger $msg) { + return false; + }); + + $msg->action("action sans step, avec code de retour autre", function (IMessenger $msg) { + return "autre"; + }); + + # ici, il n'y aura pas de message du tout + $msg->action("action sans step, avec code de retour null", function (IMessenger $msg) { + }); + + $msg->info("info"); + $msg->note("note"); + $msg->warning("warning"); + $msg->error("error"); + }); + }); +} + +if ($multilines) { + $msg->section("multi-line\nsection", function (IMessenger $msg) { + $msg->title("multi-line\ntitle"); + $msg->title("another\ntitle"); + + $msg->print("multi-line\nprint"); + $msg->info("multi-line\ninfo"); + $msg->action("multi-line\naction"); + $msg->asuccess(); + $msg->action("multi-line\naction"); + $msg->step("multi-line\nstep"); + $msg->afailure(); + $msg->action("multi-line\naction"); + $msg->step("multi-line\nstep"); + $msg->asuccess("multi-line\nsuccess"); + $msg->action("multi-line\naction"); + $msg->step("multi-line\nstep"); + $msg->adone("multi-line\ndone"); + + $msg->end(); + $msg->end(); + }); +} + +if ($exceptions) { + $msg->section("Exceptions", function (IMessenger $msg) { + $e = new Exception("message"); + $u1 = new UserException("userMessage"); + $u2 = (new UserException("userMessage"))->setTechMessage("techMessage"); + $msg->title("avec message", function (IMessenger $msg) use ($e, $u1, $u2) { + $msg->info(["exception", $e]); + $msg->info(["userException1", $u1]); + $msg->info(["userException2", $u2]); + }); + $msg->title("sans message", function (IMessenger $msg) use ($e, $u1, $u2) { + $msg->info($e); + $msg->info($u1); + $msg->info($u2); + }); + }); +} diff --git a/php/tests/cache/_TestCase.php b/php/tests/cache/_TestCase.php index f05875c..a8bfa11 100644 --- a/php/tests/cache/_TestCase.php +++ b/php/tests/cache/_TestCase.php @@ -3,7 +3,7 @@ namespace nulib\cache; use nulib\db\sqlite\SqliteStorage; use nulib\output\msg; -use nulib\output\std\StdMessenger; +use nulib\output\std\ConsoleMessenger; use nulib\tests\TestCase; class _TestCase extends TestCase { @@ -11,7 +11,7 @@ class _TestCase extends TestCase { static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - msg::set_messenger_class(StdMessenger::class); + msg::set_messenger_class(ConsoleMessenger::class); self::$storage = new SqliteStorage(__DIR__."/cache.db"); cache::set_storage(self::$storage); } diff --git a/php/tests/cache/cacheTest.php b/php/tests/cache/cacheTest.php index 7c31f37..b8d8c55 100644 --- a/php/tests/cache/cacheTest.php +++ b/php/tests/cache/cacheTest.php @@ -26,7 +26,7 @@ class cacheTest extends _TestCase { } self::assertSame($expectedCount, $count); } - + function _testGet(string $dataId, int $expectedCount, callable $gencompute) { msg::section($dataId); cache::nc(true, true); diff --git a/php/tests/db/sqlite/ChannelMigrationTest.php b/php/tests/db/sqlite/ChannelMigrationTest.php index fa48e7c..30a80f4 100644 --- a/php/tests/db/sqlite/ChannelMigrationTest.php +++ b/php/tests/db/sqlite/ChannelMigrationTest.php @@ -7,14 +7,14 @@ use nulib\db\sqlite\impl\MyChannelV2; use nulib\db\sqlite\impl\MyChannelV3; use nulib\db\sqlite\impl\MyIndexChannel; use nulib\output\msg; -use nulib\output\std\StdMessenger; +use nulib\output\std\ConsoleMessenger; use nulib\php\time\DateTime; use nulib\tests\TestCase; class ChannelMigrationTest extends TestCase { static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - msg::set_messenger_class(StdMessenger::class); + msg::set_messenger_class(ConsoleMessenger::class); } protected function addData(MyChannel $channel, array $data): void { diff --git a/php/tests/php/funcTest.php b/php/tests/php/funcTest.php index 371cc35..b942beb 100644 --- a/php/tests/php/funcTest.php +++ b/php/tests/php/funcTest.php @@ -1016,7 +1016,7 @@ namespace nulib\php { $i1 = $func->invoke([1, 2]); self::assertInstanceOf(C1::class, $i1); self::assertSame(1, $i1->base); } - + private static function invoke_asserts(): array { $inv_ok = function($func) { return func::with($func)->invoke(); From 59e2abee613c800d0b651deac39c6f5f05d1469c Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 13 Oct 2025 14:35:15 +0400 Subject: [PATCH 55/91] modifs.mineures sans commentaires --- php/src/app/cli/Application.php | 12 +++++++----- php/src/text/Word.php | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/php/src/app/cli/Application.php b/php/src/app/cli/Application.php index 85c8d13..8c943c2 100644 --- a/php/src/app/cli/Application.php +++ b/php/src/app/cli/Application.php @@ -195,19 +195,21 @@ EOT); protected static function _initialize_app(): void { app::init(static::class); app::set_fact(app::FACT_CLI_APP); - msg::set_messenger(new ConsoleMessenger([ + $con = new ConsoleMessenger([ "min_level" => msg::DEBUG, - ])); + ]); + say::set_messenger($con); + msg::set_messenger($con); } protected static function _configure_app(Application $app): void { config::configure(config::CONFIGURE_INITIAL_ONLY); $con = con::set_messenger(new ConsoleMessenger([ - "min_level" => msg::NORMAL, + "min_level" => con::NORMAL, ])); - say::set_messenger($con); - msg::set_messenger($con); + say::set_messenger($con, true); + msg::set_messenger($con, true); if (static::USE_LOGFILE) { $log = log::set_messenger(new LogMessenger([ "output" => app::get()->getLogfile(), diff --git a/php/src/text/Word.php b/php/src/text/Word.php index b25cfe3..e5889d1 100644 --- a/php/src/text/Word.php +++ b/php/src/text/Word.php @@ -28,7 +28,7 @@ use nulib\ValueException; */ class Word { /** @var bool le mot est-il féminin? */ - private bool $fem; + private ?bool $fem; /** @var string article "aucun", "aucune" */ private ?string $aucun; /** @var string article "un", "une" */ From 5da09a039bce16258353f3c5fb5c7c33c8b61687 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 13 Oct 2025 16:57:12 +0400 Subject: [PATCH 56/91] modifs.mineures sans commentaires --- runphp/phpwrapper-_wrapper.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runphp/phpwrapper-_wrapper.sh b/runphp/phpwrapper-_wrapper.sh index 5320701..27050c0 100644 --- a/runphp/phpwrapper-_wrapper.sh +++ b/runphp/phpwrapper-_wrapper.sh @@ -58,7 +58,7 @@ Vérification des liens..." [ -f "$i" ] || continue name="bin/${i%.*}.php" dest="../@@CLI@@/_wrapper.sh" - link="../bin/${i%.*}.php" + link="../bin/${i%.*}.php" #XXX if [ -L "$link" ]; then echo "* $name OK" elif [ -e "$link" ]; then From 394edb782e04f59dd910f39f45872ed427963fb0 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 14 Oct 2025 14:36:46 +0400 Subject: [PATCH 57/91] maj todo --- runphp/TODO.md | 6 ++++++ runphp/phpwrapper-_wrapper.sh | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 runphp/TODO.md diff --git a/runphp/TODO.md b/runphp/TODO.md new file mode 100644 index 0000000..b896d4a --- /dev/null +++ b/runphp/TODO.md @@ -0,0 +1,6 @@ +# TODO + +* dans `phpwrapper-_wrapper.sh`, corriger le calcul de link à la ligne 61, + parce que le chemin relatif peut être différent que ../bin + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/runphp/phpwrapper-_wrapper.sh b/runphp/phpwrapper-_wrapper.sh index 27050c0..5320701 100644 --- a/runphp/phpwrapper-_wrapper.sh +++ b/runphp/phpwrapper-_wrapper.sh @@ -58,7 +58,7 @@ Vérification des liens..." [ -f "$i" ] || continue name="bin/${i%.*}.php" dest="../@@CLI@@/_wrapper.sh" - link="../bin/${i%.*}.php" #XXX + link="../bin/${i%.*}.php" if [ -L "$link" ]; then echo "* $name OK" elif [ -e "$link" ]; then From c62de542c35b8c394a56ebab221734409e0c9759 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 14 Oct 2025 14:40:02 +0400 Subject: [PATCH 58/91] ajout cachedir --- php/src/app/TODO.md | 3 -- php/src/app/app.php | 41 ++++++++++++++++++++++++---- php/src/app/cli/Application.php | 1 + php/src/app/config/ConfigManager.php | 2 +- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/php/src/app/TODO.md b/php/src/app/TODO.md index bec0657..4b12019 100644 --- a/php/src/app/TODO.md +++ b/php/src/app/TODO.md @@ -1,8 +1,5 @@ # nulib\app -* [ ] ajouter des méthodes normalisées `app::get_cachedir()` et - `app::get_cachefile($name)` avec la valeur par défaut - `cachedir = $vardir/cache` * [ ] `app::action()` et `app::step()` appellent automatiquement `app::_dispatch_signals()` diff --git a/php/src/app/app.php b/php/src/app/app.php index c3fcdd4..87b88bc 100644 --- a/php/src/app/app.php +++ b/php/src/app/app.php @@ -36,6 +36,7 @@ class app { "datadir" => $app::DATADIR, "etcdir" => $app::ETCDIR, "vardir" => $app::VARDIR, + "cachedir" => $app::CACHEDIR, "logdir" => $app::LOGDIR, "appgroup" => $app::APPGROUP, "name" => $app::NAME, @@ -51,6 +52,7 @@ class app { "datadir" => constant("$app::DATADIR"), "etcdir" => constant("$app::ETCDIR"), "vardir" => constant("$app::VARDIR"), + "cachedir" => constant("$app::CACHEDIR"), "logdir" => constant("$app::LOGDIR"), "appgroup" => constant("$app::APPGROUP"), "name" => constant("$app::NAME"), @@ -84,6 +86,7 @@ class app { "datadir", "etcdir", "vardir", + "cachedir", "logdir", "profile", "facts", @@ -172,6 +175,7 @@ class app { "datadir" => $datadir, "etcdir" => $etcdir, "vardir" => $vardir, + "cachedir" => $cachedir, "logdir" => $logdir, ] = $params; $cwd = $params["cwd"] ?? null; @@ -223,6 +227,11 @@ class app { if ($vardir === false) $vardir = $params["vardir"] ?? null; if ($vardir === null) $vardir = "var"; $vardir = path::reljoin($datadir, $vardir); + # cachedir + $cachedir = getenv("${PROJCODE}_CACHEDIR"); + if ($cachedir === false) $cachedir = $params["cachedir"] ?? null; + if ($cachedir === null) $cachedir = "cache"; + $cachedir = path::reljoin($vardir, $cachedir); # logdir $logdir = getenv("${PROJCODE}_LOGDIR"); if ($logdir === false) $logdir = $params["logdir"] ?? null; @@ -250,6 +259,7 @@ class app { $this->datadir = $datadir; $this->etcdir = $etcdir; $this->vardir = $vardir; + $this->cachedir = $cachedir; $this->logdir = $logdir; # name, title @@ -319,6 +329,12 @@ class app { return $this->vardir; } + protected string $cachedir; + + function getCachedir(): string { + return $this->cachedir; + } + protected string $logdir; function getLogdir(): string { @@ -449,6 +465,7 @@ class app { "datadir" => $this->datadir, "etcdir" => $this->etcdir, "vardir" => $this->vardir, + "cachedir" => $this->cachedir, "logdir" => $this->logdir, "profile" => $this->getProfile(), "facts" => $this->facts, @@ -464,7 +481,7 @@ class app { * une valeur de la forme "$ETCDIR/$name[.$profile].conf" */ function getEtcfile(?string $name=null, $profile=null): string { - if ($name === null) $name = "{$this->name}.conf"; + $name ??= "{$this->name}.conf"; return $this->findFile([$this->etcdir], [$name], $profile); } @@ -473,13 +490,25 @@ class app { * valeur de la forme "$VARDIR/$appgroup/$name[.$profile].tmp" */ function getVarfile(?string $name=null, $profile=null): string { - if ($name === null) $name = "{$this->name}.tmp"; + $name ??= "{$this->name}.tmp"; $file = $this->fencedJoin($this->vardir, $this->appgroup, $name); $file = $this->withProfile($file, $profile); sh::mkdirof($file); return $file; } + /** + * obtenir le chemin vers le fichier de cache. par défaut, retourner une + * valeur de la forme "$CACHEDIR/$appgroup/$name[.$profile].cache" + */ + function getCachefile(?string $name=null, $profile=null): string { + $name ??= "{$this->name}.cache"; + $file = $this->fencedJoin($this->cachedir, $this->appgroup, $name); + $file = $this->withProfile($file, $profile); + sh::mkdirof($file); + return $file; + } + /** * obtenir le chemin vers le fichier de log. par défaut, retourner une * valeur de la forme "$LOGDIR/$appgroup/$name.log" (sans le profil, parce @@ -493,10 +522,10 @@ class app { $name = "{$this->name}.log"; $profile ??= false; } - $file = $this->fencedJoin($this->logdir, $this->appgroup, $name); - $file = $this->withProfile($file, $profile); - sh::mkdirof($file); - return $file; + $logfile = $this->fencedJoin($this->logdir, $this->appgroup, $name); + $logfile = $this->withProfile($logfile, $profile); + sh::mkdirof($logfile); + return $logfile; } /** diff --git a/php/src/app/cli/Application.php b/php/src/app/cli/Application.php index 8c943c2..2fcfdcc 100644 --- a/php/src/app/cli/Application.php +++ b/php/src/app/cli/Application.php @@ -67,6 +67,7 @@ abstract class Application { const DATADIR = null; const ETCDIR = null; const VARDIR = null; + const CACHEDIR = null; const LOGDIR = null; /** @var bool faut-il activer automatiquement l'écriture dans les logs */ diff --git a/php/src/app/config/ConfigManager.php b/php/src/app/config/ConfigManager.php index abcef01..d2b2ec2 100644 --- a/php/src/app/config/ConfigManager.php +++ b/php/src/app/config/ConfigManager.php @@ -132,7 +132,7 @@ class ConfigManager { } $value = $this->_getValue($pkey, $default, $inProfile); - $this->cacheSet($pkey, $default, $inProfile); + $this->cacheSet($pkey, $value, $inProfile); return $value; } From 20e64b8ffbae53553c48ab766af6d8bd7e906da6 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 14 Oct 2025 16:55:50 +0400 Subject: [PATCH 59/91] modifs.mineures sans commentaires --- php/src/output/std/ConsoleMessenger.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/php/src/output/std/ConsoleMessenger.php b/php/src/output/std/ConsoleMessenger.php index fd3d716..fe383cd 100644 --- a/php/src/output/std/ConsoleMessenger.php +++ b/php/src/output/std/ConsoleMessenger.php @@ -242,11 +242,13 @@ class ConsoleMessenger extends AbstractMessenger { } function _endTitle(?int $until=null): void { - $title = $this->titles[array_key_last($this->titles)]; - $until ??= $title["max_title_level"]; - $until ??= $this->_getTitleMark() - 1; - while (count($this->titles) > $until) { - array_pop($this->titles); + $title = $this->titles[array_key_last($this->titles)] ?? null; + if ($title !== null) { + $until ??= $title["max_title_level"]; + $until ??= $this->_getTitleMark() - 1; + while (count($this->titles) > $until) { + array_pop($this->titles); + } } } @@ -385,11 +387,13 @@ class ConsoleMessenger extends AbstractMessenger { } function _endAction(?int $until=null): void { - $action = $this->actions[array_key_last($this->actions)]; - $until ??= $action["max_action_level"]; - $until ??= $this->_getActionMark() - 1; - while (count($this->actions) > $until) { - array_pop($this->actions); + $action = $this->actions[array_key_last($this->actions)] ?? null; + if ($action !== null) { + $until ??= $action["max_action_level"]; + $until ??= $this->_getActionMark() - 1; + while (count($this->actions) > $until) { + array_pop($this->actions); + } } } From 51215b42eb868d457d1d8d6cd9deec828b3414aa Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 15 Oct 2025 09:48:28 +0400 Subject: [PATCH 60/91] =?UTF-8?q?ne=20pas=20changer=20le=20r=C3=A9pertoire?= =?UTF-8?q?=20courant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/runphp | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/runphp b/bin/runphp index 3e59b61..f15db8a 100755 --- a/bin/runphp +++ b/bin/runphp @@ -19,6 +19,7 @@ while true; do fi cd .. done +cd "$owd" export RUNPHP_MOUNT= if [ "$MYNAME" == composer ]; then From 55728059cf2d28db29f3c94fe6d1fcf9abf63f0c Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 15 Oct 2025 12:13:49 +0400 Subject: [PATCH 61/91] =?UTF-8?q?d=C3=A9but=20r=C3=A9organisation=20log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/php-docker-settings.xml | 15 ++ .idea/php.xml | 4 +- php/src/output/_TMessenger.php | 11 +- php/src/output/std/AbstractMessenger.php | 16 +- php/src/output/std/ConsoleMessenger.php | 212 +++++++++++++---------- php/src/output/std/LogMessenger.php | 142 ++++++++------- php/src/output/std/NullMessenger.php | 3 + php/src/output/std/ProxyMessenger.php | 122 +++++++------ php/src/output/std/_IMessenger.php | 24 +-- php/tbin/.gitignore | 1 + 10 files changed, 326 insertions(+), 224 deletions(-) diff --git a/.idea/php-docker-settings.xml b/.idea/php-docker-settings.xml index 047d43d..9e9123b 100644 --- a/.idea/php-docker-settings.xml +++ b/.idea/php-docker-settings.xml @@ -17,6 +17,21 @@ + + + + + + + diff --git a/.idea/php.xml b/.idea/php.xml index 40a93eb..06ee8b4 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -2,7 +2,7 @@ - + @@ -17,7 +17,7 @@ - + diff --git a/php/src/output/_TMessenger.php b/php/src/output/_TMessenger.php index 26de8a5..912dc0a 100644 --- a/php/src/output/_TMessenger.php +++ b/php/src/output/_TMessenger.php @@ -11,9 +11,14 @@ trait _TMessenger { static function set_messenger(IMessenger $msg, bool $replace=false): IMessenger { if (self::$msg instanceof NullMessenger) self::$msg = null; - if ($replace || self::$msg === null) return self::$msg = $msg; - elseif (self::$msg instanceof ProxyMessenger) self::$msg->addMessenger($msg); - else self::$msg = new ProxyMessenger(self::$msg); + if ($replace || self::$msg === null) { + return self::$msg = $msg; + } elseif (self::$msg instanceof ProxyMessenger) { + self::$msg->addMessenger($msg); + } else { + self::$msg = new ProxyMessenger(self::$msg); + self::$msg->addMessenger($msg); + } return $msg; } diff --git a/php/src/output/std/AbstractMessenger.php b/php/src/output/std/AbstractMessenger.php index 69da625..073c036 100644 --- a/php/src/output/std/AbstractMessenger.php +++ b/php/src/output/std/AbstractMessenger.php @@ -49,8 +49,12 @@ abstract class AbstractMessenger implements _IMessenger { protected int $lastTitleId = 1; + protected abstract function title__getId(): ?int; + protected int $lastActionId = 1; + protected abstract function action__getId(): ?int; + protected function getLinePrefix(): ?string { $linePrefix = null; if ($this->addDate) { @@ -59,9 +63,9 @@ abstract class AbstractMessenger implements _IMessenger { } if ($this->showIds) { if ($this->id !== null) $linePrefix .= "p=$this->id "; - $titleId = $this->_getTitleId(); + $titleId = $this->title__getId(); if ($titleId !== null) $linePrefix .= "t=$titleId "; - $actionId = $this->_getActionId(); + $actionId = $this->action__getId(); if ($actionId !== null) $linePrefix .= "a=$actionId "; } return $linePrefix; @@ -129,7 +133,7 @@ abstract class AbstractMessenger implements _IMessenger { } } - protected abstract function flushActions(bool $endAction=false, ?int $overrideLevel=null): void; + protected abstract function action__flush(bool $endAction=false, ?int $overrideLevel=null): void; protected function _printAction( ?string $linePrefix, int $level, @@ -269,7 +273,7 @@ abstract class AbstractMessenger implements _IMessenger { $flushActions = true; $showContent = $this->checkLevel($level); if ($content !== null && $showContent) { - $this->flushActions(); $flushActions = false; + $this->action__flush(); $flushActions = false; $this->_printGeneric($linePrefix, $level, $type, $content, $indentLevel, $out); } if ($exceptions !== null) { @@ -279,12 +283,12 @@ abstract class AbstractMessenger implements _IMessenger { # tout d'abord message $message = exceptions::get_message($exception); if ($showContent) { - if ($flushActions) { $this->flushActions(); $flushActions = false; } + if ($flushActions) { $this->action__flush(); $flushActions = false; } $this->_printGeneric($linePrefix, $level, $type, $message, $indentLevel, $out); } # puis summary et traceback if ($showTraceback) { - if ($flushActions) { $this->flushActions(); $flushActions = false; } + if ($flushActions) { $this->action__flush(); $flushActions = false; } $summary = exceptions::get_summary($exception, false); $this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out); $traceback = exceptions::get_traceback($exception); diff --git a/php/src/output/std/ConsoleMessenger.php b/php/src/output/std/ConsoleMessenger.php index fe383cd..9083f3d 100644 --- a/php/src/output/std/ConsoleMessenger.php +++ b/php/src/output/std/ConsoleMessenger.php @@ -108,8 +108,19 @@ class ConsoleMessenger extends AbstractMessenger { /** @var array section qui est en attente d'affichage */ protected ?array $section; + protected function section__end(): void { + while ($this->actions) $this->adone(); + while ($this->titles) $this->title__end(); + $this->inSection = false; + $this->section = null; + } + + function section__afterFunc(): void { + $this->section__end(); + } + function section($content, ?callable $func=null, ?int $level=null): void { - $this->_endSection(); + $this->section__end(); $this->inSection = true; if (!$this->checkLevel($level)) return; $this->section = [ @@ -122,7 +133,7 @@ class ConsoleMessenger extends AbstractMessenger { try { $func($this); } finally { - $this->_endSection(); + $this->section__afterFunc(); } } } @@ -138,13 +149,6 @@ class ConsoleMessenger extends AbstractMessenger { } } - function _endSection(): void { - while ($this->actions) $this->adone(); - while ($this->titles) $this->_endTitle(); - $this->inSection = false; - $this->section = null; - } - protected function getIndentLevel(bool $withActions=true): int { $indentLevel = count($this->titles) - 1; if ($indentLevel < 0) $indentLevel = 0; @@ -159,63 +163,26 @@ class ConsoleMessenger extends AbstractMessenger { protected array $titles; - function _getTitleMark(): int { - return count($this->titles); + function title__getMarks(): array { + return [count($this->titles)]; } - function _getTitleId(): ?int { + protected function title__getId(): ?int { return end($this->titles)["id"] ?? null; } - function title($content, ?callable $func=null, ?int $level=null): void { - if (!$this->checkLevel($level)) return; - $titleLevel = $this->_getTitleMark(); - // faire en deux temps pour linePrefix soit à jour - $this->titles[] = ["id" => $this->lastTitleId++]; - A::merge($this->titles[array_key_last($this->titles)], [ - "title_level" => $titleLevel, - "max_title_level" => null, - "line_prefix" => $this->getLinePrefix(), - "level" => $level, - "content" => $content, - "print_content" => true, - "descs" => [], - "print_descs" => false, - ]); - if ($func !== null) { - try { - $title =& $this->titles[array_key_last($this->titles)]; - $title["max_title_level"] = $titleLevel + 1; - $func($this); - } finally { - $title["max_title_level"] = null; - $this->_endTitle($titleLevel); + protected function title__end(?int $until=null): void { + $title = $this->titles[array_key_last($this->titles)] ?? null; + if ($title !== null) { + $until ??= $title["max_title_level"]; + $until ??= $this->title__getMarks()[0] - 1; + while (count($this->titles) > $until) { + array_pop($this->titles); } } } - function desc($content, ?int $level=null): void { - if (!$this->checkLevel($level)) return; - $desc = [ - "line_prefix" => $this->getLinePrefix(), - "level" => $level, - "content" => $content, - ]; - $key = array_key_last($this->titles); - if ($key !== null) { - $title =& $this->titles[$key]; - $title["descs"][] = $desc; - $title["print_descs"] = true; - } else { - # pas de titre en cours - $this->_printGeneric( - $desc["line_prefix"], $desc["level"], - "desc", $desc["content"], - 0, $this->err); - } - } - - protected function flushTitles(): void { + protected function title__flush(): void { $this->printSection(); $err = $this->err; $indentLevel = 0; @@ -241,29 +208,86 @@ class ConsoleMessenger extends AbstractMessenger { }; unset($title); } - function _endTitle(?int $until=null): void { - $title = $this->titles[array_key_last($this->titles)] ?? null; - if ($title !== null) { - $until ??= $title["max_title_level"]; - $until ??= $this->_getTitleMark() - 1; - while (count($this->titles) > $until) { - array_pop($this->titles); + function title__beforeFunc(array $marks): void { + $title =& $this->titles[array_key_last($this->titles)]; + $title["max_title_level"] = $marks[0] + 1; + } + + function title__afterFunc(array $marks): void { + $title =& $this->titles[array_key_last($this->titles)]; + $title["max_title_level"] = null; + $this->title__end($marks[0]); + } + + function title($content, ?callable $func=null, ?int $level=null): void { + if (!$this->checkLevel($level)) return; + $marks = $this->title__getMarks(); + // faire en deux temps pour linePrefix soit à jour + $this->titles[] = ["id" => $this->lastTitleId++]; + A::merge($this->titles[array_key_last($this->titles)], [ + "title_level" => $marks[0], + "max_title_level" => null, + "line_prefix" => $this->getLinePrefix(), + "level" => $level, + "content" => $content, + "print_content" => true, + "descs" => [], + "print_descs" => false, + ]); + if ($func !== null) { + try { + $this->title__beforeFunc($marks); + $func($this); + } finally { + $this->title__afterFunc($marks); } } } + function desc($content, ?int $level=null): void { + if (!$this->checkLevel($level)) return; + $desc = [ + "line_prefix" => $this->getLinePrefix(), + "level" => $level, + "content" => $content, + ]; + $key = array_key_last($this->titles); + if ($key !== null) { + $title =& $this->titles[$key]; + $title["descs"][] = $desc; + $title["print_descs"] = true; + } else { + # pas de titre en cours + $this->_printGeneric( + $desc["line_prefix"], $desc["level"], + "desc", $desc["content"], + 0, $this->err); + } + } + protected array $actions; - function _getActionMark(): int { - return count($this->actions); + function action__getMarks(): array { + return [count($this->actions)]; } - function _getActionId(): ?int { + protected function action__getId(): ?int { return end($this->actions)["id"] ?? null; } - protected function flushActions(bool $endAction=false, ?int $overrideLevel=null): void { - $this->flushTitles(); + protected function action__end(?int $until=null): void { + $action = $this->actions[array_key_last($this->actions)] ?? null; + if ($action !== null) { + $until ??= $action["max_action_level"]; + $until ??= $this->action__getMarks()[0] - 1; + while (count($this->actions) > $until) { + array_pop($this->actions); + } + } + } + + protected function action__flush(bool $endAction=false, ?int $overrideLevel=null): void { + $this->title__flush(); $err = $this->err; $indentLevel = $this->getIndentLevel(false); $lastIndex = array_key_last($this->actions); @@ -311,16 +335,27 @@ class ConsoleMessenger extends AbstractMessenger { } $indentLevel++; }; unset($action); - if ($endAction) $this->_endAction(); + if ($endAction) $this->action__end(); + } + + function action__beforeFunc(array $marks): void { + $action =& $this->actions[array_key_last($this->actions)]; + $action["max_action_level"] = $marks[0] + 1; + } + + function action__afterFunc(array $marks): void { + $action =& $this->actions[array_key_last($this->actions)]; + $action["max_action_level"] = null; + $this->action__end($marks[0]); } function action($content, ?callable $func=null, ?int $level=null): void { $this->checkLevel($level); - $actionLevel = $this->_getActionMark(); + $marks = $this->action__getMarks(); // faire en deux temps pour linePrefix soit à jour $this->actions[] = ["id" => $this->lastActionId++]; A::merge($this->actions[array_key_last($this->actions)], [ - "action_level" => $actionLevel, + "action_level" => $marks[0], "max_action_level" => null, "timestamp" => time(), "line_prefix" => $this->getLinePrefix(), @@ -332,18 +367,16 @@ class ConsoleMessenger extends AbstractMessenger { ]); if ($func !== null) { try { - $action =& $this->actions[array_key_last($this->actions)]; - $action["max_action_level"] = $actionLevel + 1; + $this->action__beforeFunc($marks); $result = $func($this); - if ($this->_getActionMark() > $actionLevel) { + if ($this->action__getMarks()[0] > $marks[0]) { $this->aresult($result); } } catch (Exception $e) { $this->afailure($e); throw $e; } finally { - $action["max_action_level"] = null; - $this->_endAction($actionLevel); + $this->action__afterFunc($marks); } } } @@ -359,7 +392,7 @@ class ConsoleMessenger extends AbstractMessenger { $action =& $this->actions[array_key_last($this->actions)]; $action["result_success"] = true; $action["result_content"] = $content; - $this->flushActions(true, $overrideLevel); + $this->action__flush(true, $overrideLevel); } function afailure($content=null, ?int $overrideLevel=null): void { @@ -367,7 +400,7 @@ class ConsoleMessenger extends AbstractMessenger { $action =& $this->actions[array_key_last($this->actions)]; $action["result_success"] = false; $action["result_content"] = $content; - $this->flushActions(true, $overrideLevel); + $this->action__flush(true, $overrideLevel); } function adone($content=null, ?int $overrideLevel=null): void { @@ -375,7 +408,7 @@ class ConsoleMessenger extends AbstractMessenger { $action =& $this->actions[array_key_last($this->actions)]; $action["result_success"] = null; $action["result_content"] = $content; - $this->flushActions(true, $overrideLevel); + $this->action__flush(true, $overrideLevel); } function aresult($result=null, ?int $overrideLevel=null): void { @@ -386,17 +419,6 @@ class ConsoleMessenger extends AbstractMessenger { else $this->adone($result, $overrideLevel); } - function _endAction(?int $until=null): void { - $action = $this->actions[array_key_last($this->actions)] ?? null; - if ($action !== null) { - $until ??= $action["max_action_level"]; - $until ??= $this->_getActionMark() - 1; - while (count($this->actions) > $until) { - array_pop($this->actions); - } - } - } - function print($content, ?int $level=null): void { $this->_printGenericOrException( $level, "print", $content, @@ -428,9 +450,9 @@ class ConsoleMessenger extends AbstractMessenger { } function end(bool $all=false): void { - if ($all) $this->_endSection(); - elseif ($this->actions) $this->_endAction(); - elseif ($this->titles) $this->_endTitle(); - else $this->_endSection(); + if ($all) $this->section__afterFunc(); + elseif ($this->actions) $this->action__end(); + elseif ($this->titles) $this->title__end(); + else $this->section__afterFunc(); } } diff --git a/php/src/output/std/LogMessenger.php b/php/src/output/std/LogMessenger.php index bc7d978..b709959 100644 --- a/php/src/output/std/LogMessenger.php +++ b/php/src/output/std/LogMessenger.php @@ -79,8 +79,16 @@ class LogMessenger extends AbstractMessenger { return $clone; } + protected function section__end(): void { + $this->end(true); + } + + function section__afterFunc(): void { + $this->section__end(); + } + function section($content, ?callable $func=null, ?int $level=null): void { - $this->_endSection(); + $this->section__end(); if (!$this->checkLevel($level)) return; $this->_printTitle( $this->getLinePrefix(), $level, @@ -90,45 +98,61 @@ class LogMessenger extends AbstractMessenger { try { $func($this); } finally { - $this->_endSection(); + $this->section__afterFunc(); } } } - function _endSection(): void { - $this->end(true); - } - protected array $titles; - function _getTitleMark(): int { - return count($this->titles); + function title__getMarks(): array { + return [count($this->titles)]; } - function _getTitleId(): ?int { + protected function title__getId(): ?int { return end($this->titles)["id"] ?? null; } + protected function title__end(?int $until=null): void { + $title = $this->titles[array_key_last($this->titles)] ?? null; + if ($title !== null) { + $until ??= $title["max_title_level"]; + $until ??= $this->title__getMarks()[0] - 1; + while (count($this->titles) > $until) { + array_pop($this->titles); + } + } + } + + function title__beforeFunc(array $marks): void { + $title =& $this->titles[array_key_last($this->titles)]; + $title["max_title_level"] = $marks[0] + 1; + } + + function title__afterFunc(array $marks): void { + $title =& $this->titles[array_key_last($this->titles)]; + $title["max_title_level"] = null; + $this->title__end($marks[0]); + } + function title($content, ?callable $func=null, ?int $level=null): void { if (!$this->checkLevel($level)) return; - $titleLevel = $this->_getTitleMark(); + $marks = $this->title__getMarks(); $this->titles[] = [ "id" => $this->lastTitleId++, - "title_level" => $titleLevel, + "title_level" => $marks[0], "max_title_level" => null, ]; $this->_printTitle( $this->getLinePrefix(), $level, "title", $content, - $titleLevel, $this->out); + $marks[0], $this->out); if ($func !== null) { try { - $title =& $this->titles[array_key_last($this->titles)]; - $title["max_title_level"] = $titleLevel + 1; + $this->title__beforeFunc($marks); $func($this); } finally { - $title["max_title_level"] = null; - $this->_endTitle($titleLevel); + $this->title__afterFunc($marks); } } } @@ -143,31 +167,47 @@ class LogMessenger extends AbstractMessenger { } - function _endTitle(?int $until=null): void { - $title = $this->titles[array_key_last($this->titles)]; - $until ??= $title["max_title_level"]; - $until ??= $this->_getTitleMark() - 1; - while (count($this->titles) > $until) { - array_pop($this->titles); + protected array $actions; + + function action__getMarks(): array { + return [count($this->actions)]; + } + + protected function action__getId(): ?int { + return end($this->actions)["id"] ?? null; + } + + protected function action__end(?int $until=null): void { + $action = $this->actions[array_key_last($this->actions)] ?? null; + if ($action !== null) { + $until ??= $action["max_action_level"]; + $until ??= $this->action__getMarks()[0] - 1; + while (count($this->actions) > $until) { + array_pop($this->actions); + } } } - protected array $actions; - - function _getActionMark(): int { - return count($this->actions); + protected function action__flush(bool $endAction=false, ?int $overrideLevel=null): void { } - function _getActionId(): ?int { - return end($this->actions)["id"] ?? null; + function action__beforeFunc(array $marks): void { + $action =& $this->actions[array_key_last($this->actions)]; + $action["max_action_level"] = $marks[0] + 1; + } + + function action__afterFunc(array $marks): void { + $action =& $this->actions[array_key_last($this->actions)]; + $action["max_action_level"] = null; + $this->action__end($marks[0]); } function action($content, ?callable $func=null, ?int $level=null): void { $this->checkLevel($level); - $actionLevel = $this->_getActionMark(); + $marks = $this->action__getMarks(); $this->actions[] = [ "id" => $this->lastActionId++, - "action_level" => $actionLevel, + "action_level" => $marks[0], "max_action_level" => null, "level" => $level ]; @@ -175,28 +215,23 @@ class LogMessenger extends AbstractMessenger { $this->getLinePrefix(), $level, true, $content, false, null, null, - $actionLevel, $this->out); + $marks[0], $this->out); if ($func !== null) { try { - $action =& $this->actions[array_key_last($this->actions)]; - $action["max_action_level"] = $actionLevel + 1; + $this->action__beforeFunc($marks); $result = $func($this); - if ($this->_getActionMark() > $actionLevel) { + if ($this->action__getMarks()[0] > $marks[0]) { $this->aresult($result); } } catch (Exception $e) { $this->afailure($e); throw $e; } finally { - $action["max_action_level"] = null; - $this->_endAction($actionLevel); + $this->action__afterFunc($marks); } } } - protected function flushActions(bool $endAction=false, ?int $overrideLevel=null): void { - } - function step($content, ?int $level=null): void { $this->_printGenericOrException( $level, "step", $content, @@ -204,7 +239,7 @@ class LogMessenger extends AbstractMessenger { } function asuccess($content=null, ?int $overrideLevel=null): void { - if ($this->_getActionMark() == 0) $this->action(null); + if ($this->action__getMarks()[0] == 0) $this->action(null); $action = end($this->actions); $level = $overrideLevel ?? $action["level"]; $this->_printAction( @@ -212,11 +247,11 @@ class LogMessenger extends AbstractMessenger { false, null, true, true, $content, $action["action_level"], $this->out); - $this->_endAction(); + $this->action__end(); } function afailure($content=null, ?int $overrideLevel=null): void { - if ($this->_getActionMark() == 0) $this->action(null); + if ($this->action__getMarks()[0] == 0) $this->action(null); $action = end($this->actions); $level = $overrideLevel ?? $action["level"]; $this->_printAction( @@ -224,11 +259,11 @@ class LogMessenger extends AbstractMessenger { false, null, true, false, $content, $action["action_level"], $this->out); - $this->_endAction(); + $this->action__end(); } function adone($content=null, ?int $overrideLevel=null): void { - if ($this->_getActionMark() == 0) $this->action(null); + if ($this->action__getMarks()[0] == 0) $this->action(null); $action = end($this->actions); $level = $overrideLevel ?? $action["level"]; $this->_printAction( @@ -236,26 +271,17 @@ class LogMessenger extends AbstractMessenger { false, null, true, null, $content, $action["action_level"], $this->out); - $this->_endAction(); + $this->action__end(); } function aresult($result=null, ?int $overrideLevel=null): void { - if ($this->_getActionMark() == 0) $this->action(null); + if ($this->action__getMarks()[0] == 0) $this->action(null); if ($result === true) $this->asuccess(null, $overrideLevel); elseif ($result === false) $this->afailure(null, $overrideLevel); elseif ($result instanceof Exception) $this->afailure($result, $overrideLevel); else $this->adone($result, $overrideLevel); } - function _endAction(?int $until=null): void { - $action = $this->actions[array_key_last($this->actions)]; - $until ??= $action["max_action_level"]; - $until ??= $this->_getActionMark() - 1; - while (count($this->actions) > $until) { - array_pop($this->actions); - } - } - protected function getIndentLevel(bool $withActions=true): int { $indentLevel = count($this->titles) - 1; if ($indentLevel < 0) $indentLevel = 0; @@ -296,11 +322,11 @@ class LogMessenger extends AbstractMessenger { function end(bool $all=false): void { if ($all) { while ($this->actions) $this->adone(); - while ($this->titles) $this->_endTitle(); + while ($this->titles) $this->title__end(); } elseif ($this->actions) { - $this->_endAction(); + $this->action__end(); } elseif ($this->titles) { - $this->_endTitle(); + $this->title__end(); } } } diff --git a/php/src/output/std/NullMessenger.php b/php/src/output/std/NullMessenger.php index ca4f816..0218ebb 100644 --- a/php/src/output/std/NullMessenger.php +++ b/php/src/output/std/NullMessenger.php @@ -12,15 +12,18 @@ class NullMessenger implements IMessenger { } function section($content, ?callable $func=null, ?int $level=null): void { + if ($func !== null) $func($this); } function title($content, ?callable $func=null, ?int $level=null): void { + if ($func !== null) $func($this); } function desc($content, ?int $level=null): void { } function action($content, ?callable $func=null, ?int $level=null): void { + if ($func !== null) $func($this); } function step($content, ?int $level=null): void { diff --git a/php/src/output/std/ProxyMessenger.php b/php/src/output/std/ProxyMessenger.php index 527fec0..6b578b4 100644 --- a/php/src/output/std/ProxyMessenger.php +++ b/php/src/output/std/ProxyMessenger.php @@ -10,14 +10,14 @@ use nulib\output\IMessenger; * NB: si cette classe est instanciée sans argument, elle agit comme * {@link NullMessenger}: elle envoie tous les messages vers /dev/null */ -class ProxyMessenger implements IMessenger { +class ProxyMessenger implements _IMessenger { function __construct(?IMessenger ...$msgs) { foreach ($msgs as $msg) { if ($msg !== null) $this->msgs[] = $msg; } } - /** @var IMessenger[] */ + /** @var _IMessenger[] */ protected ?array $msgs = []; function isEmpty(): bool { @@ -43,45 +43,64 @@ class ProxyMessenger implements IMessenger { return $clone; } + function section__afterFunc(): void { + foreach ($this->msgs as $msg) { + if ($msg instanceof _IMessenger) { + $msg->section__afterFunc(); + } + } + } + function section($content, ?callable $func=null, ?int $level=null): void { - $useFunc = false; foreach ($this->msgs as $msg) { $msg->section($content, null, $level); - if ($msg instanceof _IMessenger) $useFunc = true; } - if ($useFunc && $func !== null) { + if ($func !== null) { try { $func($this); } finally { - /** @var _IMessenger $msg */ - foreach ($this->msgs as $msg) { - $msg->_endSection(); - } + $this->section__afterFunc(); + } + } + } + + function title__getMarks(): array { + $marks = []; + foreach ($this->msgs as $key => $msg) { + if ($msg instanceof _IMessenger) { + $marks[$key] = $msg->title__getMarks(); + } + } + return $marks; + } + + function title__beforeFunc(array $marks): void { + foreach ($this->msgs as $key => $msg) { + if ($msg instanceof _IMessenger) { + $msg->title__beforeFunc($marks[$key]); + } + } + } + + function title__afterFunc(array $marks): void { + foreach ($this->msgs as $key => $msg) { + if ($msg instanceof _IMessenger) { + $msg->title__afterFunc($marks[$key]); } } } function title($content, ?callable $func=null, ?int $level=null): void { - $useFunc = false; - $untils = []; + $marks = $this->title__getMarks(); foreach ($this->msgs as $msg) { - if ($msg instanceof _IMessenger) { - $useFunc = true; - $untils[] = $msg->_getTitleMark(); - } $msg->title($content, null, $level); } - if ($useFunc && $func !== null) { + if ($func !== null) { try { + $this->title__beforeFunc($marks); $func($this); } finally { - /** @var _IMessenger $msg */ - $index = 0; - foreach ($this->msgs as $msg) { - if ($msg instanceof _IMessenger) { - $msg->_endTitle($untils[$index++]); - } - } + $this->title__afterFunc($marks); } } } @@ -92,38 +111,43 @@ class ProxyMessenger implements IMessenger { } } - function action($content, ?callable $func=null, ?int $level=null): void { - $useFunc = false; - $untils = []; - foreach ($this->msgs as $msg) { + function action__getMarks(): array { + $marks = []; + foreach ($this->msgs as $key => $msg) { if ($msg instanceof _IMessenger) { - $useFunc = true; - $untils[] = $msg->_getActionMark(); + $marks[$key] = $msg->action__getMarks(); } + } + return $marks; + } + + function action__beforeFunc(array $marks): void { + foreach ($this->msgs as $key => $msg) { + if ($msg instanceof _IMessenger) { + $msg->action__beforeFunc($marks[$key]); + } + } + } + + function action__afterFunc(array $marks): void { + foreach ($this->msgs as $key => $msg) { + if ($msg instanceof _IMessenger) { + $msg->action__afterFunc($marks[$key]); + } + } + } + + function action($content, ?callable $func=null, ?int $level=null): void { + $marks = $this->action__getMarks(); + foreach ($this->msgs as $msg) { $msg->action($content, null, $level); } - if ($useFunc && $func !== null) { + if ($func !== null) { try { - $result = $func($this); - /** @var _IMessenger $msg */ - $index = 0; - foreach ($this->msgs as $msg) { - if ($msg->_getActionMark() > $untils[$index++]) { - $msg->aresult($result); - } - } - } catch (Exception $e) { - /** @var _IMessenger $msg */ - foreach ($this->msgs as $msg) { - $msg->afailure($e); - } - throw $e; + $this->action__beforeFunc($marks); + $func($this); } finally { - /** @var _IMessenger $msg */ - $index = 0; - foreach ($this->msgs as $msg) { - $msg->_endAction($untils[$index++]); - } + $this->action__afterFunc($marks); } } } diff --git a/php/src/output/std/_IMessenger.php b/php/src/output/std/_IMessenger.php index ed1c70f..eec67f3 100644 --- a/php/src/output/std/_IMessenger.php +++ b/php/src/output/std/_IMessenger.php @@ -74,17 +74,19 @@ interface _IMessenger extends IMessenger { "done" => [null, null], ]; - function _endSection(): void; + function section__afterFunc(): void; - function _getTitleMark(): int; + /** @return int[] */ + function title__getMarks(): array; + /** @param int[] $marks */ + function title__beforeFunc(array $marks): void; + /** @param int[] $marks */ + function title__afterFunc(array $marks): void; - function _getTitleId(): ?int; - - function _endTitle(?int $until=null): void; - - function _getActionMark(): int; - - function _getActionId(): ?int; - - function _endAction(?int $until=null): void; + /** @return int[] */ + function action__getMarks(): array; + /** @param int[] $marks */ + function action__beforeFunc(array $marks): void; + /** @param int[] $marks */ + function action__afterFunc(array $marks): void; } diff --git a/php/tbin/.gitignore b/php/tbin/.gitignore index 4e0bb30..e20f16c 100644 --- a/php/tbin/.gitignore +++ b/php/tbin/.gitignore @@ -1,3 +1,4 @@ +/devel/ /*.db /*.cache /*.log From 48d5f84bbd1455c5ed79f326ae334a842250e679 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 15 Oct 2025 12:15:04 +0400 Subject: [PATCH 62/91] modifs.mineures sans commentaires --- php/tbin/steam-train.php | 14 ++++++++++ php/tbin/test-output1.php | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100755 php/tbin/steam-train.php create mode 100755 php/tbin/test-output1.php diff --git a/php/tbin/steam-train.php b/php/tbin/steam-train.php new file mode 100755 index 0000000..cba3bcf --- /dev/null +++ b/php/tbin/steam-train.php @@ -0,0 +1,14 @@ +#!/usr/bin/php + "../vendor/autoload.php", + "bindir" => "../vendor/bin", + ]; +} +SteamTrainApp::run(); diff --git a/php/tbin/test-output1.php b/php/tbin/test-output1.php new file mode 100755 index 0000000..8a2f78e --- /dev/null +++ b/php/tbin/test-output1.php @@ -0,0 +1,56 @@ +#!/usr/bin/php + parent::ARGS, + + ["-c", "--con", "name" => "use", "value" => self::CON], + ["-l", "--log", "name" => "use", "value" => self::LOG], + ["-m", "--msg", "name" => "use", "value" => self::MSG], + ]; + + protected int $use = self::MSG; + + function main() { + switch ($this->use) { + case self::MSG: + $msg = new ProxyMessenger(); + $msg->addMessenger(con::get()); + $msg->addMessenger(new LogMessenger()); + break; + case self::CON: + $msg = con::get(); + break; + case self::LOG: + $msg = new LogMessenger(); + break; + } + $msg->info("test d'information"); + $msg->action("attente de 2 secondes", function (IMessenger $msg) { + sleep(1); + $msg->asuccess("1 seconde"); + sleep(1); + $msg->asuccess("1 seconde"); + }); + $msg->action("attente de 2 secondes", function (IMessenger $msg) { + sleep(1); + $msg->info("1 seconde"); + sleep(1); + $msg->info("1 seconde"); + }); + $msg->info("fin de test-appctl"); + } +}); From a0c6fb21e6ffd20fac68d25e056cb789b01bcb3e Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 15 Oct 2025 15:33:13 +0400 Subject: [PATCH 63/91] modifs.mineures sans commentaires --- php/src/output/std/AbstractMessenger.php | 29 ++++----- php/src/output/std/ConsoleMessenger.php | 81 +++++++++++------------- php/src/output/std/LogMessenger.php | 72 ++++++++++----------- 3 files changed, 85 insertions(+), 97 deletions(-) diff --git a/php/src/output/std/AbstractMessenger.php b/php/src/output/std/AbstractMessenger.php index 073c036..759b5cf 100644 --- a/php/src/output/std/AbstractMessenger.php +++ b/php/src/output/std/AbstractMessenger.php @@ -84,9 +84,8 @@ abstract class AbstractMessenger implements _IMessenger { } protected function _printTitle( - ?string $linePrefix, int $level, - string $type, $content, - int $indentLevel, StdOutput $out + int $level, string $type, ?string $linePrefix, int $indentLevel, + StdOutput $out, $content ): void { $prefixes = self::GENERIC_PREFIXES[$level][$type]; if ($prefixes[0]) $out->print(); @@ -136,10 +135,10 @@ abstract class AbstractMessenger implements _IMessenger { protected abstract function action__flush(bool $endAction=false, ?int $overrideLevel=null): void; protected function _printAction( - ?string $linePrefix, int $level, + int $level, ?string $linePrefix, int $indentLevel, + StdOutput $out, bool $printContent, $content, - bool $printResult, ?bool $rsuccess, $rcontent, - int $indentLevel, StdOutput $out + bool $printResult, ?bool $rsuccess, $rcontent ): void { $color = $out->isColor(); if ($rsuccess === true) $type = "success"; @@ -211,9 +210,8 @@ abstract class AbstractMessenger implements _IMessenger { } protected function _printGeneric( - ?string $linePrefix, int $level, - string $type, $content, - int $indentLevel, StdOutput $out + int $level, string $type, ?string $linePrefix, int $indentLevel, + StdOutput $out, $content ): void { $prefixes = self::GENERIC_PREFIXES[$level][$type]; $content = cl::with($content); @@ -246,9 +244,8 @@ abstract class AbstractMessenger implements _IMessenger { } protected function _printGenericOrException( - ?int $level, - string $type, $content, - int $indentLevel, StdOutput $out + ?int $level, string $type, int $indentLevel, + StdOutput $out, $content ): void { $linePrefix = $this->getLinePrefix(); # si $content contient des exceptions, les afficher avec un level moindre @@ -274,7 +271,7 @@ abstract class AbstractMessenger implements _IMessenger { $showContent = $this->checkLevel($level); if ($content !== null && $showContent) { $this->action__flush(); $flushActions = false; - $this->_printGeneric($linePrefix, $level, $type, $content, $indentLevel, $out); + $this->_printGeneric($level, $type, $linePrefix, $indentLevel, $out, $content); } if ($exceptions !== null) { $level1 = $this->decrLevel($level); @@ -284,15 +281,15 @@ abstract class AbstractMessenger implements _IMessenger { $message = exceptions::get_message($exception); if ($showContent) { if ($flushActions) { $this->action__flush(); $flushActions = false; } - $this->_printGeneric($linePrefix, $level, $type, $message, $indentLevel, $out); + $this->_printGeneric($level, $type, $linePrefix, $indentLevel, $out, $message); } # puis summary et traceback if ($showTraceback) { if ($flushActions) { $this->action__flush(); $flushActions = false; } $summary = exceptions::get_summary($exception, false); - $this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out); + $this->_printGeneric($level1, $type, $linePrefix, $indentLevel, $out, $summary); $traceback = exceptions::get_traceback($exception); - $this->_printGeneric($linePrefix, $level1, $type, $traceback, $indentLevel, $out); + $this->_printGeneric($level1, $type, $linePrefix, $indentLevel, $out, $traceback); } } } diff --git a/php/src/output/std/ConsoleMessenger.php b/php/src/output/std/ConsoleMessenger.php index 9083f3d..3d38a80 100644 --- a/php/src/output/std/ConsoleMessenger.php +++ b/php/src/output/std/ConsoleMessenger.php @@ -124,8 +124,8 @@ class ConsoleMessenger extends AbstractMessenger { $this->inSection = true; if (!$this->checkLevel($level)) return; $this->section = [ + "msg_level" => $level, "line_prefix" => $this->getLinePrefix(), - "level" => $level, "content" => $content, "print_content" => true, ]; @@ -142,9 +142,8 @@ class ConsoleMessenger extends AbstractMessenger { $section =& $this->section; if ($section !== null && $section["print_content"]) { $this->_printTitle( - $section["line_prefix"], $section["level"], - "section", $section["content"], - 0, $this->err); + $section["msg_level"], "section", $section["line_prefix"], 0, + $this->err, $section["content"]); $section["print_content"] = false; } } @@ -154,7 +153,7 @@ class ConsoleMessenger extends AbstractMessenger { if ($indentLevel < 0) $indentLevel = 0; if ($withActions) { foreach ($this->actions as $action) { - if ($action["level"] < $this->minLevel) continue; + if ($action["msg_level"] < $this->minLevel) continue; $indentLevel++; } } @@ -189,17 +188,15 @@ class ConsoleMessenger extends AbstractMessenger { foreach ($this->titles as &$title) { if ($title["print_content"]) { $this->_printTitle( - $title["line_prefix"], $title["level"], - "title", $title["content"], - $indentLevel, $err); + $title["msg_level"], "title", $title["line_prefix"], $indentLevel, + $err, $title["content"]); $title["print_content"] = false; } if ($title["print_descs"]) { foreach ($title["descs"] as $desc) { $this->_printGeneric( - $desc["line_prefix"], $desc["level"], - "desc", $desc["content"], - $indentLevel, $err); + $desc["msg_level"], "desc", $desc["line_prefix"], $indentLevel, + $err, $desc["content"]); } $title["descs"] = []; $title["print_descs"] = false; @@ -227,8 +224,8 @@ class ConsoleMessenger extends AbstractMessenger { A::merge($this->titles[array_key_last($this->titles)], [ "title_level" => $marks[0], "max_title_level" => null, + "msg_level" => $level, "line_prefix" => $this->getLinePrefix(), - "level" => $level, "content" => $content, "print_content" => true, "descs" => [], @@ -247,8 +244,8 @@ class ConsoleMessenger extends AbstractMessenger { function desc($content, ?int $level=null): void { if (!$this->checkLevel($level)) return; $desc = [ + "msg_level" => $level, "line_prefix" => $this->getLinePrefix(), - "level" => $level, "content" => $content, ]; $key = array_key_last($this->titles); @@ -259,9 +256,8 @@ class ConsoleMessenger extends AbstractMessenger { } else { # pas de titre en cours $this->_printGeneric( - $desc["line_prefix"], $desc["level"], - "desc", $desc["content"], - 0, $this->err); + $desc["msg_level"], "desc", $desc["line_prefix"], 0, + $this->err, $desc["content"]); } } @@ -294,8 +290,8 @@ class ConsoleMessenger extends AbstractMessenger { $index = 0; foreach ($this->actions as &$action) { $mergeResult = $index++ == $lastIndex && $endAction; + $level = $overrideLevel?? $action["msg_level"]; $linePrefix = $action["line_prefix"]; - $level = $overrideLevel?? $action["level"]; $content = $action["content"]; $printContent = $action["print_content"]; $rsuccess = $action["result_success"]; @@ -304,33 +300,32 @@ class ConsoleMessenger extends AbstractMessenger { if ($mergeResult) { if (time() - $action["timestamp"] <= 2) { $this->_printAction( - $linePrefix, $level, + $level, $linePrefix, $indentLevel, + $err, $printContent, $content, - true, $rsuccess, $rcontent, - $indentLevel, $err); + true, $rsuccess, $rcontent); } else { # si l'action a pris plus de 2 secondes, ne pas fusionner pour que # l'on voit le temps que ça a pris $this->_printAction( - $linePrefix, $level, + $level, $linePrefix, $indentLevel, + $err, $printContent, $content, - false, null, null, - $indentLevel, $err); + false, null, null); # recalculer une nouvelle ligne de préfixe pour le résultat $linePrefix = $this->getLinePrefix(); $this->_printAction( - $linePrefix, $level, + $level, $linePrefix, $indentLevel, + $err, false, null, - true, $rsuccess, $rcontent, - $indentLevel, $err); - + true, $rsuccess, $rcontent); } } elseif ($printContent) { $this->_printAction( - $linePrefix, $level, + $level, $linePrefix, $indentLevel, + $err, $printContent, $content, - false, $rsuccess, $rcontent, - $indentLevel, $err); + false, $rsuccess, $rcontent); $action["print_content"] = false; } $indentLevel++; @@ -358,8 +353,8 @@ class ConsoleMessenger extends AbstractMessenger { "action_level" => $marks[0], "max_action_level" => null, "timestamp" => time(), + "msg_level" => $level, "line_prefix" => $this->getLinePrefix(), - "level" => $level, "content" => $content, "print_content" => true, "result_success" => null, @@ -383,8 +378,8 @@ class ConsoleMessenger extends AbstractMessenger { function step($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "step", $content, - $this->getIndentLevel(), $this->err); + $level, "step", $this->getIndentLevel(), + $this->err, $content); } function asuccess($content=null, ?int $overrideLevel=null): void { @@ -421,32 +416,32 @@ class ConsoleMessenger extends AbstractMessenger { function print($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "print", $content, - $this->getIndentLevel(), $this->out); + $level, "print", $this->getIndentLevel(), + $this->out, $content); } function info($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "info", $content, - $this->getIndentLevel(), $this->err); + $level, "info", $this->getIndentLevel(), + $this->err, $content); } function note($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "note", $content, - $this->getIndentLevel(), $this->err); + $level, "note", $this->getIndentLevel(), + $this->err, $content); } function warning($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "warning", $content, - $this->getIndentLevel(), $this->err); + $level, "warning", $this->getIndentLevel(), + $this->err, $content); } function error($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "error", $content, - $this->getIndentLevel(), $this->err); + $level, "error", $this->getIndentLevel(), + $this->err, $content); } function end(bool $all=false): void { diff --git a/php/src/output/std/LogMessenger.php b/php/src/output/std/LogMessenger.php index b709959..2da5ad2 100644 --- a/php/src/output/std/LogMessenger.php +++ b/php/src/output/std/LogMessenger.php @@ -91,9 +91,8 @@ class LogMessenger extends AbstractMessenger { $this->section__end(); if (!$this->checkLevel($level)) return; $this->_printTitle( - $this->getLinePrefix(), $level, - "section", $content, - 0, $this->out); + $level, "section", $this->getLinePrefix(), 0, + $this->out, $content); if ($func !== null) { try { $func($this); @@ -144,9 +143,8 @@ class LogMessenger extends AbstractMessenger { "max_title_level" => null, ]; $this->_printTitle( - $this->getLinePrefix(), $level, - "title", $content, - $marks[0], $this->out); + $level, "title", $this->getLinePrefix(), $marks[0], + $this->out, $content); if ($func !== null) { try { $this->title__beforeFunc($marks); @@ -161,10 +159,8 @@ class LogMessenger extends AbstractMessenger { if (!$this->checkLevel($level)) return; $titleLevel = end($this->titles)["title_level"] ?? 0; $this->_printGeneric( - $this->getLinePrefix(), $level, - "desc", $content, - $titleLevel, $this->out); - + $level, "desc", $this->getLinePrefix(), $titleLevel, + $this->out, $content); } protected array $actions; @@ -209,13 +205,13 @@ class LogMessenger extends AbstractMessenger { "id" => $this->lastActionId++, "action_level" => $marks[0], "max_action_level" => null, - "level" => $level + "msg_level" => $level ]; $this->_printAction( - $this->getLinePrefix(), $level, + $level, $this->getLinePrefix(), $marks[0], + $this->out, true, $content, - false, null, null, - $marks[0], $this->out); + false, null, null); if ($func !== null) { try { $this->action__beforeFunc($marks); @@ -234,43 +230,43 @@ class LogMessenger extends AbstractMessenger { function step($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "step", $content, - $this->getIndentLevel(), $this->out); + $level, "step", $this->getIndentLevel(), + $this->out, $content); } function asuccess($content=null, ?int $overrideLevel=null): void { if ($this->action__getMarks()[0] == 0) $this->action(null); $action = end($this->actions); - $level = $overrideLevel ?? $action["level"]; + $level = $overrideLevel ?? $action["msg_level"]; $this->_printAction( - $this->getLinePrefix(), $level, + $level, $this->getLinePrefix(), $action["action_level"], + $this->out, false, null, - true, true, $content, - $action["action_level"], $this->out); + true, true, $content); $this->action__end(); } function afailure($content=null, ?int $overrideLevel=null): void { if ($this->action__getMarks()[0] == 0) $this->action(null); $action = end($this->actions); - $level = $overrideLevel ?? $action["level"]; + $level = $overrideLevel ?? $action["msg_level"]; $this->_printAction( - $this->getLinePrefix(), $level, + $level, $this->getLinePrefix(), $action["action_level"], + $this->out, false, null, - true, false, $content, - $action["action_level"], $this->out); + true, false, $content); $this->action__end(); } function adone($content=null, ?int $overrideLevel=null): void { if ($this->action__getMarks()[0] == 0) $this->action(null); $action = end($this->actions); - $level = $overrideLevel ?? $action["level"]; + $level = $overrideLevel ?? $action["msg_level"]; $this->_printAction( - $this->getLinePrefix(), $level, + $level, $this->getLinePrefix(), $action["action_level"], + $this->out, false, null, - true, null, $content, - $action["action_level"], $this->out); + true, null, $content); $this->action__end(); } @@ -291,32 +287,32 @@ class LogMessenger extends AbstractMessenger { function print($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "print", $content, - $this->getIndentLevel(), $this->out); + $level, "print", $this->getIndentLevel(), + $this->out, $content); } function info($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "info", $content, - $this->getIndentLevel(), $this->out); + $level, "info", $this->getIndentLevel(), + $this->out, $content); } function note($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "note", $content, - $this->getIndentLevel(), $this->out); + $level, "note", $this->getIndentLevel(), + $this->out, $content); } function warning($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "warning", $content, - $this->getIndentLevel(), $this->out); + $level, "warning", $this->getIndentLevel(), + $this->out, $content); } function error($content, ?int $level=null): void { $this->_printGenericOrException( - $level, "error", $content, - $this->getIndentLevel(), $this->out); + $level, "error", $this->getIndentLevel(), + $this->out, $content); } function end(bool $all=false): void { From cf5ef38a0fea50a9c4a977ed1699922df251a004 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 15 Oct 2025 16:06:25 +0400 Subject: [PATCH 64/91] modifs.mineures sans commentaires --- php/src/output/std/ConsoleMessenger.php | 62 ++++++++++++++++--------- php/src/output/std/LogMessenger.php | 56 +++++++++++++++------- php/src/output/std/ProxyMessenger.php | 9 ++-- php/src/output/std/_IMessenger.php | 2 +- 4 files changed, 86 insertions(+), 43 deletions(-) diff --git a/php/src/output/std/ConsoleMessenger.php b/php/src/output/std/ConsoleMessenger.php index 3d38a80..1cb8bcf 100644 --- a/php/src/output/std/ConsoleMessenger.php +++ b/php/src/output/std/ConsoleMessenger.php @@ -162,16 +162,21 @@ class ConsoleMessenger extends AbstractMessenger { protected array $titles; + protected function title__last(): ?array { + $last = end($this->titles); + return $last !== false? $last: null; + } + function title__getMarks(): array { return [count($this->titles)]; } protected function title__getId(): ?int { - return end($this->titles)["id"] ?? null; + return $this->title__last()["id"] ?? null; } protected function title__end(?int $until=null): void { - $title = $this->titles[array_key_last($this->titles)] ?? null; + $title = $this->title__last(); if ($title !== null) { $until ??= $title["max_title_level"]; $until ??= $this->title__getMarks()[0] - 1; @@ -205,13 +210,17 @@ class ConsoleMessenger extends AbstractMessenger { }; unset($title); } + protected function &title__ref(): ?array { + return $this->titles[array_key_last($this->titles)]; + } + function title__beforeFunc(array $marks): void { - $title =& $this->titles[array_key_last($this->titles)]; + $title =& $this->title__ref(); $title["max_title_level"] = $marks[0] + 1; } function title__afterFunc(array $marks): void { - $title =& $this->titles[array_key_last($this->titles)]; + $title =& $this->title__ref(); $title["max_title_level"] = null; $this->title__end($marks[0]); } @@ -221,7 +230,7 @@ class ConsoleMessenger extends AbstractMessenger { $marks = $this->title__getMarks(); // faire en deux temps pour linePrefix soit à jour $this->titles[] = ["id" => $this->lastTitleId++]; - A::merge($this->titles[array_key_last($this->titles)], [ + A::merge($this->title__ref(), [ "title_level" => $marks[0], "max_title_level" => null, "msg_level" => $level, @@ -248,9 +257,9 @@ class ConsoleMessenger extends AbstractMessenger { "line_prefix" => $this->getLinePrefix(), "content" => $content, ]; - $key = array_key_last($this->titles); - if ($key !== null) { - $title =& $this->titles[$key]; + $title = $this->title__last(); + if ($title !== null) { + $title =& $this->title__ref(); $title["descs"][] = $desc; $title["print_descs"] = true; } else { @@ -263,16 +272,21 @@ class ConsoleMessenger extends AbstractMessenger { protected array $actions; + protected function action__last(): ?array { + $last = end($this->actions); + return $last !== false? $last: null; + } + function action__getMarks(): array { return [count($this->actions)]; } protected function action__getId(): ?int { - return end($this->actions)["id"] ?? null; + return $this->action__last()["id"] ?? null; } protected function action__end(?int $until=null): void { - $action = $this->actions[array_key_last($this->actions)] ?? null; + $action = $this->action__last(); if ($action !== null) { $until ??= $action["max_action_level"]; $until ??= $this->action__getMarks()[0] - 1; @@ -320,6 +334,7 @@ class ConsoleMessenger extends AbstractMessenger { false, null, true, $rsuccess, $rcontent); } + $action["action_aresult"] = true; } elseif ($printContent) { $this->_printAction( $level, $linePrefix, $indentLevel, @@ -333,13 +348,19 @@ class ConsoleMessenger extends AbstractMessenger { if ($endAction) $this->action__end(); } + protected function &action__ref(): ?array { + return $this->actions[array_key_last($this->actions)]; + } + function action__beforeFunc(array $marks): void { - $action =& $this->actions[array_key_last($this->actions)]; + $action =& $this->action__ref(); $action["max_action_level"] = $marks[0] + 1; } - function action__afterFunc(array $marks): void { - $action =& $this->actions[array_key_last($this->actions)]; + function action__afterFunc(array $marks, $result): void { + $action =& $this->action__ref(); + $aresult = $action["action_aresult"] ?? false; + if (!$aresult) $this->aresult($result); $action["max_action_level"] = null; $this->action__end($marks[0]); } @@ -349,9 +370,10 @@ class ConsoleMessenger extends AbstractMessenger { $marks = $this->action__getMarks(); // faire en deux temps pour linePrefix soit à jour $this->actions[] = ["id" => $this->lastActionId++]; - A::merge($this->actions[array_key_last($this->actions)], [ + A::merge($this->action__ref(), [ "action_level" => $marks[0], "max_action_level" => null, + "action_aresult" => false, "timestamp" => time(), "msg_level" => $level, "line_prefix" => $this->getLinePrefix(), @@ -362,16 +384,14 @@ class ConsoleMessenger extends AbstractMessenger { ]); if ($func !== null) { try { + $result = null; $this->action__beforeFunc($marks); $result = $func($this); - if ($this->action__getMarks()[0] > $marks[0]) { - $this->aresult($result); - } } catch (Exception $e) { $this->afailure($e); throw $e; } finally { - $this->action__afterFunc($marks); + $this->action__afterFunc($marks, $result); } } } @@ -384,7 +404,7 @@ class ConsoleMessenger extends AbstractMessenger { function asuccess($content=null, ?int $overrideLevel=null): void { if (!$this->actions) $this->action(null); - $action =& $this->actions[array_key_last($this->actions)]; + $action =& $this->action__ref(); $action["result_success"] = true; $action["result_content"] = $content; $this->action__flush(true, $overrideLevel); @@ -392,7 +412,7 @@ class ConsoleMessenger extends AbstractMessenger { function afailure($content=null, ?int $overrideLevel=null): void { if (!$this->actions) $this->action(null); - $action =& $this->actions[array_key_last($this->actions)]; + $action =& $this->action__ref(); $action["result_success"] = false; $action["result_content"] = $content; $this->action__flush(true, $overrideLevel); @@ -400,7 +420,7 @@ class ConsoleMessenger extends AbstractMessenger { function adone($content=null, ?int $overrideLevel=null): void { if (!$this->actions) $this->action(null); - $action =& $this->actions[array_key_last($this->actions)]; + $action =& $this->action__ref(); $action["result_success"] = null; $action["result_content"] = $content; $this->action__flush(true, $overrideLevel); diff --git a/php/src/output/std/LogMessenger.php b/php/src/output/std/LogMessenger.php index 2da5ad2..3bf3391 100644 --- a/php/src/output/std/LogMessenger.php +++ b/php/src/output/std/LogMessenger.php @@ -104,16 +104,21 @@ class LogMessenger extends AbstractMessenger { protected array $titles; + protected function title__last(): ?array { + $last = end($this->titles); + return $last !== false? $last: null; + } + function title__getMarks(): array { return [count($this->titles)]; } protected function title__getId(): ?int { - return end($this->titles)["id"] ?? null; + return $this->title__last()["id"] ?? null; } protected function title__end(?int $until=null): void { - $title = $this->titles[array_key_last($this->titles)] ?? null; + $title = $this->title__last(); if ($title !== null) { $until ??= $title["max_title_level"]; $until ??= $this->title__getMarks()[0] - 1; @@ -123,13 +128,17 @@ class LogMessenger extends AbstractMessenger { } } + protected function &title__ref(): ?array { + return $this->titles[array_key_last($this->titles)]; + } + function title__beforeFunc(array $marks): void { - $title =& $this->titles[array_key_last($this->titles)]; + $title =& $this->title__ref(); $title["max_title_level"] = $marks[0] + 1; } function title__afterFunc(array $marks): void { - $title =& $this->titles[array_key_last($this->titles)]; + $title =& $this->title__ref(); $title["max_title_level"] = null; $this->title__end($marks[0]); } @@ -157,7 +166,7 @@ class LogMessenger extends AbstractMessenger { function desc($content, ?int $level=null): void { if (!$this->checkLevel($level)) return; - $titleLevel = end($this->titles)["title_level"] ?? 0; + $titleLevel = $this->title__last()["title_level"] ?? 0; $this->_printGeneric( $level, "desc", $this->getLinePrefix(), $titleLevel, $this->out, $content); @@ -165,16 +174,21 @@ class LogMessenger extends AbstractMessenger { protected array $actions; + protected function action__last(): ?array { + $last = end($this->actions); + return $last !== false? $last: null; + } + function action__getMarks(): array { return [count($this->actions)]; } protected function action__getId(): ?int { - return end($this->actions)["id"] ?? null; + return $this->action__last()["id"] ?? null; } protected function action__end(?int $until=null): void { - $action = $this->actions[array_key_last($this->actions)] ?? null; + $action = $this->action__last(); if ($action !== null) { $until ??= $action["max_action_level"]; $until ??= $this->action__getMarks()[0] - 1; @@ -187,13 +201,19 @@ class LogMessenger extends AbstractMessenger { protected function action__flush(bool $endAction=false, ?int $overrideLevel=null): void { } + protected function &action__ref(): ?array { + return $this->actions[array_key_last($this->actions)]; + } + function action__beforeFunc(array $marks): void { - $action =& $this->actions[array_key_last($this->actions)]; + $action =& $this->action__ref(); $action["max_action_level"] = $marks[0] + 1; } - function action__afterFunc(array $marks): void { - $action =& $this->actions[array_key_last($this->actions)]; + function action__afterFunc(array $marks, $result): void { + $action =& $this->action__ref(); + $aresult = $action["action_aresult"] ?? false; + if (!$aresult) $this->aresult($result); $action["max_action_level"] = null; $this->action__end($marks[0]); } @@ -205,6 +225,7 @@ class LogMessenger extends AbstractMessenger { "id" => $this->lastActionId++, "action_level" => $marks[0], "max_action_level" => null, + "action_aresult" => false, "msg_level" => $level ]; $this->_printAction( @@ -214,16 +235,14 @@ class LogMessenger extends AbstractMessenger { false, null, null); if ($func !== null) { try { + $result = null; $this->action__beforeFunc($marks); $result = $func($this); - if ($this->action__getMarks()[0] > $marks[0]) { - $this->aresult($result); - } } catch (Exception $e) { $this->afailure($e); throw $e; } finally { - $this->action__afterFunc($marks); + $this->action__afterFunc($marks, $result); } } } @@ -236,37 +255,40 @@ class LogMessenger extends AbstractMessenger { function asuccess($content=null, ?int $overrideLevel=null): void { if ($this->action__getMarks()[0] == 0) $this->action(null); - $action = end($this->actions); + $action =& $this->action__ref(); $level = $overrideLevel ?? $action["msg_level"]; $this->_printAction( $level, $this->getLinePrefix(), $action["action_level"], $this->out, false, null, true, true, $content); + $action["action_aresult"] = true; $this->action__end(); } function afailure($content=null, ?int $overrideLevel=null): void { if ($this->action__getMarks()[0] == 0) $this->action(null); - $action = end($this->actions); + $action =& $this->action__ref(); $level = $overrideLevel ?? $action["msg_level"]; $this->_printAction( $level, $this->getLinePrefix(), $action["action_level"], $this->out, false, null, true, false, $content); + $action["action_aresult"] = true; $this->action__end(); } function adone($content=null, ?int $overrideLevel=null): void { if ($this->action__getMarks()[0] == 0) $this->action(null); - $action = end($this->actions); + $action =& $this->action__ref(); $level = $overrideLevel ?? $action["msg_level"]; $this->_printAction( $level, $this->getLinePrefix(), $action["action_level"], $this->out, false, null, true, null, $content); + $action["action_aresult"] = true; $this->action__end(); } diff --git a/php/src/output/std/ProxyMessenger.php b/php/src/output/std/ProxyMessenger.php index 6b578b4..23c91d6 100644 --- a/php/src/output/std/ProxyMessenger.php +++ b/php/src/output/std/ProxyMessenger.php @@ -129,10 +129,10 @@ class ProxyMessenger implements _IMessenger { } } - function action__afterFunc(array $marks): void { + function action__afterFunc(array $marks, $result): void { foreach ($this->msgs as $key => $msg) { if ($msg instanceof _IMessenger) { - $msg->action__afterFunc($marks[$key]); + $msg->action__afterFunc($marks[$key], $result); } } } @@ -144,10 +144,11 @@ class ProxyMessenger implements _IMessenger { } if ($func !== null) { try { + $result = null; $this->action__beforeFunc($marks); - $func($this); + $result = $func($this); } finally { - $this->action__afterFunc($marks); + $this->action__afterFunc($marks, $result); } } } diff --git a/php/src/output/std/_IMessenger.php b/php/src/output/std/_IMessenger.php index eec67f3..86d84cb 100644 --- a/php/src/output/std/_IMessenger.php +++ b/php/src/output/std/_IMessenger.php @@ -88,5 +88,5 @@ interface _IMessenger extends IMessenger { /** @param int[] $marks */ function action__beforeFunc(array $marks): void; /** @param int[] $marks */ - function action__afterFunc(array $marks): void; + function action__afterFunc(array $marks, $result): void; } From 7d332552ab4fd318be81519920d870ce7dc767cc Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 15 Oct 2025 17:09:19 +0400 Subject: [PATCH 65/91] modifs.mineures sans commentaires --- php/src/php/time/Delay.php | 16 +++++++++++----- php/src/php/time/TODO.md | 11 +++++------ php/tbin/cachectl.php | 7 +++++++ php/tbin/test-cache.php | 9 +++++++++ php/tests/php/time/DateTimeTest.php | 8 ++++++++ php/tests/php/time/DelayTest.php | 8 ++++++++ 6 files changed, 48 insertions(+), 11 deletions(-) create mode 100755 php/tbin/cachectl.php diff --git a/php/src/php/time/Delay.php b/php/src/php/time/Delay.php index 1d11b72..f1008b2 100644 --- a/php/src/php/time/Delay.php +++ b/php/src/php/time/Delay.php @@ -91,7 +91,10 @@ class Delay { $from = MutableDateTime::with($from)->clone(true); if ($delay === "INF") { $dest = $from; - $dest->add(new DateInterval("P9999Y")); + # rajouter 1000 ans pour ne pas dépasser la capacité + #XXX avant, c'était 9999 ans, mais getDest() provoque une exception parce + # que DateTime ne sait pas traiter une valeur flottante + $dest->add(new DateInterval("P1000Y")); $repr = "INF"; } elseif (is_int($delay)) { [$dest, $repr] = self::compute_dest($delay, "s", null, $from); @@ -119,10 +122,11 @@ class Delay { } function __serialize(): array { - return [$this->dest, $this->repr]; + return [$this->dest->clone(), $this->repr]; } function __unserialize(array $data): void { - [$this->dest, $this->repr] = $data; + [$dest, $this->repr] = $data; + $this->dest = $dest->clone(true); } /** @var MutableDateTime */ @@ -132,20 +136,22 @@ class Delay { return $this->dest->clone(); } - function addDuration($duration) { + function addDuration($duration): self { if (is_numeric($duration) && $duration < 0) { $this->dest->sub(DateInterval::with(-$duration)); } else { $this->dest->add(DateInterval::with($duration)); } + return $this; } - function subDuration($duration) { + function subDuration($duration): self { if (is_numeric($duration) && $duration < 0) { $this->dest->add(DateInterval::with(-$duration)); } else { $this->dest->sub(DateInterval::with($duration)); } + return $this; } /** @var string */ diff --git a/php/src/php/time/TODO.md b/php/src/php/time/TODO.md index edf17b0..f5ff33b 100644 --- a/php/src/php/time/TODO.md +++ b/php/src/php/time/TODO.md @@ -1,10 +1,9 @@ # nulib\php\time -* Date, DateTime sont immutables par défaut. par exemple, add() retourne un nouvel objet. - ajouter une version des méthodes qui modifie les données en place en les - préfixant de `_` e.g `_add()` - - en terme d'implémentation, dériver \DateTime pour supporter les modification - en place, bien que ce ne soit pas le fonctionnement par défaut +* refaire l'implémentation pour les délais INF. c'est un cas particulier qui + n'est jamais atteint. il faut donc implémenter un traitement spécifique dans + chaque méthode (i.e `if ($repr === "INF") { doSomething() }`) + * la destination est toujours 1000 ans dans le futur + * la différence est toujours de 1000 ans -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/php/tbin/cachectl.php b/php/tbin/cachectl.php new file mode 100755 index 0000000..4afa7a5 --- /dev/null +++ b/php/tbin/cachectl.php @@ -0,0 +1,7 @@ +#!/usr/bin/php +get()); if ($dumpInfos) { diff --git a/php/tests/php/time/DateTimeTest.php b/php/tests/php/time/DateTimeTest.php index 0880c67..41ce15e 100644 --- a/php/tests/php/time/DateTimeTest.php +++ b/php/tests/php/time/DateTimeTest.php @@ -118,4 +118,12 @@ class DateTimeTest extends TestCase { self::assertFalse($b >= $b2); self::assertFalse($b >= $b3); } + + function testSerialize() { + $date = new DateTime(); + $serialized = serialize($date); + echo "serialized: $serialized\n"; + $unserialized = unserialize($serialized); + self::assertEquals($date, $unserialized); + } } diff --git a/php/tests/php/time/DelayTest.php b/php/tests/php/time/DelayTest.php index b86f6af..dca3321 100644 --- a/php/tests/php/time/DelayTest.php +++ b/php/tests/php/time/DelayTest.php @@ -76,4 +76,12 @@ class DelayTest extends TestCase { sleep(5); self::assertTrue($delay->isElapsed()); } + + function testSerialize() { + $delay = new Delay(5); + $serialized = serialize($delay); + echo "serialized: $serialized\n"; + $unserialized = unserialize($serialized); + self::assertEquals($delay, $unserialized); + } } From 5eb376257f8d096bee5fefaac44d01b9a6b9a313 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 16 Oct 2025 06:07:16 +0400 Subject: [PATCH 66/91] renommer data en key --- php/src/cache/CacheFile.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/php/src/cache/CacheFile.php b/php/src/cache/CacheFile.php index 15821d5..9fa95b2 100644 --- a/php/src/cache/CacheFile.php +++ b/php/src/cache/CacheFile.php @@ -178,13 +178,14 @@ class CacheFile extends SharedFile { } protected function afterAction() { + # égalité non stricte pour start et duration $modified = false; if ($this->start != $this->ostart) $modified = true; $duration = $this->duration; $oduration = $this->oduration; if ($duration === null || $oduration === null) $modified = true; elseif ($duration->getDest() != $oduration->getDest()) $modified = true; - # égalité stricte uniquement pour $data et $datafiles + # égalité stricte pour $data if ($this->data !== $this->odata) $modified = true; if ($modified && !$this->readonly) { $this->lockWrite(); @@ -288,20 +289,20 @@ class CacheFile extends SharedFile { return $this; } - function get($data=null, bool $noCache=false) { - return $this->action(function () use ($data, $noCache) { - return $this->refreshData($data, $noCache); + function get($key=null, bool $noCache=false) { + return $this->action(function () use ($key, $noCache) { + return $this->refreshData($key, $noCache); }); } - function all($data=null, bool $noCache=false): ?iterable { - $data = $this->get($data, $noCache); + function all($key=null, bool $noCache=false): ?iterable { + $data = $this->get($key, $noCache); if ($data !== null && !is_iterable($data)) $data = [$data]; return $data; } - function delete($data=null): void { - $source = $this->sources[$data] ?? null; + function delete($key=null): void { + $source = $this->sources[$key] ?? null; if ($source !== null) $source->delete(); } From 27eb08ecffbdfdd49c40d77feaaca7975fba4a9e Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 16 Oct 2025 06:16:05 +0400 Subject: [PATCH 67/91] modifs.mineures sans commentaires --- php/src/cache/CacheFile.php | 1 + php/src/mail/MailTemplateHelper.php | 1 + php/src/output/con.php | 2 -- php/src/output/log.php | 1 - php/src/output/std/ProxyMessenger.php | 1 - php/tests/app/args/SimpleAolistTest.php | 1 - php/tests/app/args/SimpleArgsParserTest.php | 1 - php/tests/cache/cacheTest.php | 1 - php/tests/php/time/DelayTest.php | 1 - 9 files changed, 2 insertions(+), 8 deletions(-) diff --git a/php/src/cache/CacheFile.php b/php/src/cache/CacheFile.php index 9fa95b2..ea738d7 100644 --- a/php/src/cache/CacheFile.php +++ b/php/src/cache/CacheFile.php @@ -3,6 +3,7 @@ namespace nulib\cache; use Exception; use nulib\cv; +use nulib\exceptions; use nulib\ext\utils; use nulib\file\SharedFile; use nulib\os\path; diff --git a/php/src/mail/MailTemplateHelper.php b/php/src/mail/MailTemplateHelper.php index 0633749..37d4cca 100644 --- a/php/src/mail/MailTemplateHelper.php +++ b/php/src/mail/MailTemplateHelper.php @@ -1,6 +1,7 @@ Date: Thu, 16 Oct 2025 06:33:29 +0400 Subject: [PATCH 68/91] support de la session --- php/src/mail/MailTemplateHelper.php | 1 + php/src/web/params/F.php | 8 +- php/src/web/params/G.php | 8 +- php/src/web/params/P.php | 10 +- php/src/web/params/R.php | 2 +- php/src/web/session.php | 244 ++++++++++++++++++++++++++++ 6 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 php/src/web/session.php diff --git a/php/src/mail/MailTemplateHelper.php b/php/src/mail/MailTemplateHelper.php index 37d4cca..37d68d9 100644 --- a/php/src/mail/MailTemplateHelper.php +++ b/php/src/mail/MailTemplateHelper.php @@ -3,6 +3,7 @@ namespace nulib\mail; use nulib\app\config; use nulib\cl; +use nulib\web\session; class MailTemplateHelper { function __construct(?array $data) { diff --git a/php/src/web/params/F.php b/php/src/web/params/F.php index 18ed828..987b1bc 100644 --- a/php/src/web/params/F.php +++ b/php/src/web/params/F.php @@ -17,7 +17,7 @@ class F { } /** obtenir le paramètre $name en cherchant dans $_POST puis $_GET */ - static final function get($name, $default=null, bool $trim=false) { + static final function get($name, $default=null, bool $trim=false): ?string { if ($name === null || $name === false) $value = $default; elseif (array_key_exists($name, $_POST)) $value = $_POST[$name]; elseif (array_key_exists($name, $_GET)) $value = $_GET[$name]; @@ -47,6 +47,10 @@ class F { )); } + /** + * calculer la liste de tous les paramètres qui ont été passés. ensuite, + * fusionner le tableau $merge s'il est spécifié + */ static final function merge(?array $merge=null): array { $params = []; foreach (self::get_names() as $name) { @@ -56,7 +60,7 @@ class F { } /** - * retourner une liste des paramètres qui ont été passés, en les sélectionnant + * calculer une liste des paramètres qui ont été passés, en les sélectionnant * selon le contenu de $includes et $excludes. ensuite, fusionner le tableau * $merge s'il est spécifié * diff --git a/php/src/web/params/G.php b/php/src/web/params/G.php index ee37af2..2948712 100644 --- a/php/src/web/params/G.php +++ b/php/src/web/params/G.php @@ -15,7 +15,7 @@ class G { } /** obtenir le paramètre $name */ - static final function get($name, $default=null, bool $trim=false) { + static final function get($name, $default=null, bool $trim=false): ?string { $value = cl::get($_GET, $name, $default); if ($trim) $value = str::trim($value); return $value; @@ -26,7 +26,11 @@ class G { $_GET[$name] = $value; } - static final function xselect(?array $includes, ?array $excludes=null, ?array $merge=null): array { + static final function merge(?array $merge=null): array { + return cl::merge($_GET, $merge); + } + + static final function select(?array $includes, ?array $excludes=null, ?array $merge=null): array { return cl::merge(cl::xselect($_GET, $includes, $excludes), $merge); } } diff --git a/php/src/web/params/P.php b/php/src/web/params/P.php index 04faeec..eddb151 100644 --- a/php/src/web/params/P.php +++ b/php/src/web/params/P.php @@ -15,7 +15,7 @@ class P { } /** obtenir le paramètre $name */ - static final function get($name, $default=null, bool $trim=false) { + static final function get($name, $default=null, bool $trim=false): ?string { $value = cl::get($_POST, $name, $default); if ($trim) $value = str::trim($value); return $value; @@ -30,4 +30,12 @@ class P { static final function raw(): string { return file_get_contents("php://input"); } + + static final function merge(?array $merge=null): array { + return cl::merge($_POST, $merge); + } + + static final function select(?array $includes, ?array $excludes=null, ?array $merge=null): array { + return cl::merge(cl::xselect($_POST, $includes, $excludes), $merge); + } } diff --git a/php/src/web/params/R.php b/php/src/web/params/R.php index 9c1aae6..b7d38fa 100644 --- a/php/src/web/params/R.php +++ b/php/src/web/params/R.php @@ -15,7 +15,7 @@ class R { } /** obtenir le paramètre $name */ - static final function get($name, $default=null, bool $trim=false) { + static final function get($name, $default=null, bool $trim=false): ?string { $value = cl::get($_REQUEST, $name, $default); if ($trim) $value = str::trim($value); return $value; diff --git a/php/src/web/session.php b/php/src/web/session.php new file mode 100644 index 0000000..f475858 --- /dev/null +++ b/php/src/web/session.php @@ -0,0 +1,244 @@ + $duration, + ]); + self::$started_once = true; + + $creation_time = self::get(self::SESSION_CREATION_TIME, false); + if (!$creation_time) { + # création initiale + self::set(self::SESSION_CREATION_TIME, time()); + return true; + } elseif ($canSetCookies) { + # étendre la durée du cookie + $params = session_get_cookie_params(); + setcookie(session_name(), session_id(), time() + $duration, $params["path"], $params["domain"], $params["secure"], $params["httponly"]); + } + } + return false; + } + + /** + * enregistrer la session, la fermer et libérer son verrou. + * + * cette fonction peut être appelée avant une opération longue si on n'a plus + * besoin de la session. + * + * retourn true si la session a été fermée, false sinon. + */ + static final function close(): bool { + if (self::started()) { + session_write_close(); + return true; + } + return false; + } + + /** + * vider la session de toutes ses variables ($unsetOnly==true) ou la détruire + * ($unsetOnly==false). en cas de destruction de la session, supprimer aussi + * le cookie de session + * + * si $unsetOnly==true, refaire la variable SESSION_CREATION_TIME + */ + static final function destroy(bool $unsetOnly=false, bool $clearCookie=true): void { + self::start(); + if ($unsetOnly) { + session_unset(); + self::set(self::SESSION_CREATION_TIME, time()); + } else { + $canSetCookies = !headers_sent(); + if ($clearCookie && $canSetCookies && ini_get("session.use_cookies")) { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"]); + } + session_destroy(); + } + } + + /** + * Vider la session de toutes les clés spécifiées dans $keys qui ne sont pas + * mentionnées dans $keeps + * + * Si $keys vaut null, toutes les clés sont supprimées comme avec destroy(true) + * notamment, cela signifie que la variable SESSION_CREATION_TIME est refaite + */ + static final function unset_keys(?array $keys, ?array $keeps=null): void { + $updateSessionCreationTime = false; + if ($keys === null) { + $keys = array_keys($_SESSION); + $updateSessionCreationTime = true; + } + if ($keeps !== null) $keys = array_diff($keys, $keeps); + foreach ($keys as $key) { + unset($_SESSION[$key]); + } + if ($updateSessionCreationTime) { + self::set(self::SESSION_CREATION_TIME, time()); + } + } + + /** vérifier si la session est démarrée et si la clé spécifiée existe. */ + static final function has($key): bool { + if ($key === null || $key === false) return false; + return isset($_SESSION) && array_key_exists($key, $_SESSION); + } + + /** obtenir la valeur associée à la clé spécifiée si la session est démarrée. */ + static final function get(string $key, $default=null) { + if (!isset($_SESSION)) return $default; + return cl::get($_SESSION, $key, $default); + } + + /** + * mettre à jour la valeur d'une variable de session. + * + * ne pas chercher à savoir si la session est démarrée ou non + */ + static final function set(string $key, $value): void { + $_SESSION[$key] = $value; + } + + /** + * comme {@link set()} mais rouvrir automatiquement la session si nécessaire, + * à condition qu'elle aie déjà été ouverte une fois + */ + static final function setx(string $key, $value): void { + $close = !self::started() && self::started_once(); + if ($close) self::start(); + self::set($key, $value); + if ($close) self::close(); + } + + /** + * supprimer une variable de session. + * + * ne pas chercher à savoir si la session est démarrée ou non + */ + static final function del(string $key): void { + unset($_SESSION[$key]); + } + + /** + * comme {@link del()} mais rouvrir automatiquement la session si nécessaire, + * à condition qu'elle aie déjà été ouverte une fois + */ + static final function delx(string $key): void { + $close = !self::started() && self::started_once(); + if ($close) self::start(); + self::del($key); + if ($close) self::close(); + } + + /** vérifier si chemin de clé spécifié existe dans la session. */ + static final function phas($pkey): bool { + return isset($_SESSION) && cl::phas($_SESSION, $pkey); + } + + /** obtenir la valeur associée au chemin de clé spécifié si la session est démarrée. */ + static final function pget($pkey, $default=null) { + return isset($_SESSION) && cl::pget($_SESSION, $pkey, $default); + } + + /** + * mettre à jour la valeur correspondant au chemin de clé spécifié. + * + * ne pas chercher à savoir si la session est démarrée ou non + */ + static final function pset($pkey, $value): void { + cl::pset($_SESSION, $pkey, $value); + } + + /** + * comme {@link pset()} mais rouvrir automatiquement la session si nécessaire, + * à condition qu'elle aie déjà été ouverte une fois + */ + static final function psetx($pkey, $value): void { + $close = !self::started() && self::started_once(); + if ($close) self::start(); + self::pset($pkey, $value); + if ($close) self::close(); + } + + /** + * supprimer la variable au chemin de clé spécifié. + * + * ne pas chercher à savoir si la session est démarrée ou non + */ + static final function pdel($pkey): void { + cl::pdel($_SESSION, $pkey); + } + + /** + * comme {@link pdel()} mais rouvrir automatiquement la session si nécessaire, + * à condition qu'elle aie déjà été ouverte une fois + */ + static final function pdelx(string $key): void { + $close = !self::started() && self::started_once(); + if ($close) self::start(); + self::pdel($key); + if ($close) self::close(); + } +} From 2f3a21aad4707165988ca944fc375ec14fc1174f Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 16 Oct 2025 08:03:35 +0400 Subject: [PATCH 69/91] =?UTF-8?q?support=20d=C3=A9lai=20infini?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/src/php/time/Delay.php | 76 ++++++++++++++++++-------------- php/src/php/time/TODO.md | 6 --- php/tests/php/time/DelayTest.php | 15 +++++++ 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/php/src/php/time/Delay.php b/php/src/php/time/Delay.php index f1008b2..15aa96f 100644 --- a/php/src/php/time/Delay.php +++ b/php/src/php/time/Delay.php @@ -30,6 +30,11 @@ class Delay { else return new static($delay, $from); } + /** + * pour une durée infinie, l'intervalle est toujours de 1000 ans dans le futur + */ + const INF_INTERVAL = "P1000Y"; + /** valeurs par défaut de x et y pour les unités supportées */ const DEFAULTS = [ "w" => [0, 5], @@ -89,12 +94,9 @@ class Delay { function __construct($delay, ?DateTimeInterface $from=null) { $from = MutableDateTime::with($from)->clone(true); - if ($delay === "INF") { - $dest = $from; - # rajouter 1000 ans pour ne pas dépasser la capacité - #XXX avant, c'était 9999 ans, mais getDest() provoque une exception parce - # que DateTime ne sait pas traiter une valeur flottante - $dest->add(new DateInterval("P1000Y")); + if ($delay === null || $delay === "INF") { + # $dest === null signifie un délai infini + $dest = null; $repr = "INF"; } elseif (is_int($delay)) { [$dest, $repr] = self::compute_dest($delay, "s", null, $from); @@ -118,38 +120,51 @@ class Delay { } function __clone() { - $this->dest = clone $this->dest; + if ($this->dest !== null) { + $this->dest = clone $this->dest; + } } function __serialize(): array { - return [$this->dest->clone(), $this->repr]; + $dest = $this->dest; + if ($dest !== null) $dest = $dest->clone(); + return [$dest, $this->repr]; } function __unserialize(array $data): void { [$dest, $this->repr] = $data; - $this->dest = $dest->clone(true); + if ($dest !== null) $dest = $dest->clone(true); + $this->dest = $dest; } - /** @var MutableDateTime */ - protected $dest; + protected ?MutableDateTime $dest; function getDest(): DateTime { - return $this->dest->clone(); + $dest = $this->dest; + if ($dest === null) { + $dest = new MutableDateTime(); + $dest->add(new \DateInterval(self::INF_INTERVAL)); + } + return $dest->clone(); } function addDuration($duration): self { - if (is_numeric($duration) && $duration < 0) { - $this->dest->sub(DateInterval::with(-$duration)); - } else { - $this->dest->add(DateInterval::with($duration)); + if ($this->dest !== null) { + if (is_numeric($duration) && $duration < 0) { + $this->dest->sub(DateInterval::with(-$duration)); + } else { + $this->dest->add(DateInterval::with($duration)); + } } return $this; } function subDuration($duration): self { - if (is_numeric($duration) && $duration < 0) { - $this->dest->add(DateInterval::with(-$duration)); - } else { - $this->dest->sub(DateInterval::with($duration)); + if ($this->dest !== null) { + if (is_numeric($duration) && $duration < 0) { + $this->dest->add(DateInterval::with(-$duration)); + } else { + $this->dest->sub(DateInterval::with($duration)); + } } return $this; } @@ -161,23 +176,20 @@ class Delay { return $this->repr; } - protected function _getDiff(?DateTimeInterface $now=null): \DateInterval { - $now ??= new \DateTime(); - return $this->dest->diff($now); - } - - /** retourner true si le délai imparti est écoulé */ - function isElapsed(?DateTimeInterface $now=null): bool { - if ($this->repr === "INF") return false; - else return $this->_getDiff($now)->invert == 0; - } - /** * retourner l'intervalle entre le moment courant et la destination. * * l'intervalle est négatif si le délai n'est pas écoulé, positif sinon */ function getDiff(?DateTimeInterface $now=null): DateInterval { - return new DateInterval($this->_getDiff($now)); + $dest = $this->dest; + if ($dest !== null) return $dest->diff($now ?? new \DateTime()); + else return new DateInterval("-".self::INF_INTERVAL); + } + + /** retourner true si le délai imparti est écoulé */ + function isElapsed(?DateTimeInterface $now=null): bool { + if ($this->dest === null) return false; + else return $this->getDiff($now)->invert == 0; } } diff --git a/php/src/php/time/TODO.md b/php/src/php/time/TODO.md index f5ff33b..ffa6b2e 100644 --- a/php/src/php/time/TODO.md +++ b/php/src/php/time/TODO.md @@ -1,9 +1,3 @@ # nulib\php\time -* refaire l'implémentation pour les délais INF. c'est un cas particulier qui - n'est jamais atteint. il faut donc implémenter un traitement spécifique dans - chaque méthode (i.e `if ($repr === "INF") { doSomething() }`) - * la destination est toujours 1000 ans dans le futur - * la différence est toujours de 1000 ans - -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/php/tests/php/time/DelayTest.php b/php/tests/php/time/DelayTest.php index 1b2ef42..b7c1dd2 100644 --- a/php/tests/php/time/DelayTest.php +++ b/php/tests/php/time/DelayTest.php @@ -83,4 +83,19 @@ class DelayTest extends TestCase { $unserialized = unserialize($serialized); self::assertEquals($delay, $unserialized); } + + function testInf() { + $delay = new Delay("INF"); + self::assertSame("INF", strval($delay)); + self::assertFalse($delay->isElapsed()); + + $diff = $delay->getDiff(); + self::assertSame("-P1000YT", strval($diff)); + + $serialized = serialize($delay); + self::assertSame('O:20:"nulib\php\time\Delay":2:{i:0;N;i:1;s:3:"INF";}', $serialized); + echo "serialized: $serialized\n"; + $unserialized = unserialize($serialized); + self::assertEquals($delay, $unserialized); + } } From bda3cff3d35bc949220fa30c5bf6f9338236dfa4 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 16 Oct 2025 15:43:30 +0400 Subject: [PATCH 70/91] =?UTF-8?q?la=20signature=20de=20file::writer=20est?= =?UTF-8?q?=20coh=C3=A9rente=20avec=20les=20autres=20m=C3=A9thodes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/src/file.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/php/src/file.php b/php/src/file.php index 4c84f58..1045293 100644 --- a/php/src/file.php +++ b/php/src/file.php @@ -58,8 +58,8 @@ class file { return $file; } - static function writer($output, ?string $mode="w+b", ?callable $func=null): FileWriter { - $file = new FileWriter(self::fix_dash($output), $mode); + static function writer($output, ?callable $func=null): FileWriter { + $file = new FileWriter(self::fix_dash($output), "w+b"); if ($func !== null) { try { $func($file); From 0b01946090b02ca1aa0d63c1afd60755b0567ac8 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 17 Oct 2025 17:20:15 +0400 Subject: [PATCH 71/91] suite pwip --- bash/src/pman.sh | 90 ++++++++-- bash/src/pman.tool.pdev.sh | 10 -- bash/src/pman.tool.pdist.sh | 10 -- bash/src/pman.tool.pmain.sh | 10 -- wip/_pman.tool | 340 ++++++++++++++++++++++++++---------- wip/pwip | 61 +------ 6 files changed, 321 insertions(+), 200 deletions(-) delete mode 100644 bash/src/pman.tool.pdev.sh delete mode 100644 bash/src/pman.tool.pdist.sh delete mode 100644 bash/src/pman.tool.pmain.sh mode change 100755 => 120000 wip/pwip diff --git a/bash/src/pman.sh b/bash/src/pman.sh index 917969c..e9d7acd 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -28,6 +28,62 @@ CONFIG_VARS=( UPSTREAM DEVELOP FEATURE RELEASE MAIN TAG_PREFIX TAG_SUFFIX HOTFIX DIST NOAUTO ) +PMAN_TOOL_PUPS=UPSTREAM +PMAN_TOOL_PDEV=DEVELOP +PMAN_TOOL_PWIP=FEATURE +PMAN_TOOL_PMAIN=MAIN +PMAN_TOOL_PDIST=DIST +UPSTREAM_CREATE_BASE= ; UPSTREAM_MERGE_FROM= ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL= ; UPSTREAM_DELETE= +DEVELOP_CREATE_BASE=MAIN ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN ; DEVELOP_PREL=to ; DEVELOP_DELETE=from +MAIN_CREATE_BASE= ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=from ; MAIN_DELETE= +DIST_CREATE_BASE=MAIN ; DIST_MERGE_FROM=MAIN ; DIST_MERGE_TO= ; DIST_PREL= ; DIST_DELETE= +FEATURE_CREATE_BASE=DEVELOP ; FEATURE_MERGE_FROM= ; FEATURE_MERGE_TO=DEVELOP ; FEATURE_PREL= ; FEATURE_DELETE=to + +function get_create_base() { + # afficher la branche depuis laquelle créer la branche $1 + # retourner 1 en cas d'erreur (pas de branche source) + local branch="$1" infos + [ -n "$branch" ] || return 1 + infos="${branch^^}_CREATE_BASE"; branch="${!infos}" + [ -n "$branch" ] && echo "$branch" || return 1 +} + +function get_merge_from() { + # afficher la branche depuis laquelle la branche $1 doit merger + # retourner 1 en cas d'erreur (pas de branche source) + local branch="$1" infos + [ -n "$branch" ] || return 1 + infos="${branch^^}_MERGE_FROM"; branch="${!infos}" + [ -n "$branch" ] && echo "$branch" || return 1 +} + +function get_merge_to() { + # afficher la branche dans laquelle la branche $1 doit merger + # retourner 1 en cas d'erreur (pas de branche destination) + local branch="$1" infos + [ -n "$branch" ] || return 1 + infos="${branch^^}_MERGE_TO"; branch="${!infos}" + [ -n "$branch" ] && echo "$branch" || return 1 +} + +function should_prel_merge() { + # tester si la branche $1 doit être mergée avec prel dans la direction + # $2(=to) + local branch="$1" dir="${2:-to}" infos + [ -n "$branch" ] || return 1 + infos="${branch^^}_PREL" + [ "${!infos}" == "$dir" ] +} + +function should_delete_merged() { + # tester si la branche $1 doit être supprimée après avoir été mergée dans la + # direction $2(=to) + local branch="$1" dir="${2:-to}" infos + [ -n "$branch" ] || return 1 + infos="${branch^^}_DELETE" + [ "${!infos}" == "$dir" ] +} + function _init_changelog() { setx date=date +%d/%m/%Y-%H:%M ac_set_tmpfile changelog @@ -156,14 +212,14 @@ function check_gitdir() { # se mettre à la racine du dépôt git local gitdir - git_ensure_gitvcs + git_check_gitvcs || return 1 setx gitdir=git_get_toplevel cd "$gitdir" || return 1 } function ensure_gitdir() { # commencer dans le répertoire indiqué - check_gitdir "$@" || die || return + check_gitdir "$@" || die || return 1 } function load_branches() { @@ -201,6 +257,10 @@ function load_branches() { esac local branch + PmanBranch= + PmanCreateBase= + PmanMergeSrc= + PmanMergeDest= UpstreamBranch= FeatureBranches=() DevelopBranch= @@ -209,19 +269,27 @@ function load_branches() { MainBranch= DistBranch= for branch in "${LocalBranches[@]}"; do + [ -n "$PMAN_BRANCH" ] && [ "$branch" == "${!PMAN_BRANCH}" ] && + PmanBranch="$branch" + [ -n "$PMAN_CREATE_BASE" ] && [ "$branch" == "${!PMAN_CREATE_BASE}" ] && + PmanCreateBase="$branch" + [ -n "$PMAN_MERGE_SRC" ] && [ "$branch" == "${!PMAN_MERGE_SRC}" ] && + PmanMergeSrc="$branch" + [ -n "$PMAN_MERGE_DEST" ] && [ "$branch" == "${!PMAN_MERGE_DEST}" ] && + PmanMergeDest="$branch" if [ "$branch" == "$UPSTREAM" ]; then UpstreamBranch="$branch" - elif [[ "$branch" == "$FEATURE"* ]]; then + elif [ -n "$FEATURE" ] && [[ "$branch" == "$FEATURE"* ]]; then FeatureBranches+=("$branch") - elif [ "$branch" == "$DEVELOP" ]; then + elif [ -n "$DEVELOP" -a "$branch" == "$DEVELOP" ]; then DevelopBranch="$branch" - elif [[ "$branch" == "$RELEASE"* ]]; then + elif [ -n "$RELEASE" ] && [[ "$branch" == "$RELEASE"* ]]; then ReleaseBranch="$branch" - elif [[ "$branch" == "$HOTFIX"* ]]; then + elif [ -n "$HOTFIX" ] && [[ "$branch" == "$HOTFIX"* ]]; then HotfixBranch="$branch" - elif [ "$branch" == "$MAIN" ]; then + elif [ -n "$MAIN" -a "$branch" == "$MAIN" ]; then MainBranch="$branch" - elif [ "$branch" == "$DIST" ]; then + elif [ -n "$DIST" -a "$branch" == "$DIST" ]; then DistBranch="$branch" fi done @@ -249,12 +317,6 @@ function load_config() { elif [ -f .pman.conf ]; then ConfigFile="$(pwd)/.pman.conf" source "$ConfigFile" - elif [ -n "$1" -a -n "${MYNAME#$1}" ]; then - # $1 est le nom de base de l'outil e.g "pdev", et le suffixe est la - # configuration à charger par défaut. i.e pdev74 chargera par défaut la - # configuration pman74.conf - ConfigFile="$NULIBDIR/bash/src/pman${MYNAME#$1}.conf.sh" - source "$ConfigFile" else ConfigFile="$NULIBDIR/bash/src/pman.conf.sh" fi diff --git a/bash/src/pman.tool.pdev.sh b/bash/src/pman.tool.pdev.sh deleted file mode 100644 index a913374..0000000 --- a/bash/src/pman.tool.pdev.sh +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 - -PMAN_TOOLS=pdev -SRC_TYPE=DEVELOP -SRC_BRANCH="${SRC_TYPE,,}"; SRC_BRANCH="${SRC_BRANCH^}Branch" -DEST_TYPE=MAIN -DEST_BRANCH="${DEST_TYPE,,}"; DEST_BRANCH="${DEST_BRANCH^}Branch" -ALLOW_MERGE=1 -MERGE_PREL=1 -ALLOW_DELETE= diff --git a/bash/src/pman.tool.pdist.sh b/bash/src/pman.tool.pdist.sh deleted file mode 100644 index 433e3bc..0000000 --- a/bash/src/pman.tool.pdist.sh +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 - -PMAN_TOOL=pdist -SRC_TYPE=DIST -SRC_BRANCH="${SRC_TYPE,,}"; SRC_BRANCH="${SRC_BRANCH^}Branch" -DEST_TYPE= -DEST_BRANCH= -ALLOW_MERGE= -MERGE_PREL= -ALLOW_DELETE= diff --git a/bash/src/pman.tool.pmain.sh b/bash/src/pman.tool.pmain.sh deleted file mode 100644 index 3e6a4a0..0000000 --- a/bash/src/pman.tool.pmain.sh +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 - -PMAN_TOOL=pmain -SRC_TYPE=MAIN -SRC_BRANCH="${SRC_TYPE,,}"; SRC_BRANCH="${SRC_BRANCH^}Branch" -DEST_TYPE=DIST -DEST_BRANCH="${DEST_TYPE,,}"; DEST_BRANCH="${DEST_BRANCH^}Branch" -ALLOW_MERGE=1 -MERGE_PREL= -ALLOW_DELETE= diff --git a/wip/_pman.tool b/wip/_pman.tool index 3781dd6..a2d3047 100755 --- a/wip/_pman.tool +++ b/wip/_pman.tool @@ -1,29 +1,92 @@ #!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 source "$(dirname -- "$0")/../load.sh" || exit 1 -require: git pman pman.conf "pman.tool.$MYNAME" +require: git pman pman.conf + +### description des variables ### +# * PMAN_TOOL -- nom de l'outil, e.g pdev, pmain, pdist +# * PMAN_BRANCH -- code de la branche de référence basé sur le nom de l'outil +# * PMAN_UNIQUE -- si cette branche est unique +# * PMAN_CREATE_BASE -- branche de base à partir de laquelle créer la branche +# * PMAN_MERGE_FROM -- code de la branche source à partir de laquelle la fusion +# est faite dans PMAN_BRANCH. vide si la branche n'a pas de source +# * PMAN_MERGE_TO -- code de la branche destination dans laquelle la fusion est +# faite depuis PMAN_BRANCH. vide si la branche n'a pas de destination +# * PMAN_DIR -- direction de la fusion: +# "from" si on fait PMAN_MERGE_FROM --> PMAN_BRANCH +# "to" si on fait PMAN_BRANCH --> PMAN_MERGE_TO +# * PMAN_PREL_MERGE -- si la fusion devrait se faire avec prel +# * PMAN_DELETE_MERGED -- s'il faut supprimer la branche source après la fusion +# * PMAN_MERGE_SRC -- code de la branche source pour la fusion, ou vide si la +# fusion n'est pas possible +# * PMAN_MERGE_DEST -- code de la branche destination pour la fusion, ou vide si +# la fusion n'est pas possible +# * PMAN_CAN_MERGE -- indique si la fusion est possible +# * ${!PMAN_BRANCH} -- nom effectif de la branche si elle est définie dans +# .pman.conf +# * $PmanBranch -- nom effectif de la branche *si elle existe*, vide sinon +# * ${!PMAN_MERGE_SRC} -- nom effectif de la branche source si elle est définie +# dans .pman.conf +# * $PmanMergeSrc -- nom effectif de la branche source *si elle existe*, vide +# sinon +# * ${!PMAN_MERGE_DEST} -- nom effectif de la branche destination si elle est +# définie dans .pman.conf +# * $PmanMergeDest -- nom effectif de la branche source *si elle existe*, vide +# sinon + +[ -n "$PMAN_TOOL" ] || PMAN_TOOL="$MYNAME" +PMAN_BRANCH="PMAN_TOOL_${PMAN_TOOL^^}"; PMAN_BRANCH="${!PMAN_BRANCH}" +function set_pman_vars() { + case "$PMAN_BRANCH" in + FEATURE|RELEASE|HOTFIX) PMAN_UNIQUE=;; + *) PMAN_UNIQUE=1;; + esac + PMAN_CREATE_BASE=$(get_create_base "$PMAN_BRANCH") + PMAN_MERGE_FROM=$(get_merge_from "$PMAN_BRANCH") + PMAN_MERGE_TO=$(get_merge_to "$PMAN_BRANCH") + if [ -n "$1" ]; then PMAN_DIR="$1" + else PMAN_DIR=to + #elif [ -n "$PMAN_MERGE_TO" ]; then PMAN_DIR=to + #else PMAN_DIR=from + fi + PMAN_PREL_MERGE=$(should_prel_merge "$PMAN_BRANCH" "$PMAN_DIR" && echo 1) + PMAN_DELETE_MERGED=$(should_delete_merged "$PMAN_BRANCH" "$PMAN_DIR" && echo 1) + case "$PMAN_DIR" in + to) + PMAN_MERGE_SRC="$PMAN_BRANCH" + PMAN_MERGE_DEST="$PMAN_MERGE_TO" + ;; + from) + PMAN_MERGE_SRC="$PMAN_MERGE_FROM" + PMAN_MERGE_DEST="$PMAN_BRANCH" + ;; + esac + [ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ] && PMAN_CAN_MERGE=1 || PMAN_CAN_MERGE= +} git_cleancheckout_DIRTY="\ Vous avez des modifications locales. Enregistrez ces modifications avant de fusionner la branche" function dump_action() { - echo -n "\ -SRC_TYPE=$SRC_TYPE -SRC_BRANCH=$SRC_BRANCH -DEST_TYPE=$DEST_TYPE -DEST_BRANCH=$DEST_BRANCH + enote "Valeurs des variables: +PMAN_TOOL=$PMAN_TOOL +PMAN_BRANCH=$PMAN_BRANCH${PMAN_BRANCH:+ !PMAN_BRANCH=${!PMAN_BRANCH} PmanBranch=$PmanBranch} +PMAN_CREATE_BASE=$PMAN_CREATE_BASE${PMAN_CREATE_BASE:+ !PMAN_CREATE_BASE=${!PMAN_CREATE_BASE} PmanCreateBase=$PmanCreateBase} +PMAN_MERGE_FROM=$PMAN_MERGE_FROM +PMAN_MERGE_TO=$PMAN_MERGE_TO +PMAN_DIR=$PMAN_DIR +PMAN_PREL_MERGE=$PMAN_PREL_MERGE +PMAN_DELETE_MERGED=$PMAN_DELETE_MERGED +PMAN_MERGE_SRC=$PMAN_MERGE_SRC${PMAN_MERGE_SRC:+ !PMAN_MERGE_SRC=${!PMAN_MERGE_SRC} PmanMergeSrc=$PmanMergeSrc} +PMAN_MERGE_DEST=$PMAN_MERGE_DEST${PMAN_MERGE_DEST:+ !PMAN_MERGE_DEST=${!PMAN_MERGE_DEST} PmanMergeDest=$PmanMergeDest} +PMAN_CAN_MERGE=$PMAN_CAN_MERGE CurrentBranch=$CurrentBranch LocalBranches=${LocalBranches[*]} RemoteBranches=${RemoteBranches[*]} AllBranches=${AllBranches[*]} -SrcType=$SrcType -SrcBranch=$SrcBranch -DestType=$DestType -DestBranch=$DestBranch - UpstreamBranch=$UpstreamBranch FeatureBranches=${FeatureBranches[*]} DevelopBranch=$DevelopBranch @@ -34,46 +97,76 @@ DistBranch=$DistBranch " } -function _ensure_src_branch() { - [ -n "$SrcBranch" ] || die "La branche $SRC_TYPE n'a pas été définie" - [ "$1" == init -o -n "${!SRC_BRANCH}" ] || die "$SrcBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +#XXXX +#[ -n "${!PMAN_MERGE_SRC}" ] || +# die "Aucune branche définie pour $PMAN_MERGE_SRC. Veuillez éditer le fichier .pman.conf" + +function _ensure_branch() { + [ -n "${!PMAN_BRANCH}" ] || die "\ +La branche $PMAN_BRANCH n'a pas été définie. +Veuillez éditer le fichier .pman.conf" + [ "$1" == init -o -n "$PmanBranch" ] || die "${!PMAN_BRANCH}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" } -function _ensure_dest_branch() { - [ -n "$DestBranch" ] || die "La branche $DEST_TYPE n'a pas été définie" - [ "$1" == init -o -n "${!DEST_BRANCH}" ] || die "$DestBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +function _ensure_create_base() { + [ -n "${!PMAN_CREATE_BASE}" ] || die "\ +La branche $PMAN_CREATE_BASE n'a pas été définie. +Veuillez éditer le fichier .pman.conf" + [ "$1" == init -o -n "$PmanCreateBase" ] || die "${!PMAN_CREATE_BASE}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +} + +function _ensure_merge_src() { + [ -n "${!PMAN_MERGE_SRC}" ] || die "\ +La branche $PMAN_MERGE_SRC n'a pas été définie. +Veuillez éditer le fichier .pman.conf" + [ "$1" == init -o -n "$PmanMergeSrc" ] || die "${!PMAN_MERGE_SRC}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +} + +function _ensure_merge_dest() { + [ -n "${!PMAN_MERGE_DEST}" ] || die "\ +La branche $PMAN_MERGE_DEST n'a pas été définie. +Veuillez éditer le fichier .pman.conf" + [ "$1" == init -o -n "$PmanMergeDest" ] || die "${!PMAN_MERGE_DEST}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" } function checkout_action() { local -a push_branches - if [ -z "${!SRC_BRANCH}" ]; then - array_contains AllBranches "$SrcBranch" && exit_with enote "\ -$SrcBranch: une branche du même nom existe dans l'origine - git checkout $SrcBranch" - _ensure_dest_branch - _ensure_src_branch init + _ensure_branch init + if [ -n "$PmanBranch" ]; then + git checkout "$PmanBranch" + elif array_contains AllBranches "${!PMAN_BRANCH}"; then + enote "${!PMAN_BRANCH}: une branche du même nom existe dans l'origine" + ask_yesno "Voulez-vous basculer sur cette branche?" O || die + git checkout "${!PMAN_BRANCH}" + elif [ -n "$PMAN_CREATE_BASE" ]; then + _ensure_create_base resolve_should_push - enote "Vous allez créer la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}" + local SrcBranch="${!PMAN_CREATE_BASE}" DestBranch="${!PMAN_BRANCH}" + enote "Vous allez créer la branche ${COULEUR_BLEUE}$DestBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$SrcBranch${COULEUR_NORMALE}" ask_yesno "Voulez-vous continuer?" O || die - einfo "Création de la branche $SrcBranch" - git checkout -b "$SrcBranch" "$DestBranch" || die - push_branches+=("$SrcBranch") + einfo "Création de la branche $DestBranch" + git checkout -b "$DestBranch" "$SrcBranch" || die + push_branches+=("$DestBranch") _push_branches fi - git checkout "$SrcBranch" } function ensure_branches() { - [ -n "${!SRC_BRANCH}" -a -n "${!DEST_BRANCH}" ] || - die "${!SRC_BRANCH}: Aucune configuration de fusion trouvée pour cette branche" + [ -n "$PMAN_CAN_MERGE" ] || + die "${!PMAN_BRANCH}: Aucune configuration de fusion trouvée pour cette branche" - array_contains LocalBranches "${!SRC_BRANCH}" || die "${!SRC_BRANCH}: branche source introuvable" - array_contains LocalBranches "${!DEST_BRANCH}" || die "${!DEST_BRANCH}: branche destination introuvable" + local branches + [ "$1" == -a ] && branches=AllBranches || branches=LocalBranches + + SrcBranch="${!PMAN_MERGE_SRC}" + array_contains "$branches" "$SrcBranch" || die "$SrcBranch: branche source introuvable" + DestBranch="${!PMAN_MERGE_DEST}" + array_contains "$branches" "$DestBranch" || die "$DestBranch: branche destination introuvable" } function _show_action() { @@ -83,14 +176,14 @@ function _show_action() { if [ $ShowLevel -ge 2 ]; then { echo "\ -# Commits à fusionner ${!SRC_BRANCH} --> ${!DEST_BRANCH} +# Commits à fusionner $SrcBranch --> $DestBranch $commits " _sd_COLOR=always _show_diff } | less -eRF else - einfo "Commits à fusionner ${!SRC_BRANCH} --> ${!DEST_BRANCH}" + einfo "Commits à fusionner $SrcBranch --> $DestBranch" eecho "$commits" fi fi @@ -104,7 +197,7 @@ function show_action() { function _merge_action() { enote "\ Ce script va -- fusionner la branche ${COULEUR_BLEUE}${!SRC_BRANCH}${COULEUR_NORMALE} dans ${COULEUR_ROUGE}${!DEST_BRANCH}${COULEUR_NORMALE}${Push:+ +- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+ - pousser les branches modifiées}" ask_yesno "Voulez-vous continuer?" O || die @@ -121,13 +214,13 @@ Ce script va if [ -n "\$merge" ]; then esection "Fusionner la branche" EOF - hook="BEFORE_MERGE_$SRC_TYPE"; [ -n "${!hook}" ] && _scripta <" - -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" - -O:,--origin Origin= "++\ -origine à partir de laquelle les branches distantes sont considérées" - -B:,--config-branch ConfigBranch= "++\ -branche à partir de laquelle charger la configuration" - -c:,--config-file:CONFIG ConfigFile= "++\ -fichier de configuration des branches. cette option est prioritaire sur --config-branch -par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" - -n,--no-push Push= "\ -ne pas pousser les branches vers leur origine après la fusion" - --push Push=1 "++\ -pousser les branches vers leur origine après la fusion. -c'est l'option par défaut" -) -parse_args "$@"; set -- "${args[@]}" - -# charger la configuration -ensure_gitdir "$chdir" -load_branches all -load_config "$MYNAME" -load_branches current - -branch="$1" -if [ -z "$branch" -a ${#FeatureBranches[*]} -eq 1 ]; then - branch="${FeatureBranches[0]}" -fi -[ -n "$branch" ] || die "Vous devez spécifier la branche à créer" -branch="$FEATURE${branch#$FEATURE}" - -resolve_should_push -git_ensure_cleancheckout - -if array_contains AllBranches "$branch"; then - git checkout -q "$branch" -else - # si la branche source n'existe pas, la créer - args=(--origin "$Origin") - if [ -n "$ConfigFile" ]; then args+=(--config-file "$ConfigFile") - elif [ -n "$ConfigBranch" ]; then args+=(--config-branch "$ConfigBranch") - fi - [ -z "$Push" ] && args+=(--no-push) - exec "$MYDIR/pman" "${args[@]}" "$branch" -fi diff --git a/wip/pwip b/wip/pwip new file mode 120000 index 0000000..065a97c --- /dev/null +++ b/wip/pwip @@ -0,0 +1 @@ +_pman.tool \ No newline at end of file From e4e6a98be2b74c538fb81d73ef49204536144d25 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 17 Oct 2025 20:21:43 +0400 Subject: [PATCH 72/91] suite support feature branches --- wip/_pman.tool | 55 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/wip/_pman.tool b/wip/_pman.tool index a2d3047..2cba665 100755 --- a/wip/_pman.tool +++ b/wip/_pman.tool @@ -3,6 +3,10 @@ source "$(dirname -- "$0")/../load.sh" || exit 1 require: git pman pman.conf +git_cleancheckout_DIRTY="\ +Vous avez des modifications locales. +Enregistrez ces modifications avant de fusionner la branche" + ### description des variables ### # * PMAN_TOOL -- nom de l'outil, e.g pdev, pmain, pdist # * PMAN_BRANCH -- code de la branche de référence basé sur le nom de l'outil @@ -64,9 +68,29 @@ function set_pman_vars() { [ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ] && PMAN_CAN_MERGE=1 || PMAN_CAN_MERGE= } -git_cleancheckout_DIRTY="\ -Vous avez des modifications locales. -Enregistrez ces modifications avant de fusionner la branche" +function resolve_unique_branch() { + if [ "$PMAN_BRANCH" == FEATURE ]; then + if [ $# -gt 0 ]; then + UniqueBranch="$FEATURE${1#$FEATURE}" + elif [[ "$CurrentBranch" == "$FEATURE"* ]]; then + UniqueBranch="$CurrentBranch" + elif [ ${#FeatureBranches[*]} -eq 0 ]; then + die "Vous devez spécifier la branche de feature" + elif [ ${#FeatureBranches[*]} -eq 1 ]; then + UniqueBranch="${FeatureBranches[0]}" + else + simple_menu \ + UniqueBranch FeatureBranches \ + -t "Branches de feature" \ + -m "Veuillez choisir la branche de feature" \ + -d "${FeatureBranches[0]}" + fi + PMAN_BRANCH=UniqueBranch + PMAN_MERGE_SRC=UniqueBranch + else + die "resolve_unique_branch: $PMAN_BRANCH: non implémenté" + fi +} function dump_action() { enote "Valeurs des variables: @@ -97,10 +121,6 @@ DistBranch=$DistBranch " } -#XXXX -#[ -n "${!PMAN_MERGE_SRC}" ] || -# die "Aucune branche définie pour $PMAN_MERGE_SRC. Veuillez éditer le fichier .pman.conf" - function _ensure_branch() { [ -n "${!PMAN_BRANCH}" ] || die "\ La branche $PMAN_BRANCH n'a pas été définie. @@ -132,9 +152,13 @@ Veuillez éditer le fichier .pman.conf" function checkout_action() { local -a push_branches + [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" || die _ensure_branch init + if [ -n "$PmanBranch" ]; then git checkout "$PmanBranch" + elif array_contains LocalBranches "${!PMAN_BRANCH}"; then + git checkout "${!PMAN_BRANCH}" elif array_contains AllBranches "${!PMAN_BRANCH}"; then enote "${!PMAN_BRANCH}: une branche du même nom existe dans l'origine" ask_yesno "Voulez-vous basculer sur cette branche?" O || die @@ -156,7 +180,10 @@ function checkout_action() { fi } -function ensure_branches() { +#XXXX +#[ -n "${!PMAN_MERGE_SRC}" ] || +# die "Aucune branche définie pour $PMAN_MERGE_SRC. Veuillez éditer le fichier .pman.conf" +function ensure_merge_branches() { [ -n "$PMAN_CAN_MERGE" ] || die "${!PMAN_BRANCH}: Aucune configuration de fusion trouvée pour cette branche" @@ -188,9 +215,10 @@ $commits fi fi } + function show_action() { git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" - ensure_branches + ensure_merge_branches _show_action "$@" } @@ -305,7 +333,8 @@ Il y a aussi les commandes supplémentaires suivantes: } function merge_action() { - ensure_branches -a + [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" || die + ensure_merge_branches -a if [ -n "$PMAN_PREL_MERGE" ]; then [ -n "$ForceMerge" ] || die "$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel" @@ -324,6 +353,7 @@ function merge_action() { array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche locale introuvable" resolve_should_push + set -x #XXX _merge_action "$@" } @@ -335,11 +365,11 @@ function rebase_action() { # Programme principal ################################################################################ -set_pman_vars loaded_config= if check_gitdir; then load_branches all load_config + set_pman_vars if [ -n "${!PMAN_MERGE_SRC}" ]; then load_branches current "${!PMAN_MERGE_SRC}" loaded_config=1 @@ -347,6 +377,8 @@ if check_gitdir; then load_branches current "${!PMAN_BRANCH}" loaded_config=1 fi +else + set_pman_vars fi BranchDesc= @@ -509,6 +541,7 @@ if [ -z "$loaded_config" -o -n "$chdir" -o -n "$ConfigFile" -o -n "$ConfigBranch ensure_gitdir "$chdir" load_branches all load_config + set_pman_vars if [ -n "${!PMAN_MERGE_SRC}" ]; then load_branches current "${!PMAN_MERGE_SRC}" elif [ -n "${!PMAN_BRANCH}" ]; then From 39abe09f117aeb8bc7afea64473b1e94e7f45a93 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 19 Oct 2025 08:42:44 +0400 Subject: [PATCH 73/91] modifs.mineures sans commentaires --- wip/pinit | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100755 wip/pinit diff --git a/wip/pinit b/wip/pinit new file mode 100755 index 0000000..ce6361a --- /dev/null +++ b/wip/pinit @@ -0,0 +1,191 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname -- "$0")/../load.sh" || exit 1 +require: git pman pman.conf + +################################################################################ +# Informations +################################################################################ + +SHOW_VARS=( + --Configuration + "${CONFIG_VARS[@]}" + --Paramètres + CurrentBranch + CurrentType=SrcType +) + +function show_action() { + local var src + echo_setv ConfigBranch="$ConfigBranch" + echo_setv ConfigFile="$(ppath "$ConfigFile")" + for var in "${SHOW_VARS[@]}"; do + if [ "${var#--}" != "$var" ]; then + estep "${var#--}" + else + splitfsep "$var" = var src + [ -n "$src" ] || src="$var" + echo_setv "$var=${!src}" + fi + done +} + +################################################################################ +# Initialisation +################################################################################ + +function _init_config() { + if [ ! -f .pman.conf -o -n "$ForceCreate" ]; then + ac_set_tmpfile config + cp "$ConfigFile" "$config" + "${EDITOR:-nano}" "$config" + [ -s "$config" ] || return 1 + + cp "$config" .pman.conf + if testdiff .pman.conf "$ConfigFile"; then + ConfigFile="$(pwd)/.pman.conf" + load_config + load_branches current "$SrcBranch" + fi + git add .pman.conf + fi + if [ ! -f ".gitignore" ]; then + echo >.gitignore "\ +.~lock*# +.*.swp" + git add .gitignore + fi + return 0 +} + +function init_repo_action() { + local -a push_branches; local config + + [ ${#LocalBranches[*]} -eq 0 ] || die "Ce dépôt a déjà été initialisé" + + _init_config || exit_with ewarn "Initialisation du dépôt annulée" + + einfo "Création de la branche $MAIN" + git symbolic-ref HEAD "refs/heads/$MAIN" + git commit -m "commit initial" + push_branches+=("$MAIN") + + einfo "Création de la branche $DEVELOP" + git checkout -b "$DEVELOP" + push_branches+=("$DEVELOP") + + _push_branches +} + +function init_config_action() { + local -a push_branches; local config + + [ -f .pman.conf -a -z "$ForceCreate" ] && die "La configuration pman a déjà été initialisée" + + resolve_should_push + + _init_config || exit_with ewarn "Initialisation de la configuration annulée" + git commit -m "configuration pman" + push_branches+=("$CurrentBranch") + + _push_branches +} + +function _init_composer() { + if [ ! -f .composer.pman.yml -o -n "$ForceCreate" ]; then + ac_set_tmpfile config + cat >"$config" < Date: Sun, 19 Oct 2025 20:34:58 +0400 Subject: [PATCH 74/91] modifs.mineures sans commentaires --- bash/src/pman.sh | 129 +++++++++++--- bash/src/pman.tool.sh | 299 +++++++++++++++++++++++++++++++ wip/_pman.tool | 396 ++---------------------------------------- wip/pups | 1 + 4 files changed, 425 insertions(+), 400 deletions(-) create mode 100644 bash/src/pman.tool.sh create mode 120000 wip/pups diff --git a/bash/src/pman.sh b/bash/src/pman.sh index e9d7acd..6913994 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -28,27 +28,29 @@ CONFIG_VARS=( UPSTREAM DEVELOP FEATURE RELEASE MAIN TAG_PREFIX TAG_SUFFIX HOTFIX DIST NOAUTO ) +################################################################################ + PMAN_TOOL_PUPS=UPSTREAM PMAN_TOOL_PDEV=DEVELOP PMAN_TOOL_PWIP=FEATURE PMAN_TOOL_PMAIN=MAIN PMAN_TOOL_PDIST=DIST -UPSTREAM_CREATE_BASE= ; UPSTREAM_MERGE_FROM= ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL= ; UPSTREAM_DELETE= -DEVELOP_CREATE_BASE=MAIN ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN ; DEVELOP_PREL=to ; DEVELOP_DELETE=from -MAIN_CREATE_BASE= ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=from ; MAIN_DELETE= -DIST_CREATE_BASE=MAIN ; DIST_MERGE_FROM=MAIN ; DIST_MERGE_TO= ; DIST_PREL= ; DIST_DELETE= -FEATURE_CREATE_BASE=DEVELOP ; FEATURE_MERGE_FROM= ; FEATURE_MERGE_TO=DEVELOP ; FEATURE_PREL= ; FEATURE_DELETE=to +UPSTREAM_BASE= ; UPSTREAM_MERGE_FROM= ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL= ; UPSTREAM_DELETE= +DEVELOP_BASE=MAIN ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN ; DEVELOP_PREL=to ; DEVELOP_DELETE=from +MAIN_BASE= ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=from ; MAIN_DELETE= +DIST_BASE=MAIN ; DIST_MERGE_FROM=MAIN ; DIST_MERGE_TO= ; DIST_PREL= ; DIST_DELETE= +FEATURE_BASE=DEVELOP ; FEATURE_MERGE_FROM= ; FEATURE_MERGE_TO=DEVELOP ; FEATURE_PREL= ; FEATURE_DELETE=to -function get_create_base() { +function get_base_branch() { # afficher la branche depuis laquelle créer la branche $1 # retourner 1 en cas d'erreur (pas de branche source) local branch="$1" infos [ -n "$branch" ] || return 1 - infos="${branch^^}_CREATE_BASE"; branch="${!infos}" + infos="${branch^^}_BASE"; branch="${!infos}" [ -n "$branch" ] && echo "$branch" || return 1 } -function get_merge_from() { +function get_merge_from_branch() { # afficher la branche depuis laquelle la branche $1 doit merger # retourner 1 en cas d'erreur (pas de branche source) local branch="$1" infos @@ -57,7 +59,7 @@ function get_merge_from() { [ -n "$branch" ] && echo "$branch" || return 1 } -function get_merge_to() { +function get_merge_to_branch() { # afficher la branche dans laquelle la branche $1 doit merger # retourner 1 en cas d'erreur (pas de branche destination) local branch="$1" infos @@ -84,6 +86,94 @@ function should_delete_merged() { [ "${!infos}" == "$dir" ] } +: " +# description des variables # + +* PMAN_TOOL -- nom de l'outil, e.g pdev, pmain, pdist + +* PMAN_REF_BRANCH -- code de la branche de référence basé sur le nom de l'outil +* PmanRefBranch -- nom effectif de la branche si elle est définie dans + .pman.conf, vide sinon +* IfRefBranch -- nom effectif de la branche *si elle existe*, vide sinon + +* PMAN_UNIQUE -- si la branche de référence est unique. est vide pour les + codes de branches multiples, telle que FEATURE + +* PMAN_BASE_BRANCH -- branche de base à partir de laquelle créer la branche + de référence +* PmanBaseBranch -- nom effectif de la branche de base si elle est définie + dans .pman.conf, vide sinon +* IfBaseBranch -- nom effectif de la branche de base *si elle existe*, vide + sinon + +* PMAN_MERGE_FROM -- code de la branche source à partir de laquelle la fusion + est faite dans PMAN_REF_BRANCH. vide si la branche n'a pas de source +* PMAN_MERGE_TO -- code de la branche destination dans laquelle la fusion est + faite depuis PMAN_REF_BRANCH. vide si la branche n'a pas de destination +* PMAN_DIR -- direction de la fusion: + 'from' si on fait PMAN_MERGE_FROM --> PMAN_REF_BRANCH + 'to' si on fait PMAN_REF_BRANCH --> PMAN_MERGE_TO +* PMAN_PREL_MERGE -- si la fusion devrait se faire avec prel +* PMAN_DELETE_MERGED -- s'il faut supprimer la branche source après la fusion + +* PMAN_MERGE_SRC -- code de la branche source pour la fusion, ou vide si la + fusion n'est pas possible +* PmanMergeSrc -- nom effectif de la branche source si elle est définie + dans .pman.conf +* IfMergeSrc -- nom effectif de la branche source *si elle existe*, vide + sinon + +* PMAN_MERGE_DEST -- code de la branche destination pour la fusion, ou vide si + la fusion n'est pas possible +* PmanMergeDest -- nom effectif de la branche destination si elle est + définie dans .pman.conf +* IfMergeDest -- nom effectif de la branche source *si elle existe*, vide + sinon + +* PMAN_CAN_MERGE -- indique si la fusion est théoriquement possible, c'est à + dire que $PMAN_MERGE_SRC et $PMAN_MERGE_DEST sont tous les deux non vides +* IfCanMerge -- indique si la fusion est effectivement possible, c'est à dire + que $IfMergeSrc et $IfMergeDest sont tous les deux non vides" + +[ -n "$PMAN_TOOL" ] || PMAN_TOOL="$MYNAME" +PMAN_REF_BRANCH="PMAN_TOOL_${PMAN_TOOL^^}"; PMAN_REF_BRANCH="${!PMAN_REF_BRANCH}" +function set_pman_vars() { + PmanRefBranch="${!PMAN_REF_BRANCH}" + case "$PMAN_REF_BRANCH" in + FEATURE|RELEASE|HOTFIX) PMAN_UNIQUE=;; + *) PMAN_UNIQUE=1;; + esac + + PMAN_BASE_BRANCH=$(get_base_branch "$PMAN_REF_BRANCH") + [ -n "$PMAN_BASE_BRANCH" ] && PmanBaseBranch="${!PMAN_BASE_BRANCH}" || PmanBaseBranch= + + PMAN_MERGE_FROM=$(get_merge_from_branch "$PMAN_REF_BRANCH") + PMAN_MERGE_TO=$(get_merge_to_branch "$PMAN_REF_BRANCH") + if [ -n "$1" ]; then PMAN_DIR="$1" + else PMAN_DIR=to + #elif [ -n "$PMAN_MERGE_TO" ]; then PMAN_DIR=to + #else PMAN_DIR=from + fi + PMAN_PREL_MERGE=$(should_prel_merge "$PMAN_REF_BRANCH" "$PMAN_DIR" && echo 1) + PMAN_DELETE_MERGED=$(should_delete_merged "$PMAN_REF_BRANCH" "$PMAN_DIR" && echo 1) + case "$PMAN_DIR" in + to) + PMAN_MERGE_SRC="$PMAN_REF_BRANCH" + PMAN_MERGE_DEST="$PMAN_MERGE_TO" + ;; + from) + PMAN_MERGE_SRC="$PMAN_MERGE_FROM" + PMAN_MERGE_DEST="$PMAN_REF_BRANCH" + ;; + esac + + [ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ] && PMAN_CAN_MERGE=1 || PMAN_CAN_MERGE= + [ -n "$PMAN_MERGE_SRC" ] && PmanMergeSrc="${!PMAN_MERGE_SRC}" || PmanMergeSrc= + [ -n "$PMAN_MERGE_DEST" ] && PmanMergeDest="${!PMAN_MERGE_DEST}" || PmanMergeDest= +} + +################################################################################ + function _init_changelog() { setx date=date +%d/%m/%Y-%H:%M ac_set_tmpfile changelog @@ -257,10 +347,6 @@ function load_branches() { esac local branch - PmanBranch= - PmanCreateBase= - PmanMergeSrc= - PmanMergeDest= UpstreamBranch= FeatureBranches=() DevelopBranch= @@ -268,15 +354,11 @@ function load_branches() { HotfixBranch= MainBranch= DistBranch= + IfRefBranch= + IfBaseBranch= + IfMergeSrc= + IfMergeDest= for branch in "${LocalBranches[@]}"; do - [ -n "$PMAN_BRANCH" ] && [ "$branch" == "${!PMAN_BRANCH}" ] && - PmanBranch="$branch" - [ -n "$PMAN_CREATE_BASE" ] && [ "$branch" == "${!PMAN_CREATE_BASE}" ] && - PmanCreateBase="$branch" - [ -n "$PMAN_MERGE_SRC" ] && [ "$branch" == "${!PMAN_MERGE_SRC}" ] && - PmanMergeSrc="$branch" - [ -n "$PMAN_MERGE_DEST" ] && [ "$branch" == "${!PMAN_MERGE_DEST}" ] && - PmanMergeDest="$branch" if [ "$branch" == "$UPSTREAM" ]; then UpstreamBranch="$branch" elif [ -n "$FEATURE" ] && [[ "$branch" == "$FEATURE"* ]]; then @@ -292,7 +374,12 @@ function load_branches() { elif [ -n "$DIST" -a "$branch" == "$DIST" ]; then DistBranch="$branch" fi + [ -n "$PmanRefBranch" -a "$branch" == "$PmanRefBranch" ] && IfRefBranch="$branch" + [ -n "$PmanBaseBranch" -a "$branch" == "$PmanBaseBranch" ] && IfBaseBranch="$branch" + [ -n "$PmanMergeSrc" -a "$branch" == "$PmanMergeSrc" ] && IfMergeSrc="$branch" + [ -n "$PmanMergeDest" -a "$branch" == "$PmanMergeDest" ] && IfMergeDest="$branch" done + [ -n "$IfMergeSrc" -a "$IfMergeDest" ] && IfCanMerge=1 || IfCanMerge= ;; esac } diff --git a/bash/src/pman.tool.sh b/bash/src/pman.tool.sh new file mode 100644 index 0000000..82ce248 --- /dev/null +++ b/bash/src/pman.tool.sh @@ -0,0 +1,299 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +git_cleancheckout_DIRTY="\ +Vous avez des modifications locales. +Enregistrez ces modifications avant de fusionner la branche" + +function resolve_unique_branch() { + if [ "$PMAN_REF_BRANCH" == FEATURE ]; then + if [ $# -gt 0 ]; then + UniqueBranch="$FEATURE${1#$FEATURE}" + elif [[ "$CurrentBranch" == "$FEATURE"* ]]; then + UniqueBranch="$CurrentBranch" + elif [ ${#FeatureBranches[*]} -eq 0 ]; then + die "Vous devez spécifier la branche de feature" + elif [ ${#FeatureBranches[*]} -eq 1 ]; then + UniqueBranch="${FeatureBranches[0]}" + else + simple_menu \ + UniqueBranch FeatureBranches \ + -t "Branches de feature" \ + -m "Veuillez choisir la branche de feature" \ + -d "${FeatureBranches[0]}" + fi + PMAN_REF_BRANCH=UniqueBranch + PMAN_MERGE_SRC=UniqueBranch + else + die "resolve_unique_branch: $PMAN_REF_BRANCH: non implémenté" + fi +} + +function dump_action() { + enote "Valeurs des variables: +PMAN_TOOL=$PMAN_TOOL +PMAN_REF_BRANCH=$PMAN_REF_BRANCH${PmanRefBranch:+ PmanRefBranch=$PmanRefBranch IfRefBranch=$IfRefBranch} +PMAN_BASE_BRANCH=$PMAN_BASE_BRANCH${PmanBaseBranch:+ PmanBaseBranch=$PmanBaseBranch IfCreateBase=$IfCreateBase} +PMAN_MERGE_FROM=$PMAN_MERGE_FROM +PMAN_MERGE_TO=$PMAN_MERGE_TO +PMAN_DIR=$PMAN_DIR +PMAN_PREL_MERGE=$PMAN_PREL_MERGE +PMAN_DELETE_MERGED=$PMAN_DELETE_MERGED +PMAN_MERGE_SRC=$PMAN_MERGE_SRC${PmanMergeSrc:+ PmanMergeSrc=$PmanMergeSrc IfMergeSrc=$IfMergeSrc} +PMAN_MERGE_DEST=$PMAN_MERGE_DEST${PmanMergeDest:+ PmanMergeDest=$PmanMergeDest IfMergeDest=$IfMergeDest} +PMAN_CAN_MERGE=$PMAN_CAN_MERGE IfCanMerge=$IfCanMerge + +CurrentBranch=$CurrentBranch +LocalBranches=${LocalBranches[*]} +RemoteBranches=${RemoteBranches[*]} +AllBranches=${AllBranches[*]} + +UpstreamBranch=$UpstreamBranch +FeatureBranches=${FeatureBranches[*]} +DevelopBranch=$DevelopBranch +ReleaseBranch=$ReleaseBranch +HotfixBranch=$HotfixBranch +MainBranch=$MainBranch +DistBranch=$DistBranch +" +} + +function _ensure_branch() { + [ -n "$PmanRefBranch" ] || die "\ +La branche $PMAN_REF_BRANCH n'a pas été définie. +Veuillez éditer le fichier .pman.conf" + [ "$1" == init -o -n "$IfRefBranch" ] || die "$PmanRefBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +} + +function _ensure_base_branch() { + [ -n "${!PMAN_BASE_BRANCH}" ] || die "\ +La branche $PMAN_BASE_BRANCH n'a pas été définie. +Veuillez éditer le fichier .pman.conf" + [ "$1" == init -o -n "$IfCreateBase" ] || die "${!PMAN_BASE_BRANCH}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +} + +function _ensure_merge_src() { + [ -n "$PmanMergeSrc" ] || die "\ +La branche $PMAN_MERGE_SRC n'a pas été définie. +Veuillez éditer le fichier .pman.conf" + [ "$1" == init -o -n "$IfMergeSrc" ] || die "$PmanMergeSrc: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +} + +function _ensure_merge_dest() { + [ -n "$PmanMergeDest" ] || die "\ +La branche $PMAN_MERGE_DEST n'a pas été définie. +Veuillez éditer le fichier .pman.conf" + [ "$1" == init -o -n "$IfMergeDest" ] || die "$PmanMergeDest: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" +} + +function checkout_action() { + local -a push_branches + + [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" || die + _ensure_branch init + + if [ -n "$IfRefBranch" ]; then + git checkout "$IfRefBranch" + elif array_contains LocalBranches "$PmanRefBranch"; then + git checkout "$PmanRefBranch" + elif array_contains AllBranches "$PmanRefBranch"; then + enote "$PmanRefBranch: une branche du même nom existe dans l'origine" + ask_yesno "Voulez-vous basculer sur cette branche?" O || die + git checkout "$PmanRefBranch" + elif [ -n "$PMAN_BASE_BRANCH" ]; then + _ensure_base_branch + + resolve_should_push + + local SrcBranch="${!PMAN_BASE_BRANCH}" DestBranch="$PmanRefBranch" + enote "Vous allez créer la branche ${COULEUR_BLEUE}$DestBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$SrcBranch${COULEUR_NORMALE}" + ask_yesno "Voulez-vous continuer?" O || die + + einfo "Création de la branche $DestBranch" + git checkout -b "$DestBranch" "$SrcBranch" || die + push_branches+=("$DestBranch") + + _push_branches + fi +} + +#XXXX +#[ -n "$PmanMergeSrc" ] || +# die "Aucune branche définie pour $PMAN_MERGE_SRC. Veuillez éditer le fichier .pman.conf" +function ensure_merge_branches() { + [ -n "$PMAN_CAN_MERGE" ] || + die "$PmanRefBranch: Aucune configuration de fusion trouvée pour cette branche" + + local branches + [ "$1" == -a ] && branches=AllBranches || branches=LocalBranches + + SrcBranch="$PmanMergeSrc" + array_contains "$branches" "$SrcBranch" || die "$SrcBranch: branche source introuvable" + DestBranch="$PmanMergeDest" + array_contains "$branches" "$DestBranch" || die "$DestBranch: branche destination introuvable" +} + +function _show_action() { + local commits + setx commits=_list_commits + if [ -n "$commits" ]; then + if [ $ShowLevel -ge 2 ]; then + { + echo "\ +# Commits à fusionner $SrcBranch --> $DestBranch + +$commits +" + _sd_COLOR=always _show_diff + } | less -eRF + else + einfo "Commits à fusionner $SrcBranch --> $DestBranch" + eecho "$commits" + fi + fi +} + +function show_action() { + git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" + ensure_merge_branches + _show_action "$@" +} + +function _merge_action() { + enote "\ +Ce script va +- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+ +- pousser les branches modifiées}" + ask_yesno "Voulez-vous continuer?" O || die + + local script=".git/pman-merge.sh" + local -a push_branches delete_branches + local hook + local comment= + local or_die=" || exit 1" + + _mscript_start + _scripta < PMAN_BRANCH -# "to" si on fait PMAN_BRANCH --> PMAN_MERGE_TO -# * PMAN_PREL_MERGE -- si la fusion devrait se faire avec prel -# * PMAN_DELETE_MERGED -- s'il faut supprimer la branche source après la fusion -# * PMAN_MERGE_SRC -- code de la branche source pour la fusion, ou vide si la -# fusion n'est pas possible -# * PMAN_MERGE_DEST -- code de la branche destination pour la fusion, ou vide si -# la fusion n'est pas possible -# * PMAN_CAN_MERGE -- indique si la fusion est possible -# * ${!PMAN_BRANCH} -- nom effectif de la branche si elle est définie dans -# .pman.conf -# * $PmanBranch -- nom effectif de la branche *si elle existe*, vide sinon -# * ${!PMAN_MERGE_SRC} -- nom effectif de la branche source si elle est définie -# dans .pman.conf -# * $PmanMergeSrc -- nom effectif de la branche source *si elle existe*, vide -# sinon -# * ${!PMAN_MERGE_DEST} -- nom effectif de la branche destination si elle est -# définie dans .pman.conf -# * $PmanMergeDest -- nom effectif de la branche source *si elle existe*, vide -# sinon - -[ -n "$PMAN_TOOL" ] || PMAN_TOOL="$MYNAME" -PMAN_BRANCH="PMAN_TOOL_${PMAN_TOOL^^}"; PMAN_BRANCH="${!PMAN_BRANCH}" -function set_pman_vars() { - case "$PMAN_BRANCH" in - FEATURE|RELEASE|HOTFIX) PMAN_UNIQUE=;; - *) PMAN_UNIQUE=1;; - esac - PMAN_CREATE_BASE=$(get_create_base "$PMAN_BRANCH") - PMAN_MERGE_FROM=$(get_merge_from "$PMAN_BRANCH") - PMAN_MERGE_TO=$(get_merge_to "$PMAN_BRANCH") - if [ -n "$1" ]; then PMAN_DIR="$1" - else PMAN_DIR=to - #elif [ -n "$PMAN_MERGE_TO" ]; then PMAN_DIR=to - #else PMAN_DIR=from - fi - PMAN_PREL_MERGE=$(should_prel_merge "$PMAN_BRANCH" "$PMAN_DIR" && echo 1) - PMAN_DELETE_MERGED=$(should_delete_merged "$PMAN_BRANCH" "$PMAN_DIR" && echo 1) - case "$PMAN_DIR" in - to) - PMAN_MERGE_SRC="$PMAN_BRANCH" - PMAN_MERGE_DEST="$PMAN_MERGE_TO" - ;; - from) - PMAN_MERGE_SRC="$PMAN_MERGE_FROM" - PMAN_MERGE_DEST="$PMAN_BRANCH" - ;; - esac - [ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ] && PMAN_CAN_MERGE=1 || PMAN_CAN_MERGE= -} - -function resolve_unique_branch() { - if [ "$PMAN_BRANCH" == FEATURE ]; then - if [ $# -gt 0 ]; then - UniqueBranch="$FEATURE${1#$FEATURE}" - elif [[ "$CurrentBranch" == "$FEATURE"* ]]; then - UniqueBranch="$CurrentBranch" - elif [ ${#FeatureBranches[*]} -eq 0 ]; then - die "Vous devez spécifier la branche de feature" - elif [ ${#FeatureBranches[*]} -eq 1 ]; then - UniqueBranch="${FeatureBranches[0]}" - else - simple_menu \ - UniqueBranch FeatureBranches \ - -t "Branches de feature" \ - -m "Veuillez choisir la branche de feature" \ - -d "${FeatureBranches[0]}" - fi - PMAN_BRANCH=UniqueBranch - PMAN_MERGE_SRC=UniqueBranch - else - die "resolve_unique_branch: $PMAN_BRANCH: non implémenté" - fi -} - -function dump_action() { - enote "Valeurs des variables: -PMAN_TOOL=$PMAN_TOOL -PMAN_BRANCH=$PMAN_BRANCH${PMAN_BRANCH:+ !PMAN_BRANCH=${!PMAN_BRANCH} PmanBranch=$PmanBranch} -PMAN_CREATE_BASE=$PMAN_CREATE_BASE${PMAN_CREATE_BASE:+ !PMAN_CREATE_BASE=${!PMAN_CREATE_BASE} PmanCreateBase=$PmanCreateBase} -PMAN_MERGE_FROM=$PMAN_MERGE_FROM -PMAN_MERGE_TO=$PMAN_MERGE_TO -PMAN_DIR=$PMAN_DIR -PMAN_PREL_MERGE=$PMAN_PREL_MERGE -PMAN_DELETE_MERGED=$PMAN_DELETE_MERGED -PMAN_MERGE_SRC=$PMAN_MERGE_SRC${PMAN_MERGE_SRC:+ !PMAN_MERGE_SRC=${!PMAN_MERGE_SRC} PmanMergeSrc=$PmanMergeSrc} -PMAN_MERGE_DEST=$PMAN_MERGE_DEST${PMAN_MERGE_DEST:+ !PMAN_MERGE_DEST=${!PMAN_MERGE_DEST} PmanMergeDest=$PmanMergeDest} -PMAN_CAN_MERGE=$PMAN_CAN_MERGE - -CurrentBranch=$CurrentBranch -LocalBranches=${LocalBranches[*]} -RemoteBranches=${RemoteBranches[*]} -AllBranches=${AllBranches[*]} - -UpstreamBranch=$UpstreamBranch -FeatureBranches=${FeatureBranches[*]} -DevelopBranch=$DevelopBranch -ReleaseBranch=$ReleaseBranch -HotfixBranch=$HotfixBranch -MainBranch=$MainBranch -DistBranch=$DistBranch -" -} - -function _ensure_branch() { - [ -n "${!PMAN_BRANCH}" ] || die "\ -La branche $PMAN_BRANCH n'a pas été définie. -Veuillez éditer le fichier .pman.conf" - [ "$1" == init -o -n "$PmanBranch" ] || die "${!PMAN_BRANCH}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" -} - -function _ensure_create_base() { - [ -n "${!PMAN_CREATE_BASE}" ] || die "\ -La branche $PMAN_CREATE_BASE n'a pas été définie. -Veuillez éditer le fichier .pman.conf" - [ "$1" == init -o -n "$PmanCreateBase" ] || die "${!PMAN_CREATE_BASE}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" -} - -function _ensure_merge_src() { - [ -n "${!PMAN_MERGE_SRC}" ] || die "\ -La branche $PMAN_MERGE_SRC n'a pas été définie. -Veuillez éditer le fichier .pman.conf" - [ "$1" == init -o -n "$PmanMergeSrc" ] || die "${!PMAN_MERGE_SRC}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" -} - -function _ensure_merge_dest() { - [ -n "${!PMAN_MERGE_DEST}" ] || die "\ -La branche $PMAN_MERGE_DEST n'a pas été définie. -Veuillez éditer le fichier .pman.conf" - [ "$1" == init -o -n "$PmanMergeDest" ] || die "${!PMAN_MERGE_DEST}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" -} - -function checkout_action() { - local -a push_branches - - [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" || die - _ensure_branch init - - if [ -n "$PmanBranch" ]; then - git checkout "$PmanBranch" - elif array_contains LocalBranches "${!PMAN_BRANCH}"; then - git checkout "${!PMAN_BRANCH}" - elif array_contains AllBranches "${!PMAN_BRANCH}"; then - enote "${!PMAN_BRANCH}: une branche du même nom existe dans l'origine" - ask_yesno "Voulez-vous basculer sur cette branche?" O || die - git checkout "${!PMAN_BRANCH}" - elif [ -n "$PMAN_CREATE_BASE" ]; then - _ensure_create_base - - resolve_should_push - - local SrcBranch="${!PMAN_CREATE_BASE}" DestBranch="${!PMAN_BRANCH}" - enote "Vous allez créer la branche ${COULEUR_BLEUE}$DestBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$SrcBranch${COULEUR_NORMALE}" - ask_yesno "Voulez-vous continuer?" O || die - - einfo "Création de la branche $DestBranch" - git checkout -b "$DestBranch" "$SrcBranch" || die - push_branches+=("$DestBranch") - - _push_branches - fi -} - -#XXXX -#[ -n "${!PMAN_MERGE_SRC}" ] || -# die "Aucune branche définie pour $PMAN_MERGE_SRC. Veuillez éditer le fichier .pman.conf" -function ensure_merge_branches() { - [ -n "$PMAN_CAN_MERGE" ] || - die "${!PMAN_BRANCH}: Aucune configuration de fusion trouvée pour cette branche" - - local branches - [ "$1" == -a ] && branches=AllBranches || branches=LocalBranches - - SrcBranch="${!PMAN_MERGE_SRC}" - array_contains "$branches" "$SrcBranch" || die "$SrcBranch: branche source introuvable" - DestBranch="${!PMAN_MERGE_DEST}" - array_contains "$branches" "$DestBranch" || die "$DestBranch: branche destination introuvable" -} - -function _show_action() { - local commits - setx commits=_list_commits - if [ -n "$commits" ]; then - if [ $ShowLevel -ge 2 ]; then - { - echo "\ -# Commits à fusionner $SrcBranch --> $DestBranch - -$commits -" - _sd_COLOR=always _show_diff - } | less -eRF - else - einfo "Commits à fusionner $SrcBranch --> $DestBranch" - eecho "$commits" - fi - fi -} - -function show_action() { - git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" - ensure_merge_branches - _show_action "$@" -} - -function _merge_action() { - enote "\ -Ce script va -- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+ -- pousser les branches modifiées}" - ask_yesno "Voulez-vous continuer?" O || die - - local script=".git/pman-merge.sh" - local -a push_branches delete_branches - local hook - local comment= - local or_die=" || exit 1" - - _mscript_start - _scripta < Date: Mon, 20 Oct 2025 12:15:39 +0400 Subject: [PATCH 75/91] modifs.mineures sans commentaires --- bash/src/pman.conf.sh | 2 - bash/src/pman.sh | 8 +-- bash/src/pman.tool.sh | 152 +++++++++++++++++++--------------------- bash/src/pman74.conf.sh | 4 -- bash/src/pman82.conf.sh | 4 -- wip/_pman.tool | 31 ++------ wip/pinit | 3 +- 7 files changed, 81 insertions(+), 123 deletions(-) diff --git a/bash/src/pman.conf.sh b/bash/src/pman.conf.sh index c33874c..f5a4ace 100644 --- a/bash/src/pman.conf.sh +++ b/bash/src/pman.conf.sh @@ -1,7 +1,5 @@ # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -## configuration par défaut - UPSTREAM= DEVELOP=develop FEATURE=wip/ diff --git a/bash/src/pman.sh b/bash/src/pman.sh index 6913994..4cfc677 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -128,12 +128,7 @@ function should_delete_merged() { * PmanMergeDest -- nom effectif de la branche destination si elle est définie dans .pman.conf * IfMergeDest -- nom effectif de la branche source *si elle existe*, vide - sinon - -* PMAN_CAN_MERGE -- indique si la fusion est théoriquement possible, c'est à - dire que $PMAN_MERGE_SRC et $PMAN_MERGE_DEST sont tous les deux non vides -* IfCanMerge -- indique si la fusion est effectivement possible, c'est à dire - que $IfMergeSrc et $IfMergeDest sont tous les deux non vides" + sinon" [ -n "$PMAN_TOOL" ] || PMAN_TOOL="$MYNAME" PMAN_REF_BRANCH="PMAN_TOOL_${PMAN_TOOL^^}"; PMAN_REF_BRANCH="${!PMAN_REF_BRANCH}" @@ -167,7 +162,6 @@ function set_pman_vars() { ;; esac - [ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ] && PMAN_CAN_MERGE=1 || PMAN_CAN_MERGE= [ -n "$PMAN_MERGE_SRC" ] && PmanMergeSrc="${!PMAN_MERGE_SRC}" || PmanMergeSrc= [ -n "$PMAN_MERGE_DEST" ] && PmanMergeDest="${!PMAN_MERGE_DEST}" || PmanMergeDest= } diff --git a/bash/src/pman.tool.sh b/bash/src/pman.tool.sh index 82ce248..56fe559 100644 --- a/bash/src/pman.tool.sh +++ b/bash/src/pman.tool.sh @@ -4,35 +4,11 @@ git_cleancheckout_DIRTY="\ Vous avez des modifications locales. Enregistrez ces modifications avant de fusionner la branche" -function resolve_unique_branch() { - if [ "$PMAN_REF_BRANCH" == FEATURE ]; then - if [ $# -gt 0 ]; then - UniqueBranch="$FEATURE${1#$FEATURE}" - elif [[ "$CurrentBranch" == "$FEATURE"* ]]; then - UniqueBranch="$CurrentBranch" - elif [ ${#FeatureBranches[*]} -eq 0 ]; then - die "Vous devez spécifier la branche de feature" - elif [ ${#FeatureBranches[*]} -eq 1 ]; then - UniqueBranch="${FeatureBranches[0]}" - else - simple_menu \ - UniqueBranch FeatureBranches \ - -t "Branches de feature" \ - -m "Veuillez choisir la branche de feature" \ - -d "${FeatureBranches[0]}" - fi - PMAN_REF_BRANCH=UniqueBranch - PMAN_MERGE_SRC=UniqueBranch - else - die "resolve_unique_branch: $PMAN_REF_BRANCH: non implémenté" - fi -} - function dump_action() { enote "Valeurs des variables: PMAN_TOOL=$PMAN_TOOL PMAN_REF_BRANCH=$PMAN_REF_BRANCH${PmanRefBranch:+ PmanRefBranch=$PmanRefBranch IfRefBranch=$IfRefBranch} -PMAN_BASE_BRANCH=$PMAN_BASE_BRANCH${PmanBaseBranch:+ PmanBaseBranch=$PmanBaseBranch IfCreateBase=$IfCreateBase} +PMAN_BASE_BRANCH=$PMAN_BASE_BRANCH${PmanBaseBranch:+ PmanBaseBranch=$PmanBaseBranch IfBaseBranch=$IfBaseBranch} PMAN_MERGE_FROM=$PMAN_MERGE_FROM PMAN_MERGE_TO=$PMAN_MERGE_TO PMAN_DIR=$PMAN_DIR @@ -40,7 +16,6 @@ PMAN_PREL_MERGE=$PMAN_PREL_MERGE PMAN_DELETE_MERGED=$PMAN_DELETE_MERGED PMAN_MERGE_SRC=$PMAN_MERGE_SRC${PmanMergeSrc:+ PmanMergeSrc=$PmanMergeSrc IfMergeSrc=$IfMergeSrc} PMAN_MERGE_DEST=$PMAN_MERGE_DEST${PmanMergeDest:+ PmanMergeDest=$PmanMergeDest IfMergeDest=$IfMergeDest} -PMAN_CAN_MERGE=$PMAN_CAN_MERGE IfCanMerge=$IfCanMerge CurrentBranch=$CurrentBranch LocalBranches=${LocalBranches[*]} @@ -57,7 +32,34 @@ DistBranch=$DistBranch " } -function _ensure_branch() { +function resolve_unique_branch() { + if [ "$PMAN_REF_BRANCH" == FEATURE ]; then + if [ $# -gt 0 ]; then + PmanRefBranch="$FEATURE${1#$FEATURE}" + elif [[ "$CurrentBranch" == "$FEATURE"* ]]; then + PmanRefBranch="$CurrentBranch" + elif [ ${#FeatureBranches[*]} -eq 0 ]; then + die "Vous devez spécifier la branche de feature" + elif [ ${#FeatureBranches[*]} -eq 1 ]; then + PmanRefBranch="${FeatureBranches[0]}" + else + simple_menu \ + PmanRefBranch FeatureBranches \ + -t "Branches de feature" \ + -m "Veuillez choisir la branche de feature" \ + -d "${FeatureBranches[0]}" + fi + else + die "resolve_unique_branch: $PMAN_REF_BRANCH: non implémenté" + fi + if [ "$PMAN_DIR" == to ]; then + PmanMergeSrc="$PmanRefBranch" + elif [ "$PMAN_DIR" == from ]; then + PmanMergeDest="$PmanRefBranch" + fi +} + +function _ensure_ref_branch() { [ -n "$PmanRefBranch" ] || die "\ La branche $PMAN_REF_BRANCH n'a pas été définie. Veuillez éditer le fichier .pman.conf" @@ -65,88 +67,71 @@ Veuillez éditer le fichier .pman.conf" } function _ensure_base_branch() { - [ -n "${!PMAN_BASE_BRANCH}" ] || die "\ + [ -n "$PmanBaseBranch" ] || die "\ La branche $PMAN_BASE_BRANCH n'a pas été définie. Veuillez éditer le fichier .pman.conf" - [ "$1" == init -o -n "$IfCreateBase" ] || die "${!PMAN_BASE_BRANCH}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" -} - -function _ensure_merge_src() { - [ -n "$PmanMergeSrc" ] || die "\ -La branche $PMAN_MERGE_SRC n'a pas été définie. -Veuillez éditer le fichier .pman.conf" - [ "$1" == init -o -n "$IfMergeSrc" ] || die "$PmanMergeSrc: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" -} - -function _ensure_merge_dest() { - [ -n "$PmanMergeDest" ] || die "\ -La branche $PMAN_MERGE_DEST n'a pas été définie. -Veuillez éditer le fichier .pman.conf" - [ "$1" == init -o -n "$IfMergeDest" ] || die "$PmanMergeDest: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" + [ "$1" == init -o -n "$IfBaseBranch" ] || die "$PmanBaseBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" } function checkout_action() { local -a push_branches - [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" || die - _ensure_branch init + [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" + _ensure_ref_branch init - if [ -n "$IfRefBranch" ]; then - git checkout "$IfRefBranch" - elif array_contains LocalBranches "$PmanRefBranch"; then + #if [ -n "$IfRefBranch" ]; then + # git checkout "$IfRefBranch" + #el + if array_contains LocalBranches "$PmanRefBranch"; then git checkout "$PmanRefBranch" elif array_contains AllBranches "$PmanRefBranch"; then enote "$PmanRefBranch: une branche du même nom existe dans l'origine" ask_yesno "Voulez-vous basculer sur cette branche?" O || die git checkout "$PmanRefBranch" - elif [ -n "$PMAN_BASE_BRANCH" ]; then + else _ensure_base_branch - resolve_should_push - local SrcBranch="${!PMAN_BASE_BRANCH}" DestBranch="$PmanRefBranch" - enote "Vous allez créer la branche ${COULEUR_BLEUE}$DestBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$SrcBranch${COULEUR_NORMALE}" + enote "Vous allez créer la branche ${COULEUR_BLEUE}$PmanRefBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$PmanBaseBranch${COULEUR_NORMALE}" ask_yesno "Voulez-vous continuer?" O || die - einfo "Création de la branche $DestBranch" - git checkout -b "$DestBranch" "$SrcBranch" || die - push_branches+=("$DestBranch") + einfo "Création de la branche $PmanRefBranch" + git checkout -b "$PmanRefBranch" "$PmanBaseBranch" || die + push_branches+=("$PmanRefBranch") _push_branches fi } -#XXXX -#[ -n "$PmanMergeSrc" ] || -# die "Aucune branche définie pour $PMAN_MERGE_SRC. Veuillez éditer le fichier .pman.conf" function ensure_merge_branches() { - [ -n "$PMAN_CAN_MERGE" ] || - die "$PmanRefBranch: Aucune configuration de fusion trouvée pour cette branche" + [ -n "$PmanMergeSrc" ] || die "\ +$PmanRefBranch: configuration de fusion non trouvée: la branche $PMAN_MERGE_SRC n'a pas été définie. +Veuillez éditer le fichier .pman.conf" + [ -n "$PmanMergeDest" ] || die "\ +$PmanRefBranch: configuration de fusion non trouvée: la branche $PMAN_MERGE_DEST n'a pas été définie. +Veuillez éditer le fichier .pman.conf" local branches [ "$1" == -a ] && branches=AllBranches || branches=LocalBranches - - SrcBranch="$PmanMergeSrc" - array_contains "$branches" "$SrcBranch" || die "$SrcBranch: branche source introuvable" - DestBranch="$PmanMergeDest" - array_contains "$branches" "$DestBranch" || die "$DestBranch: branche destination introuvable" + array_contains "$branches" "$PmanMergeSrc" || die "$PmanMergeSrc: branche source introuvable" + array_contains "$branches" "$PmanMergeDest" || die "$PmanMergeDest: branche destination introuvable" } function _show_action() { local commits - setx commits=_list_commits + setx commits=_list_commits "$PmanMergeSrc" "$PmanMergeDest" if [ -n "$commits" ]; then if [ $ShowLevel -ge 2 ]; then { echo "\ -# Commits à fusionner $SrcBranch --> $DestBranch +# Commits à fusionner $PmanMergeSrc --> $PmanMergeDest $commits " _sd_COLOR=always _show_diff } | less -eRF else - einfo "Commits à fusionner $SrcBranch --> $DestBranch" + einfo "Commits à fusionner $PmanMergeSrc --> $PmanMergeDest" eecho "$commits" fi fi @@ -154,6 +139,7 @@ $commits function show_action() { git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" + [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" ensure_merge_branches _show_action "$@" } @@ -161,7 +147,7 @@ function show_action() { function _merge_action() { enote "\ Ce script va -- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+ +- fusionner la branche ${COULEUR_BLEUE}$PmanMergeSrc${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$PmanMergeDest${COULEUR_NORMALE}${Push:+ - pousser les branches modifiées}" ask_yesno "Voulez-vous continuer?" O || die @@ -269,31 +255,35 @@ Il y a aussi les commandes supplémentaires suivantes: } function merge_action() { - [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" || die + [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" ensure_merge_branches -a if [ -n "$PMAN_PREL_MERGE" ]; then - [ -n "$ForceMerge" ] || die "$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel" + [ -n "$ForceMerge" ] || die "$PmanMergeSrc: cette branche doit être fusionnée dans $PmanMergeDest avec prel" + fi + if [ -n "$PMAN_DELETE_MERGED" ]; then + ShouldDelete=1 + [ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$PmanMergeDest" + else + ShouldDelete= + Delete= + [ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$PmanMergeSrc" fi - [ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$SrcBranch" - [ -n "$PMAN_DELETE_MERGED" ] || Delete= [ -z "$_Fake" ] && git_ensure_cleancheckout - if ! array_contains LocalBranches "$SrcBranch" && array_contains AllBranches "$SrcBranch"; then - enote "$SrcBranch: une branche du même nom existe dans l'origine" + if ! array_contains LocalBranches "$PmanMergeSrc" && array_contains AllBranches "$PmanMergeSrc"; then + enote "$PmanMergeSrc: une branche du même nom existe dans l'origine" fi - if ! array_contains LocalBranches "$DestBranch" && array_contains AllBranches "$DestBranch"; then - enote "$DestBranch: une branche du même nom existe dans l'origine" + if ! array_contains LocalBranches "$PmanMergeDest" && array_contains AllBranches "$PmanMergeDest"; then + enote "$PmanMergeDest: une branche du même nom existe dans l'origine" fi - array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche locale introuvable" - array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche locale introuvable" + array_contains LocalBranches "$PmanMergeSrc" || die "$PmanMergeSrc: branche locale introuvable" + array_contains LocalBranches "$PmanMergeDest" || die "$PmanMergeDest: branche locale introuvable" resolve_should_push - set -x #XXX _merge_action "$@" } function rebase_action() { die "non implémenté" } - diff --git a/bash/src/pman74.conf.sh b/bash/src/pman74.conf.sh index f179165..6796315 100644 --- a/bash/src/pman74.conf.sh +++ b/bash/src/pman74.conf.sh @@ -1,9 +1,5 @@ # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -## configuration de la branche 7.4 d'un projet PHP multiversion -# il s'agit d'un projet avec deux branches parallèles: 7.4 et 8.2, les -# modifications de la 7.4 étant incluses dans la branche 8.2 - UPSTREAM= DEVELOP=dev74 FEATURE=wip74/ diff --git a/bash/src/pman82.conf.sh b/bash/src/pman82.conf.sh index 85262bc..4cf9e2b 100644 --- a/bash/src/pman82.conf.sh +++ b/bash/src/pman82.conf.sh @@ -1,9 +1,5 @@ # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -## configuration de la branche 8.2 d'un projet PHP multiversion -# il s'agit d'un projet avec deux branches parallèles: 7.4 et 8.2, les -# modifications de la 7.4 étant incluses dans la branche 8.2 - UPSTREAM=dev74 DEVELOP=dev82 FEATURE=wip82/ diff --git a/wip/_pman.tool b/wip/_pman.tool index e26613a..5782f54 100755 --- a/wip/_pman.tool +++ b/wip/_pman.tool @@ -8,13 +8,8 @@ if check_gitdir; then load_branches all load_config set_pman_vars - if [ -n "$PmanMergeSrc" ]; then - load_branches current "$PmanMergeSrc" - loaded_config=1 - elif [ -n "$PmanRefBranch" ]; then - load_branches current "$PmanRefBranch" - loaded_config=1 - fi + load_branches current + loaded_config=1 else set_pman_vars fi @@ -78,23 +73,15 @@ c'est l'option par défaut" ) fi -if [ -n "$PMAN_CAN_MERGE" ]; then +if [ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ]; then if [ -n "$PMAN_UNIQUE" ]; then usage="${usage}|--show|--merge" else usage="${usage} $PMAN_REF_BRANCH --show|--merge" fi - if [ "$PMAN_REF_BRANCH" != "$PMAN_MERGE_SRC" ]; then - usage="$usage - -NB: du fait de la configuration des branches, la fusion se fait dans le sens -inverse $MergeSrcDesc --> $MergeDestDesc" - else - usage="$usage - -NB: La fusion se fait dans le sens $MergeSrcDesc --> $MergeDestDesc" - fi + [ "$PMAN_REF_BRANCH" != "$PMAN_MERGE_SRC" ] && bewareDir=" +NB: la fusion se fait dans le sens inverse" || bewareDir= variables="\ Les variables supplémentaires suivantes peuvent être définies: BEFORE_MERGE_${PMAN_MERGE_SRC} @@ -106,7 +93,7 @@ lister ce qui serait fusionné dans la branche $MergeDestDesc" #lancer git rebase -i sur la branche $MergeSrcDesc. cela permet de réordonner #les commits pour nettoyer l'historique avant la fusion" -m,--merge action=merge "\ -fusionner la branche $MergeSrcDesc dans la branche $MergeDestDesc" +fusionner la branche $MergeSrcDesc dans la branche $MergeDestDesc$bewareDir" --tech-merge TechMerge=1 "++option non documentée" -s:,--squash:COMMIT_MSG SquashMsg= "\ fusionner les modifications de la branche comme un seul commit" @@ -180,11 +167,7 @@ if [ -z "$loaded_config" -o -n "$chdir" -o -n "$ConfigFile" -o -n "$ConfigBranch load_branches all load_config set_pman_vars - if [ -n "$PmanMergeSrc" ]; then - load_branches current "$PmanMergeSrc" - elif [ -n "$PmanRefBranch" ]; then - load_branches current "$PmanRefBranch" - fi + load_branches current fi resolve_should_push quiet diff --git a/wip/pinit b/wip/pinit index ce6361a..6bb51d0 100755 --- a/wip/pinit +++ b/wip/pinit @@ -170,7 +170,8 @@ ne pas pousser les branches vers leur origine après leur création" pousser les branches vers leur origine après leur création. c'est l'option par défaut" -f,--force-create ForceCreate=1 "\ -Avec config, forcer la (re)création du fichier .pman.conf" +Forcer la (re)création des fichiers de configuration (notamment .pman.conf, +.composer.pman.yml, etc.)" ) parse_args "$@"; set -- "${args[@]}" From 8ea5802429fe3053b716925fa93a7130d61737c2 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 20 Oct 2025 16:20:42 +0400 Subject: [PATCH 76/91] =?UTF-8?q?r=C3=A9organiser=20les=20scripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bash/src/pman.sh | 4 +- ...r_local_deps.php => .pcomp-local_deps.php} | 0 ..._profile.php => .pcomp-select_profile.php} | 0 wip/_pman.tool => bin/_pman | 10 + ...er_local_deps.php => pcomp-local_deps.php} | 0 ...t_profile.php => pcomp-select_profile.php} | 0 bin/pdev | 1 + bin/pdist | 1 + bin/pmain | 1 + bin/pwip | 61 +-- wip/pdev | 1 - wip/pdist | 1 - wip/pmain | 1 - wip/pman | 372 ------------------ bin/pman => wip/pman.orig | 0 wip/pmer | 262 ------------ bin/pmer => wip/pmer.orig | 0 wip/prel | 292 -------------- wip/pups | 1 - wip/pwip | 1 - wip/pwip.orig | 60 +++ 21 files changed, 75 insertions(+), 994 deletions(-) rename bin/{._pman-composer_local_deps.php => .pcomp-local_deps.php} (100%) rename bin/{._pman-composer_select_profile.php => .pcomp-select_profile.php} (100%) rename wip/_pman.tool => bin/_pman (93%) rename bin/{_pman-composer_local_deps.php => pcomp-local_deps.php} (100%) rename bin/{_pman-composer_select_profile.php => pcomp-select_profile.php} (100%) create mode 120000 bin/pdev create mode 120000 bin/pdist create mode 120000 bin/pmain mode change 100755 => 120000 bin/pwip delete mode 120000 wip/pdev delete mode 120000 wip/pdist delete mode 120000 wip/pmain delete mode 100755 wip/pman rename bin/pman => wip/pman.orig (100%) delete mode 100755 wip/pmer rename bin/pmer => wip/pmer.orig (100%) delete mode 100755 wip/prel delete mode 120000 wip/pups delete mode 120000 wip/pwip create mode 100755 wip/pwip.orig diff --git a/bash/src/pman.sh b/bash/src/pman.sh index 4cfc677..8558db8 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -30,7 +30,7 @@ CONFIG_VARS=( ################################################################################ -PMAN_TOOL_PUPS=UPSTREAM +PMAN_BRANCHES=(UPSTREAM DEVELOP FEATURE MAIN DIST) PMAN_TOOL_PDEV=DEVELOP PMAN_TOOL_PWIP=FEATURE PMAN_TOOL_PMAIN=MAIN @@ -130,8 +130,6 @@ function should_delete_merged() { * IfMergeDest -- nom effectif de la branche source *si elle existe*, vide sinon" -[ -n "$PMAN_TOOL" ] || PMAN_TOOL="$MYNAME" -PMAN_REF_BRANCH="PMAN_TOOL_${PMAN_TOOL^^}"; PMAN_REF_BRANCH="${!PMAN_REF_BRANCH}" function set_pman_vars() { PmanRefBranch="${!PMAN_REF_BRANCH}" case "$PMAN_REF_BRANCH" in diff --git a/bin/._pman-composer_local_deps.php b/bin/.pcomp-local_deps.php similarity index 100% rename from bin/._pman-composer_local_deps.php rename to bin/.pcomp-local_deps.php diff --git a/bin/._pman-composer_select_profile.php b/bin/.pcomp-select_profile.php similarity index 100% rename from bin/._pman-composer_select_profile.php rename to bin/.pcomp-select_profile.php diff --git a/wip/_pman.tool b/bin/_pman similarity index 93% rename from wip/_pman.tool rename to bin/_pman index 5782f54..1470581 100755 --- a/wip/_pman.tool +++ b/bin/_pman @@ -3,6 +3,16 @@ source "$(dirname -- "$0")/../load.sh" || exit 1 require: git pman pman.tool pman.conf +if [ "$MYNAME" == _pman ]; then + [ "$1" == --help ] && exit_with eecho "USAGE: _pman tool [args]" + PMAN_TOOL=_pman + PMAN_REF_BRANCH="${1^^}"; shift + array_contains PMAN_BRANCHES "$PMAN_REF_BRANCH" || die "$PMAN_REF_BRANCH: invalid branch" +else + PMAN_TOOL="${MYNAME^^}" + PMAN_REF_BRANCH="PMAN_TOOL_${PMAN_TOOL}"; PMAN_REF_BRANCH="${!PMAN_REF_BRANCH}" +fi + loaded_config= if check_gitdir; then load_branches all diff --git a/bin/_pman-composer_local_deps.php b/bin/pcomp-local_deps.php similarity index 100% rename from bin/_pman-composer_local_deps.php rename to bin/pcomp-local_deps.php diff --git a/bin/_pman-composer_select_profile.php b/bin/pcomp-select_profile.php similarity index 100% rename from bin/_pman-composer_select_profile.php rename to bin/pcomp-select_profile.php diff --git a/bin/pdev b/bin/pdev new file mode 120000 index 0000000..4568707 --- /dev/null +++ b/bin/pdev @@ -0,0 +1 @@ +_pman \ No newline at end of file diff --git a/bin/pdist b/bin/pdist new file mode 120000 index 0000000..4568707 --- /dev/null +++ b/bin/pdist @@ -0,0 +1 @@ +_pman \ No newline at end of file diff --git a/bin/pmain b/bin/pmain new file mode 120000 index 0000000..4568707 --- /dev/null +++ b/bin/pmain @@ -0,0 +1 @@ +_pman \ No newline at end of file diff --git a/bin/pwip b/bin/pwip deleted file mode 100755 index 787676b..0000000 --- a/bin/pwip +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -source "$(dirname -- "$0")/../load.sh" || exit 1 -require: git pman pman.conf - -git_cleancheckout_DIRTY="\ -Vous avez des modifications locales. -Enregistrez ces modifications avant de créer une nouvelle branche" - -chdir= -Origin= -ConfigBranch= -ConfigFile= -[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push= -args=( - "créer une branche de feature" - "" - -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" - -O:,--origin Origin= "++\ -origine à partir de laquelle les branches distantes sont considérées" - -B:,--config-branch ConfigBranch= "++\ -branche à partir de laquelle charger la configuration" - -c:,--config-file:CONFIG ConfigFile= "++\ -fichier de configuration des branches. cette option est prioritaire sur --config-branch -par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" - -n,--no-push Push= "\ -ne pas pousser les branches vers leur origine après la fusion" - --push Push=1 "++\ -pousser les branches vers leur origine après la fusion. -c'est l'option par défaut" -) -parse_args "$@"; set -- "${args[@]}" - -# charger la configuration -ensure_gitdir "$chdir" -load_branches all -load_config "$MYNAME" -load_branches current - -branch="$1" -if [ -z "$branch" -a ${#FeatureBranches[*]} -eq 1 ]; then - branch="${FeatureBranches[0]}" -fi -[ -n "$branch" ] || die "Vous devez spécifier la branche à créer" -branch="$FEATURE${branch#$FEATURE}" - -resolve_should_push -git_ensure_cleancheckout - -if array_contains AllBranches "$branch"; then - git checkout -q "$branch" -else - # si la branche source n'existe pas, la créer - args=(--origin "$Origin") - if [ -n "$ConfigFile" ]; then args+=(--config-file "$ConfigFile") - elif [ -n "$ConfigBranch" ]; then args+=(--config-branch "$ConfigBranch") - fi - [ -z "$Push" ] && args+=(--no-push) - exec "$MYDIR/pman" "${args[@]}" "$branch" -fi diff --git a/bin/pwip b/bin/pwip new file mode 120000 index 0000000..4568707 --- /dev/null +++ b/bin/pwip @@ -0,0 +1 @@ +_pman \ No newline at end of file diff --git a/wip/pdev b/wip/pdev deleted file mode 120000 index 065a97c..0000000 --- a/wip/pdev +++ /dev/null @@ -1 +0,0 @@ -_pman.tool \ No newline at end of file diff --git a/wip/pdist b/wip/pdist deleted file mode 120000 index 065a97c..0000000 --- a/wip/pdist +++ /dev/null @@ -1 +0,0 @@ -_pman.tool \ No newline at end of file diff --git a/wip/pmain b/wip/pmain deleted file mode 120000 index 065a97c..0000000 --- a/wip/pmain +++ /dev/null @@ -1 +0,0 @@ -_pman.tool \ No newline at end of file diff --git a/wip/pman b/wip/pman deleted file mode 100755 index 6679ca2..0000000 --- a/wip/pman +++ /dev/null @@ -1,372 +0,0 @@ -#!/bin/bash -# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -source "$(dirname -- "$0")/../load.sh" || exit 1 -require: git pman pman.conf - -################################################################################ -# Informations -################################################################################ - -SHOW_VARS=( - --Configuration - "${CONFIG_VARS[@]}" - --Paramètres - CurrentBranch - CurrentType=SrcType -) - -function show_action() { - local var src - echo_setv ConfigBranch="$ConfigBranch" - echo_setv ConfigFile="$(ppath "$ConfigFile")" - for var in "${SHOW_VARS[@]}"; do - if [ "${var#--}" != "$var" ]; then - estep "${var#--}" - else - splitfsep "$var" = var src - [ -n "$src" ] || src="$var" - echo_setv "$var=${!src}" - fi - done -} - -################################################################################ -# Initialisation -################################################################################ - -function _init_config() { - if [ ! -f .pman.conf -o -n "$ForceCreate" ]; then - ac_set_tmpfile config - cp "$ConfigFile" "$config" - "${EDITOR:-nano}" "$config" - [ -s "$config" ] || return 1 - - cp "$config" .pman.conf - if testdiff .pman.conf "$ConfigFile"; then - ConfigFile="$(pwd)/.pman.conf" - load_config - load_branches current "$SrcBranch" - fi - git add .pman.conf - fi - if [ ! -f ".gitignore" ]; then - echo >.gitignore "\ -.~lock*# -.*.swp" - git add .gitignore - fi - return 0 -} - -function init_repo_action() { - local -a push_branches; local config - - [ ${#LocalBranches[*]} -eq 0 ] || die "Ce dépôt a déjà été initialisé" - - _init_config || exit_with ewarn "Initialisation du dépôt annulée" - - einfo "Création de la branche $MAIN" - git symbolic-ref HEAD "refs/heads/$MAIN" - git commit -m "commit initial" - push_branches+=("$MAIN") - - einfo "Création de la branche $DEVELOP" - git checkout -b "$DEVELOP" - push_branches+=("$DEVELOP") - - _push_branches -} - -function init_config_action() { - local -a push_branches; local config - - [ -f .pman.conf -a -z "$ForceCreate" ] && die "La configuration pman a déjà été initialisée" - - resolve_should_push - - _init_config || exit_with ewarn "Initialisation de la configuration annulée" - git commit -m "configuration pman" - push_branches+=("$CurrentBranch") - - _push_branches -} - -function _init_composer() { - if [ ! -f .composer.pman.yml -o -n "$ForceCreate" ]; then - ac_set_tmpfile config - cat >"$config" <Intégration initiale de la branche $UPSTREAM" \ - -srecursive -Xours --allow-unrelated-histories \ - "$UPSTREAM" - push_branches+=("$DEVELOP") - - _push_branches - fi - git checkout -q "$UPSTREAM" -} - -function _ensure_dist_branch() { - [ -n "$DIST" ] || die "La branche DIST n'a pas été définie" - [ "$1" == init -o -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" -} - -function init_dist_action() { - local -a push_branches - - if [ -z "$DistBranch" ]; then - array_contains AllBranches "$DIST" && exit_with enote "\ -$DIST: une branche du même nom existe dans l'origine - git checkout $DIST" - _ensure_main_branch - _ensure_dist_branch init - - resolve_should_push - - enote "Vous allez créer la branche ${COULEUR_VERTE}$DIST${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MAIN${COULEUR_NORMALE}" - ask_yesno "Voulez-vous continuer?" O || die - - einfo "Création de la branche $DIST" - git checkout -b "$DIST" "$MAIN" || die - push_branches+=("$DIST") - - _push_branches - fi - git checkout -q "$DIST" -} - -function init_feature_action() { - local -a push_branches; local branch - - [ -n "$FEATURE" ] || die "La branche FEATURE n'a pas été définie" - branch="${1#$FEATURE}" - [ -n "$branch" ] || die "Vous devez spécifier le nom de la branche" - branch="$FEATURE$branch" - - if ! array_contains LocalBranches "$branch"; then - array_contains AllBranches "$branch" && exit_with enote "\ -$branch: une branche du même nom existe dans l'origine - git checkout $branch" - _ensure_develop_branch - - resolve_should_push - - enote "Vous allez créer la branche ${COULEUR_VERTE}$branch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$DEVELOP${COULEUR_NORMALE}" - ask_yesno "Voulez-vous continuer?" O || die - - einfo "Création de la branche $branch" - git checkout -b "$branch" "$DEVELOP" || die - push_branches+=("$branch") - - _push_branches - fi - git checkout -q "$branch" -} - -function init_action() { - local what="${1:-develop}"; shift - case "$what" in - init|repo|r) init_repo_action "$@";; - config) init_config_action "$@";; - composer) init_composer_action "$@";; - main|m) checkout_main_action;; - develop|dev|d) init_develop_action "$@";; - upstream|up|u) init_upstream_action "$@";; - dist|x) init_dist_action "$@";; - *) init_feature_action "$what" "$@";; - esac -} - -################################################################################ -# Programme principal -################################################################################ - -chdir= -ConfigBranch= -ConfigFile= -action=init -Origin= -[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push= -ForceCreate= -args=( - "gérer un projet git" - "\ -repo|config|composer -develop|upstream|dist - -INITIALISATION - -Par défaut, le script agit en mode initialisation qui permet de créer et/ou -configurer certaines branches du dépôt si elles n'existent pas déjà - - repo - initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP - - develop - créer la branche $DEVELOP - upstream - créer la branche ${UPSTREAM:-UPSTREAM} en tant que source de la branche $DEVELOP - dist - créer la branche ${DIST:-DIST} en tant que destination de la branche $MAIN - anything - créer la branche ${FEATURE}anything à partir de la branche $DEVELOP" - -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" - -B:,--config-branch ConfigBranch= "++\ -branche à partir de laquelle charger la configuration" - -c:,--config-file:CONFIG ConfigFile= "++\ -fichier de configuration des branches. cette option est prioritaire sur --config-branch -par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" - -w,--show-config action=show "++\ -afficher la configuration chargée" - --composer-select-profile action=composer_select_profile "\ -sélectionner le profil composer spécifié en argument" - -O:,--origin Origin= "++\ -origine vers laquelle pousser les branches" - -n,--no-push Push= "\ -ne pas pousser les branches vers leur origine après leur création" - --push Push=1 "++\ -pousser les branches vers leur origine après leur création. -c'est l'option par défaut" - -f,--force-create ForceCreate=1 "\ -Avec config, forcer la (re)création du fichier .pman.conf" -) -parse_args "$@"; set -- "${args[@]}" - -# charger la configuration -ensure_gitdir "$chdir" -load_branches all -load_config "$MYNAME" -load_branches current - -# puis faire l'action que l'on nous demande -case "$action" in -show) - show_action "$@" - ;; -init) - git_ensure_cleancheckout - init_action "$@" - ;; -composer_select_profile) - exec "$MYDIR/_pman-$action.php" "$@" - ;; -*) - die "$action: action non implémentée" - ;; -esac diff --git a/bin/pman b/wip/pman.orig similarity index 100% rename from bin/pman rename to wip/pman.orig diff --git a/wip/pmer b/wip/pmer deleted file mode 100755 index 9a7daf8..0000000 --- a/wip/pmer +++ /dev/null @@ -1,262 +0,0 @@ -#!/bin/bash -# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -source "$(dirname -- "$0")/../load.sh" || exit 1 -require: git pman pman.conf - -git_cleancheckout_DIRTY="\ -Vous avez des modifications locales. -Enregistrez ces modifications avant de fusionner la branche" - -function show_action() { - local commits - setx commits=_list_commits - if [ -n "$commits" ]; then - if [ $ShowLevel -ge 2 ]; then - { - echo "\ -# Commits à fusionner $SrcBranch --> $DestBranch - -$commits -" - _sd_COLOR=always _show_diff - } | less -eRF - else - einfo "Commits à fusionner $SrcBranch --> $DestBranch" - eecho "$commits" - fi - fi -} - -function ensure_branches() { - [ -n "$SrcBranch" -a -n "$DestBranch" ] || - die "$SrcBranch: Aucune configuration de fusion trouvée pour cette branche" - - array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche source introuvable" - array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche destination introuvable" -} - -function merge_action() { - [ -z "$ShouldPush" ] && enote "\ -L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine" - - enote "\ -Ce script va -- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+ -- pousser les branches modifiées}" - ask_yesno "Voulez-vous continuer?" O || die - - local script=".git/pman-merge.sh" - local -a push_branches delete_branches - local hook - local comment= - local or_die=" || exit 1" - - _mscript_start - _scripta < - AFTER_MERGE_ - AFTER_DELETE_ - BEFORE_PUSH_ - AFTER_PUSH_ -srcType et destType pouvant valoir UPSTREAM, DEVELOP, FEATURE, RELEASE, MAIN, HOTFIX, DIST" - -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" - -O:,--origin Origin= "++\ -origine à partir de laquelle les branches distantes sont considérées" - -B:,--config-branch ConfigBranch= "++\ -branche à partir de laquelle charger la configuration" - -c:,--config-file:CONFIG ConfigFile= "++\ -fichier de configuration des branches. cette option est prioritaire sur --config-branch -par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" - --fake _Fake=1 "++option non documentée" - --keep-script _KeepScript=1 "++option non documentée" - -w,--show '$action=show; inc@ ShowLevel' "\ -lister les modifications qui seraient fusionnées dans la branche destination" - -b,--rebase action=rebase "\ -lancer git rebase -i sur la branche source. cela permet de réordonner les -commits pour nettoyer l'historique avant la fusion" - --merge action=merge "++\ -fusionner la branche source dans la branche destination correspondante. -c'est l'action par défaut" - --tech-merge TechMerge=1 "++option non documentée" - -s:,--squash:COMMIT_MSG SquashMsg= "\ -fusionner les modifications de la branche comme un seul commit. -cette option ne devrait pas être utilisée avec --no-delete" - -n,--no-push Push= "\ -ne pas pousser les branches vers leur origine après la fusion" - --push Push=1 "++\ -pousser les branches vers leur origine après la fusion. -c'est l'option par défaut" - -k,--no-delete Delete= "\ -ne pas supprimer la branche après la fusion dans la destination" - --delete Delete=1 "++\ -supprimer la branche après la fusion dans la destination. -c'est l'option par défaut" - -f,--force-merge ForceMerge=1 "++\ -forcer la fusion pour une branche qui devrait être traitée par prel" - -a:,--after-merge AfterMerge= "\ -évaluer le script spécifié après une fusion *réussie*" -) -parse_args "$@"; set -- "${args[@]}" - -# charger la configuration -ensure_gitdir "$chdir" -load_branches all -load_config "$MYNAME" -load_branches current "$1" - -resolve_should_push quiet - -# puis faire l'action que l'on nous demande -case "$action" in -show) - git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" - ensure_branches - show_action "$@" - ;; -merge) - ShouldDelete=1 - no_merge_msg="$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel" - if [ "$SrcType" == develop ]; then - [ -z "$ForceMerge" ] && die "$no_merge_msg" - [ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$SrcBranch" - elif [ "$SrcType" == release -o "$SrcType" == hotfix ]; then - die "$no_merge_msg" - fi - # n'autoriser la suppression que pour feature - [ "$SrcType" == feature ] || ShouldDelete= - [ -z "$ShouldDelete" ] && Delete= - [ -z "$_Fake" ] && git_ensure_cleancheckout - if array_contains LocalBranches "$SrcBranch"; then - ensure_branches - merge_action "$@" - elif array_contains AllBranches "$SrcBranch"; then - enote "$SrcBranch: une branche du même nom existe dans l'origine" - die "$SrcBranch: branche locale introuvable" - else - die "$SrcBranch: branche introuvable" - fi - ;; -*) - die "$action: action non implémentée" - ;; -esac diff --git a/bin/pmer b/wip/pmer.orig similarity index 100% rename from bin/pmer rename to wip/pmer.orig diff --git a/wip/prel b/wip/prel deleted file mode 100755 index 2ec3a31..0000000 --- a/wip/prel +++ /dev/null @@ -1,292 +0,0 @@ -#!/bin/bash -# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -source "$(dirname -- "$0")/../load.sh" || exit 1 -require: git pman pman.conf - -git_cleancheckout_DIRTY="\ -Vous avez des modifications locales. -Enregistrez ces modifications avant de créer une release" - -function show_action() { - local commits - setx commits=_list_commits - if [ -n "$commits" ]; then - if [ $ShowLevel -ge 2 ]; then - { - echo "\ -# Commits à fusionner $SrcBranch --> $DestBranch - -$commits -" - _sd_COLOR=always _show_diff - } | less -eRF - else - einfo "Commits à fusionner $SrcBranch --> $DestBranch" - eecho "$commits" - fi - fi -} - -function ensure_branches() { - [ -n "$SrcBranch" -a -n "$DestBranch" ] || - die "$SrcBranch: Aucune configuration de fusion trouvée pour cette branche" - - array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche source introuvable" - array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche destination introuvable" - - Tag="$TAG_PREFIX$Version$TAG_SUFFIX" - local -a tags - setx -a tags=git tag -l "${TAG_PREFIX}*${TAG_SUFFIX}" - if [ -z "$ForceCreate" ]; then - array_contains tags "$Tag" && die "$Tag: le tag correspondant à la version existe déjà" - fi -} - -function create_release_action() { - if [ -n "$ReleaseBranch" ]; then - Version="${ReleaseBranch#$RELEASE}" - Tag="$TAG_PREFIX$Version$TAG_SUFFIX" - merge_release_action "$@"; return $? - elif [ -n "$HotfixBranch" ]; then - Version="${HotfixBranch#$HOTFIX}" - Tag="$TAG_PREFIX$Version$TAG_SUFFIX" - merge_hotfix_action "$@"; return $? - fi - - [ -n "$ManualRelease" ] && ewarn "\ -L'option --no-merge a été forcée puisque ce dépôt ne supporte pas les releases automatiques" - [ -z "$ShouldPush" ] && enote "\ -L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine" - - if [ -z "$Version" -a -n "$CurrentVersion" -a -f VERSION.txt ]; then - Version="$(" + -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" + -O:,--origin Origin= "++\ +origine à partir de laquelle les branches distantes sont considérées" + -B:,--config-branch ConfigBranch= "++\ +branche à partir de laquelle charger la configuration" + -c:,--config-file:CONFIG ConfigFile= "++\ +fichier de configuration des branches. cette option est prioritaire sur --config-branch +par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" + -n,--no-push Push= "\ +ne pas pousser les branches vers leur origine après la fusion" + --push Push=1 "++\ +pousser les branches vers leur origine après la fusion. +c'est l'option par défaut" +) +parse_args "$@"; set -- "${args[@]}" + +# charger la configuration +ensure_gitdir "$chdir" +load_branches all +load_config "$MYNAME" +load_branches current + +branch="$1" +if [ -z "$branch" -a ${#FeatureBranches[*]} -eq 1 ]; then + branch="${FeatureBranches[0]}" +fi +[ -n "$branch" ] || die "Vous devez spécifier la branche à créer" +branch="$FEATURE${branch#$FEATURE}" + +resolve_should_push +git_ensure_cleancheckout + +if array_contains AllBranches "$branch"; then + git checkout -q "$branch" +else + # si la branche source n'existe pas, la créer + args=(--origin "$Origin") + if [ -n "$ConfigFile" ]; then args+=(--config-file "$ConfigFile") + elif [ -n "$ConfigBranch" ]; then args+=(--config-branch "$ConfigBranch") + fi + [ -z "$Push" ] && args+=(--no-push) + exec "$MYDIR/pman" "${args[@]}" "$branch" +fi From e14fd4d160f73f8cfd564c4b54eb7857a369f910 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 20 Oct 2025 17:44:53 +0400 Subject: [PATCH 77/91] modifs.mineures sans commentaires --- bash/src/pman.sh | 3 +- bin/_merge82 | 2 +- bin/_pman | 184 ------------------ bin/pdev | 2 +- bin/pdist | 2 +- bin/pmain | 2 +- bash/src/pman.tool.sh => bin/ptool | 231 +++++++++++++++++++++++ bin/pwip | 2 +- wip/prel.orig | 292 +++++++++++++++++++++++++++++ 9 files changed, 530 insertions(+), 190 deletions(-) delete mode 100755 bin/_pman rename bash/src/pman.tool.sh => bin/ptool (59%) mode change 100644 => 100755 create mode 100755 wip/prel.orig diff --git a/bash/src/pman.sh b/bash/src/pman.sh index 8558db8..52f7ff3 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -25,7 +25,8 @@ DIST= NOAUTO= CONFIG_VARS=( - UPSTREAM DEVELOP FEATURE RELEASE MAIN TAG_PREFIX TAG_SUFFIX HOTFIX DIST NOAUTO + UPSTREAM DEVELOP FEATURE RELEASE MAIN HOTFIX DIST + TAG_PREFIX TAG_SUFFIX NOAUTO ) ################################################################################ diff --git a/bin/_merge82 b/bin/_merge82 index 7ccedb2..ac4a192 100755 --- a/bin/_merge82 +++ b/bin/_merge82 @@ -9,4 +9,4 @@ args=( ) parse_args "$@"; set -- "${args[@]}" -exec "$MYDIR/pmer" --tech-merge -Bdev82 dev74 ${dev74:+-a "git checkout dev74"} "$@" +exec "$MYDIR/ptool" upstream -Bdev82 -m --tech-merge ${dev74:+-a "git checkout dev74"} "$@" diff --git a/bin/_pman b/bin/_pman deleted file mode 100755 index 1470581..0000000 --- a/bin/_pman +++ /dev/null @@ -1,184 +0,0 @@ -#!/bin/bash -# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -source "$(dirname -- "$0")/../load.sh" || exit 1 -require: git pman pman.tool pman.conf - -if [ "$MYNAME" == _pman ]; then - [ "$1" == --help ] && exit_with eecho "USAGE: _pman tool [args]" - PMAN_TOOL=_pman - PMAN_REF_BRANCH="${1^^}"; shift - array_contains PMAN_BRANCHES "$PMAN_REF_BRANCH" || die "$PMAN_REF_BRANCH: invalid branch" -else - PMAN_TOOL="${MYNAME^^}" - PMAN_REF_BRANCH="PMAN_TOOL_${PMAN_TOOL}"; PMAN_REF_BRANCH="${!PMAN_REF_BRANCH}" -fi - -loaded_config= -if check_gitdir; then - load_branches all - load_config - set_pman_vars - load_branches current - loaded_config=1 -else - set_pman_vars -fi - -BranchDesc= -MergeSrcDesc= -MergeDestDesc= -if [ -n "$PMAN_REF_BRANCH" ]; then - BranchDesc="${COULEUR_BLANCHE}<$PMAN_REF_BRANCH>" - [ -n "$PmanRefBranch" -a -n "$PMAN_UNIQUE" ] && BranchDesc="$BranchDesc ($PmanRefBranch)" - BranchDesc="$BranchDesc${COULEUR_NORMALE}" -fi -if [ -n "$PMAN_MERGE_SRC" ]; then - MergeSrcDesc="${COULEUR_BLEUE}<$PMAN_MERGE_SRC>" - [ -n "$PmanMergeSrc" -a -n "$PMAN_UNIQUE" ] && MergeSrcDesc="$MergeSrcDesc ($PmanMergeSrc)" - MergeSrcDesc="$MergeSrcDesc${COULEUR_NORMALE}" -fi -if [ -n "$PMAN_MERGE_DEST" ]; then - MergeDestDesc="${COULEUR_ROUGE}<$PMAN_MERGE_DEST>" - [ -n "$PmanMergeDest" -a -n "$PMAN_UNIQUE" ] && MergeDestDesc="$MergeDestDesc ($PmanMergeDest)" - MergeDestDesc="$MergeDestDesc${COULEUR_NORMALE}" -fi - -[ -n "$PMAN_UNIQUE" ] && - purpose="gérer la branche $BranchDesc" || - purpose="gérer les branches $BranchDesc" -usage="--checkout" -variables= -args=( - -d:,--chdir:BASEDIR chdir= "\ -répertoire dans lequel se placer avant de lancer les opérations" - -O:,--origin Origin= "++\ -origine à partir de laquelle les branches distantes sont considérées" - -B:,--config-branch ConfigBranch= "++\ -branche à partir de laquelle charger la configuration" - -c:,--config-file:CONFIG ConfigFile= "++\ -fichier de configuration des branches. le fichier .pman.conf dans le répertoire -du dépôt est utilisé par défaut s'il existe. cette option est prioritaire sur ---config-branch" - --fake _Fake=1 "++option non documentée" - --keep-script _KeepScript=1 "++option non documentée" - --dump action=dump "++afficher les noms des branches" -) -if [ -n "$PmanRefBranch" -a -n "$PMAN_UNIQUE" ]; then - args+=( - --checkout action=checkout "++\ -créer le cas échéant la branche $BranchDesc et basculer vers elle. -c'est l'option par défaut" - ) -elif [ -z "$PMAN_UNIQUE" ]; then - args+=( - --checkout action=checkout "\ -créer le cas échéant la branche $BranchDesc et basculer vers elle. -c'est l'option par défaut" - ) -else - args+=( - --checkout action=checkout "\ -créer la branche $MergeDestDesc et basculer vers elle. -c'est l'option par défaut" - ) -fi - -if [ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ]; then - if [ -n "$PMAN_UNIQUE" ]; then - usage="${usage}|--show|--merge" - else - usage="${usage} $PMAN_REF_BRANCH ---show|--merge" - fi - [ "$PMAN_REF_BRANCH" != "$PMAN_MERGE_SRC" ] && bewareDir=" -NB: la fusion se fait dans le sens inverse" || bewareDir= - variables="\ -Les variables supplémentaires suivantes peuvent être définies: - BEFORE_MERGE_${PMAN_MERGE_SRC} - AFTER_MERGE_${PMAN_MERGE_SRC}" - args+=( - -w,--show '$action=show; inc@ ShowLevel' "\ -lister ce qui serait fusionné dans la branche $MergeDestDesc" -# -b,--rebase action=rebase "\ -#lancer git rebase -i sur la branche $MergeSrcDesc. cela permet de réordonner -#les commits pour nettoyer l'historique avant la fusion" - -m,--merge action=merge "\ -fusionner la branche $MergeSrcDesc dans la branche $MergeDestDesc$bewareDir" - --tech-merge TechMerge=1 "++option non documentée" - -s:,--squash:COMMIT_MSG SquashMsg= "\ -fusionner les modifications de la branche comme un seul commit" - ) - if [ -n "$PMAN_PREL_MERGE" ]; then - args+=( - -f,--force-merge ForceMerge=1 "++\ -forcer la fusion pour une branche qui devrait être traitée par prel" - ) - fi - args+=( - -n,--no-push Push= "\ -ne pas pousser les branches vers leur origine après la fusion" - --push Push=1 "++\ -pousser les branches vers leur origine après la fusion. -c'est l'option par défaut" - ) - if [ -n "$PMAN_DELETE_MERGED" ]; then - variables="${variables} - AFTER_DELETE_${PMAN_MERGE_SRC}" - args+=( - -k,--no-delete Delete= "\ -ne pas supprimer la branche $MergeSrcDesc après la fusion dans la branche -$MergeDestDesc. cette option ne devrait pas être utilisée avec --squash" - --delete Delete=1 "++\ -supprimer la branche $MergeSrcDesc après la fusion dans la branche -$MergeDestDesc. -c'est l'option par défaut" - ) - fi - if [ -n "$PMAN_MERGE_DEST" ]; then - variables="${variables} - BEFORE_PUSH_${PMAN_MERGE_DEST} - AFTER_PUSH_${PMAN_MERGE_DEST}" - fi - args+=( - -a:,--after-merge AfterMerge= "\ -évaluer le script spécifié après une fusion *réussie*" - ) -fi - -chdir= -Origin= -ConfigBranch= -ConfigFile= -_Fake= -_KeepScript= -action=checkout -ShowLevel=0 -TechMerge= -SquashMsg= -Push=1 -Delete=1 -AfterMerge= -args=( - "$purpose" - "\ - $usage - -CONFIGURATION - -Le fichier .pman.conf contient la configuration des branches. -$variables" - "${args[@]}" -) -parse_args "$@"; set -- "${args[@]}" - -if [ -z "$loaded_config" -o -n "$chdir" -o -n "$ConfigFile" -o -n "$ConfigBranch" ]; then - # charger la configuration - ensure_gitdir "$chdir" - load_branches all - load_config - set_pman_vars - load_branches current -fi -resolve_should_push quiet - -"${action}_action" "$@" diff --git a/bin/pdev b/bin/pdev index 4568707..22100e6 120000 --- a/bin/pdev +++ b/bin/pdev @@ -1 +1 @@ -_pman \ No newline at end of file +ptool \ No newline at end of file diff --git a/bin/pdist b/bin/pdist index 4568707..22100e6 120000 --- a/bin/pdist +++ b/bin/pdist @@ -1 +1 @@ -_pman \ No newline at end of file +ptool \ No newline at end of file diff --git a/bin/pmain b/bin/pmain index 4568707..22100e6 120000 --- a/bin/pmain +++ b/bin/pmain @@ -1 +1 @@ -_pman \ No newline at end of file +ptool \ No newline at end of file diff --git a/bash/src/pman.tool.sh b/bin/ptool old mode 100644 new mode 100755 similarity index 59% rename from bash/src/pman.tool.sh rename to bin/ptool index 56fe559..c75d49e --- a/bash/src/pman.tool.sh +++ b/bin/ptool @@ -1,4 +1,7 @@ +#!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname -- "$0")/../load.sh" || exit 1 +require: git pman pman.conf git_cleancheckout_DIRTY="\ Vous avez des modifications locales. @@ -287,3 +290,231 @@ function merge_action() { function rebase_action() { die "non implémenté" } + +################################################################################ + +loaded_config= +dir= +if [ "$MYNAME" == ptool ]; then + if [ "$1" == --help ]; then + exit_with eecho "$MYNAME: gérer les branches d'un projet + +USAGE + $MYNAME [-t|-f] REF args... + +OPTIONS + REF + -t, --merge-to REF + spécifier la branche de référence et indiquer que la fusion se fait dans + le sens REF --> DEST. DEST est calculé en fonction de REF + -f, --merge-from REF + spécifier la branche de référence et indiquer que la fusion se fait dans + le sens SRC --> REF. SRC est calculé en fonction de REF" + fi + + ref="$1"; shift + dir=to + [ -n "$ref" ] || die "vous spécifier la branche de référence" + + case "$ref" in + -t|--merge-to) + ref="$1"; shift + dir=to + ;; + -t*) + ref="${ref#-t}" + dir=to + ;; + -f|--merge-from) + ref="$1"; shift + dir=from + ;; + -f*) + ref="${ref#-f}" + dir=from + ;; + esac + PMAN_TOOL= + PMAN_REF_BRANCH="${ref^^}" + array_contains PMAN_BRANCHES "$PMAN_REF_BRANCH" || die "$ref: invalid branch" + +else + PMAN_TOOL="${MYNAME^^}" + PMAN_REF_BRANCH="PMAN_TOOL_${PMAN_TOOL}"; PMAN_REF_BRANCH="${!PMAN_REF_BRANCH}" +fi + +if check_gitdir; then + load_branches all + load_config + set_pman_vars "$dir" + load_branches current + loaded_config=1 +else + set_pman_vars "$dir" +fi + +BranchDesc= +MergeSrcDesc= +MergeDestDesc= +if [ -n "$PMAN_REF_BRANCH" ]; then + BranchDesc="${COULEUR_BLANCHE}<$PMAN_REF_BRANCH>" + [ -n "$PmanRefBranch" -a -n "$PMAN_UNIQUE" ] && BranchDesc="$BranchDesc ($PmanRefBranch)" + BranchDesc="$BranchDesc${COULEUR_NORMALE}" +fi +if [ -n "$PMAN_MERGE_SRC" ]; then + MergeSrcDesc="${COULEUR_BLEUE}<$PMAN_MERGE_SRC>" + [ -n "$PmanMergeSrc" -a -n "$PMAN_UNIQUE" ] && MergeSrcDesc="$MergeSrcDesc ($PmanMergeSrc)" + MergeSrcDesc="$MergeSrcDesc${COULEUR_NORMALE}" +fi +if [ -n "$PMAN_MERGE_DEST" ]; then + MergeDestDesc="${COULEUR_ROUGE}<$PMAN_MERGE_DEST>" + [ -n "$PmanMergeDest" -a -n "$PMAN_UNIQUE" ] && MergeDestDesc="$MergeDestDesc ($PmanMergeDest)" + MergeDestDesc="$MergeDestDesc${COULEUR_NORMALE}" +fi + +[ -n "$PMAN_UNIQUE" ] && + purpose="gérer la branche $BranchDesc" || + purpose="gérer les branches $BranchDesc" +usage="--checkout" +variables= +args=( + -d:,--chdir:BASEDIR chdir= "\ +répertoire dans lequel se placer avant de lancer les opérations" + -O:,--origin Origin= "++\ +origine à partir de laquelle les branches distantes sont considérées" + -B:,--config-branch ConfigBranch= "++\ +branche à partir de laquelle charger la configuration" + -c:,--config-file:CONFIG ConfigFile= "++\ +fichier de configuration des branches. le fichier .pman.conf dans le répertoire +du dépôt est utilisé par défaut s'il existe. cette option est prioritaire sur +--config-branch" + --fake _Fake=1 "++option non documentée" + --keep-script _KeepScript=1 "++option non documentée" + --dump action=dump "++afficher les noms des branches" +) + +if [ -n "$PmanRefBranch" -a -n "$PMAN_UNIQUE" ]; then + args+=( + --checkout action=checkout "++\ +créer le cas échéant la branche $BranchDesc et basculer vers elle. +c'est l'option par défaut" + ) +elif [ -z "$PMAN_UNIQUE" ]; then + args+=( + --checkout action=checkout "\ +créer le cas échéant la branche $BranchDesc et basculer vers elle. +c'est l'option par défaut" + ) +else + args+=( + --checkout action=checkout "\ +créer la branche $MergeDestDesc et basculer vers elle. +c'est l'option par défaut" + ) +fi + +if [ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ]; then + if [ -n "$PMAN_UNIQUE" ]; then + usage="${usage}|--show|--merge" + else + usage="${usage} $PMAN_REF_BRANCH +--show|--merge" + fi + [ "$PMAN_REF_BRANCH" != "$PMAN_MERGE_SRC" ] && bewareDir=" +NB: la fusion se fait dans le sens inverse" || bewareDir= + variables="\ +Les variables supplémentaires suivantes peuvent être définies: + BEFORE_MERGE_${PMAN_MERGE_SRC} + AFTER_MERGE_${PMAN_MERGE_SRC}" + + args+=( + -w,--show '$action=show; inc@ ShowLevel' "\ +lister ce qui serait fusionné dans la branche $MergeDestDesc" +# -b,--rebase action=rebase "\ +#lancer git rebase -i sur la branche $MergeSrcDesc. cela permet de réordonner +#les commits pour nettoyer l'historique avant la fusion" + -m,--merge action=merge "\ +fusionner la branche $MergeSrcDesc dans la branche $MergeDestDesc$bewareDir" + --tech-merge TechMerge=1 "++option non documentée" + -s:,--squash:COMMIT_MSG SquashMsg= "\ +fusionner les modifications de la branche comme un seul commit" + ) + + if [ -n "$PMAN_PREL_MERGE" ]; then + args+=( + -f,--force-merge ForceMerge=1 "++\ +forcer la fusion pour une branche qui devrait être traitée par prel" + ) + fi + + args+=( + -n,--no-push Push= "\ +ne pas pousser les branches vers leur origine après la fusion" + --push Push=1 "++\ +pousser les branches vers leur origine après la fusion. +c'est l'option par défaut" + ) + + if [ -n "$PMAN_DELETE_MERGED" ]; then + variables="${variables} + AFTER_DELETE_${PMAN_MERGE_SRC}" + args+=( + -k,--no-delete Delete= "\ +ne pas supprimer la branche $MergeSrcDesc après la fusion dans la branche +$MergeDestDesc. cette option ne devrait pas être utilisée avec --squash" + --delete Delete=1 "++\ +supprimer la branche $MergeSrcDesc après la fusion dans la branche +$MergeDestDesc. +c'est l'option par défaut" + ) + fi + + if [ -n "$PMAN_MERGE_DEST" ]; then + variables="${variables} + BEFORE_PUSH_${PMAN_MERGE_DEST} + AFTER_PUSH_${PMAN_MERGE_DEST}" + fi + + args+=( + -a:,--after-merge AfterMerge= "\ +évaluer le script spécifié après une fusion *réussie*" + ) +fi + +chdir= +Origin= +ConfigBranch= +ConfigFile= +_Fake= +_KeepScript= +action=checkout +ShowLevel=0 +TechMerge= +SquashMsg= +Push=1 +Delete=1 +AfterMerge= +args=( + "$purpose" + "\ + $usage + +CONFIGURATION + +Le fichier .pman.conf contient la configuration des branches. +$variables" + "${args[@]}" +) +parse_args "$@"; set -- "${args[@]}" + +if [ -z "$loaded_config" -o -n "$chdir" -o -n "$ConfigFile" -o -n "$ConfigBranch" ]; then + # charger la configuration + ensure_gitdir "$chdir" + load_branches all + load_config + set_pman_vars "$dir" + load_branches current +fi +resolve_should_push quiet + +"${action}_action" "$@" diff --git a/bin/pwip b/bin/pwip index 4568707..22100e6 120000 --- a/bin/pwip +++ b/bin/pwip @@ -1 +1 @@ -_pman \ No newline at end of file +ptool \ No newline at end of file diff --git a/wip/prel.orig b/wip/prel.orig new file mode 100755 index 0000000..2ec3a31 --- /dev/null +++ b/wip/prel.orig @@ -0,0 +1,292 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname -- "$0")/../load.sh" || exit 1 +require: git pman pman.conf + +git_cleancheckout_DIRTY="\ +Vous avez des modifications locales. +Enregistrez ces modifications avant de créer une release" + +function show_action() { + local commits + setx commits=_list_commits + if [ -n "$commits" ]; then + if [ $ShowLevel -ge 2 ]; then + { + echo "\ +# Commits à fusionner $SrcBranch --> $DestBranch + +$commits +" + _sd_COLOR=always _show_diff + } | less -eRF + else + einfo "Commits à fusionner $SrcBranch --> $DestBranch" + eecho "$commits" + fi + fi +} + +function ensure_branches() { + [ -n "$SrcBranch" -a -n "$DestBranch" ] || + die "$SrcBranch: Aucune configuration de fusion trouvée pour cette branche" + + array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche source introuvable" + array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche destination introuvable" + + Tag="$TAG_PREFIX$Version$TAG_SUFFIX" + local -a tags + setx -a tags=git tag -l "${TAG_PREFIX}*${TAG_SUFFIX}" + if [ -z "$ForceCreate" ]; then + array_contains tags "$Tag" && die "$Tag: le tag correspondant à la version existe déjà" + fi +} + +function create_release_action() { + if [ -n "$ReleaseBranch" ]; then + Version="${ReleaseBranch#$RELEASE}" + Tag="$TAG_PREFIX$Version$TAG_SUFFIX" + merge_release_action "$@"; return $? + elif [ -n "$HotfixBranch" ]; then + Version="${HotfixBranch#$HOTFIX}" + Tag="$TAG_PREFIX$Version$TAG_SUFFIX" + merge_hotfix_action "$@"; return $? + fi + + [ -n "$ManualRelease" ] && ewarn "\ +L'option --no-merge a été forcée puisque ce dépôt ne supporte pas les releases automatiques" + [ -z "$ShouldPush" ] && enote "\ +L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine" + + if [ -z "$Version" -a -n "$CurrentVersion" -a -f VERSION.txt ]; then + Version="$( Date: Mon, 20 Oct 2025 17:52:21 +0400 Subject: [PATCH 78/91] maj du nom des branches --- bash/src/pman74.conf.sh | 2 +- bash/src/pman82.conf.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/src/pman74.conf.sh b/bash/src/pman74.conf.sh index 6796315..ab4453f 100644 --- a/bash/src/pman74.conf.sh +++ b/bash/src/pman74.conf.sh @@ -4,7 +4,7 @@ UPSTREAM= DEVELOP=dev74 FEATURE=wip74/ RELEASE=rel74- -MAIN=dist74 +MAIN=main74 TAG_PREFIX= TAG_SUFFIX=p74 HOTFIX=hotf74- diff --git a/bash/src/pman82.conf.sh b/bash/src/pman82.conf.sh index 4cf9e2b..b6393be 100644 --- a/bash/src/pman82.conf.sh +++ b/bash/src/pman82.conf.sh @@ -4,7 +4,7 @@ UPSTREAM=dev74 DEVELOP=dev82 FEATURE=wip82/ RELEASE=rel82- -MAIN=dist82 +MAIN=main82 TAG_PREFIX= TAG_SUFFIX=p82 HOTFIX=hotf82- From bf4d934daa88b8d0fa458df6838391a599a598f3 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 20 Oct 2025 20:55:35 +0400 Subject: [PATCH 79/91] migration prel --- bash/src/pman.sh | 231 +++++++++++++++++++++++++++++++++++------------ bin/prel | 71 ++++----------- bin/ptool | 162 +++------------------------------ 3 files changed, 203 insertions(+), 261 deletions(-) diff --git a/bash/src/pman.sh b/bash/src/pman.sh index 52f7ff3..fab38e2 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -36,11 +36,11 @@ PMAN_TOOL_PDEV=DEVELOP PMAN_TOOL_PWIP=FEATURE PMAN_TOOL_PMAIN=MAIN PMAN_TOOL_PDIST=DIST -UPSTREAM_BASE= ; UPSTREAM_MERGE_FROM= ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL= ; UPSTREAM_DELETE= -DEVELOP_BASE=MAIN ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN ; DEVELOP_PREL=to ; DEVELOP_DELETE=from -MAIN_BASE= ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=from ; MAIN_DELETE= -DIST_BASE=MAIN ; DIST_MERGE_FROM=MAIN ; DIST_MERGE_TO= ; DIST_PREL= ; DIST_DELETE= -FEATURE_BASE=DEVELOP ; FEATURE_MERGE_FROM= ; FEATURE_MERGE_TO=DEVELOP ; FEATURE_PREL= ; FEATURE_DELETE=to +UPSTREAM_BASE= ; UPSTREAM_MERGE_FROM= ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL= ; UPSTREAM_DELETE= +DEVELOP_BASE=MAIN ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN ; DEVELOP_PREL=from ; DEVELOP_DELETE=to +MAIN_BASE= ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=to ; MAIN_DELETE= +DIST_BASE=MAIN ; DIST_MERGE_FROM=MAIN ; DIST_MERGE_TO= ; DIST_PREL= ; DIST_DELETE= +FEATURE_BASE=DEVELOP ; FEATURE_MERGE_FROM= ; FEATURE_MERGE_TO=DEVELOP ; FEATURE_PREL= ; FEATURE_DELETE=from function get_base_branch() { # afficher la branche depuis laquelle créer la branche $1 @@ -112,8 +112,8 @@ function should_delete_merged() { * PMAN_MERGE_TO -- code de la branche destination dans laquelle la fusion est faite depuis PMAN_REF_BRANCH. vide si la branche n'a pas de destination * PMAN_DIR -- direction de la fusion: - 'from' si on fait PMAN_MERGE_FROM --> PMAN_REF_BRANCH - 'to' si on fait PMAN_REF_BRANCH --> PMAN_MERGE_TO + 'from' si on fait PMAN_REF_BRANCH --> PMAN_MERGE_TO + 'to' si on fait PMAN_MERGE_FROM --> PMAN_REF_BRANCH * PMAN_PREL_MERGE -- si la fusion devrait se faire avec prel * PMAN_DELETE_MERGED -- s'il faut supprimer la branche source après la fusion @@ -144,18 +144,16 @@ function set_pman_vars() { PMAN_MERGE_FROM=$(get_merge_from_branch "$PMAN_REF_BRANCH") PMAN_MERGE_TO=$(get_merge_to_branch "$PMAN_REF_BRANCH") if [ -n "$1" ]; then PMAN_DIR="$1" - else PMAN_DIR=to - #elif [ -n "$PMAN_MERGE_TO" ]; then PMAN_DIR=to - #else PMAN_DIR=from + else PMAN_DIR=from fi PMAN_PREL_MERGE=$(should_prel_merge "$PMAN_REF_BRANCH" "$PMAN_DIR" && echo 1) PMAN_DELETE_MERGED=$(should_delete_merged "$PMAN_REF_BRANCH" "$PMAN_DIR" && echo 1) case "$PMAN_DIR" in - to) + from) PMAN_MERGE_SRC="$PMAN_REF_BRANCH" PMAN_MERGE_DEST="$PMAN_MERGE_TO" ;; - from) + to) PMAN_MERGE_SRC="$PMAN_MERGE_FROM" PMAN_MERGE_DEST="$PMAN_REF_BRANCH" ;; @@ -216,7 +214,7 @@ $1 == "|" { } function _list_commits() { - local source="${1:-$SrcBranch}" dest="${2:-$DestBranch}" mergebase + local source="${1:-$PmanMergeSrc}" dest="${2:-$PmanMergeDest}" mergebase setx mergebase=git merge-base "$dest" "$source" git log --oneline --graph --no-decorate "$mergebase..$source" | grep -vF '|\' | grep -vF '|/' | sed -r 's/^(\| )+\* +/| /; s/^\* +/+ /' | @@ -224,7 +222,7 @@ function _list_commits() { } function _show_diff() { - local source="${1:-$SrcBranch}" dest="${2:-$DestBranch}" mergebase + local source="${1:-$PmanMergeSrc}" dest="${2:-$PmanMergeDest}" mergebase setx mergebase=git merge-base "$dest" "$source" git diff ${_sd_COLOR:+--color=$_sd_COLOR} "$mergebase..$source" } @@ -306,7 +304,7 @@ function ensure_gitdir() { } function load_branches() { - local what="${1:-all}"; shift + local branch what="${1:-all}"; shift case "$what" in all) [ -n "$Origin" ] || Origin=origin @@ -316,30 +314,6 @@ function load_branches() { setx -a AllBranches=git_list_pbranches "$Origin" ;; current) - SrcBranch="$1" - [ -n "$SrcBranch" ] || SrcBranch="$CurrentBranch" - case "$SrcBranch" in - "$UPSTREAM") SrcType=upstream; DestBranch="$DEVELOP";; - "$FEATURE"*) SrcType=feature; DestBranch="$DEVELOP";; - "$DEVELOP") SrcType=develop; DestBranch="$MAIN";; - "$RELEASE"*) SrcType=release; DestBranch="$MAIN";; - "$HOTFIX"*) SrcType=hotfix; DestBranch="$MAIN";; - "$MAIN") SrcType=main; DestBranch="$DIST";; - "$DIST") SrcType=dist; DestBranch=;; - *) SrcType=; DestBranch=;; - esac - case "$DestBranch" in - "$UPSTREAM") DestType=upstream;; - "$FEATURE"*) DestType=feature;; - "$DEVELOP") DestType=develop;; - "$RELEASE"*) DestType=release;; - "$HOTFIX"*) DestType=hotfix;; - "$MAIN") DestType=main;; - "$DIST") DestType=dist;; - *) DestType=;; - esac - - local branch UpstreamBranch= FeatureBranches=() DevelopBranch= @@ -469,10 +443,8 @@ function _mscript_start() { #!/bin/bash $(qvals source "$NULIBDIR/load.sh") || exit 1 -$(echo_setv SrcBranch="$SrcBranch") -$(echo_setv SrcType="$SrcType") -$(echo_setv DestBranch="$DestBranch") -$(echo_setv DestType="$DestType") +$(echo_setv SrcBranch="$PmanMergeSrc") +$(echo_setv DestBranch="$PmanMergeDest") merge= delete= @@ -492,32 +464,32 @@ function _mscript_merge_branch() { local msg # basculer sur la branche - _scripta "switch to branch $DestBranch" < $PmanMergeDest + +$commits +" + _sd_COLOR=always _show_diff + } | less -eRF + else + einfo "Commits à fusionner $PmanMergeSrc --> $PmanMergeDest" + eecho "$commits" + fi + fi +} + +function show_action() { + git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" + [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" + ensure_merge_branches + _show_action "$@" +} diff --git a/bin/prel b/bin/prel index 2ec3a31..4f9afe5 100755 --- a/bin/prel +++ b/bin/prel @@ -7,33 +7,7 @@ git_cleancheckout_DIRTY="\ Vous avez des modifications locales. Enregistrez ces modifications avant de créer une release" -function show_action() { - local commits - setx commits=_list_commits - if [ -n "$commits" ]; then - if [ $ShowLevel -ge 2 ]; then - { - echo "\ -# Commits à fusionner $SrcBranch --> $DestBranch - -$commits -" - _sd_COLOR=always _show_diff - } | less -eRF - else - einfo "Commits à fusionner $SrcBranch --> $DestBranch" - eecho "$commits" - fi - fi -} - -function ensure_branches() { - [ -n "$SrcBranch" -a -n "$DestBranch" ] || - die "$SrcBranch: Aucune configuration de fusion trouvée pour cette branche" - - array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche source introuvable" - array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche destination introuvable" - +function ensure_rel_infos() { Tag="$TAG_PREFIX$Version$TAG_SUFFIX" local -a tags setx -a tags=git tag -l "${TAG_PREFIX}*${TAG_SUFFIX}" @@ -71,14 +45,14 @@ L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine" if [ -n "$Merge" ]; then enote "\ Ce script va: -- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} +- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$PmanMergeSrc${COULEUR_NORMALE} - la provisionner avec une description des changements -- la fusionner dans la branche destination ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+ +- la fusionner dans la branche destination ${COULEUR_ROUGE}$PmanMergeDest${COULEUR_NORMALE}${Push:+ - pousser les branches modifiées}" else enote "\ Ce script va: -- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} +- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$PmanMergeSrc${COULEUR_NORMALE} - la provisionner avec une description des changements Vous devrez: - mettre à jour les informations de release puis relancer ce script" @@ -123,8 +97,8 @@ EOF $BEFORE_MERGE_RELEASE )$or_die EOF - _rscript_merge_release_branch "$DestBranch" "$Tag" - _rscript_merge_release_branch "$SrcBranch" + _rscript_merge_release_branch "$PmanMergeDest" "$Tag" + _rscript_merge_release_branch "$PmanMergeSrc" _rscript_delete_release_branch [ -n "$AFTER_MERGE_RELEASE" ] && _scripta < $PmanMergeDest - -$commits -" - _sd_COLOR=always _show_diff - } | less -eRF - else - einfo "Commits à fusionner $PmanMergeSrc --> $PmanMergeDest" - eecho "$commits" - fi - fi -} - -function show_action() { - git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" - [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" - ensure_merge_branches - _show_action "$@" -} - function _merge_action() { enote "\ Ce script va @@ -304,10 +164,10 @@ USAGE OPTIONS REF - -t, --merge-to REF + -f, --merge-from REF spécifier la branche de référence et indiquer que la fusion se fait dans le sens REF --> DEST. DEST est calculé en fonction de REF - -f, --merge-from REF + -t, --merge-to REF spécifier la branche de référence et indiquer que la fusion se fait dans le sens SRC --> REF. SRC est calculé en fonction de REF" fi @@ -317,14 +177,6 @@ OPTIONS [ -n "$ref" ] || die "vous spécifier la branche de référence" case "$ref" in - -t|--merge-to) - ref="$1"; shift - dir=to - ;; - -t*) - ref="${ref#-t}" - dir=to - ;; -f|--merge-from) ref="$1"; shift dir=from @@ -333,6 +185,14 @@ OPTIONS ref="${ref#-f}" dir=from ;; + -t|--merge-to) + ref="$1"; shift + dir=to + ;; + -t*) + ref="${ref#-t}" + dir=to + ;; esac PMAN_TOOL= PMAN_REF_BRANCH="${ref^^}" @@ -420,7 +280,7 @@ if [ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ]; then usage="${usage} $PMAN_REF_BRANCH --show|--merge" fi - [ "$PMAN_REF_BRANCH" != "$PMAN_MERGE_SRC" ] && bewareDir=" + [ "$PMAN_REF_BRANCH" != "$PMAN_MERGE_DEST" ] && bewareDir=" NB: la fusion se fait dans le sens inverse" || bewareDir= variables="\ Les variables supplémentaires suivantes peuvent être définies: From aa9b870159474fd3c90dbe2e7cb0a545eec0e204 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 20 Oct 2025 21:32:42 +0400 Subject: [PATCH 80/91] cosmetic --- bash/src/pman.sh | 219 +++++++++++++++++++++++------------------------ bin/prel | 16 ++-- bin/ptool | 110 ++++++++++++------------ 3 files changed, 170 insertions(+), 175 deletions(-) diff --git a/bash/src/pman.sh b/bash/src/pman.sh index fab38e2..4b3a83b 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -72,95 +72,93 @@ function get_merge_to_branch() { function should_prel_merge() { # tester si la branche $1 doit être mergée avec prel dans la direction # $2(=to) - local branch="$1" dir="${2:-to}" infos + local branch="$1" merge_dir="${2:-to}" infos [ -n "$branch" ] || return 1 infos="${branch^^}_PREL" - [ "${!infos}" == "$dir" ] + [ "${!infos}" == "$merge_dir" ] } function should_delete_merged() { # tester si la branche $1 doit être supprimée après avoir été mergée dans la # direction $2(=to) - local branch="$1" dir="${2:-to}" infos + local branch="$1" merge_dir="${2:-to}" infos [ -n "$branch" ] || return 1 infos="${branch^^}_DELETE" - [ "${!infos}" == "$dir" ] + [ "${!infos}" == "$merge_dir" ] } : " # description des variables # -* PMAN_TOOL -- nom de l'outil, e.g pdev, pmain, pdist - -* PMAN_REF_BRANCH -- code de la branche de référence basé sur le nom de l'outil -* PmanRefBranch -- nom effectif de la branche si elle est définie dans +* REF_BRANCH -- code de la branche de référence basé sur le nom de l'outil +* RefBranch -- nom effectif de la branche si elle est définie dans .pman.conf, vide sinon * IfRefBranch -- nom effectif de la branche *si elle existe*, vide sinon -* PMAN_UNIQUE -- si la branche de référence est unique. est vide pour les +* REF_UNIQUE -- si la branche de référence est unique. est vide pour les codes de branches multiples, telle que FEATURE -* PMAN_BASE_BRANCH -- branche de base à partir de laquelle créer la branche +* BASE_BRANCH -- branche de base à partir de laquelle créer la branche de référence -* PmanBaseBranch -- nom effectif de la branche de base si elle est définie +* BaseBranch -- nom effectif de la branche de base si elle est définie dans .pman.conf, vide sinon * IfBaseBranch -- nom effectif de la branche de base *si elle existe*, vide sinon -* PMAN_MERGE_FROM -- code de la branche source à partir de laquelle la fusion - est faite dans PMAN_REF_BRANCH. vide si la branche n'a pas de source -* PMAN_MERGE_TO -- code de la branche destination dans laquelle la fusion est - faite depuis PMAN_REF_BRANCH. vide si la branche n'a pas de destination -* PMAN_DIR -- direction de la fusion: - 'from' si on fait PMAN_REF_BRANCH --> PMAN_MERGE_TO - 'to' si on fait PMAN_MERGE_FROM --> PMAN_REF_BRANCH -* PMAN_PREL_MERGE -- si la fusion devrait se faire avec prel -* PMAN_DELETE_MERGED -- s'il faut supprimer la branche source après la fusion +* MERGE_FROM -- code de la branche source à partir de laquelle la fusion + est faite dans REF_BRANCH. vide si la branche n'a pas de source +* MERGE_TO -- code de la branche destination dans laquelle la fusion est + faite depuis REF_BRANCH. vide si la branche n'a pas de destination +* MERGE_DIR -- direction de la fusion: + 'from' si on fait REF_BRANCH --> MERGE_TO + 'to' si on fait MERGE_FROM --> REF_BRANCH +* PREL_MERGE -- si la fusion devrait se faire avec prel +* DELETE_MERGED -- s'il faut supprimer la branche source après la fusion -* PMAN_MERGE_SRC -- code de la branche source pour la fusion, ou vide si la +* MERGE_SRC -- code de la branche source pour la fusion, ou vide si la fusion n'est pas possible -* PmanMergeSrc -- nom effectif de la branche source si elle est définie +* MergeSrc -- nom effectif de la branche source si elle est définie dans .pman.conf * IfMergeSrc -- nom effectif de la branche source *si elle existe*, vide sinon -* PMAN_MERGE_DEST -- code de la branche destination pour la fusion, ou vide si +* MERGE_DEST -- code de la branche destination pour la fusion, ou vide si la fusion n'est pas possible -* PmanMergeDest -- nom effectif de la branche destination si elle est +* MergeDest -- nom effectif de la branche destination si elle est définie dans .pman.conf * IfMergeDest -- nom effectif de la branche source *si elle existe*, vide sinon" function set_pman_vars() { - PmanRefBranch="${!PMAN_REF_BRANCH}" - case "$PMAN_REF_BRANCH" in - FEATURE|RELEASE|HOTFIX) PMAN_UNIQUE=;; - *) PMAN_UNIQUE=1;; + RefBranch="${!REF_BRANCH}" + case "$REF_BRANCH" in + FEATURE|RELEASE|HOTFIX) REF_UNIQUE=;; + *) REF_UNIQUE=1;; esac - PMAN_BASE_BRANCH=$(get_base_branch "$PMAN_REF_BRANCH") - [ -n "$PMAN_BASE_BRANCH" ] && PmanBaseBranch="${!PMAN_BASE_BRANCH}" || PmanBaseBranch= + BASE_BRANCH=$(get_base_branch "$REF_BRANCH") + [ -n "$BASE_BRANCH" ] && BaseBranch="${!BASE_BRANCH}" || BaseBranch= - PMAN_MERGE_FROM=$(get_merge_from_branch "$PMAN_REF_BRANCH") - PMAN_MERGE_TO=$(get_merge_to_branch "$PMAN_REF_BRANCH") - if [ -n "$1" ]; then PMAN_DIR="$1" - else PMAN_DIR=from + MERGE_FROM=$(get_merge_from_branch "$REF_BRANCH") + MERGE_TO=$(get_merge_to_branch "$REF_BRANCH") + if [ -n "$1" ]; then MERGE_DIR="$1" + else MERGE_DIR=from fi - PMAN_PREL_MERGE=$(should_prel_merge "$PMAN_REF_BRANCH" "$PMAN_DIR" && echo 1) - PMAN_DELETE_MERGED=$(should_delete_merged "$PMAN_REF_BRANCH" "$PMAN_DIR" && echo 1) - case "$PMAN_DIR" in + PREL_MERGE=$(should_prel_merge "$REF_BRANCH" "$MERGE_DIR" && echo 1) + DELETE_MERGED=$(should_delete_merged "$REF_BRANCH" "$MERGE_DIR" && echo 1) + case "$MERGE_DIR" in from) - PMAN_MERGE_SRC="$PMAN_REF_BRANCH" - PMAN_MERGE_DEST="$PMAN_MERGE_TO" + MERGE_SRC="$REF_BRANCH" + MERGE_DEST="$MERGE_TO" ;; to) - PMAN_MERGE_SRC="$PMAN_MERGE_FROM" - PMAN_MERGE_DEST="$PMAN_REF_BRANCH" + MERGE_SRC="$MERGE_FROM" + MERGE_DEST="$REF_BRANCH" ;; esac - [ -n "$PMAN_MERGE_SRC" ] && PmanMergeSrc="${!PMAN_MERGE_SRC}" || PmanMergeSrc= - [ -n "$PMAN_MERGE_DEST" ] && PmanMergeDest="${!PMAN_MERGE_DEST}" || PmanMergeDest= + [ -n "$MERGE_SRC" ] && MergeSrc="${!MERGE_SRC}" || MergeSrc= + [ -n "$MERGE_DEST" ] && MergeDest="${!MERGE_DEST}" || MergeDest= } ################################################################################ @@ -214,7 +212,7 @@ $1 == "|" { } function _list_commits() { - local source="${1:-$PmanMergeSrc}" dest="${2:-$PmanMergeDest}" mergebase + local source="${1:-$MergeSrc}" dest="${2:-$MergeDest}" mergebase setx mergebase=git merge-base "$dest" "$source" git log --oneline --graph --no-decorate "$mergebase..$source" | grep -vF '|\' | grep -vF '|/' | sed -r 's/^(\| )+\* +/| /; s/^\* +/+ /' | @@ -222,7 +220,7 @@ function _list_commits() { } function _show_diff() { - local source="${1:-$PmanMergeSrc}" dest="${2:-$PmanMergeDest}" mergebase + local source="${1:-$MergeSrc}" dest="${2:-$MergeDest}" mergebase setx mergebase=git merge-base "$dest" "$source" git diff ${_sd_COLOR:+--color=$_sd_COLOR} "$mergebase..$source" } @@ -341,10 +339,10 @@ function load_branches() { elif [ -n "$DIST" -a "$branch" == "$DIST" ]; then DistBranch="$branch" fi - [ -n "$PmanRefBranch" -a "$branch" == "$PmanRefBranch" ] && IfRefBranch="$branch" - [ -n "$PmanBaseBranch" -a "$branch" == "$PmanBaseBranch" ] && IfBaseBranch="$branch" - [ -n "$PmanMergeSrc" -a "$branch" == "$PmanMergeSrc" ] && IfMergeSrc="$branch" - [ -n "$PmanMergeDest" -a "$branch" == "$PmanMergeDest" ] && IfMergeDest="$branch" + [ -n "$RefBranch" -a "$branch" == "$RefBranch" ] && IfRefBranch="$branch" + [ -n "$BaseBranch" -a "$branch" == "$BaseBranch" ] && IfBaseBranch="$branch" + [ -n "$MergeSrc" -a "$branch" == "$MergeSrc" ] && IfMergeSrc="$branch" + [ -n "$MergeDest" -a "$branch" == "$MergeDest" ] && IfMergeDest="$branch" done [ -n "$IfMergeSrc" -a "$IfMergeDest" ] && IfCanMerge=1 || IfCanMerge= ;; @@ -443,8 +441,8 @@ function _mscript_start() { #!/bin/bash $(qvals source "$NULIBDIR/load.sh") || exit 1 -$(echo_setv SrcBranch="$PmanMergeSrc") -$(echo_setv DestBranch="$PmanMergeDest") +$(echo_setv MergeSrc="$MergeSrc") +$(echo_setv MergeDest="$MergeDest") merge= delete= @@ -464,32 +462,32 @@ function _mscript_merge_branch() { local msg # basculer sur la branche - _scripta "switch to branch $PmanMergeDest" < $PmanMergeDest +# Commits à fusionner $MergeSrc --> $MergeDest $commits " _sd_COLOR=always _show_diff } | less -eRF else - einfo "Commits à fusionner $PmanMergeSrc --> $PmanMergeDest" + einfo "Commits à fusionner $MergeSrc --> $MergeDest" eecho "$commits" fi fi @@ -733,7 +730,7 @@ $commits function show_action() { git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" - [ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" + [ -n "$REF_UNIQUE" ] || resolve_unique_branch "$@" ensure_merge_branches _show_action "$@" } diff --git a/bin/prel b/bin/prel index 4f9afe5..8e1aadc 100755 --- a/bin/prel +++ b/bin/prel @@ -45,14 +45,14 @@ L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine" if [ -n "$Merge" ]; then enote "\ Ce script va: -- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$PmanMergeSrc${COULEUR_NORMALE} +- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MergeSrc${COULEUR_NORMALE} - la provisionner avec une description des changements -- la fusionner dans la branche destination ${COULEUR_ROUGE}$PmanMergeDest${COULEUR_NORMALE}${Push:+ +- la fusionner dans la branche destination ${COULEUR_ROUGE}$MergeDest${COULEUR_NORMALE}${Push:+ - pousser les branches modifiées}" else enote "\ Ce script va: -- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$PmanMergeSrc${COULEUR_NORMALE} +- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MergeSrc${COULEUR_NORMALE} - la provisionner avec une description des changements Vous devrez: - mettre à jour les informations de release puis relancer ce script" @@ -97,8 +97,8 @@ EOF $BEFORE_MERGE_RELEASE )$or_die EOF - _rscript_merge_release_branch "$PmanMergeDest" "$Tag" - _rscript_merge_release_branch "$PmanMergeSrc" + _rscript_merge_release_branch "$MergeDest" "$Tag" + _rscript_merge_release_branch "$MergeSrc" _rscript_delete_release_branch [ -n "$AFTER_MERGE_RELEASE" ] && _scripta < Date: Mon, 20 Oct 2025 21:45:13 +0400 Subject: [PATCH 81/91] modifs.mineures sans commentaires --- bin/ptool | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/ptool b/bin/ptool index aaa9290..4a0bbc9 100755 --- a/bin/ptool +++ b/bin/ptool @@ -195,7 +195,7 @@ OPTIONS ;; esac REF_BRANCH="${ref^^}" - array_contains BRANCHES "$REF_BRANCH" || die "$ref: invalid branch" + array_contains PMAN_BRANCHES "$REF_BRANCH" || die "$ref: invalid branch" else REF_BRANCH="PMAN_TOOL_${MYNAME^^}"; REF_BRANCH="${!REF_BRANCH}" From 04631b8657c5216a6726fa72f894fe5cb9b45248 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 21 Oct 2025 10:07:47 +0400 Subject: [PATCH 82/91] =?UTF-8?q?r=C3=A9organiser=20les=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/ptool | 154 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 79 insertions(+), 75 deletions(-) diff --git a/bin/ptool b/bin/ptool index 4a0bbc9..300c507 100755 --- a/bin/ptool +++ b/bin/ptool @@ -230,113 +230,99 @@ if [ -n "$MERGE_DEST" ]; then MergeDestDesc="$MergeDestDesc${COULEUR_NORMALE}" fi -[ -n "$REF_UNIQUE" ] && - purpose="gérer la branche $BranchDesc" || - purpose="gérer les branches $BranchDesc" +if [ -n "$REF_UNIQUE" ] +then purpose="gérer la branche $BranchDesc" +else purpose="gérer les branches $BranchDesc" +fi usage="--checkout" variables= -args=( - -d:,--chdir:BASEDIR chdir= "\ -répertoire dans lequel se placer avant de lancer les opérations" - -O:,--origin Origin= "++\ -origine à partir de laquelle les branches distantes sont considérées" - -B:,--config-branch ConfigBranch= "++\ -branche à partir de laquelle charger la configuration" - -c:,--config-file:CONFIG ConfigFile= "++\ + +chdir_def=(chdir= "répertoire dans lequel se placer avant de lancer les opérations") +origin_def=(Origin= "++origine à partir de laquelle les branches distantes sont considérées") +config_branch_def=(ConfigBranch= "++branche à partir de laquelle charger la configuration") +config_file_def=(ConfigFile= "++\ fichier de configuration des branches. le fichier .pman.conf dans le répertoire du dépôt est utilisé par défaut s'il existe. cette option est prioritaire sur ---config-branch" - --fake _Fake=1 "++option non documentée" - --keep-script _KeepScript=1 "++option non documentée" - --dump action=dump "++afficher les noms des branches" -) +--config-branch") +fake_def=(_Fake=1 "++option non documentée") +keep_script_def=(_KeepScript=1 "++option non documentée") +dump_action_def=(action=dump "++afficher les noms des branches") +checkout_action_def=('$:' "++non applicable") +show_action_def=('$:' "++non applicable") +rebase_action_def=('$:' "++non applicable") +merge_action_def=('$:' "++non applicable") +tech_merge_def=('$:' "++non applicable") +squash_def=('$:' "++non applicable") +force_merge_def=('$:' "++non applicable") +no_push_def=('$:' "++non applicable") +push_def=('$:' "++non applicable") +no_delete_def=('$:' "++non applicable") +delete_def=('$:' "++non applicable") +after_merge_def=('$:' "++non applicable") if [ -n "$RefBranch" -a -n "$REF_UNIQUE" ]; then - args+=( - --checkout action=checkout "++\ + checkout_action_def=(action=checkout "++\ créer le cas échéant la branche $BranchDesc et basculer vers elle. -c'est l'option par défaut" - ) +c'est l'option par défaut") elif [ -z "$REF_UNIQUE" ]; then - args+=( - --checkout action=checkout "\ + checkout_action_def=(action=checkout "\ créer le cas échéant la branche $BranchDesc et basculer vers elle. -c'est l'option par défaut" - ) +c'est l'option par défaut") else - args+=( - --checkout action=checkout "\ + checkout_action_def=(action=checkout "\ créer la branche $MergeDestDesc et basculer vers elle. -c'est l'option par défaut" - ) +c'est l'option par défaut") fi if [ -n "$MERGE_SRC" -a -n "$MERGE_DEST" ]; then - if [ -n "$REF_UNIQUE" ]; then - usage="${usage}|--show|--merge" - else - usage="${usage} $REF_BRANCH + if [ -n "$REF_UNIQUE" ] + then usage="${usage}|--show|--merge" + else usage="${usage} $REF_BRANCH --show|--merge" fi - [ "$REF_BRANCH" != "$MERGE_DEST" ] && bewareDir=" -NB: la fusion se fait dans le sens inverse" || bewareDir= - variables="\ -Les variables supplémentaires suivantes peuvent être définies: + if [ "$REF_BRANCH" != "$MERGE_SRC" ] + then bewareDir=" +NB: la fusion se fait dans le sens inverse" + else bewareDir= + fi + variables="Les variables supplémentaires suivantes peuvent être définies: BEFORE_MERGE_${MERGE_SRC} AFTER_MERGE_${MERGE_SRC}" - args+=( - -w,--show '$action=show; inc@ ShowLevel' "\ -lister ce qui serait fusionné dans la branche $MergeDestDesc" -# -b,--rebase action=rebase "\ + show_action_def=('$action=show; inc@ ShowLevel' "\ +lister ce qui serait fusionné dans la branche $MergeDestDesc") + rebase_action_def=('$:' "++non implémenté") +# rebase_action_def=(action=rebase "\ #lancer git rebase -i sur la branche $MergeSrcDesc. cela permet de réordonner -#les commits pour nettoyer l'historique avant la fusion" - -m,--merge action=merge "\ -fusionner la branche $MergeSrcDesc dans la branche $MergeDestDesc$bewareDir" - --tech-merge TechMerge=1 "++option non documentée" - -s:,--squash:COMMIT_MSG SquashMsg= "\ -fusionner les modifications de la branche comme un seul commit" - ) - - if [ -n "$PREL_MERGE" ]; then - args+=( - -f,--force-merge ForceMerge=1 "++\ -forcer la fusion pour une branche qui devrait être traitée par prel" - ) - fi - - args+=( - -n,--no-push Push= "\ -ne pas pousser les branches vers leur origine après la fusion" - --push Push=1 "++\ +#les commits pour nettoyer l'historique avant la fusion") + merge_action_def=(action=merge "\ +fusionner la branche $MergeSrcDesc dans la branche $MergeDestDesc$bewareDir") + tech_merge_def=(TechMerge=1 "++option non documentée") + squash_def=(SquashMsg= "fusionner les modifications de la branche comme un seul commit") + [ -n "$PREL_MERGE" ] && force_merge_def=(ForceMerge=1 "++\ +forcer la fusion pour une branche qui devrait être traitée par prel") + no_push_def=(Push= "ne pas pousser les branches vers leur origine après la fusion") + push_def=(Push=1 "++\ pousser les branches vers leur origine après la fusion. -c'est l'option par défaut" - ) +c'est l'option par défaut") if [ -n "$DELETE_MERGED" ]; then variables="${variables} AFTER_DELETE_${MERGE_SRC}" - args+=( - -k,--no-delete Delete= "\ + no_delete_def=(Delete= "\ ne pas supprimer la branche $MergeSrcDesc après la fusion dans la branche -$MergeDestDesc. cette option ne devrait pas être utilisée avec --squash" - --delete Delete=1 "++\ +$MergeDestDesc. cette option ne devrait pas être utilisée avec --squash") + delete_def=(Delete=1 "++\ supprimer la branche $MergeSrcDesc après la fusion dans la branche $MergeDestDesc. -c'est l'option par défaut" - ) +c'est l'option par défaut") fi - if [ -n "$MERGE_DEST" ]; then - variables="${variables} + [ -n "$MERGE_DEST" ] && variables="${variables} BEFORE_PUSH_${MERGE_DEST} AFTER_PUSH_${MERGE_DEST}" - fi - args+=( - -a:,--after-merge AfterMerge= "\ -évaluer le script spécifié après une fusion *réussie*" - ) + after_merge_def=(AfterMerge= "évaluer le script spécifié après une fusion *réussie*") fi chdir= @@ -361,7 +347,25 @@ CONFIGURATION Le fichier .pman.conf contient la configuration des branches. $variables" - "${args[@]}" + -d:,--chdir:BASEDIR "${chdir_def[@]}" + -O:,--origin "${origin_def[@]}" + -B:,--config-branch "${config_branch_def[@]}" + -c:,--config-file:CONFIG "${config_file_def[@]}" + --fake "${fake_def[@]}" + --keep-script "${keep_script_def[@]}" + --dump "${dump_action_def[@]}" + --checkout "${checkout_action_def[@]}" + -w,--show "${show_action_def[@]}" + -b,--rebase "${rebase_action_def[@]}" + -m,--merge "${merge_action_def[@]}" + --tech-merge "${tech_merge_def[@]}" + -s:,--squash:COMMIT_MSG "${squash_def[@]}" + -f,--force-merge "${force_merge_def[@]}" + -n,--no-push "${no_push_def[@]}" + --push "${push_def[@]}" + -k,--no-delete "${no_delete_def[@]}" + --delete "${delete_def[@]}" + -a:,--after-merge "${after_merge_def[@]}" ) parse_args "$@"; set -- "${args[@]}" From a0c5dac5046b148c4f527bc8bdab46c13c5ead4c Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 21 Oct 2025 10:10:07 +0400 Subject: [PATCH 83/91] support d13 --- bin/_runphp_build-all | 2 +- runphp/dot-build.env.dist | 2 +- runphp/dot-runphp.conf | 2 +- runphp/runphp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/_runphp_build-all b/bin/_runphp_build-all index c6d0f4f..457abc4 100755 --- a/bin/_runphp_build-all +++ b/bin/_runphp_build-all @@ -19,7 +19,7 @@ export RUNPHP_BUILD_FLAVOUR= runphp=("$MYDIR/../runphp/runphp" --bs) [ -z "$force" ] && runphp+=(--ue) -for RUNPHP_DIST in d12 d11; do +for RUNPHP_DIST in d13 d12 d11; do for RUNPHP_BUILD_FLAVOUR in +ic none; do flavour="$RUNPHP_BUILD_FLAVOUR" [ "$flavour" == none ] && flavour= diff --git a/runphp/dot-build.env.dist b/runphp/dot-build.env.dist index 7baed50..1d19470 100644 --- a/runphp/dot-build.env.dist +++ b/runphp/dot-build.env.dist @@ -15,7 +15,7 @@ PRIVAREG= # Ne pas toucher à partir d'ici REGISTRY=pubdocker.univ-reunion.fr/dist -DIST=d12 +DIST=d13 IMAGENAME=nulib/ #DEVUSER_USERENT=user:x:1000:1000:User,,,:/home/user:/bin/bash #DEVUSER_GROUPENT=user:x:1000: diff --git a/runphp/dot-runphp.conf b/runphp/dot-runphp.conf index 0c2f16a..1906caa 100644 --- a/runphp/dot-runphp.conf +++ b/runphp/dot-runphp.conf @@ -4,5 +4,5 @@ RUNPHP= # Si RUNPHP n'est pas défini, les variables suivantes peuvent être définies -#DIST=d12 +#DIST=d13 #REGISTRY=pubdocker.univ-reunion.fr/dist diff --git a/runphp/runphp b/runphp/runphp index 10486e3..f534d5a 100755 --- a/runphp/runphp +++ b/runphp/runphp @@ -41,7 +41,7 @@ BUILD_FLAVOUR= ## ici # version de debian à utiliser pour l'image -# d12=php8.2, d11=php7.4, d10=php7.3 +# d13=php8.4 d12=php8.2, d11=php7.4, d10=php7.3 DIST= # Nom de base de l'image (sans le registry), e.g prefix/ @@ -91,7 +91,7 @@ if [ -f "$MYDIR/runphp.userconf.local" ]; then source "$MYDIR/runphp.userconf.local" fi -DEFAULT_DIST=d12 +DEFAULT_DIST=d13 if [ -n "$RUNPHP_STANDALONE" ]; then PROJDIR="$RUNPHP_PROJDIR" From e3ac4e12a7b1693ed2b6d50250afa3b818a75864 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 21 Oct 2025 10:40:07 +0400 Subject: [PATCH 84/91] corriger support conf82 --- bash/src/pman.sh | 2 +- bin/_merge82 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bash/src/pman.sh b/bash/src/pman.sh index 4b3a83b..ddc7f3d 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -38,7 +38,7 @@ PMAN_TOOL_PMAIN=MAIN PMAN_TOOL_PDIST=DIST UPSTREAM_BASE= ; UPSTREAM_MERGE_FROM= ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL= ; UPSTREAM_DELETE= DEVELOP_BASE=MAIN ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN ; DEVELOP_PREL=from ; DEVELOP_DELETE=to -MAIN_BASE= ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=to ; MAIN_DELETE= +MAIN_BASE=DEVELOP ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=to ; MAIN_DELETE= DIST_BASE=MAIN ; DIST_MERGE_FROM=MAIN ; DIST_MERGE_TO= ; DIST_PREL= ; DIST_DELETE= FEATURE_BASE=DEVELOP ; FEATURE_MERGE_FROM= ; FEATURE_MERGE_TO=DEVELOP ; FEATURE_PREL= ; FEATURE_DELETE=from diff --git a/bin/_merge82 b/bin/_merge82 index ac4a192..7a4cac4 100755 --- a/bin/_merge82 +++ b/bin/_merge82 @@ -9,4 +9,4 @@ args=( ) parse_args "$@"; set -- "${args[@]}" -exec "$MYDIR/ptool" upstream -Bdev82 -m --tech-merge ${dev74:+-a "git checkout dev74"} "$@" +exec "$MYDIR/ptool" -fupstream -Bdev82 -m --tech-merge ${dev74:+-a "git checkout dev74"} "$@" From 54c943c0e3e4888ae8e9afc13165be5e5f8bb946 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 21 Oct 2025 10:40:16 +0400 Subject: [PATCH 85/91] ajouter conf84 --- bash/src/pman84.conf.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 bash/src/pman84.conf.sh diff --git a/bash/src/pman84.conf.sh b/bash/src/pman84.conf.sh new file mode 100644 index 0000000..63590c4 --- /dev/null +++ b/bash/src/pman84.conf.sh @@ -0,0 +1,12 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +UPSTREAM=dev74 +DEVELOP=dev84 +FEATURE=wip84/ +RELEASE=rel84- +MAIN=main84 +TAG_PREFIX= +TAG_SUFFIX=p84 +HOTFIX=hotf84- +DIST= +NOAUTO= From cf867765d1f7e32f63b80b9bef35f849a819e465 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 21 Oct 2025 11:05:07 +0400 Subject: [PATCH 86/91] finaliser ptool --- bash/src/pman.sh | 61 +++++++++++++++++++++++++++++++++++++----------- bin/ptool | 43 +++++++++++++++++----------------- 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/bash/src/pman.sh b/bash/src/pman.sh index ddc7f3d..41e2da9 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -36,11 +36,13 @@ PMAN_TOOL_PDEV=DEVELOP PMAN_TOOL_PWIP=FEATURE PMAN_TOOL_PMAIN=MAIN PMAN_TOOL_PDIST=DIST -UPSTREAM_BASE= ; UPSTREAM_MERGE_FROM= ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL= ; UPSTREAM_DELETE= -DEVELOP_BASE=MAIN ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN ; DEVELOP_PREL=from ; DEVELOP_DELETE=to -MAIN_BASE=DEVELOP ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=to ; MAIN_DELETE= -DIST_BASE=MAIN ; DIST_MERGE_FROM=MAIN ; DIST_MERGE_TO= ; DIST_PREL= ; DIST_DELETE= -FEATURE_BASE=DEVELOP ; FEATURE_MERGE_FROM= ; FEATURE_MERGE_TO=DEVELOP ; FEATURE_PREL= ; FEATURE_DELETE=from +UPSTREAM_BASE=DEVELOP ; UPSTREAM_MERGE_FROM= ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL= ; UPSTREAM_DELETE= +DEVELOP_BASE=MAIN ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN ; DEVELOP_PREL=from ; DEVELOP_DELETE=to +MAIN_BASE=DEVELOP ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=to ; MAIN_DELETE= +DIST_BASE=MAIN ; DIST_MERGE_FROM=MAIN ; DIST_MERGE_TO= ; DIST_PREL= ; DIST_DELETE= +FEATURE_BASE=DEVELOP ; FEATURE_MERGE_FROM= ; FEATURE_MERGE_TO=DEVELOP ; FEATURE_PREL= ; FEATURE_DELETE=from + +UPSTREAM_CREATE_FUNCTION=_create_upstream_action function get_base_branch() { # afficher la branche depuis laquelle créer la branche $1 @@ -664,15 +666,48 @@ Veuillez éditer le fichier .pman.conf" [ "$1" == init -o -n "$IfBaseBranch" ] || die "$BaseBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" } +function _create_default_action() { + enote "Vous allez créer la branche ${COULEUR_BLEUE}$RefBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$BaseBranch${COULEUR_NORMALE}" + ask_yesno "Voulez-vous continuer?" O || die + + einfo "Création de la branche $RefBranch" + git checkout -b "$RefBranch" "$BaseBranch" || die + push_branches+=("$RefBranch") +} + +function _create_upstream_action() { + enote "Vous allez créer la branche ${COULEUR_BLEUE}$RefBranch${COULEUR_NORMALE}" + ask_yesno "Voulez-vous continuer?" O || die + + # faire une copie de la configuration actuelle + local config; ac_set_tmpfile config + set -x; ls -l "$ConfigFile" #XXX + cp "$ConfigFile" "$config" + set +x #XXX + + einfo "Création de la branche $RefBranch" + git checkout --orphan "$RefBranch" || die + git rm -rf . + cp "$config" .pman.conf + git add .pman.conf + git commit -m "commit initial" + push_branches+=("$RefBranch") + + einfo "Fusion dans $DevelopBranch" + git checkout "$DevelopBranch" + git merge \ + --no-ff -m "Intégration initiale de la branche $RefBranch" \ + -srecursive -Xours --allow-unrelated-histories \ + "$RefBranch" + push_branches+=("$DevelopBranch") +} + function checkout_action() { local -a push_branches [ -n "$REF_UNIQUE" ] || resolve_unique_branch "$@" _ensure_ref_branch init - #if [ -n "$IfRefBranch" ]; then - # git checkout "$IfRefBranch" - #el if array_contains LocalBranches "$RefBranch"; then git checkout "$RefBranch" elif array_contains AllBranches "$RefBranch"; then @@ -683,12 +718,10 @@ function checkout_action() { _ensure_base_branch resolve_should_push - enote "Vous allez créer la branche ${COULEUR_BLEUE}$RefBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$BaseBranch${COULEUR_NORMALE}" - ask_yesno "Voulez-vous continuer?" O || die - - einfo "Création de la branche $RefBranch" - git checkout -b "$RefBranch" "$BaseBranch" || die - push_branches+=("$RefBranch") + local create_function + create_function="${REF_BRANCH}_CREATE_FUNCTION"; create_function="${!create_function}" + [ -n "$create_function" ] || create_function=_create_default_action + "$create_function" _push_branches fi diff --git a/bin/ptool b/bin/ptool index 300c507..44d907b 100755 --- a/bin/ptool +++ b/bin/ptool @@ -153,6 +153,20 @@ function rebase_action() { ################################################################################ +chdir= +Origin= +ConfigBranch= +ConfigFile= +_Fake= +_KeepScript= +action=checkout +ShowLevel=0 +TechMerge= +SquashMsg= +Push=1 +Delete=1 +AfterMerge= + loaded_config= merge_dir= if [ "$MYNAME" == ptool ]; then @@ -211,13 +225,13 @@ else set_pman_vars "$merge_dir" fi -BranchDesc= +RefDesc= MergeSrcDesc= MergeDestDesc= if [ -n "$REF_BRANCH" ]; then - BranchDesc="${COULEUR_BLANCHE}<$REF_BRANCH>" - [ -n "$RefBranch" -a -n "$REF_UNIQUE" ] && BranchDesc="$BranchDesc ($RefBranch)" - BranchDesc="$BranchDesc${COULEUR_NORMALE}" + RefDesc="${COULEUR_BLANCHE}<$REF_BRANCH>" + [ -n "$RefBranch" -a -n "$REF_UNIQUE" ] && RefDesc="$RefDesc ($RefBranch)" + RefDesc="$RefDesc${COULEUR_NORMALE}" fi if [ -n "$MERGE_SRC" ]; then MergeSrcDesc="${COULEUR_BLEUE}<$MERGE_SRC>" @@ -231,8 +245,8 @@ if [ -n "$MERGE_DEST" ]; then fi if [ -n "$REF_UNIQUE" ] -then purpose="gérer la branche $BranchDesc" -else purpose="gérer les branches $BranchDesc" +then purpose="gérer la branche $RefDesc" +else purpose="gérer les branches $RefDesc" fi usage="--checkout" variables= @@ -262,11 +276,11 @@ after_merge_def=('$:' "++non applicable") if [ -n "$RefBranch" -a -n "$REF_UNIQUE" ]; then checkout_action_def=(action=checkout "++\ -créer le cas échéant la branche $BranchDesc et basculer vers elle. +créer le cas échéant la branche $RefDesc et basculer vers elle. c'est l'option par défaut") elif [ -z "$REF_UNIQUE" ]; then checkout_action_def=(action=checkout "\ -créer le cas échéant la branche $BranchDesc et basculer vers elle. +créer le cas échéant la branche $RefDesc et basculer vers elle. c'est l'option par défaut") else checkout_action_def=(action=checkout "\ @@ -325,19 +339,6 @@ c'est l'option par défaut") after_merge_def=(AfterMerge= "évaluer le script spécifié après une fusion *réussie*") fi -chdir= -Origin= -ConfigBranch= -ConfigFile= -_Fake= -_KeepScript= -action=checkout -ShowLevel=0 -TechMerge= -SquashMsg= -Push=1 -Delete=1 -AfterMerge= args=( "$purpose" "\ From 80475f044c9bbd875ba4908ef9e2e78a2c8770e7 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 21 Oct 2025 12:26:04 +0400 Subject: [PATCH 87/91] =?UTF-8?q?r=C3=A9organiser=20les=20classes=20de=20r?= =?UTF-8?q?ef?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/src/php/types/varray.php | 2 +- php/src/ref/{schema => }/ref_analyze.php | 2 +- php/src/ref/{cli => }/ref_args.php | 2 +- php/src/ref/{schema => }/ref_input.php | 2 +- php/src/ref/{schema => }/ref_schema.php | 2 +- php/src/ref/ref_types.php | 27 ++++++++++++++++++++++++ php/src/ref/schema/ref_types.php | 11 ---------- 7 files changed, 32 insertions(+), 16 deletions(-) rename php/src/ref/{schema => }/ref_analyze.php (95%) rename php/src/ref/{cli => }/ref_args.php (99%) rename php/src/ref/{schema => }/ref_input.php (98%) rename php/src/ref/{schema => }/ref_schema.php (99%) create mode 100644 php/src/ref/ref_types.php delete mode 100644 php/src/ref/schema/ref_types.php diff --git a/php/src/php/types/varray.php b/php/src/php/types/varray.php index bc46388..3a6b416 100644 --- a/php/src/php/types/varray.php +++ b/php/src/php/types/varray.php @@ -9,7 +9,7 @@ class varray { } static function ensuren(&$array): void { - if ($array !== null) varray::ensure($array); + if ($array !== null) self::ensure($array); } static function with($value): array { diff --git a/php/src/ref/schema/ref_analyze.php b/php/src/ref/ref_analyze.php similarity index 95% rename from php/src/ref/schema/ref_analyze.php rename to php/src/ref/ref_analyze.php index 060d68b..24dff0c 100644 --- a/php/src/ref/schema/ref_analyze.php +++ b/php/src/ref/ref_analyze.php @@ -1,5 +1,5 @@ "bool", + "integer" => "int", + "flt" => "float", "double" => "float", "dbl" => "float", + "function" => "func", "callable" => "func", + ]; +} diff --git a/php/src/ref/schema/ref_types.php b/php/src/ref/schema/ref_types.php deleted file mode 100644 index d7ce1d4..0000000 --- a/php/src/ref/schema/ref_types.php +++ /dev/null @@ -1,11 +0,0 @@ - "bool", - "integer" => "int", - "flt" => "float", "double" => "float", "dbl" => "float", - "func" => "callable", "function" => "callable", - ]; -} From fae12bf48e757c0c3b52f35eb31114769a114fc2 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 21 Oct 2025 14:57:00 +0400 Subject: [PATCH 88/91] modifs.mineures sans commentaires --- {wip => bin}/pinit | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {wip => bin}/pinit (100%) diff --git a/wip/pinit b/bin/pinit similarity index 100% rename from wip/pinit rename to bin/pinit From af8127c9159f1659987122e189ad0d1fb97bfdbb Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 22 Oct 2025 06:22:48 +0400 Subject: [PATCH 89/91] maj upstream --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f17024c..8fb77f5 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "symfony/yaml": "^5.0", "phpmailer/phpmailer": "^6.8", "symfony/expression-language": "^5.4", - "league/commonmark": "^2.4", + "league/commonmark": "^2.7", "ext-json": "*", "php": "^7.4" }, From 98b2b94a1581242aa45058110f52d4ee3585965d Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 22 Oct 2025 07:00:54 +0400 Subject: [PATCH 90/91] --tech-merge et --squash impliquement --merge --- bin/ptool | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/ptool b/bin/ptool index 44d907b..c1cd363 100755 --- a/bin/ptool +++ b/bin/ptool @@ -311,8 +311,8 @@ lister ce qui serait fusionné dans la branche $MergeDestDesc") #les commits pour nettoyer l'historique avant la fusion") merge_action_def=(action=merge "\ fusionner la branche $MergeSrcDesc dans la branche $MergeDestDesc$bewareDir") - tech_merge_def=(TechMerge=1 "++option non documentée") - squash_def=(SquashMsg= "fusionner les modifications de la branche comme un seul commit") + tech_merge_def=('$action=merge; TechMerge=1' "++option non documentée") + squash_def=('$action=merge; res@ SquashMsg' "fusionner les modifications de la branche comme un seul commit") [ -n "$PREL_MERGE" ] && force_merge_def=(ForceMerge=1 "++\ forcer la fusion pour une branche qui devrait être traitée par prel") no_push_def=(Push= "ne pas pousser les branches vers leur origine après la fusion") From 68b95ff5f22bfcc8d887753e5cc8ae1f84f27d4b Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 22 Oct 2025 07:30:57 +0400 Subject: [PATCH 91/91] changer la branche master --- .pman.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pman.conf b/.pman.conf index ba5edad..8301d75 100644 --- a/.pman.conf +++ b/.pman.conf @@ -4,7 +4,7 @@ UPSTREAM= DEVELOP=dev74 FEATURE=wip74/ RELEASE=rel74- -MAIN=dist74 +MAIN=main74 TAG_SUFFIX=p74 HOTFIX=hotf74- DIST=