Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
b4e28ade02 | |||
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
|
/.composer.lock.runphp
|
||||||
|
|
||||||
.~lock*#
|
.~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
|
## Release 0.3.2p74 du 28/02/2025-20:28
|
||||||
|
|
||||||
(tech) Migration vers pman
|
(tech) Migration vers pman
|
||||||
|
@ -1 +1 @@
|
|||||||
0.3.2
|
0.4.1
|
||||||
|
@ -6,15 +6,15 @@ function __esection() {
|
|||||||
local length="${COLUMNS:-80}"
|
local length="${COLUMNS:-80}"
|
||||||
setx lsep=__complete "$prefix" "$length" -
|
setx lsep=__complete "$prefix" "$length" -
|
||||||
|
|
||||||
tooenc "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
recho "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
||||||
[ -n "$*" ] || return 0
|
[ -n "$*" ] || return 0
|
||||||
length=$((length - 1))
|
length=$((length - 1))
|
||||||
setx -a lines=echo "$1"
|
setx -a lines=echo "$1"
|
||||||
for line in "${lines[@]}"; do
|
for line in "${lines[@]}"; do
|
||||||
setx line=__complete "$prefix- $line" "$length"
|
setx line=__complete "$prefix- $line" "$length"
|
||||||
tooenc "$COULEUR_BLEUE$line-$COULEUR_NORMALE"
|
recho "$COULEUR_BLEUE$line-$COULEUR_NORMALE"
|
||||||
done
|
done
|
||||||
tooenc "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
recho "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
||||||
}
|
}
|
||||||
function __etitle() {
|
function __etitle() {
|
||||||
local -a lines; local maxlen=0
|
local -a lines; local maxlen=0
|
||||||
@ -23,10 +23,10 @@ function __etitle() {
|
|||||||
setx -a lines=echo "$1"
|
setx -a lines=echo "$1"
|
||||||
for line in "${lines[@]}"; do
|
for line in "${lines[@]}"; do
|
||||||
[ ${#line} -gt $maxlen ] && maxlen=${#line}
|
[ ${#line} -gt $maxlen ] && maxlen=${#line}
|
||||||
tooenc "${prefix}${COULEUR_BLEUE}T $line$COULEUR_NORMALE"
|
recho "${prefix}${COULEUR_BLEUE}T $line$COULEUR_NORMALE"
|
||||||
done
|
done
|
||||||
maxlen=$((maxlen + 2))
|
maxlen=$((maxlen + 2))
|
||||||
tooenc "${prefix}${COULEUR_BLEUE}T$(__complete "" $maxlen -)${COULEUR_NORMALE}"
|
recho "${prefix}${COULEUR_BLEUE}T$(__complete "" $maxlen -)${COULEUR_NORMALE}"
|
||||||
}
|
}
|
||||||
function __edesc() {
|
function __edesc() {
|
||||||
local -a lines
|
local -a lines
|
||||||
@ -34,7 +34,7 @@ function __edesc() {
|
|||||||
|
|
||||||
setx -a lines=echo "$1"
|
setx -a lines=echo "$1"
|
||||||
for line in "${lines[@]}"; do
|
for line in "${lines[@]}"; do
|
||||||
tooenc "${prefix}${COULEUR_BLEUE}>${COULEUR_NORMALE} $line"
|
recho "${prefix}${COULEUR_BLEUE}>${COULEUR_NORMALE} $line"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
function __ebanner() {
|
function __ebanner() {
|
||||||
@ -43,35 +43,35 @@ function __ebanner() {
|
|||||||
local length="${COLUMNS:-80}"
|
local length="${COLUMNS:-80}"
|
||||||
setx lsep=__complete "$prefix" "$length" =
|
setx lsep=__complete "$prefix" "$length" =
|
||||||
|
|
||||||
tooenc "$COULEUR_ROUGE$lsep"
|
recho "$COULEUR_ROUGE$lsep"
|
||||||
length=$((length - 1))
|
length=$((length - 1))
|
||||||
setx -a lines=echo "$1"
|
setx -a lines=echo "$1"
|
||||||
for line in "" "${lines[@]}" ""; do
|
for line in "" "${lines[@]}" ""; do
|
||||||
setx line=__complete "$prefix= $line" "$length"
|
setx line=__complete "$prefix= $line" "$length"
|
||||||
tooenc "$line="
|
recho "$line="
|
||||||
done
|
done
|
||||||
tooenc "$lsep$COULEUR_NORMALE"
|
recho "$lsep$COULEUR_NORMALE"
|
||||||
}
|
}
|
||||||
function __eimportant() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}!${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __eimportant() { recho "$(__edate)$(__eindent0)${COULEUR_ROUGE}!${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __eattention() { tooenc "$(__edate)$(__eindent0)${COULEUR_JAUNE}*${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __eattention() { recho "$(__edate)$(__eindent0)${COULEUR_JAUNE}*${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __eerror() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}E${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __eerror() { recho "$(__edate)$(__eindent0)${COULEUR_ROUGE}E${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __ewarn() { tooenc "$(__edate)$(__eindent0)${COULEUR_JAUNE}W${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __ewarn() { recho "$(__edate)$(__eindent0)${COULEUR_JAUNE}W${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __enote() { tooenc "$(__edate)$(__eindent0)${COULEUR_VERTE}N${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __enote() { recho "$(__edate)$(__eindent0)${COULEUR_VERTE}N${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __einfo() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLEUE}I${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __einfo() { recho "$(__edate)$(__eindent0)${COULEUR_BLEUE}I${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __edebug() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLANCHE}D${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 __estep() { recho "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __estepe() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __estepe() { recho "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __estepw() { tooenc "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __estepw() { recho "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __estepn() { tooenc "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __estepn() { recho "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __estepi() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLEUE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __estepi() { recho "$(__edate)$(__eindent0)${COULEUR_BLEUE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __estep_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __estep_() { recho_ "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __estepe_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __estepe_() { recho_ "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __estepw_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __estepw_() { recho_ "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __estepn_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __estepn_() { recho_ "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __estepi_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_BLEUE}.${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 __action() { recho "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __asuccess() { tooenc "$(__edate)$(__eindent0)${COULEUR_VERTE}✔${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __asuccess() { recho "$(__edate)$(__eindent0)${COULEUR_VERTE}✔${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __afailure() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}✘${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
function __afailure() { recho "$(__edate)$(__eindent0)${COULEUR_ROUGE}✘${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
|
||||||
function __adone() { tooenc "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
function __adone() { recho "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||||
|
@ -6,23 +6,23 @@ function __esection() {
|
|||||||
local length="${COLUMNS:-80}"
|
local length="${COLUMNS:-80}"
|
||||||
setx lsep=__complete "$prefix" "$length" -
|
setx lsep=__complete "$prefix" "$length" -
|
||||||
|
|
||||||
tooenc "$lsep"
|
recho "$lsep"
|
||||||
[ -n "$*" ] || return 0
|
[ -n "$*" ] || return 0
|
||||||
length=$((length - 1))
|
length=$((length - 1))
|
||||||
setx -a lines=echo "$1"
|
setx -a lines=echo "$1"
|
||||||
for line in "${lines[@]}"; do
|
for line in "${lines[@]}"; do
|
||||||
setx line=__complete "$prefix- $line" "$length"
|
setx line=__complete "$prefix- $line" "$length"
|
||||||
tooenc "$line-"
|
recho "$line-"
|
||||||
done
|
done
|
||||||
tooenc "$lsep"
|
recho "$lsep"
|
||||||
}
|
}
|
||||||
function __etitle() {
|
function __etitle() {
|
||||||
local p="TITLE: " i=" "
|
local p="TITLE: " i=" "
|
||||||
tooenc "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
|
recho "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
|
||||||
}
|
}
|
||||||
function __edesc() {
|
function __edesc() {
|
||||||
local p="DESC: " i=" "
|
local p="DESC: " i=" "
|
||||||
tooenc "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
|
recho "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
|
||||||
}
|
}
|
||||||
function __ebanner() {
|
function __ebanner() {
|
||||||
local -a lines
|
local -a lines
|
||||||
@ -30,37 +30,37 @@ function __ebanner() {
|
|||||||
local length="${COLUMNS:-80}"
|
local length="${COLUMNS:-80}"
|
||||||
setx lsep=__complete "$prefix" "$length" =
|
setx lsep=__complete "$prefix" "$length" =
|
||||||
|
|
||||||
tooenc "$lsep"
|
recho "$lsep"
|
||||||
length=$((length - 1))
|
length=$((length - 1))
|
||||||
setx -a lines=echo "$1"
|
setx -a lines=echo "$1"
|
||||||
for line in "" "${lines[@]}" ""; do
|
for line in "" "${lines[@]}" ""; do
|
||||||
setx line=__complete "$prefix= $line" "$length"
|
setx line=__complete "$prefix= $line" "$length"
|
||||||
tooenc "$line="
|
recho "$line="
|
||||||
done
|
done
|
||||||
tooenc "$lsep"
|
recho "$lsep"
|
||||||
}
|
}
|
||||||
function __eimportant() { tooenc "$(__edate)$(__eindent0)IMPORTANT! $(__eindent "$1" " ")"; }
|
function __eimportant() { recho "$(__edate)$(__eindent0)IMPORTANT! $(__eindent "$1" " ")"; }
|
||||||
function __eattention() { tooenc "$(__edate)$(__eindent0)ATTENTION! $(__eindent "$1" " ")"; }
|
function __eattention() { recho "$(__edate)$(__eindent0)ATTENTION! $(__eindent "$1" " ")"; }
|
||||||
function __eerror() { tooenc "$(__edate)$(__eindent0)ERROR: $(__eindent "$1" " ")"; }
|
function __eerror() { recho "$(__edate)$(__eindent0)ERROR: $(__eindent "$1" " ")"; }
|
||||||
function __ewarn() { tooenc "$(__edate)$(__eindent0)WARNING: $(__eindent "$1" " ")"; }
|
function __ewarn() { recho "$(__edate)$(__eindent0)WARNING: $(__eindent "$1" " ")"; }
|
||||||
function __enote() { tooenc "$(__edate)$(__eindent0)NOTE: $(__eindent "$1" " ")"; }
|
function __enote() { recho "$(__edate)$(__eindent0)NOTE: $(__eindent "$1" " ")"; }
|
||||||
function __einfo() { tooenc "$(__edate)$(__eindent0)INFO: $(__eindent "$1" " ")"; }
|
function __einfo() { recho "$(__edate)$(__eindent0)INFO: $(__eindent "$1" " ")"; }
|
||||||
function __edebug() { tooenc "$(__edate)$(__eindent0)DEBUG: $(__eindent "$1" " ")"; }
|
function __edebug() { recho "$(__edate)$(__eindent0)DEBUG: $(__eindent "$1" " ")"; }
|
||||||
function __eecho() { tooenc "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
function __eecho() { recho "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||||
function __eecho_() { tooenc_ "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
function __eecho_() { recho_ "$(__edate)$(__eindent0)$(__eindent "$1")"; }
|
||||||
|
|
||||||
function __estep() { tooenc "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
|
function __estep() { recho "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
|
||||||
function __estepe() { tooenc "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
|
function __estepe() { recho "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
|
||||||
function __estepw() { tooenc "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
|
function __estepw() { recho "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
|
||||||
function __estepn() { tooenc "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
|
function __estepn() { recho "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
|
||||||
function __estepi() { tooenc "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
|
function __estepi() { recho "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
|
||||||
function __estep_() { tooenc_ "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
|
function __estep_() { recho_ "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
|
||||||
function __estepe_() { tooenc_ "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
|
function __estepe_() { recho_ "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
|
||||||
function __estepw_() { tooenc_ "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
|
function __estepw_() { recho_ "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
|
||||||
function __estepn_() { tooenc_ "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
|
function __estepn_() { recho_ "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
|
||||||
function __estepi_() { tooenc_ "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
|
function __estepi_() { recho_ "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
|
||||||
|
|
||||||
function __action() { tooenc "$(__edate)$(__eindent0)ACTION: $(__eindent "$1" " ")"; }
|
function __action() { recho "$(__edate)$(__eindent0)ACTION: $(__eindent "$1" " ")"; }
|
||||||
function __asuccess() { tooenc "$(__edate)$(__eindent0)(OK) $(__eindent "$1" " ")"; }
|
function __asuccess() { recho "$(__edate)$(__eindent0)(OK) $(__eindent "$1" " ")"; }
|
||||||
function __afailure() { tooenc "$(__edate)$(__eindent0)(KO) $(__eindent "$1" " ")"; }
|
function __afailure() { recho "$(__edate)$(__eindent0)(KO) $(__eindent "$1" " ")"; }
|
||||||
function __adone() { tooenc "$(__edate)$(__eindent0)$(__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."
|
commence par ++, c'est une option avancée qui n'est pas affichée par défaut."
|
||||||
function parse_args() {
|
function parse_args() {
|
||||||
eval "$NULIB__DISABLE_SET_X"
|
eval "$NULIB__DISABLE_SET_X"
|
||||||
|
[ -n "$NULIB_ARGS_ONERROR_RETURN" ] && set_die_return
|
||||||
local __r=
|
local __r=
|
||||||
local __DIE='[ -n "$NULIB_ARGS_ONERROR_RETURN" ] && return 1 || die'
|
|
||||||
|
|
||||||
if ! is_array args; then
|
if ! is_array args; then
|
||||||
eerror "Invalid args definition: args must be defined"
|
eerror "Invalid args definition: args must be defined"
|
||||||
@ -145,17 +145,33 @@ function parse_args() {
|
|||||||
__r=1
|
__r=1
|
||||||
else
|
else
|
||||||
__DEFS=("$@")
|
__DEFS=("$@")
|
||||||
__parse_args || __r=1
|
__nulib_args_parse_args || __r=1
|
||||||
fi
|
fi
|
||||||
eval "$NULIB__ENABLE_SET_X"
|
eval "$NULIB__ENABLE_SET_X"
|
||||||
if [ -n "$__r" ]; then
|
if [ -n "$__r" ]; then
|
||||||
eval "$__DIE"
|
die || return
|
||||||
fi
|
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
|
## tout d'abord, construire la liste des options
|
||||||
local __AUTOH=1 __AUTOHELP=1 # faut-il gérer automatiquement l'affichage de l'aide?
|
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 __ADVHELP # y a-t-il des options avancées?
|
||||||
local __popt __sopts __lopts
|
local __popt __sopts __lopts
|
||||||
local -a __defs
|
local -a __defs
|
||||||
@ -165,10 +181,10 @@ function __parse_args() {
|
|||||||
+) __popt="$1"; shift; continue;;
|
+) __popt="$1"; shift; continue;;
|
||||||
-) __popt="$1"; shift; continue;;
|
-) __popt="$1"; shift; continue;;
|
||||||
-*) IFS=, read -a __defs <<<"$1"; shift;;
|
-*) 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
|
esac
|
||||||
# est-ce que l'option prend un argument?
|
# est-ce que l'option prend un argument?
|
||||||
local __def __witharg __valdesc
|
local __def __longdef __witharg __valdesc
|
||||||
__witharg=
|
__witharg=
|
||||||
for __def in "${__defs[@]}"; do
|
for __def in "${__defs[@]}"; do
|
||||||
if [ "${__def%::*}" != "$__def" ]; then
|
if [ "${__def%::*}" != "$__def" ]; then
|
||||||
@ -180,23 +196,36 @@ function __parse_args() {
|
|||||||
# définitions __sopts et __lopts
|
# définitions __sopts et __lopts
|
||||||
for __def in "${__defs[@]}"; do
|
for __def in "${__defs[@]}"; do
|
||||||
__def="${__def%%:*}"
|
__def="${__def%%:*}"
|
||||||
|
__longdef=
|
||||||
if [[ "$__def" == --* ]]; then
|
if [[ "$__def" == --* ]]; then
|
||||||
# --longopt
|
# --longopt
|
||||||
__def="${__def#--}"
|
__def="${__def#--}"
|
||||||
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
|
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
|
||||||
|
__longdef=1
|
||||||
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
|
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
|
||||||
# -o
|
# -o
|
||||||
__def="${__def#-}"
|
__def="${__def#-}"
|
||||||
__sopts="$__sopts$__def$__witharg"
|
__sopts="$__sopts$__def$__witharg"
|
||||||
[ "$__def" == h ] && __AUTOH=
|
case "$__def" in
|
||||||
[ "$__def" == D ] && __AUTOD=
|
h) __AUTOH=;;
|
||||||
|
L) __NOAUTOL+=("-$__def");;
|
||||||
|
Q|q|v|D) __NOAUTOV+=("-$__def");;
|
||||||
|
b|y|i) __NOAUTOI+=("-$__def");;
|
||||||
|
esac
|
||||||
else
|
else
|
||||||
# -longopt ou longopt
|
# -longopt ou longopt
|
||||||
__def="${__def#-}"
|
__def="${__def#-}"
|
||||||
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
|
__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
|
fi
|
||||||
[ "$__def" == help -o "$__def" == help++ ] && __AUTOHELP=
|
|
||||||
[ "$__def" == debug ] && __AUTODEBUG=
|
|
||||||
done
|
done
|
||||||
# sauter l'action
|
# sauter l'action
|
||||||
shift
|
shift
|
||||||
@ -209,8 +238,12 @@ function __parse_args() {
|
|||||||
|
|
||||||
[ -n "$__AUTOH" ] && __sopts="${__sopts}h"
|
[ -n "$__AUTOH" ] && __sopts="${__sopts}h"
|
||||||
[ -n "$__AUTOHELP" ] && __lopts="$__lopts${__lopts:+,}help,help++"
|
[ -n "$__AUTOHELP" ] && __lopts="$__lopts${__lopts:+,}help,help++"
|
||||||
[ -n "$__AUTOD" ] && __sopts="${__sopts}D"
|
__nulib_args_add_sopt __NOAUTOL L
|
||||||
[ -n "$__AUTODEBUG" ] && __lopts="$__lopts${__lopts:+,}debug"
|
__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"
|
__sopts="$__popt$__sopts"
|
||||||
local -a __getopt_args
|
local -a __getopt_args
|
||||||
@ -222,7 +255,7 @@ function __parse_args() {
|
|||||||
else
|
else
|
||||||
# relancer pour avoir le message d'erreur
|
# relancer pour avoir le message d'erreur
|
||||||
LANG=C getopt "${__getopt_args[@]}" 2>&1 1>/dev/null
|
LANG=C getopt "${__getopt_args[@]}" 2>&1 1>/dev/null
|
||||||
eval "$__DIE"
|
die || return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## puis traiter les options
|
## puis traiter les options
|
||||||
@ -373,7 +406,7 @@ $prefix$usage"
|
|||||||
fi
|
fi
|
||||||
[[ "$1" == -* ]] || break
|
[[ "$1" == -* ]] || break
|
||||||
option_="$1"; shift
|
option_="$1"; shift
|
||||||
__parse_opt "$option_"
|
__nulib_args_parse_opt "$option_"
|
||||||
if [ -n "$__witharg" ]; then
|
if [ -n "$__witharg" ]; then
|
||||||
# l'option prend un argument
|
# l'option prend un argument
|
||||||
value_="$1"; shift
|
value_="$1"; shift
|
||||||
@ -387,7 +420,7 @@ $prefix$usage"
|
|||||||
unset -f inc@ res@ add@ set@ showhelp@
|
unset -f inc@ res@ add@ set@ showhelp@
|
||||||
args=("$@")
|
args=("$@")
|
||||||
}
|
}
|
||||||
function __parse_opt() {
|
function __nulib_args_parse_opt() {
|
||||||
# $1 est l'option spécifiée
|
# $1 est l'option spécifiée
|
||||||
local option_="$1"
|
local option_="$1"
|
||||||
set -- "${__DEFS[@]}"
|
set -- "${__DEFS[@]}"
|
||||||
@ -460,27 +493,62 @@ function __parse_opt() {
|
|||||||
|
|
||||||
[ -n "$__found" ] && return 0
|
[ -n "$__found" ] && return 0
|
||||||
done
|
done
|
||||||
if [ -n "$__AUTOH" -a "$option_" == -h ]; then
|
case "$option_" in
|
||||||
__action="showhelp@"
|
-h)
|
||||||
|
if [ -n "$__AUTOH" ]; then
|
||||||
|
__action='showhelp@'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
;;
|
||||||
|
--help)
|
||||||
if [ -n "$__AUTOHELP" ]; then
|
if [ -n "$__AUTOHELP" ]; then
|
||||||
if [ "$option_" == --help ]; then
|
__action='showhelp@'
|
||||||
__action="showhelp@"
|
|
||||||
return 0
|
|
||||||
elif [ "$option_" == --help++ ]; then
|
|
||||||
__action="showhelp@ ++"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
;;
|
||||||
if [ -n "$__AUTOD" -a "$option_" == -D ]; then
|
--help++)
|
||||||
__action=set_debug
|
if [ -n "$__AUTOHELP" ]; then
|
||||||
|
__action='showhelp@ ++'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if [ -n "$__AUTODEBUG" -a "$option_" == --debug ]; then
|
;;
|
||||||
__action=set_debug
|
-L)
|
||||||
|
if ! array_contains __NOAUTOL "$option_"; then
|
||||||
|
__action='elogto $value_'
|
||||||
return 0
|
return 0
|
||||||
fi
|
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
|
# 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
|
else
|
||||||
__eecho_ "Voulez-vous continuer?" 1>&2
|
__eecho_ "Voulez-vous continuer?" 1>&2
|
||||||
fi
|
fi
|
||||||
tooenc_ " $prompt " 1>&2
|
echo_ " $prompt " 1>&2
|
||||||
uread r
|
uread r
|
||||||
is_yes "${r:-$default}"
|
is_yes "${r:-$default}"
|
||||||
else
|
else
|
||||||
@ -198,17 +198,17 @@ function __rv_read() {
|
|||||||
__eecho_ "Entrez la valeur" 1>&2
|
__eecho_ "Entrez la valeur" 1>&2
|
||||||
fi
|
fi
|
||||||
if [ -n "$__rv_readline" ]; then
|
if [ -n "$__rv_readline" ]; then
|
||||||
tooenc_ ": " 1>&2
|
echo_ ": " 1>&2
|
||||||
uread -e ${__rv_d:+-i"$__rv_d"} "${__rv_opts[@]}" __rv_r
|
uread -e ${__rv_d:+-i"$__rv_d"} "${__rv_opts[@]}" __rv_r
|
||||||
else
|
else
|
||||||
if [ -n "$__rv_d" ]; then
|
if [ -n "$__rv_d" ]; then
|
||||||
if [ -n "$__rv_showdef" ]; then
|
if [ -n "$__rv_showdef" ]; then
|
||||||
tooenc_ " [$__rv_d]" 1>&2
|
echo_ " [$__rv_d]" 1>&2
|
||||||
else
|
else
|
||||||
tooenc_ " [****]" 1>&2
|
echo_ " [****]" 1>&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
tooenc_ ": " 1>&2
|
echo_ ": " 1>&2
|
||||||
uread "${__rv_opts[@]}" __rv_r
|
uread "${__rv_opts[@]}" __rv_r
|
||||||
[ -n "$__rv_nl" ] && echo
|
[ -n "$__rv_nl" ] && echo
|
||||||
fi
|
fi
|
||||||
@ -217,6 +217,7 @@ function __rv_read() {
|
|||||||
_setv "$__rv_v" "$__rv_r"
|
_setv "$__rv_v" "$__rv_r"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
echo
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +269,7 @@ function simple_menu() {
|
|||||||
else
|
else
|
||||||
__eecho_ "Entrez le numéro de l'option choisie" 1>&2
|
__eecho_ "Entrez le numéro de l'option choisie" 1>&2
|
||||||
fi
|
fi
|
||||||
tooenc_ ": " 1>&2
|
echo_ ": " 1>&2
|
||||||
uread __sm_choice
|
uread __sm_choice
|
||||||
|
|
||||||
# Valeur par défaut
|
# Valeur par défaut
|
||||||
@ -291,7 +292,7 @@ function simple_menu() {
|
|||||||
let __sm_c=$__sm_c+1
|
let __sm_c=$__sm_c+1
|
||||||
if [ "$__sm_c" -eq 5 ]; then
|
if [ "$__sm_c" -eq 5 ]; then
|
||||||
# sauter une ligne toutes les 4 tentatives
|
# sauter une ligne toutes les 4 tentatives
|
||||||
tooenc "" 1>&2
|
echo 1>&2
|
||||||
__sm_c=0
|
__sm_c=0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -438,7 +439,7 @@ function __void_actions_menu() {
|
|||||||
if [ $c -eq 0 ]; then
|
if [ $c -eq 0 ]; then
|
||||||
[ -n "$title" ] && __etitle "$title" 1>&2
|
[ -n "$title" ] && __etitle "$title" 1>&2
|
||||||
__eecho_ "=== Actions disponibles: " 1>&2
|
__eecho_ "=== Actions disponibles: " 1>&2
|
||||||
tooenc "$action_title" 1>&2
|
recho "$action_title" 1>&2
|
||||||
fi
|
fi
|
||||||
if [ -n "$actyc" ]; then
|
if [ -n "$actyc" ]; then
|
||||||
__eecho_ "$actyc" 1>&2
|
__eecho_ "$actyc" 1>&2
|
||||||
@ -447,7 +448,7 @@ function __void_actions_menu() {
|
|||||||
else
|
else
|
||||||
__eecho_ "Entrez l'action à effectuer" 1>&2
|
__eecho_ "Entrez l'action à effectuer" 1>&2
|
||||||
fi
|
fi
|
||||||
tooenc_ ": " 1>&2
|
echo_ ": " 1>&2
|
||||||
uread choice
|
uread choice
|
||||||
if [ -z "$choice" -a -n "$default_action" ]; then
|
if [ -z "$choice" -a -n "$default_action" ]; then
|
||||||
select_action="$default_action"
|
select_action="$default_action"
|
||||||
@ -468,7 +469,7 @@ function __void_actions_menu() {
|
|||||||
let c=$c+1
|
let c=$c+1
|
||||||
if [ $c -eq 5 ]; then
|
if [ $c -eq 5 ]; then
|
||||||
# sauter une ligne toutes les 4 tentatives
|
# sauter une ligne toutes les 4 tentatives
|
||||||
tooenc "" 1>&2
|
echo 1>&2
|
||||||
c=0
|
c=0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -484,21 +485,21 @@ function __options_actions_menu() {
|
|||||||
i=1
|
i=1
|
||||||
for option in "${options[@]}"; do
|
for option in "${options[@]}"; do
|
||||||
if [ "$option" == "$select_option" ]; then
|
if [ "$option" == "$select_option" ]; then
|
||||||
tooenc "$i*- $option" 1>&2
|
echo "$i*- $option" 1>&2
|
||||||
else
|
else
|
||||||
tooenc "$i - $option" 1>&2
|
echo "$i - $option" 1>&2
|
||||||
fi
|
fi
|
||||||
let i=$i+1
|
let i=$i+1
|
||||||
done
|
done
|
||||||
__estepn_ "Actions disponibles: " 1>&2
|
__estepn_ "Actions disponibles: " 1>&2
|
||||||
tooenc "$action_title" 1>&2
|
recho "$action_title" 1>&2
|
||||||
fi
|
fi
|
||||||
if [ -n "$optyc" ]; then
|
if [ -n "$optyc" ]; then
|
||||||
__eecho_ "$optyc" 1>&2
|
__eecho_ "$optyc" 1>&2
|
||||||
else
|
else
|
||||||
__eecho_ "Entrez l'action et le numéro de l'option choisie" 1>&2
|
__eecho_ "Entrez l'action et le numéro de l'option choisie" 1>&2
|
||||||
fi
|
fi
|
||||||
tooenc_ ": " 1>&2
|
echo_ ": " 1>&2
|
||||||
uread choice
|
uread choice
|
||||||
|
|
||||||
# vérifier la saisie
|
# vérifier la saisie
|
||||||
@ -572,7 +573,7 @@ function __options_actions_menu() {
|
|||||||
let c=$c+1
|
let c=$c+1
|
||||||
if [ $c -eq 5 ]; then
|
if [ $c -eq 5 ]; then
|
||||||
# sauter une ligne toutes les 4 tentatives
|
# sauter une ligne toutes les 4 tentatives
|
||||||
tooenc "" 1>&2
|
echo "" 1>&2
|
||||||
c=0
|
c=0
|
||||||
fi
|
fi
|
||||||
done
|
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
|
# $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
|
# qui vaut par défaut $NULIB_OUTPUT_ENCODING
|
||||||
local value="$1" to="${2:-$NULIB_OUTPUT_ENCODING}"
|
local value="$1" to="${2:-$NULIB_OUTPUT_ENCODING}"
|
||||||
@ -93,9 +93,8 @@ function tooenc() {
|
|||||||
iconv -f -utf-8 -t "$to" <<<"$value"
|
iconv -f -utf-8 -t "$to" <<<"$value"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
function uecho() { tooenc "$*"; }
|
|
||||||
|
|
||||||
function tooenc_() {
|
function uecho_() {
|
||||||
# $1 étant une chaine encodée en utf-8, l'afficher sans passer à la ligne dans
|
# $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
|
# l'encoding de sortie $2 qui vaut par défaut $NULIB_OUTPUT_ENCODING
|
||||||
local value="$1" to="${2:-$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"
|
recho_ "$value" | iconv -f utf-8 -t "$to"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
function uecho_() { tooenc_ "$*"; }
|
|
||||||
|
|
||||||
export NULIB_QUIETLOG
|
export NULIB_QUIETLOG
|
||||||
export NULIB__TMPLOG
|
export NULIB__TMPLOG
|
||||||
@ -210,7 +208,7 @@ function __eindent() {
|
|||||||
# indenter les lignes de $1, sauf la première
|
# indenter les lignes de $1, sauf la première
|
||||||
local -a lines; local line first=1
|
local -a lines; local line first=1
|
||||||
local indent="$(__eindent0)$2"
|
local indent="$(__eindent0)$2"
|
||||||
setx -a lines=echo "$1"
|
setx -a lines=recho "$1"
|
||||||
for line in "${lines[@]}"; do
|
for line in "${lines[@]}"; do
|
||||||
if [ -n "$first" ]; then
|
if [ -n "$first" ]; then
|
||||||
recho "$line"
|
recho "$line"
|
||||||
@ -232,7 +230,11 @@ function __complete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PRETTYOPTS=()
|
PRETTYOPTS=()
|
||||||
function set_verbosity() { :;}
|
function set_verbosity() {
|
||||||
|
case "$1" in
|
||||||
|
-D|--debug) NULIB_DEBUG=1;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
function check_verbosity() { return 0; }
|
function check_verbosity() { return 0; }
|
||||||
function get_verbosity_option() { :;}
|
function get_verbosity_option() { :;}
|
||||||
|
|
||||||
|
@ -2,20 +2,13 @@
|
|||||||
|
|
||||||
## configuration par défaut
|
## configuration par défaut
|
||||||
|
|
||||||
# branche upstream
|
|
||||||
UPSTREAM=
|
UPSTREAM=
|
||||||
# branches de développement
|
|
||||||
DEVELOP=develop
|
DEVELOP=develop
|
||||||
FEATURE=wip/
|
FEATURE=wip/
|
||||||
# branche de préparation de release
|
|
||||||
RELEASE=release-
|
RELEASE=release-
|
||||||
# branche de release
|
|
||||||
MAIN=master
|
MAIN=master
|
||||||
TAG_PREFIX=
|
TAG_PREFIX=
|
||||||
TAG_SUFFIX=
|
TAG_SUFFIX=
|
||||||
# branche de hotfix
|
|
||||||
HOTFIX=hotfix-
|
HOTFIX=hotfix-
|
||||||
# branche de distribution
|
|
||||||
DIST=
|
DIST=
|
||||||
# désactiver les releases automatiques?
|
|
||||||
NOAUTO=
|
NOAUTO=
|
||||||
|
@ -5,15 +5,23 @@
|
|||||||
# les branches sont mergées dans cet ordre:
|
# les branches sont mergées dans cet ordre:
|
||||||
# upstream --> develop --> [release -->] main --> dist
|
# upstream --> develop --> [release -->] main --> dist
|
||||||
# feature _/ hotfix _/
|
# feature _/ hotfix _/
|
||||||
|
|
||||||
|
# branche upstream
|
||||||
UPSTREAM=
|
UPSTREAM=
|
||||||
|
# branches de développement
|
||||||
DEVELOP=develop
|
DEVELOP=develop
|
||||||
FEATURE=wip/
|
FEATURE=wip/
|
||||||
|
# branche de préparation de release
|
||||||
RELEASE=release-
|
RELEASE=release-
|
||||||
|
# branche de release
|
||||||
MAIN=master
|
MAIN=master
|
||||||
TAG_PREFIX=
|
TAG_PREFIX=
|
||||||
TAG_SUFFIX=
|
TAG_SUFFIX=
|
||||||
|
# branche de hotfix
|
||||||
HOTFIX=hotfix-
|
HOTFIX=hotfix-
|
||||||
|
# branche de distribution
|
||||||
DIST=
|
DIST=
|
||||||
|
# désactiver les releases automatiques?
|
||||||
NOAUTO=
|
NOAUTO=
|
||||||
|
|
||||||
CONFIG_VARS=(
|
CONFIG_VARS=(
|
||||||
@ -45,7 +53,7 @@ $0 !~ /<pman>/ { print }
|
|||||||
function _filter_changes() {
|
function _filter_changes() {
|
||||||
# enlever les commits "inutiles" pour générer le fichier CHANGES.md
|
# 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]+ 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() {
|
function _format_md() {
|
||||||
@ -151,6 +159,7 @@ function load_branches() {
|
|||||||
local what="${1:-all}"; shift
|
local what="${1:-all}"; shift
|
||||||
case "$what" in
|
case "$what" in
|
||||||
all)
|
all)
|
||||||
|
[ -n "$Origin" ] || Origin=origin
|
||||||
setx CurrentBranch=git_get_branch
|
setx CurrentBranch=git_get_branch
|
||||||
setx -a LocalBranches=git_list_branches
|
setx -a LocalBranches=git_list_branches
|
||||||
setx -a RemoteBranches=git_list_rbranches "$Origin"
|
setx -a RemoteBranches=git_list_rbranches "$Origin"
|
||||||
@ -167,7 +176,17 @@ function load_branches() {
|
|||||||
"$HOTFIX"*) SrcType=hotfix; DestBranch="$MAIN";;
|
"$HOTFIX"*) SrcType=hotfix; DestBranch="$MAIN";;
|
||||||
"$MAIN") SrcType=main; DestBranch="$DIST";;
|
"$MAIN") SrcType=main; DestBranch="$DIST";;
|
||||||
"$DIST") SrcType=dist; DestBranch=;;
|
"$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
|
esac
|
||||||
|
|
||||||
local branch
|
local branch
|
||||||
@ -177,12 +196,12 @@ function load_branches() {
|
|||||||
ReleaseBranch=
|
ReleaseBranch=
|
||||||
HotfixBranch=
|
HotfixBranch=
|
||||||
MainBranch=
|
MainBranch=
|
||||||
Dist=Branch
|
DistBranch=
|
||||||
for branch in "${AllBranches[@]}"; do
|
for branch in "${LocalBranches[@]}"; do
|
||||||
if [ "$branch" == "$UPSTREAM" ]; then
|
if [ "$branch" == "$UPSTREAM" ]; then
|
||||||
UpstreamBranch="$branch"
|
UpstreamBranch="$branch"
|
||||||
elif [[ "$branch" == "$FEATURE"* ]]; then
|
elif [[ "$branch" == "$FEATURE"* ]]; then
|
||||||
FeatureBranch+=("$branch")
|
FeatureBranches+=("$branch")
|
||||||
elif [ "$branch" == "$DEVELOP" ]; then
|
elif [ "$branch" == "$DEVELOP" ]; then
|
||||||
DevelopBranch="$branch"
|
DevelopBranch="$branch"
|
||||||
elif [[ "$branch" == "$RELEASE"* ]]; then
|
elif [[ "$branch" == "$RELEASE"* ]]; then
|
||||||
@ -200,31 +219,50 @@ function load_branches() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function load_config() {
|
function load_config() {
|
||||||
|
if [ "${ConfigFile#::}" != "$ConfigFile" ]; then
|
||||||
|
ConfigFile="$NULIBDIR/bash/src/pman${ConfigFile#::}.conf.sh"
|
||||||
|
fi
|
||||||
if [ -n "$ConfigFile" ]; then
|
if [ -n "$ConfigFile" ]; then
|
||||||
source "$ConfigFile" || die || return
|
source "$ConfigFile" || die || return
|
||||||
elif [ -n "$ConfigBranch" ]; then
|
elif [ -n "$ConfigBranch" ]; then
|
||||||
|
# c'est le seul cas où ConfigFile reste vide
|
||||||
if ! array_contains LocalBranches "$ConfigBranch"; then
|
if ! array_contains LocalBranches "$ConfigBranch"; then
|
||||||
die "$ConfigBranch: branche de configuration introuvable" || return
|
die "$ConfigBranch: branche de configuration introuvable" || return
|
||||||
else
|
else
|
||||||
ac_set_tmpfile ConfigFile
|
local config
|
||||||
git show "$ConfigBranch:.pman.conf" >"$ConfigFile" 2>/dev/null
|
ac_set_tmpfile config
|
||||||
[ -s "$ConfigFile" ] || die "$ConfigBranch: aucune configuration trouvée sur cette branche" || return
|
git show "$ConfigBranch:.pman.conf" >"$config" 2>/dev/null
|
||||||
source "$ConfigFile"
|
[ -s "$config" ] || die "$ConfigBranch: aucune configuration trouvée sur cette branche" || return
|
||||||
|
source "$config"
|
||||||
fi
|
fi
|
||||||
elif [ -f .pman.conf ]; then
|
elif [ -f .pman.conf ]; then
|
||||||
ConfigFile="$(pwd)/.pman.conf"
|
ConfigFile="$(pwd)/.pman.conf"
|
||||||
source "$ConfigFile"
|
source "$ConfigFile"
|
||||||
elif [ -n "${MYNAME#prel}" ]; then
|
elif [ -n "$1" -a -n "${MYNAME#$1}" ]; then
|
||||||
ConfigFile="$NULIBDIR/bash/src/pman${MYNAME#$1}.conf.sh"
|
ConfigFile="$NULIBDIR/bash/src/pman${MYNAME#$1}.conf.sh"
|
||||||
source "$ConfigFile"
|
source "$ConfigFile"
|
||||||
else
|
else
|
||||||
ConfigFile="$NULIBDIR/bash/src/pman.conf.sh"
|
ConfigFile="$NULIBDIR/bash/src/pman.conf.sh"
|
||||||
fi
|
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
|
# 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() {
|
function _push_branches() {
|
||||||
[ ${#push_branches[*]} -gt 0 ] || return
|
[ ${#push_branches[*]} -gt 0 ] || return
|
||||||
[ -n "$Origin" ] || Origin=origin
|
[ -n "$Origin" ] || Origin=origin
|
||||||
@ -275,6 +313,11 @@ function _mscript_start() {
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
$(qvals source "$NULIBDIR/load.sh") || exit 1
|
$(qvals source "$NULIBDIR/load.sh") || exit 1
|
||||||
|
|
||||||
|
$(echo_setv SrcBranch="$SrcBranch")
|
||||||
|
$(echo_setv SrcType="$SrcType")
|
||||||
|
$(echo_setv DestBranch="$DestBranch")
|
||||||
|
$(echo_setv DestType="$DestType")
|
||||||
|
|
||||||
merge=
|
merge=
|
||||||
delete=
|
delete=
|
||||||
push=
|
push=
|
||||||
@ -330,6 +373,14 @@ function _rscript_start() {
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
$(qvals source "$NULIBDIR/load.sh") || exit 1
|
$(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=
|
create=
|
||||||
merge=
|
merge=
|
||||||
push=
|
push=
|
||||||
@ -382,7 +433,7 @@ EOF
|
|||||||
|
|
||||||
# Enregistrer les changements
|
# Enregistrer les changements
|
||||||
_scripta "commit" <<EOF
|
_scripta "commit" <<EOF
|
||||||
$(qvals git commit -m "<pman>Init changelog & version $Version")
|
$(qvals git commit -m "<pman>Init changelog & version $Tag")
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,11 +108,17 @@ function set_interaction() {
|
|||||||
# set_interaction en fonction des arguments de la ligne de commande. A utiliser
|
# set_interaction en fonction des arguments de la ligne de commande. A utiliser
|
||||||
# de cette manière:
|
# de cette manière:
|
||||||
# parse_opts ... "${PRETTYOPTS[@]}" @ args -- ...
|
# 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é"
|
-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"
|
-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"
|
-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_error() { [ "$__verbosity" -ge 1 ]; }
|
||||||
function show_warn() { [ "$__verbosity" -ge 2 ]; }
|
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'
|
Si \$2 n'est pas spécifié, on assume que \$1 est de la forme '.file.ext'
|
||||||
et \$2 vaudra alors 'file'
|
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"
|
Ajouter file au tableau userfiles"
|
||||||
function template_copy_replace() {
|
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'
|
Si \$2 n'est pas spécifié, on assume que \$1 est de la forme '.file.ext'
|
||||||
et \$2 vaudra alors 'file'
|
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"
|
Ajouter file au tableau userfiles"
|
||||||
function template_copy_missing() {
|
function template_copy_missing() {
|
||||||
@ -205,6 +207,18 @@ function template_generate_scripts() {
|
|||||||
#etitle "sedscript" cat "$sedscript"
|
#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() {
|
function template_process_userfiles() {
|
||||||
local awkscript sedscript workfile userfile
|
local awkscript sedscript workfile userfile
|
||||||
ac_set_tmpfile awkscript
|
ac_set_tmpfile awkscript
|
||||||
@ -213,6 +227,7 @@ function template_process_userfiles() {
|
|||||||
|
|
||||||
ac_set_tmpfile workfile
|
ac_set_tmpfile workfile
|
||||||
for userfile in "${userfiles[@]}"; do
|
for userfile in "${userfiles[@]}"; do
|
||||||
|
_template_can_process "$userfile" || continue
|
||||||
if cat "$userfile" | awk -f "$awkscript" | sed -rf "$sedscript" >"$workfile"; then
|
if cat "$userfile" | awk -f "$awkscript" | sed -rf "$sedscript" >"$workfile"; then
|
||||||
if testdiff "$workfile" "$userfile"; then
|
if testdiff "$workfile" "$userfile"; then
|
||||||
# n'écrire le fichier que s'il a changé
|
# 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=
|
Banner=
|
||||||
args=(
|
args=(
|
||||||
"afficher divers messages avec les fonctions e*"
|
"afficher divers messages avec les fonctions e*"
|
||||||
-D,--debug '$set_debug'
|
|
||||||
-d,--date NULIB_ELOG_DATE=1
|
-d,--date NULIB_ELOG_DATE=1
|
||||||
-m,--myname NULIB_ELOG_MYNAME=1
|
-m,--myname NULIB_ELOG_MYNAME=1
|
||||||
-n,--nc,--no-color '$__set_no_colors 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
|
#!/bin/bash
|
||||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
# -*- 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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function git_statuses() {
|
||||||
|
local cwd="$(pwd)" dir
|
||||||
|
for dir in "$@"; do
|
||||||
|
cd "$dir" || die
|
||||||
|
git_status --porcelain
|
||||||
|
cd "$cwd"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
chdir=
|
chdir=
|
||||||
all=
|
all=
|
||||||
|
composer=
|
||||||
args=(
|
args=(
|
||||||
"afficher l'état du dépôt"
|
"afficher l'état du dépôt"
|
||||||
"[-d chdir] [-a patterns...]
|
"[-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"
|
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"
|
-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"
|
-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[@]}"
|
parse_args "$@"; set -- "${args[@]}"
|
||||||
|
|
||||||
@ -50,16 +61,23 @@ if [ -n "$all" ]; then
|
|||||||
dirs+=("${dir%/.git}")
|
dirs+=("${dir%/.git}")
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
setx cwd=pwd
|
git_statuses "${dirs[@]}"
|
||||||
for dir in "${dirs[@]}"; do
|
|
||||||
cd "$dir" || die
|
elif [ -n "$composer" ]; then
|
||||||
git_status --porcelain
|
# projets dépendants
|
||||||
cd "$cwd"
|
git_ensure_gitvcs
|
||||||
done
|
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
|
else
|
||||||
# répertoire courant uniquement
|
# répertoire courant uniquement
|
||||||
setx toplevel=git_get_toplevel
|
git_ensure_gitvcs
|
||||||
[ -n "$toplevel" ] && Cwd="$toplevel"
|
Cwd="$(git_get_toplevel)"
|
||||||
|
|
||||||
args=()
|
args=()
|
||||||
isatty || args+=(--porcelain)
|
isatty || args+=(--porcelain)
|
||||||
|
130
bin/pdev
130
bin/pdev
@ -25,14 +25,18 @@ function ensure_branches() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function merge_action() {
|
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 "\
|
enote "\
|
||||||
Ce script va
|
Ce script va
|
||||||
- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+
|
- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+
|
||||||
- pousser les branches modifiées}"
|
- pousser les branches modifiées}"
|
||||||
ask_yesno "Voulez-vous continuer?" O || die
|
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 -a push_branches delete_branches
|
||||||
|
local hook
|
||||||
local comment=
|
local comment=
|
||||||
local or_die=" || exit 1"
|
local or_die=" || exit 1"
|
||||||
|
|
||||||
@ -42,12 +46,23 @@ Ce script va
|
|||||||
# merge
|
# merge
|
||||||
if [ -n "\$merge" ]; then
|
if [ -n "\$merge" ]; then
|
||||||
esection "Fusionner la branche"
|
esection "Fusionner la branche"
|
||||||
|
EOF
|
||||||
|
hook="BEFORE_MERGE_${SrcType^^}"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
${!hook}
|
||||||
|
)$or_die
|
||||||
EOF
|
EOF
|
||||||
_mscript_merge_branch
|
_mscript_merge_branch
|
||||||
|
hook="AFTER_MERGE_${SrcType^^}"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
${!hook}
|
||||||
|
)$or_die
|
||||||
|
EOF
|
||||||
_scripta <<EOF
|
_scripta <<EOF
|
||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$ShouldDelete" ]; then
|
||||||
_scripta <<EOF
|
_scripta <<EOF
|
||||||
################################################################################
|
################################################################################
|
||||||
# delete
|
# delete
|
||||||
@ -55,15 +70,26 @@ if [ -n "\$delete" ]; then
|
|||||||
esection "Supprimer la branche"
|
esection "Supprimer la branche"
|
||||||
EOF
|
EOF
|
||||||
_mscript_delete_branch
|
_mscript_delete_branch
|
||||||
|
hook="AFTER_DELETE_${SrcType^^}"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
${!hook}
|
||||||
|
)$or_die
|
||||||
|
EOF
|
||||||
_scripta <<EOF
|
_scripta <<EOF
|
||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
_scripta <<EOF
|
_scripta <<EOF
|
||||||
################################################################################
|
################################################################################
|
||||||
# push
|
# push
|
||||||
if [ -n "\$push" ]; then
|
if [ -n "\$push" ]; then
|
||||||
esection "Pousser les branches"
|
esection "Pousser les branches"
|
||||||
|
EOF
|
||||||
|
hook="BEFORE_PUSH_${DestType^^}"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
${!hook}
|
||||||
|
)$or_die
|
||||||
EOF
|
EOF
|
||||||
_script_push_branches
|
_script_push_branches
|
||||||
if [ ${#delete_branches[*]} -gt 0 ]; then
|
if [ ${#delete_branches[*]} -gt 0 ]; then
|
||||||
@ -72,27 +98,42 @@ EOF
|
|||||||
_script_push_branches
|
_script_push_branches
|
||||||
_scripta <<<fi
|
_scripta <<<fi
|
||||||
fi
|
fi
|
||||||
|
hook="AFTER_PUSH_${DestType^^}"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
${!hook}
|
||||||
|
)$or_die
|
||||||
|
EOF
|
||||||
_scripta <<EOF
|
_scripta <<EOF
|
||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
[ -n "$Delete" -o "$ForbidDelete" ] && Deleted=1 || Deleted=
|
[ -n "$Delete" -o -z "$ShouldDelete" ] && Deleted=1 || Deleted=
|
||||||
[ -n "$Push" -o "$ForbidPush" ] && Pushed=1 || Pushed=
|
[ -n "$ShouldDelete" -a -n "$Delete" ] && ShouldDelete=
|
||||||
if [ -n "$_NoRunScript" ]; then
|
[ -n "$ShouldPush" -a -n "$Push" ] && ShouldPush=
|
||||||
einfo "Veuillez consulter le script $script pour le détail des opérations à effectuer"
|
if [ -n "$_Fake" ]; then
|
||||||
|
cat "$script"
|
||||||
elif ! "$script" merge ${Delete:+delete} ${Push:+push}; then
|
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
|
die
|
||||||
elif [ -n "$Deleted" -a -n "$Pushed" ]; then
|
elif [ -n "$Deleted" -a -n "$Push" ]; then
|
||||||
[ -n "$_KeepScript" ] || rm "$script"
|
[ -n "$_KeepScript" ] || rm "$script"
|
||||||
|
[ -n "$AfterMerge" ] && eval "$AfterMerge"
|
||||||
else
|
else
|
||||||
local cmd
|
local msg="\
|
||||||
[ -n "$Deleted" ] || cmd="$cmd
|
Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
|
||||||
./$script delete"
|
Vous pouvez consulter le script et/ou le relancer
|
||||||
[ -n "$Pushed" ] || cmd="$cmd
|
./$script${ShouldDelete:+ delete}${ShouldPush:+ push}"
|
||||||
./$script push"
|
[ -n "$AfterMerge" ] && msg="$msg
|
||||||
einfo "Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
|
Il y a aussi les commandes supplémentaires suivantes:
|
||||||
Veuillez le consulter pour le détail des autres opérations à effectuer$cmd"
|
${AfterMerge//
|
||||||
|
/
|
||||||
|
}"
|
||||||
|
einfo "$msg"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,16 +145,27 @@ chdir=
|
|||||||
Origin=
|
Origin=
|
||||||
ConfigBranch=
|
ConfigBranch=
|
||||||
ConfigFile=
|
ConfigFile=
|
||||||
|
_Fake=
|
||||||
_KeepScript=
|
_KeepScript=
|
||||||
_NoRunScript=
|
|
||||||
action=merge
|
action=merge
|
||||||
TechMerge=
|
TechMerge=
|
||||||
SquashMsg=
|
SquashMsg=
|
||||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||||
[ -z "$PMAN_NO_DELETE" ] && Delete=1 || Delete=
|
[ -z "$PMAN_NO_DELETE" ] && Delete=1 || Delete=
|
||||||
|
AfterMerge=
|
||||||
args=(
|
args=(
|
||||||
"fusionner la branche source dans la branche destination correspondante"
|
"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"
|
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
|
||||||
-O:,--origin Origin= "++\
|
-O:,--origin Origin= "++\
|
||||||
origine à partir de laquelle les branches distantes sont considérées"
|
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= "++\
|
-c:,--config-file:CONFIG ConfigFile= "++\
|
||||||
fichier de configuration des branches. cette option est prioritaire sur --config-branch
|
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"
|
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"
|
--keep-script _KeepScript=1 "++option non documentée"
|
||||||
--no-run-script _NoRunScript=1 "++option non documentée"
|
|
||||||
-w,--show action=show "\
|
-w,--show action=show "\
|
||||||
lister les modifications qui seraient fusionnées dans la branche destination"
|
lister les modifications qui seraient fusionnées dans la branche destination"
|
||||||
-b,--rebase action=rebase "\
|
-b,--rebase action=rebase "\
|
||||||
@ -146,6 +198,10 @@ ne pas supprimer la branche après la fusion dans la destination"
|
|||||||
--delete Delete=1 "++\
|
--delete Delete=1 "++\
|
||||||
supprimer la branche après la fusion dans la destination.
|
supprimer la branche après la fusion dans la destination.
|
||||||
c'est l'option par défaut"
|
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[@]}"
|
parse_args "$@"; set -- "${args[@]}"
|
||||||
|
|
||||||
@ -155,13 +211,7 @@ load_branches all
|
|||||||
load_config "$MYNAME"
|
load_config "$MYNAME"
|
||||||
load_branches current "$1"
|
load_branches current "$1"
|
||||||
|
|
||||||
ForbidPush=
|
resolve_should_push quiet
|
||||||
[ -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=
|
|
||||||
|
|
||||||
# puis faire l'action que l'on nous demande
|
# puis faire l'action que l'on nous demande
|
||||||
case "$action" in
|
case "$action" in
|
||||||
@ -171,24 +221,26 @@ show)
|
|||||||
show_action "$@"
|
show_action "$@"
|
||||||
;;
|
;;
|
||||||
merge)
|
merge)
|
||||||
ForbidDelete=
|
ShouldDelete=1
|
||||||
case "$SrcType" in
|
no_merge_msg="$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel"
|
||||||
develop|release|hotfix)
|
if [ "$SrcType" == develop ]; then
|
||||||
die "$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel"
|
[ -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
|
# n'autoriser la suppression que pour feature
|
||||||
[ "$SrcType" == feature ] || ForbidDelete=1
|
[ "$SrcType" == feature ] || ShouldDelete=
|
||||||
;;
|
[ -z "$ShouldDelete" ] && Delete=
|
||||||
esac
|
[ -z "$_Fake" ] && git_ensure_cleancheckout
|
||||||
[ -n "$ForbidDelete" ] && Delete=
|
if array_contains LocalBranches "$SrcBranch"; then
|
||||||
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
|
|
||||||
ensure_branches
|
ensure_branches
|
||||||
merge_action "$@"
|
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
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
189
bin/pman
189
bin/pman
@ -34,16 +34,12 @@ function show_action() {
|
|||||||
# Initialisation
|
# Initialisation
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
function init_repo_action() {
|
function _init_config() {
|
||||||
[ ${#LocalBranches[*]} -eq 0 ] || die "Ce dépôt a déjà été initialisé"
|
if [ ! -f .pman.conf -o -n "$ForceCreate" ]; then
|
||||||
|
|
||||||
local -a push_branches
|
|
||||||
|
|
||||||
if [ ! -f .pman.conf ]; then
|
|
||||||
ac_set_tmpfile config
|
ac_set_tmpfile config
|
||||||
cp "$ConfigFile" "$config"
|
cp "$ConfigFile" "$config"
|
||||||
"${EDITOR:-nano}" "$config"
|
"${EDITOR:-nano}" "$config"
|
||||||
[ -s "$config" ] || exit_with ewarn "Initialisation du dépôt annulée"
|
[ -s "$config" ] || return 1
|
||||||
|
|
||||||
cp "$config" .pman.conf
|
cp "$config" .pman.conf
|
||||||
if testdiff .pman.conf "$ConfigFile"; then
|
if testdiff .pman.conf "$ConfigFile"; then
|
||||||
@ -59,6 +55,15 @@ function init_repo_action() {
|
|||||||
.*.swp"
|
.*.swp"
|
||||||
git add .gitignore
|
git add .gitignore
|
||||||
fi
|
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"
|
einfo "Création de la branche $MAIN"
|
||||||
git symbolic-ref HEAD "refs/heads/$MAIN"
|
git symbolic-ref HEAD "refs/heads/$MAIN"
|
||||||
@ -72,17 +77,97 @@ function init_repo_action() {
|
|||||||
_push_branches
|
_push_branches
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_develop_action() {
|
function init_config_action() {
|
||||||
if [ -z "$DevelopBranch" ]; then
|
local -a push_branches; config
|
||||||
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
|
||||||
|
[ -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 "$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é?)"
|
[ -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}"
|
enote "Vous allez créer la branche ${COULEUR_VERTE}$DEVELOP${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MAIN${COULEUR_NORMALE}"
|
||||||
ask_yesno "Voulez-vous continuer?" O || die
|
ask_yesno "Voulez-vous continuer?" O || die
|
||||||
|
|
||||||
local -a push_branches
|
|
||||||
|
|
||||||
einfo "Création de la branche $DEVELOP"
|
einfo "Création de la branche $DEVELOP"
|
||||||
git checkout -b "$DEVELOP" "$MAIN" || die
|
git checkout -b "$DEVELOP" "$MAIN" || die
|
||||||
push_branches+=("$DEVELOP")
|
push_branches+=("$DEVELOP")
|
||||||
@ -92,17 +177,26 @@ function init_develop_action() {
|
|||||||
git checkout -q "$DEVELOP"
|
git checkout -q "$DEVELOP"
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_upstream_action() {
|
function _ensure_upstream_branch() {
|
||||||
if [ -z "$UpstreamBranch" ]; then
|
|
||||||
[ -n "$UPSTREAM" ] || die "La branche UPSTREAM n'a pas été définie"
|
[ -n "$UPSTREAM" ] || die "La branche UPSTREAM n'a pas été définie"
|
||||||
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
[ "$1" == init -o -n "$UpstreamBranch" ] || die "$UPSTREAM: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||||
[ -n "$DevelopBranch" ] || die "$DEVELOP: 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}"
|
enote "Vous allez créer la branche ${COULEUR_VERTE}$UPSTREAM${COULEUR_NORMALE}"
|
||||||
ask_yesno "Voulez-vous continuer?" O || die
|
ask_yesno "Voulez-vous continuer?" O || die
|
||||||
|
|
||||||
local -a push_branches; local config
|
|
||||||
|
|
||||||
# faire une copie de la configuration actuelle
|
# faire une copie de la configuration actuelle
|
||||||
ac_set_tmpfile config
|
ac_set_tmpfile config
|
||||||
cp "$ConfigFile" "$config"
|
cp "$ConfigFile" "$config"
|
||||||
@ -128,17 +222,26 @@ function init_upstream_action() {
|
|||||||
git checkout -q "$UPSTREAM"
|
git checkout -q "$UPSTREAM"
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_dist_action() {
|
function _ensure_dist_branch() {
|
||||||
if [ -z "$DistBranch" ]; then
|
|
||||||
[ -n "$DIST" ] || die "La branche DIST n'a pas été définie"
|
[ -n "$DIST" ] || die "La branche DIST n'a pas été définie"
|
||||||
[ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
|
[ "$1" == init -o -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||||
[ -n "$MainBranch" ] || die "$MAIN: 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}"
|
enote "Vous allez créer la branche ${COULEUR_VERTE}$DIST${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MAIN${COULEUR_NORMALE}"
|
||||||
ask_yesno "Voulez-vous continuer?" O || die
|
ask_yesno "Voulez-vous continuer?" O || die
|
||||||
|
|
||||||
local -a push_branches
|
|
||||||
|
|
||||||
einfo "Création de la branche $DIST"
|
einfo "Création de la branche $DIST"
|
||||||
git checkout -b "$DIST" "$MAIN" || die
|
git checkout -b "$DIST" "$MAIN" || die
|
||||||
push_branches+=("$DIST")
|
push_branches+=("$DIST")
|
||||||
@ -149,18 +252,24 @@ function init_dist_action() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init_feature_action() {
|
function init_feature_action() {
|
||||||
local branch="${1#$FEATURE}"
|
local -a push_branches; local branch
|
||||||
[ -n "$branch" ] || die "Vous devez définir la nom de la branche à créer"
|
|
||||||
|
[ -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"
|
branch="$FEATURE$branch"
|
||||||
if ! array_contains AllBranches "$branch"; then
|
|
||||||
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
if ! array_contains LocalBranches "$branch"; then
|
||||||
[ -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
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}"
|
enote "Vous allez créer la branche ${COULEUR_VERTE}$branch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$DEVELOP${COULEUR_NORMALE}"
|
||||||
ask_yesno "Voulez-vous continuer?" O || die
|
ask_yesno "Voulez-vous continuer?" O || die
|
||||||
|
|
||||||
local -a push_branches
|
|
||||||
|
|
||||||
einfo "Création de la branche $branch"
|
einfo "Création de la branche $branch"
|
||||||
git checkout -b "$branch" "$DEVELOP" || die
|
git checkout -b "$branch" "$DEVELOP" || die
|
||||||
push_branches+=("$branch")
|
push_branches+=("$branch")
|
||||||
@ -174,7 +283,9 @@ function init_action() {
|
|||||||
local what="${1:-develop}"; shift
|
local what="${1:-develop}"; shift
|
||||||
case "$what" in
|
case "$what" in
|
||||||
init|repo|r) init_repo_action "$@";;
|
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 "$@";;
|
develop|dev|d) init_develop_action "$@";;
|
||||||
upstream|up|u) init_upstream_action "$@";;
|
upstream|up|u) init_upstream_action "$@";;
|
||||||
dist|x) init_dist_action "$@";;
|
dist|x) init_dist_action "$@";;
|
||||||
@ -191,10 +302,12 @@ ConfigBranch=
|
|||||||
ConfigFile=
|
ConfigFile=
|
||||||
action=init
|
action=init
|
||||||
Origin=
|
Origin=
|
||||||
Push=1
|
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||||
|
ForceCreate=
|
||||||
args=(
|
args=(
|
||||||
"gérer un projet git"
|
"gérer un projet git"
|
||||||
"repo|develop|upstream|dist
|
"repo|config|composer
|
||||||
|
develop|upstream|dist
|
||||||
|
|
||||||
INITIALISATION
|
INITIALISATION
|
||||||
|
|
||||||
@ -203,6 +316,7 @@ configurer certaines branches du dépôt si elles n'existent pas déjà
|
|||||||
|
|
||||||
repo
|
repo
|
||||||
initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
|
initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
|
||||||
|
|
||||||
develop
|
develop
|
||||||
créer la branche $DEVELOP
|
créer la branche $DEVELOP
|
||||||
upstream
|
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"
|
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
||||||
-w,--show-config action=show "++\
|
-w,--show-config action=show "++\
|
||||||
afficher la configuration chargée"
|
afficher la configuration chargée"
|
||||||
|
--composer-select-profile action=composer_select_profile "\
|
||||||
|
sélectionner le profil composer spécifié en argument"
|
||||||
-O:,--origin Origin= "++\
|
-O:,--origin Origin= "++\
|
||||||
origine vers laquelle pousser les branches"
|
origine vers laquelle pousser les branches"
|
||||||
-n,--no-push Push= "\
|
-n,--no-push Push= "\
|
||||||
@ -226,6 +342,8 @@ ne pas pousser les branches vers leur origine après leur création"
|
|||||||
--push Push=1 "++\
|
--push Push=1 "++\
|
||||||
pousser les branches vers leur origine après leur création.
|
pousser les branches vers leur origine après leur création.
|
||||||
c'est l'option par défaut"
|
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[@]}"
|
parse_args "$@"; set -- "${args[@]}"
|
||||||
|
|
||||||
@ -244,6 +362,9 @@ init)
|
|||||||
git_ensure_cleancheckout
|
git_ensure_cleancheckout
|
||||||
init_action "$@"
|
init_action "$@"
|
||||||
;;
|
;;
|
||||||
|
composer_select_profile)
|
||||||
|
exec "$MYDIR/_pman-$action.php" "$@"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
die "$action: action non implémentée"
|
die "$action: action non implémentée"
|
||||||
;;
|
;;
|
||||||
|
103
bin/prel
103
bin/prel
@ -34,12 +34,19 @@ function ensure_branches() {
|
|||||||
function create_release_action() {
|
function create_release_action() {
|
||||||
if [ -n "$ReleaseBranch" ]; then
|
if [ -n "$ReleaseBranch" ]; then
|
||||||
Version="${ReleaseBranch#$RELEASE}"
|
Version="${ReleaseBranch#$RELEASE}"
|
||||||
|
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
|
||||||
merge_release_action "$@"; return $?
|
merge_release_action "$@"; return $?
|
||||||
elif [ -n "$HotfixBranch" ]; then
|
elif [ -n "$HotfixBranch" ]; then
|
||||||
Version="${HotfixBranch#$HOTFIX}"
|
Version="${HotfixBranch#$HOTFIX}"
|
||||||
|
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
|
||||||
merge_hotfix_action "$@"; return $?
|
merge_hotfix_action "$@"; return $?
|
||||||
fi
|
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
|
if [ -z "$Version" -a -n "$CurrentVersion" -a -f VERSION.txt ]; then
|
||||||
Version="$(<VERSION.txt)"
|
Version="$(<VERSION.txt)"
|
||||||
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
|
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
|
||||||
@ -67,7 +74,7 @@ Vous devrez:
|
|||||||
fi
|
fi
|
||||||
ask_yesno "Voulez-vous continuer?" O || die
|
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 -a push_branches push_tags
|
||||||
local comment=
|
local comment=
|
||||||
local or_die=" || exit 1"
|
local or_die=" || exit 1"
|
||||||
@ -78,8 +85,18 @@ Vous devrez:
|
|||||||
# create
|
# create
|
||||||
if [ -n "\$create" ]; then
|
if [ -n "\$create" ]; then
|
||||||
esection "Création de la release"
|
esection "Création de la release"
|
||||||
|
EOF
|
||||||
|
[ -n "$BEFORE_CREATE_RELEASE" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
$BEFORE_CREATE_RELEASE
|
||||||
|
)$or_die
|
||||||
EOF
|
EOF
|
||||||
_rscript_create_release_branch
|
_rscript_create_release_branch
|
||||||
|
[ -n "$AFTER_CREATE_RELEASE" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
$AFTER_CREATE_RELEASE
|
||||||
|
)$or_die
|
||||||
|
EOF
|
||||||
_scripta <<EOF
|
_scripta <<EOF
|
||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
@ -89,10 +106,20 @@ EOF
|
|||||||
# merge
|
# merge
|
||||||
if [ -n "\$merge" ]; then
|
if [ -n "\$merge" ]; then
|
||||||
esection "Fusionner la release"
|
esection "Fusionner la release"
|
||||||
|
EOF
|
||||||
|
[ -n "$BEFORE_MERGE_RELEASE" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
$BEFORE_MERGE_RELEASE
|
||||||
|
)$or_die
|
||||||
EOF
|
EOF
|
||||||
_rscript_merge_release_branch "$DestBranch" "$Tag"
|
_rscript_merge_release_branch "$DestBranch" "$Tag"
|
||||||
_rscript_merge_release_branch "$SrcBranch"
|
_rscript_merge_release_branch "$SrcBranch"
|
||||||
_rscript_delete_release_branch
|
_rscript_delete_release_branch
|
||||||
|
[ -n "$AFTER_MERGE_RELEASE" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
$AFTER_MERGE_RELEASE
|
||||||
|
)$or_die
|
||||||
|
EOF
|
||||||
_scripta <<EOF
|
_scripta <<EOF
|
||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
@ -102,30 +129,42 @@ EOF
|
|||||||
# push
|
# push
|
||||||
if [ -n "\$push" ]; then
|
if [ -n "\$push" ]; then
|
||||||
esection "Pousser branches et tags"
|
esection "Pousser branches et tags"
|
||||||
|
EOF
|
||||||
|
[ -n "$BEFORE_PUSH_RELEASE" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
$BEFORE_PUSH_RELEASE
|
||||||
|
)$or_die
|
||||||
EOF
|
EOF
|
||||||
_script_push_branches
|
_script_push_branches
|
||||||
_script_push_tags
|
_script_push_tags
|
||||||
|
[ -n "$AFTER_PUSH_RELEASE" ] && _scripta <<EOF
|
||||||
|
(
|
||||||
|
$AFTER_PUSH_RELEASE
|
||||||
|
)$or_die
|
||||||
|
EOF
|
||||||
_scripta <<EOF
|
_scripta <<EOF
|
||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
[ -n "$Merge" ] && Merged=1 || Merged=
|
[ -z "$ManualRelease" -a -n "$Merge" ] && ShouldMerge= || ShouldMerge=1
|
||||||
[ -n "$Push" -o "$ForbidPush" ] && Pushed=1 || Pushed=
|
[ -n "$ShouldPush" -a -n "$Push" ] && ShouldPush=
|
||||||
if [ -n "$_NoRunScript" ]; then
|
if [ -n "$_Fake" ]; then
|
||||||
einfo "Veuillez consulter le script $script pour le détail des opérations à effectuer"
|
cat "$script"
|
||||||
elif ! "$script" create ${Merge:+merge} ${Push:+push}; then
|
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
|
die
|
||||||
elif [ -n "$Merged" -a -n "$Pushed" ]; then
|
elif [ -n "$Merge" -a -n "$Push" ]; then
|
||||||
[ -n "$_KeepScript" ] || rm "$script"
|
[ -n "$_KeepScript" ] || rm "$script"
|
||||||
else
|
else
|
||||||
local cmd
|
einfo "\
|
||||||
[ -n "$Merged" ] || cmd="$cmd
|
Le script $script a été lancé avec les arguments 'create${Merge:+ merge}${Push:+ push}'
|
||||||
./$script merge"
|
Vous pouvez consulter le script et/ou le relancer
|
||||||
[ -n "$Pushed" ] || cmd="$cmd
|
./$script${ShouldMerge:+ merge}${ShouldPush:+ push}"
|
||||||
./$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"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,8 +191,8 @@ chdir=
|
|||||||
Origin=
|
Origin=
|
||||||
ConfigBranch=
|
ConfigBranch=
|
||||||
ConfigFile=
|
ConfigFile=
|
||||||
|
_Fake=
|
||||||
_KeepScript=
|
_KeepScript=
|
||||||
_NoRunScript=
|
|
||||||
action=release
|
action=release
|
||||||
[ -z "$PMAN_NO_MERGE" ] && Merge=1 || Merge=
|
[ -z "$PMAN_NO_MERGE" ] && Merge=1 || Merge=
|
||||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||||
@ -162,7 +201,17 @@ CurrentVersion=
|
|||||||
ForceCreate=
|
ForceCreate=
|
||||||
args=(
|
args=(
|
||||||
"faire une nouvelle release à partir de la branche source"
|
"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"
|
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
|
||||||
-O:,--origin Origin= "++\
|
-O:,--origin Origin= "++\
|
||||||
origine à partir de laquelle les branches distantes sont considérées"
|
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= "++\
|
-c:,--config-file:CONFIG ConfigFile= "++\
|
||||||
fichier de configuration des branches. cette option est prioritaire sur --config-branch
|
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"
|
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"
|
--keep-script _KeepScript=1 "++option non documentée"
|
||||||
--no-run-script _NoRunScript=1 "++option non documentée"
|
|
||||||
-w,--show action=show "\
|
-w,--show action=show "\
|
||||||
lister les modifications qui seraient intégrées dans la release"
|
lister les modifications qui seraient intégrées dans la release"
|
||||||
--release action=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 "++\
|
--push Push=1 "++\
|
||||||
pousser les branches vers leur origine après la création de la release.
|
pousser les branches vers leur origine après la création de la release.
|
||||||
c'est l'option par défaut"
|
c'est l'option par défaut"
|
||||||
-v:,--version Version= "\
|
-v:,--version:VERSION Version= "\
|
||||||
spécifier la version de la release à créer"
|
spécifier la version de la release à créer"
|
||||||
-C,--current-version CurrentVersion=1 "++\
|
-C,--current-version CurrentVersion=1 "++\
|
||||||
si aucune version n'est spécifiée, prendre la version présente dans le fichier VERSION.txt"
|
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à"
|
forcer la création de la release même si le tag correspond à la version existe déjà"
|
||||||
)
|
)
|
||||||
parse_args "$@"; set -- "${args[@]}"
|
parse_args "$@"; set -- "${args[@]}"
|
||||||
@ -203,19 +252,11 @@ load_branches all
|
|||||||
load_config "$MYNAME"
|
load_config "$MYNAME"
|
||||||
load_branches current "$1"; shift
|
load_branches current "$1"; shift
|
||||||
|
|
||||||
if [ -n "$Merge" -a -n "$NOAUTO" ]; then
|
[ -n "$Merge" -a -n "$NOAUTO" ] && ManualRelease=1 || ManualRelease=
|
||||||
ewarn "L'option --no-merge a été forcée puisque ce dépôt ne supporte pas les releases automatiques"
|
[ -n "$ManualRelease" ] && Merge=
|
||||||
Merge=
|
|
||||||
fi
|
|
||||||
[ -z "$Merge" ] && Push=
|
[ -z "$Merge" ] && Push=
|
||||||
|
|
||||||
ForbidPush=
|
resolve_should_push quiet
|
||||||
[ -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=
|
|
||||||
|
|
||||||
# puis faire l'action que l'on nous demande
|
# puis faire l'action que l'on nous demande
|
||||||
case "$action" in
|
case "$action" in
|
||||||
@ -225,7 +266,7 @@ show)
|
|||||||
show_action "$@"
|
show_action "$@"
|
||||||
;;
|
;;
|
||||||
release)
|
release)
|
||||||
git_ensure_cleancheckout
|
[ -z "$_Fake" ] && git_ensure_cleancheckout
|
||||||
ensure_branches
|
ensure_branches
|
||||||
case "$SrcType" in
|
case "$SrcType" in
|
||||||
release) merge_release_action "$@";;
|
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-posix": "*",
|
||||||
"ext-pcntl": "*",
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"ext-pgsql": "*",
|
||||||
"ext-sqlite3": "*"
|
"ext-sqlite3": "*"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"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",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "ab280aa4a5f5c83fa488537530b29759",
|
"content-hash": "a8b9dc80255663640bda855729ef2d47",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "symfony/deprecation-contracts",
|
"name": "symfony/deprecation-contracts",
|
||||||
@ -301,16 +301,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "myclabs/deep-copy",
|
"name": "myclabs/deep-copy",
|
||||||
"version": "1.12.1",
|
"version": "1.13.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||||
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
|
"reference": "024473a478be9df5fdaca2c793f2232fe788e414"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
|
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
|
||||||
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
|
"reference": "024473a478be9df5fdaca2c793f2232fe788e414",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -349,7 +349,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
"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": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -357,7 +357,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-11-08T17:47:46+00:00"
|
"time": "2025-02-12T12:17:51+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/php-parser",
|
"name": "nikic/php-parser",
|
||||||
@ -423,13 +423,19 @@
|
|||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.univ-reunion.fr/sda-php/nulib-tests.git",
|
"url": "https://git.univ-reunion.fr/sda-php/nulib-tests.git",
|
||||||
"reference": "9b5c9c295c3dee6fc02ccddbd8a70bca797c8045"
|
"reference": "8f641d9a7cf6aba1453cb42ebd15951aa7002e1b"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.3",
|
"php": "^7.3 || 8.0.*",
|
||||||
"phpunit/phpunit": "^9"
|
"phpunit/phpunit": "^9"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-pu9": "7.3.x-dev",
|
||||||
|
"dev-pu10": "8.1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"nulib\\tests\\": "src"
|
"nulib\\tests\\": "src"
|
||||||
@ -447,7 +453,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "fonctions et classes pour les tests",
|
"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",
|
"name": "phar-io/manifest",
|
||||||
@ -2015,7 +2021,10 @@
|
|||||||
"platform-dev": {
|
"platform-dev": {
|
||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"ext-pcntl": "*",
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*"
|
"ext-curl": "*",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"ext-pgsql": "*",
|
||||||
|
"ext-sqlite3": "*"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.2.0"
|
"plugin-api-version": "2.2.0"
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ COPY --from=builder /src/su-exec/su-exec /g/
|
|||||||
RUN /g/build
|
RUN /g/build
|
||||||
|
|
||||||
COPY --from=php /g/ /g/
|
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
|
EXPOSE 80 443
|
||||||
ENTRYPOINT ["/g/entrypoint"]
|
ENTRYPOINT ["/g/entrypoint"]
|
||||||
|
@ -34,7 +34,7 @@ COPY --from=legacytools /g/ /g/
|
|||||||
RUN /g/build nutools
|
RUN /g/build nutools
|
||||||
|
|
||||||
COPY --from=php /g/ /g/
|
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=instantclient /g/ /g/
|
||||||
COPY --from=builder /opt/oracle/ /opt/oracle/
|
COPY --from=builder /opt/oracle/ /opt/oracle/
|
||||||
|
@ -15,9 +15,9 @@ class StateException extends LogicException {
|
|||||||
return new static($prefix.$message);
|
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";
|
$message = "unexpected state";
|
||||||
if ($prefix) $prefix = "$prefix: ";
|
if ($suffix) $suffix = ": $suffix";
|
||||||
return new static($prefix.$message);
|
return new static($message.$suffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
namespace nulib;
|
namespace nulib;
|
||||||
|
|
||||||
use ArrayAccess;
|
use ArrayAccess;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use Traversable;
|
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 = [];
|
$result = [];
|
||||||
if ($array !== null) {
|
if ($array !== null) {
|
||||||
$ctx = nur_func::_prepare($callback);
|
$func = func::with($func);
|
||||||
foreach ($array as $key => $value) {
|
foreach ($array as $key => $value) {
|
||||||
$result[$key] = nur_func::_call($ctx, [$value, $key]);
|
$result[$key] = $func->invoke([$value, $key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $result;
|
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
|
* retourner le tableau $array en "renommant" les clés selon le tableau
|
||||||
* $mappings qui contient des associations de la forme [$from => $to]
|
* $mappings qui contient des associations de la forme [$from => $to]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ class Capacitor implements ITransactor {
|
|||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$commited = false;
|
$commited = false;
|
||||||
try {
|
try {
|
||||||
nur_func::call($func, $this);
|
func::call($func, $this);
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->commit();
|
$this->commit();
|
||||||
$commited = true;
|
$commited = true;
|
||||||
@ -120,10 +120,6 @@ class Capacitor implements ITransactor {
|
|||||||
if ($db->inTransaction()) $db->rollback();
|
if ($db->inTransaction()) $db->rollback();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCreateSql(): string {
|
|
||||||
return $this->storage->_getCreateSql($this->channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
function exists(): bool {
|
function exists(): bool {
|
||||||
return $this->storage->_exists($this->channel);
|
return $this->storage->_exists($this->channel);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ class CapacitorChannel {
|
|||||||
|
|
||||||
const PRIMARY_KEYS = null;
|
const PRIMARY_KEYS = null;
|
||||||
|
|
||||||
|
const MIGRATION = null;
|
||||||
|
|
||||||
const MANAGE_TRANSACTIONS = true;
|
const MANAGE_TRANSACTIONS = true;
|
||||||
|
|
||||||
const EACH_COMMIT_THRESHOLD = 100;
|
const EACH_COMMIT_THRESHOLD = 100;
|
||||||
@ -63,15 +65,29 @@ class CapacitorChannel {
|
|||||||
$this->created = false;
|
$this->created = false;
|
||||||
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
|
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
|
||||||
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
|
$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;
|
$index = 0;
|
||||||
foreach ($columnDefinitions as $col => $def) {
|
foreach ($columnDefinitions as $col => $def) {
|
||||||
if ($col === $index) {
|
if ($col === $index) {
|
||||||
|
# si définition séquentielle, seules les définitions de clé
|
||||||
|
# primaires sont supportées
|
||||||
$index++;
|
$index++;
|
||||||
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
|
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
|
||||||
$primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
|
$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 {
|
} 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)) {
|
if (preg_match('/\bprimary\s+key\b/i', $def)) {
|
||||||
$primaryKeys[] = $col;
|
$primaryKeys[] = $col;
|
||||||
}
|
}
|
||||||
@ -80,6 +96,7 @@ class CapacitorChannel {
|
|||||||
}
|
}
|
||||||
$this->columnDefinitions = $columnDefinitions;
|
$this->columnDefinitions = $columnDefinitions;
|
||||||
$this->primaryKeys = $primaryKeys;
|
$this->primaryKeys = $primaryKeys;
|
||||||
|
$this->migration = $migration;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string $name;
|
protected string $name;
|
||||||
@ -192,6 +209,12 @@ class CapacitorChannel {
|
|||||||
return $this->columnDefinitions;
|
return $this->columnDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ?array $migration;
|
||||||
|
|
||||||
|
function getMigration(): ?array {
|
||||||
|
return $this->migration;
|
||||||
|
}
|
||||||
|
|
||||||
protected ?array $primaryKeys;
|
protected ?array $primaryKeys;
|
||||||
|
|
||||||
function getPrimaryKeys(): ?array {
|
function getPrimaryKeys(): ?array {
|
||||||
@ -245,9 +268,6 @@ class CapacitorChannel {
|
|||||||
return $serial !== null? unserialize($serial): null;
|
return $serial !== null? unserialize($serial): null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SERIAL_DEFINITION = "mediumtext";
|
|
||||||
const SUM_DEFINITION = "varchar(40)";
|
|
||||||
|
|
||||||
final function sum(?string $serial, $value=null): ?string {
|
final function sum(?string $serial, $value=null): ?string {
|
||||||
if ($serial === null) $serial = $this->serialize($value);
|
if ($serial === null) $serial = $this->serialize($value);
|
||||||
return $serial !== null? sha1($serial): null;
|
return $serial !== null? sha1($serial): null;
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nulib\db\_private\_migration;
|
||||||
use nulib\db\cache\cache;
|
use nulib\db\cache\cache;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
@ -35,14 +36,18 @@ abstract class CapacitorStorage {
|
|||||||
/** DOIT être défini dans les classes dérivées */
|
/** DOIT être défini dans les classes dérivées */
|
||||||
const PRIMARY_KEY_DEFINITION = null;
|
const PRIMARY_KEY_DEFINITION = null;
|
||||||
|
|
||||||
|
const SERDATA_DEFINITION = "mediumtext";
|
||||||
|
const SERSUM_DEFINITION = "varchar(40)";
|
||||||
|
const SERTS_DEFINITION = "datetime";
|
||||||
|
|
||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
"item__" => CapacitorChannel::SERIAL_DEFINITION,
|
"item__" => "serdata",
|
||||||
"item__sum_" => CapacitorChannel::SUM_DEFINITION,
|
"item__sum_" => "sersum",
|
||||||
"created_" => "datetime",
|
"created_" => "serts",
|
||||||
"modified_" => "datetime",
|
"modified_" => "serts",
|
||||||
];
|
];
|
||||||
|
|
||||||
protected function ColumnDefinitions(CapacitorChannel $channel): array {
|
protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
|
||||||
$definitions = [];
|
$definitions = [];
|
||||||
if ($channel->getPrimaryKeys() === null) {
|
if ($channel->getPrimaryKeys() === null) {
|
||||||
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
||||||
@ -56,16 +61,31 @@ abstract class CapacitorStorage {
|
|||||||
$constraints = [];
|
$constraints = [];
|
||||||
$index = 0;
|
$index = 0;
|
||||||
foreach ($tmp as $col => $def) {
|
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) {
|
if ($col === $index) {
|
||||||
$index++;
|
$index++;
|
||||||
$constraints[] = $def;
|
$constraints[] = $def;
|
||||||
} else {
|
} elseif (is_array($def)) {
|
||||||
|
# éventuellement, ignorer les migrations
|
||||||
|
$def = implode(" ", $def);
|
||||||
|
if ($def && !$ignoreMigrations) {
|
||||||
$definitions[$col] = $def;
|
$definitions[$col] = $def;
|
||||||
}
|
}
|
||||||
|
} elseif (is_scalar($def)) {
|
||||||
|
$definitions[$col] = strval($def);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return cl::merge($definitions, $constraints);
|
return cl::merge($definitions, $constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getMigration(CapacitorChannel $channel): ?array {
|
||||||
|
return $channel->getMigration();
|
||||||
|
}
|
||||||
|
|
||||||
/** sérialiser les valeurs qui doivent l'être dans $values */
|
/** sérialiser les valeurs qui doivent l'être dans $values */
|
||||||
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
|
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
|
||||||
if ($values === null) return null;
|
if ($values === null) return null;
|
||||||
@ -128,11 +148,10 @@ abstract class CapacitorStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _createSql(CapacitorChannel $channel): array {
|
protected function _createSql(CapacitorChannel $channel): array {
|
||||||
$cols = $this->ColumnDefinitions($channel);
|
|
||||||
return [
|
return [
|
||||||
"create table if not exists",
|
"create table if not exists",
|
||||||
"table" => $channel->getTableName(),
|
"table" => $channel->getTableName(),
|
||||||
"cols" => $cols,
|
"cols" => $this->ColumnDefinitions($channel, true),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,20 +166,45 @@ $sql;
|
|||||||
EOT;
|
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 */
|
const CHANNELS_TABLE = "_channels";
|
||||||
function getCreateSql(?string $channel): string {
|
const CHANNELS_COLS = [
|
||||||
return $this->_getCreateSql($this->getChannel($channel));
|
"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 {
|
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||||
|
$db = $this->db();
|
||||||
|
$db->exec($this->_createChannelsSql());
|
||||||
|
$db->exec($this->_addToChannelsSql($channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _create(CapacitorChannel $channel): void {
|
protected function _create(CapacitorChannel $channel): void {
|
||||||
$channel->ensureSetup();
|
$channel->ensureSetup();
|
||||||
if (!$channel->isCreated()) {
|
if (!$channel->isCreated()) {
|
||||||
$this->db->exec($this->_createSql($channel));
|
$this->_getMigration($channel)->migrate($this->db());
|
||||||
$this->_afterCreate($channel);
|
$this->_afterCreate($channel);
|
||||||
$channel->setCreated();
|
$channel->setCreated();
|
||||||
}
|
}
|
||||||
@ -183,12 +227,28 @@ EOT;
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
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é */
|
/** supprimer le canal spécifié */
|
||||||
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
||||||
$this->_beforeReset($channel);
|
$this->_beforeReset($channel);
|
||||||
$this->db->exec([
|
$this->db()->exec([
|
||||||
"drop table if exists",
|
"drop table if exists",
|
||||||
$channel->getTableName(),
|
$channel->getTableName(),
|
||||||
]);
|
]);
|
||||||
@ -230,10 +290,7 @@ EOT;
|
|||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$args ??= [];
|
$args ??= [];
|
||||||
|
|
||||||
$initFunc = [$channel, "getItemValues"];
|
$values = func::call([$channel, "getItemValues"], $item, ...$args);
|
||||||
$initArgs = $args;
|
|
||||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
|
||||||
$values = nur_func::call($initFunc, $item, ...$initArgs);
|
|
||||||
if ($values === [false]) return 0;
|
if ($values === [false]) return 0;
|
||||||
|
|
||||||
$row = cl::merge(
|
$row = cl::merge(
|
||||||
@ -259,9 +316,7 @@ EOT;
|
|||||||
"modified_" => $now,
|
"modified_" => $now,
|
||||||
]);
|
]);
|
||||||
$insert = true;
|
$insert = true;
|
||||||
$initFunc = [$channel, "onCreate"];
|
$initFunc = func::with([$channel, "onCreate"], $args);
|
||||||
$initArgs = $args;
|
|
||||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
|
||||||
$values = $this->unserialize($channel, $row);
|
$values = $this->unserialize($channel, $row);
|
||||||
$pvalues = null;
|
$pvalues = null;
|
||||||
} else {
|
} else {
|
||||||
@ -276,14 +331,12 @@ EOT;
|
|||||||
} else {
|
} else {
|
||||||
$row = cl::merge($prow, $row);
|
$row = cl::merge($prow, $row);
|
||||||
}
|
}
|
||||||
$initFunc = [$channel, "onUpdate"];
|
$initFunc = func::with([$channel, "onUpdate"], $args);
|
||||||
$initArgs = $args;
|
|
||||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
|
||||||
$values = $this->unserialize($channel, $row);
|
$values = $this->unserialize($channel, $row);
|
||||||
$pvalues = $this->unserialize($channel, $prow);
|
$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 ($updates === [false]) return 0;
|
||||||
if (is_array($updates) && $updates) {
|
if (is_array($updates) && $updates) {
|
||||||
if ($insert === null) $insert = false;
|
if ($insert === null) $insert = false;
|
||||||
@ -295,8 +348,10 @@ EOT;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
nur_func::ensure_func($func, $channel, $args);
|
$updates = func::with($func)
|
||||||
$updates = nur_func::call($func, $item, $values, $pvalues, ...$args);
|
->prependArgs([$item, $values, $pvalues])
|
||||||
|
->bind($channel, true)
|
||||||
|
->invoke();
|
||||||
if ($updates === [false]) return 0;
|
if ($updates === [false]) return 0;
|
||||||
if (is_array($updates) && $updates) {
|
if (is_array($updates) && $updates) {
|
||||||
if ($insert === null) $insert = false;
|
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 {
|
function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
if ($func === null) $func = CapacitorChannel::onEach;
|
if ($func === null) $func = CapacitorChannel::onEach;
|
||||||
nur_func::ensure_func($func, $channel, $args);
|
$onEach = func::with($func)->bind($channel, true);
|
||||||
$onEach = nur_func::_prepare($func);
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||||
@ -528,7 +582,7 @@ EOT;
|
|||||||
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
||||||
foreach ($all as $values) {
|
foreach ($all as $values) {
|
||||||
$rowIds = $this->getRowIds($channel, $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 (is_array($updates) && $updates) {
|
||||||
if (!array_key_exists("modified_", $updates)) {
|
if (!array_key_exists("modified_", $updates)) {
|
||||||
$updates["modified_"] = date("Y-m-d H:i:s");
|
$updates["modified_"] = date("Y-m-d H:i:s");
|
||||||
@ -579,8 +633,7 @@ EOT;
|
|||||||
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
if ($func === null) $func = CapacitorChannel::onDelete;
|
if ($func === null) $func = CapacitorChannel::onDelete;
|
||||||
nur_func::ensure_func($func, $channel, $args);
|
$onEach = func::with($func)->bind($channel, true);
|
||||||
$onEach = nur_func::_prepare($func);
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||||
@ -596,7 +649,7 @@ EOT;
|
|||||||
$all = $this->_allCached("delete", $channel, $filter);
|
$all = $this->_allCached("delete", $channel, $filter);
|
||||||
foreach ($all as $values) {
|
foreach ($all as $values) {
|
||||||
$rowIds = $this->getRowIds($channel, $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) {
|
if ($delete) {
|
||||||
$db->exec([
|
$db->exec([
|
||||||
"delete",
|
"delete",
|
||||||
|
@ -15,5 +15,9 @@ interface IDatabase extends ITransactor {
|
|||||||
|
|
||||||
function one($query, ?array $params=null): ?array;
|
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;
|
function all($query, ?array $params=null, $primaryKeys=null): iterable;
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,13 @@ interface ITransactor {
|
|||||||
*/
|
*/
|
||||||
function willUpdate(...$transactors): self;
|
function willUpdate(...$transactors): self;
|
||||||
|
|
||||||
|
/** Indiquer si une transaction est en cours */
|
||||||
function inTransaction(): bool;
|
function inTransaction(): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* démarrer une transaction
|
* 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,
|
* transaction. si une erreur se produit lors de l'appel de la fonction,
|
||||||
* annuler la transaction
|
* annuler la transaction
|
||||||
*
|
*
|
||||||
@ -24,7 +25,9 @@ interface ITransactor {
|
|||||||
*/
|
*/
|
||||||
function beginTransaction(?callable $func=null, bool $commit=true): void;
|
function beginTransaction(?callable $func=null, bool $commit=true): void;
|
||||||
|
|
||||||
|
/** valider la transaction */
|
||||||
function commit(): void;
|
function commit(): void;
|
||||||
|
|
||||||
|
/** annuler la transaction */
|
||||||
function rollback(): void;
|
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\str;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
|
|
||||||
abstract class _base {
|
abstract class _base extends _common {
|
||||||
protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
|
protected static function verifix(&$sql, ?array &$bindings=null, ?array &$meta=null): void {
|
||||||
if (!preg_match("/^$pattern/i", $string, $ms)) return false;
|
if (is_array($sql)) {
|
||||||
$string = substr($string, strlen($ms[0]));
|
$prefix = $sql[0] ?? null;
|
||||||
return true;
|
if ($prefix === null) {
|
||||||
}
|
throw new ValueException("requête invalide");
|
||||||
|
} elseif (_create::isa($prefix)) {
|
||||||
/** fusionner toutes les parties séquentielles d'une requête */
|
$sql = _create::parse($sql, $bindings);
|
||||||
protected static function merge_seq(array $query): string {
|
$meta = ["isa" => "create", "type" => "ddl"];
|
||||||
$index = 0;
|
} elseif (_select::isa($prefix)) {
|
||||||
$sql = "";
|
$sql = _select::parse($sql, $bindings);
|
||||||
foreach ($query as $key => $value) {
|
$meta = ["isa" => "select", "type" => "dql"];
|
||||||
if ($key === $index) {
|
} elseif (_insert::isa($prefix)) {
|
||||||
$index++;
|
$sql = _insert::parse($sql, $bindings);
|
||||||
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
|
$meta = ["isa" => "insert", "type" => "dml"];
|
||||||
$sql .= " ";
|
} elseif (_update::isa($prefix)) {
|
||||||
}
|
$sql = _update::parse($sql, $bindings);
|
||||||
$sql .= $value;
|
$meta = ["isa" => "update", "type" => "dml"];
|
||||||
}
|
} elseif (_delete::isa($prefix)) {
|
||||||
}
|
$sql = _delete::parse($sql, $bindings);
|
||||||
return $sql;
|
$meta = ["isa" => "delete", "type" => "dml"];
|
||||||
}
|
} elseif (_generic::isa($prefix)) {
|
||||||
|
$sql = _generic::parse($sql, $bindings);
|
||||||
protected static function is_sep(&$cond): bool {
|
$meta = ["isa" => "generic", "type" => null];
|
||||||
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 {
|
} else {
|
||||||
# condition litérale
|
throw ValueException::invalid_kind($sql, "query");
|
||||||
$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 {
|
} else {
|
||||||
$param = "$param0$i";
|
if (!is_string($sql)) $sql = strval($sql);
|
||||||
$parts[] = ":$param";
|
if (_create::isa($sql)) {
|
||||||
$bindings[$param] = $condvalue;
|
$meta = ["isa" => "create", "type" => "ddl"];
|
||||||
if ($i === false) $i = 2;
|
} elseif (_select::isa($sql)) {
|
||||||
else $i++;
|
$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 {
|
static function with($sql, ?array $params=null): array {
|
||||||
if (!$values) return;
|
static::verifix($sql, $params);
|
||||||
$index = 0;
|
return [$sql, $params];
|
||||||
$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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void;
|
|
||||||
|
|
||||||
function __construct($sql, ?array $bindings=null) {
|
function __construct($sql, ?array $bindings=null) {
|
||||||
static::verifix($sql, $bindings, $meta);
|
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
|
<?php
|
||||||
namespace nulib\db\pdo;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
use nulib\php\nur_func;
|
use nulib\db\IDatabase;
|
||||||
|
use nulib\php\func;
|
||||||
|
|
||||||
class _config {
|
class _config {
|
||||||
static function with($configs): self {
|
static function with($configs): self {
|
||||||
@ -23,13 +24,12 @@ class _config {
|
|||||||
/** @var array */
|
/** @var array */
|
||||||
protected $configs;
|
protected $configs;
|
||||||
|
|
||||||
function configure(Pdo $pdo): void {
|
function configure(IDatabase $db): void {
|
||||||
foreach ($this->configs as $key => $config) {
|
foreach ($this->configs as $key => $config) {
|
||||||
if (is_string($config) && !nur_func::is_method($config)) {
|
if (is_string($config) && !func::is_method($config)) {
|
||||||
$pdo->exec($config);
|
$db->exec($config);
|
||||||
} else {
|
} else {
|
||||||
nur_func::ensure_func($config, $this, $args);
|
func::with($config)->bind($this, true)->invoke([$db, $key]);
|
||||||
nur_func::call($config, $pdo, $key, ...$args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _create {
|
class _create extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"table" => "string",
|
"table" => "string",
|
||||||
@ -9,4 +9,46 @@ class _create {
|
|||||||
"cols" => "?array",
|
"cols" => "?array",
|
||||||
"suffix" => "?string",
|
"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
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _delete {
|
class _delete extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"from" => "?string",
|
"from" => "?string",
|
||||||
"where" => "?array",
|
"where" => "?array",
|
||||||
"suffix" => "?string",
|
"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
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _generic {
|
use nulib\cl;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class _generic extends _common {
|
||||||
const SCHEMA = [
|
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
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _insert {
|
use nulib\cl;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class _insert extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"into" => "?string",
|
"into" => "?string",
|
||||||
@ -10,4 +13,79 @@ class _insert {
|
|||||||
"values" => "?array",
|
"values" => "?array",
|
||||||
"suffix" => "?string",
|
"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
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _select {
|
use nulib\cl;
|
||||||
|
use nulib\str;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class _select extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"schema" => "?array",
|
"schema" => "?array",
|
||||||
@ -14,4 +18,164 @@ class _select {
|
|||||||
"having" => "?array",
|
"having" => "?array",
|
||||||
"suffix" => "?string",
|
"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
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _update {
|
class _update extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"table" => "?string",
|
"table" => "?string",
|
||||||
@ -11,4 +11,43 @@ class _update {
|
|||||||
"where" => "?array",
|
"where" => "?array",
|
||||||
"suffix" => "?string",
|
"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
|
<?php
|
||||||
namespace nulib\db\mysql;
|
namespace nulib\db\mysql;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\CapacitorStorage;
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
@ -12,8 +13,7 @@ class MysqlStorage extends CapacitorStorage {
|
|||||||
$this->db = Mysql::with($mysql);
|
$this->db = Mysql::with($mysql);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Mysql */
|
protected Mysql $db;
|
||||||
protected $db;
|
|
||||||
|
|
||||||
function db(): Mysql {
|
function db(): Mysql {
|
||||||
return $this->db;
|
return $this->db;
|
||||||
@ -23,17 +23,35 @@ class MysqlStorage extends CapacitorStorage {
|
|||||||
"id_" => "integer primary key auto_increment",
|
"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 {
|
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());
|
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 {
|
function _exists(CapacitorChannel $channel): bool {
|
||||||
$db = $this->db;
|
$mysql = $this->db;
|
||||||
$tableName = $db->get([
|
$tableName = $mysql->get([
|
||||||
"select table_name from information_schema.tables",
|
"select table_name from information_schema.tables",
|
||||||
"where" => [
|
"where" => [
|
||||||
"table_schema" => $db->getDbname(),
|
"table_schema" => $mysql->getDbname(),
|
||||||
"table_name" => $channel->getTableName(),
|
"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 Generator;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nulib\db\_private\_config;
|
||||||
use nulib\db\_private\Tvalues;
|
use nulib\db\_private\Tvalues;
|
||||||
use nulib\db\IDatabase;
|
use nulib\db\IDatabase;
|
||||||
use nulib\db\ITransactor;
|
use nulib\db\ITransactor;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
|
|
||||||
class Pdo implements IDatabase {
|
class Pdo implements IDatabase {
|
||||||
@ -21,7 +22,7 @@ class Pdo implements IDatabase {
|
|||||||
"dbconn" => $pdo->dbconn,
|
"dbconn" => $pdo->dbconn,
|
||||||
"options" => $pdo->options,
|
"options" => $pdo->options,
|
||||||
"config" => $pdo->config,
|
"config" => $pdo->config,
|
||||||
"migrate" => $pdo->migration,
|
"migration" => $pdo->migration,
|
||||||
], $params));
|
], $params));
|
||||||
} else {
|
} else {
|
||||||
return new static($pdo, $params);
|
return new static($pdo, $params);
|
||||||
@ -49,7 +50,7 @@ class Pdo implements IDatabase {
|
|||||||
|
|
||||||
protected const CONFIG = null;
|
protected const CONFIG = null;
|
||||||
|
|
||||||
protected const MIGRATE = null;
|
protected const MIGRATION = null;
|
||||||
|
|
||||||
const dbconn_SCHEMA = [
|
const dbconn_SCHEMA = [
|
||||||
"name" => "string",
|
"name" => "string",
|
||||||
@ -62,7 +63,7 @@ class Pdo implements IDatabase {
|
|||||||
"options" => ["?array|callable"],
|
"options" => ["?array|callable"],
|
||||||
"replace_config" => ["?array|callable"],
|
"replace_config" => ["?array|callable"],
|
||||||
"config" => ["?array|callable"],
|
"config" => ["?array|callable"],
|
||||||
"migrate" => ["?array|string|callable"],
|
"migration" => ["?array|string|callable"],
|
||||||
"auto_open" => ["bool", true],
|
"auto_open" => ["bool", true],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ class Pdo implements IDatabase {
|
|||||||
}
|
}
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
# migrations
|
# migrations
|
||||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||||
#
|
#
|
||||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||||
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||||
@ -104,7 +105,7 @@ class Pdo implements IDatabase {
|
|||||||
protected ?array $dbconn;
|
protected ?array $dbconn;
|
||||||
|
|
||||||
/** @var array|callable */
|
/** @var array|callable */
|
||||||
protected array $options;
|
protected $options;
|
||||||
|
|
||||||
/** @var array|string|callable */
|
/** @var array|string|callable */
|
||||||
protected $config;
|
protected $config;
|
||||||
@ -119,8 +120,7 @@ class Pdo implements IDatabase {
|
|||||||
$dbconn = $this->dbconn;
|
$dbconn = $this->dbconn;
|
||||||
$options = $this->options;
|
$options = $this->options;
|
||||||
if (is_callable($options)) {
|
if (is_callable($options)) {
|
||||||
nur_func::ensure_func($options, $this, $args);
|
$options = func::with($options)->bind($this, true)->invoke();
|
||||||
$options = nur_func::call($options, ...$args);
|
|
||||||
}
|
}
|
||||||
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
|
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
|
||||||
_config::with($this->config)->configure($this);
|
_config::with($this->config)->configure($this);
|
||||||
@ -143,21 +143,16 @@ class Pdo implements IDatabase {
|
|||||||
return $this->db()->exec($query);
|
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) {
|
function exec($query, ?array $params=null) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
if ($stmt->execute() === false) return false;
|
if ($stmt->execute() === false) return false;
|
||||||
if ($query->isInsert()) return $db->lastInsertId();
|
if ($query->isInsert()) return $db->lastInsertId();
|
||||||
else return $stmt->rowCount();
|
else return $stmt->rowCount();
|
||||||
} else {
|
} else {
|
||||||
$rowCount = $db->exec($sql);
|
$rowCount = $db->exec($sql);
|
||||||
if (self::is_insert($sql)) return $db->lastInsertId();
|
if ($query->isInsert()) return $db->lastInsertId();
|
||||||
else return $rowCount;
|
else return $rowCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +186,7 @@ class Pdo implements IDatabase {
|
|||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$commited = false;
|
$commited = false;
|
||||||
try {
|
try {
|
||||||
nur_func::call($func, $this);
|
func::call($func, $this);
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->commit();
|
$this->commit();
|
||||||
$commited = true;
|
$commited = true;
|
||||||
@ -222,11 +217,11 @@ class Pdo implements IDatabase {
|
|||||||
|
|
||||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
$stmt = null;
|
$stmt = null;
|
||||||
try {
|
try {
|
||||||
/** @var \PDOStatement $stmt */
|
/** @var \PDOStatement $stmt */
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
if ($stmt->execute() === false) return null;
|
if ($stmt->execute() === false) return null;
|
||||||
} else {
|
} else {
|
||||||
$stmt = $db->query($sql);
|
$stmt = $db->query($sql);
|
||||||
@ -245,22 +240,18 @@ class Pdo implements IDatabase {
|
|||||||
return $this->get($query, $params, true);
|
return $this->get($query, $params, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||||
* 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 {
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
$stmt = null;
|
$stmt = null;
|
||||||
try {
|
try {
|
||||||
/** @var \PDOStatement $stmt */
|
/** @var \PDOStatement $stmt */
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
if ($stmt->execute() === false) return;
|
if ($stmt->execute() === false) return;
|
||||||
} else {
|
} else {
|
||||||
$stmt = $db->query($sql);
|
$stmt = $db->query($sql);
|
||||||
}
|
}
|
||||||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
$primaryKeys = cl::withn($primaryKeys);
|
||||||
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
|
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
|
||||||
$this->verifixRow($row);
|
$this->verifixRow($row);
|
||||||
if ($primaryKeys !== null) {
|
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 Generator;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nulib\db\_private\_config;
|
||||||
use nulib\db\_private\Tvalues;
|
use nulib\db\_private\Tvalues;
|
||||||
use nulib\db\IDatabase;
|
use nulib\db\IDatabase;
|
||||||
use nulib\db\ITransactor;
|
use nulib\db\ITransactor;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use SQLite3;
|
use SQLite3;
|
||||||
use SQLite3Result;
|
use SQLite3Result;
|
||||||
@ -29,7 +30,7 @@ class Sqlite implements IDatabase {
|
|||||||
"encryption_key" => $sqlite->encryptionKey,
|
"encryption_key" => $sqlite->encryptionKey,
|
||||||
"allow_wal" => $sqlite->allowWal,
|
"allow_wal" => $sqlite->allowWal,
|
||||||
"config" => $sqlite->config,
|
"config" => $sqlite->config,
|
||||||
"migrate" => $sqlite->migration,
|
"migration" => $sqlite->migration,
|
||||||
], $params));
|
], $params));
|
||||||
} elseif (is_array($sqlite)) {
|
} elseif (is_array($sqlite)) {
|
||||||
return new static(null, cl::merge($sqlite, $params));
|
return new static(null, cl::merge($sqlite, $params));
|
||||||
@ -71,7 +72,7 @@ class Sqlite implements IDatabase {
|
|||||||
|
|
||||||
const CONFIG = null;
|
const CONFIG = null;
|
||||||
|
|
||||||
const MIGRATE = null;
|
const MIGRATION = null;
|
||||||
|
|
||||||
const params_SCHEMA = [
|
const params_SCHEMA = [
|
||||||
"file" => ["string", ""],
|
"file" => ["string", ""],
|
||||||
@ -80,7 +81,7 @@ class Sqlite implements IDatabase {
|
|||||||
"allow_wal" => ["?bool"],
|
"allow_wal" => ["?bool"],
|
||||||
"replace_config" => ["?array|callable"],
|
"replace_config" => ["?array|callable"],
|
||||||
"config" => ["?array|callable"],
|
"config" => ["?array|callable"],
|
||||||
"migrate" => ["?array|string|callable"],
|
"migration" => ["?array|string|callable"],
|
||||||
"auto_open" => ["bool", true],
|
"auto_open" => ["bool", true],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ class Sqlite implements IDatabase {
|
|||||||
}
|
}
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
# migrations
|
# migrations
|
||||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||||
#
|
#
|
||||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||||
$this->inTransaction = false;
|
$this->inTransaction = false;
|
||||||
@ -149,7 +150,7 @@ class Sqlite implements IDatabase {
|
|||||||
if ($this->db === null) {
|
if ($this->db === null) {
|
||||||
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
||||||
_config::with($this->config)->configure($this);
|
_config::with($this->config)->configure($this);
|
||||||
_migration::with($this->migration)->migrate($this);
|
_sqliteMigration::with($this->migration)->migrate($this);
|
||||||
$this->inTransaction = false;
|
$this->inTransaction = false;
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
@ -180,15 +181,10 @@ class Sqlite implements IDatabase {
|
|||||||
return $this->db()->exec($query);
|
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) {
|
function exec($query, ?array $params=null) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
try {
|
try {
|
||||||
$result = $stmt->execute();
|
$result = $stmt->execute();
|
||||||
if ($result === false) return false;
|
if ($result === false) return false;
|
||||||
@ -201,7 +197,7 @@ class Sqlite implements IDatabase {
|
|||||||
} else {
|
} else {
|
||||||
$result = $db->exec($sql);
|
$result = $db->exec($sql);
|
||||||
if ($result === false) return false;
|
if ($result === false) return false;
|
||||||
if (self::is_insert($sql)) return $db->lastInsertRowID();
|
if ($query->isInsert()) return $db->lastInsertRowID();
|
||||||
else return $db->changes();
|
else return $db->changes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,7 +233,7 @@ class Sqlite implements IDatabase {
|
|||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$commited = false;
|
$commited = false;
|
||||||
try {
|
try {
|
||||||
nur_func::call($func, $this);
|
func::call($func, $this);
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->commit();
|
$this->commit();
|
||||||
$commited = true;
|
$commited = true;
|
||||||
@ -274,8 +270,8 @@ class Sqlite implements IDatabase {
|
|||||||
|
|
||||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
try {
|
try {
|
||||||
$result = $this->checkResult($stmt->execute());
|
$result = $this->checkResult($stmt->execute());
|
||||||
try {
|
try {
|
||||||
@ -300,7 +296,7 @@ class Sqlite implements IDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null, $primaryKeys=null): Generator {
|
protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null, $primaryKeys=null): Generator {
|
||||||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
$primaryKeys = cl::withn($primaryKeys);
|
||||||
try {
|
try {
|
||||||
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
||||||
$this->verifixRow($row);
|
$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 {
|
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
$result = $this->checkResult($stmt->execute());
|
$result = $this->checkResult($stmt->execute());
|
||||||
return $this->_fetchResult($result, $stmt, $primaryKeys);
|
return $this->_fetchResult($result, $stmt, $primaryKeys);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\sqlite;
|
namespace nulib\db\sqlite;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\CapacitorStorage;
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
@ -12,8 +13,7 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
$this->db = Sqlite::with($sqlite);
|
$this->db = Sqlite::with($sqlite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Sqlite */
|
protected Sqlite $db;
|
||||||
protected $db;
|
|
||||||
|
|
||||||
function db(): Sqlite {
|
function db(): Sqlite {
|
||||||
return $this->db;
|
return $this->db;
|
||||||
@ -23,8 +23,14 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
"id_" => "integer primary key autoincrement",
|
"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 {
|
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());
|
return self::format_sql($channel, $query->getSql());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,54 +45,33 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function channelExists(string $name): bool {
|
function channelExists(string $name): bool {
|
||||||
$name = $this->db->get([
|
return null !== $this->db->get([
|
||||||
"select name from _channels",
|
"select name",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
"where" => ["name" => $name],
|
"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 {
|
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||||
$db = $this->db;
|
$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
|
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
|
||||||
# verrou en écriture
|
# verrou en écriture
|
||||||
$db->exec([
|
$db->exec($this->_createChannelsSql());
|
||||||
"create table if not exists",
|
|
||||||
"table" => "_channels",
|
|
||||||
"cols" => [
|
|
||||||
"name" => "varchar primary key",
|
|
||||||
"table_name" => "varchar",
|
|
||||||
"class" => "varchar",
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
if (!$this->channelExists($channel->getName())) {
|
if (!$this->channelExists($channel->getName())) {
|
||||||
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
||||||
# verrou en écriture
|
# verrou en écriture
|
||||||
$db->exec([
|
$db->exec($this->_addToChannelsSql($channel));
|
||||||
"insert",
|
|
||||||
"into" => "_channels",
|
|
||||||
"values" => [
|
|
||||||
"name" => $channel->getName(),
|
|
||||||
"table_name" => $channel->getTableName(),
|
|
||||||
"class" => get_class($channel),
|
|
||||||
],
|
|
||||||
"suffix" => "on conflict do nothing",
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
|
||||||
$this->db->exec([
|
|
||||||
"delete",
|
|
||||||
"from" => "_channels",
|
|
||||||
"where" => [
|
|
||||||
"name" => $channel->getName(),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _exists(CapacitorChannel $channel): bool {
|
function _exists(CapacitorChannel $channel): bool {
|
||||||
return $this->tableExists($channel->getTableName());
|
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;
|
namespace nulib\ext;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use nulib\ext\json\JsonException;
|
|
||||||
use nulib\file;
|
use nulib\file;
|
||||||
use nulib\os\IOException;
|
use nulib\os\IOException;
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use DateTimeInterface;
|
|||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\file\TempStream;
|
use nulib\file\TempStream;
|
||||||
use nulib\os\path;
|
use nulib\os\path;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\php\time\DateTime;
|
use nulib\php\time\DateTime;
|
||||||
use nulib\web\http;
|
use nulib\web\http;
|
||||||
|
|
||||||
@ -35,13 +35,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
$this->rows = $rows;
|
$this->rows = $rows;
|
||||||
$this->index = 0;
|
$this->index = 0;
|
||||||
$cookFunc = $params["cook_func"] ?? null;
|
$cookFunc = $params["cook_func"] ?? null;
|
||||||
$cookCtx = $cookArgs = null;
|
if ($cookFunc !== null) $cookFunc = func::with($cookFunc)->bind($this, true);
|
||||||
if ($cookFunc !== null) {
|
$this->cookFunc = $cookFunc;
|
||||||
nur_func::ensure_func($cookFunc, $this, $cookArgs);
|
|
||||||
$cookCtx = nur_func::_prepare($cookFunc);
|
|
||||||
}
|
|
||||||
$this->cookCtx = $cookCtx;
|
|
||||||
$this->cookArgs = $cookArgs;
|
|
||||||
$this->output = $params["output"] ?? static::OUTPUT;
|
$this->output = $params["output"] ?? static::OUTPUT;
|
||||||
$maxMemory = $params["max_memory"] ?? null;
|
$maxMemory = $params["max_memory"] ?? null;
|
||||||
$throwOnError = $params["throw_on_error"] ?? null;
|
$throwOnError = $params["throw_on_error"] ?? null;
|
||||||
@ -60,9 +55,7 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
|
|
||||||
protected ?string $output;
|
protected ?string $output;
|
||||||
|
|
||||||
protected ?array $cookCtx;
|
protected ?func $cookFunc;
|
||||||
|
|
||||||
protected ?array $cookArgs;
|
|
||||||
|
|
||||||
protected function ensureHeaders(?array $row=null): void {
|
protected function ensureHeaders(?array $row=null): void {
|
||||||
if ($this->headers !== null || !$this->useHeaders) return;
|
if ($this->headers !== null || !$this->useHeaders) return;
|
||||||
@ -87,9 +80,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function cookRow(?array $row): ?array {
|
protected function cookRow(?array $row): ?array {
|
||||||
if ($this->cookCtx !== null) {
|
if ($this->cookFunc !== null) {
|
||||||
$args = cl::merge([$row], $this->cookArgs);
|
$row = $this->cookFunc->prependArgs([$row])->invoke();
|
||||||
$row = nur_func::_call($this->cookCtx, $args);
|
|
||||||
}
|
}
|
||||||
if ($row !== null) {
|
if ($row !== null) {
|
||||||
foreach ($row as &$col) {
|
foreach ($row as &$col) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
namespace nulib\output;
|
namespace nulib\output;
|
||||||
|
|
||||||
use nulib\output\std\ProxyMessenger;
|
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
|
* 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 !== null && $log !== false) {
|
||||||
if ($log instanceof IMessenger) log::set_messenger($log);
|
if ($log instanceof IMessenger) log::set_messenger($log);
|
||||||
elseif (is_string($log)) log::set_messenger_class($log);
|
elseif (is_string($log)) log::set_messenger_class($log);
|
||||||
elseif (is_array($log)) {
|
else $log = func::call($log);
|
||||||
nur_func::ensure_class($log, $args);
|
|
||||||
$log = nur_func::cons($log, $args);
|
|
||||||
}
|
|
||||||
log::set_messenger($log);
|
log::set_messenger($log);
|
||||||
$msgs[] = $log;
|
$msgs[] = $log;
|
||||||
}
|
}
|
||||||
if ($console !== null && $console !== false) {
|
if ($console !== null && $console !== false) {
|
||||||
if ($console instanceof IMessenger) console::set_messenger($console);
|
if ($console instanceof IMessenger) console::set_messenger($console);
|
||||||
elseif (is_string($console)) console::set_messenger_class($console);
|
elseif (is_string($console)) console::set_messenger_class($console);
|
||||||
elseif (is_array($console)) {
|
else $console = func::call($console);
|
||||||
nur_func::ensure_class($console, $args);
|
|
||||||
$console = nur_func::cons($console, $args);
|
|
||||||
}
|
|
||||||
console::set_messenger($console);
|
console::set_messenger($console);
|
||||||
$msgs[] = $console;
|
$msgs[] = $console;
|
||||||
}
|
}
|
||||||
if ($say !== null && $say !== false) {
|
if ($say !== null && $say !== false) {
|
||||||
if ($say instanceof IMessenger) say::set_messenger($say);
|
if ($say instanceof IMessenger) say::set_messenger($say);
|
||||||
elseif (is_string($say)) say::set_messenger_class($say);
|
elseif (is_string($say)) say::set_messenger_class($say);
|
||||||
elseif (is_array($say)) {
|
else $say = func::call($say);
|
||||||
nur_func::ensure_class($say, $args);
|
|
||||||
$say = nur_func::cons($say, $args);
|
|
||||||
}
|
|
||||||
say::set_messenger($say);
|
say::set_messenger($say);
|
||||||
$msgs[] = $say;
|
$msgs[] = $say;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ namespace nulib\php\content;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class c: classe outil pour gérer du contenu
|
* 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
|
# 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()
|
# ce contenu est rajouté à la suite après avoir été quoté avec self::q()
|
||||||
$func = $value;
|
$func = $value;
|
||||||
nur_func::ensure_func($func, $object_or_class, $args);
|
$values = self::q(func::call($func));
|
||||||
$values = self::q(nur_func::call($func, ...$args));
|
|
||||||
self::add_static_content($dest, $values, $key, $seq);
|
self::add_static_content($dest, $values, $key, $seq);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -83,16 +82,7 @@ class c {
|
|||||||
$arg = self::resolve($arg, $object_or_class, false);
|
$arg = self::resolve($arg, $object_or_class, false);
|
||||||
if (!$array) $arg = $arg[0];
|
if (!$array) $arg = $arg[0];
|
||||||
}; unset($arg);
|
}; unset($arg);
|
||||||
if (nur_func::is_static($func)) {
|
$value = func::with($func, $args)->bind($object_or_class, true)->invoke();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($seq) $dest[] = $value;
|
if ($seq) $dest[] = $value;
|
||||||
|
@ -3,13 +3,16 @@ namespace nulib\php;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Generator;
|
||||||
use nulib\A;
|
use nulib\A;
|
||||||
|
use nulib\cl;
|
||||||
use nulib\cv;
|
use nulib\cv;
|
||||||
use nulib\StateException;
|
use nulib\StateException;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionFunction;
|
use ReflectionFunction;
|
||||||
use ReflectionMethod;
|
use ReflectionMethod;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class func: outils pour appeler fonctions et méthodes dynamiquement
|
* 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)
|
* la fonction (ne pas uniquement faire une vérification syntaxique)
|
||||||
*/
|
*/
|
||||||
static function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool {
|
static function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionFunction) return true;
|
if ($func instanceof ReflectionFunction) return true;
|
||||||
if (is_string($func)) {
|
if (is_string($func)) {
|
||||||
$c = false;
|
$c = false;
|
||||||
@ -82,11 +82,11 @@ class func {
|
|||||||
if ($strict) {
|
if ($strict) {
|
||||||
$reason = null;
|
$reason = null;
|
||||||
if (class_exists($f)) {
|
if (class_exists($f)) {
|
||||||
$reason = "$msg: is a class";
|
$reason = "$f: is a class";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!function_exists($f)) {
|
if (!function_exists($f)) {
|
||||||
$reason = "$msg: function not found";
|
$reason = "$f: function not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,10 +117,7 @@ class func {
|
|||||||
* faire une vérification syntaxique)
|
* faire une vérification syntaxique)
|
||||||
*/
|
*/
|
||||||
static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool {
|
static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionClass) return true;
|
if ($func instanceof ReflectionClass) return true;
|
||||||
if (is_string($func)) {
|
if (is_string($func)) {
|
||||||
$c = $func;
|
$c = $func;
|
||||||
@ -138,12 +135,10 @@ class func {
|
|||||||
if (self::_parse_static($c)) return false;
|
if (self::_parse_static($c)) return false;
|
||||||
if (self::_parse_method($c)) return false;
|
if (self::_parse_method($c)) return false;
|
||||||
if ($f !== false) return false;
|
if ($f !== false) return false;
|
||||||
if ($strict) {
|
if ($strict && !class_exists($c)) {
|
||||||
if (!class_exists($c)) {
|
$reason = "$c: class not found";
|
||||||
$reason = "$msg: class not found";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$func = [$c, false];
|
$func = [$c, false];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -207,10 +202,7 @@ class func {
|
|||||||
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
* 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 {
|
static function verifix_static(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionMethod) {
|
if ($func instanceof ReflectionMethod) {
|
||||||
$bound = false;
|
$bound = false;
|
||||||
return true;
|
return true;
|
||||||
@ -265,18 +257,19 @@ class func {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($strict) {
|
if ($strict) {
|
||||||
|
[$c, $f] = $cf;
|
||||||
$reason = null;
|
$reason = null;
|
||||||
if ($bound) {
|
if ($bound) {
|
||||||
if (!class_exists($c)) {
|
if (!class_exists($c)) {
|
||||||
$reason = "$msg: class not found";
|
$reason = "$c: class not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!method_exists($c, $f)) {
|
if (!method_exists($c, $f)) {
|
||||||
$reason = "$msg: method not found";
|
$reason = "$c::$f: method not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$reason = "$msg: not bound";
|
$reason = "$c::$f: not bound";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$func = $cf;
|
$func = $cf;
|
||||||
@ -342,10 +335,7 @@ class func {
|
|||||||
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
* 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 {
|
static function verifix_method(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionMethod) {
|
if ($func instanceof ReflectionMethod) {
|
||||||
$bound = false;
|
$bound = false;
|
||||||
return true;
|
return true;
|
||||||
@ -401,18 +391,19 @@ class func {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($strict) {
|
if ($strict) {
|
||||||
|
[$c, $f] = $cf;
|
||||||
$reason = null;
|
$reason = null;
|
||||||
if ($bound) {
|
if ($bound) {
|
||||||
if (!is_object($c) && !class_exists($c)) {
|
if (!is_object($c) && !class_exists($c)) {
|
||||||
$reason = "$msg: class not found";
|
$reason = "$c: class not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!method_exists($c, $f)) {
|
if (!method_exists($c, $f)) {
|
||||||
$reason = "$msg: method not found";
|
$reason = "$c::$f: method not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$reason = "$msg: not bound";
|
$reason = "$c::$f: not bound";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$func = $cf;
|
$func = $cf;
|
||||||
@ -446,7 +437,7 @@ class func {
|
|||||||
return new ValueException($reason);
|
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 (!is_array($func)) {
|
||||||
if ($func instanceof Closure) {
|
if ($func instanceof Closure) {
|
||||||
return new self(self::TYPE_CLOSURE, $func, $args);
|
return new self(self::TYPE_CLOSURE, $func, $args);
|
||||||
@ -467,6 +458,12 @@ class func {
|
|||||||
} elseif (self::verifix_static($func, $strict, $bound, $reason)) {
|
} elseif (self::verifix_static($func, $strict, $bound, $reason)) {
|
||||||
return new self(self::TYPE_STATIC, $func, $args, $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);
|
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) {
|
static function call($func, ...$args) {
|
||||||
return self::with($func)->invoke($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) {
|
protected function __construct(int $type, $func, ?array $args=null, bool $bound=false, ?string $reason=null) {
|
||||||
@ -561,6 +593,27 @@ class func {
|
|||||||
|
|
||||||
protected int $maxArgs;
|
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 {
|
protected function updateReflection($reflection): void {
|
||||||
$variadic = false;
|
$variadic = false;
|
||||||
$minArgs = $maxArgs = 0;
|
$minArgs = $maxArgs = 0;
|
||||||
@ -596,11 +649,16 @@ class func {
|
|||||||
else return $this->bound && $this->object !== null;
|
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->type !== self::TYPE_METHOD) return $this;
|
||||||
|
if ($this->bound && $unlessAlreadyBound) return $this;
|
||||||
|
|
||||||
[$c, $f] = $this->func;
|
[$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->func[0] = $c = $object;
|
||||||
$this->updateReflection(new ReflectionMethod($c, $f));
|
$this->updateReflection(new ReflectionMethod($c, $f));
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class mprop {
|
|||||||
} catch (ReflectionException $e) {
|
} catch (ReflectionException $e) {
|
||||||
return oprop::get($object, $property, $default);
|
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é */
|
/** spécifier la valeur d'une propriété */
|
||||||
@ -60,7 +60,7 @@ class mprop {
|
|||||||
} catch (ReflectionException $e) {
|
} catch (ReflectionException $e) {
|
||||||
return oprop::_set($c, $object, $property, $value);
|
return oprop::_set($c, $object, $property, $value);
|
||||||
}
|
}
|
||||||
nur_func::call([$object, $m], $value);
|
func::call([$object, $m], $value);
|
||||||
return $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 {
|
class ref_schema {
|
||||||
/** @var array schéma des natures de schéma */
|
/** @var array schéma des natures de schéma */
|
||||||
const NATURE_METASCHEMA = [
|
const NATURE_METASCHEMA = [
|
||||||
"nature" => ["string", null, "nature du schéma",
|
0 => ["string", null, "nature du schéma",
|
||||||
"pkey" => 0,
|
"allowed_values" => ["scalar", "assoc", "list"],
|
||||||
"allowed_values" => ["assoc", "list", "scalar"],
|
|
||||||
],
|
],
|
||||||
"title" => ["?string", null, "libellé de la valeur"],
|
"compute_func" => ["?callable", null, "fonction qui calcule les valeurs des champs computed"],
|
||||||
"required" => ["bool", false, "la valeur est-elle requise?"],
|
"validate_func" => ["?callable", null, "fonction qui vérifie la conformité de l'objet dans son ensemble"],
|
||||||
"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"],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var array meta-schema d'un schéma de nature scalaire */
|
/** @var array meta-schéma d'une valeur */
|
||||||
const SCALAR_METASCHEMA = [
|
const VALUE_METASCHEMA = [
|
||||||
"type" => ["array", null, "types possibles de la valeur", "required" => true],
|
"type" => ["array", null, "types possibles de la valeur", "required" => true],
|
||||||
"default" => [null, null, "valeur par défaut si la valeur n'existe pas"],
|
"default" => [null, null, "valeur par défaut si la valeur n'existe pas"],
|
||||||
"title" => ["?string", null, "libellé de la valeur"],
|
"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"],
|
"messages" => ["?array", null, "messages à afficher en cas d'erreur d'analyse"],
|
||||||
"formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
|
"formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
|
||||||
"format" => [null, null, "format à utiliser pour l'affichage"],
|
"format" => [null, null, "format à utiliser pour l'affichage"],
|
||||||
"" => ["array", "scalar", "nature du schéma",
|
"size" => ["?int", null, "nom de caractères ou de chiffres de la valeur"],
|
||||||
"" => ["assoc", "schema" => self::NATURE_METASCHEMA],
|
"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"],
|
"name" => ["?string", null, "identifiant de la valeur"],
|
||||||
"pkey" => ["?pkey", null, "chemin de clé de la valeur dans un tableau associatif"],
|
"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"],
|
"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 = [
|
const MESSAGES = [
|
||||||
"missing" => "{key}: Vous devez spécifier cette valeur",
|
"missing" => "vous devez spécifier cette valeur",
|
||||||
"unavailable" => "{key}: Vous devez spécifier cette valeur",
|
"unavailable" => "vous devez spécifier cette valeur",
|
||||||
"null" => "{key}: cette valeur ne doit pas être nulle",
|
"null" => "cette valeur ne doit pas être nulle",
|
||||||
"empty" => "{key}: cette valeur ne doit pas être vide",
|
"empty" => "cette valeur ne doit pas être vide",
|
||||||
"invalid" => "{key}: {orig}: cette valeur est invalide",
|
"invalid" => "cette valeur est invalide",
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var array meta-schema d'un schéma de nature associative */
|
const PARAMS_SCHEMA = [
|
||||||
const ASSOC_METASCHEMA = [
|
"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 */
|
/** @var array clés supplémentaires de schéma de la nature scalaire */
|
||||||
const LIST_METASCHEMA = [
|
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",
|
"boolean" => "bool",
|
||||||
"integer" => "int",
|
"integer" => "int",
|
||||||
"flt" => "float", "double" => "float", "dbl" => "float",
|
"flt" => "float", "double" => "float", "dbl" => "float",
|
||||||
|
"func" => "callable", "function" => "callable",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,21 @@ class str {
|
|||||||
return true;
|
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
|
* 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