Compare commits
79 Commits
Author | SHA1 | Date | |
---|---|---|---|
d63a0cb704 | |||
91beea9c29 | |||
0e9be5f221 | |||
5e141b575e | |||
8ee13a85c0 | |||
2af417a193 | |||
d4cc8bfa42 | |||
ca129dfda4 | |||
2a50167241 | |||
146461a184 | |||
2a46c12e08 | |||
d241ce6561 | |||
86136e75a5 | |||
64c872cf3f | |||
ebbd9e06c0 | |||
5c6d55ed46 | |||
bab9ba81fe | |||
ecd01777c1 | |||
bd1f901b70 | |||
1536e091fb | |||
a8d55d329a | |||
60ab13ff84 | |||
f2614385fe | |||
5f3ea483da | |||
39bc8fed3b | |||
5beb5e6621 | |||
0e9c4e971d | |||
c3357de203 | |||
df9bc0d971 | |||
2e61d7bc21 | |||
2fd5b9933d | |||
8cbe9c7654 | |||
2cdc059810 | |||
927915093a | |||
3df9ed2bba | |||
cd16509af1 | |||
cf9fab5273 | |||
5c5d8784a4 | |||
3def99b378 | |||
953f614caa | |||
9723c146d5 | |||
5ae3e8b100 | |||
cfc386dff8 | |||
d44537a9bb | |||
e8abaca6ae | |||
a3116e5dac | |||
7b12600848 | |||
a85cd2faf4 | |||
0d2ca2013d | |||
bd0da9cffe | |||
7e05caf4d7 | |||
9def939cf1 | |||
01c65a6e6a | |||
8e3569ac4b | |||
ead5b5adcf | |||
882549375c | |||
e129e0aa7f | |||
9a2378ba74 | |||
0cfad79ec0 | |||
8cfd803ead | |||
d9989c6be7 | |||
7eb5efb421 | |||
92363cd543 | |||
3b379eb799 | |||
c64b0801e2 | |||
745e5420df | |||
3d55e81899 | |||
0f4636fa77 | |||
bbb55599f7 | |||
939f7726ab | |||
192a7f52c3 | |||
f191506035 | |||
a964a23a9a | |||
62b9230bff | |||
608ac724ee | |||
0d15cb9192 | |||
2163ea992e | |||
d3c2299c13 | |||
1b188f29a1 |
8
.composer.pman.yml
Normal file
8
.composer.pman.yml
Normal file
@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
|
||||
|
||||
composer:
|
||||
profiles: [ dev, dist ]
|
||||
dev:
|
||||
link: true
|
||||
dist:
|
||||
link: false
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
/test_*
|
||||
|
||||
/.composer.lock.runphp
|
||||
|
||||
.~lock*#
|
||||
|
48
CHANGES.md
48
CHANGES.md
@ -1,3 +1,51 @@
|
||||
## Release 0.4.1p82 du 25/03/2025-08:47
|
||||
|
||||
## Release 0.4.1p74 du 25/03/2025-08:47
|
||||
|
||||
* `5beb5e6` corriger la prise en compte du proxy
|
||||
* `0e9c4e9` renommer fichier de config pman
|
||||
* `2fd5b99` support fichiers template
|
||||
* `8cbe9c7` template ignore les fichiers binaires (implémentation gros doigt)
|
||||
|
||||
## Release 0.4.0p82 du 14/03/2025-15:24
|
||||
|
||||
## Release 0.4.0p74 du 14/03/2025-15:23
|
||||
|
||||
* `cf9fab5` maj src/php
|
||||
* `5c5d878` pdev: option --force-merge
|
||||
* `9723c14` ajout cl::same_keys()
|
||||
* `5ae3e8b` pdev: ajout --after-merge
|
||||
* `cfc386d` Revert "prel/pdev: tracer les hooks"
|
||||
* `d44537a` prel/pdev: tracer les hooks
|
||||
* `e8abaca` supprimer tests qui sont encore dans nur/ture
|
||||
* `a3116e5` ajout du schéma
|
||||
* `0d2ca20` prel/pdev: option --fake
|
||||
* `bd0da9c` prel/pdev: ajouter les hook BEFORE_*
|
||||
* `7e05caf` runphp: passer les arguments inchangés à composer
|
||||
* `9def939` p: support des projets dépendants composer
|
||||
* `01c65a6` tests verbosity et interaction
|
||||
* `8e3569a` ne plus utiliser tooenc par défaut. renommer tooenc en uecho
|
||||
* `ead5b5a` support automatique options verbosity et interaction
|
||||
* `8825493` pman: tenir compte des branches distantes
|
||||
* `e129e0a` ajout pwip
|
||||
* `9a2378b` pman: améliorer l'ergonomie
|
||||
* `8cfd803` gestion des profils composer
|
||||
* `d9989c6` supprimer composer.phar puisque c'est fourni par runphp
|
||||
* `7eb5efb` ajout config .pman.yml
|
||||
* `92363cd` pman: option --force-create
|
||||
* `3b379eb` maj doc
|
||||
* `bbb5559` pman: ajout init pman
|
||||
* `939f772` pman: support des config standard nommées
|
||||
|
||||
## Release 0.3.4p82 du 01/03/2025-06:23
|
||||
|
||||
## Release 0.3.4p74 du 01/03/2025-06:22
|
||||
|
||||
* `62b9230` pdev: ne pas inscrire delete si cette opération est interdite
|
||||
* `2163ea9` pdev/prel: scripts post merge/release
|
||||
|
||||
## Release 0.3.2p82 du 28/02/2025-20:30
|
||||
|
||||
## Release 0.3.2p74 du 28/02/2025-20:28
|
||||
|
||||
(tech) Migration vers pman
|
||||
|
@ -1 +1 @@
|
||||
0.3.2
|
||||
0.4.1
|
||||
|
@ -6,15 +6,15 @@ function __esection() {
|
||||
local length="${COLUMNS:-80}"
|
||||
setx lsep=__complete "$prefix" "$length" -
|
||||
|
||||
tooenc "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
||||
recho "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
||||
[ -n "$*" ] || return 0
|
||||
length=$((length - 1))
|
||||
setx -a lines=echo "$1"
|
||||
for line in "${lines[@]}"; do
|
||||
setx line=__complete "$prefix- $line" "$length"
|
||||
tooenc "$COULEUR_BLEUE$line-$COULEUR_NORMALE"
|
||||
recho "$COULEUR_BLEUE$line-$COULEUR_NORMALE"
|
||||
done
|
||||
tooenc "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
||||
recho "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
||||
}
|
||||
function __etitle() {
|
||||
local -a lines; local maxlen=0
|
||||
@ -23,10 +23,10 @@ function __etitle() {
|
||||
setx -a lines=echo "$1"
|
||||
for line in "${lines[@]}"; do
|
||||
[ ${#line} -gt $maxlen ] && maxlen=${#line}
|
||||
tooenc "${prefix}${COULEUR_BLEUE}T $line$COULEUR_NORMALE"
|
||||
recho "${prefix}${COULEUR_BLEUE}T $line$COULEUR_NORMALE"
|
||||
done
|
||||
maxlen=$((maxlen + 2))
|
||||
tooenc "${prefix}${COULEUR_BLEUE}T$(__complete "" $maxlen -)${COULEUR_NORMALE}"
|
||||
recho "${prefix}${COULEUR_BLEUE}T$(__complete "" $maxlen -)${COULEUR_NORMALE}"
|
||||
}
|
||||
function __edesc() {
|
||||
local -a lines
|
||||
@ -34,7 +34,7 @@ function __edesc() {
|
||||
|
||||
setx -a lines=echo "$1"
|
||||
for line in "${lines[@]}"; do
|
||||
tooenc "${prefix}${COULEUR_BLEUE}>${COULEUR_NORMALE} $line"
|
||||
recho "${prefix}${COULEUR_BLEUE}>${COULEUR_NORMALE} $line"
|
||||
done
|
||||
}
|
||||
function __ebanner() {
|
||||
@ -43,35 +43,35 @@ function __ebanner() {
|
||||
local length="${COLUMNS:-80}"
|
||||
setx lsep=__complete "$prefix" "$length" =
|
||||
|
||||
tooenc "$COULEUR_ROUGE$lsep"
|
||||
recho "$COULEUR_ROUGE$lsep"
|
||||
length=$((length - 1))
|
||||
setx -a lines=echo "$1"
|
||||
for line in "" "${lines[@]}" ""; do
|
||||
setx line=__complete "$prefix= $line" "$length"
|
||||
tooenc "$line="
|
||||
recho "$line="
|
||||
done
|
||||
tooenc "$lsep$COULEUR_NORMALE"
|
||||
recho "$lsep$COULEUR_NORMALE"
|
||||
}
|
||||
function __eimportant() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}!${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __eattention() { tooenc "$(__edate)$(__eindent0)${COULEUR_JAUNE}*${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __eerror() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}E${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __ewarn() { tooenc "$(__edate)$(__eindent0)${COULEUR_JAUNE}W${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __enote() { tooenc "$(__edate)$(__eindent0)${COULEUR_VERTE}N${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __einfo() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLEUE}I${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __edebug() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLANCHE}D${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __eimportant() { recho "$(__edate)$(__eindent0)${COULEUR_ROUGE}!${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __eattention() { recho "$(__edate)$(__eindent0)${COULEUR_JAUNE}*${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __eerror() { recho "$(__edate)$(__eindent0)${COULEUR_ROUGE}E${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __ewarn() { recho "$(__edate)$(__eindent0)${COULEUR_JAUNE}W${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __enote() { recho "$(__edate)$(__eindent0)${COULEUR_VERTE}N${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __einfo() { recho "$(__edate)$(__eindent0)${COULEUR_BLEUE}I${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __edebug() { recho "$(__edate)$(__eindent0)${COULEUR_BLANCHE}D${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
|
||||
function __estep() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepe() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepw() { tooenc "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepn() { tooenc "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepi() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLEUE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estep_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepe_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepw_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepn_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepi_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_BLEUE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estep() { recho "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepe() { recho "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepw() { recho "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepn() { recho "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepi() { recho "$(__edate)$(__eindent0)${COULEUR_BLEUE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estep_() { recho_ "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepe_() { recho_ "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepw_() { recho_ "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepn_() { recho_ "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __estepi_() { recho_ "$(__edate)$(__eindent0)${COULEUR_BLEUE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
|
||||
function __action() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __asuccess() { tooenc "$(__edate)$(__eindent0)${COULEUR_VERTE}✔${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __afailure() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}✘${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __adone() { tooenc "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||
function __action() { recho "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __asuccess() { recho "$(__edate)$(__eindent0)${COULEUR_VERTE}✔${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __afailure() { recho "$(__edate)$(__eindent0)${COULEUR_ROUGE}✘${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||
function __adone() { recho "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||
|
@ -6,23 +6,23 @@ function __esection() {
|
||||
local length="${COLUMNS:-80}"
|
||||
setx lsep=__complete "$prefix" "$length" -
|
||||
|
||||
tooenc "$lsep"
|
||||
recho "$lsep"
|
||||
[ -n "$*" ] || return 0
|
||||
length=$((length - 1))
|
||||
setx -a lines=echo "$1"
|
||||
for line in "${lines[@]}"; do
|
||||
setx line=__complete "$prefix- $line" "$length"
|
||||
tooenc "$line-"
|
||||
recho "$line-"
|
||||
done
|
||||
tooenc "$lsep"
|
||||
recho "$lsep"
|
||||
}
|
||||
function __etitle() {
|
||||
local p="TITLE: " i=" "
|
||||
tooenc "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
|
||||
recho "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
|
||||
}
|
||||
function __edesc() {
|
||||
local p="DESC: " i=" "
|
||||
tooenc "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
|
||||
recho "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
|
||||
}
|
||||
function __ebanner() {
|
||||
local -a lines
|
||||
@ -30,37 +30,37 @@ function __ebanner() {
|
||||
local length="${COLUMNS:-80}"
|
||||
setx lsep=__complete "$prefix" "$length" =
|
||||
|
||||
tooenc "$lsep"
|
||||
recho "$lsep"
|
||||
length=$((length - 1))
|
||||
setx -a lines=echo "$1"
|
||||
for line in "" "${lines[@]}" ""; do
|
||||
setx line=__complete "$prefix= $line" "$length"
|
||||
tooenc "$line="
|
||||
recho "$line="
|
||||
done
|
||||
tooenc "$lsep"
|
||||
recho "$lsep"
|
||||
}
|
||||
function __eimportant() { tooenc "$(__edate)$(__eindent0)IMPORTANT! $(__eindent "$1" " ")"; }
|
||||
function __eattention() { tooenc "$(__edate)$(__eindent0)ATTENTION! $(__eindent "$1" " ")"; }
|
||||
function __eerror() { tooenc "$(__edate)$(__eindent0)ERROR: $(__eindent "$1" " ")"; }
|
||||
function __ewarn() { tooenc "$(__edate)$(__eindent0)WARNING: $(__eindent "$1" " ")"; }
|
||||
function __enote() { tooenc "$(__edate)$(__eindent0)NOTE: $(__eindent "$1" " ")"; }
|
||||
function __einfo() { tooenc "$(__edate)$(__eindent0)INFO: $(__eindent "$1" " ")"; }
|
||||
function __edebug() { tooenc "$(__edate)$(__eindent0)DEBUG: $(__eindent "$1" " ")"; }
|
||||
function __eecho() { tooenc "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||
function __eecho_() { tooenc_ "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||
function __eimportant() { recho "$(__edate)$(__eindent0)IMPORTANT! $(__eindent "$1" " ")"; }
|
||||
function __eattention() { recho "$(__edate)$(__eindent0)ATTENTION! $(__eindent "$1" " ")"; }
|
||||
function __eerror() { recho "$(__edate)$(__eindent0)ERROR: $(__eindent "$1" " ")"; }
|
||||
function __ewarn() { recho "$(__edate)$(__eindent0)WARNING: $(__eindent "$1" " ")"; }
|
||||
function __enote() { recho "$(__edate)$(__eindent0)NOTE: $(__eindent "$1" " ")"; }
|
||||
function __einfo() { recho "$(__edate)$(__eindent0)INFO: $(__eindent "$1" " ")"; }
|
||||
function __edebug() { recho "$(__edate)$(__eindent0)DEBUG: $(__eindent "$1" " ")"; }
|
||||
function __eecho() { recho "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||
function __eecho_() { recho_ "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||
|
||||
function __estep() { tooenc "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
|
||||
function __estepe() { tooenc "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
|
||||
function __estepw() { tooenc "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
|
||||
function __estepn() { tooenc "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
|
||||
function __estepi() { tooenc "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
|
||||
function __estep_() { tooenc_ "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
|
||||
function __estepe_() { tooenc_ "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
|
||||
function __estepw_() { tooenc_ "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
|
||||
function __estepn_() { tooenc_ "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
|
||||
function __estepi_() { tooenc_ "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
|
||||
function __estep() { recho "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
|
||||
function __estepe() { recho "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
|
||||
function __estepw() { recho "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
|
||||
function __estepn() { recho "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
|
||||
function __estepi() { recho "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
|
||||
function __estep_() { recho_ "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
|
||||
function __estepe_() { recho_ "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
|
||||
function __estepw_() { recho_ "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
|
||||
function __estepn_() { recho_ "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
|
||||
function __estepi_() { recho_ "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
|
||||
|
||||
function __action() { tooenc "$(__edate)$(__eindent0)ACTION: $(__eindent "$1" " ")"; }
|
||||
function __asuccess() { tooenc "$(__edate)$(__eindent0)(OK) $(__eindent "$1" " ")"; }
|
||||
function __afailure() { tooenc "$(__edate)$(__eindent0)(KO) $(__eindent "$1" " ")"; }
|
||||
function __adone() { tooenc "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||
function __action() { recho "$(__edate)$(__eindent0)ACTION: $(__eindent "$1" " ")"; }
|
||||
function __asuccess() { recho "$(__edate)$(__eindent0)(OK) $(__eindent "$1" " ")"; }
|
||||
function __afailure() { recho "$(__edate)$(__eindent0)(KO) $(__eindent "$1" " ")"; }
|
||||
function __adone() { recho "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||
|
@ -124,8 +124,8 @@ optdesc
|
||||
commence par ++, c'est une option avancée qui n'est pas affichée par défaut."
|
||||
function parse_args() {
|
||||
eval "$NULIB__DISABLE_SET_X"
|
||||
[ -n "$NULIB_ARGS_ONERROR_RETURN" ] && set_die_return
|
||||
local __r=
|
||||
local __DIE='[ -n "$NULIB_ARGS_ONERROR_RETURN" ] && return 1 || die'
|
||||
|
||||
if ! is_array args; then
|
||||
eerror "Invalid args definition: args must be defined"
|
||||
@ -145,17 +145,33 @@ function parse_args() {
|
||||
__r=1
|
||||
else
|
||||
__DEFS=("$@")
|
||||
__parse_args || __r=1
|
||||
__nulib_args_parse_args || __r=1
|
||||
fi
|
||||
eval "$NULIB__ENABLE_SET_X"
|
||||
if [ -n "$__r" ]; then
|
||||
eval "$__DIE"
|
||||
die || return
|
||||
fi
|
||||
}
|
||||
function __parse_args() {
|
||||
function __nulib_args_add_sopt() {
|
||||
local noauto="$1"; shift
|
||||
local def
|
||||
for def in "$@"; do
|
||||
array_contains "$noauto" "$def" || __sopts="$__sopts$def"
|
||||
done
|
||||
}
|
||||
function __nulib_args_add_lopt() {
|
||||
local noauto="$1"; shift
|
||||
local def
|
||||
for def in "$@"; do
|
||||
array_contains "$noauto" "$def" || __lopts="$__lopts${__lopts:+,}$def"
|
||||
done
|
||||
}
|
||||
function __nulib_args_parse_args() {
|
||||
## tout d'abord, construire la liste des options
|
||||
local __AUTOH=1 __AUTOHELP=1 # faut-il gérer automatiquement l'affichage de l'aide?
|
||||
local __AUTOD=1 __AUTODEBUG=1 # faut-il rajouter les options -D et --debug
|
||||
local -a __NOAUTOL __NOAUTOLOGTO # faut-il rajouter les options pour gérer la journalisation?
|
||||
local -a __NOAUTOV __NOAUTOVERBOSITY # options de verbosité qui ont été définies par l'utilisateur
|
||||
local -a __NOAUTOI __NOAUTOINTERACTION # options d'interaction qui ont été définies par l'utilisateur
|
||||
local __ADVHELP # y a-t-il des options avancées?
|
||||
local __popt __sopts __lopts
|
||||
local -a __defs
|
||||
@ -165,10 +181,10 @@ function __parse_args() {
|
||||
+) __popt="$1"; shift; continue;;
|
||||
-) __popt="$1"; shift; continue;;
|
||||
-*) IFS=, read -a __defs <<<"$1"; shift;;
|
||||
*) eerror "Invalid arg definition: expected option, got '$1'"; eval "$__DIE";;
|
||||
*) die "Invalid arg definition: expected option, got '$1'" || return;;
|
||||
esac
|
||||
# est-ce que l'option prend un argument?
|
||||
local __def __witharg __valdesc
|
||||
local __def __longdef __witharg __valdesc
|
||||
__witharg=
|
||||
for __def in "${__defs[@]}"; do
|
||||
if [ "${__def%::*}" != "$__def" ]; then
|
||||
@ -180,23 +196,36 @@ function __parse_args() {
|
||||
# définitions __sopts et __lopts
|
||||
for __def in "${__defs[@]}"; do
|
||||
__def="${__def%%:*}"
|
||||
__longdef=
|
||||
if [[ "$__def" == --* ]]; then
|
||||
# --longopt
|
||||
__def="${__def#--}"
|
||||
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
|
||||
__longdef=1
|
||||
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
|
||||
# -o
|
||||
__def="${__def#-}"
|
||||
__sopts="$__sopts$__def$__witharg"
|
||||
[ "$__def" == h ] && __AUTOH=
|
||||
[ "$__def" == D ] && __AUTOD=
|
||||
case "$__def" in
|
||||
h) __AUTOH=;;
|
||||
L) __NOAUTOL+=("-$__def");;
|
||||
Q|q|v|D) __NOAUTOV+=("-$__def");;
|
||||
b|y|i) __NOAUTOI+=("-$__def");;
|
||||
esac
|
||||
else
|
||||
# -longopt ou longopt
|
||||
__def="${__def#-}"
|
||||
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
|
||||
__longdef=1
|
||||
fi
|
||||
if [ -n "$__longdef" ]; then
|
||||
case "$__def" in
|
||||
help|help++) __AUTOHELP=;;
|
||||
log-to) __NOAUTOLOGTO+=("--$__def");;
|
||||
very-quiet|quiet|verbose|debug) __NOAUTOVERBOSITY+=("--$__def");;
|
||||
batch|automatic|interactive) __NOAUTOINTERACTION+=("--$__def");;
|
||||
esac
|
||||
fi
|
||||
[ "$__def" == help -o "$__def" == help++ ] && __AUTOHELP=
|
||||
[ "$__def" == debug ] && __AUTODEBUG=
|
||||
done
|
||||
# sauter l'action
|
||||
shift
|
||||
@ -209,8 +238,12 @@ function __parse_args() {
|
||||
|
||||
[ -n "$__AUTOH" ] && __sopts="${__sopts}h"
|
||||
[ -n "$__AUTOHELP" ] && __lopts="$__lopts${__lopts:+,}help,help++"
|
||||
[ -n "$__AUTOD" ] && __sopts="${__sopts}D"
|
||||
[ -n "$__AUTODEBUG" ] && __lopts="$__lopts${__lopts:+,}debug"
|
||||
__nulib_args_add_sopt __NOAUTOL L
|
||||
__nulib_args_add_lopt __NOAUTOLOGTO log-to
|
||||
__nulib_args_add_sopt __NOAUTOV Q q v D
|
||||
__nulib_args_add_lopt __NOAUTOVERBOSITY very-quiet quiet verbose debug
|
||||
__nulib_args_add_sopt __NOAUTOI b y i
|
||||
__nulib_args_add_lopt __NOAUTOINTERACTION batch automatic interactive
|
||||
|
||||
__sopts="$__popt$__sopts"
|
||||
local -a __getopt_args
|
||||
@ -222,7 +255,7 @@ function __parse_args() {
|
||||
else
|
||||
# relancer pour avoir le message d'erreur
|
||||
LANG=C getopt "${__getopt_args[@]}" 2>&1 1>/dev/null
|
||||
eval "$__DIE"
|
||||
die || return
|
||||
fi
|
||||
|
||||
## puis traiter les options
|
||||
@ -373,7 +406,7 @@ $prefix$usage"
|
||||
fi
|
||||
[[ "$1" == -* ]] || break
|
||||
option_="$1"; shift
|
||||
__parse_opt "$option_"
|
||||
__nulib_args_parse_opt "$option_"
|
||||
if [ -n "$__witharg" ]; then
|
||||
# l'option prend un argument
|
||||
value_="$1"; shift
|
||||
@ -387,7 +420,7 @@ $prefix$usage"
|
||||
unset -f inc@ res@ add@ set@ showhelp@
|
||||
args=("$@")
|
||||
}
|
||||
function __parse_opt() {
|
||||
function __nulib_args_parse_opt() {
|
||||
# $1 est l'option spécifiée
|
||||
local option_="$1"
|
||||
set -- "${__DEFS[@]}"
|
||||
@ -460,27 +493,62 @@ function __parse_opt() {
|
||||
|
||||
[ -n "$__found" ] && return 0
|
||||
done
|
||||
if [ -n "$__AUTOH" -a "$option_" == -h ]; then
|
||||
__action="showhelp@"
|
||||
case "$option_" in
|
||||
-h)
|
||||
if [ -n "$__AUTOH" ]; then
|
||||
__action='showhelp@'
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
--help)
|
||||
if [ -n "$__AUTOHELP" ]; then
|
||||
if [ "$option_" == --help ]; then
|
||||
__action="showhelp@"
|
||||
return 0
|
||||
elif [ "$option_" == --help++ ]; then
|
||||
__action="showhelp@ ++"
|
||||
__action='showhelp@'
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if [ -n "$__AUTOD" -a "$option_" == -D ]; then
|
||||
__action=set_debug
|
||||
;;
|
||||
--help++)
|
||||
if [ -n "$__AUTOHELP" ]; then
|
||||
__action='showhelp@ ++'
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$__AUTODEBUG" -a "$option_" == --debug ]; then
|
||||
__action=set_debug
|
||||
;;
|
||||
-L)
|
||||
if ! array_contains __NOAUTOL "$option_"; then
|
||||
__action='elogto $value_'
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
--log-to)
|
||||
if ! array_contains __NOAUTOL "$option_"; then
|
||||
__action='elogto $value_'
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
-Q|-q|-v|-D)
|
||||
if ! array_contains __NOAUTOV "$option_"; then
|
||||
__action='set_verbosity $option_'
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
--very-quiet|--quiet|--verbose|--debug)
|
||||
if ! array_contains __NOAUTOVERBOSITY "$option_"; then
|
||||
__action='set_verbosity $option_'
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
-b|-y|-i)
|
||||
if ! array_contains __NOAUTOI "$option_"; then
|
||||
__action='set_interaction $option_'
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
--batch|--automatic|--interactive)
|
||||
if ! array_contains __NOAUTOINTERACTION "$option_"; then
|
||||
__action='set_interaction $option_'
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
# ici, l'option n'a pas été trouvée, on ne devrait pas arriver ici
|
||||
eerror "Unexpected option '$option_'"; eval "$__DIE"
|
||||
die "Unexpected option '$option_'" || return
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ function ask_yesno() {
|
||||
else
|
||||
__eecho_ "Voulez-vous continuer?" 1>&2
|
||||
fi
|
||||
tooenc_ " $prompt " 1>&2
|
||||
echo_ " $prompt " 1>&2
|
||||
uread r
|
||||
is_yes "${r:-$default}"
|
||||
else
|
||||
@ -198,17 +198,17 @@ function __rv_read() {
|
||||
__eecho_ "Entrez la valeur" 1>&2
|
||||
fi
|
||||
if [ -n "$__rv_readline" ]; then
|
||||
tooenc_ ": " 1>&2
|
||||
echo_ ": " 1>&2
|
||||
uread -e ${__rv_d:+-i"$__rv_d"} "${__rv_opts[@]}" __rv_r
|
||||
else
|
||||
if [ -n "$__rv_d" ]; then
|
||||
if [ -n "$__rv_showdef" ]; then
|
||||
tooenc_ " [$__rv_d]" 1>&2
|
||||
echo_ " [$__rv_d]" 1>&2
|
||||
else
|
||||
tooenc_ " [****]" 1>&2
|
||||
echo_ " [****]" 1>&2
|
||||
fi
|
||||
fi
|
||||
tooenc_ ": " 1>&2
|
||||
echo_ ": " 1>&2
|
||||
uread "${__rv_opts[@]}" __rv_r
|
||||
[ -n "$__rv_nl" ] && echo
|
||||
fi
|
||||
@ -217,6 +217,7 @@ function __rv_read() {
|
||||
_setv "$__rv_v" "$__rv_r"
|
||||
return 0
|
||||
fi
|
||||
echo
|
||||
done
|
||||
}
|
||||
|
||||
@ -268,7 +269,7 @@ function simple_menu() {
|
||||
else
|
||||
__eecho_ "Entrez le numéro de l'option choisie" 1>&2
|
||||
fi
|
||||
tooenc_ ": " 1>&2
|
||||
echo_ ": " 1>&2
|
||||
uread __sm_choice
|
||||
|
||||
# Valeur par défaut
|
||||
@ -291,7 +292,7 @@ function simple_menu() {
|
||||
let __sm_c=$__sm_c+1
|
||||
if [ "$__sm_c" -eq 5 ]; then
|
||||
# sauter une ligne toutes les 4 tentatives
|
||||
tooenc "" 1>&2
|
||||
echo 1>&2
|
||||
__sm_c=0
|
||||
fi
|
||||
done
|
||||
@ -438,7 +439,7 @@ function __void_actions_menu() {
|
||||
if [ $c -eq 0 ]; then
|
||||
[ -n "$title" ] && __etitle "$title" 1>&2
|
||||
__eecho_ "=== Actions disponibles: " 1>&2
|
||||
tooenc "$action_title" 1>&2
|
||||
recho "$action_title" 1>&2
|
||||
fi
|
||||
if [ -n "$actyc" ]; then
|
||||
__eecho_ "$actyc" 1>&2
|
||||
@ -447,7 +448,7 @@ function __void_actions_menu() {
|
||||
else
|
||||
__eecho_ "Entrez l'action à effectuer" 1>&2
|
||||
fi
|
||||
tooenc_ ": " 1>&2
|
||||
echo_ ": " 1>&2
|
||||
uread choice
|
||||
if [ -z "$choice" -a -n "$default_action" ]; then
|
||||
select_action="$default_action"
|
||||
@ -468,7 +469,7 @@ function __void_actions_menu() {
|
||||
let c=$c+1
|
||||
if [ $c -eq 5 ]; then
|
||||
# sauter une ligne toutes les 4 tentatives
|
||||
tooenc "" 1>&2
|
||||
echo 1>&2
|
||||
c=0
|
||||
fi
|
||||
done
|
||||
@ -484,21 +485,21 @@ function __options_actions_menu() {
|
||||
i=1
|
||||
for option in "${options[@]}"; do
|
||||
if [ "$option" == "$select_option" ]; then
|
||||
tooenc "$i*- $option" 1>&2
|
||||
echo "$i*- $option" 1>&2
|
||||
else
|
||||
tooenc "$i - $option" 1>&2
|
||||
echo "$i - $option" 1>&2
|
||||
fi
|
||||
let i=$i+1
|
||||
done
|
||||
__estepn_ "Actions disponibles: " 1>&2
|
||||
tooenc "$action_title" 1>&2
|
||||
recho "$action_title" 1>&2
|
||||
fi
|
||||
if [ -n "$optyc" ]; then
|
||||
__eecho_ "$optyc" 1>&2
|
||||
else
|
||||
__eecho_ "Entrez l'action et le numéro de l'option choisie" 1>&2
|
||||
fi
|
||||
tooenc_ ": " 1>&2
|
||||
echo_ ": " 1>&2
|
||||
uread choice
|
||||
|
||||
# vérifier la saisie
|
||||
@ -572,7 +573,7 @@ function __options_actions_menu() {
|
||||
let c=$c+1
|
||||
if [ $c -eq 5 ]; then
|
||||
# sauter une ligne toutes les 4 tentatives
|
||||
tooenc "" 1>&2
|
||||
echo "" 1>&2
|
||||
c=0
|
||||
fi
|
||||
done
|
||||
|
@ -83,7 +83,7 @@ function err_isatty() {
|
||||
|
||||
################################################################################
|
||||
|
||||
function tooenc() {
|
||||
function uecho() {
|
||||
# $1 étant une chaine encodée en utf-8, l'afficher dans l'encoding de sortie $2
|
||||
# qui vaut par défaut $NULIB_OUTPUT_ENCODING
|
||||
local value="$1" to="${2:-$NULIB_OUTPUT_ENCODING}"
|
||||
@ -93,9 +93,8 @@ function tooenc() {
|
||||
iconv -f -utf-8 -t "$to" <<<"$value"
|
||||
fi
|
||||
}
|
||||
function uecho() { tooenc "$*"; }
|
||||
|
||||
function tooenc_() {
|
||||
function uecho_() {
|
||||
# $1 étant une chaine encodée en utf-8, l'afficher sans passer à la ligne dans
|
||||
# l'encoding de sortie $2 qui vaut par défaut $NULIB_OUTPUT_ENCODING
|
||||
local value="$1" to="${2:-$NULIB_OUTPUT_ENCODING}"
|
||||
@ -105,7 +104,6 @@ function tooenc_() {
|
||||
recho_ "$value" | iconv -f utf-8 -t "$to"
|
||||
fi
|
||||
}
|
||||
function uecho_() { tooenc_ "$*"; }
|
||||
|
||||
export NULIB_QUIETLOG
|
||||
export NULIB__TMPLOG
|
||||
@ -210,7 +208,7 @@ function __eindent() {
|
||||
# indenter les lignes de $1, sauf la première
|
||||
local -a lines; local line first=1
|
||||
local indent="$(__eindent0)$2"
|
||||
setx -a lines=echo "$1"
|
||||
setx -a lines=recho "$1"
|
||||
for line in "${lines[@]}"; do
|
||||
if [ -n "$first" ]; then
|
||||
recho "$line"
|
||||
@ -232,7 +230,11 @@ function __complete() {
|
||||
}
|
||||
|
||||
PRETTYOPTS=()
|
||||
function set_verbosity() { :;}
|
||||
function set_verbosity() {
|
||||
case "$1" in
|
||||
-D|--debug) NULIB_DEBUG=1;;
|
||||
esac
|
||||
}
|
||||
function check_verbosity() { return 0; }
|
||||
function get_verbosity_option() { :;}
|
||||
|
||||
|
@ -2,20 +2,13 @@
|
||||
|
||||
## configuration par défaut
|
||||
|
||||
# branche upstream
|
||||
UPSTREAM=
|
||||
# branches de développement
|
||||
DEVELOP=develop
|
||||
FEATURE=wip/
|
||||
# branche de préparation de release
|
||||
RELEASE=release-
|
||||
# branche de release
|
||||
MAIN=master
|
||||
TAG_PREFIX=
|
||||
TAG_SUFFIX=
|
||||
# branche de hotfix
|
||||
HOTFIX=hotfix-
|
||||
# branche de distribution
|
||||
DIST=
|
||||
# désactiver les releases automatiques?
|
||||
NOAUTO=
|
||||
|
@ -5,15 +5,23 @@
|
||||
# les branches sont mergées dans cet ordre:
|
||||
# upstream --> develop --> [release -->] main --> dist
|
||||
# feature _/ hotfix _/
|
||||
|
||||
# branche upstream
|
||||
UPSTREAM=
|
||||
# branches de développement
|
||||
DEVELOP=develop
|
||||
FEATURE=wip/
|
||||
# branche de préparation de release
|
||||
RELEASE=release-
|
||||
# branche de release
|
||||
MAIN=master
|
||||
TAG_PREFIX=
|
||||
TAG_SUFFIX=
|
||||
# branche de hotfix
|
||||
HOTFIX=hotfix-
|
||||
# branche de distribution
|
||||
DIST=
|
||||
# désactiver les releases automatiques?
|
||||
NOAUTO=
|
||||
|
||||
CONFIG_VARS=(
|
||||
@ -45,7 +53,7 @@ $0 !~ /<pman>/ { print }
|
||||
function _filter_changes() {
|
||||
# enlever les commits "inutiles" pour générer le fichier CHANGES.md
|
||||
grep -vE '^([+|] )?[0-9a-f]+ modifs\.mineures sans commentaires$' |
|
||||
grep -vE '^([+|] )?[0-9a-f]+ (cosmetic|typo|bug|fix|maj projet|maj deps)\$'
|
||||
grep -vE '^([+|] )?[0-9a-f]+ (cosmetic|typo|bug|fix|maj projet|maj deps)$'
|
||||
}
|
||||
|
||||
function _format_md() {
|
||||
@ -151,6 +159,7 @@ function load_branches() {
|
||||
local what="${1:-all}"; shift
|
||||
case "$what" in
|
||||
all)
|
||||
[ -n "$Origin" ] || Origin=origin
|
||||
setx CurrentBranch=git_get_branch
|
||||
setx -a LocalBranches=git_list_branches
|
||||
setx -a RemoteBranches=git_list_rbranches "$Origin"
|
||||
@ -167,7 +176,17 @@ function load_branches() {
|
||||
"$HOTFIX"*) SrcType=hotfix; DestBranch="$MAIN";;
|
||||
"$MAIN") SrcType=main; DestBranch="$DIST";;
|
||||
"$DIST") SrcType=dist; DestBranch=;;
|
||||
*) DestBranch=;;
|
||||
*) SrcType=; DestBranch=;;
|
||||
esac
|
||||
case "$DestBranch" in
|
||||
"$UPSTREAM") DestType=upstream;;
|
||||
"$FEATURE"*) DestType=feature;;
|
||||
"$DEVELOP") DestType=develop;;
|
||||
"$RELEASE"*) DestType=release;;
|
||||
"$HOTFIX"*) DestType=hotfix;;
|
||||
"$MAIN") DestType=main;;
|
||||
"$DIST") DestType=dist;;
|
||||
*) DestType=;;
|
||||
esac
|
||||
|
||||
local branch
|
||||
@ -177,12 +196,12 @@ function load_branches() {
|
||||
ReleaseBranch=
|
||||
HotfixBranch=
|
||||
MainBranch=
|
||||
Dist=Branch
|
||||
for branch in "${AllBranches[@]}"; do
|
||||
DistBranch=
|
||||
for branch in "${LocalBranches[@]}"; do
|
||||
if [ "$branch" == "$UPSTREAM" ]; then
|
||||
UpstreamBranch="$branch"
|
||||
elif [[ "$branch" == "$FEATURE"* ]]; then
|
||||
FeatureBranch+=("$branch")
|
||||
FeatureBranches+=("$branch")
|
||||
elif [ "$branch" == "$DEVELOP" ]; then
|
||||
DevelopBranch="$branch"
|
||||
elif [[ "$branch" == "$RELEASE"* ]]; then
|
||||
@ -200,31 +219,50 @@ function load_branches() {
|
||||
}
|
||||
|
||||
function load_config() {
|
||||
if [ "${ConfigFile#::}" != "$ConfigFile" ]; then
|
||||
ConfigFile="$NULIBDIR/bash/src/pman${ConfigFile#::}.conf.sh"
|
||||
fi
|
||||
if [ -n "$ConfigFile" ]; then
|
||||
source "$ConfigFile" || die || return
|
||||
elif [ -n "$ConfigBranch" ]; then
|
||||
# c'est le seul cas où ConfigFile reste vide
|
||||
if ! array_contains LocalBranches "$ConfigBranch"; then
|
||||
die "$ConfigBranch: branche de configuration introuvable" || return
|
||||
else
|
||||
ac_set_tmpfile ConfigFile
|
||||
git show "$ConfigBranch:.pman.conf" >"$ConfigFile" 2>/dev/null
|
||||
[ -s "$ConfigFile" ] || die "$ConfigBranch: aucune configuration trouvée sur cette branche" || return
|
||||
source "$ConfigFile"
|
||||
local config
|
||||
ac_set_tmpfile config
|
||||
git show "$ConfigBranch:.pman.conf" >"$config" 2>/dev/null
|
||||
[ -s "$config" ] || die "$ConfigBranch: aucune configuration trouvée sur cette branche" || return
|
||||
source "$config"
|
||||
fi
|
||||
elif [ -f .pman.conf ]; then
|
||||
ConfigFile="$(pwd)/.pman.conf"
|
||||
source "$ConfigFile"
|
||||
elif [ -n "${MYNAME#prel}" ]; then
|
||||
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
|
||||
|
||||
# S'assurer que nulib est dans le PATH pour que les scripts utilisateurs
|
||||
# puissent utiliser les outils fournis
|
||||
export PATH="$NULIBDIR/bin:$PATH"
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Divers
|
||||
|
||||
function resolve_should_push() {
|
||||
local quiet="$1"
|
||||
ShouldPush=1
|
||||
if ! git_have_remote "$Origin" && [ -n "$Push" ]; then
|
||||
[ -n "$quiet" ] || enote "L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine"
|
||||
ShouldPush=
|
||||
fi
|
||||
[ -z "$ShouldPush" ] && Push=
|
||||
}
|
||||
|
||||
function _push_branches() {
|
||||
[ ${#push_branches[*]} -gt 0 ] || return
|
||||
[ -n "$Origin" ] || Origin=origin
|
||||
@ -275,6 +313,11 @@ function _mscript_start() {
|
||||
#!/bin/bash
|
||||
$(qvals source "$NULIBDIR/load.sh") || exit 1
|
||||
|
||||
$(echo_setv SrcBranch="$SrcBranch")
|
||||
$(echo_setv SrcType="$SrcType")
|
||||
$(echo_setv DestBranch="$DestBranch")
|
||||
$(echo_setv DestType="$DestType")
|
||||
|
||||
merge=
|
||||
delete=
|
||||
push=
|
||||
@ -330,6 +373,14 @@ function _rscript_start() {
|
||||
#!/bin/bash
|
||||
$(qvals source "$NULIBDIR/load.sh") || exit 1
|
||||
|
||||
$(echo_setv SrcBranch="$SrcBranch")
|
||||
$(echo_setv SrcType="$SrcType")
|
||||
$(echo_setv Version="$Version")
|
||||
$(echo_setv Tag="$Tag")
|
||||
$(echo_setv ReleaseBranch="$ReleaseBranch")
|
||||
$(echo_setv DestBranch="$DestBranch")
|
||||
$(echo_setv DestType="$DestType")
|
||||
|
||||
create=
|
||||
merge=
|
||||
push=
|
||||
@ -382,7 +433,7 @@ EOF
|
||||
|
||||
# Enregistrer les changements
|
||||
_scripta "commit" <<EOF
|
||||
$(qvals git commit -m "<pman>Init changelog & version $Version")
|
||||
$(qvals git commit -m "<pman>Init changelog & version $Tag")
|
||||
EOF
|
||||
}
|
||||
|
||||
|
@ -108,11 +108,17 @@ function set_interaction() {
|
||||
# set_interaction en fonction des arguments de la ligne de commande. A utiliser
|
||||
# de cette manière:
|
||||
# parse_opts ... "${PRETTYOPTS[@]}" @ args -- ...
|
||||
PRETTYOPTS=(
|
||||
# NB: ce n'est pas nécessaire, sauf si on veut afficher ces options dans l'aide
|
||||
LOGTOOPTS=(
|
||||
-L:,--log-to:LOGFILE '$elogto $value_' "++enregistrer les messages dans le fichier spécifié"
|
||||
)
|
||||
VERBOSITYOPTS=(
|
||||
-Q,--very-quiet,-q,--quiet,-v,--verbose,-D,--debug '$set_verbosity $option_' "++spécifier le niveau de verbiage"
|
||||
)
|
||||
INTERACTIONOPTS=(
|
||||
-b,--batch,-y,--automatic,-i,--interactive '$set_interaction $option_' "++spécifier le niveau d'interaction"
|
||||
)
|
||||
PRETTYOPTS=("${LOGTOOPTS[@]}" "${VERBOSITYOPTS[@]}" "${INTERACTIONOPTS[@]}")
|
||||
|
||||
function show_error() { [ "$__verbosity" -ge 1 ]; }
|
||||
function show_warn() { [ "$__verbosity" -ge 2 ]; }
|
||||
|
@ -19,7 +19,8 @@ Copier \$1 vers \$2 de façon inconditionnelle
|
||||
Si \$2 n'est pas spécifié, on assume que \$1 est de la forme '.file.ext'
|
||||
et \$2 vaudra alors 'file'
|
||||
|
||||
si un fichier \${2#.}.local existe, prendre ce fichier à la place comme source
|
||||
si un fichier \${2#.}.local existe (e.g 'file.ext.local'), prendre ce fichier à
|
||||
la place comme source
|
||||
|
||||
Ajouter file au tableau userfiles"
|
||||
function template_copy_replace() {
|
||||
@ -47,7 +48,8 @@ Copier \$1 vers \$2 si ce fichier n'existe pas déjà
|
||||
Si \$2 n'est pas spécifié, on assume que \$1 est de la forme '.file.ext'
|
||||
et \$2 vaudra alors 'file'
|
||||
|
||||
si un fichier \${2#.}.local existe, prendre ce fichier à la place comme source
|
||||
si un fichier \${1#.}.local existe (e.g 'file.ext.local'), prendre ce fichier à
|
||||
la place comme source
|
||||
|
||||
Ajouter file au tableau userfiles"
|
||||
function template_copy_missing() {
|
||||
@ -205,6 +207,18 @@ function template_generate_scripts() {
|
||||
#etitle "sedscript" cat "$sedscript"
|
||||
}
|
||||
|
||||
function: _template_can_process "\
|
||||
Indiquer si \$1 est un fichier texte, qui peut être traité par
|
||||
template_process_userfiles"
|
||||
function _template_can_process() {
|
||||
case "$1" in
|
||||
*.png|*.jpg|*.gif|*.bmp) return 1;;
|
||||
*.zip|*.jar|*.war|*.ear) return 1;;
|
||||
*.tar|*.gz|*.tgz|*.bz2|*.tbz2) return 1;;
|
||||
*) return 0;;
|
||||
esac
|
||||
}
|
||||
|
||||
function template_process_userfiles() {
|
||||
local awkscript sedscript workfile userfile
|
||||
ac_set_tmpfile awkscript
|
||||
@ -213,6 +227,7 @@ function template_process_userfiles() {
|
||||
|
||||
ac_set_tmpfile workfile
|
||||
for userfile in "${userfiles[@]}"; do
|
||||
_template_can_process "$userfile" || continue
|
||||
if cat "$userfile" | awk -f "$awkscript" | sed -rf "$sedscript" >"$workfile"; then
|
||||
if testdiff "$workfile" "$userfile"; then
|
||||
# n'écrire le fichier que s'il a changé
|
||||
|
29
bash/tests/test-interaction.sh
Executable file
29
bash/tests/test-interaction.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
source "$(dirname -- "$0")/../src/nulib.sh" || exit 1
|
||||
#NULIB_NO_DISABLE_SET_X=1
|
||||
|
||||
args=(
|
||||
"tester diverses fonctions de saisie"
|
||||
)
|
||||
parse_args "$@"; set -- "${args[@]}"
|
||||
|
||||
estep "inter non auto non"
|
||||
ask_yesno "oui ou non?" && echo oui || echo non
|
||||
estep "inter oui auto oui"
|
||||
ask_yesno "oui ou non?" O && echo oui || echo non
|
||||
estep "inter non auto non"
|
||||
ask_yesno "oui ou non?" N && echo oui || echo non
|
||||
estep "inter non auto oui"
|
||||
ask_yesno "oui ou non?" C && echo oui || echo non
|
||||
estep "inter oui auto non"
|
||||
ask_yesno "oui ou non?" X && echo oui || echo non
|
||||
|
||||
estep "valeur par défaut vide"
|
||||
read_value "valeur" empty "" N; echo "valeur=$empty"
|
||||
|
||||
estep "valeur par défaut non vide"
|
||||
read_value "valeur" default default N; echo "valeur=$default"
|
||||
|
||||
estep "valeur requise"
|
||||
read_value "valeur" required; echo "valeur=$required"
|
@ -7,7 +7,6 @@ Multiline=
|
||||
Banner=
|
||||
args=(
|
||||
"afficher divers messages avec les fonctions e*"
|
||||
-D,--debug '$set_debug'
|
||||
-d,--date NULIB_ELOG_DATE=1
|
||||
-m,--myname NULIB_ELOG_MYNAME=1
|
||||
-n,--nc,--no-color '$__set_no_colors 1'
|
||||
|
24
bash/tests/test-verbosity.sh
Executable file
24
bash/tests/test-verbosity.sh
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
source "$(dirname -- "$0")/../src/nulib.sh" || exit 1
|
||||
#NULIB_NO_DISABLE_SET_X=1
|
||||
|
||||
args=(
|
||||
"afficher divers messages avec les fonctions e*"
|
||||
)
|
||||
parse_args "$@"; set -- "${args[@]}"
|
||||
|
||||
eimportant "important (q)"
|
||||
eattention "attention (q)"
|
||||
eerror "error (q)"
|
||||
ewarn "warn (q)"
|
||||
enote "note (qv)"
|
||||
einfo "info (qv)"
|
||||
eecho "echo (qv)"
|
||||
edebug "debug (D)"
|
||||
|
||||
estep "step (qv)"
|
||||
estepe "stepe (qv)"
|
||||
estepw "stepw (qv)"
|
||||
estepn "stepn (qv)"
|
||||
estepi "stepi (qv)"
|
@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
|
||||
exec "$(dirname -- "$0")/pdev" --tech-merge -Bdev82 dev74 "$@"
|
||||
exec "$(dirname -- "$0")/pdev" --tech-merge -Bdev82 dev74 -a "git checkout dev74" "$@"
|
||||
|
14
bin/_pman-composer_local_deps.php
Executable file
14
bin/_pman-composer_local_deps.php
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
|
||||
use nulib\tools\pman\ComposerFile;
|
||||
use nulib\tools\pman\ComposerPmanFile;
|
||||
use nulib\ValueException;
|
||||
|
||||
$composer = new ComposerFile();
|
||||
|
||||
$deps = $composer->getLocalDeps();
|
||||
foreach ($deps as $dep => $path) {
|
||||
echo "$path\n";
|
||||
}
|
22
bin/_pman-composer_select_profile.php
Executable file
22
bin/_pman-composer_select_profile.php
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
|
||||
use nulib\tools\pman\ComposerFile;
|
||||
use nulib\tools\pman\ComposerPmanFile;
|
||||
use nulib\ValueException;
|
||||
|
||||
$composer = new ComposerFile();
|
||||
$config = new ComposerPmanFile();
|
||||
|
||||
if ($argc <= 1) {
|
||||
throw new ValueException("Il faut spécifier le profil à sélectionner");
|
||||
}
|
||||
$profile = $argv[1];
|
||||
|
||||
$composer->selectProfile($profile, $config);
|
||||
if (getenv("PMAN_COMPOSER_DEBUG")) {
|
||||
$composer->print();
|
||||
} else {
|
||||
$composer->write();
|
||||
}
|
34
bin/p
34
bin/p
@ -20,8 +20,18 @@ function git_status() {
|
||||
fi
|
||||
}
|
||||
|
||||
function git_statuses() {
|
||||
local cwd="$(pwd)" dir
|
||||
for dir in "$@"; do
|
||||
cd "$dir" || die
|
||||
git_status --porcelain
|
||||
cd "$cwd"
|
||||
done
|
||||
}
|
||||
|
||||
chdir=
|
||||
all=
|
||||
composer=
|
||||
args=(
|
||||
"afficher l'état du dépôt"
|
||||
"[-d chdir] [-a patterns...]
|
||||
@ -29,6 +39,7 @@ args=(
|
||||
Si l'option -a est utilisée, ce script accepte comme arguments une liste de patterns permettant de filtrer les répertoires concernés"
|
||||
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
|
||||
-a,--all all=1 "faire l'opération sur tous les sous-répertoires de BASEDIR qui sont des dépôts git"
|
||||
-r,--composer composer=1 "faire l'opération sur tous les projets composer dépendants"
|
||||
)
|
||||
parse_args "$@"; set -- "${args[@]}"
|
||||
|
||||
@ -50,16 +61,23 @@ if [ -n "$all" ]; then
|
||||
dirs+=("${dir%/.git}")
|
||||
done
|
||||
fi
|
||||
setx cwd=pwd
|
||||
for dir in "${dirs[@]}"; do
|
||||
cd "$dir" || die
|
||||
git_status --porcelain
|
||||
cd "$cwd"
|
||||
done
|
||||
git_statuses "${dirs[@]}"
|
||||
|
||||
elif [ -n "$composer" ]; then
|
||||
# projets dépendants
|
||||
git_ensure_gitvcs
|
||||
setx toplevel=git_get_toplevel
|
||||
cd "$toplevel" || die
|
||||
setx cwd=ppath2 . "$OrigCwd"
|
||||
[ -f composer.json ] || die "$cwd: ce n'est pas un projet composer"
|
||||
|
||||
setx -a dirs="$MYDIR/_pman-composer_local_deps.php"
|
||||
git_statuses "${dirs[@]}"
|
||||
|
||||
else
|
||||
# répertoire courant uniquement
|
||||
setx toplevel=git_get_toplevel
|
||||
[ -n "$toplevel" ] && Cwd="$toplevel"
|
||||
git_ensure_gitvcs
|
||||
Cwd="$(git_get_toplevel)"
|
||||
|
||||
args=()
|
||||
isatty || args+=(--porcelain)
|
||||
|
130
bin/pdev
130
bin/pdev
@ -25,14 +25,18 @@ function ensure_branches() {
|
||||
}
|
||||
|
||||
function merge_action() {
|
||||
[ -z "$ShouldPush" ] && enote "\
|
||||
L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine"
|
||||
|
||||
enote "\
|
||||
Ce script va
|
||||
- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+
|
||||
- pousser les branches modifiées}"
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
local script=".git/rel-merge.sh"
|
||||
local script=".git/pman-merge.sh"
|
||||
local -a push_branches delete_branches
|
||||
local hook
|
||||
local comment=
|
||||
local or_die=" || exit 1"
|
||||
|
||||
@ -42,12 +46,23 @@ Ce script va
|
||||
# merge
|
||||
if [ -n "\$merge" ]; then
|
||||
esection "Fusionner la branche"
|
||||
EOF
|
||||
hook="BEFORE_MERGE_${SrcType^^}"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_mscript_merge_branch
|
||||
hook="AFTER_MERGE_${SrcType^^}"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
|
||||
if [ -n "$ShouldDelete" ]; then
|
||||
_scripta <<EOF
|
||||
################################################################################
|
||||
# delete
|
||||
@ -55,15 +70,26 @@ if [ -n "\$delete" ]; then
|
||||
esection "Supprimer la branche"
|
||||
EOF
|
||||
_mscript_delete_branch
|
||||
hook="AFTER_DELETE_${SrcType^^}"; [ -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_${DestType^^}"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_script_push_branches
|
||||
if [ ${#delete_branches[*]} -gt 0 ]; then
|
||||
@ -72,27 +98,42 @@ EOF
|
||||
_script_push_branches
|
||||
_scripta <<<fi
|
||||
fi
|
||||
hook="AFTER_PUSH_${DestType^^}"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
|
||||
[ -n "$Delete" -o "$ForbidDelete" ] && Deleted=1 || Deleted=
|
||||
[ -n "$Push" -o "$ForbidPush" ] && Pushed=1 || Pushed=
|
||||
if [ -n "$_NoRunScript" ]; then
|
||||
einfo "Veuillez consulter le script $script pour le détail des opérations à effectuer"
|
||||
[ -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 "Veuillez consulter le script $script pour le détail des opérations qui n'ont pas pu êtres effectuées"
|
||||
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 "$Pushed" ]; then
|
||||
elif [ -n "$Deleted" -a -n "$Push" ]; then
|
||||
[ -n "$_KeepScript" ] || rm "$script"
|
||||
[ -n "$AfterMerge" ] && eval "$AfterMerge"
|
||||
else
|
||||
local cmd
|
||||
[ -n "$Deleted" ] || cmd="$cmd
|
||||
./$script delete"
|
||||
[ -n "$Pushed" ] || cmd="$cmd
|
||||
./$script push"
|
||||
einfo "Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
|
||||
Veuillez le consulter pour le détail des autres opérations à effectuer$cmd"
|
||||
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
|
||||
}
|
||||
|
||||
@ -104,16 +145,27 @@ chdir=
|
||||
Origin=
|
||||
ConfigBranch=
|
||||
ConfigFile=
|
||||
_Fake=
|
||||
_KeepScript=
|
||||
_NoRunScript=
|
||||
action=merge
|
||||
TechMerge=
|
||||
SquashMsg=
|
||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||
[ -z "$PMAN_NO_DELETE" ] && Delete=1 || Delete=
|
||||
AfterMerge=
|
||||
args=(
|
||||
"fusionner la branche source dans la branche destination correspondante"
|
||||
" [source]"
|
||||
" [source]
|
||||
|
||||
CONFIGURATION
|
||||
Le fichier .pman.conf contient la configuration des branches. Les variables
|
||||
supplémentaires suivantes peuvent être définies:
|
||||
BEFORE_MERGE_<srcType>
|
||||
AFTER_MERGE_<srcType>
|
||||
AFTER_DELETE_<srcType>
|
||||
BEFORE_PUSH_<destType>
|
||||
AFTER_PUSH_<destType>
|
||||
srcType et destType pouvant valoir UPSTREAM, DEVELOP, FEATURE, RELEASE, MAIN, HOTFIX, DIST"
|
||||
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
|
||||
-O:,--origin Origin= "++\
|
||||
origine à partir de laquelle les branches distantes sont considérées"
|
||||
@ -122,8 +174,8 @@ branche à partir de laquelle charger la configuration"
|
||||
-c:,--config-file:CONFIG ConfigFile= "++\
|
||||
fichier de configuration des branches. cette option est prioritaire sur --config-branch
|
||||
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
||||
--fake _Fake=1 "++option non documentée"
|
||||
--keep-script _KeepScript=1 "++option non documentée"
|
||||
--no-run-script _NoRunScript=1 "++option non documentée"
|
||||
-w,--show action=show "\
|
||||
lister les modifications qui seraient fusionnées dans la branche destination"
|
||||
-b,--rebase action=rebase "\
|
||||
@ -146,6 +198,10 @@ ne pas supprimer la branche après la fusion dans la destination"
|
||||
--delete Delete=1 "++\
|
||||
supprimer la branche après la fusion dans la destination.
|
||||
c'est l'option par défaut"
|
||||
-f,--force-merge ForceMerge=1 "++\
|
||||
forcer la fusion pour une branche qui devrait être traitée par prel"
|
||||
-a:,--after-merge AfterMerge= "\
|
||||
évaluer le script spécifié après une fusion *réussie*"
|
||||
)
|
||||
parse_args "$@"; set -- "${args[@]}"
|
||||
|
||||
@ -155,13 +211,7 @@ load_branches all
|
||||
load_config "$MYNAME"
|
||||
load_branches current "$1"
|
||||
|
||||
ForbidPush=
|
||||
[ -n "$Origin" ] || Origin=origin
|
||||
if ! git_have_remote "$Origin" && [ -n "$Push" ]; then
|
||||
ewarn "L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine"
|
||||
ForbidPush=1
|
||||
fi
|
||||
[ -n "$ForbidPush" ] && Push=
|
||||
resolve_should_push quiet
|
||||
|
||||
# puis faire l'action que l'on nous demande
|
||||
case "$action" in
|
||||
@ -171,24 +221,26 @@ show)
|
||||
show_action "$@"
|
||||
;;
|
||||
merge)
|
||||
ForbidDelete=
|
||||
case "$SrcType" in
|
||||
develop|release|hotfix)
|
||||
die "$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel"
|
||||
;;
|
||||
*)
|
||||
ShouldDelete=1
|
||||
no_merge_msg="$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel"
|
||||
if [ "$SrcType" == develop ]; then
|
||||
[ -z "$ForceMerge" ] && die "$no_merge_msg"
|
||||
[ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$SrcBranch"
|
||||
elif [ "$SrcType" == release -o "$SrcType" == hotfix ]; then
|
||||
die "$no_merge_msg"
|
||||
fi
|
||||
# n'autoriser la suppression que pour feature
|
||||
[ "$SrcType" == feature ] || ForbidDelete=1
|
||||
;;
|
||||
esac
|
||||
[ -n "$ForbidDelete" ] && Delete=
|
||||
git_ensure_cleancheckout
|
||||
if ! array_contains LocalBranches "$SrcBranch"; then
|
||||
# si la branche source n'existe pas, la créer
|
||||
exec "$MYDIR/pman" "$FEATURE${SrcBranch#$FEATURE}"
|
||||
else
|
||||
[ "$SrcType" == feature ] || ShouldDelete=
|
||||
[ -z "$ShouldDelete" ] && Delete=
|
||||
[ -z "$_Fake" ] && git_ensure_cleancheckout
|
||||
if array_contains LocalBranches "$SrcBranch"; then
|
||||
ensure_branches
|
||||
merge_action "$@"
|
||||
elif array_contains AllBranches "$SrcBranch"; then
|
||||
enote "$SrcBranch: une branche du même nom existe dans l'origine"
|
||||
die "$SrcBranch: branche locale introuvable"
|
||||
else
|
||||
die "$SrcBranch: branche introuvable"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
|
189
bin/pman
189
bin/pman
@ -34,16 +34,12 @@ function show_action() {
|
||||
# Initialisation
|
||||
################################################################################
|
||||
|
||||
function init_repo_action() {
|
||||
[ ${#LocalBranches[*]} -eq 0 ] || die "Ce dépôt a déjà été initialisé"
|
||||
|
||||
local -a push_branches
|
||||
|
||||
if [ ! -f .pman.conf ]; then
|
||||
function _init_config() {
|
||||
if [ ! -f .pman.conf -o -n "$ForceCreate" ]; then
|
||||
ac_set_tmpfile config
|
||||
cp "$ConfigFile" "$config"
|
||||
"${EDITOR:-nano}" "$config"
|
||||
[ -s "$config" ] || exit_with ewarn "Initialisation du dépôt annulée"
|
||||
[ -s "$config" ] || return 1
|
||||
|
||||
cp "$config" .pman.conf
|
||||
if testdiff .pman.conf "$ConfigFile"; then
|
||||
@ -59,6 +55,15 @@ function init_repo_action() {
|
||||
.*.swp"
|
||||
git add .gitignore
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function init_repo_action() {
|
||||
local -a push_branches; local config
|
||||
|
||||
[ ${#LocalBranches[*]} -eq 0 ] || die "Ce dépôt a déjà été initialisé"
|
||||
|
||||
_init_config || exit_with ewarn "Initialisation du dépôt annulée"
|
||||
|
||||
einfo "Création de la branche $MAIN"
|
||||
git symbolic-ref HEAD "refs/heads/$MAIN"
|
||||
@ -72,17 +77,97 @@ function init_repo_action() {
|
||||
_push_branches
|
||||
}
|
||||
|
||||
function init_develop_action() {
|
||||
if [ -z "$DevelopBranch" ]; then
|
||||
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
||||
function init_config_action() {
|
||||
local -a push_branches; config
|
||||
|
||||
[ -f .pman.conf -a -z "$ForceCreate" ] && die "La configuration pman a déjà été initialisée"
|
||||
|
||||
resolve_should_push
|
||||
|
||||
_init_config || exit_with ewarn "Initialisation de la configuration annulée"
|
||||
git commit -m "configuration pman"
|
||||
push_branches+=("$CurrentBranch")
|
||||
|
||||
_push_branches
|
||||
}
|
||||
|
||||
function _init_composer() {
|
||||
if [ ! -f .composer.pman.yml -o -n "$ForceCreate" ]; then
|
||||
ac_set_tmpfile config
|
||||
cat >"$config" <<EOF
|
||||
# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
|
||||
|
||||
composer:
|
||||
match_prefix:
|
||||
match_prefix-dev:
|
||||
profiles: [ dev, dist ]
|
||||
dev:
|
||||
link: true
|
||||
require:
|
||||
reqire-dev:
|
||||
dist:
|
||||
link: false
|
||||
require:
|
||||
reqire-dev:
|
||||
EOF
|
||||
"${EDITOR:-nano}" "$config"
|
||||
[ -s "$config" ] || return 1
|
||||
|
||||
cp "$config" .composer.pman.yml
|
||||
git add .composer.pman.yml
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function init_composer_action() {
|
||||
local -a push_branches; local config
|
||||
|
||||
[ -f .composer.pman.yml -a -z "$ForceCreate" ] && die "La configuration pman composer a déjà été initialisée"
|
||||
|
||||
resolve_should_push
|
||||
|
||||
_init_composer || exit_with ewarn "Initialisation de la configuration annulée"
|
||||
git commit -m "configuration pman composer"
|
||||
push_branches+=("$CurrentBranch")
|
||||
|
||||
_push_branches
|
||||
}
|
||||
|
||||
function _ensure_main_branch() {
|
||||
[ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
|
||||
[ -n "$MainBranch" ] || die "$MAIN: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function checkout_main_action() {
|
||||
if [ -z "$MainBranch" ]; then
|
||||
array_contains AllBranches "$MAIN" && exit_with enote "\
|
||||
$MAIN: une branche du même nom existe dans l'origine
|
||||
git checkout $MAIN"
|
||||
_ensure_main_branch
|
||||
fi
|
||||
git checkout -q "$MAIN"
|
||||
}
|
||||
|
||||
function _ensure_develop_branch() {
|
||||
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
||||
[ "$1" == init -o -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function init_develop_action() {
|
||||
local -a push_branches
|
||||
|
||||
if [ -z "$DevelopBranch" ]; then
|
||||
array_contains AllBranches "$DEVELOP" && exit_with enote "\
|
||||
$DEVELOP: une branche du même nom existe dans l'origine
|
||||
git checkout $DEVELOP"
|
||||
_ensure_main_branch
|
||||
_ensure_develop_branch init
|
||||
|
||||
resolve_should_push
|
||||
|
||||
enote "Vous allez créer la branche ${COULEUR_VERTE}$DEVELOP${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MAIN${COULEUR_NORMALE}"
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
local -a push_branches
|
||||
|
||||
einfo "Création de la branche $DEVELOP"
|
||||
git checkout -b "$DEVELOP" "$MAIN" || die
|
||||
push_branches+=("$DEVELOP")
|
||||
@ -92,17 +177,26 @@ function init_develop_action() {
|
||||
git checkout -q "$DEVELOP"
|
||||
}
|
||||
|
||||
function init_upstream_action() {
|
||||
if [ -z "$UpstreamBranch" ]; then
|
||||
function _ensure_upstream_branch() {
|
||||
[ -n "$UPSTREAM" ] || die "La branche UPSTREAM n'a pas été définie"
|
||||
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
||||
[ -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
[ "$1" == init -o -n "$UpstreamBranch" ] || die "$UPSTREAM: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function init_upstream_action() {
|
||||
local -a push_branches; local config
|
||||
|
||||
if [ -z "$UpstreamBranch" ]; then
|
||||
array_contains AllBranches "$UPSTREAM" && exit_with enote "\
|
||||
$UPSTREAM: une branche du même nom existe dans l'origine
|
||||
git checkout $UPSTREAM"
|
||||
_ensure_develop_branch
|
||||
_ensure_upstream_branch init
|
||||
|
||||
resolve_should_push
|
||||
|
||||
enote "Vous allez créer la branche ${COULEUR_VERTE}$UPSTREAM${COULEUR_NORMALE}"
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
local -a push_branches; local config
|
||||
|
||||
# faire une copie de la configuration actuelle
|
||||
ac_set_tmpfile config
|
||||
cp "$ConfigFile" "$config"
|
||||
@ -128,17 +222,26 @@ function init_upstream_action() {
|
||||
git checkout -q "$UPSTREAM"
|
||||
}
|
||||
|
||||
function init_dist_action() {
|
||||
if [ -z "$DistBranch" ]; then
|
||||
function _ensure_dist_branch() {
|
||||
[ -n "$DIST" ] || die "La branche DIST n'a pas été définie"
|
||||
[ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
|
||||
[ -n "$MainBranch" ] || die "$MAIN: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
[ "$1" == init -o -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function init_dist_action() {
|
||||
local -a push_branches
|
||||
|
||||
if [ -z "$DistBranch" ]; then
|
||||
array_contains AllBranches "$DIST" && exit_with enote "\
|
||||
$DIST: une branche du même nom existe dans l'origine
|
||||
git checkout $DIST"
|
||||
_ensure_main_branch
|
||||
_ensure_dist_branch init
|
||||
|
||||
resolve_should_push
|
||||
|
||||
enote "Vous allez créer la branche ${COULEUR_VERTE}$DIST${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MAIN${COULEUR_NORMALE}"
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
local -a push_branches
|
||||
|
||||
einfo "Création de la branche $DIST"
|
||||
git checkout -b "$DIST" "$MAIN" || die
|
||||
push_branches+=("$DIST")
|
||||
@ -149,18 +252,24 @@ function init_dist_action() {
|
||||
}
|
||||
|
||||
function init_feature_action() {
|
||||
local branch="${1#$FEATURE}"
|
||||
[ -n "$branch" ] || die "Vous devez définir la nom de la branche à créer"
|
||||
local -a push_branches; local branch
|
||||
|
||||
[ -n "$FEATURE" ] || die "La branche FEATURE n'a pas été définie"
|
||||
branch="${1#$FEATURE}"
|
||||
[ -n "$branch" ] || die "Vous devez spécifier le nom de la branche"
|
||||
branch="$FEATURE$branch"
|
||||
if ! array_contains AllBranches "$branch"; then
|
||||
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
||||
[ -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
|
||||
if ! array_contains LocalBranches "$branch"; then
|
||||
array_contains AllBranches "$branch" && exit_with enote "\
|
||||
$branch: une branche du même nom existe dans l'origine
|
||||
git checkout $branch"
|
||||
_ensure_develop_branch
|
||||
|
||||
resolve_should_push
|
||||
|
||||
enote "Vous allez créer la branche ${COULEUR_VERTE}$branch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$DEVELOP${COULEUR_NORMALE}"
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
local -a push_branches
|
||||
|
||||
einfo "Création de la branche $branch"
|
||||
git checkout -b "$branch" "$DEVELOP" || die
|
||||
push_branches+=("$branch")
|
||||
@ -174,7 +283,9 @@ function init_action() {
|
||||
local what="${1:-develop}"; shift
|
||||
case "$what" in
|
||||
init|repo|r) init_repo_action "$@";;
|
||||
main|m) git checkout -q "$MAIN";;
|
||||
config) init_config_action "$@";;
|
||||
composer) init_composer_action "$@";;
|
||||
main|m) checkout_main_action;;
|
||||
develop|dev|d) init_develop_action "$@";;
|
||||
upstream|up|u) init_upstream_action "$@";;
|
||||
dist|x) init_dist_action "$@";;
|
||||
@ -191,10 +302,12 @@ ConfigBranch=
|
||||
ConfigFile=
|
||||
action=init
|
||||
Origin=
|
||||
Push=1
|
||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||
ForceCreate=
|
||||
args=(
|
||||
"gérer un projet git"
|
||||
"repo|develop|upstream|dist
|
||||
"repo|config|composer
|
||||
develop|upstream|dist
|
||||
|
||||
INITIALISATION
|
||||
|
||||
@ -203,6 +316,7 @@ configurer certaines branches du dépôt si elles n'existent pas déjà
|
||||
|
||||
repo
|
||||
initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
|
||||
|
||||
develop
|
||||
créer la branche $DEVELOP
|
||||
upstream
|
||||
@ -219,6 +333,8 @@ fichier de configuration des branches. cette option est prioritaire sur --config
|
||||
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
||||
-w,--show-config action=show "++\
|
||||
afficher la configuration chargée"
|
||||
--composer-select-profile action=composer_select_profile "\
|
||||
sélectionner le profil composer spécifié en argument"
|
||||
-O:,--origin Origin= "++\
|
||||
origine vers laquelle pousser les branches"
|
||||
-n,--no-push Push= "\
|
||||
@ -226,6 +342,8 @@ ne pas pousser les branches vers leur origine après leur création"
|
||||
--push Push=1 "++\
|
||||
pousser les branches vers leur origine après leur création.
|
||||
c'est l'option par défaut"
|
||||
-f,--force-create ForceCreate=1 "\
|
||||
Avec config, forcer la (re)création du fichier .pman.conf"
|
||||
)
|
||||
parse_args "$@"; set -- "${args[@]}"
|
||||
|
||||
@ -244,6 +362,9 @@ init)
|
||||
git_ensure_cleancheckout
|
||||
init_action "$@"
|
||||
;;
|
||||
composer_select_profile)
|
||||
exec "$MYDIR/_pman-$action.php" "$@"
|
||||
;;
|
||||
*)
|
||||
die "$action: action non implémentée"
|
||||
;;
|
||||
|
103
bin/prel
103
bin/prel
@ -34,12 +34,19 @@ function ensure_branches() {
|
||||
function create_release_action() {
|
||||
if [ -n "$ReleaseBranch" ]; then
|
||||
Version="${ReleaseBranch#$RELEASE}"
|
||||
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
|
||||
merge_release_action "$@"; return $?
|
||||
elif [ -n "$HotfixBranch" ]; then
|
||||
Version="${HotfixBranch#$HOTFIX}"
|
||||
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
|
||||
merge_hotfix_action "$@"; return $?
|
||||
fi
|
||||
|
||||
[ -n "$ManualRelease" ] && ewarn "\
|
||||
L'option --no-merge a été forcée puisque ce dépôt ne supporte pas les releases automatiques"
|
||||
[ -z "$ShouldPush" ] && enote "\
|
||||
L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine"
|
||||
|
||||
if [ -z "$Version" -a -n "$CurrentVersion" -a -f VERSION.txt ]; then
|
||||
Version="$(<VERSION.txt)"
|
||||
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
|
||||
@ -67,7 +74,7 @@ Vous devrez:
|
||||
fi
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
local script=".git/rel-release.sh"
|
||||
local script=".git/pman-release.sh"
|
||||
local -a push_branches push_tags
|
||||
local comment=
|
||||
local or_die=" || exit 1"
|
||||
@ -78,8 +85,18 @@ Vous devrez:
|
||||
# create
|
||||
if [ -n "\$create" ]; then
|
||||
esection "Création de la release"
|
||||
EOF
|
||||
[ -n "$BEFORE_CREATE_RELEASE" ] && _scripta <<EOF
|
||||
(
|
||||
$BEFORE_CREATE_RELEASE
|
||||
)$or_die
|
||||
EOF
|
||||
_rscript_create_release_branch
|
||||
[ -n "$AFTER_CREATE_RELEASE" ] && _scripta <<EOF
|
||||
(
|
||||
$AFTER_CREATE_RELEASE
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
@ -89,10 +106,20 @@ EOF
|
||||
# merge
|
||||
if [ -n "\$merge" ]; then
|
||||
esection "Fusionner la release"
|
||||
EOF
|
||||
[ -n "$BEFORE_MERGE_RELEASE" ] && _scripta <<EOF
|
||||
(
|
||||
$BEFORE_MERGE_RELEASE
|
||||
)$or_die
|
||||
EOF
|
||||
_rscript_merge_release_branch "$DestBranch" "$Tag"
|
||||
_rscript_merge_release_branch "$SrcBranch"
|
||||
_rscript_delete_release_branch
|
||||
[ -n "$AFTER_MERGE_RELEASE" ] && _scripta <<EOF
|
||||
(
|
||||
$AFTER_MERGE_RELEASE
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
@ -102,30 +129,42 @@ EOF
|
||||
# push
|
||||
if [ -n "\$push" ]; then
|
||||
esection "Pousser branches et tags"
|
||||
EOF
|
||||
[ -n "$BEFORE_PUSH_RELEASE" ] && _scripta <<EOF
|
||||
(
|
||||
$BEFORE_PUSH_RELEASE
|
||||
)$or_die
|
||||
EOF
|
||||
_script_push_branches
|
||||
_script_push_tags
|
||||
[ -n "$AFTER_PUSH_RELEASE" ] && _scripta <<EOF
|
||||
(
|
||||
$AFTER_PUSH_RELEASE
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
|
||||
[ -n "$Merge" ] && Merged=1 || Merged=
|
||||
[ -n "$Push" -o "$ForbidPush" ] && Pushed=1 || Pushed=
|
||||
if [ -n "$_NoRunScript" ]; then
|
||||
einfo "Veuillez consulter le script $script pour le détail des opérations à effectuer"
|
||||
[ -z "$ManualRelease" -a -n "$Merge" ] && ShouldMerge= || ShouldMerge=1
|
||||
[ -n "$ShouldPush" -a -n "$Push" ] && ShouldPush=
|
||||
if [ -n "$_Fake" ]; then
|
||||
cat "$script"
|
||||
elif ! "$script" create ${Merge:+merge} ${Push:+push}; then
|
||||
eimportant "Veuillez consulter le script $script pour le détail des opérations qui n'ont pas pu êtres effectuées"
|
||||
eimportant "\
|
||||
Le script $script a été lancé avec les arguments 'create${Merge:+ merge}${Push:+ push}'
|
||||
En cas d'erreur de merge, veuillez corriger les erreurs puis continuer avec
|
||||
git merge --continue
|
||||
Veuillez aussi consulter le script et/ou le relancer
|
||||
./$script${Push:+ push}"
|
||||
die
|
||||
elif [ -n "$Merged" -a -n "$Pushed" ]; then
|
||||
elif [ -n "$Merge" -a -n "$Push" ]; then
|
||||
[ -n "$_KeepScript" ] || rm "$script"
|
||||
else
|
||||
local cmd
|
||||
[ -n "$Merged" ] || cmd="$cmd
|
||||
./$script merge"
|
||||
[ -n "$Pushed" ] || cmd="$cmd
|
||||
./$script push"
|
||||
einfo "Le script $script a été lancé avec les arguments 'create${Merge:+ merge}${Push:+ push}'
|
||||
Veuillez le consulter pour le détail des autres opérations à effectuer$cmd"
|
||||
einfo "\
|
||||
Le script $script a été lancé avec les arguments 'create${Merge:+ merge}${Push:+ push}'
|
||||
Vous pouvez consulter le script et/ou le relancer
|
||||
./$script${ShouldMerge:+ merge}${ShouldPush:+ push}"
|
||||
fi
|
||||
}
|
||||
|
||||
@ -152,8 +191,8 @@ chdir=
|
||||
Origin=
|
||||
ConfigBranch=
|
||||
ConfigFile=
|
||||
_Fake=
|
||||
_KeepScript=
|
||||
_NoRunScript=
|
||||
action=release
|
||||
[ -z "$PMAN_NO_MERGE" ] && Merge=1 || Merge=
|
||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||
@ -162,7 +201,17 @@ CurrentVersion=
|
||||
ForceCreate=
|
||||
args=(
|
||||
"faire une nouvelle release à partir de la branche source"
|
||||
" -v VERSION [source]"
|
||||
" -v VERSION [source]
|
||||
|
||||
CONFIGURATION
|
||||
Le fichier .pman.conf contient la configuration des branches. Les variables
|
||||
supplémentaires suivantes peuvent être définies:
|
||||
BEFORE_CREATE_RELEASE
|
||||
AFTER_CREATE_RELEASE
|
||||
BEFORE_MERGE_RELEASE
|
||||
AFTER_MERGE_RELEASE
|
||||
BEFORE_PUSH_RELEASE
|
||||
AFTER_PUSH_RELEASE"
|
||||
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
|
||||
-O:,--origin Origin= "++\
|
||||
origine à partir de laquelle les branches distantes sont considérées"
|
||||
@ -171,8 +220,8 @@ branche à partir de laquelle charger la configuration"
|
||||
-c:,--config-file:CONFIG ConfigFile= "++\
|
||||
fichier de configuration des branches. cette option est prioritaire sur --config-branch
|
||||
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
||||
--fake _Fake=1 "++option non documentée"
|
||||
--keep-script _KeepScript=1 "++option non documentée"
|
||||
--no-run-script _NoRunScript=1 "++option non documentée"
|
||||
-w,--show action=show "\
|
||||
lister les modifications qui seraient intégrées dans la release"
|
||||
--release action=release "++\
|
||||
@ -188,11 +237,11 @@ ne pas pousser les branches vers leur origine après la création de la release"
|
||||
--push Push=1 "++\
|
||||
pousser les branches vers leur origine après la création de la release.
|
||||
c'est l'option par défaut"
|
||||
-v:,--version Version= "\
|
||||
-v:,--version:VERSION Version= "\
|
||||
spécifier la version de la release à créer"
|
||||
-C,--current-version CurrentVersion=1 "++\
|
||||
si aucune version n'est spécifiée, prendre la version présente dans le fichier VERSION.txt"
|
||||
-f:,--force-create ForceCreate= "\
|
||||
-f,--force-create ForceCreate=1 "\
|
||||
forcer la création de la release même si le tag correspond à la version existe déjà"
|
||||
)
|
||||
parse_args "$@"; set -- "${args[@]}"
|
||||
@ -203,19 +252,11 @@ load_branches all
|
||||
load_config "$MYNAME"
|
||||
load_branches current "$1"; shift
|
||||
|
||||
if [ -n "$Merge" -a -n "$NOAUTO" ]; then
|
||||
ewarn "L'option --no-merge a été forcée puisque ce dépôt ne supporte pas les releases automatiques"
|
||||
Merge=
|
||||
fi
|
||||
[ -n "$Merge" -a -n "$NOAUTO" ] && ManualRelease=1 || ManualRelease=
|
||||
[ -n "$ManualRelease" ] && Merge=
|
||||
[ -z "$Merge" ] && Push=
|
||||
|
||||
ForbidPush=
|
||||
[ -n "$Origin" ] || Origin=origin
|
||||
if ! git_have_remote "$Origin" && [ -n "$Push" ]; then
|
||||
ewarn "L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine"
|
||||
ForbidPush=1
|
||||
fi
|
||||
[ -n "$ForbidPush" ] && Push=
|
||||
resolve_should_push quiet
|
||||
|
||||
# puis faire l'action que l'on nous demande
|
||||
case "$action" in
|
||||
@ -225,7 +266,7 @@ show)
|
||||
show_action "$@"
|
||||
;;
|
||||
release)
|
||||
git_ensure_cleancheckout
|
||||
[ -z "$_Fake" ] && git_ensure_cleancheckout
|
||||
ensure_branches
|
||||
case "$SrcType" in
|
||||
release) merge_release_action "$@";;
|
||||
|
60
bin/pwip
Executable file
60
bin/pwip
Executable file
@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
source "$(dirname -- "$0")/../load.sh" || exit 1
|
||||
require: git pman pman.conf
|
||||
|
||||
git_cleancheckout_DIRTY="\
|
||||
Vous avez des modifications locales.
|
||||
Enregistrez ces modifications avant de créer une nouvelle branche"
|
||||
|
||||
chdir=
|
||||
Origin=
|
||||
ConfigBranch=
|
||||
ConfigFile=
|
||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||
args=(
|
||||
"créer une branche de feature"
|
||||
"<feature>"
|
||||
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
|
||||
-O:,--origin Origin= "++\
|
||||
origine à partir de laquelle les branches distantes sont considérées"
|
||||
-B:,--config-branch ConfigBranch= "++\
|
||||
branche à partir de laquelle charger la configuration"
|
||||
-c:,--config-file:CONFIG ConfigFile= "++\
|
||||
fichier de configuration des branches. cette option est prioritaire sur --config-branch
|
||||
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
||||
-n,--no-push Push= "\
|
||||
ne pas pousser les branches vers leur origine après la fusion"
|
||||
--push Push=1 "++\
|
||||
pousser les branches vers leur origine après la fusion.
|
||||
c'est l'option par défaut"
|
||||
)
|
||||
parse_args "$@"; set -- "${args[@]}"
|
||||
|
||||
# charger la configuration
|
||||
ensure_gitdir "$chdir"
|
||||
load_branches all
|
||||
load_config "$MYNAME"
|
||||
load_branches current
|
||||
|
||||
branch="$1"
|
||||
if [ -z "$branch" -a ${#FeatureBranches[*]} -eq 1 ]; then
|
||||
branch="${FeatureBranches[0]}"
|
||||
fi
|
||||
[ -n "$branch" ] || die "Vous devez spécifier la branche à créer"
|
||||
branch="$FEATURE${branch#$FEATURE}"
|
||||
|
||||
resolve_should_push
|
||||
git_ensure_cleancheckout
|
||||
|
||||
if array_contains AllBranches "$branch"; then
|
||||
git checkout -q "$branch"
|
||||
else
|
||||
# si la branche source n'existe pas, la créer
|
||||
args=(--origin "$Origin")
|
||||
if [ -n "$ConfigFile" ]; then args+=(--config-file "$ConfigFile")
|
||||
elif [ -n "$ConfigBranch" ]; then args+=(--config-branch "$ConfigBranch")
|
||||
fi
|
||||
[ -z "$Push" ] && args+=(--no-push)
|
||||
exec "$MYDIR/pman" "${args[@]}" "$branch"
|
||||
fi
|
@ -24,6 +24,8 @@
|
||||
"ext-posix": "*",
|
||||
"ext-pcntl": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"ext-pgsql": "*",
|
||||
"ext-sqlite3": "*"
|
||||
},
|
||||
"autoload": {
|
||||
|
31
composer.lock
generated
31
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ab280aa4a5f5c83fa488537530b29759",
|
||||
"content-hash": "a8b9dc80255663640bda855729ef2d47",
|
||||
"packages": [
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@ -301,16 +301,16 @@
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.12.1",
|
||||
"version": "1.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
|
||||
"reference": "024473a478be9df5fdaca2c793f2232fe788e414"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
|
||||
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
|
||||
"reference": "024473a478be9df5fdaca2c793f2232fe788e414",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -349,7 +349,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -357,7 +357,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-08T17:47:46+00:00"
|
||||
"time": "2025-02-12T12:17:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
@ -423,13 +423,19 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.univ-reunion.fr/sda-php/nulib-tests.git",
|
||||
"reference": "9b5c9c295c3dee6fc02ccddbd8a70bca797c8045"
|
||||
"reference": "8f641d9a7cf6aba1453cb42ebd15951aa7002e1b"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.3",
|
||||
"php": "^7.3 || 8.0.*",
|
||||
"phpunit/phpunit": "^9"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-pu9": "7.3.x-dev",
|
||||
"dev-pu10": "8.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"nulib\\tests\\": "src"
|
||||
@ -447,7 +453,7 @@
|
||||
}
|
||||
],
|
||||
"description": "fonctions et classes pour les tests",
|
||||
"time": "2025-01-30T13:18:31+00:00"
|
||||
"time": "2025-02-28T17:12:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
@ -2015,7 +2021,10 @@
|
||||
"platform-dev": {
|
||||
"ext-posix": "*",
|
||||
"ext-pcntl": "*",
|
||||
"ext-curl": "*"
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"ext-pgsql": "*",
|
||||
"ext-sqlite3": "*"
|
||||
},
|
||||
"plugin-api-version": "2.2.0"
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ COPY --from=builder /src/su-exec/su-exec /g/
|
||||
RUN /g/build
|
||||
|
||||
COPY --from=php /g/ /g/
|
||||
RUN /g/build -a @apache-php-cas php-utils
|
||||
RUN /g/build -a @php-apache-cas php-utils
|
||||
|
||||
EXPOSE 80 443
|
||||
ENTRYPOINT ["/g/entrypoint"]
|
||||
|
@ -34,7 +34,7 @@ COPY --from=legacytools /g/ /g/
|
||||
RUN /g/build nutools
|
||||
|
||||
COPY --from=php /g/ /g/
|
||||
RUN /g/build -a @apache-php-cas php-utils
|
||||
RUN /g/build -a @php-apache-cas php-utils
|
||||
|
||||
COPY --from=instantclient /g/ /g/
|
||||
COPY --from=builder /opt/oracle/ /opt/oracle/
|
||||
|
@ -15,9 +15,9 @@ class StateException extends LogicException {
|
||||
return new static($prefix.$message);
|
||||
}
|
||||
|
||||
static final function unexpected_state(?string $prefix=null): self {
|
||||
static final function unexpected_state(?string $suffix=null): self {
|
||||
$message = "unexpected state";
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
return new static($prefix.$message);
|
||||
if ($suffix) $suffix = ": $suffix";
|
||||
return new static($message.$suffix);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
namespace nulib;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\func;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
@ -348,12 +348,12 @@ class cl {
|
||||
|
||||
#############################################################################
|
||||
|
||||
static final function map(callable $callback, ?iterable $array): array {
|
||||
static final function map($func, ?iterable $array): array {
|
||||
$result = [];
|
||||
if ($array !== null) {
|
||||
$ctx = nur_func::_prepare($callback);
|
||||
$func = func::with($func);
|
||||
foreach ($array as $key => $value) {
|
||||
$result[$key] = nur_func::_call($ctx, [$value, $key]);
|
||||
$result[$key] = $func->invoke([$value, $key]);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
@ -610,6 +610,42 @@ class cl {
|
||||
|
||||
#############################################################################
|
||||
|
||||
/**
|
||||
* tester si $array a en début de tableau les mêmes clés que $ref, et dans le
|
||||
* même ordre. $array peut avoir d'autres clés, ça n'influe pas sur le résultat
|
||||
*
|
||||
* $keys obtient la liste des clés de $ref trouvées, dans l'ordre de $array
|
||||
* $remainKeys obtient la liste des clés de $array qui ne sont pas dans $ref
|
||||
* $missingKeys obtient la liste des clés de $ref qui ne sont pas dans $array
|
||||
*/
|
||||
static function same_keys(?array $array, ?array $ref, ?array &$keys=null, ?array &$remainKeys=null, ?array &$missingKeys=null): bool {
|
||||
$keys = [];
|
||||
$remainKeys = [];
|
||||
$missingKeys = [];
|
||||
if ($array === null || $array === []) {
|
||||
if ($ref === null || $ref === []) return true;
|
||||
$missingKeys = array_keys($ref);
|
||||
return false;
|
||||
} elseif ($ref === null || $ref === []) {
|
||||
$remainKeys = array_keys($array);
|
||||
return true;
|
||||
}
|
||||
$refKeys = array_keys($ref);
|
||||
$refCount = count($ref);
|
||||
$index = 0;
|
||||
$sameKeys = true;
|
||||
foreach (array_keys($array) as $key) {
|
||||
if ($index < $refCount) {
|
||||
if ($key !== $refKeys[$index]) $sameKeys = false;
|
||||
$index++;
|
||||
}
|
||||
if (array_key_exists($key, $ref)) $keys[] = $key;
|
||||
else $remainKeys[] = $key;
|
||||
}
|
||||
$missingKeys = array_values(array_diff($refKeys, $keys));
|
||||
return $sameKeys && $index == $refCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner le tableau $array en "renommant" les clés selon le tableau
|
||||
* $mappings qui contient des associations de la forme [$from => $to]
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace nulib\db;
|
||||
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
use Traversable;
|
||||
|
||||
@ -87,7 +87,7 @@ class Capacitor implements ITransactor {
|
||||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
nur_func::call($func, $this);
|
||||
func::call($func, $this);
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
@ -120,10 +120,6 @@ class Capacitor implements ITransactor {
|
||||
if ($db->inTransaction()) $db->rollback();
|
||||
}
|
||||
|
||||
function getCreateSql(): string {
|
||||
return $this->storage->_getCreateSql($this->channel);
|
||||
}
|
||||
|
||||
function exists(): bool {
|
||||
return $this->storage->_exists($this->channel);
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ class CapacitorChannel {
|
||||
|
||||
const PRIMARY_KEYS = null;
|
||||
|
||||
const MIGRATION = null;
|
||||
|
||||
const MANAGE_TRANSACTIONS = true;
|
||||
|
||||
const EACH_COMMIT_THRESHOLD = 100;
|
||||
@ -63,15 +65,29 @@ class CapacitorChannel {
|
||||
$this->created = false;
|
||||
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
|
||||
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
|
||||
if ($primaryKeys === null && $columnDefinitions !== null) {
|
||||
$migration = cl::withn(static::MIGRATION);
|
||||
if ($columnDefinitions !== null) {
|
||||
# mettre à jour la liste des clés primaires et des migrations
|
||||
$index = 0;
|
||||
foreach ($columnDefinitions as $col => $def) {
|
||||
if ($col === $index) {
|
||||
# si définition séquentielle, seules les définitions de clé
|
||||
# primaires sont supportées
|
||||
$index++;
|
||||
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
|
||||
$primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
|
||||
}
|
||||
} elseif (is_array($def)) {
|
||||
# tableau: c'est une migration
|
||||
$def = implode(" ", $def);
|
||||
if ($def) {
|
||||
$migration["add_$col"] = "alter table $tableName add column $col $def";
|
||||
} else {
|
||||
$migration["drop_$col"] = "alter table $tableName drop column $col";
|
||||
}
|
||||
} elseif (is_scalar($def)) {
|
||||
# chaine: c'est une définition
|
||||
$def = strval($def);
|
||||
if (preg_match('/\bprimary\s+key\b/i', $def)) {
|
||||
$primaryKeys[] = $col;
|
||||
}
|
||||
@ -80,6 +96,7 @@ class CapacitorChannel {
|
||||
}
|
||||
$this->columnDefinitions = $columnDefinitions;
|
||||
$this->primaryKeys = $primaryKeys;
|
||||
$this->migration = $migration;
|
||||
}
|
||||
|
||||
protected string $name;
|
||||
@ -192,6 +209,12 @@ class CapacitorChannel {
|
||||
return $this->columnDefinitions;
|
||||
}
|
||||
|
||||
protected ?array $migration;
|
||||
|
||||
function getMigration(): ?array {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
protected ?array $primaryKeys;
|
||||
|
||||
function getPrimaryKeys(): ?array {
|
||||
@ -245,9 +268,6 @@ class CapacitorChannel {
|
||||
return $serial !== null? unserialize($serial): null;
|
||||
}
|
||||
|
||||
const SERIAL_DEFINITION = "mediumtext";
|
||||
const SUM_DEFINITION = "varchar(40)";
|
||||
|
||||
final function sum(?string $serial, $value=null): ?string {
|
||||
if ($serial === null) $serial = $this->serialize($value);
|
||||
return $serial !== null? sha1($serial): null;
|
||||
|
@ -2,8 +2,9 @@
|
||||
namespace nulib\db;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\_private\_migration;
|
||||
use nulib\db\cache\cache;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
use Traversable;
|
||||
|
||||
@ -35,14 +36,18 @@ abstract class CapacitorStorage {
|
||||
/** DOIT être défini dans les classes dérivées */
|
||||
const PRIMARY_KEY_DEFINITION = null;
|
||||
|
||||
const SERDATA_DEFINITION = "mediumtext";
|
||||
const SERSUM_DEFINITION = "varchar(40)";
|
||||
const SERTS_DEFINITION = "datetime";
|
||||
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"item__" => CapacitorChannel::SERIAL_DEFINITION,
|
||||
"item__sum_" => CapacitorChannel::SUM_DEFINITION,
|
||||
"created_" => "datetime",
|
||||
"modified_" => "datetime",
|
||||
"item__" => "serdata",
|
||||
"item__sum_" => "sersum",
|
||||
"created_" => "serts",
|
||||
"modified_" => "serts",
|
||||
];
|
||||
|
||||
protected function ColumnDefinitions(CapacitorChannel $channel): array {
|
||||
protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
|
||||
$definitions = [];
|
||||
if ($channel->getPrimaryKeys() === null) {
|
||||
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
||||
@ -56,16 +61,31 @@ abstract class CapacitorStorage {
|
||||
$constraints = [];
|
||||
$index = 0;
|
||||
foreach ($tmp as $col => $def) {
|
||||
switch ($def) {
|
||||
case "serdata": $def = static::SERDATA_DEFINITION; break;
|
||||
case "sersum": $def = static::SERSUM_DEFINITION; break;
|
||||
case "serts": $def = static::SERTS_DEFINITION; break;
|
||||
}
|
||||
if ($col === $index) {
|
||||
$index++;
|
||||
$constraints[] = $def;
|
||||
} else {
|
||||
} elseif (is_array($def)) {
|
||||
# éventuellement, ignorer les migrations
|
||||
$def = implode(" ", $def);
|
||||
if ($def && !$ignoreMigrations) {
|
||||
$definitions[$col] = $def;
|
||||
}
|
||||
} elseif (is_scalar($def)) {
|
||||
$definitions[$col] = strval($def);
|
||||
}
|
||||
}
|
||||
return cl::merge($definitions, $constraints);
|
||||
}
|
||||
|
||||
protected function getMigration(CapacitorChannel $channel): ?array {
|
||||
return $channel->getMigration();
|
||||
}
|
||||
|
||||
/** sérialiser les valeurs qui doivent l'être dans $values */
|
||||
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
|
||||
if ($values === null) return null;
|
||||
@ -128,11 +148,10 @@ abstract class CapacitorStorage {
|
||||
}
|
||||
|
||||
protected function _createSql(CapacitorChannel $channel): array {
|
||||
$cols = $this->ColumnDefinitions($channel);
|
||||
return [
|
||||
"create table if not exists",
|
||||
"table" => $channel->getTableName(),
|
||||
"cols" => $cols,
|
||||
"cols" => $this->ColumnDefinitions($channel, true),
|
||||
];
|
||||
}
|
||||
|
||||
@ -147,20 +166,45 @@ $sql;
|
||||
EOT;
|
||||
}
|
||||
|
||||
abstract function _getCreateSql(CapacitorChannel $channel): string;
|
||||
abstract function _getMigration(CapacitorChannel $channel): _migration;
|
||||
|
||||
/** obtenir la requête SQL utilisée pour créer la table */
|
||||
function getCreateSql(?string $channel): string {
|
||||
return $this->_getCreateSql($this->getChannel($channel));
|
||||
const CHANNELS_TABLE = "_channels";
|
||||
const CHANNELS_COLS = [
|
||||
"name" => "varchar primary key",
|
||||
"table_name" => "varchar",
|
||||
"class_name" => "varchar",
|
||||
];
|
||||
|
||||
protected function _createChannelsSql(): array {
|
||||
return [
|
||||
"create table if not exists",
|
||||
"table" => static::CHANNELS_TABLE,
|
||||
"cols" => static::CHANNELS_COLS,
|
||||
];
|
||||
}
|
||||
|
||||
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||
return [
|
||||
"insert",
|
||||
"into" => static::CHANNELS_TABLE,
|
||||
"values" => [
|
||||
"name" => $channel->getName(),
|
||||
"table_name" => $channel->getTableName(),
|
||||
"class_name" => get_class($channel),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||
$db = $this->db();
|
||||
$db->exec($this->_createChannelsSql());
|
||||
$db->exec($this->_addToChannelsSql($channel));
|
||||
}
|
||||
|
||||
protected function _create(CapacitorChannel $channel): void {
|
||||
$channel->ensureSetup();
|
||||
if (!$channel->isCreated()) {
|
||||
$this->db->exec($this->_createSql($channel));
|
||||
$this->_getMigration($channel)->migrate($this->db());
|
||||
$this->_afterCreate($channel);
|
||||
$channel->setCreated();
|
||||
}
|
||||
@ -183,12 +227,28 @@ EOT;
|
||||
}
|
||||
|
||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
||||
$db = $this->db;
|
||||
$name = $channel->getName();
|
||||
$db->exec([
|
||||
"delete",
|
||||
"from" => _migration::MIGRATION_TABLE,
|
||||
"where" => [
|
||||
"channel" => $name,
|
||||
],
|
||||
]);
|
||||
$db->exec([
|
||||
"delete",
|
||||
"from" => static::CHANNELS_TABLE,
|
||||
"where" => [
|
||||
"name" => $name,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/** supprimer le canal spécifié */
|
||||
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
||||
$this->_beforeReset($channel);
|
||||
$this->db->exec([
|
||||
$this->db()->exec([
|
||||
"drop table if exists",
|
||||
$channel->getTableName(),
|
||||
]);
|
||||
@ -230,10 +290,7 @@ EOT;
|
||||
$db = $this->db();
|
||||
$args ??= [];
|
||||
|
||||
$initFunc = [$channel, "getItemValues"];
|
||||
$initArgs = $args;
|
||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
||||
$values = nur_func::call($initFunc, $item, ...$initArgs);
|
||||
$values = func::call([$channel, "getItemValues"], $item, ...$args);
|
||||
if ($values === [false]) return 0;
|
||||
|
||||
$row = cl::merge(
|
||||
@ -259,9 +316,7 @@ EOT;
|
||||
"modified_" => $now,
|
||||
]);
|
||||
$insert = true;
|
||||
$initFunc = [$channel, "onCreate"];
|
||||
$initArgs = $args;
|
||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
||||
$initFunc = func::with([$channel, "onCreate"], $args);
|
||||
$values = $this->unserialize($channel, $row);
|
||||
$pvalues = null;
|
||||
} else {
|
||||
@ -276,14 +331,12 @@ EOT;
|
||||
} else {
|
||||
$row = cl::merge($prow, $row);
|
||||
}
|
||||
$initFunc = [$channel, "onUpdate"];
|
||||
$initArgs = $args;
|
||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
||||
$initFunc = func::with([$channel, "onUpdate"], $args);
|
||||
$values = $this->unserialize($channel, $row);
|
||||
$pvalues = $this->unserialize($channel, $prow);
|
||||
}
|
||||
|
||||
$updates = nur_func::call($initFunc, $item, $values, $pvalues, ...$initArgs);
|
||||
$updates = $initFunc->prependArgs([$item, $values, $pvalues])->invoke();
|
||||
if ($updates === [false]) return 0;
|
||||
if (is_array($updates) && $updates) {
|
||||
if ($insert === null) $insert = false;
|
||||
@ -295,8 +348,10 @@ EOT;
|
||||
}
|
||||
|
||||
if ($func !== null) {
|
||||
nur_func::ensure_func($func, $channel, $args);
|
||||
$updates = nur_func::call($func, $item, $values, $pvalues, ...$args);
|
||||
$updates = func::with($func)
|
||||
->prependArgs([$item, $values, $pvalues])
|
||||
->bind($channel, true)
|
||||
->invoke();
|
||||
if ($updates === [false]) return 0;
|
||||
if (is_array($updates) && $updates) {
|
||||
if ($insert === null) $insert = false;
|
||||
@ -510,8 +565,7 @@ EOT;
|
||||
function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||
$this->_create($channel);
|
||||
if ($func === null) $func = CapacitorChannel::onEach;
|
||||
nur_func::ensure_func($func, $channel, $args);
|
||||
$onEach = nur_func::_prepare($func);
|
||||
$onEach = func::with($func)->bind($channel, true);
|
||||
$db = $this->db();
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
@ -528,7 +582,7 @@ EOT;
|
||||
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
||||
foreach ($all as $values) {
|
||||
$rowIds = $this->getRowIds($channel, $values);
|
||||
$updates = nur_func::_call($onEach, [$values["item"], $values, ...$args]);
|
||||
$updates = $onEach->invoke([$values["item"], $values, ...$args]);
|
||||
if (is_array($updates) && $updates) {
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = date("Y-m-d H:i:s");
|
||||
@ -579,8 +633,7 @@ EOT;
|
||||
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
||||
$this->_create($channel);
|
||||
if ($func === null) $func = CapacitorChannel::onDelete;
|
||||
nur_func::ensure_func($func, $channel, $args);
|
||||
$onEach = nur_func::_prepare($func);
|
||||
$onEach = func::with($func)->bind($channel, true);
|
||||
$db = $this->db();
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
@ -596,7 +649,7 @@ EOT;
|
||||
$all = $this->_allCached("delete", $channel, $filter);
|
||||
foreach ($all as $values) {
|
||||
$rowIds = $this->getRowIds($channel, $values);
|
||||
$delete = boolval(nur_func::_call($onEach, [$values["item"], $values, ...$args]));
|
||||
$delete = boolval($onEach->invoke([$values["item"], $values, ...$args]));
|
||||
if ($delete) {
|
||||
$db->exec([
|
||||
"delete",
|
||||
|
@ -15,5 +15,9 @@ interface IDatabase extends ITransactor {
|
||||
|
||||
function one($query, ?array $params=null): ?array;
|
||||
|
||||
/**
|
||||
* si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
|
||||
* spécifiée(s)
|
||||
*/
|
||||
function all($query, ?array $params=null, $primaryKeys=null): iterable;
|
||||
}
|
||||
|
@ -11,12 +11,13 @@ interface ITransactor {
|
||||
*/
|
||||
function willUpdate(...$transactors): self;
|
||||
|
||||
/** Indiquer si une transaction est en cours */
|
||||
function inTransaction(): bool;
|
||||
|
||||
/**
|
||||
* démarrer une transaction
|
||||
*
|
||||
* si $func!==null, l'apppeler. ensuite, si $commit===true, commiter la
|
||||
* si $func!==null, l'apppeler. ensuite, si $commit===true, valider la
|
||||
* transaction. si une erreur se produit lors de l'appel de la fonction,
|
||||
* annuler la transaction
|
||||
*
|
||||
@ -24,7 +25,9 @@ interface ITransactor {
|
||||
*/
|
||||
function beginTransaction(?callable $func=null, bool $commit=true): void;
|
||||
|
||||
/** valider la transaction */
|
||||
function commit(): void;
|
||||
|
||||
/** annuler la transaction */
|
||||
function rollback(): void;
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
trait Tcreate {
|
||||
static function isa(string $sql): bool {
|
||||
//return preg_match("/^create(?:\s+table)?\b/i", $sql);
|
||||
#XXX implémentation minimale
|
||||
return preg_match("/^create\s+table\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$sql[] = $query["table"];
|
||||
|
||||
## columns
|
||||
$cols = $query["cols"];
|
||||
$index = 0;
|
||||
foreach ($cols as $col => &$definition) {
|
||||
if ($col === $index) {
|
||||
$index++;
|
||||
} else {
|
||||
$definition = "$col $definition";
|
||||
}
|
||||
}; unset($definition);
|
||||
$sql[] = "(\n ".implode("\n, ", $cols)."\n)";
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
trait Tdelete {
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^delete(?:\s+from)?\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
#XXX implémentation minimale
|
||||
$tmpsql = self::merge_seq($query);
|
||||
self::consume('delete(?:\s+from)?\b', $tmpsql);
|
||||
$sql = ["delete from", $tmpsql];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$sql[] = $query["from"];
|
||||
|
||||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
self::parse_conds($where, $wheresql, $bindings);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
}
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
|
||||
trait Tgeneric {
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match('/^(?:drop\s+table)\b/i', $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
if (!cl::is_list($query)) {
|
||||
throw new ValueException("Seuls les tableaux séquentiels sont supportés");
|
||||
}
|
||||
return self::merge_seq($query);
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
|
||||
trait Tinsert {
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^insert\b/i", $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* parser une chaine de la forme
|
||||
* "insert [into] [TABLE] [(COLS)] [values (VALUES)]"
|
||||
*/
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
# fusionner d'abord toutes les parties séquentielles
|
||||
$usersql = $tmpsql = self::merge_seq($query);
|
||||
|
||||
### vérifier la présence des parties nécessaires
|
||||
$sql = [];
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## insert
|
||||
self::consume('(insert(?:\s+or\s+(?:ignore|replace))?)\s*', $tmpsql, $ms);
|
||||
$sql[] = $ms[1];
|
||||
|
||||
## into
|
||||
self::consume('into\s*', $tmpsql);
|
||||
$sql[] = "into";
|
||||
$into = $query["into"] ?? null;
|
||||
if (self::consume('([a-z_][a-z0-9_]*)\s*', $tmpsql, $ms)) {
|
||||
if ($into === null) $into = $ms[1];
|
||||
$sql[] = $into;
|
||||
} elseif ($into !== null) {
|
||||
$sql[] = $into;
|
||||
} else {
|
||||
throw new ValueException("expected table name: $usersql");
|
||||
}
|
||||
|
||||
## cols & values
|
||||
$usercols = [];
|
||||
$uservalues = [];
|
||||
if (self::consume('\(([^)]*)\)\s*', $tmpsql, $ms)) {
|
||||
$usercols = array_merge($usercols, preg_split("/\s*,\s*/", $ms[1]));
|
||||
}
|
||||
$cols = cl::withn($query["cols"] ?? null);
|
||||
$values = cl::withn($query["values"] ?? null);
|
||||
$schema = $query["schema"] ?? null;
|
||||
if ($cols === null) {
|
||||
if ($usercols) {
|
||||
$cols = $usercols;
|
||||
} elseif ($values) {
|
||||
$cols = array_keys($values);
|
||||
$usercols = array_merge($usercols, $cols);
|
||||
} elseif ($schema && is_array($schema)) {
|
||||
#XXX implémenter support AssocSchema
|
||||
$cols = array_keys($schema);
|
||||
$usercols = array_merge($usercols, $cols);
|
||||
}
|
||||
}
|
||||
if (self::consume('values\s+\(\s*(.*)\s*\)\s*', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $uservalues[] = $ms[1];
|
||||
}
|
||||
if ($cols !== null && !$uservalues) {
|
||||
if (!$usercols) $usercols = $cols;
|
||||
foreach ($cols as $col) {
|
||||
$uservalues[] = ":$col";
|
||||
$bindings[$col] = $values[$col] ?? null;
|
||||
}
|
||||
}
|
||||
$sql[] = "(" . implode(", ", $usercols) . ")";
|
||||
$sql[] = "values (" . implode(", ", $uservalues) . ")";
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
self::check_eof($tmpsql, $usersql);
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
|
||||
trait Tselect {
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^select\b/i", $sql);
|
||||
}
|
||||
|
||||
private static function add_prefix(string $col, ?string $prefix): string {
|
||||
if ($prefix === null) return $col;
|
||||
if (strpos($col, ".") !== false) return $col;
|
||||
return "$prefix$col";
|
||||
}
|
||||
|
||||
/**
|
||||
* parser une chaine de la forme
|
||||
* "select [COLS] [from TABLE] [where CONDS] [order by ORDERS] [group by GROUPS] [having CONDS]"
|
||||
*/
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
# fusionner d'abord toutes les parties séquentielles
|
||||
$usersql = $tmpsql = self::merge_seq($query);
|
||||
|
||||
### vérifier la présence des parties nécessaires
|
||||
$sql = [];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## select
|
||||
self::consume('(select(?:\s*distinct)?)\s*', $tmpsql, $ms);
|
||||
$sql[] = $ms[1];
|
||||
|
||||
## cols
|
||||
$usercols = [];
|
||||
if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $usercols[] = $ms[1];
|
||||
}
|
||||
$colPrefix = $query["col_prefix"] ?? null;
|
||||
if ($colPrefix !== null) str::add_suffix($colPrefix, ".");
|
||||
$tmpcols = cl::withn($query["cols"] ?? null);
|
||||
$schema = $query["schema"] ?? null;
|
||||
if ($tmpcols !== null) {
|
||||
$cols = [];
|
||||
$index = 0;
|
||||
foreach ($tmpcols as $key => $col) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
$cols[] = $col;
|
||||
$usercols[] = self::add_prefix($col, $colPrefix);
|
||||
} else {
|
||||
$cols[] = $key;
|
||||
$usercols[] = self::add_prefix($col, $colPrefix)." as $key";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$cols = null;
|
||||
if ($schema && is_array($schema) && !in_array("*", $usercols)) {
|
||||
$cols = array_keys($schema);
|
||||
foreach ($cols as $col) {
|
||||
$usercols[] = self::add_prefix($col, $colPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$usercols && !$cols) $usercols = [self::add_prefix("*", $colPrefix)];
|
||||
$sql[] = implode(", ", $usercols);
|
||||
|
||||
## from
|
||||
$from = $query["from"] ?? null;
|
||||
if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?=;?\s*$|\bwhere\b)', $tmpsql, $ms)) {
|
||||
if ($from === null) $from = $ms[1];
|
||||
$sql[] = "from";
|
||||
$sql[] = $from;
|
||||
} elseif ($from !== null) {
|
||||
$sql[] = "from";
|
||||
$sql[] = $from;
|
||||
} else {
|
||||
throw new ValueException("expected table name: $usersql");
|
||||
}
|
||||
|
||||
## where
|
||||
$userwhere = [];
|
||||
if (self::consume('where\b\s*(.*?)(?=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $userwhere[] = $ms[1];
|
||||
}
|
||||
$where = cl::withn($query["where"] ?? null);
|
||||
if ($where !== null) self::parse_conds($where, $userwhere, $bindings);
|
||||
if ($userwhere) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $userwhere);
|
||||
}
|
||||
|
||||
## order by
|
||||
$userorderby = [];
|
||||
if (self::consume('order\s+by\b\s*(.*?)(?=;?\s*$|\bgroup\s+by\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $userorderby[] = $ms[1];
|
||||
}
|
||||
$orderby = cl::withn($query["order by"] ?? null);
|
||||
if ($orderby !== null) {
|
||||
$index = 0;
|
||||
foreach ($orderby as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$userorderby[] = $value;
|
||||
$index++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$userorderby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$userorderby[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($userorderby) {
|
||||
$sql[] = "order by";
|
||||
$sql[] = implode(", ", $userorderby);
|
||||
}
|
||||
## group by
|
||||
$usergroupby = [];
|
||||
if (self::consume('group\s+by\b\s*(.*?)(?=;?\s*$|\bhaving\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $usergroupby[] = $ms[1];
|
||||
}
|
||||
$groupby = cl::withn($query["group by"] ?? null);
|
||||
if ($groupby !== null) {
|
||||
$index = 0;
|
||||
foreach ($groupby as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$usergroupby[] = $value;
|
||||
$index++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$usergroupby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$usergroupby[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($usergroupby) {
|
||||
$sql[] = "group by";
|
||||
$sql[] = implode(", ", $usergroupby);
|
||||
}
|
||||
|
||||
## having
|
||||
$userhaving = [];
|
||||
if (self::consume('having\b\s*(.*?)(?=;?\s*$)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $userhaving[] = $ms[1];
|
||||
}
|
||||
$having = cl::withn($query["having"] ?? null);
|
||||
if ($having !== null) self::parse_conds($having, $userhaving, $bindings);
|
||||
if ($userhaving) {
|
||||
$sql[] = "having";
|
||||
$sql[] = implode(" and ", $userhaving);
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
self::check_eof($tmpsql, $usersql);
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
trait Tupdate {
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^update\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$sql[] = $query["table"];
|
||||
|
||||
## set
|
||||
self::parse_set_values($query["values"], $setsql, $bindings);
|
||||
$sql[] = "set";
|
||||
$sql[] = implode(", ", $setsql);
|
||||
|
||||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
self::parse_conds($where, $wheresql, $bindings);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
}
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
@ -5,255 +5,57 @@ use nulib\cl;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
|
||||
abstract class _base {
|
||||
protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
|
||||
if (!preg_match("/^$pattern/i", $string, $ms)) return false;
|
||||
$string = substr($string, strlen($ms[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** fusionner toutes les parties séquentielles d'une requête */
|
||||
protected static function merge_seq(array $query): string {
|
||||
$index = 0;
|
||||
$sql = "";
|
||||
foreach ($query as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
|
||||
$sql .= " ";
|
||||
}
|
||||
$sql .= $value;
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
protected static function is_sep(&$cond): bool {
|
||||
if (!is_string($cond)) return false;
|
||||
if (!preg_match('/^\s*(and|or|not)\s*$/i', $cond, $ms)) return false;
|
||||
$cond = $ms[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
static function parse_conds(?array $conds, ?array &$sql, ?array &$bindings): void {
|
||||
if (!$conds) return;
|
||||
$sep = null;
|
||||
$index = 0;
|
||||
$condsql = [];
|
||||
foreach ($conds as $key => $cond) {
|
||||
if ($key === $index) {
|
||||
## séquentiel
|
||||
if ($index === 0 && self::is_sep($cond)) {
|
||||
$sep = $cond;
|
||||
} elseif (is_bool($cond)) {
|
||||
# ignorer les valeurs true et false
|
||||
} elseif (is_array($cond)) {
|
||||
# condition récursive
|
||||
self::parse_conds($cond, $condsql, $bindings);
|
||||
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");
|
||||
} elseif (_create::isa($prefix)) {
|
||||
$sql = _create::parse($sql, $bindings);
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_select::isa($prefix)) {
|
||||
$sql = _select::parse($sql, $bindings);
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_insert::isa($prefix)) {
|
||||
$sql = _insert::parse($sql, $bindings);
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_update::isa($prefix)) {
|
||||
$sql = _update::parse($sql, $bindings);
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_delete::isa($prefix)) {
|
||||
$sql = _delete::parse($sql, $bindings);
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_generic::isa($prefix)) {
|
||||
$sql = _generic::parse($sql, $bindings);
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
# condition litérale
|
||||
$condsql[] = strval($cond);
|
||||
}
|
||||
$index++;
|
||||
} elseif ($cond === false) {
|
||||
## associatif
|
||||
# condition litérale ignorée car condition false
|
||||
} elseif ($cond === true) {
|
||||
# condition litérale sélectionnée car condition true
|
||||
$condsql[] = strval($key);
|
||||
} else {
|
||||
## associatif
|
||||
# paramètre
|
||||
$param0 = preg_replace('/^.+\./', "", $key);
|
||||
$i = false;
|
||||
if ($bindings !== null && array_key_exists($param0, $bindings)) {
|
||||
$i = 2;
|
||||
while (array_key_exists("$param0$i", $bindings)) {
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
# value ou [operator, value]
|
||||
$condprefix = $condsep = $condsuffix = null;
|
||||
if (is_array($cond)) {
|
||||
$condkey = 0;
|
||||
$condkeys = array_keys($cond);
|
||||
$op = null;
|
||||
if (array_key_exists("op", $cond)) {
|
||||
$op = $cond["op"];
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$op = $cond[$condkeys[$condkey]];
|
||||
$condkey++;
|
||||
}
|
||||
$op = strtolower($op);
|
||||
$condvalues = null;
|
||||
switch ($op) {
|
||||
case "between":
|
||||
# ["between", $upper, $lower]
|
||||
$condsep = " and ";
|
||||
if (array_key_exists("lower", $cond)) {
|
||||
$condvalues[] = $cond["lower"];
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues[] = $cond[$condkeys[$condkey]];
|
||||
$condkey++;
|
||||
}
|
||||
if (array_key_exists("upper", $cond)) {
|
||||
$condvalues[] = $cond["upper"];
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues[] = $cond[$condkeys[$condkey]];
|
||||
$condkey++;
|
||||
}
|
||||
break;
|
||||
case "any":
|
||||
case "all":
|
||||
case "not any":
|
||||
case "not all":
|
||||
# ["list", $values]
|
||||
if ($op === "any" || $op === "all") {
|
||||
$condprefix = $op;
|
||||
$op = "=";
|
||||
} elseif ($op === "not any" || $op === "not all") {
|
||||
$condprefix = substr($op, strlen("not "));
|
||||
$op = "<>";
|
||||
}
|
||||
$condprefix .= "(array[";
|
||||
$condsep = ", ";
|
||||
$condsuffix = "])";
|
||||
$condvalues = null;
|
||||
if (array_key_exists("values", $cond)) {
|
||||
$condvalues = cl::with($cond["values"]);
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues = cl::with($cond[$condkeys[$condkey]]);
|
||||
$condkey++;
|
||||
}
|
||||
break;
|
||||
case "in":
|
||||
# ["in", $values]
|
||||
$condprefix = "(";
|
||||
$condsep = ", ";
|
||||
$condsuffix = ")";
|
||||
$condvalues = null;
|
||||
if (array_key_exists("values", $cond)) {
|
||||
$condvalues = cl::with($cond["values"]);
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues = cl::with($cond[$condkeys[$condkey]]);
|
||||
$condkey++;
|
||||
}
|
||||
break;
|
||||
case "null":
|
||||
case "is null":
|
||||
$op = "is null";
|
||||
break;
|
||||
case "not null":
|
||||
case "is not null":
|
||||
$op = "is not null";
|
||||
break;
|
||||
default:
|
||||
if (array_key_exists("value", $cond)) {
|
||||
$condvalues = [$cond["value"]];
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues = [$cond[$condkeys[$condkey]]];
|
||||
$condkey++;
|
||||
}
|
||||
}
|
||||
} elseif ($cond !== null) {
|
||||
$op = "=";
|
||||
$condvalues = [$cond];
|
||||
} else {
|
||||
$op = "is null";
|
||||
$condvalues = null;
|
||||
}
|
||||
$cond = [$key, $op];
|
||||
if ($condvalues !== null) {
|
||||
$parts = [];
|
||||
foreach ($condvalues as $condvalue) {
|
||||
if (is_array($condvalue)) {
|
||||
$first = true;
|
||||
foreach ($condvalue as $value) {
|
||||
if ($first) {
|
||||
$first = false;
|
||||
} else {
|
||||
if ($sep === null) $sep = "and";
|
||||
$parts[] = " $sep ";
|
||||
$parts[] = $key;
|
||||
$parts[] = " $op ";
|
||||
}
|
||||
$param = "$param0$i";
|
||||
$parts[] = ":$param";
|
||||
$bindings[$param] = $value;
|
||||
if ($i === false) $i = 2;
|
||||
else $i++;
|
||||
throw ValueException::invalid_kind($sql, "query");
|
||||
}
|
||||
} else {
|
||||
$param = "$param0$i";
|
||||
$parts[] = ":$param";
|
||||
$bindings[$param] = $condvalue;
|
||||
if ($i === false) $i = 2;
|
||||
else $i++;
|
||||
if (!is_string($sql)) $sql = strval($sql);
|
||||
if (_create::isa($sql)) {
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_select::isa($sql)) {
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_insert::isa($sql)) {
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_update::isa($sql)) {
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_delete::isa($sql)) {
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_generic::isa($sql)) {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
}
|
||||
}
|
||||
$cond[] = $condprefix.implode($condsep, $parts).$condsuffix;
|
||||
}
|
||||
$condsql[] = implode(" ", $cond);
|
||||
}
|
||||
}
|
||||
if ($sep === null) $sep = "and";
|
||||
$count = count($condsql);
|
||||
if ($count > 1) {
|
||||
$sql[] = "(" . implode(" $sep ", $condsql) . ")";
|
||||
} elseif ($count == 1) {
|
||||
$sql[] = $condsql[0];
|
||||
}
|
||||
}
|
||||
|
||||
static function parse_set_values(?array $values, ?array &$sql, ?array &$bindings): void {
|
||||
if (!$values) return;
|
||||
$index = 0;
|
||||
$parts = [];
|
||||
foreach ($values as $key => $part) {
|
||||
if ($key === $index) {
|
||||
## séquentiel
|
||||
if (is_array($part)) {
|
||||
# paramètres récursifs
|
||||
self::parse_set_values($part, $parts, $bindings);
|
||||
} else {
|
||||
# paramètre litéral
|
||||
$parts[] = strval($part);
|
||||
static function with($sql, ?array $params=null): array {
|
||||
static::verifix($sql, $params);
|
||||
return [$sql, $params];
|
||||
}
|
||||
$index++;
|
||||
} else {
|
||||
## associatif
|
||||
# paramètre
|
||||
$param = $param0 = preg_replace('/^.+\./', "", $key);
|
||||
if ($bindings !== null && array_key_exists($param0, $bindings)) {
|
||||
$i = 2;
|
||||
while (array_key_exists("$param0$i", $bindings)) {
|
||||
$i++;
|
||||
}
|
||||
$param = "$param0$i";
|
||||
}
|
||||
# value
|
||||
$value = $part;
|
||||
$part = [$key, "="];
|
||||
if ($value === null) {
|
||||
$part[] = "null";
|
||||
} else {
|
||||
$part[] = ":$param";
|
||||
$bindings[$param] = $value;
|
||||
}
|
||||
$parts[] = implode(" ", $part);
|
||||
}
|
||||
}
|
||||
$sql = cl::merge($sql, $parts);
|
||||
}
|
||||
|
||||
protected static function check_eof(string $tmpsql, string $usersql): void {
|
||||
self::consume(';\s*', $tmpsql);
|
||||
if ($tmpsql) {
|
||||
throw new ValueException("unexpected value at end: $usersql");
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void;
|
||||
|
||||
function __construct($sql, ?array $bindings=null) {
|
||||
static::verifix($sql, $bindings, $meta);
|
||||
|
255
php/src/db/_private/_common.php
Normal file
255
php/src/db/_private/_common.php
Normal file
@ -0,0 +1,255 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
|
||||
class _common {
|
||||
protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
|
||||
if (!preg_match("/^$pattern/i", $string, $ms)) return false;
|
||||
$string = substr($string, strlen($ms[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** fusionner toutes les parties séquentielles d'une requête */
|
||||
protected static function merge_seq(array $query): string {
|
||||
$index = 0;
|
||||
$sql = "";
|
||||
foreach ($query as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
|
||||
$sql .= " ";
|
||||
}
|
||||
$sql .= $value;
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
protected static function is_sep(&$cond): bool {
|
||||
if (!is_string($cond)) return false;
|
||||
if (!preg_match('/^\s*(and|or|not)\s*$/i', $cond, $ms)) return false;
|
||||
$cond = $ms[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
static function parse_conds(?array $conds, ?array &$sql, ?array &$bindings): void {
|
||||
if (!$conds) return;
|
||||
$sep = null;
|
||||
$index = 0;
|
||||
$condsql = [];
|
||||
foreach ($conds as $key => $cond) {
|
||||
if ($key === $index) {
|
||||
## séquentiel
|
||||
if ($index === 0 && self::is_sep($cond)) {
|
||||
$sep = $cond;
|
||||
} elseif (is_bool($cond)) {
|
||||
# ignorer les valeurs true et false
|
||||
} elseif (is_array($cond)) {
|
||||
# condition récursive
|
||||
self::parse_conds($cond, $condsql, $bindings);
|
||||
} else {
|
||||
# condition litérale
|
||||
$condsql[] = strval($cond);
|
||||
}
|
||||
$index++;
|
||||
} elseif ($cond === false) {
|
||||
## associatif
|
||||
# condition litérale ignorée car condition false
|
||||
} elseif ($cond === true) {
|
||||
# condition litérale sélectionnée car condition true
|
||||
$condsql[] = strval($key);
|
||||
} else {
|
||||
## associatif
|
||||
# paramètre
|
||||
$param0 = preg_replace('/^.+\./', "", $key);
|
||||
$i = false;
|
||||
if ($bindings !== null && array_key_exists($param0, $bindings)) {
|
||||
$i = 2;
|
||||
while (array_key_exists("$param0$i", $bindings)) {
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
# value ou [operator, value]
|
||||
$condprefix = $condsep = $condsuffix = null;
|
||||
if (is_array($cond)) {
|
||||
$condkey = 0;
|
||||
$condkeys = array_keys($cond);
|
||||
$op = null;
|
||||
if (array_key_exists("op", $cond)) {
|
||||
$op = $cond["op"];
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$op = $cond[$condkeys[$condkey]];
|
||||
$condkey++;
|
||||
}
|
||||
$op = strtolower($op);
|
||||
$condvalues = null;
|
||||
switch ($op) {
|
||||
case "between":
|
||||
# ["between", $upper, $lower]
|
||||
$condsep = " and ";
|
||||
if (array_key_exists("lower", $cond)) {
|
||||
$condvalues[] = $cond["lower"];
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues[] = $cond[$condkeys[$condkey]];
|
||||
$condkey++;
|
||||
}
|
||||
if (array_key_exists("upper", $cond)) {
|
||||
$condvalues[] = $cond["upper"];
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues[] = $cond[$condkeys[$condkey]];
|
||||
$condkey++;
|
||||
}
|
||||
break;
|
||||
case "any":
|
||||
case "all":
|
||||
case "not any":
|
||||
case "not all":
|
||||
# ["list", $values]
|
||||
if ($op === "any" || $op === "all") {
|
||||
$condprefix = $op;
|
||||
$op = "=";
|
||||
} elseif ($op === "not any" || $op === "not all") {
|
||||
$condprefix = substr($op, strlen("not "));
|
||||
$op = "<>";
|
||||
}
|
||||
$condprefix .= "(array[";
|
||||
$condsep = ", ";
|
||||
$condsuffix = "])";
|
||||
$condvalues = null;
|
||||
if (array_key_exists("values", $cond)) {
|
||||
$condvalues = cl::with($cond["values"]);
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues = cl::with($cond[$condkeys[$condkey]]);
|
||||
$condkey++;
|
||||
}
|
||||
break;
|
||||
case "in":
|
||||
# ["in", $values]
|
||||
$condprefix = "(";
|
||||
$condsep = ", ";
|
||||
$condsuffix = ")";
|
||||
$condvalues = null;
|
||||
if (array_key_exists("values", $cond)) {
|
||||
$condvalues = cl::with($cond["values"]);
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues = cl::with($cond[$condkeys[$condkey]]);
|
||||
$condkey++;
|
||||
}
|
||||
break;
|
||||
case "null":
|
||||
case "is null":
|
||||
$op = "is null";
|
||||
break;
|
||||
case "not null":
|
||||
case "is not null":
|
||||
$op = "is not null";
|
||||
break;
|
||||
default:
|
||||
if (array_key_exists("value", $cond)) {
|
||||
$condvalues = [$cond["value"]];
|
||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||
$condvalues = [$cond[$condkeys[$condkey]]];
|
||||
$condkey++;
|
||||
}
|
||||
}
|
||||
} elseif ($cond !== null) {
|
||||
$op = "=";
|
||||
$condvalues = [$cond];
|
||||
} else {
|
||||
$op = "is null";
|
||||
$condvalues = null;
|
||||
}
|
||||
$cond = [$key, $op];
|
||||
if ($condvalues !== null) {
|
||||
$parts = [];
|
||||
foreach ($condvalues as $condvalue) {
|
||||
if (is_array($condvalue)) {
|
||||
$first = true;
|
||||
foreach ($condvalue as $value) {
|
||||
if ($first) {
|
||||
$first = false;
|
||||
} else {
|
||||
if ($sep === null) $sep = "and";
|
||||
$parts[] = " $sep ";
|
||||
$parts[] = $key;
|
||||
$parts[] = " $op ";
|
||||
}
|
||||
$param = "$param0$i";
|
||||
$parts[] = ":$param";
|
||||
$bindings[$param] = $value;
|
||||
if ($i === false) $i = 2;
|
||||
else $i++;
|
||||
}
|
||||
} else {
|
||||
$param = "$param0$i";
|
||||
$parts[] = ":$param";
|
||||
$bindings[$param] = $condvalue;
|
||||
if ($i === false) $i = 2;
|
||||
else $i++;
|
||||
}
|
||||
}
|
||||
$cond[] = $condprefix.implode($condsep, $parts).$condsuffix;
|
||||
}
|
||||
$condsql[] = implode(" ", $cond);
|
||||
}
|
||||
}
|
||||
if ($sep === null) $sep = "and";
|
||||
$count = count($condsql);
|
||||
if ($count > 1) {
|
||||
$sql[] = "(" . implode(" $sep ", $condsql) . ")";
|
||||
} elseif ($count == 1) {
|
||||
$sql[] = $condsql[0];
|
||||
}
|
||||
}
|
||||
|
||||
static function parse_set_values(?array $values, ?array &$sql, ?array &$bindings): void {
|
||||
if (!$values) return;
|
||||
$index = 0;
|
||||
$parts = [];
|
||||
foreach ($values as $key => $part) {
|
||||
if ($key === $index) {
|
||||
## séquentiel
|
||||
if (is_array($part)) {
|
||||
# paramètres récursifs
|
||||
self::parse_set_values($part, $parts, $bindings);
|
||||
} else {
|
||||
# paramètre litéral
|
||||
$parts[] = strval($part);
|
||||
}
|
||||
$index++;
|
||||
} else {
|
||||
## associatif
|
||||
# paramètre
|
||||
$param = $param0 = preg_replace('/^.+\./', "", $key);
|
||||
if ($bindings !== null && array_key_exists($param0, $bindings)) {
|
||||
$i = 2;
|
||||
while (array_key_exists("$param0$i", $bindings)) {
|
||||
$i++;
|
||||
}
|
||||
$param = "$param0$i";
|
||||
}
|
||||
# value
|
||||
$value = $part;
|
||||
$part = [$key, "="];
|
||||
if ($value === null) {
|
||||
$part[] = "null";
|
||||
} else {
|
||||
$part[] = ":$param";
|
||||
$bindings[$param] = $value;
|
||||
}
|
||||
$parts[] = implode(" ", $part);
|
||||
}
|
||||
}
|
||||
$sql = cl::merge($sql, $parts);
|
||||
}
|
||||
|
||||
protected static function check_eof(string $tmpsql, string $usersql): void {
|
||||
self::consume(';\s*', $tmpsql);
|
||||
if ($tmpsql) {
|
||||
throw new ValueException("unexpected value at end: $usersql");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
namespace nulib\db\_private;
|
||||
|
||||
use nulib\php\nur_func;
|
||||
use nulib\db\IDatabase;
|
||||
use nulib\php\func;
|
||||
|
||||
class _config {
|
||||
static function with($configs): self {
|
||||
@ -23,13 +24,12 @@ class _config {
|
||||
/** @var array */
|
||||
protected $configs;
|
||||
|
||||
function configure(Pdo $pdo): void {
|
||||
function configure(IDatabase $db): void {
|
||||
foreach ($this->configs as $key => $config) {
|
||||
if (is_string($config) && !nur_func::is_method($config)) {
|
||||
$pdo->exec($config);
|
||||
if (is_string($config) && !func::is_method($config)) {
|
||||
$db->exec($config);
|
||||
} else {
|
||||
nur_func::ensure_func($config, $this, $args);
|
||||
nur_func::call($config, $pdo, $key, ...$args);
|
||||
func::with($config)->bind($this, true)->invoke([$db, $key]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
class _create {
|
||||
class _create extends _common {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"table" => "string",
|
||||
@ -9,4 +9,46 @@ class _create {
|
||||
"cols" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
#XXX implémentation minimale
|
||||
return preg_match("/^create(?:\s+table)?\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
#XXX implémentation minimale
|
||||
$tmpsql = self::merge_seq($query);
|
||||
self::consume('create(?:\s+table)?\b', $tmpsql);
|
||||
$sql = ["create table"];
|
||||
if ($tmpsql) $sql[] = $tmpsql;
|
||||
|
||||
## préfixe
|
||||
$prefix = $query["prefix"] ?? null;
|
||||
if ($prefix !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$table = $query["table"] ?? null;
|
||||
if ($table !== null) $sql[] = $table;
|
||||
|
||||
## columns
|
||||
$cols = $query["cols"] ?? null;
|
||||
if ($cols !== null) {
|
||||
$index = 0;
|
||||
foreach ($cols as $col => &$definition) {
|
||||
if ($col === $index) {
|
||||
$index++;
|
||||
} else {
|
||||
$definition = "$col $definition";
|
||||
}
|
||||
}; unset($definition);
|
||||
$sql[] = "(\n ".implode("\n, ", $cols)."\n)";
|
||||
}
|
||||
|
||||
## suffixe
|
||||
$suffix = $query["suffix"] ?? null;
|
||||
if ($suffix !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,48 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
class _delete {
|
||||
class _delete extends _common {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"from" => "?string",
|
||||
"where" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^delete(?:\s+from)?\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
#XXX implémentation minimale
|
||||
$tmpsql = self::merge_seq($query);
|
||||
self::consume('delete(?:\s+from)?\b', $tmpsql);
|
||||
$sql = ["delete from"];
|
||||
if ($tmpsql) $sql[] = $tmpsql;
|
||||
|
||||
## préfixe
|
||||
$prefix = $query["prefix"] ?? null;
|
||||
if ($prefix !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$from = $query["from"] ?? null;
|
||||
if ($from !== null) $sql[] = $from;
|
||||
|
||||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
self::parse_conds($where, $wheresql, $bindings);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
}
|
||||
}
|
||||
|
||||
## suffixe
|
||||
$suffix = $query["suffix"] ?? null;
|
||||
if ($suffix !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,21 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
class _generic {
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
|
||||
class _generic extends _common {
|
||||
const SCHEMA = [
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match('/^drop\s+table\b/i', $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
if (!cl::is_list($query)) {
|
||||
throw new ValueException("Seuls les tableaux séquentiels sont supportés");
|
||||
}
|
||||
return self::merge_seq($query);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
class _insert {
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
|
||||
class _insert extends _common {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"into" => "?string",
|
||||
@ -10,4 +13,79 @@ class _insert {
|
||||
"values" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^insert\b/i", $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* parser une chaine de la forme
|
||||
* "insert [into] [TABLE] [(COLS)] [values (VALUES)]"
|
||||
*/
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
# fusionner d'abord toutes les parties séquentielles
|
||||
$usersql = $tmpsql = self::merge_seq($query);
|
||||
|
||||
### vérifier la présence des parties nécessaires
|
||||
$sql = [];
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## insert
|
||||
self::consume('(insert(?:\s+or\s+(?:ignore|replace))?)\s*', $tmpsql, $ms);
|
||||
$sql[] = $ms[1];
|
||||
|
||||
## into
|
||||
self::consume('into\s*', $tmpsql);
|
||||
$sql[] = "into";
|
||||
$into = $query["into"] ?? null;
|
||||
if (self::consume('([a-z_][a-z0-9_]*)\s*', $tmpsql, $ms)) {
|
||||
if ($into === null) $into = $ms[1];
|
||||
$sql[] = $into;
|
||||
} elseif ($into !== null) {
|
||||
$sql[] = $into;
|
||||
} else {
|
||||
throw new ValueException("expected table name: $usersql");
|
||||
}
|
||||
|
||||
## cols & values
|
||||
$usercols = [];
|
||||
$uservalues = [];
|
||||
if (self::consume('\(([^)]*)\)\s*', $tmpsql, $ms)) {
|
||||
$usercols = array_merge($usercols, preg_split("/\s*,\s*/", $ms[1]));
|
||||
}
|
||||
$cols = cl::withn($query["cols"] ?? null);
|
||||
$values = cl::withn($query["values"] ?? null);
|
||||
$schema = $query["schema"] ?? null;
|
||||
if ($cols === null) {
|
||||
if ($usercols) {
|
||||
$cols = $usercols;
|
||||
} elseif ($values) {
|
||||
$cols = array_keys($values);
|
||||
$usercols = array_merge($usercols, $cols);
|
||||
} elseif ($schema && is_array($schema)) {
|
||||
#XXX implémenter support AssocSchema
|
||||
$cols = array_keys($schema);
|
||||
$usercols = array_merge($usercols, $cols);
|
||||
}
|
||||
}
|
||||
if (self::consume('values\s+\(\s*(.*)\s*\)\s*', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $uservalues[] = $ms[1];
|
||||
}
|
||||
if ($cols !== null && !$uservalues) {
|
||||
if (!$usercols) $usercols = $cols;
|
||||
foreach ($cols as $col) {
|
||||
$uservalues[] = ":$col";
|
||||
$bindings[$col] = $values[$col] ?? null;
|
||||
}
|
||||
}
|
||||
$sql[] = "(" . implode(", ", $usercols) . ")";
|
||||
$sql[] = "values (" . implode(", ", $uservalues) . ")";
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
self::check_eof($tmpsql, $usersql);
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
||||
|
72
php/src/db/_private/_migration.php
Normal file
72
php/src/db/_private/_migration.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
use nulib\db\IDatabase;
|
||||
use nulib\php\func;
|
||||
|
||||
abstract class _migration {
|
||||
const MIGRATION = null;
|
||||
|
||||
function __construct($migrations, string $channel="", ?IDatabase $db=null) {
|
||||
$this->db = $db;
|
||||
$this->channel = $channel;
|
||||
if ($migrations === null) $migrations = static::MIGRATION;
|
||||
if ($migrations === null) $migrations = [];
|
||||
elseif (is_string($migrations)) $migrations = [$migrations];
|
||||
elseif (is_callable($migrations)) $migrations = [$migrations];
|
||||
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
|
||||
$this->migrations = $migrations;
|
||||
}
|
||||
|
||||
protected ?IDatabase $db;
|
||||
|
||||
protected string $channel;
|
||||
|
||||
const MIGRATION_TABLE = "_migration";
|
||||
const MIGRATION_COLS = [
|
||||
"channel" => "varchar not null",
|
||||
"name" => "varchar not null",
|
||||
"done" => "integer not null default 0",
|
||||
"primary key (channel, name)",
|
||||
];
|
||||
|
||||
protected function ensureTable(): void {
|
||||
$this->db->exec([
|
||||
"create table if not exists",
|
||||
"table" => static::MIGRATION_TABLE,
|
||||
"cols" => static::MIGRATION_COLS,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function isMigrated(string $name): bool {
|
||||
return boolval($this->db->get([
|
||||
"select 1",
|
||||
"from" => static::MIGRATION_TABLE,
|
||||
"where" => [
|
||||
"channel" => $this->channel,
|
||||
"name" => $name,
|
||||
"done" => 1,
|
||||
],
|
||||
]));
|
||||
}
|
||||
|
||||
abstract protected function setMigrated(string $name, bool $done): void;
|
||||
|
||||
/** @var callable[]|string[] */
|
||||
protected $migrations;
|
||||
|
||||
function migrate(?IDatabase $db=null): void {
|
||||
$db = ($this->db ??= $db);
|
||||
$this->ensureTable();
|
||||
foreach ($this->migrations as $name => $migration) {
|
||||
if ($this->isMigrated($name)) continue;
|
||||
$this->setMigrated($name, false);
|
||||
if (is_string($migration) || !func::is_callable($migration)) {
|
||||
$db->exec($migration);
|
||||
} else {
|
||||
func::with($migration)->bind($this, true)->invoke([$db, $name]);
|
||||
}
|
||||
$this->setMigrated($name, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
class _select {
|
||||
use nulib\cl;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
|
||||
class _select extends _common {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"schema" => "?array",
|
||||
@ -14,4 +18,164 @@ class _select {
|
||||
"having" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^select\b/i", $sql);
|
||||
}
|
||||
|
||||
private static function add_prefix(string $col, ?string $prefix): string {
|
||||
if ($prefix === null) return $col;
|
||||
if (strpos($col, ".") !== false) return $col;
|
||||
return "$prefix$col";
|
||||
}
|
||||
|
||||
/**
|
||||
* parser une chaine de la forme
|
||||
* "select [COLS] [from TABLE] [where CONDS] [order by ORDERS] [group by GROUPS] [having CONDS]"
|
||||
*/
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
# fusionner d'abord toutes les parties séquentielles
|
||||
$usersql = $tmpsql = self::merge_seq($query);
|
||||
|
||||
### vérifier la présence des parties nécessaires
|
||||
$sql = [];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## select
|
||||
self::consume('(select(?:\s*distinct)?)\s*', $tmpsql, $ms);
|
||||
$sql[] = $ms[1];
|
||||
|
||||
## cols
|
||||
$usercols = [];
|
||||
if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $usercols[] = $ms[1];
|
||||
}
|
||||
$colPrefix = $query["col_prefix"] ?? null;
|
||||
if ($colPrefix !== null) str::add_suffix($colPrefix, ".");
|
||||
$tmpcols = cl::withn($query["cols"] ?? null);
|
||||
$schema = $query["schema"] ?? null;
|
||||
if ($tmpcols !== null) {
|
||||
$cols = [];
|
||||
$index = 0;
|
||||
foreach ($tmpcols as $key => $col) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
$cols[] = $col;
|
||||
$usercols[] = self::add_prefix($col, $colPrefix);
|
||||
} else {
|
||||
$cols[] = $key;
|
||||
$usercols[] = self::add_prefix($col, $colPrefix)." as $key";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$cols = null;
|
||||
if ($schema && is_array($schema) && !in_array("*", $usercols)) {
|
||||
$cols = array_keys($schema);
|
||||
foreach ($cols as $col) {
|
||||
$usercols[] = self::add_prefix($col, $colPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$usercols && !$cols) $usercols = [self::add_prefix("*", $colPrefix)];
|
||||
$sql[] = implode(", ", $usercols);
|
||||
|
||||
## from
|
||||
$from = $query["from"] ?? null;
|
||||
if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?=;?\s*$|\bwhere\b)', $tmpsql, $ms)) {
|
||||
if ($from === null) $from = $ms[1];
|
||||
$sql[] = "from";
|
||||
$sql[] = $from;
|
||||
} elseif ($from !== null) {
|
||||
$sql[] = "from";
|
||||
$sql[] = $from;
|
||||
} else {
|
||||
throw new ValueException("expected table name: $usersql");
|
||||
}
|
||||
|
||||
## where
|
||||
$userwhere = [];
|
||||
if (self::consume('where\b\s*(.*?)(?=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $userwhere[] = $ms[1];
|
||||
}
|
||||
$where = cl::withn($query["where"] ?? null);
|
||||
if ($where !== null) self::parse_conds($where, $userwhere, $bindings);
|
||||
if ($userwhere) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $userwhere);
|
||||
}
|
||||
|
||||
## order by
|
||||
$userorderby = [];
|
||||
if (self::consume('order\s+by\b\s*(.*?)(?=;?\s*$|\bgroup\s+by\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $userorderby[] = $ms[1];
|
||||
}
|
||||
$orderby = cl::withn($query["order by"] ?? null);
|
||||
if ($orderby !== null) {
|
||||
$index = 0;
|
||||
foreach ($orderby as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$userorderby[] = $value;
|
||||
$index++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$userorderby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$userorderby[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($userorderby) {
|
||||
$sql[] = "order by";
|
||||
$sql[] = implode(", ", $userorderby);
|
||||
}
|
||||
## group by
|
||||
$usergroupby = [];
|
||||
if (self::consume('group\s+by\b\s*(.*?)(?=;?\s*$|\bhaving\b)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $usergroupby[] = $ms[1];
|
||||
}
|
||||
$groupby = cl::withn($query["group by"] ?? null);
|
||||
if ($groupby !== null) {
|
||||
$index = 0;
|
||||
foreach ($groupby as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$usergroupby[] = $value;
|
||||
$index++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$usergroupby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$usergroupby[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($usergroupby) {
|
||||
$sql[] = "group by";
|
||||
$sql[] = implode(", ", $usergroupby);
|
||||
}
|
||||
|
||||
## having
|
||||
$userhaving = [];
|
||||
if (self::consume('having\b\s*(.*?)(?=;?\s*$)', $tmpsql, $ms)) {
|
||||
if ($ms[1]) $userhaving[] = $ms[1];
|
||||
}
|
||||
$having = cl::withn($query["having"] ?? null);
|
||||
if ($having !== null) self::parse_conds($having, $userhaving, $bindings);
|
||||
if ($userhaving) {
|
||||
$sql[] = "having";
|
||||
$sql[] = implode(" and ", $userhaving);
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
self::check_eof($tmpsql, $usersql);
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace nulib\db\_private;
|
||||
|
||||
class _update {
|
||||
class _update extends _common {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"table" => "?string",
|
||||
@ -11,4 +11,43 @@ class _update {
|
||||
"where" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match("/^update\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
## préfixe
|
||||
$prefix = $query["prefix"] ?? null;
|
||||
if ($prefix !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$table = $query["table"] ?? null;
|
||||
if ($table !== null) $sql[] = $table;
|
||||
|
||||
## set
|
||||
self::parse_set_values($query["values"], $setsql, $bindings);
|
||||
$sql[] = "set";
|
||||
$sql[] = implode(", ", $setsql);
|
||||
|
||||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
self::parse_conds($where, $wheresql, $bindings);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
}
|
||||
}
|
||||
|
||||
## suffixe
|
||||
$suffix = $query["suffix"] ?? null;
|
||||
if ($suffix !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\CapacitorStorage;
|
||||
|
||||
@ -12,8 +13,7 @@ class MysqlStorage extends CapacitorStorage {
|
||||
$this->db = Mysql::with($mysql);
|
||||
}
|
||||
|
||||
/** @var Mysql */
|
||||
protected $db;
|
||||
protected Mysql $db;
|
||||
|
||||
function db(): Mysql {
|
||||
return $this->db;
|
||||
@ -23,17 +23,35 @@ class MysqlStorage extends CapacitorStorage {
|
||||
"id_" => "integer primary key auto_increment",
|
||||
];
|
||||
|
||||
function _getMigration(CapacitorChannel $channel): _mysqlMigration {
|
||||
return new _mysqlMigration(cl::merge([
|
||||
$this->_createSql($channel),
|
||||
], $channel->getMigration()), $channel->getName());
|
||||
}
|
||||
|
||||
function _getCreateSql(CapacitorChannel $channel): string {
|
||||
$query = new _query_base($this->_createSql($channel));
|
||||
$query = new _mysqlQuery($this->_createSql($channel));
|
||||
return self::format_sql($channel, $query->getSql());
|
||||
}
|
||||
|
||||
const CHANNELS_COLS = [
|
||||
"name" => "varchar(255) primary key",
|
||||
"table_name" => "varchar(64)",
|
||||
"class_name" => "varchar(255)",
|
||||
];
|
||||
|
||||
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||
return cl::merge(parent::_addToChannelsSql($channel), [
|
||||
"suffix" => "on duplicate key update name = name",
|
||||
]);
|
||||
}
|
||||
|
||||
function _exists(CapacitorChannel $channel): bool {
|
||||
$db = $this->db;
|
||||
$tableName = $db->get([
|
||||
$mysql = $this->db;
|
||||
$tableName = $mysql->get([
|
||||
"select table_name from information_schema.tables",
|
||||
"where" => [
|
||||
"table_schema" => $db->getDbname(),
|
||||
"table_schema" => $mysql->getDbname(),
|
||||
"table_name" => $channel->getTableName(),
|
||||
],
|
||||
]);
|
||||
|
31
php/src/db/mysql/_mysqlMigration.php
Normal file
31
php/src/db/mysql/_mysqlMigration.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\db\_private\_migration;
|
||||
|
||||
class _mysqlMigration extends _migration {
|
||||
static function with($migration): self {
|
||||
if ($migration instanceof self) return $migration;
|
||||
else return new static($migration);
|
||||
}
|
||||
|
||||
const MIGRATION_COLS = [
|
||||
"channel" => "varchar(64) not null",
|
||||
"name" => "varchar(64) not null",
|
||||
"done" => "integer not null default 0",
|
||||
"primary key (channel, name)",
|
||||
];
|
||||
|
||||
protected function setMigrated(string $name, bool $done): void {
|
||||
$this->db->exec([
|
||||
"insert",
|
||||
"into" => static::MIGRATION_TABLE,
|
||||
"values" => [
|
||||
"channel" => $this->channel,
|
||||
"name" => $name,
|
||||
"done" => $done? 1: 0,
|
||||
],
|
||||
"suffix" => "on duplicate key update done = :done",
|
||||
]);
|
||||
}
|
||||
}
|
8
php/src/db/mysql/_mysqlQuery.php
Normal file
8
php/src/db/mysql/_mysqlQuery.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\db\pdo\_pdoQuery;
|
||||
|
||||
class _mysqlQuery extends _pdoQuery {
|
||||
const DEBUG_QUERIES = false;
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\ValueException;
|
||||
|
||||
class _query_base extends \nulib\db\pdo\_query_base {
|
||||
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
|
||||
if (is_array($sql)) {
|
||||
$prefix = $sql[0] ?? null;
|
||||
if ($prefix === null) {
|
||||
throw new ValueException("requête invalide");
|
||||
} elseif (_query_create::isa($prefix)) {
|
||||
$sql = _query_create::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_query_select::isa($prefix)) {
|
||||
$sql = _query_select::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_query_insert::isa($prefix)) {
|
||||
$sql = _query_insert::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_query_update::isa($prefix)) {
|
||||
$sql = _query_update::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_query_delete::isa($prefix)) {
|
||||
$sql = _query_delete::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_query_generic::isa($prefix)) {
|
||||
$sql = _query_generic::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
throw ValueException::invalid_kind($sql, "query");
|
||||
}
|
||||
} else {
|
||||
if (!is_string($sql)) $sql = strval($sql);
|
||||
if (_query_create::isa($sql)) {
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_query_select::isa($sql)) {
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_query_insert::isa($sql)) {
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_query_update::isa($sql)) {
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_query_delete::isa($sql)) {
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_query_generic::isa($sql)) {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\db\_private\_create;
|
||||
use nulib\db\_private\Tcreate;
|
||||
|
||||
class _query_create extends _query_base {
|
||||
use Tcreate;
|
||||
const SCHEMA = _create::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\db\_private\_delete;
|
||||
use nulib\db\_private\Tdelete;
|
||||
|
||||
class _query_delete extends _query_base {
|
||||
use Tdelete;
|
||||
const SCHEMA = _delete::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\db\_private\_generic;
|
||||
use nulib\db\_private\Tgeneric;
|
||||
|
||||
class _query_generic extends _query_base {
|
||||
use Tgeneric;
|
||||
const SCHEMA = _generic::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\db\_private\_insert;
|
||||
use nulib\db\_private\Tinsert;
|
||||
|
||||
class _query_insert extends _query_base {
|
||||
use Tinsert;
|
||||
const SCHEMA = _insert::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\db\_private\_select;
|
||||
use nulib\db\_private\Tselect;
|
||||
|
||||
class _query_select extends _query_base {
|
||||
use Tselect;
|
||||
const SCHEMA = _select::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
use nulib\db\_private\_update;
|
||||
use nulib\db\_private\Tupdate;
|
||||
|
||||
class _query_update extends _query_base {
|
||||
use Tupdate;
|
||||
const SCHEMA = _update::SCHEMA;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\mysql;
|
||||
|
||||
/**
|
||||
* Class query: classe outil temporaire pour générer les requêtes
|
||||
*/
|
||||
class query extends _query_base {
|
||||
static function with($sql, ?array $params=null): array {
|
||||
self::verifix($sql, $params);
|
||||
return [$sql, $params];
|
||||
}
|
||||
}
|
@ -3,10 +3,11 @@ namespace nulib\db\pdo;
|
||||
|
||||
use Generator;
|
||||
use nulib\cl;
|
||||
use nulib\db\_private\_config;
|
||||
use nulib\db\_private\Tvalues;
|
||||
use nulib\db\IDatabase;
|
||||
use nulib\db\ITransactor;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
|
||||
class Pdo implements IDatabase {
|
||||
@ -21,7 +22,7 @@ class Pdo implements IDatabase {
|
||||
"dbconn" => $pdo->dbconn,
|
||||
"options" => $pdo->options,
|
||||
"config" => $pdo->config,
|
||||
"migrate" => $pdo->migration,
|
||||
"migration" => $pdo->migration,
|
||||
], $params));
|
||||
} else {
|
||||
return new static($pdo, $params);
|
||||
@ -49,7 +50,7 @@ class Pdo implements IDatabase {
|
||||
|
||||
protected const CONFIG = null;
|
||||
|
||||
protected const MIGRATE = null;
|
||||
protected const MIGRATION = null;
|
||||
|
||||
const dbconn_SCHEMA = [
|
||||
"name" => "string",
|
||||
@ -62,7 +63,7 @@ class Pdo implements IDatabase {
|
||||
"options" => ["?array|callable"],
|
||||
"replace_config" => ["?array|callable"],
|
||||
"config" => ["?array|callable"],
|
||||
"migrate" => ["?array|string|callable"],
|
||||
"migration" => ["?array|string|callable"],
|
||||
"auto_open" => ["bool", true],
|
||||
];
|
||||
|
||||
@ -93,7 +94,7 @@ class Pdo implements IDatabase {
|
||||
}
|
||||
$this->config = $config;
|
||||
# migrations
|
||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
||||
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||
#
|
||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||
@ -104,7 +105,7 @@ class Pdo implements IDatabase {
|
||||
protected ?array $dbconn;
|
||||
|
||||
/** @var array|callable */
|
||||
protected array $options;
|
||||
protected $options;
|
||||
|
||||
/** @var array|string|callable */
|
||||
protected $config;
|
||||
@ -119,8 +120,7 @@ class Pdo implements IDatabase {
|
||||
$dbconn = $this->dbconn;
|
||||
$options = $this->options;
|
||||
if (is_callable($options)) {
|
||||
nur_func::ensure_func($options, $this, $args);
|
||||
$options = nur_func::call($options, ...$args);
|
||||
$options = func::with($options)->bind($this, true)->invoke();
|
||||
}
|
||||
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
|
||||
_config::with($this->config)->configure($this);
|
||||
@ -143,21 +143,16 @@ class Pdo implements IDatabase {
|
||||
return $this->db()->exec($query);
|
||||
}
|
||||
|
||||
private static function is_insert(?string $sql): bool {
|
||||
if ($sql === null) return false;
|
||||
return preg_match('/^\s*insert\b/i', $sql);
|
||||
}
|
||||
|
||||
function exec($query, ?array $params=null) {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
$query = new _pdoQuery($query, $params);
|
||||
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||
if ($stmt->execute() === false) return false;
|
||||
if ($query->isInsert()) return $db->lastInsertId();
|
||||
else return $stmt->rowCount();
|
||||
} else {
|
||||
$rowCount = $db->exec($sql);
|
||||
if (self::is_insert($sql)) return $db->lastInsertId();
|
||||
if ($query->isInsert()) return $db->lastInsertId();
|
||||
else return $rowCount;
|
||||
}
|
||||
}
|
||||
@ -191,7 +186,7 @@ class Pdo implements IDatabase {
|
||||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
nur_func::call($func, $this);
|
||||
func::call($func, $this);
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
@ -222,11 +217,11 @@ class Pdo implements IDatabase {
|
||||
|
||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
$query = new _pdoQuery($query, $params);
|
||||
$stmt = null;
|
||||
try {
|
||||
/** @var \PDOStatement $stmt */
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||
if ($stmt->execute() === false) return null;
|
||||
} else {
|
||||
$stmt = $db->query($sql);
|
||||
@ -245,22 +240,18 @@ class Pdo implements IDatabase {
|
||||
return $this->get($query, $params, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
|
||||
* spécifiée(s)
|
||||
*/
|
||||
function all($query, ?array $params=null, $primaryKeys=null): Generator {
|
||||
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
$query = new _pdoQuery($query, $params);
|
||||
$stmt = null;
|
||||
try {
|
||||
/** @var \PDOStatement $stmt */
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||
if ($stmt->execute() === false) return;
|
||||
} else {
|
||||
$stmt = $db->query($sql);
|
||||
}
|
||||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
||||
$primaryKeys = cl::withn($primaryKeys);
|
||||
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
|
||||
$this->verifixRow($row);
|
||||
if ($primaryKeys !== null) {
|
||||
|
30
php/src/db/pdo/_pdoQuery.php
Normal file
30
php/src/db/pdo/_pdoQuery.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use nulib\db\_private\_base;
|
||||
use nulib\db\_private\Tbindings;
|
||||
use nulib\output\msg;
|
||||
|
||||
class _pdoQuery extends _base {
|
||||
use Tbindings;
|
||||
|
||||
const DEBUG_QUERIES = false;
|
||||
|
||||
function _use_stmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
|
||||
if (static::DEBUG_QUERIES) {#XXX
|
||||
msg::info($this->sql);
|
||||
//msg::info(var_export($this->bindings, true));
|
||||
}
|
||||
if ($this->bindings !== null) {
|
||||
$stmt = $db->prepare($this->sql);
|
||||
foreach ($this->bindings as $name => $value) {
|
||||
$this->verifixBindings($value);
|
||||
$stmt->bindValue($name, $value);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
$sql = $this->sql;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use nulib\db\_private\_base;
|
||||
use nulib\db\_private\Tbindings;
|
||||
use nulib\ValueException;
|
||||
|
||||
class _query_base extends _base {
|
||||
use Tbindings;
|
||||
|
||||
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
|
||||
if (is_array($sql)) {
|
||||
$prefix = $sql[0] ?? null;
|
||||
if ($prefix === null) {
|
||||
throw new ValueException("requête invalide");
|
||||
} elseif (_query_create::isa($prefix)) {
|
||||
$sql = _query_create::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_query_select::isa($prefix)) {
|
||||
$sql = _query_select::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_query_insert::isa($prefix)) {
|
||||
$sql = _query_insert::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_query_update::isa($prefix)) {
|
||||
$sql = _query_update::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_query_delete::isa($prefix)) {
|
||||
$sql = _query_delete::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_query_generic::isa($prefix)) {
|
||||
$sql = _query_generic::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
throw ValueException::invalid_kind($sql, "query");
|
||||
}
|
||||
} else {
|
||||
if (!is_string($sql)) $sql = strval($sql);
|
||||
if (_query_create::isa($sql)) {
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_query_select::isa($sql)) {
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_query_insert::isa($sql)) {
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_query_update::isa($sql)) {
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_query_delete::isa($sql)) {
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_query_generic::isa($sql)) {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const DEBUG_QUERIES = false;
|
||||
|
||||
function useStmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
|
||||
if (static::DEBUG_QUERIES) { #XXX
|
||||
error_log($this->sql);
|
||||
//error_log(var_export($this->bindings, true));
|
||||
}
|
||||
if ($this->bindings !== null) {
|
||||
$stmt = $db->prepare($this->sql);
|
||||
foreach ($this->bindings as $name => $value) {
|
||||
$this->verifixBindings($value);
|
||||
$stmt->bindValue($name, $value);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
$sql = $this->sql;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use nulib\db\_private\_create;
|
||||
use nulib\db\_private\Tcreate;
|
||||
|
||||
class _query_create extends _query_base {
|
||||
use Tcreate;
|
||||
const SCHEMA = _create::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use nulib\db\_private\_delete;
|
||||
use nulib\db\_private\Tdelete;
|
||||
|
||||
class _query_delete extends _query_base {
|
||||
use Tdelete;
|
||||
const SCHEMA = _delete::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use nulib\db\_private\_generic;
|
||||
use nulib\db\_private\Tgeneric;
|
||||
|
||||
class _query_generic extends _query_base {
|
||||
use Tgeneric;
|
||||
const SCHEMA = _generic::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use nulib\db\_private\_insert;
|
||||
use nulib\db\_private\Tinsert;
|
||||
|
||||
class _query_insert extends _query_base {
|
||||
use Tinsert;
|
||||
const SCHEMA = _insert::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use nulib\db\_private\_select;
|
||||
use nulib\db\_private\Tselect;
|
||||
|
||||
class _query_select extends _query_base {
|
||||
use Tselect;
|
||||
const SCHEMA = _select::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\pdo;
|
||||
|
||||
use nulib\db\_private\_update;
|
||||
use nulib\db\_private\Tupdate;
|
||||
|
||||
class _query_update extends _query_base {
|
||||
use Tupdate;
|
||||
const SCHEMA = _update::SCHEMA;
|
||||
}
|
294
php/src/db/pgsql/Pgsql.php
Normal file
294
php/src/db/pgsql/Pgsql.php
Normal file
@ -0,0 +1,294 @@
|
||||
<?php
|
||||
namespace nulib\db\pgsql;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\_private\_config;
|
||||
use nulib\db\_private\Tvalues;
|
||||
use nulib\db\IDatabase;
|
||||
use nulib\db\ITransactor;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
|
||||
class Pgsql implements IDatabase {
|
||||
use Tvalues;
|
||||
|
||||
static function with($pgsql, ?array $params=null): self {
|
||||
if ($pgsql instanceof static) {
|
||||
return $pgsql;
|
||||
} elseif ($pgsql instanceof self) {
|
||||
# recréer avec les mêmes paramètres
|
||||
return new static(null, cl::merge([
|
||||
"dbconn" => $pgsql->dbconn,
|
||||
"options" => $pgsql->options,
|
||||
"config" => $pgsql->config,
|
||||
"migration" => $pgsql->migration,
|
||||
], $params));
|
||||
} else {
|
||||
return new static($pgsql, $params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected const OPTIONS = [
|
||||
"persistent" => true,
|
||||
"force_new" => false,
|
||||
"serial_support" => true,
|
||||
];
|
||||
|
||||
const CONFIG = null;
|
||||
|
||||
const MIGRATION = null;
|
||||
|
||||
const params_SCHEMA = [
|
||||
"dbconn" => ["array"],
|
||||
"options" => ["?array|callable"],
|
||||
"replace_config" => ["?array|callable"],
|
||||
"config" => ["?array|callable"],
|
||||
"migration" => ["?array|string|callable"],
|
||||
"auto_open" => ["bool", true],
|
||||
];
|
||||
|
||||
const dbconn_SCHEMA = [
|
||||
"" => "?string",
|
||||
"host" => "string",
|
||||
"hostaddr" => "?string",
|
||||
"port" => "?int",
|
||||
"dbname" => "string",
|
||||
"user" => "string",
|
||||
"password" => "string",
|
||||
"connect_timeout" => "?int",
|
||||
"options" => "?string",
|
||||
"sslmode" => "?string",
|
||||
"service" => "?string",
|
||||
];
|
||||
|
||||
protected const dbconn_MAP = [
|
||||
"name" => "dbname",
|
||||
"pass" => "password",
|
||||
];
|
||||
|
||||
const options_SCHEMA = [
|
||||
"persistent" => ["bool", self::OPTIONS["persistent"]],
|
||||
"force_new" => ["bool", self::OPTIONS["force_new"]],
|
||||
];
|
||||
|
||||
function __construct($dbconn=null, ?array $params=null) {
|
||||
if ($dbconn !== null) {
|
||||
if (!is_array($dbconn)) {
|
||||
$dbconn = ["" => $dbconn];
|
||||
#XXX à terme, il faudra interroger config
|
||||
#$tmp = config::db($dbconn);
|
||||
#if ($tmp !== null) $dbconn = $tmp;
|
||||
#else $dbconn = ["" => $dbconn];
|
||||
}
|
||||
$params["dbconn"] = $dbconn;
|
||||
}
|
||||
# dbconn
|
||||
$this->dbconn = $params["dbconn"] ?? null;
|
||||
# options
|
||||
$this->options = $params["options"] ?? static::OPTIONS;
|
||||
# configuration
|
||||
$config = $params["replace_config"] ?? null;
|
||||
if ($config === null) {
|
||||
$config = $params["config"] ?? static::CONFIG;
|
||||
if (is_callable($config)) $config = [$config];
|
||||
}
|
||||
$this->config = $config;
|
||||
# migrations
|
||||
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||
#
|
||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||
$this->open();
|
||||
}
|
||||
}
|
||||
|
||||
protected ?array $dbconn;
|
||||
|
||||
/** @var array|callable|null */
|
||||
protected $options;
|
||||
|
||||
/** @var array|string|callable */
|
||||
protected $config;
|
||||
|
||||
/** @var array|string|callable */
|
||||
protected $migration;
|
||||
|
||||
/** @var resource */
|
||||
protected $db = null;
|
||||
|
||||
function open(): self {
|
||||
if ($this->db === null) {
|
||||
$dbconn = $this->dbconn;
|
||||
$connection_string = [$dbconn[""] ?? null];
|
||||
unset($dbconn[""]);
|
||||
foreach ($dbconn as $key => $value) {
|
||||
if ($value === null) continue;
|
||||
$value = strval($value);
|
||||
if ($value === "" || preg_match("/[ '\\\\]/", $value)) {
|
||||
$value = str_replace("\\", "\\\\", $value);
|
||||
$value = str_replace("'", "\\'", $value);
|
||||
$value = "'$value'";
|
||||
}
|
||||
$key = cl::get(self::dbconn_MAP, $key, $key);
|
||||
$connection_string[] = "$key=$value";
|
||||
}
|
||||
$connection_string = implode(" ", array_filter($connection_string));
|
||||
$options = $this->options;
|
||||
if (is_callable($options)) {
|
||||
$options = func::with($options)->bind($this, true)->invoke();
|
||||
}
|
||||
$forceNew = $options["force_new"] ?? false;
|
||||
$flags = $forceNew? PGSQL_CONNECT_FORCE_NEW: 0;
|
||||
|
||||
if ($options["persistent"] ?? true) $db = pg_pconnect($connection_string, $flags);
|
||||
else $db = pg_connect($connection_string, $flags);
|
||||
if ($db === false) throw new PgsqlException("unable to connect");
|
||||
$this->db = $db;
|
||||
|
||||
_config::with($this->config)->configure($this);
|
||||
//_migration::with($this->migration)->migrate($this);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function close(): self {
|
||||
if ($this->db !== null) {
|
||||
pg_close($this->db);
|
||||
$this->db = null;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function db() {
|
||||
$this->open();
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
function _exec(string $query): bool {
|
||||
$result = pg_query($this->db(), $query);
|
||||
if ($result === false) return false;
|
||||
pg_free_result($result);
|
||||
return true;
|
||||
}
|
||||
|
||||
function getLastSerial() {
|
||||
$db = $this->db();
|
||||
$result = @pg_query($db, "select lastval()");
|
||||
if ($result === false) return false;
|
||||
$lastSerial = pg_fetch_row($result)[0];
|
||||
pg_free_result($result);
|
||||
return $lastSerial;
|
||||
}
|
||||
|
||||
function exec($query, ?array $params=null) {
|
||||
$db = $this->db();
|
||||
$query = new _pgsqlQuery($query, $params);
|
||||
$result = $query->_exec($db);
|
||||
$serialSupport = $this->options["serial_support"] ?? true;
|
||||
if ($serialSupport && $query->isInsert()) return $this->getLastSerial();
|
||||
$affected_rows = pg_affected_rows($result);
|
||||
pg_free_result($result);
|
||||
return $affected_rows;
|
||||
}
|
||||
|
||||
/** @var ITransactor[] */
|
||||
protected ?array $transactors = null;
|
||||
|
||||
function willUpdate(...$transactors): self {
|
||||
foreach ($transactors as $transactor) {
|
||||
if ($transactor instanceof ITransactor) {
|
||||
$this->transactors[] = $transactor;
|
||||
$transactor->willUpdate();
|
||||
} else {
|
||||
throw ValueException::invalid_type($transactor, ITransactor::class);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function inTransaction(?bool &$inerror=null): bool {
|
||||
$status = pg_transaction_status($this->db());
|
||||
if ($status === PGSQL_TRANSACTION_ACTIVE || $status === PGSQL_TRANSACTION_INTRANS) {
|
||||
$inerror = false;
|
||||
return true;
|
||||
} elseif ($status === PGSQL_TRANSACTION_INERROR) {
|
||||
$inerror = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function beginTransaction(?callable $func=null, bool $commit=true): void {
|
||||
$this->_exec("begin");
|
||||
if ($this->transactors !== null) {
|
||||
foreach ($this->transactors as $transactor) {
|
||||
$transactor->beginTransaction();
|
||||
}
|
||||
}
|
||||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
func::call($func, $this);
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
}
|
||||
} finally {
|
||||
if ($commit && !$commited) $this->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
$this->_exec("commit");
|
||||
if ($this->transactors !== null) {
|
||||
foreach ($this->transactors as $transactor) {
|
||||
$transactor->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rollback(): void {
|
||||
$this->_exec("rollback");
|
||||
if ($this->transactors !== null) {
|
||||
foreach ($this->transactors as $transactor) {
|
||||
$transactor->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||
$db = $this->db();
|
||||
$query = new _pgsqlQuery($query, $params);
|
||||
$result = $query->_exec($db);
|
||||
$row = pg_fetch_assoc($result);
|
||||
pg_free_result($result);
|
||||
if ($row === false) return null;
|
||||
$this->verifixRow($row);
|
||||
if ($entireRow) return $row;
|
||||
else return cl::first($row);
|
||||
}
|
||||
|
||||
function one($query, ?array $params=null): ?array {
|
||||
return $this->get($query, $params, true);
|
||||
}
|
||||
|
||||
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||
$db = $this->db();
|
||||
$query = new _pgsqlQuery($query, $params);
|
||||
$result = $query->_exec($db);
|
||||
$primaryKeys = cl::withn($primaryKeys);
|
||||
while (($row = pg_fetch_assoc($result)) !== false) {
|
||||
$this->verifixRow($row);
|
||||
if ($primaryKeys !== null) {
|
||||
$key = implode("-", cl::select($row, $primaryKeys));
|
||||
yield $key => $row;
|
||||
} else {
|
||||
yield $row;
|
||||
}
|
||||
}
|
||||
pg_free_result($result);
|
||||
}
|
||||
}
|
16
php/src/db/pgsql/PgsqlException.php
Normal file
16
php/src/db/pgsql/PgsqlException.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace nulib\db\pgsql;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
use SQLite3;
|
||||
|
||||
class PgsqlException extends RuntimeException {
|
||||
static final function last_error($db): self {
|
||||
return new static(pg_last_error($db));
|
||||
}
|
||||
|
||||
static final function wrap(Exception $e): self {
|
||||
return new static($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
64
php/src/db/pgsql/PgsqlStorage.php
Normal file
64
php/src/db/pgsql/PgsqlStorage.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace nulib\db\pgsql;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\CapacitorStorage;
|
||||
|
||||
class PgsqlStorage extends CapacitorStorage {
|
||||
const SERDATA_DEFINITION = "text";
|
||||
const SERSUM_DEFINITION = "varchar(40)";
|
||||
const SERTS_DEFINITION = "timestamp";
|
||||
|
||||
function __construct($pgsql) {
|
||||
$this->db = Pgsql::with($pgsql);
|
||||
}
|
||||
|
||||
protected Pgsql $db;
|
||||
|
||||
function db(): Pgsql {
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
const PRIMARY_KEY_DEFINITION = [
|
||||
"id_" => "serial primary key",
|
||||
];
|
||||
|
||||
function _getMigration(CapacitorChannel $channel): _pgsqlMigration {
|
||||
return new _pgsqlMigration(cl::merge([
|
||||
$this->_createSql($channel),
|
||||
], $channel->getMigration()), $channel->getName());
|
||||
}
|
||||
|
||||
function _getCreateSql(CapacitorChannel $channel): string {
|
||||
$query = new _pgsqlQuery($this->_createSql($channel));
|
||||
return self::format_sql($channel, $query->getSql());
|
||||
}
|
||||
|
||||
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||
return cl::merge(parent::_addToChannelsSql($channel), [
|
||||
"suffix" => "on conflict (name) do nothing",
|
||||
]);
|
||||
}
|
||||
|
||||
function _exists(CapacitorChannel $channel): bool {
|
||||
$tableName = $channel->getTableName();
|
||||
if (($index = strpos($tableName, ".")) !== false) {
|
||||
$schemaName = substr($tableName, 0, $index);
|
||||
$tableName = substr($tableName, $index + 1);
|
||||
} else {
|
||||
$schemaName = "public";
|
||||
}
|
||||
return null !== $this->db->get([
|
||||
"select tablename from pg_tables",
|
||||
"where" => [
|
||||
"schemaname" => $schemaName,
|
||||
"tablename" => $tableName,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
$this->db->close();
|
||||
}
|
||||
}
|
24
php/src/db/pgsql/_pgsqlMigration.php
Normal file
24
php/src/db/pgsql/_pgsqlMigration.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace nulib\db\pgsql;
|
||||
|
||||
use nulib\db\_private\_migration;
|
||||
|
||||
class _pgsqlMigration extends _migration {
|
||||
static function with($migration): self {
|
||||
if ($migration instanceof self) return $migration;
|
||||
else return new static($migration);
|
||||
}
|
||||
|
||||
protected function setMigrated(string $name, bool $done): void {
|
||||
$this->db->exec([
|
||||
"insert",
|
||||
"into" => static::MIGRATION_TABLE,
|
||||
"values" => [
|
||||
"channel" => $this->channel,
|
||||
"name" => $name,
|
||||
"done" => $done? 1: 0,
|
||||
],
|
||||
"suffix" => "on conflict (channel, name) do update set done = :done",
|
||||
]);
|
||||
}
|
||||
}
|
44
php/src/db/pgsql/_pgsqlQuery.php
Normal file
44
php/src/db/pgsql/_pgsqlQuery.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace nulib\db\pgsql;
|
||||
|
||||
use nulib\cv;
|
||||
use nulib\db\_private\_base;
|
||||
use nulib\db\_private\Tbindings;
|
||||
use nulib\output\msg;
|
||||
|
||||
class _pgsqlQuery extends _base {
|
||||
use Tbindings;
|
||||
|
||||
const DEBUG_QUERIES = false;
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
function _exec($db) {
|
||||
$sql = $this->sql;
|
||||
$bindings = $this->bindings;
|
||||
if (static::DEBUG_QUERIES) {#XXX
|
||||
msg::info($sql);
|
||||
//msg::info(var_export($bindings, true));
|
||||
}
|
||||
if ($bindings !== null) {
|
||||
# trier d'abord les champ par ordre de longueur, pour éviter les overlaps
|
||||
$names = array_keys($bindings);
|
||||
usort($names, function ($a, $b) {
|
||||
return -cv::compare(strlen(strval($a)), strlen(strval($b)));
|
||||
});
|
||||
$bparams = [];
|
||||
$number = 1;
|
||||
foreach ($names as $name) {
|
||||
$sql = str_replace(":$name", "\$$number", $sql);
|
||||
$bparams[] = $bindings[$name];
|
||||
$number++;
|
||||
}
|
||||
$result = pg_query_params($db, $sql, $bparams);
|
||||
} else {
|
||||
$result = pg_query($db, $sql);
|
||||
}
|
||||
if ($result === false) throw PgsqlException::last_error($db);
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -3,10 +3,11 @@ namespace nulib\db\sqlite;
|
||||
|
||||
use Generator;
|
||||
use nulib\cl;
|
||||
use nulib\db\_private\_config;
|
||||
use nulib\db\_private\Tvalues;
|
||||
use nulib\db\IDatabase;
|
||||
use nulib\db\ITransactor;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
use SQLite3;
|
||||
use SQLite3Result;
|
||||
@ -29,7 +30,7 @@ class Sqlite implements IDatabase {
|
||||
"encryption_key" => $sqlite->encryptionKey,
|
||||
"allow_wal" => $sqlite->allowWal,
|
||||
"config" => $sqlite->config,
|
||||
"migrate" => $sqlite->migration,
|
||||
"migration" => $sqlite->migration,
|
||||
], $params));
|
||||
} elseif (is_array($sqlite)) {
|
||||
return new static(null, cl::merge($sqlite, $params));
|
||||
@ -71,7 +72,7 @@ class Sqlite implements IDatabase {
|
||||
|
||||
const CONFIG = null;
|
||||
|
||||
const MIGRATE = null;
|
||||
const MIGRATION = null;
|
||||
|
||||
const params_SCHEMA = [
|
||||
"file" => ["string", ""],
|
||||
@ -80,7 +81,7 @@ class Sqlite implements IDatabase {
|
||||
"allow_wal" => ["?bool"],
|
||||
"replace_config" => ["?array|callable"],
|
||||
"config" => ["?array|callable"],
|
||||
"migrate" => ["?array|string|callable"],
|
||||
"migration" => ["?array|string|callable"],
|
||||
"auto_open" => ["bool", true],
|
||||
];
|
||||
|
||||
@ -108,7 +109,7 @@ class Sqlite implements IDatabase {
|
||||
}
|
||||
$this->config = $config;
|
||||
# migrations
|
||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
||||
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||
#
|
||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||
$this->inTransaction = false;
|
||||
@ -149,7 +150,7 @@ class Sqlite implements IDatabase {
|
||||
if ($this->db === null) {
|
||||
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
||||
_config::with($this->config)->configure($this);
|
||||
_migration::with($this->migration)->migrate($this);
|
||||
_sqliteMigration::with($this->migration)->migrate($this);
|
||||
$this->inTransaction = false;
|
||||
}
|
||||
return $this;
|
||||
@ -180,15 +181,10 @@ class Sqlite implements IDatabase {
|
||||
return $this->db()->exec($query);
|
||||
}
|
||||
|
||||
private static function is_insert(?string $sql): bool {
|
||||
if ($sql === null) return false;
|
||||
return preg_match('/^\s*insert\b/i', $sql);
|
||||
}
|
||||
|
||||
function exec($query, ?array $params=null) {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
$query = new _sqliteQuery($query, $params);
|
||||
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||
try {
|
||||
$result = $stmt->execute();
|
||||
if ($result === false) return false;
|
||||
@ -201,7 +197,7 @@ class Sqlite implements IDatabase {
|
||||
} else {
|
||||
$result = $db->exec($sql);
|
||||
if ($result === false) return false;
|
||||
if (self::is_insert($sql)) return $db->lastInsertRowID();
|
||||
if ($query->isInsert()) return $db->lastInsertRowID();
|
||||
else return $db->changes();
|
||||
}
|
||||
}
|
||||
@ -237,7 +233,7 @@ class Sqlite implements IDatabase {
|
||||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
nur_func::call($func, $this);
|
||||
func::call($func, $this);
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
@ -274,8 +270,8 @@ class Sqlite implements IDatabase {
|
||||
|
||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
$query = new _sqliteQuery($query, $params);
|
||||
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||
try {
|
||||
$result = $this->checkResult($stmt->execute());
|
||||
try {
|
||||
@ -300,7 +296,7 @@ class Sqlite implements IDatabase {
|
||||
}
|
||||
|
||||
protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null, $primaryKeys=null): Generator {
|
||||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
||||
$primaryKeys = cl::withn($primaryKeys);
|
||||
try {
|
||||
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
||||
$this->verifixRow($row);
|
||||
@ -317,14 +313,10 @@ class Sqlite implements IDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
|
||||
* spécifiée(s)
|
||||
*/
|
||||
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
$query = new _sqliteQuery($query, $params);
|
||||
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||
$result = $this->checkResult($stmt->execute());
|
||||
return $this->_fetchResult($result, $stmt, $primaryKeys);
|
||||
} else {
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\CapacitorStorage;
|
||||
|
||||
@ -12,8 +13,7 @@ class SqliteStorage extends CapacitorStorage {
|
||||
$this->db = Sqlite::with($sqlite);
|
||||
}
|
||||
|
||||
/** @var Sqlite */
|
||||
protected $db;
|
||||
protected Sqlite $db;
|
||||
|
||||
function db(): Sqlite {
|
||||
return $this->db;
|
||||
@ -23,8 +23,14 @@ class SqliteStorage extends CapacitorStorage {
|
||||
"id_" => "integer primary key autoincrement",
|
||||
];
|
||||
|
||||
function _getMigration(CapacitorChannel $channel): _sqliteMigration {
|
||||
return new _sqliteMigration(cl::merge([
|
||||
$this->_createSql($channel),
|
||||
], $channel->getMigration()), $channel->getName());
|
||||
}
|
||||
|
||||
function _getCreateSql(CapacitorChannel $channel): string {
|
||||
$query = new _query_base($this->_createSql($channel));
|
||||
$query = new _sqliteQuery($this->_createSql($channel));
|
||||
return self::format_sql($channel, $query->getSql());
|
||||
}
|
||||
|
||||
@ -39,54 +45,33 @@ class SqliteStorage extends CapacitorStorage {
|
||||
}
|
||||
|
||||
function channelExists(string $name): bool {
|
||||
$name = $this->db->get([
|
||||
"select name from _channels",
|
||||
return null !== $this->db->get([
|
||||
"select name",
|
||||
"from" => static::CHANNELS_TABLE,
|
||||
"where" => ["name" => $name],
|
||||
]);
|
||||
return $name !== null;
|
||||
}
|
||||
|
||||
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||
$sql = parent::_addToChannelsSql($channel);
|
||||
$sql[0] = "insert or ignore";
|
||||
return $sql;
|
||||
}
|
||||
|
||||
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||
$db = $this->db;
|
||||
if (!$this->tableExists("_channels")) {
|
||||
if (!$this->tableExists(static::CHANNELS_TABLE)) {
|
||||
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
|
||||
# verrou en écriture
|
||||
$db->exec([
|
||||
"create table if not exists",
|
||||
"table" => "_channels",
|
||||
"cols" => [
|
||||
"name" => "varchar primary key",
|
||||
"table_name" => "varchar",
|
||||
"class" => "varchar",
|
||||
],
|
||||
]);
|
||||
$db->exec($this->_createChannelsSql());
|
||||
}
|
||||
if (!$this->channelExists($channel->getName())) {
|
||||
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
||||
# verrou en écriture
|
||||
$db->exec([
|
||||
"insert",
|
||||
"into" => "_channels",
|
||||
"values" => [
|
||||
"name" => $channel->getName(),
|
||||
"table_name" => $channel->getTableName(),
|
||||
"class" => get_class($channel),
|
||||
],
|
||||
"suffix" => "on conflict do nothing",
|
||||
]);
|
||||
$db->exec($this->_addToChannelsSql($channel));
|
||||
}
|
||||
}
|
||||
|
||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
||||
$this->db->exec([
|
||||
"delete",
|
||||
"from" => "_channels",
|
||||
"where" => [
|
||||
"name" => $channel->getName(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
function _exists(CapacitorChannel $channel): bool {
|
||||
return $this->tableExists($channel->getTableName());
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\php\nur_func;
|
||||
|
||||
class _config {
|
||||
static function with($configs): self {
|
||||
if ($configs instanceof static) return $configs;
|
||||
return new static($configs);
|
||||
}
|
||||
|
||||
const CONFIG = null;
|
||||
|
||||
function __construct($configs) {
|
||||
if ($configs === null) $configs = static::CONFIG;
|
||||
if ($configs === null) $configs = [];
|
||||
elseif (is_string($configs)) $configs = [$configs];
|
||||
elseif (is_callable($configs)) $configs = [$configs];
|
||||
elseif (!is_array($configs)) $configs = [strval($configs)];
|
||||
$this->configs = $configs;
|
||||
}
|
||||
|
||||
/** @var array */
|
||||
protected $configs;
|
||||
|
||||
function configure(Sqlite $sqlite): void {
|
||||
foreach ($this->configs as $key => $config) {
|
||||
if (is_string($config) && !nur_func::is_method($config)) {
|
||||
$sqlite->exec($config);
|
||||
} else {
|
||||
nur_func::ensure_func($config, $this, $args);
|
||||
nur_func::call($config, $sqlite, $key, ...$args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\php\nur_func;
|
||||
|
||||
class _migration {
|
||||
static function with($migrations): self {
|
||||
if ($migrations instanceof static) {
|
||||
return $migrations;
|
||||
} elseif ($migrations instanceof self) {
|
||||
return new static($migrations->migrations);
|
||||
} else {
|
||||
return new static($migrations);
|
||||
}
|
||||
}
|
||||
|
||||
const MIGRATE = null;
|
||||
|
||||
function __construct($migrations) {
|
||||
if ($migrations === null) $migrations = static::MIGRATE;
|
||||
if ($migrations === null) $migrations = [];
|
||||
elseif (is_string($migrations)) $migrations = [$migrations];
|
||||
elseif (is_callable($migrations)) $migrations = [$migrations];
|
||||
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
|
||||
$this->migrations = $migrations;
|
||||
}
|
||||
|
||||
/** @var callable[]|string[] */
|
||||
protected $migrations;
|
||||
|
||||
function migrate(Sqlite $sqlite): void {
|
||||
$sqlite->exec("create table if not exists _migration(key varchar primary key, value varchar not null, done integer default 0)");
|
||||
foreach ($this->migrations as $key => $migration) {
|
||||
$exists = $sqlite->get("select 1 from _migration where key = :key and done = 1", [
|
||||
"key" => $key,
|
||||
]);
|
||||
if (!$exists) {
|
||||
$sqlite->exec("insert or replace into _migration(key, value, done) values(:key, :value, :done)", [
|
||||
"key" => $key,
|
||||
"value" => $migration,
|
||||
"done" => 0,
|
||||
]);
|
||||
if (is_string($migration) && !nur_func::is_method($migration)) {
|
||||
$sqlite->exec($migration);
|
||||
} else {
|
||||
nur_func::ensure_func($migration, $this, $args);
|
||||
nur_func::call($migration, $sqlite, $key, ...$args);
|
||||
}
|
||||
$sqlite->exec("update _migration set done = 1 where key = :key", [
|
||||
"key" => $key,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_base;
|
||||
use nulib\db\_private\Tbindings;
|
||||
use nulib\output\msg;
|
||||
use nulib\ValueException;
|
||||
use SQLite3;
|
||||
use SQLite3Stmt;
|
||||
|
||||
class _query_base extends _base {
|
||||
use Tbindings;
|
||||
|
||||
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
|
||||
if (is_array($sql)) {
|
||||
$prefix = $sql[0] ?? null;
|
||||
if ($prefix === null) {
|
||||
throw new ValueException("requête invalide");
|
||||
} elseif (_query_create::isa($prefix)) {
|
||||
$sql = _query_create::parse($sql, $bindinds);
|
||||
} elseif (_query_select::isa($prefix)) {
|
||||
$sql = _query_select::parse($sql, $bindinds);
|
||||
} elseif (_query_insert::isa($prefix)) {
|
||||
$sql = _query_insert::parse($sql, $bindinds);
|
||||
} elseif (_query_update::isa($prefix)) {
|
||||
$sql = _query_update::parse($sql, $bindinds);
|
||||
} elseif (_query_delete::isa($prefix)) {
|
||||
$sql = _query_delete::parse($sql, $bindinds);
|
||||
} elseif (_query_generic::isa($prefix)) {
|
||||
$sql = _query_generic::parse($sql, $bindinds);
|
||||
} else {
|
||||
throw SqliteException::wrap(ValueException::invalid_kind($sql, "query"));
|
||||
}
|
||||
} elseif (!is_string($sql)) {
|
||||
$sql = strval($sql);
|
||||
}
|
||||
}
|
||||
|
||||
const DEBUG_QUERIES = false;
|
||||
|
||||
function useStmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
|
||||
if (static::DEBUG_QUERIES) msg::info($this->sql); #XXX
|
||||
if ($this->bindings !== null) {
|
||||
/** @var SQLite3Stmt $stmt */
|
||||
$stmt = SqliteException::check($db, $db->prepare($this->sql));
|
||||
$close = true;
|
||||
try {
|
||||
foreach ($this->bindings as $param => $value) {
|
||||
$this->verifixBindings($value);
|
||||
SqliteException::check($db, $stmt->bindValue($param, $value));
|
||||
}
|
||||
$close = false;
|
||||
return true;
|
||||
} finally {
|
||||
if ($close) $stmt->close();
|
||||
}
|
||||
} else {
|
||||
$sql = $this->sql;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_create;
|
||||
use nulib\db\_private\Tcreate;
|
||||
|
||||
class _query_create extends _query_base {
|
||||
use Tcreate;
|
||||
const SCHEMA = _create::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_delete;
|
||||
use nulib\db\_private\Tdelete;
|
||||
|
||||
class _query_delete extends _query_base {
|
||||
use Tdelete;
|
||||
const SCHEMA = _delete::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_generic;
|
||||
use nulib\db\_private\Tgeneric;
|
||||
|
||||
class _query_generic extends _query_base {
|
||||
use Tgeneric;
|
||||
const SCHEMA = _generic::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_insert;
|
||||
use nulib\db\_private\Tinsert;
|
||||
|
||||
class _query_insert extends _query_base {
|
||||
use Tinsert;
|
||||
const SCHEMA = _insert::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_select;
|
||||
use nulib\db\_private\Tselect;
|
||||
|
||||
class _query_select extends _query_base {
|
||||
use Tselect;
|
||||
const SCHEMA = _select::SCHEMA;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_update;
|
||||
use nulib\db\_private\Tupdate;
|
||||
|
||||
class _query_update extends _query_base {
|
||||
use Tupdate;
|
||||
const SCHEMA = _update::SCHEMA;
|
||||
}
|
23
php/src/db/sqlite/_sqliteMigration.php
Normal file
23
php/src/db/sqlite/_sqliteMigration.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_migration;
|
||||
|
||||
class _sqliteMigration extends _migration {
|
||||
static function with($migration): self {
|
||||
if ($migration instanceof self) return $migration;
|
||||
else return new static($migration);
|
||||
}
|
||||
|
||||
protected function setMigrated(string $name, bool $done): void {
|
||||
$this->db->exec([
|
||||
"insert or replace",
|
||||
"into" => static::MIGRATION_TABLE,
|
||||
"values" => [
|
||||
"channel" => $this->channel,
|
||||
"name" => $name,
|
||||
"done" => $done? 1: 0,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
46
php/src/db/sqlite/_sqliteQuery.php
Normal file
46
php/src/db/sqlite/_sqliteQuery.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\db\_private\_base;
|
||||
use nulib\db\_private\_create;
|
||||
use nulib\db\_private\_delete;
|
||||
use nulib\db\_private\_generic;
|
||||
use nulib\db\_private\_insert;
|
||||
use nulib\db\_private\_select;
|
||||
use nulib\db\_private\_update;
|
||||
use nulib\db\_private\Tbindings;
|
||||
use nulib\output\msg;
|
||||
use nulib\ValueException;
|
||||
use SQLite3;
|
||||
use SQLite3Stmt;
|
||||
|
||||
class _sqliteQuery extends _base {
|
||||
use Tbindings;
|
||||
|
||||
const DEBUG_QUERIES = false;
|
||||
|
||||
function _use_stmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
|
||||
if (static::DEBUG_QUERIES) {#XXX
|
||||
msg::info($this->sql);
|
||||
//msg::info(var_export($this->bindings, true));
|
||||
}
|
||||
if ($this->bindings !== null) {
|
||||
/** @var SQLite3Stmt $stmt */
|
||||
$stmt = SqliteException::check($db, $db->prepare($this->sql));
|
||||
$close = true;
|
||||
try {
|
||||
foreach ($this->bindings as $param => $value) {
|
||||
$this->verifixBindings($value);
|
||||
SqliteException::check($db, $stmt->bindValue($param, $value));
|
||||
}
|
||||
$close = false;
|
||||
return true;
|
||||
} finally {
|
||||
if ($close) $stmt->close();
|
||||
}
|
||||
} else {
|
||||
$sql = $this->sql;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
namespace nulib\ext;
|
||||
|
||||
use Exception;
|
||||
use nulib\ext\json\JsonException;
|
||||
use nulib\file;
|
||||
use nulib\os\IOException;
|
||||
|
||||
|
@ -5,7 +5,7 @@ use DateTimeInterface;
|
||||
use nulib\cl;
|
||||
use nulib\file\TempStream;
|
||||
use nulib\os\path;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\func;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\web\http;
|
||||
|
||||
@ -35,13 +35,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
||||
$this->rows = $rows;
|
||||
$this->index = 0;
|
||||
$cookFunc = $params["cook_func"] ?? null;
|
||||
$cookCtx = $cookArgs = null;
|
||||
if ($cookFunc !== null) {
|
||||
nur_func::ensure_func($cookFunc, $this, $cookArgs);
|
||||
$cookCtx = nur_func::_prepare($cookFunc);
|
||||
}
|
||||
$this->cookCtx = $cookCtx;
|
||||
$this->cookArgs = $cookArgs;
|
||||
if ($cookFunc !== null) $cookFunc = func::with($cookFunc)->bind($this, true);
|
||||
$this->cookFunc = $cookFunc;
|
||||
$this->output = $params["output"] ?? static::OUTPUT;
|
||||
$maxMemory = $params["max_memory"] ?? null;
|
||||
$throwOnError = $params["throw_on_error"] ?? null;
|
||||
@ -60,9 +55,7 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
||||
|
||||
protected ?string $output;
|
||||
|
||||
protected ?array $cookCtx;
|
||||
|
||||
protected ?array $cookArgs;
|
||||
protected ?func $cookFunc;
|
||||
|
||||
protected function ensureHeaders(?array $row=null): void {
|
||||
if ($this->headers !== null || !$this->useHeaders) return;
|
||||
@ -87,9 +80,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
||||
}
|
||||
|
||||
protected function cookRow(?array $row): ?array {
|
||||
if ($this->cookCtx !== null) {
|
||||
$args = cl::merge([$row], $this->cookArgs);
|
||||
$row = nur_func::_call($this->cookCtx, $args);
|
||||
if ($this->cookFunc !== null) {
|
||||
$row = $this->cookFunc->prependArgs([$row])->invoke();
|
||||
}
|
||||
if ($row !== null) {
|
||||
foreach ($row as &$col) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\output\std\ProxyMessenger;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\func;
|
||||
|
||||
/**
|
||||
* Class msg: inscrire un message dans les logs ET l'afficher à l'utilisateur
|
||||
@ -39,30 +39,21 @@ class msg extends _messenger {
|
||||
if ($log !== null && $log !== false) {
|
||||
if ($log instanceof IMessenger) log::set_messenger($log);
|
||||
elseif (is_string($log)) log::set_messenger_class($log);
|
||||
elseif (is_array($log)) {
|
||||
nur_func::ensure_class($log, $args);
|
||||
$log = nur_func::cons($log, $args);
|
||||
}
|
||||
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);
|
||||
elseif (is_array($console)) {
|
||||
nur_func::ensure_class($console, $args);
|
||||
$console = nur_func::cons($console, $args);
|
||||
}
|
||||
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);
|
||||
elseif (is_array($say)) {
|
||||
nur_func::ensure_class($say, $args);
|
||||
$say = nur_func::cons($say, $args);
|
||||
}
|
||||
else $say = func::call($say);
|
||||
say::set_messenger($say);
|
||||
$msgs[] = $say;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ namespace nulib\php\content;
|
||||
|
||||
use Closure;
|
||||
use nulib\cl;
|
||||
use nulib\php\nur_func;
|
||||
use nulib\php\func;
|
||||
|
||||
/**
|
||||
* Class c: classe outil pour gérer du contenu
|
||||
@ -62,8 +62,7 @@ class c {
|
||||
# contenu dynamique: le contenu est la valeur de retour de la fonction
|
||||
# ce contenu est rajouté à la suite après avoir été quoté avec self::q()
|
||||
$func = $value;
|
||||
nur_func::ensure_func($func, $object_or_class, $args);
|
||||
$values = self::q(nur_func::call($func, ...$args));
|
||||
$values = self::q(func::call($func));
|
||||
self::add_static_content($dest, $values, $key, $seq);
|
||||
continue;
|
||||
}
|
||||
@ -83,16 +82,7 @@ class c {
|
||||
$arg = self::resolve($arg, $object_or_class, false);
|
||||
if (!$array) $arg = $arg[0];
|
||||
}; unset($arg);
|
||||
if (nur_func::is_static($func)) {
|
||||
nur_func::ensure_func($func, $object_or_class, $args);
|
||||
$value = nur_func::call($func, ...$args);
|
||||
} elseif (nur_func::is_class($func)) {
|
||||
nur_func::fix_class_args($func, $args);
|
||||
$value = nur_func::cons($func, ...$args);
|
||||
} else {
|
||||
nur_func::ensure_func($func, $object_or_class, $args);
|
||||
$value = nur_func::call($func, ...$args);
|
||||
}
|
||||
$value = func::with($func, $args)->bind($object_or_class, true)->invoke();
|
||||
}
|
||||
}
|
||||
if ($seq) $dest[] = $value;
|
||||
|
@ -3,13 +3,16 @@ namespace nulib\php;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Generator;
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\cv;
|
||||
use nulib\StateException;
|
||||
use nulib\ValueException;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class func: outils pour appeler fonctions et méthodes dynamiquement
|
||||
@ -58,10 +61,7 @@ class func {
|
||||
* la fonction (ne pas uniquement faire une vérification syntaxique)
|
||||
*/
|
||||
static function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool {
|
||||
if ($strict) {
|
||||
$msg = var_export($func, true);
|
||||
$reason = null;
|
||||
}
|
||||
if ($strict) $reason = null;
|
||||
if ($func instanceof ReflectionFunction) return true;
|
||||
if (is_string($func)) {
|
||||
$c = false;
|
||||
@ -82,11 +82,11 @@ class func {
|
||||
if ($strict) {
|
||||
$reason = null;
|
||||
if (class_exists($f)) {
|
||||
$reason = "$msg: is a class";
|
||||
$reason = "$f: is a class";
|
||||
return false;
|
||||
}
|
||||
if (!function_exists($f)) {
|
||||
$reason = "$msg: function not found";
|
||||
$reason = "$f: function not found";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -117,10 +117,7 @@ class func {
|
||||
* faire une vérification syntaxique)
|
||||
*/
|
||||
static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool {
|
||||
if ($strict) {
|
||||
$msg = var_export($func, true);
|
||||
$reason = null;
|
||||
}
|
||||
if ($strict) $reason = null;
|
||||
if ($func instanceof ReflectionClass) return true;
|
||||
if (is_string($func)) {
|
||||
$c = $func;
|
||||
@ -138,12 +135,10 @@ class func {
|
||||
if (self::_parse_static($c)) return false;
|
||||
if (self::_parse_method($c)) return false;
|
||||
if ($f !== false) return false;
|
||||
if ($strict) {
|
||||
if (!class_exists($c)) {
|
||||
$reason = "$msg: class not found";
|
||||
if ($strict && !class_exists($c)) {
|
||||
$reason = "$c: class not found";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$func = [$c, false];
|
||||
return true;
|
||||
}
|
||||
@ -207,10 +202,7 @@ class func {
|
||||
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
||||
*/
|
||||
static function verifix_static(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||
if ($strict) {
|
||||
$msg = var_export($func, true);
|
||||
$reason = null;
|
||||
}
|
||||
if ($strict) $reason = null;
|
||||
if ($func instanceof ReflectionMethod) {
|
||||
$bound = false;
|
||||
return true;
|
||||
@ -265,18 +257,19 @@ class func {
|
||||
return false;
|
||||
}
|
||||
if ($strict) {
|
||||
[$c, $f] = $cf;
|
||||
$reason = null;
|
||||
if ($bound) {
|
||||
if (!class_exists($c)) {
|
||||
$reason = "$msg: class not found";
|
||||
$reason = "$c: class not found";
|
||||
return false;
|
||||
}
|
||||
if (!method_exists($c, $f)) {
|
||||
$reason = "$msg: method not found";
|
||||
$reason = "$c::$f: method not found";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$reason = "$msg: not bound";
|
||||
$reason = "$c::$f: not bound";
|
||||
}
|
||||
}
|
||||
$func = $cf;
|
||||
@ -342,10 +335,7 @@ class func {
|
||||
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
||||
*/
|
||||
static function verifix_method(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||
if ($strict) {
|
||||
$msg = var_export($func, true);
|
||||
$reason = null;
|
||||
}
|
||||
if ($strict) $reason = null;
|
||||
if ($func instanceof ReflectionMethod) {
|
||||
$bound = false;
|
||||
return true;
|
||||
@ -401,18 +391,19 @@ class func {
|
||||
return false;
|
||||
}
|
||||
if ($strict) {
|
||||
[$c, $f] = $cf;
|
||||
$reason = null;
|
||||
if ($bound) {
|
||||
if (!is_object($c) && !class_exists($c)) {
|
||||
$reason = "$msg: class not found";
|
||||
$reason = "$c: class not found";
|
||||
return false;
|
||||
}
|
||||
if (!method_exists($c, $f)) {
|
||||
$reason = "$msg: method not found";
|
||||
$reason = "$c::$f: method not found";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$reason = "$msg: not bound";
|
||||
$reason = "$c::$f: not bound";
|
||||
}
|
||||
}
|
||||
$func = $cf;
|
||||
@ -446,7 +437,7 @@ class func {
|
||||
return new ValueException($reason);
|
||||
}
|
||||
|
||||
static function with($func, ?array $args=null, bool $strict=true): self {
|
||||
private static function _with($func, ?array $args=null, bool $strict=true, ?string &$reason=null): ?self {
|
||||
if (!is_array($func)) {
|
||||
if ($func instanceof Closure) {
|
||||
return new self(self::TYPE_CLOSURE, $func, $args);
|
||||
@ -467,6 +458,12 @@ class func {
|
||||
} elseif (self::verifix_static($func, $strict, $bound, $reason)) {
|
||||
return new self(self::TYPE_STATIC, $func, $args, $bound, $reason);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static function with($func, ?array $args=null, bool $strict=true): self {
|
||||
$func = self::_with($func, $args, $strict, $reason);
|
||||
if ($func !== null) return $func;
|
||||
throw self::not_a_callable($func, $reason);
|
||||
}
|
||||
|
||||
@ -487,10 +484,45 @@ class func {
|
||||
}
|
||||
}
|
||||
|
||||
static function is_callable($func): bool {
|
||||
$func = self::_with($func);
|
||||
if ($func === null) return false;
|
||||
if (!$func->isBound()) return false;
|
||||
return $func->type !== self::TYPE_CLASS;
|
||||
}
|
||||
|
||||
static function call($func, ...$args) {
|
||||
return self::with($func)->invoke($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* si $value est une fonction, l'appeler
|
||||
* si $value ou le résultat de l'appel est un Traversable, le résoudre
|
||||
* sinon retourner $value tel quel
|
||||
*
|
||||
* en définitive, la valeur de retour de cette fonction est soit un scalaire,
|
||||
* soit un array, soit un objet qui n'est pas Traversable
|
||||
* @return mixed
|
||||
*/
|
||||
static function get_value($value, ...$args) {
|
||||
if ($value instanceof self) $value = $value->invoke($args);
|
||||
elseif (is_callable($value)) $value = self::call($value, ...$args);
|
||||
if ($value instanceof Traversable) $value = cl::all($value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* si $value est une fonction, l'appeler
|
||||
* si $value ou le résultat de l'appel est un Traversable, le retourner
|
||||
* sinon retourner $value en tant qu'array
|
||||
*/
|
||||
static function get_iterable($value, ...$args): ?iterable {
|
||||
if ($value instanceof self) $value = $value->invoke($args);
|
||||
elseif (is_callable($value)) $value = self::call($value, ...$args);
|
||||
if ($value instanceof Traversable) return $value;
|
||||
else return cl::withn($value);
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
protected function __construct(int $type, $func, ?array $args=null, bool $bound=false, ?string $reason=null) {
|
||||
@ -561,6 +593,27 @@ class func {
|
||||
|
||||
protected int $maxArgs;
|
||||
|
||||
function replaceArgs(?array $args): self {
|
||||
$this->prefixArgs = $args?? [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
function prependArgs(?array $args, ?int $stripCount=null): self {
|
||||
if ($stripCount !== null || $args !== null) {
|
||||
array_splice($this->prefixArgs, 0, $stripCount ?? 0, $args);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function appendArgs(?array $args, ?int $stripCount=null): self {
|
||||
if ($stripCount !== null || $args !== null) {
|
||||
$stripCount ??= 0;
|
||||
if ($stripCount > 0) array_splice($this->prefixArgs, -$stripCount);
|
||||
$this->prefixArgs = array_merge($this->prefixArgs, $args);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function updateReflection($reflection): void {
|
||||
$variadic = false;
|
||||
$minArgs = $maxArgs = 0;
|
||||
@ -596,11 +649,16 @@ class func {
|
||||
else return $this->bound && $this->object !== null;
|
||||
}
|
||||
|
||||
function bind($object): self {
|
||||
function bind($object, bool $unlessAlreadyBound=false, bool $replace=false): self {
|
||||
if ($this->type !== self::TYPE_METHOD) return $this;
|
||||
if ($this->bound && $unlessAlreadyBound) return $this;
|
||||
|
||||
[$c, $f] = $this->func;
|
||||
if ($this->reflection === null) {
|
||||
if ($replace) {
|
||||
$c = $object;
|
||||
$this->func = [$c, $f];
|
||||
$this->updateReflection(new ReflectionMethod($c, $f));
|
||||
} elseif ($this->reflection === null) {
|
||||
$this->func[0] = $c = $object;
|
||||
$this->updateReflection(new ReflectionMethod($c, $f));
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class mprop {
|
||||
} catch (ReflectionException $e) {
|
||||
return oprop::get($object, $property, $default);
|
||||
}
|
||||
return nur_func::call([$object, $m], $default);
|
||||
return func::call([$object, $m], $default);
|
||||
}
|
||||
|
||||
/** spécifier la valeur d'une propriété */
|
||||
@ -60,7 +60,7 @@ class mprop {
|
||||
} catch (ReflectionException $e) {
|
||||
return oprop::_set($c, $object, $property, $value);
|
||||
}
|
||||
nur_func::call([$object, $m], $value);
|
||||
func::call([$object, $m], $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
@ -1,453 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\php;
|
||||
|
||||
use Closure;
|
||||
use nulib\cl;
|
||||
use nulib\ref\php\ref_func;
|
||||
use nulib\ValueException;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Class func: outils pour appeler des fonctions et méthodes dynamiquement
|
||||
*/
|
||||
class nur_func {
|
||||
/**
|
||||
* tester si $func est une chaine de la forme "XXX::method" où XXX est une
|
||||
* chaine quelconque éventuellement vide, ou un tableau de la forme ["method"]
|
||||
* ou [anything, "method", ...]
|
||||
*
|
||||
* Avec la forme tableau, "method" ne doit pas contenir le caractère '\', pour
|
||||
* pouvoir utiliser conjointement {@link is_class()}
|
||||
*/
|
||||
static final function is_static($func, bool $allowClass=false): bool {
|
||||
if (is_string($func)) {
|
||||
$pos = strpos($func, "::");
|
||||
if ($pos === false) return false;
|
||||
return $pos + 2 < strlen($func);
|
||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
||||
$count = count($func);
|
||||
if ($count == 1) {
|
||||
if (!is_string($func[0]) || strlen($func[0]) == 0) return false;
|
||||
if (strpos($func[0], "\\") !== false) return false;
|
||||
return true;
|
||||
} elseif ($count > 1) {
|
||||
if (!array_key_exists(1, $func)) return false;
|
||||
if (!is_string($func[1]) || strlen($func[1]) == 0) return false;
|
||||
if (strpos($func[1], "\\") !== false) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* si $func est une chaine de la forme "::method" alors la remplacer par la
|
||||
* chaine "$class::method"
|
||||
*
|
||||
* si $func est un tableau de la forme ["method"] ou [null, "method"], alors
|
||||
* le remplacer par [$class, "method"]
|
||||
*
|
||||
* on assume que {@link is_static()}($func) retourne true
|
||||
*
|
||||
* @return bool true si la correction a été faite
|
||||
*/
|
||||
static final function fix_static(&$func, $class): bool {
|
||||
if (is_object($class)) $class = get_class($class);
|
||||
|
||||
if (is_string($func) && substr($func, 0, 2) == "::") {
|
||||
$func = "$class$func";
|
||||
return true;
|
||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
||||
$count = count($func);
|
||||
if ($count == 1) {
|
||||
$func = [$class, $func[0]];
|
||||
return true;
|
||||
} elseif ($count > 1 && $func[0] === null) {
|
||||
$func[0] = $class;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** tester si $method est une chaine de la forme "->method" */
|
||||
private static function isam($method): bool {
|
||||
return is_string($method)
|
||||
&& strlen($method) > 2
|
||||
&& substr($method, 0, 2) == "->";
|
||||
}
|
||||
|
||||
/**
|
||||
* tester si $func est une chaine de la forme "->method" ou un tableau de la
|
||||
* forme ["->method", ...] ou [anything, "->method", ...]
|
||||
*/
|
||||
static final function is_method($func): bool {
|
||||
if (is_string($func)) {
|
||||
return self::isam($func);
|
||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
||||
if (self::isam($func[0])) {
|
||||
# ["->method", ...]
|
||||
return true;
|
||||
}
|
||||
if (array_key_exists(1, $func) && self::isam($func[1])) {
|
||||
# [anything, "->method", ...]
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* si $func est une chaine de la forme "->method" alors la remplacer par le
|
||||
* tableau [$object, "method"]
|
||||
*
|
||||
* si $func est un tableau de la forme ["->method"] ou [anything, "->method"],
|
||||
* alors le remplacer par [$object, "method"]
|
||||
*
|
||||
* @return bool true si la correction a été faite
|
||||
*/
|
||||
static final function fix_method(&$func, $object): bool {
|
||||
if (!is_object($object)) return false;
|
||||
|
||||
if (is_string($func)) {
|
||||
if (self::isam($func)) {
|
||||
$func = [$object, substr($func, 2)];
|
||||
return true;
|
||||
}
|
||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
||||
if (self::isam($func[0])) $func = array_merge([null], $func);
|
||||
if (count($func) > 1 && array_key_exists(1, $func) && self::isam($func[1])) {
|
||||
$func[0] = $object;
|
||||
$func[1] = substr($func[1], 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* si $func est un tableau de plus de 2 éléments, alors déplacer les éléments
|
||||
* supplémentaires au début de $args. par exemple:
|
||||
* ~~~
|
||||
* $func = ["class", "method", "arg1", "arg2"];
|
||||
* $args = ["arg3"];
|
||||
* func::fix_args($func, $args)
|
||||
* # $func === ["class", "method"]
|
||||
* # $args === ["arg1", "arg2", "arg3"]
|
||||
* ~~~
|
||||
*
|
||||
* @return bool true si la correction a été faite
|
||||
*/
|
||||
static final function fix_args(&$func, ?array &$args): bool {
|
||||
if ($args === null) $args = [];
|
||||
if (is_array($func) && count($func) > 2) {
|
||||
$prefix_args = array_slice($func, 2);
|
||||
$func = array_slice($func, 0, 2);
|
||||
$args = array_merge($prefix_args, $args);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* s'assurer que $func est un appel de méthode ou d'une méthode statique;
|
||||
* et renseigner le cas échéant les arguments. si $func ne fait pas mention
|
||||
* de la classe ou de l'objet, le renseigner avec $class_or_object.
|
||||
*
|
||||
* @return bool true si c'est une fonction valide. il ne reste plus qu'à
|
||||
* l'appeler avec {@link call()}
|
||||
*/
|
||||
static final function check_func(&$func, $class_or_object, &$args=null): bool {
|
||||
if ($func instanceof Closure) return true;
|
||||
if (self::is_method($func)) {
|
||||
# méthode
|
||||
self::fix_method($func, $class_or_object);
|
||||
self::fix_args($func, $args);
|
||||
return true;
|
||||
} elseif (self::is_static($func)) {
|
||||
# méthode statique
|
||||
self::fix_static($func, $class_or_object);
|
||||
self::fix_args($func, $args);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comme {@link check_func()} mais lance une exception si la fonction est
|
||||
* invalide
|
||||
*
|
||||
* @throws ValueException si $func n'est pas une fonction ou une méthode valide
|
||||
*/
|
||||
static final function ensure_func(&$func, $class_or_object, &$args=null): void {
|
||||
if (!self::check_func($func, $class_or_object, $args)) {
|
||||
throw ValueException::invalid_type($func, "callable");
|
||||
}
|
||||
}
|
||||
|
||||
static final function _prepare($func): array {
|
||||
$object = null;
|
||||
if (is_callable($func)) {
|
||||
if (is_array($func)) {
|
||||
$rf = new ReflectionMethod(...$func);
|
||||
$object = $func[0];
|
||||
if (is_string($object)) $object = null;
|
||||
} elseif ($func instanceof Closure) {
|
||||
$rf = new ReflectionFunction($func);
|
||||
} elseif (is_string($func) && strpos($func, "::") === false) {
|
||||
$rf = new ReflectionFunction($func);
|
||||
} else {
|
||||
$rf = new ReflectionMethod($func);
|
||||
}
|
||||
} elseif ($func instanceof ReflectionMethod) {
|
||||
$rf = $func;
|
||||
} elseif ($func instanceof ReflectionFunction) {
|
||||
$rf = $func;
|
||||
} elseif (is_array($func) && count($func) == 2 && isset($func[0]) && isset($func[1])
|
||||
&& ($func[1] instanceof ReflectionMethod || $func[1] instanceof ReflectionFunction)) {
|
||||
$object = $func[0];
|
||||
if (is_string($object)) $object = null;
|
||||
$rf = $func[1];
|
||||
} elseif (is_string($func) && strpos($func, "::") === false) {
|
||||
$rf = new ReflectionFunction($func);
|
||||
} else {
|
||||
throw ValueException::invalid_type($func, "callable");
|
||||
}
|
||||
$minArgs = $rf->getNumberOfRequiredParameters();
|
||||
$maxArgs = $rf->getNumberOfParameters();
|
||||
$variadic = $rf->isVariadic();
|
||||
return [$rf instanceof ReflectionMethod, $object, $rf, $minArgs, $maxArgs, $variadic];
|
||||
}
|
||||
|
||||
static final function _fill(array $context, array &$args): void {
|
||||
$minArgs = $context[3];
|
||||
$maxArgs = $context[4];
|
||||
$variadic = $context[5];
|
||||
if (!$variadic) $args = array_slice($args, 0, $maxArgs);
|
||||
while (count($args) < $minArgs) $args[] = null;
|
||||
}
|
||||
|
||||
static final function _call($context, array $args) {
|
||||
self::_fill($context, $args);
|
||||
$use_object = $context[0];
|
||||
$object = $context[1];
|
||||
$method = $context[2];
|
||||
if ($use_object) {
|
||||
if (count($args) === 0) return $method->invoke($object);
|
||||
else return $method->invokeArgs($object, $args);
|
||||
} else {
|
||||
if (count($args) === 0) return $method->invoke();
|
||||
else return $method->invokeArgs($args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appeler la fonction spécifiée avec les arguments spécifiés.
|
||||
* Adapter $args en fonction du nombre réel d'arguments de $func
|
||||
*
|
||||
* @param callable|ReflectionFunction|ReflectionMethod $func
|
||||
*/
|
||||
static final function call($func, ...$args) {
|
||||
return self::_call(self::_prepare($func), $args);
|
||||
}
|
||||
|
||||
/** remplacer $value par $func($value, ...$args) */
|
||||
static final function apply(&$value, $func, ...$args): void {
|
||||
if ($func !== null) {
|
||||
if ($args) $args = array_merge([$value], $args);
|
||||
else $args = [$value];
|
||||
$value = self::call($func, ...$args);
|
||||
}
|
||||
}
|
||||
|
||||
const MASK_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC;
|
||||
const MASK_P = ReflectionMethod::IS_PUBLIC;
|
||||
const METHOD_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC;
|
||||
const METHOD_P = ReflectionMethod::IS_PUBLIC;
|
||||
|
||||
private static function matches(string $name, array $includes, array $excludes): bool {
|
||||
if ($includes) {
|
||||
$matches = false;
|
||||
foreach ($includes as $include) {
|
||||
if (substr($include, 0, 1) == "/") {
|
||||
# expression régulière
|
||||
if (preg_match($include, $name)) {
|
||||
$matches = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
# tester la présence de la sous-chaine
|
||||
if (strpos($name, $include) !== false) {
|
||||
$matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$matches) return false;
|
||||
}
|
||||
foreach ($excludes as $exclude) {
|
||||
if (substr($exclude, 0, 1) == "/") {
|
||||
# expression régulière
|
||||
if (preg_match($exclude, $name)) return false;
|
||||
} else {
|
||||
# tester la présence de la sous-chaine
|
||||
if (strpos($name, $exclude) !== false) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var Schema */
|
||||
private static $call_all_params_schema;
|
||||
|
||||
/**
|
||||
* retourner la liste des méthodes de $class_or_object qui correspondent au
|
||||
* filtre $options. le filtre doit respecter le schéme {@link CALL_ALL_PARAMS_SCHEMA}
|
||||
*/
|
||||
static function get_all($class_or_object, $params=null): array {
|
||||
Schema::nv($paramsv, $params, null
|
||||
, self::$call_all_params_schema, ref_func::CALL_ALL_PARAMS_SCHEMA);
|
||||
if (is_callable($class_or_object, true) && is_array($class_or_object)) {
|
||||
# callable sous forme de tableau
|
||||
$class_or_object = $class_or_object[0];
|
||||
}
|
||||
if (is_string($class_or_object)) {
|
||||
# lister les méthodes publiques statiques de la classe
|
||||
$mask = self::MASK_PS;
|
||||
$expected = self::METHOD_PS;
|
||||
$c = new ReflectionClass($class_or_object);
|
||||
} elseif (is_object($class_or_object)) {
|
||||
# lister les méthodes publiques de la classe
|
||||
$c = new ReflectionClass($class_or_object);
|
||||
$mask = $params["static_only"]? self::MASK_PS: self::MASK_P;
|
||||
$expected = $params["static_only"]? self::METHOD_PS: self::METHOD_P;
|
||||
} else {
|
||||
throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet");
|
||||
}
|
||||
$prefix = $params["prefix"]; $prefixlen = strlen($prefix);
|
||||
$args = $params["args"];
|
||||
$includes = $params["include"];
|
||||
$excludes = $params["exclude"];
|
||||
$methods = [];
|
||||
foreach ($c->getMethods() as $m) {
|
||||
if (($m->getModifiers() & $mask) != $expected) continue;
|
||||
$name = $m->getName();
|
||||
if (substr($name, 0, $prefixlen) != $prefix) continue;
|
||||
if (!self::matches($name, $includes, $excludes)) continue;
|
||||
$methods[] = cl::merge([$class_or_object, $name], $args);
|
||||
}
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appeler toutes les méthodes publiques de $object_or_class et retourner un
|
||||
* tableau [$method_name => $return_value] des valeurs de retour.
|
||||
*/
|
||||
static final function call_all($class_or_object, $params=null): array {
|
||||
$methods = self::get_all($class_or_object, $params);
|
||||
$values = [];
|
||||
foreach ($methods as $method) {
|
||||
self::fix_args($method, $args);
|
||||
$values[$method[1]] = self::call($method, ...$args);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* tester si $func est une chaine de la forme "XXX" où XXX est une classe
|
||||
* valide, ou un tableau de la forme ["XXX", ...]
|
||||
*
|
||||
* NB: il est possible d'avoir {@link is_static()} et {@link is_class()}
|
||||
* vraies pour la même valeur. s'il faut supporter les deux cas, appeler
|
||||
* {@link is_static()} d'abord, mais dans ce cas, on ne supporte que les
|
||||
* classes qui sont dans un package
|
||||
*/
|
||||
static final function is_class($class): bool {
|
||||
if (is_string($class)) {
|
||||
return class_exists($class);
|
||||
} elseif (is_array($class) && array_key_exists(0, $class)) {
|
||||
return class_exists($class[0]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* en assumant que {@link is_class()} est vrai, si $class est un tableau de
|
||||
* plus de 1 éléments, alors déplacer les éléments supplémentaires au début de
|
||||
* $args. par exemple:
|
||||
* ~~~
|
||||
* $class = ["class", "arg1", "arg2"];
|
||||
* $args = ["arg3"];
|
||||
* func::fix_class_args($class, $args)
|
||||
* # $class === "class"
|
||||
* # $args === ["arg1", "arg2", "arg3"]
|
||||
* ~~~
|
||||
*
|
||||
* @return bool true si la correction a été faite
|
||||
*/
|
||||
static final function fix_class_args(&$class, ?array &$args): bool {
|
||||
if ($args === null) $args = [];
|
||||
if (is_array($class)) {
|
||||
if (count($class) > 1) {
|
||||
$prefix_args = array_slice($class, 1);
|
||||
$class = array_slice($class, 0, 1)[0];
|
||||
$args = array_merge($prefix_args, $args);
|
||||
} else {
|
||||
$class = $class[0];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* s'assurer que $class est une classe et renseigner le cas échéant les
|
||||
* arguments.
|
||||
*
|
||||
* @return bool true si c'est une classe valide. il ne reste plus qu'à
|
||||
* l'instancier avec {@link cons()}
|
||||
*/
|
||||
static final function check_class(&$class, &$args=null): bool {
|
||||
if (self::is_class($class)) {
|
||||
self::fix_class_args($class, $args);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comme {@link check_class()} mais lance une exception si la classe est
|
||||
* invalide
|
||||
*
|
||||
* @throws ValueException si $class n'est pas une classe valide
|
||||
*/
|
||||
static final function ensure_class(&$class, &$args=null): void {
|
||||
if (!self::check_class($class, $args)) {
|
||||
throw ValueException::invalid_type($class, "class");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instancier la classe avec les arguments spécifiés.
|
||||
* Adapter $args en fonction du nombre réel d'arguments du constructeur
|
||||
*/
|
||||
static final function cons(string $class, ...$args) {
|
||||
$c = new ReflectionClass($class);
|
||||
$rf = $c->getConstructor();
|
||||
if ($rf === null) {
|
||||
return $c->newInstance();
|
||||
} else {
|
||||
if (!$rf->isVariadic()) {
|
||||
$minArgs = $rf->getNumberOfRequiredParameters();
|
||||
$maxArgs = $rf->getNumberOfParameters();
|
||||
$args = array_slice($args, 0, $maxArgs);
|
||||
while (count($args) < $minArgs) {
|
||||
$args[] = null;
|
||||
}
|
||||
}
|
||||
return $c->newInstanceArgs($args);
|
||||
}
|
||||
}
|
||||
}
|
42
php/src/ref/schema/ref_input.php
Normal file
42
php/src/ref/schema/ref_input.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace nulib\ref\schema;
|
||||
|
||||
class ref_input {
|
||||
const ACCESS_AUTO = 0, ACCESS_KEY = 1, ACCESS_PROPERTY = 2;
|
||||
|
||||
const INPUT_PARAMS_SCHEMA = [
|
||||
"access_type" => ["int", self::ACCESS_AUTO, "type d'accès: clé ou propriété"],
|
||||
"allow_empty" => ["bool", true, "la chaine vide est-elle autorisée?"],
|
||||
"allow_null" => ["bool", true, "la valeur null est-elle autorisée?"],
|
||||
];
|
||||
|
||||
const ACCESS_PARAMS_SCHEMA = [
|
||||
"allow_empty" => ["bool", true, "la chaine vide est-elle autorisée?"],
|
||||
"allow_null" => ["bool", null, "la valeur null est-elle autorisée?"],
|
||||
"allow_false" => ["bool", null, "la valeur false est-elle autorisée?"],
|
||||
"protect_dest" => ["bool", null, "faut-il protéger la destination?"],
|
||||
];
|
||||
|
||||
const VALUE_ACCESS_PARAMS_SCHEMA = [
|
||||
"allow_null" => ["bool", false],
|
||||
"allow_false" => ["bool", true],
|
||||
"protect_dest" => ["bool", false],
|
||||
];
|
||||
|
||||
const ARRAY_ACCESS_PARAMS_SCHEMA = [
|
||||
"allow_null" => ["bool", true],
|
||||
"allow_false" => ["bool", false],
|
||||
"protect_dest" => ["bool", true],
|
||||
"key_prefix" => ["?string", null, "préfixe des clés pour les méthodes ensureXxx()"],
|
||||
"key_suffix" => ["?string", null, "suffixe des clés pour les méthodes ensureXxx()"],
|
||||
];
|
||||
|
||||
const PROPERTY_ACCESS_PARAMS_SCHEMA = [
|
||||
"allow_null" => ["bool", true],
|
||||
"allow_false" => ["bool", false],
|
||||
"protect_dest" => ["bool", true],
|
||||
"key_prefix" => ["?string", null, "préfixe des clés pour les méthodes ensureXxx()"],
|
||||
"key_suffix" => ["?string", null, "suffixe des clés pour les méthodes ensureXxx()"],
|
||||
"map_names" => ["bool", true, "faut-il mapper les clés en camelCase?"]
|
||||
];
|
||||
}
|
@ -4,20 +4,15 @@ namespace nulib\ref\schema;
|
||||
class ref_schema {
|
||||
/** @var array schéma des natures de schéma */
|
||||
const NATURE_METASCHEMA = [
|
||||
"nature" => ["string", null, "nature du schéma",
|
||||
"pkey" => 0,
|
||||
"allowed_values" => ["assoc", "list", "scalar"],
|
||||
0 => ["string", null, "nature du schéma",
|
||||
"allowed_values" => ["scalar", "assoc", "list"],
|
||||
],
|
||||
"title" => ["?string", null, "libellé de la valeur"],
|
||||
"required" => ["bool", false, "la valeur est-elle requise?"],
|
||||
"nullable" => ["?bool", null, "la valeur peut-elle être nulle?"],
|
||||
"desc" => ["?content", null, "description de la valeur"],
|
||||
"name" => ["?key", null, "identifiant de la valeur"],
|
||||
"schema" => ["?array", null, "définition du schéma"],
|
||||
"compute_func" => ["?callable", null, "fonction qui calcule les valeurs des champs computed"],
|
||||
"validate_func" => ["?callable", null, "fonction qui vérifie la conformité de l'objet dans son ensemble"],
|
||||
];
|
||||
|
||||
/** @var array meta-schema d'un schéma de nature scalaire */
|
||||
const SCALAR_METASCHEMA = [
|
||||
/** @var array meta-schéma d'une valeur */
|
||||
const VALUE_METASCHEMA = [
|
||||
"type" => ["array", null, "types possibles de la valeur", "required" => true],
|
||||
"default" => [null, null, "valeur par défaut si la valeur n'existe pas"],
|
||||
"title" => ["?string", null, "libellé de la valeur"],
|
||||
@ -31,28 +26,61 @@ class ref_schema {
|
||||
"messages" => ["?array", null, "messages à afficher en cas d'erreur d'analyse"],
|
||||
"formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
|
||||
"format" => [null, null, "format à utiliser pour l'affichage"],
|
||||
"" => ["array", "scalar", "nature du schéma",
|
||||
"" => ["assoc", "schema" => self::NATURE_METASCHEMA],
|
||||
"size" => ["?int", null, "nom de caractères ou de chiffres de la valeur"],
|
||||
"precision" => ["?int", null, "nombre de chiffres après la virgule pour une valeur numérique flottante"],
|
||||
"" => ["array", ["scalar"], "nature du schéma",
|
||||
"schema" => self::NATURE_METASCHEMA,
|
||||
],
|
||||
"schema" => ["?array", null, "schéma de la valeur si c'est un array"],
|
||||
"name" => ["?string", null, "identifiant de la valeur"],
|
||||
"pkey" => ["?pkey", null, "chemin de clé de la valeur dans un tableau associatif"],
|
||||
"header" => ["?string", null, "nom de l'en-tête s'il faut présenter cette donnée dans un tableau"],
|
||||
"composite" => ["?bool", null, "ce champ fait-il partie d'une valeur composite?"],
|
||||
"computed" => ["?bool", null, "ce champ est-il calculé? si oui, il n'est pas demandé en entrée ni validé"],
|
||||
];
|
||||
|
||||
const MESSAGES = [
|
||||
"missing" => "{key}: Vous devez spécifier cette valeur",
|
||||
"unavailable" => "{key}: Vous devez spécifier cette valeur",
|
||||
"null" => "{key}: cette valeur ne doit pas être nulle",
|
||||
"empty" => "{key}: cette valeur ne doit pas être vide",
|
||||
"invalid" => "{key}: {orig}: cette valeur est invalide",
|
||||
"missing" => "Vous devez spécifier cette valeur",
|
||||
"unavailable" => "Vous devez spécifier cette valeur",
|
||||
"null" => "Cette valeur ne doit pas être nulle",
|
||||
"empty" => "Cette valeur ne doit pas être vide",
|
||||
"invalid" => "Cette valeur est invalide",
|
||||
];
|
||||
|
||||
/** @var array meta-schema d'un schéma de nature associative */
|
||||
const ASSOC_METASCHEMA = [
|
||||
const PARAMS_SCHEMA = [
|
||||
"analyze" => ["bool", true, "faut-il analyser la valeur?"],
|
||||
"reanalyze" => ["bool", true, "faut-il forcer l'analyse de la valeur?"],
|
||||
"normalize" => ["bool", true, "faut-il normaliser la valeur?"],
|
||||
"renormalize" => ["bool", true, "faut-il forcer la normalisation de la valeur?"],
|
||||
"throw" => ["bool", true, "faut-il lancer une exception en cas d'erreur?"],
|
||||
//...ref_input::INPUT_PARAMS_SCHEMA,
|
||||
];
|
||||
|
||||
/** @var array meta-schema d'un schéma de nature liste */
|
||||
const LIST_METASCHEMA = [
|
||||
/** @var array clés supplémentaires de schéma de la nature scalaire */
|
||||
const SCALAR_NATURE_METASCHEMA = [
|
||||
];
|
||||
|
||||
const SCALAR_PARAMS_SCHEMA = [
|
||||
];
|
||||
|
||||
/** @var array clés supplémentaires de schéma de la nature associative */
|
||||
const ASSOC_NATURE_METASCHEMA = [
|
||||
"ensure_array" => ["bool", null, "faut-il s'assurer que le tableau destination est non nul?"],
|
||||
"ensure_assoc" => ["bool", null, "faut-il s'assurer que le tableau destination est associatif?"],
|
||||
"ensure_keys" => ["bool", null, "faut-il s'assurer que toutes les clés existent avec la valeur par défaut?"],
|
||||
"ensure_order" => ["bool", null, "faut-il s'assurer que les clés soient dans l'ordre?"],
|
||||
];
|
||||
|
||||
const ASSOC_PARAMS_SCHEMA = [
|
||||
"ensure_array" => ["bool", false],
|
||||
"ensure_assoc" => ["bool", true],
|
||||
"ensure_keys" => ["bool", true],
|
||||
"ensure_order" => ["bool", true],
|
||||
];
|
||||
|
||||
/** @var array clés supplémentaires de schéma de la nature liste */
|
||||
const LIST_NATURE_METASCHEMA = [
|
||||
];
|
||||
|
||||
const LIST_PARAMS_SCHEMA = [
|
||||
];
|
||||
}
|
||||
|
@ -6,5 +6,6 @@ class ref_types {
|
||||
"boolean" => "bool",
|
||||
"integer" => "int",
|
||||
"flt" => "float", "double" => "float", "dbl" => "float",
|
||||
"func" => "callable", "function" => "callable",
|
||||
];
|
||||
}
|
||||
|
@ -242,6 +242,21 @@ class str {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* vérifier si $s a le préfixe $prefix
|
||||
* - si $prefix commence par /, c'est une expression régulière, et elle doit
|
||||
* matcher $s
|
||||
* - sinon $s doit commencer par la chaine $prefix
|
||||
*/
|
||||
static final function match_prefix(?string $s, ?string $prefix): bool {
|
||||
if ($s === null || $prefix === null) return false;
|
||||
if (substr($prefix, 0, 1) === "/") {
|
||||
return preg_match($prefix, $s);
|
||||
} else {
|
||||
return self::_starts_with($prefix, $s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ajouter $sep$prefix$text$suffix à $s si $text est non vide
|
||||
*
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user