Compare commits
43 Commits
wip74/upda
...
dev74
Author | SHA1 | Date | |
---|---|---|---|
e99716735c | |||
39abe09f11 | |||
e4e6a98be2 | |||
0b01946090 | |||
bda3cff3d3 | |||
2f3a21aad4 | |||
24efdddb68 | |||
27eb08ecff | |||
5eb376257f | |||
7d332552ab | |||
cf5ef38a0f | |||
a0c6fb21e6 | |||
48d5f84bbd | |||
55728059cf | |||
51215b42eb | |||
20e64b8ffb | |||
c62de542c3 | |||
394edb782e | |||
5da09a039b | |||
59e2abee61 | |||
5035d5522a | |||
8974cf09a1 | |||
c37748b6f4 | |||
d52ab4f5a1 | |||
23fe2859b5 | |||
42992c84d4 | |||
cf30ff6386 | |||
75c06e7038 | |||
ee058e00cd | |||
c748fed388 | |||
810ead58d6 | |||
95b0263969 | |||
c1c369f554 | |||
90ca62984d | |||
f55c66e1f3 | |||
526a693ead | |||
ff4ef34037 | |||
f52da16f44 | |||
d8b70d7ee0 | |||
291db941b9 | |||
651ba8c553 | |||
6cedfe9493 | |||
014825f09d |
15
.idea/php-docker-settings.xml
generated
15
.idea/php-docker-settings.xml
generated
@ -17,6 +17,21 @@
|
||||
</DockerContainerSettings>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="385aa179-8c50-45e2-9ad7-4d9bfba298a3">
|
||||
<value>
|
||||
<DockerContainerSettings>
|
||||
<option name="version" value="1" />
|
||||
<option name="volumeBindings">
|
||||
<list>
|
||||
<DockerVolumeBindingImpl>
|
||||
<option name="containerPath" value="/opt/project" />
|
||||
<option name="hostPath" value="$PROJECT_DIR$" />
|
||||
</DockerVolumeBindingImpl>
|
||||
</list>
|
||||
</option>
|
||||
</DockerContainerSettings>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="38915385-b3ff-4f4b-8a9a-d5f3ecae559e">
|
||||
<value>
|
||||
<DockerContainerSettings>
|
||||
|
4
.idea/php.xml
generated
4
.idea/php.xml
generated
@ -2,7 +2,7 @@
|
||||
<project version="4">
|
||||
<component name="MessDetector">
|
||||
<phpmd_settings>
|
||||
<phpmd_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" />
|
||||
<phpmd_by_interpreter asDefaultInterpreter="true" interpreter_id="38915385-b3ff-4f4b-8a9a-d5f3ecae559e" timeout="30000" />
|
||||
</phpmd_settings>
|
||||
</component>
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
@ -17,7 +17,7 @@
|
||||
</component>
|
||||
<component name="PhpCodeSniffer">
|
||||
<phpcs_settings>
|
||||
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" />
|
||||
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="38915385-b3ff-4f4b-8a9a-d5f3ecae559e" timeout="30000" />
|
||||
</phpcs_settings>
|
||||
</component>
|
||||
<component name="PhpIncludePathManager">
|
||||
|
183
bash/src/pman.sh
183
bash/src/pman.sh
@ -28,6 +28,152 @@ 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_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_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^^}_BASE"; branch="${!infos}"
|
||||
[ -n "$branch" ] && echo "$branch" || return 1
|
||||
}
|
||||
|
||||
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
|
||||
[ -n "$branch" ] || return 1
|
||||
infos="${branch^^}_MERGE_FROM"; branch="${!infos}"
|
||||
[ -n "$branch" ] && echo "$branch" || return 1
|
||||
}
|
||||
|
||||
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
|
||||
[ -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" ]
|
||||
}
|
||||
|
||||
: "
|
||||
# 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
|
||||
@ -147,18 +293,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
|
||||
git_check_gitvcs || return 1
|
||||
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 1
|
||||
}
|
||||
|
||||
function load_branches() {
|
||||
@ -203,23 +354,32 @@ function load_branches() {
|
||||
HotfixBranch=
|
||||
MainBranch=
|
||||
DistBranch=
|
||||
IfRefBranch=
|
||||
IfBaseBranch=
|
||||
IfMergeSrc=
|
||||
IfMergeDest=
|
||||
for branch in "${LocalBranches[@]}"; do
|
||||
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
|
||||
[ -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
|
||||
}
|
||||
@ -244,9 +404,6 @@ function load_config() {
|
||||
elif [ -f .pman.conf ]; then
|
||||
ConfigFile="$(pwd)/.pman.conf"
|
||||
source "$ConfigFile"
|
||||
elif [ -n "$1" -a -n "${MYNAME#$1}" ]; then
|
||||
ConfigFile="$NULIBDIR/bash/src/pman${MYNAME#$1}.conf.sh"
|
||||
source "$ConfigFile"
|
||||
else
|
||||
ConfigFile="$NULIBDIR/bash/src/pman.conf.sh"
|
||||
fi
|
||||
|
299
bash/src/pman.tool.sh
Normal file
299
bash/src/pman.tool.sh
Normal file
@ -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 <<EOF
|
||||
################################################################################
|
||||
# merge
|
||||
if [ -n "\$merge" ]; then
|
||||
esection "Fusionner la branche"
|
||||
EOF
|
||||
hook="BEFORE_MERGE_$PMAN_MERGE_SRC"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_mscript_merge_branch
|
||||
hook="AFTER_MERGE_$PMAN_MERGE_SRC"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
|
||||
if [ -n "$ShouldDelete" ]; then
|
||||
_scripta <<EOF
|
||||
################################################################################
|
||||
# delete
|
||||
if [ -n "\$delete" ]; then
|
||||
esection "Supprimer la branche"
|
||||
EOF
|
||||
_mscript_delete_branch
|
||||
hook="AFTER_DELETE_$PMAN_MERGE_SRC"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
fi
|
||||
|
||||
_scripta <<EOF
|
||||
################################################################################
|
||||
# push
|
||||
if [ -n "\$push" ]; then
|
||||
esection "Pousser les branches"
|
||||
EOF
|
||||
hook="BEFORE_PUSH_$PMAN_MERGE_DEST"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_script_push_branches
|
||||
if [ ${#delete_branches[*]} -gt 0 ]; then
|
||||
_scripta <<<"if [ -n \"\$delete\" ]; then"
|
||||
push_branches=("${delete_branches[@]}")
|
||||
_script_push_branches
|
||||
_scripta <<<fi
|
||||
fi
|
||||
hook="AFTER_PUSH_$PMAN_MERGE_DEST"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
|
||||
[ -n "$Delete" -o -z "$ShouldDelete" ] && Deleted=1 || Deleted=
|
||||
[ -n "$ShouldDelete" -a -n "$Delete" ] && ShouldDelete=
|
||||
[ -n "$ShouldPush" -a -n "$Push" ] && ShouldPush=
|
||||
if [ -n "$_Fake" ]; then
|
||||
cat "$script"
|
||||
elif ! "$script" merge ${Delete:+delete} ${Push:+push}; then
|
||||
eimportant "\
|
||||
Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
|
||||
En cas d'erreur de merge, veuillez corriger les erreurs puis continuer avec
|
||||
git merge --continue
|
||||
Sinon, veuillez consulter le script et/ou le relancer
|
||||
./$script${Delete:+ delete}${Push:+ push}"
|
||||
die
|
||||
elif [ -n "$Deleted" -a -n "$Push" ]; then
|
||||
[ -n "$_KeepScript" ] || rm "$script"
|
||||
[ -n "$AfterMerge" ] && eval "$AfterMerge"
|
||||
else
|
||||
local msg="\
|
||||
Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
|
||||
Vous pouvez consulter le script et/ou le relancer
|
||||
./$script${ShouldDelete:+ delete}${ShouldPush:+ push}"
|
||||
[ -n "$AfterMerge" ] && msg="$msg
|
||||
Il y a aussi les commandes supplémentaires suivantes:
|
||||
${AfterMerge//
|
||||
/
|
||||
}"
|
||||
einfo "$msg"
|
||||
fi
|
||||
}
|
||||
|
||||
function merge_action() {
|
||||
[ -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"
|
||||
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"
|
||||
fi
|
||||
if ! array_contains LocalBranches "$DestBranch" && array_contains AllBranches "$DestBranch"; then
|
||||
enote "$DestBranch: 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"
|
||||
|
||||
resolve_should_push
|
||||
set -x #XXX
|
||||
_merge_action "$@"
|
||||
}
|
||||
|
||||
function rebase_action() {
|
||||
die "non implémenté"
|
||||
}
|
||||
|
1
bin/.cachectl.php
Symbolic link
1
bin/.cachectl.php
Symbolic link
@ -0,0 +1 @@
|
||||
../php/bin/cachectl.php
|
1
bin/cachectl.php
Symbolic link
1
bin/cachectl.php
Symbolic link
@ -0,0 +1 @@
|
||||
runphp
|
@ -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")
|
||||
|
@ -19,6 +19,7 @@ while true; do
|
||||
fi
|
||||
cd ..
|
||||
done
|
||||
cd "$owd"
|
||||
|
||||
export RUNPHP_MOUNT=
|
||||
if [ "$MYNAME" == composer ]; then
|
||||
|
@ -48,6 +48,7 @@
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"php/bin/cachectl.php",
|
||||
"php/bin/dumpser.php",
|
||||
"php/bin/json2yml.php",
|
||||
"php/bin/yml2json.php",
|
||||
|
7
php/bin/cachectl.php
Executable file
7
php/bin/cachectl.php
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use cli\CachectlApp;
|
||||
|
||||
CachectlApp::run();
|
132
php/cli/CachectlApp.php
Normal file
132
php/cli/CachectlApp.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
namespace cli;
|
||||
|
||||
use Exception;
|
||||
use nulib\app\cli\Application;
|
||||
use nulib\cache\CacheFile;
|
||||
use nulib\ext\yaml;
|
||||
use nulib\os\path;
|
||||
use nulib\output\msg;
|
||||
|
||||
class CachectlApp extends Application {
|
||||
const ACTION_READ = 10, ACTION_INFOS = 20, ACTION_CLEAN = 30;
|
||||
const ACTION_UPDATE = 40, ACTION_UPDATE_ADD = 41, ACTION_UPDATE_SUB = 42, ACTION_UPDATE_SET = 43;
|
||||
|
||||
const ARGS = [
|
||||
"merge" => parent::ARGS,
|
||||
"purpose" => "gestion de fichiers cache",
|
||||
["-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",
|
||||
],
|
||||
["-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;
|
||||
|
||||
protected $data = null;
|
||||
|
||||
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($this->data));
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -1,36 +1,38 @@
|
||||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class AccessException: indiquer que la resource ou l'objet auquel on veut
|
||||
* accéder n'est pas accessible. il s'agit donc d'une erreur de l'utilisateur
|
||||
*/
|
||||
class AccessException extends UserException {
|
||||
class AccessException extends RuntimeException {
|
||||
static final function read_only(?string $dest=null, ?string $prefix=null): self {
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
if ($dest === null) $dest = "this property";
|
||||
$message = "$dest is read-only";
|
||||
return new static($prefix.$message);
|
||||
return new static("$prefix$message");
|
||||
}
|
||||
|
||||
static final function immutable_object(?string $dest=null, ?string $prefix=null): self {
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
if ($dest === null) $dest = "this object";
|
||||
$message = "$dest is immutable";
|
||||
return new static($prefix.$message);
|
||||
return new static("$prefix$message");
|
||||
}
|
||||
|
||||
static final function not_allowed(?string $action=null, ?string $prefix=null): self {
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
if ($action === null) $action = "this operation";
|
||||
$message = "$action is not allowed";
|
||||
return new static($prefix.$message);
|
||||
return new static("$prefix$message");
|
||||
}
|
||||
|
||||
static final function not_accessible(?string $dest=null, ?string $prefix=null): self {
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
if ($dest === null) $dest = "this resource";
|
||||
$message = "$dest is not accessible";
|
||||
return new static($prefix.$message);
|
||||
return new static("$prefix$message");
|
||||
}
|
||||
}
|
||||
|
@ -38,17 +38,22 @@ class ExceptionShadow {
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -1,90 +1,35 @@
|
||||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use nulib\php\content\c;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class UserException: une exception qui peut en plus contenir un message
|
||||
* utilisateur
|
||||
* Class UserException: une exception qui peut contenir un message utilisateur
|
||||
* et un message technique
|
||||
*/
|
||||
class UserException extends RuntimeException {
|
||||
/** @param Throwable|ExceptionShadow $e */
|
||||
static function get_user_message($e): ?string {
|
||||
if ($e instanceof self) return $e->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 {
|
||||
if ($techMessage !== null) $techMessage = c::resolve($techMessage);
|
||||
$this->techMessage = $techMessage;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
@ -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()`
|
||||
|
||||
|
@ -5,12 +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 {
|
||||
@ -35,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,
|
||||
@ -50,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"),
|
||||
@ -58,7 +61,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;
|
||||
}
|
||||
@ -83,6 +86,7 @@ class app {
|
||||
"datadir",
|
||||
"etcdir",
|
||||
"vardir",
|
||||
"cachedir",
|
||||
"logdir",
|
||||
"profile",
|
||||
"facts",
|
||||
@ -114,13 +118,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 {
|
||||
@ -137,7 +149,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();
|
||||
}
|
||||
@ -163,6 +175,7 @@ class app {
|
||||
"datadir" => $datadir,
|
||||
"etcdir" => $etcdir,
|
||||
"vardir" => $vardir,
|
||||
"cachedir" => $cachedir,
|
||||
"logdir" => $logdir,
|
||||
] = $params;
|
||||
$cwd = $params["cwd"] ?? null;
|
||||
@ -214,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;
|
||||
@ -241,6 +259,7 @@ class app {
|
||||
$this->datadir = $datadir;
|
||||
$this->etcdir = $etcdir;
|
||||
$this->vardir = $vardir;
|
||||
$this->cachedir = $cachedir;
|
||||
$this->logdir = $logdir;
|
||||
|
||||
# name, title
|
||||
@ -310,6 +329,12 @@ class app {
|
||||
return $this->vardir;
|
||||
}
|
||||
|
||||
protected string $cachedir;
|
||||
|
||||
function getCachedir(): string {
|
||||
return $this->cachedir;
|
||||
}
|
||||
|
||||
protected string $logdir;
|
||||
|
||||
function getLogdir(): string {
|
||||
@ -401,7 +426,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_value($path, "path");
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
@ -440,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,
|
||||
@ -455,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);
|
||||
}
|
||||
|
||||
@ -464,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
|
||||
@ -484,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,7 +366,7 @@ class Aodef {
|
||||
$haveNull = true;
|
||||
break;
|
||||
} else {
|
||||
throw ArgsException::invalid("$desc: $arg", "option arg");
|
||||
throw _exceptions::invalid_value("$desc: $arg");
|
||||
}
|
||||
}
|
||||
if (!$haveOpt) $haveNull = true;
|
||||
@ -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";
|
||||
@ -514,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, null, "action non supportée");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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, null, "ce n'est pas un groupe valide");
|
||||
}
|
||||
# réordonner les clés numériques
|
||||
$defs = array_merge($defs);
|
||||
|
@ -1,20 +1,7 @@
|
||||
<?php
|
||||
namespace nulib\app\args;
|
||||
|
||||
use nulib\ValueException;
|
||||
use nulib\UserException;
|
||||
|
||||
class ArgsException extends ValueException {
|
||||
static function missing(?string $value, string $kind): self {
|
||||
$msg = $value;
|
||||
if ($msg !== null) $msg .= ": ";
|
||||
$msg .= "missing $kind";
|
||||
throw new self($msg);
|
||||
}
|
||||
|
||||
static function invalid(?string $value, string $kind): self {
|
||||
$msg = $value;
|
||||
if ($msg !== null) $msg .= ": ";
|
||||
$msg .= "invalid $kind";
|
||||
throw new self($msg);
|
||||
}
|
||||
class ArgsException extends UserException {
|
||||
}
|
||||
|
10
php/src/app/args/_exceptions.php
Normal file
10
php/src/app/args/_exceptions.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace nulib\app\args;
|
||||
|
||||
use nulib\exceptions;
|
||||
|
||||
class _exceptions extends exceptions {
|
||||
const EXCEPTION = ArgsException::class;
|
||||
|
||||
const WORD = "masc:l'argument#s";
|
||||
}
|
@ -10,10 +10,14 @@ 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;
|
||||
|
||||
/**
|
||||
* Class Application: application de base
|
||||
@ -63,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 */
|
||||
@ -191,26 +196,30 @@ EOT);
|
||||
protected static function _initialize_app(): void {
|
||||
app::init(static::class);
|
||||
app::set_fact(app::FACT_CLI_APP);
|
||||
msg::set_messenger(new StdMessenger([
|
||||
$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);
|
||||
|
||||
$msgs = null;
|
||||
$msgs["console"] = new StdMessenger([
|
||||
"min_level" => msg::NORMAL,
|
||||
]);
|
||||
$con = con::set_messenger(new ConsoleMessenger([
|
||||
"min_level" => con::NORMAL,
|
||||
]));
|
||||
say::set_messenger($con, true);
|
||||
msg::set_messenger($con, true);
|
||||
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();
|
||||
@ -257,9 +266,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]],
|
||||
],
|
||||
];
|
||||
|
||||
@ -267,26 +276,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"]],
|
||||
],
|
||||
];
|
||||
|
||||
@ -307,9 +326,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";
|
||||
|
||||
|
@ -4,8 +4,8 @@ namespace nulib\app;
|
||||
use nulib\app\config\ConfigManager;
|
||||
use nulib\app\config\JsonConfig;
|
||||
use nulib\app\config\YamlConfig;
|
||||
use nulib\exceptions;
|
||||
use nulib\os\path;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class config: gestion de la configuration de l'application
|
||||
@ -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 ValueException::invalid_value($file, "config file");
|
||||
throw exceptions::invalid_value($file, "config file");
|
||||
}
|
||||
self::add($config);
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ namespace nulib\app\config;
|
||||
use nulib\A;
|
||||
use nulib\app\app;
|
||||
use nulib\cl;
|
||||
use nulib\exceptions;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
use ReflectionClass;
|
||||
|
||||
class ConfigManager {
|
||||
@ -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;
|
||||
}
|
||||
@ -93,7 +93,7 @@ class ConfigManager {
|
||||
} elseif (is_array($config)) {
|
||||
$config = new ArrayConfig($config);
|
||||
} elseif (!($config instanceof IConfig)) {
|
||||
throw ValueException::invalid_type($config, "array|IConfig");
|
||||
throw exceptions::invalid_type($config, "config", ["array", IConfig::class]);
|
||||
}
|
||||
|
||||
if (!$inProfiles) $inProfiles = [IConfig::PROFILE_ALL];
|
||||
@ -132,7 +132,7 @@ class ConfigManager {
|
||||
}
|
||||
|
||||
$value = $this->_getValue($pkey, $default, $inProfile);
|
||||
$this->cacheSet($pkey, $default, $inProfile);
|
||||
$this->cacheSet($pkey, $value, $inProfile);
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
50
php/src/cache/CacheData.php
vendored
Normal file
50
php/src/cache/CacheData.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace nulib\cache;
|
||||
|
||||
use nulib\php\func;
|
||||
|
||||
/**
|
||||
* Class CacheData: gestion d'une donnée mise en cache
|
||||
*/
|
||||
abstract class CacheData {
|
||||
function __construct(?string $name, $compute) {
|
||||
$this->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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/** 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();
|
||||
}
|
356
php/src/cache/CacheFile.php
vendored
Normal file
356
php/src/cache/CacheFile.php
vendored
Normal file
@ -0,0 +1,356 @@
|
||||
<?php
|
||||
namespace nulib\cache;
|
||||
|
||||
use Exception;
|
||||
use nulib\cv;
|
||||
use nulib\exceptions;
|
||||
use nulib\ext\utils;
|
||||
use nulib\file\SharedFile;
|
||||
use nulib\os\path;
|
||||
use nulib\php\func;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\php\time\Delay;
|
||||
use nulib\str;
|
||||
|
||||
class CacheFile extends SharedFile {
|
||||
/** @var string|int durée de vie par défaut des données mises en cache */
|
||||
const DURATION = "1D"; // jusqu'au lendemain
|
||||
|
||||
static function with($data, ?string $file=null): self {
|
||||
if ($data instanceof self) return $data;
|
||||
else return new static($file, $data);
|
||||
}
|
||||
|
||||
protected static function ensure_source($data, ?CacheData &$source, bool $allowArray=true): bool {
|
||||
if ($data === null || $data instanceof CacheData) {
|
||||
$source = $data;
|
||||
} elseif (is_subclass_of($data, CacheData::class)) {
|
||||
$source = new $data();
|
||||
} elseif (func::is_callable($data)) {
|
||||
$source = new DataCacheData(null, $data);
|
||||
} elseif (is_array($data) && $allowArray) {
|
||||
return false;
|
||||
} elseif (is_iterable($data)) {
|
||||
$source = new DataCacheData(null, static function() use ($data) {
|
||||
yield from $data;
|
||||
});
|
||||
} else {
|
||||
throw exceptions::invalid_type($source, "source", CacheData::class);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function __construct(?string $file, $data=null, ?array $params=null) {
|
||||
$file ??= path::join(sys_get_temp_dir(), utils::uuidgen());
|
||||
$file = path::ensure_ext($file, cache::EXT);
|
||||
$basefile = str::without_suffix(cache::EXT, $file);
|
||||
|
||||
$this->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() {
|
||||
# é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 pour $data
|
||||
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($key, bool $noCache) {
|
||||
$source = $this->sources[$key] ?? null;
|
||||
$external = $source !== null && $source->isExternal();
|
||||
|
||||
$updateMetadata = $this->shouldUpdate($noCache);
|
||||
if (!$key && !$external) $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;
|
||||
}
|
||||
if (!$key && !$external) {
|
||||
# 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;
|
||||
} 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);
|
||||
} else {
|
||||
# ne pas garder le fichier s'il ne faut pas mettre en cache
|
||||
$source->delete();
|
||||
$data = null;
|
||||
}
|
||||
} else {
|
||||
$data = null;
|
||||
}
|
||||
} elseif (!$key && !$external) {
|
||||
$data = $this->data;
|
||||
} elseif ($source !== null && $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($key=null, bool $noCache=false) {
|
||||
return $this->action(function () use ($key, $noCache) {
|
||||
return $this->refreshData($key, $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($key=null): void {
|
||||
$source = $this->sources[$key] ?? 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);
|
||||
}
|
||||
}
|
68
php/src/cache/CacheManager.php
vendored
Normal file
68
php/src/cache/CacheManager.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace nulib\cache;
|
||||
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class CacheManager: un gestionnaire de cache permettant de désactiver la mise
|
||||
* en cache d'une valeur dans le cadre d'une session.
|
||||
*
|
||||
* en effet, si on désactive le cache, il doit être réactivé après que la valeur
|
||||
* est calculée, pour éviter qu'une valeur soit calculée encore et encore dans
|
||||
* une session de travail
|
||||
*/
|
||||
class CacheManager {
|
||||
function __construct(?array $includes=null, ?array $excludes=null) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
40
php/src/cache/CursorCacheData.php
vendored
Normal file
40
php/src/cache/CursorCacheData.php
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
namespace nulib\cache;
|
||||
|
||||
class CursorCacheData extends CacheData {
|
||||
function __construct(array $cursorId, $compute=null, ?CursorChannel $channel=null) {
|
||||
$name = $cursorId["group_id"];
|
||||
if ($name) $name .= "_";
|
||||
$name .= $cursorId["id"];
|
||||
parent::__construct($name, $compute);
|
||||
$channel ??= (new CursorChannel($cursorId))->initStorage(cache::storage());
|
||||
$this->channel = $channel;
|
||||
}
|
||||
|
||||
function isExternal(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
127
php/src/cache/CursorChannel.php
vendored
Normal file
127
php/src/cache/CursorChannel.php
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
namespace nulib\cache;
|
||||
|
||||
use IteratorAggregate;
|
||||
use nulib\cl;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\CapacitorStorage;
|
||||
use nulib\php\func;
|
||||
use Traversable;
|
||||
|
||||
class CursorChannel extends CapacitorChannel implements IteratorAggregate {
|
||||
static function with($cursorId=null, ?iterable $rows=null, ?CapacitorStorage $storage=null): self {
|
||||
$storage ??= cache::storage();
|
||||
$channel = (new static($cursorId))->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;
|
||||
}
|
||||
}
|
||||
}
|
62
php/src/cache/DataCacheData.php
vendored
Normal file
62
php/src/cache/DataCacheData.php
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace nulib\cache;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\file;
|
||||
use nulib\os\path;
|
||||
use Traversable;
|
||||
|
||||
class DataCacheData extends CacheData {
|
||||
/** @var string identifiant de cette donnée */
|
||||
const NAME = null;
|
||||
|
||||
/** @var callable une fonction permettant de calculer la donnée */
|
||||
const COMPUTE = null;
|
||||
|
||||
function __construct(?string $name=null, $compute=null, ?string $basefile=null) {
|
||||
$name ??= static::NAME ?? "";
|
||||
$compute ??= static::COMPUTE;
|
||||
parent::__construct($name, $compute);
|
||||
$this->setDatafile($basefile);
|
||||
}
|
||||
|
||||
function compute() {
|
||||
$data = parent::compute();
|
||||
if ($data instanceof Traversable) $data = cl::all($data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
function isExternal(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
6
php/src/cache/TODO.md
vendored
Normal file
6
php/src/cache/TODO.md
vendored
Normal file
@ -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
|
93
php/src/cache/cache.php
vendored
Normal file
93
php/src/cache/cache.php
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace nulib\cache;
|
||||
|
||||
use nulib\app\app;
|
||||
use nulib\db\CapacitorStorage;
|
||||
use nulib\db\sqlite\SqliteStorage;
|
||||
use nulib\ext\utils;
|
||||
|
||||
class cache {
|
||||
/** @var string extension des fichiers de cache */
|
||||
const EXT = ".cache";
|
||||
|
||||
protected static ?string $dbfile = null;
|
||||
|
||||
protected static function dbfile(): ?string {
|
||||
return self::$dbfile ??= app::get()->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);
|
||||
}
|
||||
}
|
@ -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 = [];
|
||||
|
@ -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";
|
||||
@ -166,6 +166,12 @@ class cv {
|
||||
|
||||
#############################################################################
|
||||
|
||||
/** retourner $value si elle est non nulle, lancer une exception sinon */
|
||||
static final function not_null($value, ?string $kind=null) {
|
||||
if ($value !== null) return $value;
|
||||
throw exceptions::null_value($kind);
|
||||
}
|
||||
|
||||
/** vérifier si $value est un booléen, sinon retourner null */
|
||||
static final function check_bool($value): ?bool {
|
||||
return is_bool($value)? $value: null;
|
||||
@ -192,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 ValueException::invalid_kind($value, "key", $prefix);
|
||||
if ($index === null && $key === null && $throwException) {
|
||||
throw exceptions::invalid_type($value, $kind, "key");
|
||||
} else {
|
||||
return [$index, $key];
|
||||
}
|
||||
@ -208,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 ValueException::invalid_kind($value, "value", $prefix);
|
||||
if ($bool === null && $scalar === null && $array === null && $throwException) {
|
||||
throw exceptions::invalid_type($value, $kind, ["bool", "scalar", "array"]);
|
||||
} else {
|
||||
return [$bool, $scalar, $array];
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
namespace nulib\db;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\exceptions;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
@ -66,7 +66,7 @@ class Capacitor implements ITransactor {
|
||||
if ($channel instanceof CapacitorChannel) {
|
||||
$this->subChannels[] = $channel;
|
||||
} else {
|
||||
throw ValueException::invalid_type($channel, CapacitorChannel::class);
|
||||
throw exceptions::invalid_type($channel, "channel", CapacitorChannel::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
@ -17,7 +17,7 @@ abstract class CapacitorStorage {
|
||||
abstract function db(): IDatabase;
|
||||
|
||||
function ensureLive(): self {
|
||||
$this->db()->ensure();
|
||||
$this->db()->ensureLive();
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -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([
|
||||
|
@ -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
|
||||
|
@ -1,14 +1,14 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
use nulib\ValueException;
|
||||
use nulib\exceptions;
|
||||
|
||||
abstract class _base extends _common {
|
||||
protected static function verifix(&$sql, ?array &$bindings=null, ?array &$meta=null): void {
|
||||
if (is_array($sql)) {
|
||||
$prefix = $sql[0] ?? null;
|
||||
if ($prefix === null) {
|
||||
throw new ValueException("requête invalide");
|
||||
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 ValueException::invalid_kind($sql, "query");
|
||||
throw exceptions::invalid_value($sql, "cette requête sql");
|
||||
}
|
||||
} else {
|
||||
if (!is_string($sql)) $sql = strval($sql);
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
@ -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);
|
||||
@ -41,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 = [
|
||||
@ -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) {
|
||||
@ -195,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;
|
||||
@ -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) {
|
||||
|
@ -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;
|
||||
@ -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) {
|
||||
@ -247,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;
|
||||
@ -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) {
|
||||
|
@ -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;
|
||||
@ -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) {
|
||||
@ -247,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;
|
||||
@ -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) {
|
||||
|
253
php/src/exceptions.php
Normal file
253
php/src/exceptions.php
Normal file
@ -0,0 +1,253 @@
|
||||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use nulib\php\content\c;
|
||||
use nulib\text\Word;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class exceptions: répertoire d'exceptions normalisées
|
||||
*/
|
||||
class exceptions {
|
||||
/** @param Throwable|ExceptionShadow $e */
|
||||
public static function get_user_message($e): ?string {
|
||||
if ($e instanceof UserException) $userMessage = $e->getUserMessage();
|
||||
elseif ($e instanceof ExceptionShadow) $userMessage = $e->getUserMessage();
|
||||
else return null;
|
||||
if ($userMessage === null) return null;
|
||||
else 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;
|
||||
if ($techMessage === null) return null;
|
||||
else 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;
|
||||
|
||||
protected static function word(): Word {
|
||||
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 $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, ?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;
|
||||
} 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, $kind, $reason, $previous);
|
||||
}
|
||||
|
||||
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;
|
||||
} 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, $kind, $reason, $previous);
|
||||
}
|
||||
|
||||
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";
|
||||
} else if ($min !== null) {
|
||||
$reason = "$pronom doit être $superieur à $min";
|
||||
} elseif ($max !== null) {
|
||||
$reason = "$pronom doit être $inferieur à $max";
|
||||
} else {
|
||||
$reason = null;
|
||||
}
|
||||
return self::invalid_value($value, $kind, $reason, $previous);
|
||||
}
|
||||
|
||||
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 $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 {
|
||||
if ($kind !== null) $kind = "de $kind";
|
||||
else $kind = self::word()->_de(2);
|
||||
$message = "il y a trop $kind";
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* indiquer qu'une valeur est en trop
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
@ -57,9 +57,9 @@ 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);
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace nulib\file;
|
||||
|
||||
use nulib\ValueException;
|
||||
use nulib\exceptions;
|
||||
|
||||
class SharedFile extends FileWriter {
|
||||
const USE_LOCKING = true;
|
||||
@ -9,7 +9,7 @@ class SharedFile extends FileWriter {
|
||||
const DEFAULT_MODE = "c+b";
|
||||
|
||||
function __construct($file, ?string $mode=null, ?bool $throwOnError=null, ?bool $allowLocking=null) {
|
||||
if ($file === null) throw ValueException::null("file");
|
||||
if ($file === null) throw exceptions::null_value("file");
|
||||
parent::__construct($file, $mode, $throwOnError, $allowLocking);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
<?php
|
||||
namespace nulib\file;
|
||||
|
||||
use nulib\exceptions;
|
||||
use nulib\file\csv\csv_flavours;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class Stream: lecture/écriture générique dans un flux
|
||||
@ -61,7 +61,7 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||
protected $stat;
|
||||
|
||||
function __construct($fd, bool $close=true, ?bool $throwOnError=null, ?bool $useLocking=null) {
|
||||
if ($fd === null) throw ValueException::null("resource");
|
||||
if ($fd === null) throw exceptions::null_value("resource");
|
||||
$this->fd = $fd;
|
||||
$this->close = $close;
|
||||
$this->throwOnError = $throwOnError ?? static::THROW_ON_ERROR;
|
||||
|
@ -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 {
|
||||
@ -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]];
|
||||
}
|
||||
|
@ -2,10 +2,10 @@
|
||||
namespace nulib\file\tab;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\exceptions;
|
||||
use nulib\file\csv\CsvBuilder;
|
||||
use nulib\file\web\Upload;
|
||||
use nulib\os\path;
|
||||
use nulib\ValueException;
|
||||
|
||||
trait TAbstractBuilder {
|
||||
/** @param Upload|string|array $builder */
|
||||
@ -32,7 +32,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;
|
||||
|
@ -2,10 +2,10 @@
|
||||
namespace nulib\file\tab;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\exceptions;
|
||||
use nulib\file\csv\CsvReader;
|
||||
use nulib\file\web\Upload;
|
||||
use nulib\os\path;
|
||||
use nulib\ValueException;
|
||||
|
||||
trait TAbstractReader {
|
||||
/** @param Upload|string|array $reader */
|
||||
@ -31,7 +31,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;
|
||||
|
@ -3,7 +3,6 @@ namespace nulib\mail;
|
||||
|
||||
use nulib\cv;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
|
||||
class MailTemplate {
|
||||
@ -19,8 +18,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)) {
|
||||
|
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
namespace nulib\mail;
|
||||
|
||||
use nulib\app\config;
|
||||
use nulib\cl;
|
||||
use nulib\web\session;
|
||||
|
||||
class MailTemplateHelper {
|
||||
function __construct(?array $data) {
|
||||
|
@ -1,11 +1,12 @@
|
||||
<?php
|
||||
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;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
|
||||
@ -20,6 +21,7 @@ class mailer {
|
||||
return true;
|
||||
} else {
|
||||
switch (strval($value)) {
|
||||
case "":
|
||||
case "0":
|
||||
case "no":
|
||||
case "off":
|
||||
@ -56,25 +58,40 @@ class mailer {
|
||||
"secure" => "?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");
|
||||
throw exceptions::null_value("host");
|
||||
}
|
||||
msg::debug("new PHPMailer using SMTP to $host:$port");
|
||||
$mailer->isSMTP();
|
||||
@ -90,17 +107,15 @@ 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"] ?? 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;
|
||||
} 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
|
||||
@ -130,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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
67
php/src/output/_TMessenger.php
Normal file
67
php/src/output/_TMessenger.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\app\app;
|
||||
use nulib\exceptions;
|
||||
use nulib\output\std\NullMessenger;
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
|
||||
trait _TMessenger {
|
||||
protected static ?IMessenger $msg = null;
|
||||
|
||||
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);
|
||||
self::$msg->addMessenger($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"]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,44 +1,16 @@
|
||||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class _messenger: classe de base pour say, log et msg
|
||||
*/
|
||||
abstract class _messenger {
|
||||
abstract static function is_setup(): bool;
|
||||
abstract static function set_messenger(IMessenger $msg);
|
||||
abstract static function set_messenger(IMessenger $msg, bool $replace=false): IMessenger;
|
||||
|
||||
abstract static function get(): IMessenger;
|
||||
|
||||
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);
|
||||
}
|
||||
static::set_messenger(new $msg_class($params));
|
||||
}
|
||||
|
||||
static function create_or_reset_params(?array $params=null, string $log_class=null, ?array $create_params=null) {
|
||||
if (static::is_setup()) {
|
||||
self::reset_params($params);
|
||||
} else {
|
||||
$params = cl::merge($params, $create_params);
|
||||
self::set_messenger_class($log_class, $params);
|
||||
}
|
||||
}
|
||||
|
||||
/** obtenir une nouvelle instance, avec un nouveau paramétrage */
|
||||
static function new(?array $params=null): IMessenger {
|
||||
return static::get()->clone($params);
|
||||
}
|
||||
|
||||
static final function __callStatic($name, $args) {
|
||||
$name = str::us2camel($name);
|
||||
call_user_func_array([static::get(), $name], $args);
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
const DEBUG = IMessenger::DEBUG;
|
||||
|
25
php/src/output/con.php
Normal file
25
php/src/output/con.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\output\std\ConsoleMessenger;
|
||||
|
||||
/**
|
||||
* Class con: afficher un message à l'utilisateur sur la console (c'est à dire
|
||||
* la sortie standard pour {@link con::print()}, et la sortie d'erreur pour
|
||||
* toutes les autres méthodes)
|
||||
*
|
||||
* Cette classe est utilisable sans initialisation préalable
|
||||
*/
|
||||
class con extends _messenger {
|
||||
use _TMessenger;
|
||||
|
||||
static function get(): IMessenger {
|
||||
return static::$msg ??= new ConsoleMessenger();
|
||||
}
|
||||
|
||||
static function set_color(bool $color=true): void {
|
||||
self::get()->resetParams([
|
||||
"color" => $color,
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\app\app;
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* Class console: afficher un message sur la console
|
||||
*
|
||||
* Cette classe DOIT être initialisée avant d'être utilisée
|
||||
*/
|
||||
class console extends _messenger {
|
||||
private static ?IMessenger $msg = null;
|
||||
|
||||
private static bool $setup = false;
|
||||
|
||||
static function is_setup(): bool {
|
||||
return self::$setup;
|
||||
}
|
||||
|
||||
static function set_messenger(IMessenger $msg) {
|
||||
self::$msg = $msg;
|
||||
self::$setup = true;
|
||||
}
|
||||
|
||||
static function get(): IMessenger {
|
||||
return self::$msg ??= new ProxyMessenger();
|
||||
}
|
||||
|
||||
static function set_verbosity(string $verbosity): void {
|
||||
$console = self::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_color(bool $color=true): void {
|
||||
console::reset_params([
|
||||
"color" => $color,
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,38 +1,40 @@
|
||||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\output\std\LogMessenger;
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
use nulib\output\std\StdMessenger;
|
||||
|
||||
/**
|
||||
* Class log: inscrire un message dans les logs uniquement
|
||||
* Class log: ajouter un message dans les logs
|
||||
*
|
||||
* Cette classe DOIT être initialisée avant d'être utilisée
|
||||
* Cette classe DEVRAIT être initialisée avant utilisation. Sinon, elle envoie
|
||||
* ses messages vers /dev/null
|
||||
*
|
||||
* mode | con | web | log
|
||||
* ------|-----|-----|-----
|
||||
* cli | | | x
|
||||
* web | | | x
|
||||
*/
|
||||
class log extends _messenger {
|
||||
private static ?IMessenger $msg = null;
|
||||
|
||||
private static bool $setup = false;
|
||||
|
||||
static function is_setup(): bool {
|
||||
return self::$setup;
|
||||
}
|
||||
|
||||
static function set_messenger(IMessenger $msg) {
|
||||
self::$msg = $msg;
|
||||
self::$setup = true;
|
||||
}
|
||||
use _TMessenger;
|
||||
|
||||
static function get(): IMessenger {
|
||||
return self::$msg ??= new ProxyMessenger();
|
||||
return self::$msg ??= static::$msg = new ProxyMessenger();
|
||||
}
|
||||
|
||||
protected static function ensure_log(): IMessenger {
|
||||
$msg = self::$msg;
|
||||
if ($msg instanceof ProxyMessenger && $msg->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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +1,18 @@
|
||||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
use nulib\php\func;
|
||||
|
||||
/**
|
||||
* Class msg: inscrire un message dans les logs ET l'afficher à l'utilisateur
|
||||
* Class msg: afficher un message à l'utilisateur et l'ajouter aussi dans les
|
||||
* logs
|
||||
*
|
||||
* Cette classe DOIT être initialisée avec {@link set_messenger()} ou
|
||||
* {@link set_messenger_class()} avant d'être utilisée.
|
||||
* Cette classe DEVRAIT être initialisée avant utilisation. Sinon, elle envoie
|
||||
* ses messages vers /dev/null
|
||||
*
|
||||
* mode | con | web | log
|
||||
* ------|-----|-----|-----
|
||||
* cli | x | | x
|
||||
* web | | x | x
|
||||
*/
|
||||
class msg extends _messenger {
|
||||
private static ?IMessenger $msg = null;
|
||||
|
||||
private static bool $setup = false;
|
||||
|
||||
static function is_setup(): bool {
|
||||
return self::$setup;
|
||||
}
|
||||
|
||||
static function set_messenger(IMessenger $msg) {
|
||||
self::$msg = $msg;
|
||||
self::$setup = true;
|
||||
}
|
||||
|
||||
static function get(): IMessenger {
|
||||
return self::$msg ??= new ProxyMessenger();
|
||||
}
|
||||
|
||||
/**
|
||||
* initialiser les instances say, console, log.
|
||||
*/
|
||||
static function init(array $msgs) {
|
||||
$say = $msgs["say"] ?? null;
|
||||
$console = $msgs["console"] ?? null;
|
||||
$log = $msgs["log"] ?? null;
|
||||
$msgs = [];
|
||||
if ($log !== null && $log !== false) {
|
||||
if ($log instanceof IMessenger) log::set_messenger($log);
|
||||
elseif (is_string($log)) log::set_messenger_class($log);
|
||||
else $log = func::call($log);
|
||||
log::set_messenger($log);
|
||||
$msgs[] = $log;
|
||||
}
|
||||
if ($console !== null && $console !== false) {
|
||||
if ($console instanceof IMessenger) console::set_messenger($console);
|
||||
elseif (is_string($console)) console::set_messenger_class($console);
|
||||
else $console = func::call($console);
|
||||
console::set_messenger($console);
|
||||
$msgs[] = $console;
|
||||
}
|
||||
if ($say !== null && $say !== false) {
|
||||
if ($say instanceof IMessenger) say::set_messenger($say);
|
||||
elseif (is_string($say)) say::set_messenger_class($say);
|
||||
else $say = func::call($say);
|
||||
say::set_messenger($say);
|
||||
$msgs[] = $say;
|
||||
}
|
||||
if ($say === null && $console !== null) {
|
||||
say::set_messenger($console);
|
||||
} elseif ($console === null && $say !== null) {
|
||||
console::set_messenger($say);
|
||||
}
|
||||
self::set_messenger(new ProxyMessenger(...$msgs));
|
||||
}
|
||||
use _TMessenger;
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\output\std\StdOutput;
|
||||
|
||||
/**
|
||||
* Class out: affichage sur la sortie standard
|
||||
*/
|
||||
class out {
|
||||
/** @var StdOutput */
|
||||
private static $out;
|
||||
|
||||
static function get(): StdOutput {
|
||||
return self::$out;
|
||||
}
|
||||
protected static function set(StdOutput $out): StdOutput {
|
||||
return self::$out = $out;
|
||||
}
|
||||
|
||||
/** reparamétrer l'instance */
|
||||
static function reset($output=null, ?array $params=null): StdOutput {
|
||||
if (self::$out === null) return self::set(new StdOutput($output, $params));
|
||||
if ($output !== null) $params["output"] = $output;
|
||||
self::$out->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();
|
@ -1,28 +1,17 @@
|
||||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
|
||||
/**
|
||||
* Class say: afficher un message pour l'utilisateur
|
||||
* Class say: afficher un message à l'utilisateur
|
||||
*
|
||||
* Cette classe DOIT être initialisée avant d'être utilisée
|
||||
* Cette classe DEVRAIT être initialisée avant utilisation. Sinon, elle envoie
|
||||
* ses messages vers /dev/null
|
||||
*
|
||||
* mode | con | web | log
|
||||
* ------|-----|-----|-----
|
||||
* cli | x | |
|
||||
* web | | x |
|
||||
*/
|
||||
class say extends _messenger {
|
||||
private static ?IMessenger $msg = null;
|
||||
|
||||
private static bool $setup = false;
|
||||
|
||||
static function is_setup(): bool {
|
||||
return self::$setup;
|
||||
}
|
||||
|
||||
static function set_messenger(IMessenger $msg) {
|
||||
self::$msg = $msg;
|
||||
self::$setup = true;
|
||||
}
|
||||
|
||||
static function get(): IMessenger {
|
||||
return self::$msg ??= new ProxyMessenger();
|
||||
}
|
||||
use _TMessenger;
|
||||
}
|
||||
|
297
php/src/output/std/AbstractMessenger.php
Normal file
297
php/src/output/std/AbstractMessenger.php
Normal file
@ -0,0 +1,297 @@
|
||||
<?php
|
||||
namespace nulib\output\std;
|
||||
|
||||
use Exception;
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\exceptions;
|
||||
use nulib\ExceptionShadow;
|
||||
use Throwable;
|
||||
|
||||
abstract class AbstractMessenger implements _IMessenger {
|
||||
const ADD_DATE = false;
|
||||
|
||||
const SHOW_IDS = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/** @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 abstract function title__getId(): ?int;
|
||||
|
||||
protected int $lastActionId = 1;
|
||||
|
||||
protected abstract function action__getId(): ?int;
|
||||
|
||||
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->title__getId();
|
||||
if ($titleId !== null) $linePrefix .= "t=$titleId ";
|
||||
$actionId = $this->action__getId();
|
||||
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(
|
||||
int $level, string $type, ?string $linePrefix, int $indentLevel,
|
||||
StdOutput $out, $content
|
||||
): 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 action__flush(bool $endAction=false, ?int $overrideLevel=null): void;
|
||||
|
||||
protected function _printAction(
|
||||
int $level, ?string $linePrefix, int $indentLevel,
|
||||
StdOutput $out,
|
||||
bool $printContent, $content,
|
||||
bool $printResult, ?bool $rsuccess, $rcontent
|
||||
): 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(
|
||||
int $level, string $type, ?string $linePrefix, int $indentLevel,
|
||||
StdOutput $out, $content
|
||||
): 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, int $indentLevel,
|
||||
StdOutput $out, $content
|
||||
): 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->action__flush(); $flushActions = false;
|
||||
$this->_printGeneric($level, $type, $linePrefix, $indentLevel, $out, $content);
|
||||
}
|
||||
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->action__flush(); $flushActions = false; }
|
||||
$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($level1, $type, $linePrefix, $indentLevel, $out, $summary);
|
||||
$traceback = exceptions::get_traceback($exception);
|
||||
$this->_printGeneric($level1, $type, $linePrefix, $indentLevel, $out, $traceback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
473
php/src/output/std/ConsoleMessenger.php
Normal file
473
php/src/output/std/ConsoleMessenger.php
Normal file
@ -0,0 +1,473 @@
|
||||
<?php
|
||||
namespace nulib\output\std;
|
||||
|
||||
use Exception;
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\output\IMessenger;
|
||||
|
||||
class ConsoleMessenger extends AbstractMessenger {
|
||||
function __construct(?array $params=null) {
|
||||
$output = $params["output"] ?? null;
|
||||
$color = $params["color"] ?? null;
|
||||
$indent = $params["indent"] ?? static::INDENT;
|
||||
|
||||
$defaultLevel = $params["default_level"] ?? null;
|
||||
$defaultLevel = self::verifix_level($defaultLevel ?? self::NORMAL);
|
||||
|
||||
$debug = boolval($params["debug"] ?? null);
|
||||
$minLevel = $params["min_level"] ?? null;
|
||||
if ($debug) $minLevel ??= self::DEBUG;
|
||||
$minLevel ??= $params["verbosity"] ?? null; # alias
|
||||
$minLevel = self::verifix_level($minLevel ?? self::NORMAL, self::NONE);
|
||||
|
||||
$addDate = boolval($params["add_date"] ?? static::ADD_DATE);
|
||||
$dateFormat = cl::get($params, "date_format", static::DATE_FORMAT);
|
||||
$id = $params["id"] ?? null;
|
||||
$showIds = $params["show_ids"] ?? static::SHOW_IDS;
|
||||
|
||||
$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->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;
|
||||
|
||||
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->section__end();
|
||||
$this->inSection = true;
|
||||
if (!$this->checkLevel($level)) return;
|
||||
$this->section = [
|
||||
"msg_level" => $level,
|
||||
"line_prefix" => $this->getLinePrefix(),
|
||||
"content" => $content,
|
||||
"print_content" => true,
|
||||
];
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($this);
|
||||
} finally {
|
||||
$this->section__afterFunc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function printSection() {
|
||||
$section =& $this->section;
|
||||
if ($section !== null && $section["print_content"]) {
|
||||
$this->_printTitle(
|
||||
$section["msg_level"], "section", $section["line_prefix"], 0,
|
||||
$this->err, $section["content"]);
|
||||
$section["print_content"] = false;
|
||||
}
|
||||
}
|
||||
|
||||
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["msg_level"] < $this->minLevel) continue;
|
||||
$indentLevel++;
|
||||
}
|
||||
}
|
||||
return $indentLevel;
|
||||
}
|
||||
|
||||
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 $this->title__last()["id"] ?? null;
|
||||
}
|
||||
|
||||
protected function title__end(?int $until=null): void {
|
||||
$title = $this->title__last();
|
||||
if ($title !== null) {
|
||||
$until ??= $title["max_title_level"];
|
||||
$until ??= $this->title__getMarks()[0] - 1;
|
||||
while (count($this->titles) > $until) {
|
||||
array_pop($this->titles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function title__flush(): void {
|
||||
$this->printSection();
|
||||
$err = $this->err;
|
||||
$indentLevel = 0;
|
||||
foreach ($this->titles as &$title) {
|
||||
if ($title["print_content"]) {
|
||||
$this->_printTitle(
|
||||
$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["msg_level"], "desc", $desc["line_prefix"], $indentLevel,
|
||||
$err, $desc["content"]);
|
||||
}
|
||||
$title["descs"] = [];
|
||||
$title["print_descs"] = false;
|
||||
}
|
||||
$indentLevel++;
|
||||
}; unset($title);
|
||||
}
|
||||
|
||||
protected function &title__ref(): ?array {
|
||||
return $this->titles[array_key_last($this->titles)];
|
||||
}
|
||||
|
||||
function title__beforeFunc(array $marks): void {
|
||||
$title =& $this->title__ref();
|
||||
$title["max_title_level"] = $marks[0] + 1;
|
||||
}
|
||||
|
||||
function title__afterFunc(array $marks): void {
|
||||
$title =& $this->title__ref();
|
||||
$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->title__ref(), [
|
||||
"title_level" => $marks[0],
|
||||
"max_title_level" => null,
|
||||
"msg_level" => $level,
|
||||
"line_prefix" => $this->getLinePrefix(),
|
||||
"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 = [
|
||||
"msg_level" => $level,
|
||||
"line_prefix" => $this->getLinePrefix(),
|
||||
"content" => $content,
|
||||
];
|
||||
$title = $this->title__last();
|
||||
if ($title !== null) {
|
||||
$title =& $this->title__ref();
|
||||
$title["descs"][] = $desc;
|
||||
$title["print_descs"] = true;
|
||||
} else {
|
||||
# pas de titre en cours
|
||||
$this->_printGeneric(
|
||||
$desc["msg_level"], "desc", $desc["line_prefix"], 0,
|
||||
$this->err, $desc["content"]);
|
||||
}
|
||||
}
|
||||
|
||||
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 $this->action__last()["id"] ?? null;
|
||||
}
|
||||
|
||||
protected function action__end(?int $until=null): void {
|
||||
$action = $this->action__last();
|
||||
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);
|
||||
$index = 0;
|
||||
foreach ($this->actions as &$action) {
|
||||
$mergeResult = $index++ == $lastIndex && $endAction;
|
||||
$level = $overrideLevel?? $action["msg_level"];
|
||||
$linePrefix = $action["line_prefix"];
|
||||
$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(
|
||||
$level, $linePrefix, $indentLevel,
|
||||
$err,
|
||||
$printContent, $content,
|
||||
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(
|
||||
$level, $linePrefix, $indentLevel,
|
||||
$err,
|
||||
$printContent, $content,
|
||||
false, null, null);
|
||||
# recalculer une nouvelle ligne de préfixe pour le résultat
|
||||
$linePrefix = $this->getLinePrefix();
|
||||
$this->_printAction(
|
||||
$level, $linePrefix, $indentLevel,
|
||||
$err,
|
||||
false, null,
|
||||
true, $rsuccess, $rcontent);
|
||||
}
|
||||
$action["action_aresult"] = true;
|
||||
} elseif ($printContent) {
|
||||
$this->_printAction(
|
||||
$level, $linePrefix, $indentLevel,
|
||||
$err,
|
||||
$printContent, $content,
|
||||
false, $rsuccess, $rcontent);
|
||||
$action["print_content"] = false;
|
||||
}
|
||||
$indentLevel++;
|
||||
}; unset($action);
|
||||
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->action__ref();
|
||||
$action["max_action_level"] = $marks[0] + 1;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
function action($content, ?callable $func=null, ?int $level=null): void {
|
||||
$this->checkLevel($level);
|
||||
$marks = $this->action__getMarks();
|
||||
// faire en deux temps pour linePrefix soit à jour
|
||||
$this->actions[] = ["id" => $this->lastActionId++];
|
||||
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(),
|
||||
"content" => $content,
|
||||
"print_content" => true,
|
||||
"result_success" => null,
|
||||
"result_content" => null,
|
||||
]);
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$result = null;
|
||||
$this->action__beforeFunc($marks);
|
||||
$result = $func($this);
|
||||
} catch (Exception $e) {
|
||||
$this->afailure($e);
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->action__afterFunc($marks, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function step($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "step", $this->getIndentLevel(),
|
||||
$this->err, $content);
|
||||
}
|
||||
|
||||
function asuccess($content=null, ?int $overrideLevel=null): void {
|
||||
if (!$this->actions) $this->action(null);
|
||||
$action =& $this->action__ref();
|
||||
$action["result_success"] = true;
|
||||
$action["result_content"] = $content;
|
||||
$this->action__flush(true, $overrideLevel);
|
||||
}
|
||||
|
||||
function afailure($content=null, ?int $overrideLevel=null): void {
|
||||
if (!$this->actions) $this->action(null);
|
||||
$action =& $this->action__ref();
|
||||
$action["result_success"] = false;
|
||||
$action["result_content"] = $content;
|
||||
$this->action__flush(true, $overrideLevel);
|
||||
}
|
||||
|
||||
function adone($content=null, ?int $overrideLevel=null): void {
|
||||
if (!$this->actions) $this->action(null);
|
||||
$action =& $this->action__ref();
|
||||
$action["result_success"] = null;
|
||||
$action["result_content"] = $content;
|
||||
$this->action__flush(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 print($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "print", $this->getIndentLevel(),
|
||||
$this->out, $content);
|
||||
}
|
||||
|
||||
function info($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "info", $this->getIndentLevel(),
|
||||
$this->err, $content);
|
||||
}
|
||||
|
||||
function note($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "note", $this->getIndentLevel(),
|
||||
$this->err, $content);
|
||||
}
|
||||
|
||||
function warning($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "warning", $this->getIndentLevel(),
|
||||
$this->err, $content);
|
||||
}
|
||||
|
||||
function error($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "error", $this->getIndentLevel(),
|
||||
$this->err, $content);
|
||||
}
|
||||
|
||||
function end(bool $all=false): void {
|
||||
if ($all) $this->section__afterFunc();
|
||||
elseif ($this->actions) $this->action__end();
|
||||
elseif ($this->titles) $this->title__end();
|
||||
else $this->section__afterFunc();
|
||||
}
|
||||
}
|
350
php/src/output/std/LogMessenger.php
Normal file
350
php/src/output/std/LogMessenger.php
Normal file
@ -0,0 +1,350 @@
|
||||
<?php
|
||||
namespace nulib\output\std;
|
||||
|
||||
use Exception;
|
||||
use nulib\cl;
|
||||
use nulib\output\IMessenger;
|
||||
|
||||
class LogMessenger extends AbstractMessenger {
|
||||
const ADD_DATE = true;
|
||||
|
||||
const SHOW_IDS = true;
|
||||
|
||||
function __construct(?array $params=null) {
|
||||
$output = $params["output"] ?? null;
|
||||
$color = $params["color"] ?? false;
|
||||
$indent = $params["indent"] ?? static::INDENT;
|
||||
|
||||
$defaultLevel = $params["default_level"] ?? null;
|
||||
$defaultLevel = self::verifix_level($defaultLevel ?? self::NORMAL);
|
||||
|
||||
$debug = boolval($params["debug"] ?? null);
|
||||
$minLevel = $params["min_level"] ?? null;
|
||||
if ($debug) $minLevel ??= self::DEBUG;
|
||||
$minLevel ??= $params["verbosity"] ?? null; # alias
|
||||
$minLevel = self::verifix_level($minLevel ?? self::NORMAL, self::NONE);
|
||||
|
||||
$addDate = boolval($params["add_date"] ?? static::ADD_DATE);
|
||||
$dateFormat = cl::get($params, "date_format", static::DATE_FORMAT);
|
||||
$id = $params["id"] ?? null;
|
||||
$showIds = $params["show_ids"] ?? static::SHOW_IDS;
|
||||
|
||||
$this->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;
|
||||
}
|
||||
|
||||
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->section__end();
|
||||
if (!$this->checkLevel($level)) return;
|
||||
$this->_printTitle(
|
||||
$level, "section", $this->getLinePrefix(), 0,
|
||||
$this->out, $content);
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($this);
|
||||
} finally {
|
||||
$this->section__afterFunc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 $this->title__last()["id"] ?? null;
|
||||
}
|
||||
|
||||
protected function title__end(?int $until=null): void {
|
||||
$title = $this->title__last();
|
||||
if ($title !== null) {
|
||||
$until ??= $title["max_title_level"];
|
||||
$until ??= $this->title__getMarks()[0] - 1;
|
||||
while (count($this->titles) > $until) {
|
||||
array_pop($this->titles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function &title__ref(): ?array {
|
||||
return $this->titles[array_key_last($this->titles)];
|
||||
}
|
||||
|
||||
function title__beforeFunc(array $marks): void {
|
||||
$title =& $this->title__ref();
|
||||
$title["max_title_level"] = $marks[0] + 1;
|
||||
}
|
||||
|
||||
function title__afterFunc(array $marks): void {
|
||||
$title =& $this->title__ref();
|
||||
$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();
|
||||
$this->titles[] = [
|
||||
"id" => $this->lastTitleId++,
|
||||
"title_level" => $marks[0],
|
||||
"max_title_level" => null,
|
||||
];
|
||||
$this->_printTitle(
|
||||
$level, "title", $this->getLinePrefix(), $marks[0],
|
||||
$this->out, $content);
|
||||
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;
|
||||
$titleLevel = $this->title__last()["title_level"] ?? 0;
|
||||
$this->_printGeneric(
|
||||
$level, "desc", $this->getLinePrefix(), $titleLevel,
|
||||
$this->out, $content);
|
||||
}
|
||||
|
||||
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 $this->action__last()["id"] ?? null;
|
||||
}
|
||||
|
||||
protected function action__end(?int $until=null): void {
|
||||
$action = $this->action__last();
|
||||
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 {
|
||||
}
|
||||
|
||||
protected function &action__ref(): ?array {
|
||||
return $this->actions[array_key_last($this->actions)];
|
||||
}
|
||||
|
||||
function action__beforeFunc(array $marks): void {
|
||||
$action =& $this->action__ref();
|
||||
$action["max_action_level"] = $marks[0] + 1;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
function action($content, ?callable $func=null, ?int $level=null): void {
|
||||
$this->checkLevel($level);
|
||||
$marks = $this->action__getMarks();
|
||||
$this->actions[] = [
|
||||
"id" => $this->lastActionId++,
|
||||
"action_level" => $marks[0],
|
||||
"max_action_level" => null,
|
||||
"action_aresult" => false,
|
||||
"msg_level" => $level
|
||||
];
|
||||
$this->_printAction(
|
||||
$level, $this->getLinePrefix(), $marks[0],
|
||||
$this->out,
|
||||
true, $content,
|
||||
false, null, null);
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$result = null;
|
||||
$this->action__beforeFunc($marks);
|
||||
$result = $func($this);
|
||||
} catch (Exception $e) {
|
||||
$this->afailure($e);
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->action__afterFunc($marks, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function step($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$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 =& $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 =& $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 =& $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();
|
||||
}
|
||||
|
||||
function aresult($result=null, ?int $overrideLevel=null): void {
|
||||
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);
|
||||
}
|
||||
|
||||
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", $this->getIndentLevel(),
|
||||
$this->out, $content);
|
||||
}
|
||||
|
||||
function info($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "info", $this->getIndentLevel(),
|
||||
$this->out, $content);
|
||||
}
|
||||
|
||||
function note($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "note", $this->getIndentLevel(),
|
||||
$this->out, $content);
|
||||
}
|
||||
|
||||
function warning($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "warning", $this->getIndentLevel(),
|
||||
$this->out, $content);
|
||||
}
|
||||
|
||||
function error($content, ?int $level=null): void {
|
||||
$this->_printGenericOrException(
|
||||
$level, "error", $this->getIndentLevel(),
|
||||
$this->out, $content);
|
||||
}
|
||||
|
||||
function end(bool $all=false): void {
|
||||
if ($all) {
|
||||
while ($this->actions) $this->adone();
|
||||
while ($this->titles) $this->title__end();
|
||||
} elseif ($this->actions) {
|
||||
$this->action__end();
|
||||
} elseif ($this->titles) {
|
||||
$this->title__end();
|
||||
}
|
||||
}
|
||||
}
|
61
php/src/output/std/NullMessenger.php
Normal file
61
php/src/output/std/NullMessenger.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace nulib\output\std;
|
||||
|
||||
use nulib\output\IMessenger;
|
||||
|
||||
class NullMessenger implements IMessenger {
|
||||
function resetParams(?array $params=null): void {
|
||||
}
|
||||
|
||||
function clone(?array $params=null): self {
|
||||
return clone $this;
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
function asuccess($content=null, ?int $overrideLevel=null): void {
|
||||
}
|
||||
|
||||
function afailure($content=null, ?int $overrideLevel=null): void {
|
||||
}
|
||||
|
||||
function adone($content=null, ?int $overrideLevel=null): void {
|
||||
}
|
||||
|
||||
function aresult($result=null, ?int $overrideLevel=null): void {
|
||||
}
|
||||
|
||||
function print($content, ?int $level=null): void {
|
||||
}
|
||||
|
||||
function info($content, ?int $level=null): void {
|
||||
}
|
||||
|
||||
function note($content, ?int $level=null): void {
|
||||
}
|
||||
|
||||
function warning($content, ?int $level=null): void {
|
||||
}
|
||||
|
||||
function error($content, ?int $level=null): void {
|
||||
}
|
||||
|
||||
function end(bool $all=false): void {
|
||||
}
|
||||
}
|
@ -1,28 +1,39 @@
|
||||
<?php
|
||||
namespace nulib\output\std;
|
||||
|
||||
use Exception;
|
||||
use nulib\output\IMessenger;
|
||||
|
||||
/**
|
||||
* Class ProxyMessenger: un proxy vers ou un plusieurs instances de IMessenger
|
||||
*
|
||||
* NB: si cette classe est instanciée sans argument, elle agit comme un
|
||||
* "NullMessenger", c'est à dire une instance qui envoie tous les messages vers
|
||||
* /dev/null
|
||||
* 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) {
|
||||
$this->msgs = [];
|
||||
foreach ($msgs as $msg) {
|
||||
if ($msg !== null) $this->msgs[] = $msg;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var IMessenger[] */
|
||||
protected $msgs;
|
||||
/** @var _IMessenger[] */
|
||||
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,92 +41,180 @@ class ProxyMessenger implements IMessenger {
|
||||
}; unset($msg);
|
||||
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($content, ?callable $func=null, ?int $level=null): void {
|
||||
$useFunc = false;
|
||||
$untils = [];
|
||||
foreach ($this->msgs as $msg) {
|
||||
|
||||
function title__getMarks(): array {
|
||||
$marks = [];
|
||||
foreach ($this->msgs as $key => $msg) {
|
||||
if ($msg instanceof _IMessenger) {
|
||||
$useFunc = true;
|
||||
$untils[] = $msg->_getTitleMark();
|
||||
$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 {
|
||||
$marks = $this->title__getMarks();
|
||||
foreach ($this->msgs as $msg) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 = [];
|
||||
|
||||
function desc($content, ?int $level=null): void {
|
||||
foreach ($this->msgs as $msg) {
|
||||
$msg->desc($content, $level);
|
||||
}
|
||||
}
|
||||
|
||||
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, $result): void {
|
||||
foreach ($this->msgs as $key => $msg) {
|
||||
if ($msg instanceof _IMessenger) {
|
||||
$msg->action__afterFunc($marks[$key], $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = null;
|
||||
$this->action__beforeFunc($marks);
|
||||
$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;
|
||||
} finally {
|
||||
/** @var _IMessenger $msg */
|
||||
$index = 0;
|
||||
foreach ($this->msgs as $msg) {
|
||||
$msg->_endAction($untils[$index++]);
|
||||
}
|
||||
$this->action__afterFunc($marks, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,723 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\output\std;
|
||||
|
||||
use Exception;
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\ExceptionShadow;
|
||||
use nulib\output\IMessenger;
|
||||
use nulib\UserException;
|
||||
use Throwable;
|
||||
|
||||
class StdMessenger implements _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,
|
||||
];
|
||||
|
||||
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!", "===", "<color @b>=", "=</color>", "==="],
|
||||
"title" => [false, "TITLE!", null, "<color @b>T", "</color>", "==="],
|
||||
"desc" => ["DESC!", "<color @b>></color>", ""],
|
||||
"error" => ["CRIT.ERROR!", "<color @r>E!", "</color>"],
|
||||
"warning" => ["CRIT.WARNING!", "<color @y>W!", "</color>"],
|
||||
"note" => ["ATTENTION!", "<color @g>N!", "</color>"],
|
||||
"info" => ["IMPORTANT!", "<color @b>N!", "</color>"],
|
||||
"step" => ["*", "<color @w>.</color>", ""],
|
||||
"print" => [null, null, null],
|
||||
],
|
||||
self::NORMAL => [
|
||||
"section" => [true, "SECTION:", "---", "<color @b>-", "-</color>", "---"],
|
||||
"title" => [false, "TITLE:", null, "<color @b>T</color><color b>", "</color>", "---"],
|
||||
"desc" => ["DESC:", "<color @b>></color>", ""],
|
||||
"error" => ["ERROR:", "<color @r>E</color><color r>", "</color>"],
|
||||
"warning" => ["WARNING:", "<color @y>W</color><color y>", "</color>"],
|
||||
"note" => ["NOTE:", "<color @g>N</color>", ""],
|
||||
"info" => ["INFO:", "<color @b>I</color>", ""],
|
||||
"step" => ["*", "<color @w>.</color>", ""],
|
||||
"print" => [null, null, null],
|
||||
],
|
||||
self::MINOR => [
|
||||
"section" => [true, "section", null, "<color @w>>>", "<<</color>", null],
|
||||
"title" => [false, "title", null, "<color b>t", "</color>", null],
|
||||
"desc" => ["desc", "<color b>></color>", ""],
|
||||
"error" => ["error", "<color r>E</color><color -r>", "</color>"],
|
||||
"warning" => ["warning", "<color y>W</color><color -y>", "</color>"],
|
||||
"note" => ["note", "<color g>N</color>", ""],
|
||||
"info" => ["info", "<color b>I</color><color w>", "</color>"],
|
||||
"step" => ["*", "<color w>.</color>", ""],
|
||||
"print" => [null, null, null],
|
||||
],
|
||||
self::DEBUG => [
|
||||
"section" => [true, "section", null, "<color @w>>>", "<<</color>", null],
|
||||
"title" => [false, "title", null, "<color b>t", "</color>", null],
|
||||
"desc" => ["desc", "<color b>></color>", ""],
|
||||
"error" => ["debugE", "<color r>e</color><color -r>", "</color>"],
|
||||
"warning" => ["debugW", "<color y>w</color><color -y>", "</color>"],
|
||||
"note" => ["debugN", "<color b>i</color>", ""],
|
||||
"info" => ["debug", "<color @w>D</color><color w>", "</color>"],
|
||||
"step" => ["*", "<color w>.</color>", ""],
|
||||
"print" => [null, null, null],
|
||||
],
|
||||
];
|
||||
|
||||
const RESULT_PREFIXES = [
|
||||
"failure" => ["(FAILURE)", "<color r>✘</color>"],
|
||||
"success" => ["(SUCCESS)", "<color @g>✔</color>"],
|
||||
"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 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) {
|
||||
if ($printActions) { $this->printActions(); $printActions = false; }
|
||||
$this->_printGeneric($linePrefix, $level, $type, $userMessage, $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);
|
||||
$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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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('/<color([^>]*)>/', [self::class, "replace_colors"], $text);
|
||||
@ -178,6 +179,7 @@ class StdOutput {
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
function filterColors(string $text): string {
|
||||
return preg_replace('/\x1B\[.*?m/', "", $text);
|
||||
}
|
||||
|
@ -7,13 +7,86 @@ use nulib\output\IMessenger;
|
||||
* Interface _IMessenger: méthodes privées de IMessenger
|
||||
*/
|
||||
interface _IMessenger extends IMessenger {
|
||||
function _endSection(): void;
|
||||
const INDENT = " ";
|
||||
|
||||
function _getTitleMark(): int;
|
||||
const DATE_FORMAT = 'Y-m-d\TH:i:s.u';
|
||||
|
||||
function _endTitle(?int $until=null): void;
|
||||
const VALID_LEVELS = [self::DEBUG, self::MINOR, self::NORMAL, self::MAJOR, self::NONE];
|
||||
|
||||
function _getActionMark(): int;
|
||||
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,
|
||||
];
|
||||
|
||||
function _endAction(?int $until=null): void;
|
||||
const GENERIC_PREFIXES = [
|
||||
self::MAJOR => [
|
||||
"section" => [true, "SECTION!", "===", "<color @b>=", "=</color>", "==="],
|
||||
"title" => [false, "TITLE!", null, "<color @b>T", "</color>", "==="],
|
||||
"desc" => ["DESC!", "<color @b>></color>", ""],
|
||||
"error" => ["CRIT.ERROR!", "<color @r>E!", "</color>"],
|
||||
"warning" => ["CRIT.WARNING!", "<color @y>W!", "</color>"],
|
||||
"note" => ["ATTENTION!", "<color @g>N!", "</color>"],
|
||||
"info" => ["IMPORTANT!", "<color @b>N!", "</color>"],
|
||||
"step" => ["*", "<color @w>.</color>", ""],
|
||||
"print" => [null, null, null],
|
||||
],
|
||||
self::NORMAL => [
|
||||
"section" => [true, "SECTION:", "---", "<color @b>-", "-</color>", "---"],
|
||||
"title" => [false, "TITLE:", null, "<color @b>T</color><color b>", "</color>", "---"],
|
||||
"desc" => ["DESC:", "<color @b>></color>", ""],
|
||||
"error" => ["ERROR:", "<color @r>E</color><color r>", "</color>"],
|
||||
"warning" => ["WARNING:", "<color @y>W</color><color y>", "</color>"],
|
||||
"note" => ["NOTE:", "<color @g>N</color>", ""],
|
||||
"info" => ["INFO:", "<color @b>I</color>", ""],
|
||||
"step" => ["*", "<color @w>.</color>", ""],
|
||||
"print" => [null, null, null],
|
||||
],
|
||||
self::MINOR => [
|
||||
"section" => [true, "section", null, "<color @w>>>", "<<</color>", null],
|
||||
"title" => [false, "title", null, "<color b>t", "</color>", null],
|
||||
"desc" => ["desc", "<color b>></color>", ""],
|
||||
"error" => ["error", "<color r>E</color><color -r>", "</color>"],
|
||||
"warning" => ["warning", "<color y>W</color><color -y>", "</color>"],
|
||||
"note" => ["note", "<color g>N</color>", ""],
|
||||
"info" => ["info", "<color b>I</color><color w>", "</color>"],
|
||||
"step" => ["*", "<color w>.</color>", ""],
|
||||
"print" => [null, null, null],
|
||||
],
|
||||
self::DEBUG => [
|
||||
"section" => [true, "section", null, "<color @w>>>", "<<</color>", null],
|
||||
"title" => [false, "title", null, "<color b>t", "</color>", null],
|
||||
"desc" => ["desc", "<color b>></color>", ""],
|
||||
"error" => ["debugE", "<color r>e</color><color -r>", "</color>"],
|
||||
"warning" => ["debugW", "<color y>w</color><color -y>", "</color>"],
|
||||
"note" => ["debugN", "<color b>i</color>", ""],
|
||||
"info" => ["debug", "<color @w>D</color><color w>", "</color>"],
|
||||
"step" => ["*", "<color w>.</color>", ""],
|
||||
"print" => [null, null, null],
|
||||
],
|
||||
];
|
||||
|
||||
const RESULT_PREFIXES = [
|
||||
"failure" => ["(FAILURE)", "<color r>✘</color>"],
|
||||
"success" => ["(SUCCESS)", "<color @g>✔</color>"],
|
||||
"done" => [null, null],
|
||||
];
|
||||
|
||||
function section__afterFunc(): void;
|
||||
|
||||
/** @return int[] */
|
||||
function title__getMarks(): array;
|
||||
/** @param int[] $marks */
|
||||
function title__beforeFunc(array $marks): void;
|
||||
/** @param int[] $marks */
|
||||
function title__afterFunc(array $marks): void;
|
||||
|
||||
/** @return int[] */
|
||||
function action__getMarks(): array;
|
||||
/** @param int[] $marks */
|
||||
function action__beforeFunc(array $marks): void;
|
||||
/** @param int[] $marks */
|
||||
function action__afterFunc(array $marks, $result): void;
|
||||
}
|
||||
|
15
php/src/output/web.php
Normal file
15
php/src/output/web.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
/**
|
||||
* Class web: afficher un message à l'utilisateur en HTML
|
||||
*
|
||||
* Cette classe est utilisable directement sans initialisation préalable.
|
||||
*/
|
||||
class web extends _messenger {
|
||||
use _TMessenger;
|
||||
|
||||
static function get(): IMessenger {
|
||||
return static::$msg ??= new WebMessenger();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeZone;
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* Class Date: une date
|
||||
@ -9,9 +9,14 @@ use DateTimeZone;
|
||||
class Date extends DateTime {
|
||||
const DEFAULT_FORMAT = "d/m/Y";
|
||||
|
||||
function __construct($datetime="now", DateTimeZone $timezone=null) {
|
||||
parent::__construct($datetime, $timezone);
|
||||
$this->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 {
|
||||
|
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
use InvalidArgumentException;
|
||||
use nulib\str;
|
||||
|
||||
/**
|
||||
* Class DateTime: une date et une heure
|
||||
@ -24,252 +24,78 @@ use nulib\str;
|
||||
* @property-read string $YmdHMS
|
||||
* @property-read string $YmdHMSZ
|
||||
*/
|
||||
class DateTime extends \DateTime {
|
||||
static function with($datetime): self {
|
||||
if ($datetime instanceof static) return $datetime;
|
||||
else return new static($datetime);
|
||||
}
|
||||
|
||||
static function withn($datetime): ?self {
|
||||
if ($datetime === null) return null;
|
||||
elseif ($datetime instanceof static) return $datetime;
|
||||
else return new static($datetime);
|
||||
}
|
||||
|
||||
static function ensure(&$datetime): void {
|
||||
$datetime = static::withn($datetime);
|
||||
}
|
||||
|
||||
const DMY_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))?$/';
|
||||
const YMD_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})$/';
|
||||
const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/';
|
||||
const YMDHISZ_PATTERN = '/^((?:\d{2})?\d{2})-?(\d{2})-?(\d{2})[tT](\d{2}):?(\d{2}):?(\d{2})?([zZ]|\+\d{2}:?\d{2})?$/';
|
||||
|
||||
|
||||
protected 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;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
protected 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];
|
||||
}
|
||||
|
||||
static function isa($datetime): bool {
|
||||
if ($datetime === null) return false;
|
||||
if ($datetime instanceof DateTimeInterface) return true;
|
||||
if (is_int($datetime)) return true;
|
||||
if (is_string($datetime)) {
|
||||
return preg_match(self::DMY_PATTERN, $datetime) ||
|
||||
preg_match(self::YMD_PATTERN, $datetime) ||
|
||||
preg_match(self::DMYHIS_PATTERN, $datetime) ||
|
||||
preg_match(self::YMDHISZ_PATTERN, $datetime);
|
||||
}
|
||||
if (is_array($datetime)) {
|
||||
return self::parse_array($datetime) !== null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static function isa_datetime($datetime, bool $frOnly=false): bool {
|
||||
if ($datetime === null) return false;
|
||||
if ($datetime instanceof DateTimeInterface) return true;
|
||||
if (is_int($datetime)) return true;
|
||||
if (is_string($datetime)) {
|
||||
return preg_match(self::DMYHIS_PATTERN, $datetime) ||
|
||||
(!$frOnly && preg_match(self::YMDHISZ_PATTERN, $datetime));
|
||||
}
|
||||
if (is_array($datetime)) {
|
||||
return self::parse_array($datetime) !== null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static function isa_date($date, bool $frOnly=false): bool {
|
||||
if ($date === null) return false;
|
||||
if ($date instanceof DateTimeInterface) return true;
|
||||
if (is_int($date)) return true;
|
||||
if (is_string($date)) {
|
||||
return preg_match(self::DMY_PATTERN, $date) ||
|
||||
(!$frOnly && preg_match(self::YMD_PATTERN, $date));
|
||||
}
|
||||
if (is_array($date)) {
|
||||
return self::parse_array($date) !== null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** retourner le nombre de secondes depuis minuit */
|
||||
static function _nbsecs_format(\DateTime $datetime): string {
|
||||
[$h, $m, $s] = explode(",", $datetime->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")));
|
||||
}
|
||||
}
|
||||
|
@ -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],
|
||||
@ -39,8 +44,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,10 +93,10 @@ class Delay {
|
||||
}
|
||||
|
||||
function __construct($delay, ?DateTimeInterface $from=null) {
|
||||
if ($from === null) $from = new DateTime();
|
||||
if ($delay === "INF") {
|
||||
$dest = DateTime::with($from)->clone();
|
||||
$dest->add(new DateInterval("P9999Y"));
|
||||
$from = MutableDateTime::with($from)->clone(true);
|
||||
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);
|
||||
@ -116,37 +120,53 @@ class Delay {
|
||||
}
|
||||
|
||||
function __clone() {
|
||||
$this->dest = clone $this->dest;
|
||||
if ($this->dest !== null) {
|
||||
$this->dest = clone $this->dest;
|
||||
}
|
||||
}
|
||||
|
||||
function __serialize(): array {
|
||||
return [$this->dest, $this->repr];
|
||||
$dest = $this->dest;
|
||||
if ($dest !== null) $dest = $dest->clone();
|
||||
return [$dest, $this->repr];
|
||||
}
|
||||
function __unserialize(array $data): void {
|
||||
[$this->dest, $this->repr] = $data;
|
||||
[$dest, $this->repr] = $data;
|
||||
if ($dest !== null) $dest = $dest->clone(true);
|
||||
$this->dest = $dest;
|
||||
}
|
||||
|
||||
/** @var DateTime */
|
||||
protected $dest;
|
||||
protected ?MutableDateTime $dest;
|
||||
|
||||
function getDest(): DateTime {
|
||||
return $this->dest;
|
||||
$dest = $this->dest;
|
||||
if ($dest === null) {
|
||||
$dest = new MutableDateTime();
|
||||
$dest->add(new \DateInterval(self::INF_INTERVAL));
|
||||
}
|
||||
return $dest->clone();
|
||||
}
|
||||
|
||||
function addDuration($duration) {
|
||||
if (is_numeric($duration) && $duration < 0) {
|
||||
$this->dest->sub(DateInterval::with(-$duration));
|
||||
} else {
|
||||
$this->dest->add(DateInterval::with($duration));
|
||||
function addDuration($duration): self {
|
||||
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) {
|
||||
if (is_numeric($duration) && $duration < 0) {
|
||||
$this->dest->add(DateInterval::with(-$duration));
|
||||
} else {
|
||||
$this->dest->sub(DateInterval::with($duration));
|
||||
function subDuration($duration): self {
|
||||
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;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
@ -156,23 +176,20 @@ class Delay {
|
||||
return $this->repr;
|
||||
}
|
||||
|
||||
protected function _getDiff(?DateTimeInterface $now=null): \DateInterval {
|
||||
if ($now === null) $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;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* Class Elapsed: durée entre deux événements
|
||||
*/
|
||||
@ -123,20 +125,20 @@ class Elapsed {
|
||||
$d = intdiv($rs, $oneDay);
|
||||
return self::format_generic($prefix, $d, 0, 0);
|
||||
}
|
||||
|
||||
static function format_at(DateTime $start, ?DateTime $now=null, ?int $resolution=null): string {
|
||||
|
||||
static function format_at(DateTimeInterface $start, ?DateTimeInterface $now=null, ?int $resolution=null): string {
|
||||
$now ??= new DateTime();
|
||||
$seconds = $now->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();
|
||||
|
24
php/src/php/time/MutableDate.php
Normal file
24
php/src/php/time/MutableDate.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
|
||||
class MutableDate extends MutableDateTime {
|
||||
const DEFAULT_FORMAT = "d/m/Y";
|
||||
|
||||
function __construct($datetime="now", DateTimeZone $timezone=null) {
|
||||
parent::__construct($datetime, $timezone);
|
||||
$this->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);
|
||||
}
|
||||
}
|
180
php/src/php/time/MutableDateTime.php
Normal file
180
php/src/php/time/MutableDateTime.php
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class DateTime: une date et une heure mutables
|
||||
*
|
||||
* @property-read int $year
|
||||
* @property-read int $month
|
||||
* @property-read int $day
|
||||
* @property-read int $hour
|
||||
* @property-read int $minute
|
||||
* @property-read int $second
|
||||
* @property-read int $wday
|
||||
* @property-read int $wnum
|
||||
* @property-read string $timezone
|
||||
* @property-read string $datetime
|
||||
* @property-read string $date
|
||||
* @property-read string $Ymd
|
||||
* @property-read string $YmdHMS
|
||||
* @property-read string $YmdHMSZ
|
||||
*/
|
||||
class MutableDateTime extends \DateTime {
|
||||
use _TDateTime;
|
||||
|
||||
const DEFAULT_FORMAT = "d/m/Y H:i:s";
|
||||
|
||||
/**
|
||||
* $datetime est une spécification de date, avec ou sans fuseau horaire
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
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);
|
||||
|
||||
} 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;
|
||||
}
|
||||
function getStartOfDay(): self {
|
||||
return $this->clone(true)->setStartOfDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 getEndOfDay(): self {
|
||||
return $this->clone(true)->setEndOfDay();
|
||||
}
|
||||
|
||||
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 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) {
|
||||
$wday = $this->wday;
|
||||
if ($wday > 5) $nbDays = 8 - $this->wday;
|
||||
}
|
||||
$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);
|
||||
}
|
||||
}
|
@ -1,10 +1,3 @@
|
||||
# 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
|
||||
|
||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
103
php/src/php/time/_TDateTime.php
Normal file
103
php/src/php/time/_TDateTime.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
trait _TDateTime {
|
||||
static function with($datetime): self {
|
||||
if ($datetime instanceof static) return $datetime;
|
||||
else return new static($datetime);
|
||||
}
|
||||
|
||||
static function withn($datetime): ?self {
|
||||
if ($datetime === null) return null;
|
||||
elseif ($datetime instanceof static) return $datetime;
|
||||
else return new static($datetime);
|
||||
}
|
||||
|
||||
static function ensure(&$datetime): void {
|
||||
$datetime = static::withn($datetime);
|
||||
}
|
||||
|
||||
static function isa($datetime): bool {
|
||||
if ($datetime === null) return false;
|
||||
if ($datetime instanceof DateTimeInterface) return true;
|
||||
if (is_int($datetime)) return true;
|
||||
if (is_string($datetime)) {
|
||||
return preg_match(_utils::DMY_PATTERN, $datetime) ||
|
||||
preg_match(_utils::YMD_PATTERN, $datetime) ||
|
||||
preg_match(_utils::DMYHIS_PATTERN, $datetime) ||
|
||||
preg_match(_utils::YMDHISZ_PATTERN, $datetime);
|
||||
}
|
||||
if (is_array($datetime)) return _utils::parse_array($datetime) !== null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static function isa_datetime($datetime, bool $frOnly=false): bool {
|
||||
if ($datetime === null) return false;
|
||||
if ($datetime instanceof DateTimeInterface) return true;
|
||||
if (is_int($datetime)) return true;
|
||||
if (is_string($datetime)) {
|
||||
return preg_match(_utils::DMYHIS_PATTERN, $datetime) ||
|
||||
(!$frOnly && preg_match(_utils::YMDHISZ_PATTERN, $datetime));
|
||||
}
|
||||
if (is_array($datetime)) return _utils::parse_array($datetime) !== null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static function isa_date($date, bool $frOnly=false): bool {
|
||||
if ($date === null) return false;
|
||||
if ($date instanceof DateTimeInterface) return true;
|
||||
if (is_int($date)) return true;
|
||||
if (is_string($date)) {
|
||||
return preg_match(_utils::DMY_PATTERN, $date) ||
|
||||
(!$frOnly && preg_match(_utils::YMD_PATTERN, $date));
|
||||
}
|
||||
if (is_array($date)) return _utils::parse_array($date) !== null;
|
||||
return false;
|
||||
}
|
||||
|
||||
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, _utils::INT_FORMATS)) {
|
||||
$format = _utils::INT_FORMATS[$format];
|
||||
} elseif (array_key_exists($format, _utils::STRING_FORMATS)) {
|
||||
$format = _utils::STRING_FORMATS[$format];
|
||||
}
|
||||
if (is_callable($format)) return $format($this);
|
||||
else return parent::format($format);
|
||||
}
|
||||
|
||||
function __toString(): string {
|
||||
return $this->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);
|
||||
}
|
||||
}
|
147
php/src/php/time/_utils.php
Normal file
147
php/src/php/time/_utils.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use nulib\str;
|
||||
|
||||
class _utils {
|
||||
const DMY_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))?$/';
|
||||
const YMD_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})$/';
|
||||
const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/';
|
||||
const YMDHISZ_PATTERN = '/^((?:\d{2})?\d{2})-?(\d{2})-?(\d{2})[tT](\d{2}):?(\d{2}):?(\d{2})?([zZ]|\+\d{2}:?\d{2})?$/';
|
||||
|
||||
/** retourner le nombre de secondes depuis minuit */
|
||||
static function _nbsecs_format(\DateTimeInterface $datetime): string {
|
||||
[$h, $m, $s] = explode(",", $datetime->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];
|
||||
}
|
||||
}
|
@ -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"];
|
||||
|
11
php/src/ref/ref_cache.php
Normal file
11
php/src/ref/ref_cache.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace nulib\ref;
|
||||
|
||||
/**
|
||||
* Class ref_cache: référence des durées de mise en cache
|
||||
*/
|
||||
class ref_cache {
|
||||
const MINUTE = 60;
|
||||
const HOUR = 60 * self::MINUTE;
|
||||
const DAY = 24 * self::HOUR;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nulib\ref\file\csv;
|
||||
namespace nulib\ref;
|
||||
|
||||
/**
|
||||
* Class ref_csv: références des valeurs normalisées pour les fichiers CSV à
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nulib\ref\php;
|
||||
namespace nulib\ref;
|
||||
|
||||
class ref_func {
|
||||
const CALL_ALL_PARAMS_SCHEMA = [
|
15
php/src/ref/ref_jquery.php
Normal file
15
php/src/ref/ref_jquery.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace nulib\ref;
|
||||
|
||||
class ref_jquery {
|
||||
const HAVE_JQUERY = true;
|
||||
|
||||
function printJquery(): void {
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
jQuery.noConflict()(function($) {
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace nulib\ref\web;
|
||||
namespace nulib\ref;
|
||||
|
||||
class ref_mimetypes {
|
||||
const TXT = "text/plain";
|
22
php/src/ref/ref_profiles.php
Normal file
22
php/src/ref/ref_profiles.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace nulib\ref;
|
||||
|
||||
/**
|
||||
* Class ref_profiles: noms de profils normalisés
|
||||
*/
|
||||
class ref_profiles {
|
||||
const PROD = "prod";
|
||||
const TEST = "test";
|
||||
const DEVEL = "devel";
|
||||
|
||||
const PROFILES = [
|
||||
self::PROD,
|
||||
self::TEST,
|
||||
self::DEVEL,
|
||||
];
|
||||
|
||||
const PRODUCTION_MODES = [
|
||||
self::PROD => true,
|
||||
self::TEST => true,
|
||||
];
|
||||
}
|
@ -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 ValueException::invalid_kind($camel, "camel string");
|
||||
throw exceptions::invalid_value($camel, "camel string");
|
||||
}
|
||||
$parts[] = strtolower($ms[1][0]);
|
||||
$index = intval($ms[1][1]) + strlen($ms[1][0]);
|
||||
|
@ -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,18 +104,29 @@ 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;
|
||||
}
|
||||
|
||||
function isMasculin(): bool {
|
||||
return !$this->fem;
|
||||
}
|
||||
|
||||
function isFeminin(): bool {
|
||||
return $this->fem;
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner le mot sans article
|
||||
*
|
||||
@ -168,15 +197,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 +232,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 +257,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 +282,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 +315,10 @@ class Word {
|
||||
}
|
||||
}
|
||||
|
||||
function articleAu(): string {
|
||||
return $this->au;
|
||||
}
|
||||
|
||||
function au(int $amount=1): string {
|
||||
$abs_amount = abs($amount);
|
||||
if ($abs_amount == 0) {
|
||||
|
@ -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";
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user