Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
3c8ef57246 | |||
529633e3ba | |||
7d4c385492 | |||
26821f2a1a | |||
94feff75db | |||
368de874b4 | |||
7587572f2c | |||
4208412189 | |||
52736d8af0 | |||
4a9eecbda8 | |||
8cbdf25372 | |||
c7e6571c08 | |||
f221342c09 | |||
6238e0b9ff | |||
8cc8baca10 | |||
8f5c30c8c1 | |||
ad801d8486 | |||
2c45aa677a | |||
1f14faf08c | |||
aeaf3eb1dd | |||
e3a9d342d0 | |||
9107399805 | |||
c0f330ca82 | |||
1b8b3cf27f | |||
48941f9f90 | |||
792e1ff965 | |||
e90d5cf883 | |||
12fe3a65a5 | |||
3e642b84bc | |||
599d646372 | |||
933b6cec3b | |||
4f17d19609 | |||
b71e879823 | |||
2c7020f44d | |||
f34694e12d | |||
5dc2d3d019 | |||
ce337ce3bc | |||
0945a86763 | |||
6bcd8d4cf6 | |||
70c82ae504 | |||
8b7b318acd | |||
9cf7ac908f | |||
a44da62b94 | |||
04e7dab54e | |||
d704ce8c07 | |||
a82143a41b | |||
0d47b2a757 | |||
8fcf865bf0 | |||
0467934a82 | |||
5f4cdd2a76 | |||
2f17d85121 | |||
5ecc9ac3d7 | |||
88436296e7 | |||
8cab7b6064 | |||
9676be2dd6 | |||
c8f7f7cbaa | |||
7b118682db | |||
add9cfde3b | |||
2c63bcc1b3 |
.idea
.udirCHANGES.mdREADME.mdTODO.mdbash/src
bin
._pman-composer_local_deps.php._pman-composer_select_profile.php_merge82_pman-composer_local_deps.php_pman-composer_select_profile.phpjsondiffrunphp
composer.jsoncomposer.lockphp
src
A.phpTODO.md
app
cl.phpcv.phpdb
ext
file.phpfile
output
php
str.phptools
txt.phpsupport
tests
runphp
buildphpwrapper-.launcher.phpphpwrapper-_bg_launcher.phpphpwrapper-_wrapper.shrunphpupdate-runphp.sh
wip
1
.idea/nulib-base.iml
generated
1
.idea/nulib-base.iml
generated
@ -4,6 +4,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/php/src" isTestSource="false" packagePrefix="nulib\" />
|
<sourceFolder url="file://$MODULE_DIR$/php/src" isTestSource="false" packagePrefix="nulib\" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/php/tests" isTestSource="true" packagePrefix="nulib\" />
|
<sourceFolder url="file://$MODULE_DIR$/php/tests" isTestSource="true" packagePrefix="nulib\" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/php/vendor" />
|
<excludeFolder url="file://$MODULE_DIR$/php/vendor" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
14
.idea/php-test-framework.xml
generated
Normal file
14
.idea/php-test-framework.xml
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PhpTestFrameworkVersionCache">
|
||||||
|
<tools_cache>
|
||||||
|
<tool tool_name="PHPUnit">
|
||||||
|
<cache>
|
||||||
|
<versions>
|
||||||
|
<info id="Local/php/vendor/autoload.php" version="9.6.23" />
|
||||||
|
</versions>
|
||||||
|
</cache>
|
||||||
|
</tool>
|
||||||
|
</tools_cache>
|
||||||
|
</component>
|
||||||
|
</project>
|
12
.idea/php.xml
generated
12
.idea/php.xml
generated
@ -1,5 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="MessDetector">
|
||||||
|
<phpmd_settings>
|
||||||
|
<phpmd_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" />
|
||||||
|
</phpmd_settings>
|
||||||
|
</component>
|
||||||
<component name="MessDetectorOptionsConfiguration">
|
<component name="MessDetectorOptionsConfiguration">
|
||||||
<option name="transferred" value="true" />
|
<option name="transferred" value="true" />
|
||||||
</component>
|
</component>
|
||||||
@ -10,6 +15,11 @@
|
|||||||
<option name="highlightLevel" value="WARNING" />
|
<option name="highlightLevel" value="WARNING" />
|
||||||
<option name="transferred" value="true" />
|
<option name="transferred" value="true" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="PhpCodeSniffer">
|
||||||
|
<phpcs_settings>
|
||||||
|
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" />
|
||||||
|
</phpcs_settings>
|
||||||
|
</component>
|
||||||
<component name="PhpIncludePathManager">
|
<component name="PhpIncludePathManager">
|
||||||
<include_path>
|
<include_path>
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-ctype" />
|
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-ctype" />
|
||||||
@ -55,7 +65,7 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="PhpUnit">
|
<component name="PhpUnit">
|
||||||
<phpunit_settings>
|
<phpunit_settings>
|
||||||
<PhpUnitSettings configuration_file_path="$PROJECT_DIR$/php/vendor/sebastian/object-enumerator/phpunit.xml" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" use_configuration_file="true" />
|
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/php/vendor/autoload.php" phpunit_phar_path="" />
|
||||||
</phpunit_settings>
|
</phpunit_settings>
|
||||||
</component>
|
</component>
|
||||||
<component name="PsalmOptionsConfiguration">
|
<component name="PsalmOptionsConfiguration">
|
||||||
|
10
.idea/phpunit.xml
generated
Normal file
10
.idea/phpunit.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PHPUnit">
|
||||||
|
<option name="directories">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/tests" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
2
.udir
2
.udir
@ -9,7 +9,7 @@ uinc_options=()
|
|||||||
uinc_args=()
|
uinc_args=()
|
||||||
preconfig_scripts=()
|
preconfig_scripts=()
|
||||||
configure_variables=(dest)
|
configure_variables=(dest)
|
||||||
configure_dest_for=(lib/profile.d/nulib-base)
|
configure_dest_for=(lib/profile.d/nulib)
|
||||||
config_scripts=(lib/uinst/conf)
|
config_scripts=(lib/uinst/conf)
|
||||||
install_profiles=true
|
install_profiles=true
|
||||||
profiledir=lib/profile.d
|
profiledir=lib/profile.d
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
## Release 0.6.1p82 du 03/06/2025-10:22
|
||||||
|
|
||||||
## Release 0.6.1p74 du 03/06/2025-10:21
|
## Release 0.6.1p74 du 03/06/2025-10:21
|
||||||
|
|
||||||
* `51ce95e` maj doc
|
* `51ce95e` maj doc
|
||||||
|
@ -38,6 +38,8 @@ echo "commit=$commit"
|
|||||||
# reprendre la valeur affichée par la précédente commande
|
# reprendre la valeur affichée par la précédente commande
|
||||||
commit=XXX
|
commit=XXX
|
||||||
|
|
||||||
|
pu
|
||||||
|
|
||||||
git checkout dev74
|
git checkout dev74
|
||||||
|
|
||||||
git cherry-pick "$commit"
|
git cherry-pick "$commit"
|
||||||
|
16
TODO.md
16
TODO.md
@ -1,5 +1,6 @@
|
|||||||
# nulib
|
# nulib/bash
|
||||||
|
|
||||||
|
* [nulib/bash](bash/TODO.md)
|
||||||
* runners
|
* runners
|
||||||
* [ ] rnlphp -- lancer un programme php avec la bonne version (+docker le cas échéant)
|
* [ ] rnlphp -- lancer un programme php avec la bonne version (+docker le cas échéant)
|
||||||
* [ ] utilisable en shebang
|
* [ ] utilisable en shebang
|
||||||
@ -12,4 +13,17 @@
|
|||||||
* [ ] rnlsh -- lancer un shell avec les librairies bash / lancer un script
|
* [ ] rnlsh -- lancer un shell avec les librairies bash / lancer un script
|
||||||
* MYTRUEDIR, MYTRUENAME, MYTRUESELF -- résoudre les liens symboliques
|
* MYTRUEDIR, MYTRUENAME, MYTRUESELF -- résoudre les liens symboliques
|
||||||
|
|
||||||
|
# nulib/php
|
||||||
|
|
||||||
|
* [nulib](php/src/TODO.md)
|
||||||
|
* [nulib\app](php/src/app/TODO.md)
|
||||||
|
* [nulib\db](php/src/db/TODO.md)
|
||||||
|
* [nulib\os](php/src/os/TODO.md)
|
||||||
|
* [nulib\output](php/src/output/TODO.md)
|
||||||
|
* [nulib\php\time](php/src/php/time/TODO.md)
|
||||||
|
|
||||||
|
vrac:
|
||||||
|
* PID dans les logs
|
||||||
|
* build --ci
|
||||||
|
|
||||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
@ -6,7 +6,8 @@ function __esection() {
|
|||||||
local length="${COLUMNS:-80}"
|
local length="${COLUMNS:-80}"
|
||||||
setx lsep=__complete "$prefix" "$length" -
|
setx lsep=__complete "$prefix" "$length" -
|
||||||
|
|
||||||
recho "$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"
|
||||||
|
@ -6,7 +6,8 @@ function __esection() {
|
|||||||
local length="${COLUMNS:-80}"
|
local length="${COLUMNS:-80}"
|
||||||
setx lsep=__complete "$prefix" "$length" -
|
setx lsep=__complete "$prefix" "$length" -
|
||||||
|
|
||||||
recho "$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"
|
||||||
|
@ -184,7 +184,7 @@ function __nulib_args_parse_args() {
|
|||||||
*) die "Invalid arg definition: expected option, got '$1'" || return;;
|
*) 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 __longdef __witharg __valdesc
|
local __def __longdef __witharg __valdesc __defvaldesc
|
||||||
__witharg=
|
__witharg=
|
||||||
for __def in "${__defs[@]}"; do
|
for __def in "${__defs[@]}"; do
|
||||||
if [ "${__def%::*}" != "$__def" ]; then
|
if [ "${__def%::*}" != "$__def" ]; then
|
||||||
@ -346,16 +346,19 @@ $prefix$usage"
|
|||||||
fi
|
fi
|
||||||
# est-ce que l'option prend un argument?
|
# est-ce que l'option prend un argument?
|
||||||
__witharg=
|
__witharg=
|
||||||
__valdesc=value
|
__valdesc=
|
||||||
|
__defvaldesc=value
|
||||||
for __def in "${__defs[@]}"; do
|
for __def in "${__defs[@]}"; do
|
||||||
if [ "${__def%::*}" != "$__def" ]; then
|
if [ "${__def%::*}" != "$__def" ]; then
|
||||||
[ "$__witharg" != : ] && __witharg=::
|
[ "$__witharg" != : ] && __witharg=::
|
||||||
[ -n "${__def#*::}" ] && __valdesc="[${__def#*::}]"
|
[ -n "${__def#*::}" ] && __valdesc="[${__def#*::}]"
|
||||||
|
__defvaldesc="[value]"
|
||||||
elif [ "${__def%:*}" != "$__def" ]; then
|
elif [ "${__def%:*}" != "$__def" ]; then
|
||||||
__witharg=:
|
__witharg=:
|
||||||
[ -n "${__def#*:}" ] && __valdesc="${__def#*:}"
|
[ -n "${__def#*:}" ] && __valdesc="${__def#*:}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
[ -n "$__valdesc" ] || __valdesc="$__defvaldesc"
|
||||||
# description de l'option
|
# description de l'option
|
||||||
local first=1 thelp tdesc
|
local first=1 thelp tdesc
|
||||||
for __def in "${__defs[@]}"; do
|
for __def in "${__defs[@]}"; do
|
||||||
|
@ -21,6 +21,13 @@ if [ -z "$NULIB_NO_INIT_ENV" ]; then
|
|||||||
fi
|
fi
|
||||||
[ -n "$NULIBDIR" ] || NULIBDIR="$MYDIR"
|
[ -n "$NULIBDIR" ] || NULIBDIR="$MYDIR"
|
||||||
|
|
||||||
|
# Si le script courant est un lien, calculer le répertoire destination
|
||||||
|
if [ -n "$MYREALSELF" -a -n "$MYSELF" ]; then
|
||||||
|
MYREALSELF="$(readlink -f "$MYSELF")"
|
||||||
|
MYREALDIR="$(dirname -- "$MYREALSELF")"
|
||||||
|
MYREALNAME="$(basename -- "$MYREALSELF")"
|
||||||
|
fi
|
||||||
|
|
||||||
# Repertoire temporaire
|
# Repertoire temporaire
|
||||||
[ -z "$TMPDIR" -a -d "$HOME/tmp" ] && TMPDIR="$HOME/tmp"
|
[ -z "$TMPDIR" -a -d "$HOME/tmp" ] && TMPDIR="$HOME/tmp"
|
||||||
[ -z "$TMPDIR" ] && TMPDIR="${TMP:-${TEMP:-/tmp}}"
|
[ -z "$TMPDIR" ] && TMPDIR="${TMP:-${TEMP:-/tmp}}"
|
||||||
|
147
bash/src/install.sh
Normal file
147
bash/src/install.sh
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||||
|
##@cooked nocomments
|
||||||
|
module: install "Outils de haut niveau pour installer des fichiers de configuration"
|
||||||
|
require: base
|
||||||
|
|
||||||
|
if [ -z "$NULIB_INSTALL_CONFIGURED" ]; then
|
||||||
|
# Faut-il afficher le nom des fichiers copié/créés?
|
||||||
|
export NULIB_INSTALL_VERBOSE=1
|
||||||
|
# Faut-il afficher les destinations avec ppath?
|
||||||
|
export NULIB_INSTALL_USES_PPATH=
|
||||||
|
fi
|
||||||
|
export NULIB_INSTALL_CONFIGURED=1
|
||||||
|
|
||||||
|
function ensure_exists() {
|
||||||
|
# Créer le fichier vide "$1" s'il n'existe pas déjà, avec les permissions
|
||||||
|
# $2(=644). retourner vrai si le fichier a été créé sans erreur
|
||||||
|
[ -f "$1" ] || {
|
||||||
|
if [ -n "$NULIB_INSTALL_VERBOSE" ]; then
|
||||||
|
if [ -n "$NULIB_INSTALL_USES_PPATH" ]; then
|
||||||
|
estep "$(ppath "$1")"
|
||||||
|
else
|
||||||
|
estep "$1"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
mkdirof "$1"
|
||||||
|
local r=0
|
||||||
|
touch "$1" && chmod "${2:-644}" "$1" || r=$?
|
||||||
|
return $r
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function __nulib_install_show_args() {
|
||||||
|
if [ -z "$NULIB_INSTALL_VERBOSE" ]; then
|
||||||
|
:
|
||||||
|
elif [ -n "$NULIB_INSTALL_USES_PPATH" ]; then
|
||||||
|
estep "$1 --> $(ppath "$2")${3:+/}"
|
||||||
|
else
|
||||||
|
estep "$1 --> $2${3:+/}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy_replace() {
|
||||||
|
# Copier de façon inconditionnelle le fichier $1 vers le fichier $2, en
|
||||||
|
# réinitialisation les permissions à la valeur $3
|
||||||
|
local src="$1" dest="$2"
|
||||||
|
local srcname="$(basename -- "$src")"
|
||||||
|
|
||||||
|
[ -d "$dest" ] && dest="$dest/$srcname"
|
||||||
|
mkdirof "$dest" || return 1
|
||||||
|
|
||||||
|
if [ -n "$NULIB_INSTALL_VERBOSE" ]; then
|
||||||
|
local destarg destname slash
|
||||||
|
destarg="$(dirname -- "$dest")"
|
||||||
|
destname="$(basename -- "$dest")"
|
||||||
|
if [ "$srcname" == "$destname" ]; then
|
||||||
|
slash=1
|
||||||
|
else
|
||||||
|
destarg="$destarg/$destname"
|
||||||
|
fi
|
||||||
|
__nulib_install_show_args "$srcname" "$destarg" "$slash"
|
||||||
|
fi
|
||||||
|
local r=0
|
||||||
|
if cp "$src" "$dest"; then
|
||||||
|
if [ -n "$3" ]; then
|
||||||
|
chmod "$3" "$dest" || r=$?
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return $r
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy_new() {
|
||||||
|
# Copier le fichier "$1" vers le fichier "$2", avec les permissions $3(=644)
|
||||||
|
# Ne pas écraser le fichier destination s'il existe déjà
|
||||||
|
# Retourner vrai si le fichier a été copié sans erreur
|
||||||
|
local src="$1" dest="$2"
|
||||||
|
|
||||||
|
[ -d "$dest" ] && dest="$dest/$(basename -- "$src")"
|
||||||
|
mkdirof "$dest" || return 1
|
||||||
|
|
||||||
|
if [ ! -e "$dest" ]; then
|
||||||
|
copy_replace "$src" "$dest" "$3"
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy_update() {
|
||||||
|
# Copier le fichier "$1" vers le fichier "$2", si $2 n'existe pas, ou si $1
|
||||||
|
# a été modifié par rapport à $2. Réinitialiser le cas échéant les
|
||||||
|
# permissions à la valeur $3
|
||||||
|
# Retourner vrai si le fichier a été copié sans erreur.
|
||||||
|
local src="$1" dest="$2"
|
||||||
|
|
||||||
|
[ -d "$dest" ] && dest="$dest/$(basename -- "$src")"
|
||||||
|
mkdirof "$dest" || return 1
|
||||||
|
|
||||||
|
if [ ! -e "$dest" ]; then
|
||||||
|
copy_replace "$src" "$dest" "$3"
|
||||||
|
elif testdiff "$src" "$dest"; then
|
||||||
|
copy_replace "$src" "$dest" "$3"
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
COPY_UPDATE_ASK_DEFAULT=
|
||||||
|
function copy_update_ask() {
|
||||||
|
# Copier ou mettre à jour le fichier $1 vers le fichier $2.
|
||||||
|
# Si le fichier existe déjà, la différence est affichée, et une confirmation
|
||||||
|
# est demandée pour l'écrasement du fichier.
|
||||||
|
# si $1 commence par '-' alors on s'en sert comme option pour configurer le
|
||||||
|
# niveau d'interaction pour demander la confirmation. les paramètres sont
|
||||||
|
# alors décalés
|
||||||
|
# Retourner vrai si le fichier a été copié sans erreur.
|
||||||
|
local interopt=-c
|
||||||
|
if [[ "$1" == -* ]]; then
|
||||||
|
interopt="$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
local src="$1" dest="$2"
|
||||||
|
|
||||||
|
[ -d "$dest" ] && dest="$dest/$(basename -- "$src")"
|
||||||
|
mkdirof "$dest" || return 1
|
||||||
|
|
||||||
|
[ -f "$dest" ] || copy_replace "$src" "$dest"
|
||||||
|
if testdiff "$src" "$dest"; then
|
||||||
|
check_interaction "$interopt" && diff -u "$dest" "$src"
|
||||||
|
if ask_yesno "$interopt" "Voulez-vous remplacer $(ppath "$dest") par la nouvelle version?" "${COPY_UPDATE_ASK_DEFAULT:-C}"; then
|
||||||
|
copy_replace "$src" "$dest" "$3"
|
||||||
|
return $?
|
||||||
|
elif ! check_interaction "$interopt"; then
|
||||||
|
ewarn "Les mises à jours suivantes sont disponibles:"
|
||||||
|
diff -u "$dest" "$src"
|
||||||
|
ewarn "Le fichier $(ppath "$dest") n'a pas été mis à jour"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function link_new() {
|
||||||
|
# Si $2 n'existe pas, créer le lien symbolique $2 pointant vers $1
|
||||||
|
[ -e "$2" ] && return 0
|
||||||
|
|
||||||
|
__nulib_install_show_args "$(basename -- "$2")" "$(dirname -- "$1")"
|
||||||
|
ln -s "$1" "$2"
|
||||||
|
}
|
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__ . "/../php/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__ . "/../php/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();
|
||||||
|
}
|
10
bin/_merge82
10
bin/_merge82
@ -1,4 +1,12 @@
|
|||||||
#!/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
|
||||||
|
source "$(dirname -- "$0")/../load.sh" || exit 1
|
||||||
|
|
||||||
exec "$(dirname -- "$0")/pmer" --tech-merge -Bdev82 dev74 -a "git checkout dev74" "$@"
|
dev74=1
|
||||||
|
args=(
|
||||||
|
"merger la branche dev74 dans la branche dev82"
|
||||||
|
-n,--no-dev74 dev74= "ne pas basculer sur la branche dev74 après la fusion"
|
||||||
|
)
|
||||||
|
parse_args "$@"; set -- "${args[@]}"
|
||||||
|
|
||||||
|
exec "$MYDIR/pmer" --tech-merge -Bdev82 dev74 ${dev74:+-a "git checkout dev74"} "$@"
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/php
|
|
||||||
<?php
|
|
||||||
require __DIR__ . "/../php/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";
|
|
||||||
}
|
|
1
bin/_pman-composer_local_deps.php
Symbolic link
1
bin/_pman-composer_local_deps.php
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
runphp
|
@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/php
|
|
||||||
<?php
|
|
||||||
require __DIR__ . "/../php/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();
|
|
||||||
}
|
|
1
bin/_pman-composer_select_profile.php
Symbolic link
1
bin/_pman-composer_select_profile.php
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
runphp
|
24
bin/jsondiff
Executable file
24
bin/jsondiff
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")/../load.sh" || exit 1
|
||||||
|
|
||||||
|
args=(
|
||||||
|
"comparer deux fichiers JSON"
|
||||||
|
"first.json second.json"
|
||||||
|
)
|
||||||
|
parse_args "$@"; set -- "${args[@]}"
|
||||||
|
|
||||||
|
first="$1"; shift
|
||||||
|
[ -n "$first" ] || die "vous devez spécifier le premier fichier"
|
||||||
|
second="$1"; shift
|
||||||
|
[ -n "$second" ] || die "vous devez spécifier le deuxième fichier"
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
options=("$@")
|
||||||
|
else
|
||||||
|
options=(-u)
|
||||||
|
fi
|
||||||
|
|
||||||
|
diff "${options[@]}" \
|
||||||
|
<(jq . <"$first") \
|
||||||
|
<(jq . <"$second")
|
21
bin/runphp
21
bin/runphp
@ -20,14 +20,25 @@ while true; do
|
|||||||
cd ..
|
cd ..
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -z "$PROJDIR" ]; then
|
export RUNPHP_MOUNT=
|
||||||
# s'il n'y a pas de projet, --bs est l'action par défaut
|
if [ "$MYNAME" == composer ]; then
|
||||||
[ $# -gt 0 ] || set -- --bs --ue
|
|
||||||
elif [ "$MYNAME" == composer ]; then
|
|
||||||
set -- composer "$@"
|
set -- composer "$@"
|
||||||
|
elif [[ "$MYNAME" == *.php ]]; then
|
||||||
|
# frontend pour une commande php
|
||||||
|
set -- php "$MYDIR/.$MYNAME" "$@"
|
||||||
|
# s'assurer que NULIBDIR est monté
|
||||||
|
RUNPHP_MOUNT="$NULIBDIR"
|
||||||
|
elif [ $# -eq 0 ]; then
|
||||||
|
# s'il n'y a pas de projet, --bs est l'action par défaut
|
||||||
|
[ -n "$PROJDIR" ] || set -- --bs --ue
|
||||||
else
|
else
|
||||||
case "$1" in
|
case "$1" in
|
||||||
*.php|*.phar) set -- php "$@";;
|
*.php|*.phar)
|
||||||
|
set -- php "$@"
|
||||||
|
# s'assurer que le répertoire du script est monté
|
||||||
|
setx RUNPHP_MOUNT=dirname "$1"
|
||||||
|
setx RUNPHP_MOUNT=abspath "$RUNPHP_MOUNT"
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"nulib/tests": "^7.4",
|
"nulib/tests": "^7.4",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"ext-pcntl": "*",
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
|
28
composer.lock
generated
28
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": "2d630ab5ff0ffe6139447cf93c362ed7",
|
"content-hash": "71744d15224f445d1aeefe16ec7d1099",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "symfony/deprecation-contracts",
|
"name": "symfony/deprecation-contracts",
|
||||||
@ -301,16 +301,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "myclabs/deep-copy",
|
"name": "myclabs/deep-copy",
|
||||||
"version": "1.13.1",
|
"version": "1.13.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||||
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
|
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
|
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
|
||||||
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
|
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
|
||||||
"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.13.1"
|
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -357,20 +357,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-04-29T12:36:36+00:00"
|
"time": "2025-07-05T12:25:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/php-parser",
|
"name": "nikic/php-parser",
|
||||||
"version": "v5.4.0",
|
"version": "v5.5.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||||
"reference": "447a020a1f875a434d62f2a401f53b82a396e494"
|
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
|
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
|
||||||
"reference": "447a020a1f875a434d62f2a401f53b82a396e494",
|
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -413,9 +413,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0"
|
"source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
|
||||||
},
|
},
|
||||||
"time": "2024-12-30T11:07:19+00:00"
|
"time": "2025-05-31T08:24:38+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nulib/tests",
|
"name": "nulib/tests",
|
||||||
@ -2027,6 +2027,8 @@
|
|||||||
"php": "^7.4"
|
"php": "^7.4"
|
||||||
},
|
},
|
||||||
"platform-dev": {
|
"platform-dev": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"ext-pcntl": "*",
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
|
@ -218,10 +218,10 @@ class A {
|
|||||||
static final function filter_f($dest): void { self::filter_if($dest, [cv::class, "f"]);}
|
static final function filter_f($dest): void { self::filter_if($dest, [cv::class, "f"]);}
|
||||||
static final function filter_pt($dest): void { self::filter_if($dest, [cv::class, "pt"]);}
|
static final function filter_pt($dest): void { self::filter_if($dest, [cv::class, "pt"]);}
|
||||||
static final function filter_pf($dest): void { self::filter_if($dest, [cv::class, "pf"]);}
|
static final function filter_pf($dest): void { self::filter_if($dest, [cv::class, "pf"]);}
|
||||||
static final function filter_equals($dest, $value): void { self::filter_if($dest, cv::equals($value)); }
|
static final function filter_equals($dest, $value): void { self::filter_if($dest, cv::Fequals($value)); }
|
||||||
static final function filter_not_equals($dest, $value): void { self::filter_if($dest, cv::not_equals($value)); }
|
static final function filter_not_equals($dest, $value): void { self::filter_if($dest, cv::Fnot_equals($value)); }
|
||||||
static final function filter_same($dest, $value): void { self::filter_if($dest, cv::same($value)); }
|
static final function filter_same($dest, $value): void { self::filter_if($dest, cv::Fsame($value)); }
|
||||||
static final function filter_not_same($dest, $value): void { self::filter_if($dest, cv::not_same($value)); }
|
static final function filter_not_same($dest, $value): void { self::filter_if($dest, cv::Fnot_same($value)); }
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
|
5
php/src/TODO.md
Normal file
5
php/src/TODO.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# nulib
|
||||||
|
|
||||||
|
* [ ] support de UserException pour ExceptionShadow: distinguer userMessage et techMessage
|
||||||
|
|
||||||
|
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
9
php/src/app/TODO.md
Normal file
9
php/src/app/TODO.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# nulib\app
|
||||||
|
|
||||||
|
* [ ] ajouter des méthodes normalisées `app::get_cachedir()` et
|
||||||
|
`app::get_cachefile($name)` avec la valeur par défaut
|
||||||
|
`cachedir = $vardir/cache`
|
||||||
|
* [ ] `app::action()` et `app::step()` appellent automatiquement
|
||||||
|
`app::_dispatch_signals()`
|
||||||
|
|
||||||
|
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
@ -2,6 +2,9 @@
|
|||||||
namespace nulib\app;
|
namespace nulib\app;
|
||||||
|
|
||||||
use nulib\A;
|
use nulib\A;
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\cv;
|
||||||
|
use nulib\file;
|
||||||
use nulib\str;
|
use nulib\str;
|
||||||
|
|
||||||
class args {
|
class args {
|
||||||
@ -10,7 +13,10 @@ class args {
|
|||||||
* - ["myArg" => $value] devient ["--my-arg", "$value"]
|
* - ["myArg" => $value] devient ["--my-arg", "$value"]
|
||||||
* - ["myOpt" => true] devient ["--my-opt"]
|
* - ["myOpt" => true] devient ["--my-opt"]
|
||||||
* - ["myOpt" => false] est omis
|
* - ["myOpt" => false] est omis
|
||||||
* - les autres valeurs sont prises telles quelles
|
* - les autres valeurs sont transformées en chaines puis ajoutée
|
||||||
|
*
|
||||||
|
* ainsi, ["myOpt" => "value", "myArg", "myBool" => true]
|
||||||
|
* devient ["--my-opt", "value", "myArg", "--my-bool"]
|
||||||
*/
|
*/
|
||||||
static function from_array(?array $array): array {
|
static function from_array(?array $array): array {
|
||||||
$args = [];
|
$args = [];
|
||||||
@ -36,4 +42,205 @@ class args {
|
|||||||
}
|
}
|
||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function tint(string $value): int {
|
||||||
|
return intval($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function tbool(string $value): bool {
|
||||||
|
return boolval($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function tarray(string $value): ?array {
|
||||||
|
if ($value === "") return null;
|
||||||
|
$tmparray = explode(",", $value);
|
||||||
|
$array = null;
|
||||||
|
foreach ($tmparray as $tmpvalue) {
|
||||||
|
[$tmpkey, $tmpvalue] = str::split_pair($tmpvalue);
|
||||||
|
if ($tmpvalue === null) cv::swap($tmpkey, $tmpvalue);
|
||||||
|
if ($tmpkey === null) {
|
||||||
|
$array[] = $tmpvalue;
|
||||||
|
} else {
|
||||||
|
if (str::del_suffix($tmpkey, ":int")) {
|
||||||
|
$tmpvalue = self::tint($tmpvalue);
|
||||||
|
} elseif (str::del_suffix($tmpkey, ":bool")) {
|
||||||
|
$tmpvalue = self::tbool($tmpvalue);
|
||||||
|
}
|
||||||
|
$array[$tmpkey] = $tmpvalue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convertir une liste d'arguments en tableau qui est utilisable comme un
|
||||||
|
* filtre de base de données ou des données d'une requête REST. les arguments
|
||||||
|
* peuvent être de la forme:
|
||||||
|
* - "name=value"
|
||||||
|
* qui devient dans le tableau ["name" => "value"]
|
||||||
|
* - "+arg" ou "arg"
|
||||||
|
* qui devient dans le tableau ["arg" => true]
|
||||||
|
* - "-arg" ou "~arg"
|
||||||
|
* qui est stocké dans le tableau $query ["arg" => false]
|
||||||
|
*
|
||||||
|
* si $allow_file == true, les formes d'arguments suivantes sont reconnues
|
||||||
|
* aussi:
|
||||||
|
* - "name=@file" (1 argument) OU
|
||||||
|
* "name=@" "file" (2 arguments)
|
||||||
|
* qui deviennent ["name" => new FileReader(file)]
|
||||||
|
*/
|
||||||
|
static function build_query(?array $args, bool $allow_file=true): ?array {
|
||||||
|
$query = null;
|
||||||
|
$args ??= [];
|
||||||
|
$keys = array_keys($args);
|
||||||
|
$index = 0;
|
||||||
|
$count = count($keys);
|
||||||
|
while ($index < $count) {
|
||||||
|
$arg = $args[$keys[$index++]];
|
||||||
|
[$name, $value] = str::split_pair($arg, "=");
|
||||||
|
$checkType = true;
|
||||||
|
if ($value === null) {
|
||||||
|
if (str::del_prefix($name, "+")) {
|
||||||
|
$value = true;
|
||||||
|
} elseif (str::del_prefix($name, "-") || str::del_prefix($name, "~")) {
|
||||||
|
$value = false;
|
||||||
|
} else {
|
||||||
|
$value = true;
|
||||||
|
}
|
||||||
|
} elseif ($allow_file) {
|
||||||
|
if ($value === "@") {
|
||||||
|
$value = $args[$keys[$index++]];
|
||||||
|
$value = file::reader($value);
|
||||||
|
$checkType = false;
|
||||||
|
} elseif (substr($value, 0, 1) === "@") {
|
||||||
|
$value = substr($value, 1);
|
||||||
|
$value = file::reader($value);
|
||||||
|
$checkType = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($checkType) {
|
||||||
|
if (str::del_suffix($name, ":int")) {
|
||||||
|
if (str::del_suffix($name, ":array")) {
|
||||||
|
$value = array_map([self::class, "tint"], self::tarray($value));
|
||||||
|
} else {
|
||||||
|
$value = self::tint($value);
|
||||||
|
}
|
||||||
|
} elseif (str::del_suffix($name, ":bool")) {
|
||||||
|
if (str::del_suffix($name, ":array")) {
|
||||||
|
$value = array_map([self::class, "tbool"], self::tarray($value));
|
||||||
|
} else {
|
||||||
|
$value = self::tbool($value);
|
||||||
|
}
|
||||||
|
} elseif (str::del_suffix($name, ":array")) {
|
||||||
|
$value = self::tarray($value);
|
||||||
|
if (str::del_suffix($name, ":int")) {
|
||||||
|
$value = array_map([self::class, "tint"], $value);
|
||||||
|
} elseif (str::del_suffix($name, ":bool")) {
|
||||||
|
$value = array_map([self::class, "tbool"], $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cl::has($query, $name)) {
|
||||||
|
A::ensure_array($query[$name]);
|
||||||
|
$query[$name][] = $value;
|
||||||
|
} else {
|
||||||
|
$query[$name] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convertir une liste d'arguments de façon qu'ils soient utilisables pour un
|
||||||
|
* appel de méthode. les arguments peuvent être de la forme:
|
||||||
|
* - "name=value"
|
||||||
|
* qui est stocké dans le tableau $query ["name" => "value"]
|
||||||
|
* il est possible de forcer le type de la valeur avec l'un des suffixes
|
||||||
|
* :int, :bool ou :array, e.g
|
||||||
|
* un entier: "name:int=42"
|
||||||
|
* un tableau de chaines: "name:array=a,b,c"
|
||||||
|
* un tableau d'entiers: "name:array:int=1,2,3"
|
||||||
|
* - "+arg"
|
||||||
|
* qui est stocké dans le tableau $query ["arg" => true]
|
||||||
|
* - "-arg" ou "~arg"
|
||||||
|
* qui est stocké dans le tableau $query ["arg" => false]
|
||||||
|
* - "array:sval,key:aval,..."
|
||||||
|
* qui devient l'argument ["sval", "key" => "aval", ...]
|
||||||
|
* il est possible de forcer le types des éléments avec le préfixe int: ou
|
||||||
|
* bool: e.g "array:int:1,2,3"
|
||||||
|
* - "int:value"
|
||||||
|
* qui devient l'argument intval("value")
|
||||||
|
* - "bool:value"
|
||||||
|
* qui devient l'argument boolval("value")
|
||||||
|
* - "value"
|
||||||
|
* qui devient l'argument "value"
|
||||||
|
*
|
||||||
|
* à la fin, la liste des arguments est retournée [$arguments...]
|
||||||
|
* si le tableau $query est renseigné, il est en premier dans la liste des
|
||||||
|
* arguments e.g [$query, $arguments...]
|
||||||
|
*/
|
||||||
|
static function build_method_args(?array $args): ?array {
|
||||||
|
$query = null;
|
||||||
|
$margs = [];
|
||||||
|
$args ??= [];
|
||||||
|
foreach ($args as $arg) {
|
||||||
|
[$name, $value] = str::split_pair($arg, "=");
|
||||||
|
if ($value === null) {
|
||||||
|
if (str::del_prefix($name, "+")) {
|
||||||
|
$value = true;
|
||||||
|
} elseif (str::del_prefix($name, "-") || str::del_prefix($name, "~")) {
|
||||||
|
$value = false;
|
||||||
|
} elseif (str::del_prefix($name, "int:")) {
|
||||||
|
$margs[] = self::tint($name);
|
||||||
|
continue;
|
||||||
|
} elseif (str::del_prefix($name, "bool:")) {
|
||||||
|
$margs[] = self::tbool($name);
|
||||||
|
continue;
|
||||||
|
} elseif (str::del_prefix($name, "array:")) {
|
||||||
|
if (str::del_prefix($name, "int:")) {
|
||||||
|
$map = [self::class, "tint"];
|
||||||
|
} elseif (str::del_prefix($name, "bool:")) {
|
||||||
|
$map = [self::class, "tbool"];
|
||||||
|
} else {
|
||||||
|
$map = null;
|
||||||
|
}
|
||||||
|
$value = self::tarray($name);
|
||||||
|
if ($map !== null) $value = array_map($map, $value);
|
||||||
|
$margs[] = $value;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$margs[] = $name;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (str::del_suffix($name, ":int")) {
|
||||||
|
if (str::del_suffix($name, ":array")) {
|
||||||
|
$value = array_map([self::class, "tint"], self::tarray($value));
|
||||||
|
} else {
|
||||||
|
$value = self::tint($value);
|
||||||
|
}
|
||||||
|
} elseif (str::del_suffix($name, ":bool")) {
|
||||||
|
if (str::del_suffix($name, ":array")) {
|
||||||
|
$value = array_map([self::class, "tbool"], self::tarray($value));
|
||||||
|
} else {
|
||||||
|
$value = self::tbool($value);
|
||||||
|
}
|
||||||
|
} elseif (str::del_suffix($name, ":array")) {
|
||||||
|
$value = self::tarray($value);
|
||||||
|
if (str::del_suffix($name, ":int")) {
|
||||||
|
$value = array_map([self::class, "tint"], $value);
|
||||||
|
} elseif (str::del_suffix($name, ":bool")) {
|
||||||
|
$value = array_map([self::class, "tbool"], $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cl::has($query, $name)) {
|
||||||
|
A::ensure_array($query[$name]);
|
||||||
|
$query[$name][] = $value;
|
||||||
|
} else {
|
||||||
|
$query[$name] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($query !== null) array_unshift($margs, $query);
|
||||||
|
return $margs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -817,10 +817,10 @@ class cl {
|
|||||||
static final function all_f(?array $array): bool { return self::all_if($array, [cv::class, "f"]);}
|
static final function all_f(?array $array): bool { return self::all_if($array, [cv::class, "f"]);}
|
||||||
static final function all_pt(?array $array): bool { return self::all_if($array, [cv::class, "pt"]);}
|
static final function all_pt(?array $array): bool { return self::all_if($array, [cv::class, "pt"]);}
|
||||||
static final function all_pf(?array $array): bool { return self::all_if($array, [cv::class, "pf"]);}
|
static final function all_pf(?array $array): bool { return self::all_if($array, [cv::class, "pf"]);}
|
||||||
static final function all_equals(?array $array, $value): bool { return self::all_if($array, cv::equals($value)); }
|
static final function all_equals(?array $array, $value): bool { return self::all_if($array, cv::Fequals($value)); }
|
||||||
static final function all_not_equals(?array $array, $value): bool { return self::all_if($array, cv::not_equals($value)); }
|
static final function all_not_equals(?array $array, $value): bool { return self::all_if($array, cv::Fnot_equals($value)); }
|
||||||
static final function all_same(?array $array, $value): bool { return self::all_if($array, cv::same($value)); }
|
static final function all_same(?array $array, $value): bool { return self::all_if($array, cv::Fsame($value)); }
|
||||||
static final function all_not_same(?array $array, $value): bool { return self::all_if($array, cv::not_same($value)); }
|
static final function all_not_same(?array $array, $value): bool { return self::all_if($array, cv::Fnot_same($value)); }
|
||||||
|
|
||||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -842,10 +842,10 @@ class cl {
|
|||||||
static final function any_f(?array $array): bool { return self::any_if($array, [cv::class, "f"]);}
|
static final function any_f(?array $array): bool { return self::any_if($array, [cv::class, "f"]);}
|
||||||
static final function any_pt(?array $array): bool { return self::any_if($array, [cv::class, "pt"]);}
|
static final function any_pt(?array $array): bool { return self::any_if($array, [cv::class, "pt"]);}
|
||||||
static final function any_pf(?array $array): bool { return self::any_if($array, [cv::class, "pf"]);}
|
static final function any_pf(?array $array): bool { return self::any_if($array, [cv::class, "pf"]);}
|
||||||
static final function any_equals(?array $array, $value): bool { return self::any_if($array, cv::equals($value)); }
|
static final function any_equals(?array $array, $value): bool { return self::any_if($array, cv::Fequals($value)); }
|
||||||
static final function any_not_equals(?array $array, $value): bool { return self::any_if($array, cv::not_equals($value)); }
|
static final function any_not_equals(?array $array, $value): bool { return self::any_if($array, cv::Fnot_equals($value)); }
|
||||||
static final function any_same(?array $array, $value): bool { return self::any_if($array, cv::same($value)); }
|
static final function any_same(?array $array, $value): bool { return self::any_if($array, cv::Fsame($value)); }
|
||||||
static final function any_not_same(?array $array, $value): bool { return self::any_if($array, cv::not_same($value)); }
|
static final function any_not_same(?array $array, $value): bool { return self::any_if($array, cv::Fnot_same($value)); }
|
||||||
|
|
||||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -876,10 +876,10 @@ class cl {
|
|||||||
static final function filter_f(?array $array): ?array { return self::filter_if($array, [cv::class, "f"]);}
|
static final function filter_f(?array $array): ?array { return self::filter_if($array, [cv::class, "f"]);}
|
||||||
static final function filter_pt(?array $array): ?array { return self::filter_if($array, [cv::class, "pt"]);}
|
static final function filter_pt(?array $array): ?array { return self::filter_if($array, [cv::class, "pt"]);}
|
||||||
static final function filter_pf(?array $array): ?array { return self::filter_if($array, [cv::class, "pf"]);}
|
static final function filter_pf(?array $array): ?array { return self::filter_if($array, [cv::class, "pf"]);}
|
||||||
static final function filter_equals(?array $array, $value): ?array { return self::filter_if($array, cv::equals($value)); }
|
static final function filter_equals(?array $array, $value): ?array { return self::filter_if($array, cv::Fequals($value)); }
|
||||||
static final function filter_not_equals(?array $array, $value): ?array { return self::filter_if($array, cv::not_equals($value)); }
|
static final function filter_not_equals(?array $array, $value): ?array { return self::filter_if($array, cv::Fnot_equals($value)); }
|
||||||
static final function filter_same(?array $array, $value): ?array { return self::filter_if($array, cv::same($value)); }
|
static final function filter_same(?array $array, $value): ?array { return self::filter_if($array, cv::Fsame($value)); }
|
||||||
static final function filter_not_same(?array $array, $value): ?array { return self::filter_if($array, cv::not_same($value)); }
|
static final function filter_not_same(?array $array, $value): ?array { return self::filter_if($array, cv::Fnot_same($value)); }
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class cv {
|
|||||||
* contruire une fonction qui retourne vrai si on lui passe en argument une
|
* contruire une fonction qui retourne vrai si on lui passe en argument une
|
||||||
* valeur égale à $value
|
* valeur égale à $value
|
||||||
*/
|
*/
|
||||||
static final function equals($value): callable {
|
static final function Fequals($value): callable {
|
||||||
return function ($arg) use($value) { return $arg == $value; };
|
return function ($arg) use($value) { return $arg == $value; };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class cv {
|
|||||||
* contruire une fonction qui retourne vrai si on lui passe en argument une
|
* contruire une fonction qui retourne vrai si on lui passe en argument une
|
||||||
* valeur qui n'est pas égale à $value
|
* valeur qui n'est pas égale à $value
|
||||||
*/
|
*/
|
||||||
static final function not_equals($value): callable {
|
static final function Fnot_equals($value): callable {
|
||||||
return function ($arg) use($value) { return $arg != $value; };
|
return function ($arg) use($value) { return $arg != $value; };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ class cv {
|
|||||||
* contruire une fonction qui retourne vrai si on lui passe en argument une
|
* contruire une fonction qui retourne vrai si on lui passe en argument une
|
||||||
* valeur strictement égale à $value
|
* valeur strictement égale à $value
|
||||||
*/
|
*/
|
||||||
static final function same($value): callable {
|
static final function Fsame($value): callable {
|
||||||
return function ($arg) use($value) { return $arg === $value; };
|
return function ($arg) use($value) { return $arg === $value; };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ class cv {
|
|||||||
* contruire une fonction qui retourne vrai si on lui passe en argument une
|
* contruire une fonction qui retourne vrai si on lui passe en argument une
|
||||||
* valeur qui n'est pas strictement égale à $value
|
* valeur qui n'est pas strictement égale à $value
|
||||||
*/
|
*/
|
||||||
static final function not_same($value): callable {
|
static final function Fnot_same($value): callable {
|
||||||
return function ($arg) use($value) { return $arg !== $value; };
|
return function ($arg) use($value) { return $arg !== $value; };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,6 +231,24 @@ class cv {
|
|||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tester l'égalité non stricte de deux valeurs, dans le cas où leurs types ne
|
||||||
|
* sont pas forcément cohérents: par exemple eq("1", 1) === true
|
||||||
|
*
|
||||||
|
* cette fonction est utile par exemple quand on veut comparer des données
|
||||||
|
* provenant d'une base de données: PHP a la facheuse tendance de retourner
|
||||||
|
* les nombres sous forme de chaines
|
||||||
|
*/
|
||||||
|
static final function equals($a, $b): bool {
|
||||||
|
# cette fonction existe parce que le type des valeurs retournées par la BDD
|
||||||
|
# n'est pas cohérent, "1" au lieu de 1 par exemple
|
||||||
|
if ($a === null) return $b === null;
|
||||||
|
elseif ($b === null) return $a === null;
|
||||||
|
elseif (is_scalar($a)) return strval($a) === strval($b);
|
||||||
|
# ne pas utiliser l'égalité stricte ici: ce sont des objets
|
||||||
|
else return $a == $b;
|
||||||
|
}
|
||||||
|
|
||||||
/** retourner -1, 0 ou 1 en fonction de l'ordre relatif entre $a et $b */
|
/** retourner -1, 0 ou 1 en fonction de l'ordre relatif entre $a et $b */
|
||||||
static final function compare($a, $b): int {
|
static final function compare($a, $b): int {
|
||||||
if ($a === $b) return 0;
|
if ($a === $b) return 0;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
|
use nulib\A;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\php\func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
@ -29,6 +30,11 @@ class Capacitor implements ITransactor {
|
|||||||
return $this->getStorage()->db();
|
return $this->getStorage()->db();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureLive(): self {
|
||||||
|
$this->getStorage()->ensureLive();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/** @var CapacitorChannel */
|
/** @var CapacitorChannel */
|
||||||
protected $channel;
|
protected $channel;
|
||||||
|
|
||||||
@ -138,9 +144,22 @@ class Capacitor implements ITransactor {
|
|||||||
$this->storage->_reset($this->channel, $recreate);
|
$this->storage->_reset($this->channel, $recreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function charge($item, $func=null, ?array $args=null, ?array &$values=null): int {
|
function charge($item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||||
if ($this->subChannels !== null) $this->beginTransaction();
|
if ($this->subChannels !== null) $this->beginTransaction();
|
||||||
return $this->storage->_charge($this->channel, $item, $func, $args, $values);
|
return $this->storage->_charge($this->channel, $item, $func, $args, $row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
|
||||||
|
$count = 0;
|
||||||
|
if ($items !== null) {
|
||||||
|
if ($func !== null) {
|
||||||
|
$func = func::with($func, $args)->bind($this->channel);
|
||||||
|
}
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$count += $this->charge($item, $func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
function discharge(bool $reset=true): Traversable {
|
function discharge(bool $reset=true): Traversable {
|
||||||
@ -169,11 +188,27 @@ class Capacitor implements ITransactor {
|
|||||||
return $this->storage->_delete($this->channel, $filter, $func, $args);
|
return $this->storage->_delete($this->channel, $filter, $func, $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dbUpdate(array $update) {
|
function dbAll(array $query, ?array $params=null): iterable {
|
||||||
|
$primaryKeys = $this->channel->getPrimaryKeys();
|
||||||
|
return $this->storage->db()->all(cl::merge([
|
||||||
|
"select",
|
||||||
|
"from" => $this->getTableName(),
|
||||||
|
], $query), $params, $primaryKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbOne(array $query, ?array $params=null): ?array {
|
||||||
|
return $this->storage->db()->one(cl::merge([
|
||||||
|
"select",
|
||||||
|
"from" => $this->getTableName(),
|
||||||
|
], $query), $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return int|false */
|
||||||
|
function dbUpdate(array $query, ?array $params=null) {
|
||||||
return $this->storage->db()->exec(cl::merge([
|
return $this->storage->db()->exec(cl::merge([
|
||||||
"update",
|
"update",
|
||||||
"table" => $this->getTableName(),
|
"table" => $this->getTableName(),
|
||||||
], $update));
|
], $query), $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function close(): void {
|
function close(): void {
|
||||||
|
@ -25,8 +25,6 @@ class CapacitorChannel implements ITransactor {
|
|||||||
|
|
||||||
const EACH_COMMIT_THRESHOLD = 100;
|
const EACH_COMMIT_THRESHOLD = 100;
|
||||||
|
|
||||||
const USE_CACHE = false;
|
|
||||||
|
|
||||||
static function verifix_name(?string &$name, ?string &$tableName=null): void {
|
static function verifix_name(?string &$name, ?string &$tableName=null): void {
|
||||||
if ($name !== null) {
|
if ($name !== null) {
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
@ -60,7 +58,6 @@ class CapacitorChannel implements ITransactor {
|
|||||||
$this->tableName = $tableName;
|
$this->tableName = $tableName;
|
||||||
$this->manageTransactions = $manageTransactions ?? static::MANAGE_TRANSACTIONS;
|
$this->manageTransactions = $manageTransactions ?? static::MANAGE_TRANSACTIONS;
|
||||||
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
||||||
$this->useCache = static::USE_CACHE;
|
|
||||||
$this->setup = false;
|
$this->setup = false;
|
||||||
$this->created = false;
|
$this->created = false;
|
||||||
$columnDefinitions = $this->COLUMN_DEFINITIONS();
|
$columnDefinitions = $this->COLUMN_DEFINITIONS();
|
||||||
@ -111,6 +108,8 @@ class CapacitorChannel implements ITransactor {
|
|||||||
$def = strval($def);
|
$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;
|
||||||
|
} elseif ($def === "genserial") {
|
||||||
|
$primaryKeys[] = $col;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,23 +165,6 @@ class CapacitorChannel implements ITransactor {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool faut-il passer par le cache pour les requêtes de all(), each()
|
|
||||||
* et delete()?
|
|
||||||
* ça peut être nécessaire avec MySQL/MariaDB si on utilise les requêtes non
|
|
||||||
* bufférisées, et que la fonction manipule la base de données
|
|
||||||
*/
|
|
||||||
protected bool $useCache;
|
|
||||||
|
|
||||||
function isUseCache(): bool {
|
|
||||||
return $this->useCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setUseCache(bool $useCache=true): self {
|
|
||||||
$this->useCache = $useCache;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialiser ce channel avant sa première utilisation.
|
* initialiser ce channel avant sa première utilisation.
|
||||||
*/
|
*/
|
||||||
@ -232,8 +214,18 @@ class CapacitorChannel implements ITransactor {
|
|||||||
|
|
||||||
protected ?array $migration;
|
protected ?array $migration;
|
||||||
|
|
||||||
function getMigration(): ?array {
|
function getMigration(?string $prefix=null): ?array {
|
||||||
return $this->migration;
|
if ($prefix === null || $this->migration === null) return $this->migration;
|
||||||
|
$migration = null;
|
||||||
|
str::add_suffix($prefix, ":");
|
||||||
|
foreach ($this->migration as $mkey => $mdef) {
|
||||||
|
if (str::starts_with($prefix, $mkey)) {
|
||||||
|
$migration[$mkey] = $mdef;
|
||||||
|
} elseif (strpos($mkey, ":") === false) {
|
||||||
|
$migration[$mkey] = $mdef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $migration;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ?array $primaryKeys;
|
protected ?array $primaryKeys;
|
||||||
@ -255,6 +247,10 @@ class CapacitorChannel implements ITransactor {
|
|||||||
* Retourner la clé primaire par cette méthode est l'unique moyen de
|
* Retourner la clé primaire par cette méthode est l'unique moyen de
|
||||||
* déclencher une mise à jour plutôt qu'une nouvelle création.
|
* déclencher une mise à jour plutôt qu'une nouvelle création.
|
||||||
*
|
*
|
||||||
|
* Bien que ce ne soit pas prévu à la base, si on veut modifier $item dans
|
||||||
|
* cette méthode pour des raisons pratiques, il suffit de retournerla valeur
|
||||||
|
* modifiée avec la clé "item"
|
||||||
|
*
|
||||||
* Retourner [false] pour annuler le chargement
|
* Retourner [false] pour annuler le chargement
|
||||||
*/
|
*/
|
||||||
function getItemValues($item): ?array {
|
function getItemValues($item): ?array {
|
||||||
@ -277,8 +273,8 @@ class CapacitorChannel implements ITransactor {
|
|||||||
*
|
*
|
||||||
* cette méthode doit être utilisée dans {@link self::onUpdate()}
|
* cette méthode doit être utilisée dans {@link self::onUpdate()}
|
||||||
*/
|
*/
|
||||||
function wasRowModified(array $values, array $pvalues): bool {
|
function wasRowModified(array $row, array $prow): bool {
|
||||||
return $values["item__sum_"] !== $pvalues["item__sum_"];
|
return $row["item__sum_"] !== $prow["item__sum_"];
|
||||||
}
|
}
|
||||||
|
|
||||||
final function serialize($item): ?string {
|
final function serialize($item): ?string {
|
||||||
@ -309,17 +305,17 @@ class CapacitorChannel implements ITransactor {
|
|||||||
return array_combine($sumCols, [$serial, $sum]);
|
return array_combine($sumCols, [$serial, $sum]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function wasSumModified(string $key, $value, array $pvalues): bool {
|
function wasSumModified(string $key, $value, array $prow): bool {
|
||||||
$sumCol = $this->getSumCols($key)[1];
|
$sumCol = $this->getSumCols($key)[1];
|
||||||
$sum = $this->sum(null, $value);
|
$sum = $this->sum(null, $value);
|
||||||
$psum = $pvalues[$sumCol] ?? $this->sum(null, $pvalues[$key] ?? null);
|
$psum = $prow[$sumCol] ?? $this->sum(null, $prow[$key] ?? null);
|
||||||
return $sum !== $psum;
|
return $sum !== $psum;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _wasSumModified(string $key, array $row, array $prow): bool {
|
function _wasSumModified(string $key, array $raw, array $praw): bool {
|
||||||
$sumCol = $this->getSumCols($key)[1];
|
$sumCol = $this->getSumCols($key)[1];
|
||||||
$sum = $row[$sumCol] ?? null;
|
$sum = $raw[$sumCol] ?? null;
|
||||||
$psum = $prow[$sumCol] ?? null;
|
$psum = $praw[$sumCol] ?? null;
|
||||||
return $sum !== $psum;
|
return $sum !== $psum;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,21 +324,21 @@ class CapacitorChannel implements ITransactor {
|
|||||||
* créer un nouvel élément
|
* créer un nouvel élément
|
||||||
*
|
*
|
||||||
* @param mixed $item l'élément à charger
|
* @param mixed $item l'élément à charger
|
||||||
* @param array $values la ligne à créer, calculée à partir de $item et des
|
* @param array $row la ligne à créer, calculée à partir de $item et des
|
||||||
* valeurs retournées par {@link getItemValues()}
|
* valeurs retournées par {@link getItemValues()}
|
||||||
* @return ?array le cas échéant, un tableau non null à merger dans $values et
|
* @return ?array le cas échéant, un tableau non null à merger dans $row et
|
||||||
* utilisé pour provisionner la ligne nouvellement créée.
|
* utilisé pour provisionner la ligne nouvellement créée.
|
||||||
* Retourner [false] pour annuler le chargement (la ligne n'est pas créée)
|
* Retourner [false] pour annuler le chargement (la ligne n'est pas créée)
|
||||||
*
|
*
|
||||||
* Si $item est modifié dans cette méthode, il est possible de le retourner
|
* Si $item est modifié dans cette méthode, il est possible de le retourner
|
||||||
* avec la clé "item" pour mettre à jour la ligne correspondante.
|
* avec la clé "item" pour mettre à jour la colonne correspondante.
|
||||||
*
|
*
|
||||||
* la création ou la mise à jour est uniquement décidée en fonction des
|
* la création ou la mise à jour est uniquement décidée en fonction des
|
||||||
* valeurs calculées par {@link self::getItemValues()}. Bien que cette méthode
|
* valeurs calculées par {@link self::getItemValues()}. Bien que cette méthode
|
||||||
* peut techniquement retourner de nouvelles valeurs pour la clé primaire, ça
|
* peut techniquement retourner de nouvelles valeurs pour la clé primaire, ça
|
||||||
* risque de créer des doublons
|
* risque de créer des doublons
|
||||||
*/
|
*/
|
||||||
function onCreate($item, array $values, ?array $alwaysNull): ?array {
|
function onCreate($item, array $row, ?array $alwaysNull): ?array {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,12 +347,12 @@ class CapacitorChannel implements ITransactor {
|
|||||||
* mettre à jour un élément existant
|
* mettre à jour un élément existant
|
||||||
*
|
*
|
||||||
* @param mixed $item l'élément à charger
|
* @param mixed $item l'élément à charger
|
||||||
* @param array $values la nouvelle ligne, calculée à partir de $item et
|
* @param array $row la nouvelle ligne, calculée à partir de $item et
|
||||||
* des valeurs retournées par {@link getItemValues()}
|
* des valeurs retournées par {@link getItemValues()}
|
||||||
* @param array $pvalues la précédente ligne, chargée depuis la base de
|
* @param array $prow la précédente ligne, chargée depuis la base de
|
||||||
* données
|
* données
|
||||||
* @return ?array null s'il ne faut pas mettre à jour la ligne. sinon, ce
|
* @return ?array null s'il ne faut pas mettre à jour la ligne. sinon, ce
|
||||||
* tableau est mergé dans $values puis utilisé pour mettre à jour la ligne
|
* tableau est mergé dans $row puis utilisé pour mettre à jour la ligne
|
||||||
* existante
|
* existante
|
||||||
* Retourner [false] pour annuler le chargement (la ligne n'est pas mise à
|
* Retourner [false] pour annuler le chargement (la ligne n'est pas mise à
|
||||||
* jour)
|
* jour)
|
||||||
@ -365,24 +361,25 @@ class CapacitorChannel implements ITransactor {
|
|||||||
* - La clé primaire (il s'agit généralement de "id_") ne peut pas être
|
* - La clé primaire (il s'agit généralement de "id_") ne peut pas être
|
||||||
* modifiée. si elle est retournée, elle est ignorée
|
* modifiée. si elle est retournée, elle est ignorée
|
||||||
*/
|
*/
|
||||||
function onUpdate($item, array $values, array $pvalues): ?array {
|
function onUpdate($item, array $row, array $prow): ?array {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* méthode appelée lors du parcours des éléments avec
|
* méthode appelée lors du parcours des éléments avec {@link Capacitor::each()}
|
||||||
* {@link Capacitor::each()}
|
|
||||||
*
|
*
|
||||||
* @param mixed $item l'élément courant
|
* @param ?array $row la ligne courante. l'élément courant est accessible via
|
||||||
* @param ?array $values la ligne courante
|
* $row["item"]
|
||||||
* @return ?array le cas échéant, un tableau non null utilisé pour mettre à
|
* @return ?array le cas échéant, un tableau non null utilisé pour mettre à
|
||||||
* jour la ligne courante
|
* jour la ligne courante
|
||||||
|
* Retourner [false] pour arrêter le parcours des éléments (la ligne courante
|
||||||
|
* ainsi que les autres lignes ne sont plus mise à jour)
|
||||||
*
|
*
|
||||||
* - Il est possible de mettre à jour $item en le retourant avec la clé "item"
|
* - Il est possible de mettre à jour $item en le retourant avec la clé "item"
|
||||||
* - La clé primaire (il s'agit généralement de "id_") ne peut pas être
|
* - La clé primaire (il s'agit généralement de "id_") ne peut pas être
|
||||||
* modifiée. si elle est retournée, elle est ignorée
|
* modifiée. si elle est retournée, elle est ignorée
|
||||||
*/
|
*/
|
||||||
function onEach($item, array $values): ?array {
|
function onEach(array $row): ?array {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const onEach = "->".[self::class, "onEach"][1];
|
const onEach = "->".[self::class, "onEach"][1];
|
||||||
@ -391,11 +388,11 @@ class CapacitorChannel implements ITransactor {
|
|||||||
* méthode appelée lors du parcours des éléments avec
|
* méthode appelée lors du parcours des éléments avec
|
||||||
* {@link Capacitor::delete()}
|
* {@link Capacitor::delete()}
|
||||||
*
|
*
|
||||||
* @param mixed $item l'élément courant
|
* @param ?array $row la ligne courante. l'élément courant est accessible via
|
||||||
* @param ?array $values la ligne courante
|
* $row["item"]
|
||||||
* @return bool true s'il faut supprimer la ligne, false sinon
|
* @return bool true s'il faut supprimer la ligne, false sinon
|
||||||
*/
|
*/
|
||||||
function onDelete($item, array $values): bool {
|
function onDelete(array $row): bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const onDelete = "->".[self::class, "onDelete"][1];
|
const onDelete = "->".[self::class, "onDelete"][1];
|
||||||
@ -418,6 +415,16 @@ class CapacitorChannel implements ITransactor {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initStorage(CapacitorStorage $storage): self {
|
||||||
|
new Capacitor($storage, $this);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureLive(): self {
|
||||||
|
$this->capacitor->ensureLive();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
function willUpdate(...$transactors): ITransactor {
|
function willUpdate(...$transactors): ITransactor {
|
||||||
return $this->capacitor->willUpdate(...$transactors);
|
return $this->capacitor->willUpdate(...$transactors);
|
||||||
}
|
}
|
||||||
@ -454,36 +461,75 @@ class CapacitorChannel implements ITransactor {
|
|||||||
$this->capacitor->reset($recreate);
|
$this->capacitor->reset($recreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function charge($item, $func=null, ?array $args=null, ?array &$values=null): int {
|
function charge($item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||||
return $this->capacitor->charge($item, $func, $args, $values);
|
return $this->capacitor->charge($item, $func, $args, $row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
|
||||||
|
return $this->capacitor->chargeAll($items, $func, $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
function discharge(bool $reset=true): Traversable {
|
function discharge(bool $reset=true): Traversable {
|
||||||
return $this->capacitor->discharge($reset);
|
return $this->capacitor->discharge($reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retourner le filtre de base: les filtres de toutes les fonctions ci-dessous
|
||||||
|
* sont fusionnées avec le filtre de base
|
||||||
|
*
|
||||||
|
* cela permet de limiter toutes les opérations à un sous-ensemble des données
|
||||||
|
* du canal
|
||||||
|
*/
|
||||||
|
function getBaseFilter(): ?array {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function verifixFilter(&$filter): void {
|
||||||
|
if ($filter !== null && !is_array($filter)) {
|
||||||
|
$primaryKeys = $this->primaryKeys ?? ["id_"];
|
||||||
|
$id = $filter;
|
||||||
|
$this->verifixId($id);
|
||||||
|
$filter = [$primaryKeys[0] => $id];
|
||||||
|
}
|
||||||
|
$filter = cl::merge($this->getBaseFilter(), $filter);
|
||||||
|
}
|
||||||
|
|
||||||
function count($filter=null): int {
|
function count($filter=null): int {
|
||||||
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->count($filter);
|
return $this->capacitor->count($filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function one($filter, ?array $mergeQuery=null): ?array {
|
function one($filter, ?array $mergeQuery=null): ?array {
|
||||||
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->one($filter, $mergeQuery);
|
return $this->capacitor->one($filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
function all($filter, ?array $mergeQuery=null): Traversable {
|
function all($filter, ?array $mergeQuery=null): Traversable {
|
||||||
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->all($filter, $mergeQuery);
|
return $this->capacitor->all($filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||||
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->each($filter, $func, $args, $mergeQuery, $nbUpdated);
|
return $this->capacitor->each($filter, $func, $args, $mergeQuery, $nbUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
function delete($filter, $func=null, ?array $args=null): int {
|
function delete($filter, $func=null, ?array $args=null): int {
|
||||||
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->delete($filter, $func, $args);
|
return $this->capacitor->delete($filter, $func, $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dbUpdate(array $update) {
|
function dbAll(array $query, ?array $params=null): iterable {
|
||||||
return $this->capacitor->dbUpdate($update);
|
return $this->capacitor->dbAll($query, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbOne(array $query, ?array $params=null): ?array {
|
||||||
|
return $this->capacitor->dbOne($query, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return int|false */
|
||||||
|
function dbUpdate(array $query, ?array $params=null) {
|
||||||
|
return $this->capacitor->dbUpdate($query, $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function close(): void {
|
function close(): void {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
|
use nulib\A;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nulib\cv;
|
||||||
use nulib\db\_private\_migration;
|
use nulib\db\_private\_migration;
|
||||||
use nulib\php\func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
@ -14,7 +16,12 @@ use Traversable;
|
|||||||
abstract class CapacitorStorage {
|
abstract class CapacitorStorage {
|
||||||
abstract function db(): IDatabase;
|
abstract function db(): IDatabase;
|
||||||
|
|
||||||
/** @var CapacitorChannel[] */
|
function ensureLive(): self {
|
||||||
|
$this->db()->ensure();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var CapacitorChannel[] */
|
||||||
protected $channels;
|
protected $channels;
|
||||||
|
|
||||||
function addChannel(CapacitorChannel $channel): CapacitorChannel {
|
function addChannel(CapacitorChannel $channel): CapacitorChannel {
|
||||||
@ -32,21 +39,43 @@ abstract class CapacitorStorage {
|
|||||||
return $channel;
|
return $channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** DOIT être défini dans les classes dérivées */
|
const PRIMARY_KEY_DEFINITION = [
|
||||||
const PRIMARY_KEY_DEFINITION = null;
|
"id_" => "genserial",
|
||||||
|
];
|
||||||
|
|
||||||
|
# les définitions sont par défaut pour MariaDB/MySQL
|
||||||
const SERDATA_DEFINITION = "mediumtext";
|
const SERDATA_DEFINITION = "mediumtext";
|
||||||
const SERSUM_DEFINITION = "varchar(40)";
|
const SERSUM_DEFINITION = "varchar(40)";
|
||||||
const SERTS_DEFINITION = "datetime";
|
const SERTS_DEFINITION = "datetime";
|
||||||
|
const GENSERIAL_DEFINITION = "integer primary key auto_increment";
|
||||||
|
const GENLIC_DEFINITION = "varchar(80)";
|
||||||
|
const GENLIB_DEFINITION = "varchar(255)";
|
||||||
|
const GENTEXT_DEFINITION = "mediumtext";
|
||||||
|
const GENBOOL_DEFINITION = "integer(1) default 0";
|
||||||
|
const GENUUID_DEFINITION = "varchar(36)";
|
||||||
|
|
||||||
protected static function sercol($def): string {
|
protected static function gencol($def): string {
|
||||||
if (!is_string($def)) $def = strval($def);
|
if (!is_string($def)) $def = strval($def);
|
||||||
|
$def = trim($def);
|
||||||
|
$parts = preg_split('/\s+/', $def, 2);
|
||||||
|
if (count($parts) == 2) {
|
||||||
|
$def = $parts[0];
|
||||||
|
$rest = " $parts[1]";
|
||||||
|
} else {
|
||||||
|
$rest = null;
|
||||||
|
}
|
||||||
switch ($def) {
|
switch ($def) {
|
||||||
case "serdata": $def = static::SERDATA_DEFINITION; break;
|
case "serdata": $def = static::SERDATA_DEFINITION; break;
|
||||||
case "sersum": $def = static::SERSUM_DEFINITION; break;
|
case "sersum": $def = static::SERSUM_DEFINITION; break;
|
||||||
case "serts": $def = static::SERTS_DEFINITION; break;
|
case "serts": $def = static::SERTS_DEFINITION; break;
|
||||||
|
case "genserial": $def = static::GENSERIAL_DEFINITION; break;
|
||||||
|
case "genlic": $def = static::GENLIC_DEFINITION; break;
|
||||||
|
case "genlib": $def = static::GENLIB_DEFINITION; break;
|
||||||
|
case "gentext": $def = static::GENTEXT_DEFINITION; break;
|
||||||
|
case "genbool": $def = static::GENBOOL_DEFINITION; break;
|
||||||
|
case "genuuid": $def = static::GENUUID_DEFINITION; break;
|
||||||
}
|
}
|
||||||
return $def;
|
return "$def$rest";
|
||||||
}
|
}
|
||||||
|
|
||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
@ -81,7 +110,7 @@ abstract class CapacitorStorage {
|
|||||||
$mindex++;
|
$mindex++;
|
||||||
} else {
|
} else {
|
||||||
if ($mdef) {
|
if ($mdef) {
|
||||||
$definitions[$mcol] = self::sercol($mdef);
|
$definitions[$mcol] = self::gencol($mdef);
|
||||||
} else {
|
} else {
|
||||||
unset($definitions[$mcol]);
|
unset($definitions[$mcol]);
|
||||||
}
|
}
|
||||||
@ -92,19 +121,45 @@ abstract class CapacitorStorage {
|
|||||||
$constraints[] = $def;
|
$constraints[] = $def;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$definitions[$col] = self::sercol($def);
|
$definitions[$col] = self::gencol($def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cl::merge($definitions, $constraints);
|
return cl::merge($definitions, $constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getMigration(CapacitorChannel $channel): ?array {
|
protected function getMigration(CapacitorChannel $channel): ?array {
|
||||||
return $channel->getMigration();
|
return $channel->getMigration($this->db()->getPrefix());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** sérialiser les valeurs qui doivent l'être dans $values */
|
/** sérialiser les valeurs qui doivent l'être dans $row */
|
||||||
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
|
protected function serialize(CapacitorChannel $channel, ?array $row): ?array {
|
||||||
if ($values === null) return null;
|
if ($row === null) return null;
|
||||||
|
$cols = $this->ColumnDefinitions($channel);
|
||||||
|
$index = 0;
|
||||||
|
$raw = [];
|
||||||
|
foreach (array_keys($cols) as $col) {
|
||||||
|
$key = $col;
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
} elseif ($channel->isSerialCol($key)) {
|
||||||
|
[$serialCol, $sumCol] = $channel->getSumCols($key);
|
||||||
|
if (array_key_exists($key, $row)) {
|
||||||
|
$sum = $channel->getSum($key, $row[$key]);
|
||||||
|
$raw[$serialCol] = $sum[$serialCol];
|
||||||
|
if (array_key_exists($sumCol, $cols)) {
|
||||||
|
$raw[$sumCol] = $sum[$sumCol];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (array_key_exists($key, $row)) {
|
||||||
|
$raw[$col] = $row[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** désérialiser les valeurs qui doivent l'être dans $values */
|
||||||
|
protected function unserialize(CapacitorChannel $channel, ?array $raw): ?array {
|
||||||
|
if ($raw === null) return null;
|
||||||
$cols = $this->ColumnDefinitions($channel);
|
$cols = $this->ColumnDefinitions($channel);
|
||||||
$index = 0;
|
$index = 0;
|
||||||
$row = [];
|
$row = [];
|
||||||
@ -112,44 +167,18 @@ abstract class CapacitorStorage {
|
|||||||
$key = $col;
|
$key = $col;
|
||||||
if ($key === $index) {
|
if ($key === $index) {
|
||||||
$index++;
|
$index++;
|
||||||
|
} elseif (!array_key_exists($col, $raw)) {
|
||||||
} elseif ($channel->isSerialCol($key)) {
|
} elseif ($channel->isSerialCol($key)) {
|
||||||
[$serialCol, $sumCol] = $channel->getSumCols($key);
|
$value = $raw[$col];
|
||||||
if (array_key_exists($key, $values)) {
|
if ($value !== null) $value = $channel->unserialize($value);
|
||||||
$sum = $channel->getSum($key, $values[$key]);
|
$row[$key] = $value;
|
||||||
$row[$serialCol] = $sum[$serialCol];
|
} else {
|
||||||
if (array_key_exists($sumCol, $cols)) {
|
$row[$key] = $raw[$col];
|
||||||
$row[$sumCol] = $sum[$sumCol];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} elseif (array_key_exists($key, $values)) {
|
|
||||||
$row[$col] = $values[$key];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** désérialiser les valeurs qui doivent l'être dans $values */
|
|
||||||
protected function unserialize(CapacitorChannel $channel, ?array $row): ?array {
|
|
||||||
if ($row === null) return null;
|
|
||||||
$cols = $this->ColumnDefinitions($channel);
|
|
||||||
$index = 0;
|
|
||||||
$values = [];
|
|
||||||
foreach (array_keys($cols) as $col) {
|
|
||||||
$key = $col;
|
|
||||||
if ($key === $index) {
|
|
||||||
$index++;
|
|
||||||
} elseif (!array_key_exists($col, $row)) {
|
|
||||||
} elseif ($channel->isSerialCol($key)) {
|
|
||||||
$value = $row[$col];
|
|
||||||
if ($value !== null) $value = $channel->unserialize($value);
|
|
||||||
$values[$key] = $value;
|
|
||||||
} else {
|
|
||||||
$values[$key] = $row[$col];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $values;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPrimaryKeys(CapacitorChannel $channel): array {
|
function getPrimaryKeys(CapacitorChannel $channel): array {
|
||||||
$primaryKeys = $channel->getPrimaryKeys();
|
$primaryKeys = $channel->getPrimaryKeys();
|
||||||
if ($primaryKeys === null) $primaryKeys = ["id_"];
|
if ($primaryKeys === null) $primaryKeys = ["id_"];
|
||||||
@ -215,6 +244,22 @@ abstract class CapacitorStorage {
|
|||||||
"class_name" => "varchar",
|
"class_name" => "varchar",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function channelExists(string $name, ?array &$raw=null): bool {
|
||||||
|
$raw = $this->db()->one([
|
||||||
|
"select",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
|
"where" => ["name" => $name],
|
||||||
|
]);
|
||||||
|
return $raw !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannels(): iterable {
|
||||||
|
return $this->db()->all([
|
||||||
|
"select",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
protected function _createChannelsSql(): array {
|
protected function _createChannelsSql(): array {
|
||||||
return [
|
return [
|
||||||
"create table if not exists",
|
"create table if not exists",
|
||||||
@ -316,7 +361,7 @@ abstract class CapacitorStorage {
|
|||||||
* en fonction du type d'opération: création ou mise à jour
|
* en fonction du type d'opération: création ou mise à jour
|
||||||
*
|
*
|
||||||
* Dans les deux cas, si la fonction retourne un tableau, il est utilisé pour
|
* Dans les deux cas, si la fonction retourne un tableau, il est utilisé pour
|
||||||
* modifier les valeurs insérées/mises à jour. De plus, $values obtient la
|
* modifier les valeurs insérées/mises à jour. De plus, $row obtient la
|
||||||
* valeur finale des données insérées/mises à jour
|
* valeur finale des données insérées/mises à jour
|
||||||
*
|
*
|
||||||
* Si $args est renseigné, il est ajouté aux arguments utilisés pour appeler
|
* Si $args est renseigné, il est ajouté aux arguments utilisés pour appeler
|
||||||
@ -327,23 +372,27 @@ abstract class CapacitorStorage {
|
|||||||
* @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait
|
* @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait
|
||||||
* déjà à l'identique dans le canal
|
* déjà à l'identique dans le canal
|
||||||
*/
|
*/
|
||||||
function _charge(CapacitorChannel $channel, $item, $func, ?array $args, ?array &$values=null): int {
|
function _charge(CapacitorChannel $channel, $item, $func, ?array $args, ?array &$row=null): int {
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
$tableName = $channel->getTableName();
|
$tableName = $channel->getTableName();
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$args ??= [];
|
$args ??= [];
|
||||||
|
|
||||||
$values = func::call([$channel, "getItemValues"], $item, ...$args);
|
$row = func::call([$channel, "getItemValues"], $item, ...$args);
|
||||||
if ($values === [false]) return 0;
|
if ($row === [false]) return 0;
|
||||||
|
|
||||||
$row = cl::merge(
|
if ($row !== null && array_key_exists("item", $row)) {
|
||||||
|
$item = A::pop($row, "item");
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw = cl::merge(
|
||||||
$channel->getSum("item", $item),
|
$channel->getSum("item", $item),
|
||||||
$this->serialize($channel, $values));
|
$this->serialize($channel, $row));
|
||||||
$prow = null;
|
$praw = null;
|
||||||
$rowIds = $this->getRowIds($channel, $row, $primaryKeys);
|
$rowIds = $this->getRowIds($channel, $raw, $primaryKeys);
|
||||||
if ($rowIds !== null) {
|
if ($rowIds !== null) {
|
||||||
# modification
|
# modification
|
||||||
$prow = $db->one([
|
$praw = $db->one([
|
||||||
"select",
|
"select",
|
||||||
"from" => $tableName,
|
"from" => $tableName,
|
||||||
"where" => $rowIds,
|
"where" => $rowIds,
|
||||||
@ -352,47 +401,47 @@ abstract class CapacitorStorage {
|
|||||||
|
|
||||||
$now = date("Y-m-d H:i:s");
|
$now = date("Y-m-d H:i:s");
|
||||||
$insert = null;
|
$insert = null;
|
||||||
if ($prow === null) {
|
if ($praw === null) {
|
||||||
# création
|
# création
|
||||||
$row = cl::merge($row, [
|
$raw = cl::merge($raw, [
|
||||||
"created_" => $now,
|
"created_" => $now,
|
||||||
"modified_" => $now,
|
"modified_" => $now,
|
||||||
]);
|
]);
|
||||||
$insert = true;
|
$insert = true;
|
||||||
$initFunc = func::with([$channel, "onCreate"], $args);
|
$initFunc = func::with([$channel, "onCreate"], $args);
|
||||||
$values = $this->unserialize($channel, $row);
|
$row = $this->unserialize($channel, $raw);
|
||||||
$pvalues = null;
|
$prow = null;
|
||||||
} else {
|
} else {
|
||||||
# modification
|
# modification
|
||||||
# intégrer autant que possible les valeurs de prow dans row, de façon que
|
# intégrer autant que possible les valeurs de praw dans raw, de façon que
|
||||||
# l'utilisateur puisse voir clairement ce qui a été modifié
|
# l'utilisateur puisse voir clairement ce qui a été modifié
|
||||||
if ($channel->_wasSumModified("item", $row, $prow)) {
|
if ($channel->_wasSumModified("item", $raw, $praw)) {
|
||||||
$insert = false;
|
$insert = false;
|
||||||
$row = cl::merge($prow, $row, [
|
$raw = cl::merge($praw, $raw, [
|
||||||
"modified_" => $now,
|
"modified_" => $now,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$row = cl::merge($prow, $row);
|
$raw = cl::merge($praw, $raw);
|
||||||
}
|
}
|
||||||
$initFunc = func::with([$channel, "onUpdate"], $args);
|
$initFunc = func::with([$channel, "onUpdate"], $args);
|
||||||
$values = $this->unserialize($channel, $row);
|
$row = $this->unserialize($channel, $raw);
|
||||||
$pvalues = $this->unserialize($channel, $prow);
|
$prow = $this->unserialize($channel, $praw);
|
||||||
}
|
}
|
||||||
|
|
||||||
$updates = $initFunc->prependArgs([$item, $values, $pvalues])->invoke();
|
$updates = $initFunc->prependArgs([$item, $row, $prow])->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;
|
||||||
if (!array_key_exists("modified_", $updates)) {
|
if (!array_key_exists("modified_", $updates)) {
|
||||||
$updates["modified_"] = $now;
|
$updates["modified_"] = $now;
|
||||||
}
|
}
|
||||||
$values = cl::merge($values, $updates);
|
$row = cl::merge($row, $updates);
|
||||||
$row = cl::merge($row, $this->serialize($channel, $updates));
|
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$updates = func::with($func)
|
$updates = func::with($func, $args)
|
||||||
->prependArgs([$item, $values, $pvalues])
|
->prependArgs([$item, $row, $prow])
|
||||||
->bind($channel)
|
->bind($channel)
|
||||||
->invoke();
|
->invoke();
|
||||||
if ($updates === [false]) return 0;
|
if ($updates === [false]) return 0;
|
||||||
@ -401,8 +450,8 @@ abstract class CapacitorStorage {
|
|||||||
if (!array_key_exists("modified_", $updates)) {
|
if (!array_key_exists("modified_", $updates)) {
|
||||||
$updates["modified_"] = $now;
|
$updates["modified_"] = $now;
|
||||||
}
|
}
|
||||||
$values = cl::merge($values, $updates);
|
$row = cl::merge($row, $updates);
|
||||||
$row = cl::merge($row, $this->serialize($channel, $updates));
|
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,23 +470,22 @@ abstract class CapacitorStorage {
|
|||||||
$id = $db->exec([
|
$id = $db->exec([
|
||||||
"insert",
|
"insert",
|
||||||
"into" => $tableName,
|
"into" => $tableName,
|
||||||
"values" => $row,
|
"values" => $raw,
|
||||||
]);
|
]);
|
||||||
if (count($primaryKeys) == 1 && $rowIds === null) {
|
if (count($primaryKeys) == 1 && $rowIds === null) {
|
||||||
# mettre à jour avec l'id généré
|
# mettre à jour avec l'id généré
|
||||||
$values[$primaryKeys[0]] = $id;
|
$row[$primaryKeys[0]] = $id;
|
||||||
}
|
}
|
||||||
$nbModified = 1;
|
$nbModified = 1;
|
||||||
} else {
|
} else {
|
||||||
# calculer ce qui a changé pour ne mettre à jour que le nécessaire
|
# calculer ce qui a changé pour ne mettre à jour que le nécessaire
|
||||||
$updates = [];
|
$updates = [];
|
||||||
foreach ($row as $col => $value) {
|
foreach ($raw as $col => $value) {
|
||||||
if (array_key_exists($col, $rowIds)) {
|
if (array_key_exists($col, $rowIds)) {
|
||||||
# ne jamais mettre à jour la clé primaire
|
# ne jamais mettre à jour la clé primaire
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$pvalue = $prow[$col] ?? null;
|
if (!cv::equals($value, $praw[$col] ?? null)) {
|
||||||
if ($value !== ($pvalue)) {
|
|
||||||
$updates[$col] = $value;
|
$updates[$col] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -467,19 +515,22 @@ abstract class CapacitorStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function charge(?string $channel, $item, $func=null, ?array $args=null, ?array &$values=null): int {
|
function charge(?string $channel, $item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||||
return $this->_charge($this->getChannel($channel), $item, $func, $args, $values);
|
return $this->_charge($this->getChannel($channel), $item, $func, $args, $row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** décharger les données du canal spécifié */
|
/**
|
||||||
|
* décharger les données du canal spécifié. seul la valeur de $item est
|
||||||
|
* fournie
|
||||||
|
*/
|
||||||
function _discharge(CapacitorChannel $channel, bool $reset=true): Traversable {
|
function _discharge(CapacitorChannel $channel, bool $reset=true): Traversable {
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
$rows = $this->db()->all([
|
$raws = $this->db()->all([
|
||||||
"select item__",
|
"select item__",
|
||||||
"from" => $channel->getTableName(),
|
"from" => $channel->getTableName(),
|
||||||
]);
|
]);
|
||||||
foreach ($rows as $row) {
|
foreach ($raws as $raw) {
|
||||||
yield unserialize($row['item__']);
|
yield unserialize($raw['item__']);
|
||||||
}
|
}
|
||||||
if ($reset) $this->_reset($channel);
|
if ($reset) $this->_reset($channel);
|
||||||
}
|
}
|
||||||
@ -548,45 +599,34 @@ abstract class CapacitorStorage {
|
|||||||
if ($filter === null) throw ValueException::null("filter");
|
if ($filter === null) throw ValueException::null("filter");
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
$this->verifixFilter($channel, $filter);
|
$this->verifixFilter($channel, $filter);
|
||||||
$row = $this->db()->one(cl::merge([
|
$raw = $this->db()->one(cl::merge([
|
||||||
"select",
|
"select",
|
||||||
"from" => $channel->getTableName(),
|
"from" => $channel->getTableName(),
|
||||||
"where" => $filter,
|
"where" => $filter,
|
||||||
], $mergeQuery));
|
], $mergeQuery));
|
||||||
return $this->unserialize($channel, $row);
|
return $this->unserialize($channel, $raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
function one(?string $channel, $filter, ?array $mergeQuery=null): ?array {
|
function one(?string $channel, $filter, ?array $mergeQuery=null): ?array {
|
||||||
return $this->_one($this->getChannel($channel), $filter, $mergeQuery);
|
return $this->_one($this->getChannel($channel), $filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function _allCached(string $id, CapacitorChannel $channel, $filter, ?array $mergeQuery=null): Traversable {
|
|
||||||
$this->_create($channel);
|
|
||||||
$this->verifixFilter($channel, $filter);
|
|
||||||
$rows = $this->db()->all(cl::merge([
|
|
||||||
"select",
|
|
||||||
"from" => $channel->getTableName(),
|
|
||||||
"where" => $filter,
|
|
||||||
], $mergeQuery), null, $this->getPrimaryKeys($channel));
|
|
||||||
if ($channel->isUseCache()) {
|
|
||||||
$cacheIds = [$id, get_class($channel)];
|
|
||||||
cache::get()->resetCached($cacheIds);
|
|
||||||
$rows = cache::new(null, $cacheIds, function() use ($rows) {
|
|
||||||
yield from $rows;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
foreach ($rows as $key => $row) {
|
|
||||||
yield $key => $this->unserialize($channel, $row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* obtenir les lignes correspondant au filtre sur le canal spécifié
|
* obtenir les lignes correspondant au filtre sur le canal spécifié
|
||||||
*
|
*
|
||||||
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
||||||
*/
|
*/
|
||||||
function _all(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): Traversable {
|
function _all(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): Traversable {
|
||||||
return $this->_allCached("all", $channel, $filter, $mergeQuery);
|
$this->_create($channel);
|
||||||
|
$this->verifixFilter($channel, $filter);
|
||||||
|
$raws = $this->db()->all(cl::merge([
|
||||||
|
"select",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
"where" => $filter,
|
||||||
|
], $mergeQuery), null, $this->getPrimaryKeys($channel));
|
||||||
|
foreach ($raws as $key => $raw) {
|
||||||
|
yield $key => $this->unserialize($channel, $raw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function all(?string $channel, $filter, $mergeQuery=null): Traversable {
|
function all(?string $channel, $filter, $mergeQuery=null): Traversable {
|
||||||
@ -622,11 +662,13 @@ abstract class CapacitorStorage {
|
|||||||
$tableName = $channel->getTableName();
|
$tableName = $channel->getTableName();
|
||||||
try {
|
try {
|
||||||
$args ??= [];
|
$args ??= [];
|
||||||
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
$rows = $this->_all($channel, $filter, $mergeQuery);
|
||||||
foreach ($all as $values) {
|
foreach ($rows as $row) {
|
||||||
$rowIds = $this->getRowIds($channel, $values);
|
$rowIds = $this->getRowIds($channel, $row);
|
||||||
$updates = $onEach->invoke([$values["item"], $values, ...$args]);
|
$updates = $onEach->invoke([$row, ...$args]);
|
||||||
if (is_array($updates) && $updates) {
|
if ($updates === [false]) {
|
||||||
|
break;
|
||||||
|
} elseif ($updates !== null) {
|
||||||
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");
|
||||||
}
|
}
|
||||||
@ -689,10 +731,10 @@ abstract class CapacitorStorage {
|
|||||||
$tableName = $channel->getTableName();
|
$tableName = $channel->getTableName();
|
||||||
try {
|
try {
|
||||||
$args ??= [];
|
$args ??= [];
|
||||||
$all = $this->_allCached("delete", $channel, $filter);
|
$rows = $this->_all($channel, $filter);
|
||||||
foreach ($all as $values) {
|
foreach ($rows as $row) {
|
||||||
$rowIds = $this->getRowIds($channel, $values);
|
$rowIds = $this->getRowIds($channel, $row);
|
||||||
$shouldDelete = boolval($onDelete->invoke([$values["item"], $values, ...$args]));
|
$shouldDelete = boolval($onDelete->invoke([$row, ...$args]));
|
||||||
if ($shouldDelete) {
|
if ($shouldDelete) {
|
||||||
$db->exec([
|
$db->exec([
|
||||||
"delete",
|
"delete",
|
||||||
|
@ -2,9 +2,23 @@
|
|||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
interface IDatabase extends ITransactor {
|
interface IDatabase extends ITransactor {
|
||||||
|
/**
|
||||||
|
* retourner le type de la base de données (mysql, pgsql, sqlite, ...)
|
||||||
|
* ce préfixe peut servir à qualifier les migrations
|
||||||
|
*/
|
||||||
|
function getPrefix(): ?string;
|
||||||
|
|
||||||
/** obtenir la requête SQL correspondant à $query */
|
/** obtenir la requête SQL correspondant à $query */
|
||||||
function getSql($query, ?array $params=null): string;
|
function getSql($query, ?array $params=null): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vérifier la connexion à la base de données, et refaire la connexion si
|
||||||
|
* nécessaire. NB: si la connexion a la base de données était perdue, les
|
||||||
|
* transactions en cours sont perdues. cette méthode est donc prévue pour
|
||||||
|
* vérifier la validité de la connexion avant de lancer une transaction
|
||||||
|
*/
|
||||||
|
function ensure(): self;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - si c'est un insert, retourner l'identifiant autogénéré de la ligne
|
* - si c'est un insert, retourner l'identifiant autogénéré de la ligne
|
||||||
* - sinon retourner le nombre de lignes modifiées en cas de succès, ou false
|
* - sinon retourner le nombre de lignes modifiées en cas de succès, ou false
|
||||||
|
@ -9,6 +9,7 @@ class _select extends _common {
|
|||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"schema" => "?array",
|
"schema" => "?array",
|
||||||
|
"distinct" => "bool",
|
||||||
"cols" => "?array",
|
"cols" => "?array",
|
||||||
"col_prefix" => "?string",
|
"col_prefix" => "?string",
|
||||||
"from" => "?string",
|
"from" => "?string",
|
||||||
@ -45,8 +46,16 @@ class _select extends _common {
|
|||||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||||
|
|
||||||
## select
|
## select
|
||||||
self::consume('(select(?:\s*distinct)?)\s*', $tmpsql, $ms);
|
self::consume('(select(?:\s*(distinct))?)\s*', $tmpsql, $ms);
|
||||||
$sql[] = $ms[1];
|
$sql[] = $ms[1];
|
||||||
|
if (($ms[2] ?? null) !== null) {
|
||||||
|
# ne pas le rajouter de nouveau ci-dessous
|
||||||
|
$distinct = false;
|
||||||
|
} else $distinct = null;
|
||||||
|
|
||||||
|
## distinct
|
||||||
|
$distinct ??= $query["distinct"] ?? false;
|
||||||
|
if ($distinct) $sql[] = "distinct";
|
||||||
|
|
||||||
## cols
|
## cols
|
||||||
$usercols = [];
|
$usercols = [];
|
||||||
|
@ -6,7 +6,6 @@ class _update extends _common {
|
|||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"table" => "?string",
|
"table" => "?string",
|
||||||
"schema" => "?array",
|
"schema" => "?array",
|
||||||
"cols" => "?array",
|
|
||||||
"values" => "?array",
|
"values" => "?array",
|
||||||
"where" => "?array",
|
"where" => "?array",
|
||||||
"suffix" => "?string",
|
"suffix" => "?string",
|
||||||
|
@ -4,6 +4,13 @@ namespace nulib\db\mysql;
|
|||||||
use nulib\db\pdo\Pdo;
|
use nulib\db\pdo\Pdo;
|
||||||
|
|
||||||
class Mysql extends Pdo {
|
class Mysql extends Pdo {
|
||||||
|
const PREFIX = "mysql";
|
||||||
|
|
||||||
|
static function config_unbufferedQueries(self $mysql): void {
|
||||||
|
$mysql->db->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
|
||||||
|
}
|
||||||
|
const CONFIG_unbufferedQueries = [self::class, "config_unbufferedQueries"];
|
||||||
|
|
||||||
function getDbname(): ?string {
|
function getDbname(): ?string {
|
||||||
$url = $this->dbconn["name"] ?? null;
|
$url = $this->dbconn["name"] ?? null;
|
||||||
if ($url !== null && preg_match('/^mysql(?::|.*;)dbname=([^;]+)/i', $url, $ms)) {
|
if ($url !== null && preg_match('/^mysql(?::|.*;)dbname=([^;]+)/i', $url, $ms)) {
|
||||||
|
@ -19,10 +19,6 @@ class MysqlStorage extends CapacitorStorage {
|
|||||||
return $this->db;
|
return $this->db;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRIMARY_KEY_DEFINITION = [
|
|
||||||
"id_" => "integer primary key auto_increment",
|
|
||||||
];
|
|
||||||
|
|
||||||
protected function tableExists(string $tableName): bool {
|
protected function tableExists(string $tableName): bool {
|
||||||
$db = $this->db;
|
$db = $this->db;
|
||||||
$found = $db->get([
|
$found = $db->get([
|
||||||
@ -43,7 +39,7 @@ class MysqlStorage extends CapacitorStorage {
|
|||||||
function _getMigration(CapacitorChannel $channel): _mysqlMigration {
|
function _getMigration(CapacitorChannel $channel): _mysqlMigration {
|
||||||
$migrations = cl::merge([
|
$migrations = cl::merge([
|
||||||
"0init" => [$this->_createSql($channel)],
|
"0init" => [$this->_createSql($channel)],
|
||||||
], $channel->getMigration());
|
], $channel->getMigration($this->db->getPrefix()));
|
||||||
return new _mysqlMigration($migrations, $channel->getName());
|
return new _mysqlMigration($migrations, $channel->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,12 @@ use nulib\ValueException;
|
|||||||
class Pdo implements IDatabase {
|
class Pdo implements IDatabase {
|
||||||
use Tvalues;
|
use Tvalues;
|
||||||
|
|
||||||
|
const PREFIX = null;
|
||||||
|
|
||||||
|
function getPrefix(): ?string {
|
||||||
|
return static::PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
static function with($pdo, ?array $params=null): self {
|
static function with($pdo, ?array $params=null): self {
|
||||||
if ($pdo instanceof static) {
|
if ($pdo instanceof static) {
|
||||||
return $pdo;
|
return $pdo;
|
||||||
@ -34,11 +40,6 @@ class Pdo implements IDatabase {
|
|||||||
}
|
}
|
||||||
const CONFIG_errmodeException_lowerCase = [self::class, "config_errmodeException_lowerCase"];
|
const CONFIG_errmodeException_lowerCase = [self::class, "config_errmodeException_lowerCase"];
|
||||||
|
|
||||||
static function config_unbufferedQueries(self $pdo): void {
|
|
||||||
$pdo->db->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
|
|
||||||
}
|
|
||||||
const CONFIG_unbufferedQueries = [self::class, "config_unbufferedQueries"];
|
|
||||||
|
|
||||||
protected const OPTIONS = [
|
protected const OPTIONS = [
|
||||||
\PDO::ATTR_PERSISTENT => true,
|
\PDO::ATTR_PERSISTENT => true,
|
||||||
];
|
];
|
||||||
@ -119,8 +120,8 @@ class Pdo implements IDatabase {
|
|||||||
return $query->getSql();
|
return $query->getSql();
|
||||||
}
|
}
|
||||||
|
|
||||||
function open(): self {
|
function open(bool $reopen=false): self {
|
||||||
if ($this->db === null) {
|
if ($this->db === null || $reopen) {
|
||||||
$dbconn = $this->dbconn;
|
$dbconn = $this->dbconn;
|
||||||
$options = $this->options;
|
$options = $this->options;
|
||||||
if (is_callable($options)) {
|
if (is_callable($options)) {
|
||||||
@ -147,6 +148,30 @@ class Pdo implements IDatabase {
|
|||||||
return $this->db()->exec($query);
|
return $this->db()->exec($query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return array|null */
|
||||||
|
function _query(string $query) {
|
||||||
|
$db = $this->db();
|
||||||
|
/** @var \PDOStatement $stmt */
|
||||||
|
$stmt = $db->query($query);
|
||||||
|
if ($stmt === false) return null;
|
||||||
|
try {
|
||||||
|
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
} finally {
|
||||||
|
$stmt->closeCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SQL_CHECK_LIVE = "select 1";
|
||||||
|
|
||||||
|
function ensure(): self {
|
||||||
|
try {
|
||||||
|
$this->_query(static::SQL_CHECK_LIVE);
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->open(true);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
function exec($query, ?array $params=null) {
|
function exec($query, ?array $params=null) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _pdoQuery($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
|
@ -12,6 +12,12 @@ use nulib\ValueException;
|
|||||||
class Pgsql implements IDatabase {
|
class Pgsql implements IDatabase {
|
||||||
use Tvalues;
|
use Tvalues;
|
||||||
|
|
||||||
|
const PREFIX = "pgsql";
|
||||||
|
|
||||||
|
function getPrefix(): ?string {
|
||||||
|
return self::PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
static function with($pgsql, ?array $params=null): self {
|
static function with($pgsql, ?array $params=null): self {
|
||||||
if ($pgsql instanceof static) {
|
if ($pgsql instanceof static) {
|
||||||
return $pgsql;
|
return $pgsql;
|
||||||
@ -132,8 +138,8 @@ class Pgsql implements IDatabase {
|
|||||||
return $query->getSql();
|
return $query->getSql();
|
||||||
}
|
}
|
||||||
|
|
||||||
function open(): self {
|
function open(bool $reopen=false): self {
|
||||||
if ($this->db === null) {
|
if ($this->db === null || $reopen) {
|
||||||
$dbconn = $this->dbconn;
|
$dbconn = $this->dbconn;
|
||||||
$connection_string = [$dbconn[""] ?? null];
|
$connection_string = [$dbconn[""] ?? null];
|
||||||
unset($dbconn[""]);
|
unset($dbconn[""]);
|
||||||
@ -187,6 +193,31 @@ class Pgsql implements IDatabase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _query(string $query): ?array {
|
||||||
|
$result = pg_query($this->db(), $query);
|
||||||
|
if ($result === false) return null;
|
||||||
|
try {
|
||||||
|
$rows = [];
|
||||||
|
while (($row = pg_fetch_assoc($result)) !== false) {
|
||||||
|
$rows[] = $row;
|
||||||
|
}
|
||||||
|
return $rows;
|
||||||
|
} finally {
|
||||||
|
pg_free_result($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SQL_CHECK_LIVE = "select 1";
|
||||||
|
|
||||||
|
function ensure(): self {
|
||||||
|
try {
|
||||||
|
$this->_query(static::SQL_CHECK_LIVE);
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->open(true);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
function getLastSerial() {
|
function getLastSerial() {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$result = @pg_query($db, "select lastval()");
|
$result = @pg_query($db, "select lastval()");
|
||||||
|
@ -9,6 +9,10 @@ class PgsqlStorage extends CapacitorStorage {
|
|||||||
const SERDATA_DEFINITION = "text";
|
const SERDATA_DEFINITION = "text";
|
||||||
const SERSUM_DEFINITION = "varchar(40)";
|
const SERSUM_DEFINITION = "varchar(40)";
|
||||||
const SERTS_DEFINITION = "timestamp";
|
const SERTS_DEFINITION = "timestamp";
|
||||||
|
const GENSERIAL_DEFINITION = "serial primary key";
|
||||||
|
const GENTEXT_DEFINITION = "text";
|
||||||
|
const GENBOOL_DEFINITION = "boolean default false";
|
||||||
|
const GENUUID_DEFINITION = "uuid";
|
||||||
|
|
||||||
function __construct($pgsql) {
|
function __construct($pgsql) {
|
||||||
$this->db = Pgsql::with($pgsql);
|
$this->db = Pgsql::with($pgsql);
|
||||||
@ -20,10 +24,6 @@ class PgsqlStorage extends CapacitorStorage {
|
|||||||
return $this->db;
|
return $this->db;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRIMARY_KEY_DEFINITION = [
|
|
||||||
"id_" => "serial primary key",
|
|
||||||
];
|
|
||||||
|
|
||||||
protected function tableExists(string $tableName): bool {
|
protected function tableExists(string $tableName): bool {
|
||||||
if (($index = strpos($tableName, ".")) !== false) {
|
if (($index = strpos($tableName, ".")) !== false) {
|
||||||
$schemaName = substr($tableName, 0, $index);
|
$schemaName = substr($tableName, 0, $index);
|
||||||
@ -44,7 +44,7 @@ class PgsqlStorage extends CapacitorStorage {
|
|||||||
function _getMigration(CapacitorChannel $channel): _pgsqlMigration {
|
function _getMigration(CapacitorChannel $channel): _pgsqlMigration {
|
||||||
$migrations = cl::merge([
|
$migrations = cl::merge([
|
||||||
"0init" => [$this->_createSql($channel)],
|
"0init" => [$this->_createSql($channel)],
|
||||||
], $channel->getMigration());
|
], $channel->getMigration($this->db->getPrefix()));
|
||||||
return new _pgsqlMigration($migrations, $channel->getName());
|
return new _pgsqlMigration($migrations, $channel->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,12 @@ use SQLite3Stmt;
|
|||||||
class Sqlite implements IDatabase {
|
class Sqlite implements IDatabase {
|
||||||
use Tvalues;
|
use Tvalues;
|
||||||
|
|
||||||
|
const PREFIX = "sqlite";
|
||||||
|
|
||||||
|
function getPrefix(): ?string {
|
||||||
|
return self::PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
static function with($sqlite, ?array $params=null): self {
|
static function with($sqlite, ?array $params=null): self {
|
||||||
if ($sqlite instanceof static) {
|
if ($sqlite instanceof static) {
|
||||||
return $sqlite;
|
return $sqlite;
|
||||||
@ -151,8 +157,8 @@ class Sqlite implements IDatabase {
|
|||||||
return $query->getSql();
|
return $query->getSql();
|
||||||
}
|
}
|
||||||
|
|
||||||
function open(): self {
|
function open(bool $reopen=false): self {
|
||||||
if ($this->db === null) {
|
if ($this->db === null || $reopen) {
|
||||||
$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);
|
||||||
_sqliteMigration::with($this->migration)->migrate($this);
|
_sqliteMigration::with($this->migration)->migrate($this);
|
||||||
@ -186,6 +192,31 @@ class Sqlite implements IDatabase {
|
|||||||
return $this->db()->exec($query);
|
return $this->db()->exec($query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _query(string $query): ?array {
|
||||||
|
$result = $this->db()->query($query);
|
||||||
|
if ($result === false) return null;
|
||||||
|
try {
|
||||||
|
$rows = [];
|
||||||
|
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
||||||
|
$rows[] = $row;
|
||||||
|
}
|
||||||
|
return $rows;
|
||||||
|
} finally {
|
||||||
|
$result->finalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SQL_CHECK_LIVE = "select 1";
|
||||||
|
|
||||||
|
function ensure(): self {
|
||||||
|
try {
|
||||||
|
$this->_query(static::SQL_CHECK_LIVE);
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->open(true);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
function exec($query, ?array $params=null) {
|
function exec($query, ?array $params=null) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _sqliteQuery($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
|
@ -9,6 +9,8 @@ use nulib\db\CapacitorStorage;
|
|||||||
* Class SqliteStorage
|
* Class SqliteStorage
|
||||||
*/
|
*/
|
||||||
class SqliteStorage extends CapacitorStorage {
|
class SqliteStorage extends CapacitorStorage {
|
||||||
|
const GENSERIAL_DEFINITION = "integer primary key autoincrement";
|
||||||
|
|
||||||
function __construct($sqlite) {
|
function __construct($sqlite) {
|
||||||
$this->db = Sqlite::with($sqlite);
|
$this->db = Sqlite::with($sqlite);
|
||||||
}
|
}
|
||||||
@ -19,10 +21,6 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
return $this->db;
|
return $this->db;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRIMARY_KEY_DEFINITION = [
|
|
||||||
"id_" => "integer primary key autoincrement",
|
|
||||||
];
|
|
||||||
|
|
||||||
protected function tableExists(string $tableName): bool {
|
protected function tableExists(string $tableName): bool {
|
||||||
$found = $this->db->get([
|
$found = $this->db->get([
|
||||||
# depuis la version 3.33.0 le nom officiel de la table est sqlite_schema,
|
# depuis la version 3.33.0 le nom officiel de la table est sqlite_schema,
|
||||||
@ -36,26 +34,10 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
function _getMigration(CapacitorChannel $channel): _sqliteMigration {
|
function _getMigration(CapacitorChannel $channel): _sqliteMigration {
|
||||||
$migrations = cl::merge([
|
$migrations = cl::merge([
|
||||||
"0init" => [$this->_createSql($channel)],
|
"0init" => [$this->_createSql($channel)],
|
||||||
], $channel->getMigration());
|
], $channel->getMigration($this->db->getPrefix()));
|
||||||
return new _sqliteMigration($migrations, $channel->getName());
|
return new _sqliteMigration($migrations, $channel->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
function channelExists(string $name, ?array &$row=null): bool {
|
|
||||||
$row = $this->db->one([
|
|
||||||
"select",
|
|
||||||
"from" => static::CHANNELS_TABLE,
|
|
||||||
"where" => ["name" => $name],
|
|
||||||
]);
|
|
||||||
return $row !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChannels(): iterable {
|
|
||||||
return $this->db->all([
|
|
||||||
"select",
|
|
||||||
"from" => static::CHANNELS_TABLE,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||||
$sql = parent::_addToChannelsSql($channel);
|
$sql = parent::_addToChannelsSql($channel);
|
||||||
$sql[0] = "insert or ignore";
|
$sql[0] = "insert or ignore";
|
||||||
|
12
php/src/ext/utils.php
Normal file
12
php/src/ext/utils.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ext;
|
||||||
|
|
||||||
|
class utils {
|
||||||
|
/** générateur d'UUIDv4 "du pauvre" */
|
||||||
|
static final function uuidgen(): string {
|
||||||
|
$data = random_bytes(16);
|
||||||
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
|
||||||
|
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
|
||||||
|
return vsprintf("%s%s-%s-%s-%s-%s%s%s", str_split(bin2hex($data), 4));
|
||||||
|
}
|
||||||
|
}
|
@ -117,4 +117,18 @@ class file {
|
|||||||
}
|
}
|
||||||
return $file;
|
return $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function string_reader(string $content, ?callable $func=null): MemoryStream {
|
||||||
|
$file = new MemoryStream();
|
||||||
|
$file->fwrite($content);
|
||||||
|
$file->rewind();
|
||||||
|
if ($func !== null) {
|
||||||
|
try {
|
||||||
|
$func($file);
|
||||||
|
} finally {
|
||||||
|
$file ->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ class CsvBuilder extends AbstractBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _checkOk(): bool {
|
protected function _checkOk(): bool {
|
||||||
$size = $this->ftell();
|
$this->size = $size = $this->ftell();
|
||||||
if ($size === 0) return false;
|
if ($size === 0) return false;
|
||||||
$this->rewind();
|
$this->rewind();
|
||||||
return true;
|
return true;
|
||||||
|
@ -118,18 +118,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
if ($unsetRows) $this->rows = null;
|
if ($unsetRows) $this->rows = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected function _sendContentType(): void;
|
function getCount(): int {
|
||||||
|
return $this->index;
|
||||||
protected bool $sentHeaders = false;
|
|
||||||
|
|
||||||
function sendHeaders(): void {
|
|
||||||
if ($this->sentHeaders) return;
|
|
||||||
$this->_sendContentType();
|
|
||||||
$output = $this->output;
|
|
||||||
if ($output !== null) {
|
|
||||||
http::download_as(path::filename($output));
|
|
||||||
}
|
|
||||||
$this->sentHeaders = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _build(?iterable $rows=null): void {
|
protected function _build(?iterable $rows=null): void {
|
||||||
@ -141,6 +131,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
|
|
||||||
protected bool $built = false, $closed = false;
|
protected bool $built = false, $closed = false;
|
||||||
|
|
||||||
|
protected ?int $size = null;
|
||||||
|
|
||||||
function build(?iterable $rows=null, bool $close=true): bool {
|
function build(?iterable $rows=null, bool $close=true): bool {
|
||||||
$ok = true;
|
$ok = true;
|
||||||
if (!$this->built) {
|
if (!$this->built) {
|
||||||
@ -154,6 +146,24 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
return $ok;
|
return $ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract protected function _sendContentType(): void;
|
||||||
|
|
||||||
|
protected bool $sentHeaders = false;
|
||||||
|
|
||||||
|
function sendHeaders(): void {
|
||||||
|
if ($this->sentHeaders) return;
|
||||||
|
$this->_sendContentType();
|
||||||
|
$output = $this->output;
|
||||||
|
if ($output !== null) {
|
||||||
|
http::download_as(path::filename($output));
|
||||||
|
}
|
||||||
|
$size = $this->size;
|
||||||
|
if ($size !== null) {
|
||||||
|
header("Content-Length: $size");
|
||||||
|
}
|
||||||
|
$this->sentHeaders = true;
|
||||||
|
}
|
||||||
|
|
||||||
function sendFile(?iterable $rows=null): int {
|
function sendFile(?iterable $rows=null): int {
|
||||||
if (!$this->built) {
|
if (!$this->built) {
|
||||||
$this->_build($rows);
|
$this->_build($rows);
|
||||||
|
@ -52,6 +52,7 @@ abstract class AbstractReader implements IReader {
|
|||||||
$this->headers = $params["headers"] ?? static::HEADERS;
|
$this->headers = $params["headers"] ?? static::HEADERS;
|
||||||
$this->useHeaders = $params["use_headers"] ?? static::USE_HEADERS;
|
$this->useHeaders = $params["use_headers"] ?? static::USE_HEADERS;
|
||||||
$this->input = $params["input"] ?? static::INPUT;
|
$this->input = $params["input"] ?? static::INPUT;
|
||||||
|
$this->skip = $params["skip"] ?? 0;
|
||||||
$this->trim = boolval($params["trim"] ?? static::TRIM);
|
$this->trim = boolval($params["trim"] ?? static::TRIM);
|
||||||
$this->emptyAsNull = boolval($params["empty_as_null"] ?? static::EMPTY_AS_NULL);
|
$this->emptyAsNull = boolval($params["empty_as_null"] ?? static::EMPTY_AS_NULL);
|
||||||
$this->parseNone = boolval($params["parse_none"] ?? static::PARSE_NONE);
|
$this->parseNone = boolval($params["parse_none"] ?? static::PARSE_NONE);
|
||||||
@ -67,6 +68,8 @@ abstract class AbstractReader implements IReader {
|
|||||||
|
|
||||||
protected $input;
|
protected $input;
|
||||||
|
|
||||||
|
protected int $skip;
|
||||||
|
|
||||||
protected bool $trim;
|
protected bool $trim;
|
||||||
|
|
||||||
protected bool $emptyAsNull;
|
protected bool $emptyAsNull;
|
||||||
@ -81,15 +84,16 @@ abstract class AbstractReader implements IReader {
|
|||||||
|
|
||||||
protected function cookRow(array &$row): bool {
|
protected function cookRow(array &$row): bool {
|
||||||
if (!$this->useHeaders) return true;
|
if (!$this->useHeaders) return true;
|
||||||
if ($this->isrc == 0) {
|
if ($this->skip > 0) {
|
||||||
|
$this->skip--;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($this->headers === null) {
|
||||||
# ligne d'en-tête
|
# ligne d'en-tête
|
||||||
$headers = $this->headers;
|
if ($this->schema === null) $headers = null;
|
||||||
if ($headers === null) {
|
else $headers = array_keys($this->schema);
|
||||||
if ($this->schema === null) $headers = null;
|
if ($headers === null) $headers = $row;
|
||||||
else $headers = array_keys($this->schema);
|
$this->headers = $headers;
|
||||||
if ($headers === null) $headers = $row;
|
|
||||||
$this->headers = $headers;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
A::ensure_size($row, count($this->headers));
|
A::ensure_size($row, count($this->headers));
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\file\tab;
|
namespace nulib\file\tab;
|
||||||
|
|
||||||
|
use nulib\file\IWriter;
|
||||||
|
|
||||||
interface IBuilder extends \nulib\file\IReader {
|
interface IBuilder extends \nulib\file\IReader {
|
||||||
function writeHeaders(?array $headers=null): void;
|
function writeHeaders(?array $headers=null): void;
|
||||||
|
|
||||||
@ -8,7 +10,23 @@ interface IBuilder extends \nulib\file\IReader {
|
|||||||
|
|
||||||
function writeAll(?iterable $rows=null): void;
|
function writeAll(?iterable $rows=null): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int le nombre de ligne qui ont été écrites (sans compte l'en-tête)
|
||||||
|
*/
|
||||||
|
function getCount(): int;
|
||||||
|
|
||||||
|
/** préparer le fichier (sans l'envoyer) */
|
||||||
|
function build(?iterable $rows=null, bool $close=true): bool;
|
||||||
|
|
||||||
|
/** enregistrer le fichier préparé avec {@link build()} */
|
||||||
|
function copyTo(IWriter $dest, bool $closeWriter=false, bool $closeReader=true): void;
|
||||||
|
|
||||||
|
/** envoyer les en-têtes */
|
||||||
function sendHeaders(): void;
|
function sendHeaders(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* envoyer le fichier pour téléchargement (la méthode {@link sendHeaders()}
|
||||||
|
* est automatiquement appelée le cas échéant
|
||||||
|
*/
|
||||||
function sendFile(?iterable $rows=null): int;
|
function sendFile(?iterable $rows=null): int;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# TOOD
|
# nulib\output
|
||||||
|
|
||||||
* dans msg::action($m, function() {}), *bloquer* la marque pour empêcher d'aller
|
* dans msg::action($m, function() {}), *bloquer* la marque pour empêcher d'aller
|
||||||
plus bas que prévu. comme ça s'il y a plusieurs success ou failure dans la
|
plus bas que prévu. comme ça s'il y a plusieurs success ou failure dans la
|
||||||
@ -32,4 +32,10 @@ pour l'UI
|
|||||||
peut-être rajouter `ui` (ou `web`?) en plus de say, log, debuglog?
|
peut-être rajouter `ui` (ou `web`?) en plus de say, log, debuglog?
|
||||||
--> ou renommer `say` en `console`, et `ui` en `say`
|
--> ou renommer `say` en `console`, et `ui` en `say`
|
||||||
|
|
||||||
|
* [ ] ajouter une option `Application::MSG_SIGNALS` qui fait que
|
||||||
|
* les méthodes `msg::eXXX()` appellent automatiquement `app::_dispatch_signals()`
|
||||||
|
* [ ] ajouter une option `Application::MSG_ACTIONS` qui fait que
|
||||||
|
* `msg::section()` et/ou `msg::title()` appellent automatiquement `app::action()`
|
||||||
|
* `msg::estep()` appelle automatiquement `app::step()`
|
||||||
|
|
||||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
@ -7,10 +7,10 @@ use nulib\php\func;
|
|||||||
* Class Printer: wrapper autour d'une instance de {@link IContent} qui affiche
|
* Class Printer: wrapper autour d'une instance de {@link IContent} qui affiche
|
||||||
* le contenu au lieu de le retourner
|
* le contenu au lieu de le retourner
|
||||||
*
|
*
|
||||||
* cette classe est conçue pour wrapper des objets complexes dont le contenu est
|
* cette classe est conçue pour wrapper des objets complexes dont le contenu
|
||||||
* contruit au fur et à mesure: ainsi, les objets peuvent être utilisés tels
|
* peut être multiple et est contruit au fur et à mesure: ainsi, les objets
|
||||||
* quels dans un contenu, ou alors être wrappés si on veut simplement les
|
* peuvent être utilisés tels quels dans un contenu, ou alors être wrappés si
|
||||||
* afficher
|
* on veut simplement les afficher
|
||||||
*/
|
*/
|
||||||
class Printer implements IPrintable {
|
class Printer implements IPrintable {
|
||||||
function __construct(IContent $content) {
|
function __construct(IContent $content) {
|
||||||
@ -24,6 +24,9 @@ class Printer implements IPrintable {
|
|||||||
c::write($content);
|
c::write($content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* afficher le contenu retourné par une méthode spécifique de $content
|
||||||
|
*/
|
||||||
function __call($name, $args) {
|
function __call($name, $args) {
|
||||||
$content = func::call([$this->content, $name], ...$args);
|
$content = func::call([$this->content, $name], ...$args);
|
||||||
c::write($content);
|
c::write($content);
|
||||||
|
@ -15,6 +15,6 @@ class Date extends DateTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function format($format=self::DEFAULT_FORMAT): string {
|
function format($format=self::DEFAULT_FORMAT): string {
|
||||||
return \DateTime::format($format);
|
return parent::format($format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ namespace nulib\php\time;
|
|||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use nulib\str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class DateTime: une date et une heure
|
* Class DateTime: une date et une heure
|
||||||
@ -29,10 +30,20 @@ class DateTime extends \DateTime {
|
|||||||
else return new static($datetime);
|
else return new static($datetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function withn($datetime): ?self {
|
||||||
|
if ($datetime === null) return null;
|
||||||
|
elseif ($datetime instanceof static) return $datetime;
|
||||||
|
else return new static($datetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function ensure(&$datetime): void {
|
||||||
|
$datetime = static::withn($datetime);
|
||||||
|
}
|
||||||
|
|
||||||
const DMY_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))?$/';
|
const DMY_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))?$/';
|
||||||
const YMD_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})$/';
|
const YMD_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})$/';
|
||||||
const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/';
|
const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/';
|
||||||
const YMDHISZ_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})[tT](\d{2})(\d{2})(\d{2})?([zZ])?$/';
|
const YMDHISZ_PATTERN = '/^((?:\d{2})?\d{2})-?(\d{2})-?(\d{2})[tT](\d{2}):?(\d{2}):?(\d{2})?([zZ]|\+\d{2}:?\d{2})?$/';
|
||||||
|
|
||||||
|
|
||||||
protected static function get_value(array $datetime, ?string $key, ?string $k, ?int $index) {
|
protected static function get_value(array $datetime, ?string $key, ?string $k, ?int $index) {
|
||||||
@ -128,6 +139,12 @@ class DateTime extends \DateTime {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** retourner le nombre de secondes depuis minuit */
|
||||||
|
static function _nbsecs_format(\DateTime $datetime): string {
|
||||||
|
[$h, $m, $s] = explode(",", $datetime->format("H,i,s"));
|
||||||
|
return $h * 3600 + $m * 60 + $s;
|
||||||
|
}
|
||||||
|
|
||||||
static function _YmdHMSZ_format(\DateTime $datetime): string {
|
static function _YmdHMSZ_format(\DateTime $datetime): string {
|
||||||
$YmdHMS = $datetime->format("Ymd\\THis");
|
$YmdHMS = $datetime->format("Ymd\\THis");
|
||||||
$Z = $datetime->format("P");
|
$Z = $datetime->format("P");
|
||||||
@ -145,6 +162,7 @@ class DateTime extends \DateTime {
|
|||||||
"second" => "s",
|
"second" => "s",
|
||||||
"wday" => "N",
|
"wday" => "N",
|
||||||
"wnum" => "W",
|
"wnum" => "W",
|
||||||
|
"nbsecs" => [self::class, "_nbsecs_format"],
|
||||||
];
|
];
|
||||||
const STRING_FORMATS = [
|
const STRING_FORMATS = [
|
||||||
"timezone" => "P",
|
"timezone" => "P",
|
||||||
@ -155,14 +173,6 @@ class DateTime extends \DateTime {
|
|||||||
"YmdHMSZ" => [self::class, "_YmdHMSZ_format"],
|
"YmdHMSZ" => [self::class, "_YmdHMSZ_format"],
|
||||||
];
|
];
|
||||||
|
|
||||||
static function clone(DateTimeInterface $dateTime): self {
|
|
||||||
if ($dateTime instanceof static) return clone $dateTime;
|
|
||||||
$clone = new static();
|
|
||||||
$clone->setTimestamp($dateTime->getTimestamp());
|
|
||||||
$clone->setTimezone($dateTime->getTimezone());
|
|
||||||
return $clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* corriger une année à deux chiffres qui est située dans le passé et
|
* corriger une année à deux chiffres qui est située dans le passé et
|
||||||
* retourner l'année à 4 chiffres.
|
* retourner l'année à 4 chiffres.
|
||||||
@ -206,16 +216,34 @@ class DateTime extends \DateTime {
|
|||||||
return $year;
|
return $year;
|
||||||
}
|
}
|
||||||
|
|
||||||
function __construct($datetime="now", DateTimeZone $timezone=null) {
|
static function fix_z(?string $Z): ?string {
|
||||||
|
$Z = strtoupper($Z);
|
||||||
|
str::del_prefix($Z, "+");
|
||||||
|
if (preg_match('/^\d{4}$/', $Z)) {
|
||||||
|
$Z = substr($Z, 0, 2).":".substr($Z, 2);
|
||||||
|
}
|
||||||
|
if ($Z === "Z" || $Z === "UTC" || $Z === "00:00") return "UTC";
|
||||||
|
return "GMT+$Z";
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct($datetime="now", DateTimeZone $timezone=null, ?bool $forceLocalTimezone=null) {
|
||||||
|
$forceLocalTimezone ??= $timezone === null;
|
||||||
|
if ($forceLocalTimezone) {
|
||||||
|
$setTimezone = $timezone;
|
||||||
|
$timezone = null;
|
||||||
|
}
|
||||||
|
|
||||||
$datetime ??= "now";
|
$datetime ??= "now";
|
||||||
if ($datetime instanceof \DateTimeInterface) {
|
if ($datetime instanceof \DateTimeInterface) {
|
||||||
if ($timezone === null) $timezone = $datetime->getTimezone();
|
$timezone ??= $datetime->getTimezone();
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->setTimestamp($datetime->getTimestamp());
|
$this->setTimestamp($datetime->getTimestamp());
|
||||||
$this->setTimezone($timezone);
|
$this->setTimezone($timezone);
|
||||||
|
|
||||||
} elseif (is_int($datetime)) {
|
} elseif (is_int($datetime)) {
|
||||||
parent::__construct("now", $timezone);
|
parent::__construct("now", $timezone);
|
||||||
$this->setTimestamp($datetime);
|
$this->setTimestamp($datetime);
|
||||||
|
|
||||||
} elseif (is_string($datetime)) {
|
} elseif (is_string($datetime)) {
|
||||||
$Y = $H = $Z = null;
|
$Y = $H = $Z = null;
|
||||||
if (preg_match(self::DMY_PATTERN, $datetime, $ms)) {
|
if (preg_match(self::DMY_PATTERN, $datetime, $ms)) {
|
||||||
@ -253,22 +281,34 @@ class DateTime extends \DateTime {
|
|||||||
if ($Y !== null) {
|
if ($Y !== null) {
|
||||||
if ($H === null) $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d);
|
if ($H === null) $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d);
|
||||||
else $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H, $M, $S);
|
else $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H, $M, $S);
|
||||||
if ($Z !== null) $timezone = new DateTimeZone("UTC");
|
if ($Z !== null) $timezone ??= new DateTimeZone(self::fix_z($Z));
|
||||||
}
|
}
|
||||||
parent::__construct($datetime, $timezone);
|
parent::__construct($datetime, $timezone);
|
||||||
|
|
||||||
} elseif (is_array($datetime) && ($datetime = self::parse_array($datetime)) !== null) {
|
} elseif (is_array($datetime) && ($datetime = self::parse_array($datetime)) !== null) {
|
||||||
[$Y, $m, $d, $H, $M, $S, $tz] = $datetime;
|
$setTimezone = $timezone;
|
||||||
|
$timezone = null;
|
||||||
|
[$Y, $m, $d, $H, $M, $S, $Z] = $datetime;
|
||||||
if ($H === null && $M === null && $S === null) {
|
if ($H === null && $M === null && $S === null) {
|
||||||
$datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d);
|
$datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d);
|
||||||
} else {
|
} else {
|
||||||
$datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H ?? 0, $M ?? 0, $S ?? 0);
|
$datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H ?? 0, $M ?? 0, $S ?? 0);
|
||||||
}
|
}
|
||||||
if ($tz === "Z" || $tz === "z") $tz = "UTC";
|
if ($Z !== null) $timezone ??= new DateTimeZone(self::fix_z($Z));
|
||||||
$timezone = $tz !== null? new DateTimeZone($tz): null;
|
|
||||||
parent::__construct($datetime, $timezone);
|
parent::__construct($datetime, $timezone);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface");
|
throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($forceLocalTimezone) {
|
||||||
|
$setTimezone ??= new DateTimeZone(date_default_timezone_get());
|
||||||
|
$this->setTimezone($setTimezone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clone(): self {
|
||||||
|
return clone $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function diff($target, $absolute=false): DateInterval {
|
function diff($target, $absolute=false): DateInterval {
|
||||||
@ -276,7 +316,13 @@ class DateTime extends \DateTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function format($format=self::DEFAULT_FORMAT): string {
|
function format($format=self::DEFAULT_FORMAT): string {
|
||||||
return \DateTime::format($format);
|
if (array_key_exists($format, self::INT_FORMATS)) {
|
||||||
|
$format = self::INT_FORMATS[$format];
|
||||||
|
} elseif (array_key_exists($format, self::STRING_FORMATS)) {
|
||||||
|
$format = self::STRING_FORMATS[$format];
|
||||||
|
}
|
||||||
|
if (is_callable($format)) return $format($this);
|
||||||
|
else return \DateTime::format($format);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -331,7 +377,7 @@ class DateTime extends \DateTime {
|
|||||||
function __get($name) {
|
function __get($name) {
|
||||||
if (array_key_exists($name, self::INT_FORMATS)) {
|
if (array_key_exists($name, self::INT_FORMATS)) {
|
||||||
$format = self::INT_FORMATS[$name];
|
$format = self::INT_FORMATS[$name];
|
||||||
if (is_callable($format)) return $format($this);
|
if (is_callable($format)) return intval($format($this));
|
||||||
else return intval($this->format($format));
|
else return intval($this->format($format));
|
||||||
} elseif (array_key_exists($name, self::STRING_FORMATS)) {
|
} elseif (array_key_exists($name, self::STRING_FORMATS)) {
|
||||||
$format = self::STRING_FORMATS[$name];
|
$format = self::STRING_FORMATS[$name];
|
||||||
|
@ -40,8 +40,8 @@ class Delay {
|
|||||||
"s" => [1, 0],
|
"s" => [1, 0],
|
||||||
];
|
];
|
||||||
|
|
||||||
static function compute_dest(int $x, string $u, ?int $y, DateTime $from): array {
|
static function compute_dest(int $x, string $u, ?int $y, ?DateTimeInterface $from): array {
|
||||||
$dest = DateTime::clone($from);
|
$dest = DateTime::with($from)->clone();
|
||||||
$yu = null;
|
$yu = null;
|
||||||
switch ($u) {
|
switch ($u) {
|
||||||
case "w":
|
case "w":
|
||||||
@ -92,7 +92,7 @@ class Delay {
|
|||||||
function __construct($delay, ?DateTimeInterface $from=null) {
|
function __construct($delay, ?DateTimeInterface $from=null) {
|
||||||
if ($from === null) $from = new DateTime();
|
if ($from === null) $from = new DateTime();
|
||||||
if ($delay === "INF") {
|
if ($delay === "INF") {
|
||||||
$dest = DateTime::clone($from);
|
$dest = DateTime::with($from)->clone();
|
||||||
$dest->add(new DateInterval("P9999Y"));
|
$dest->add(new DateInterval("P9999Y"));
|
||||||
$repr = "INF";
|
$repr = "INF";
|
||||||
} elseif (is_int($delay)) {
|
} elseif (is_int($delay)) {
|
||||||
|
10
php/src/php/time/TODO.md
Normal file
10
php/src/php/time/TODO.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# nulib\php\time
|
||||||
|
|
||||||
|
* Date, DateTime sont immutables par défaut. par exemple, add() retourne un nouvel objet.
|
||||||
|
ajouter une version des méthodes qui modifie les données en place en les
|
||||||
|
préfixant de `_` e.g `_add()`
|
||||||
|
|
||||||
|
en terme d'implémentation, dériver \DateTime pour supporter les modification
|
||||||
|
en place, bien que ce ne soit pas le fonctionnement par défaut
|
||||||
|
|
||||||
|
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
@ -17,6 +17,11 @@ class str {
|
|||||||
return $s;
|
return $s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** s'assure que $s est une chaine s'il n'est pas null */
|
||||||
|
static final function ensure(&$s): void {
|
||||||
|
if ($s !== null) $s = self::with($s);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retourner $s converti en chaine non nulle, ou "" si $s est fausse selon les
|
* Retourner $s converti en chaine non nulle, ou "" si $s est fausse selon les
|
||||||
* règles de PHP
|
* règles de PHP
|
||||||
|
@ -1,124 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\tools;
|
|
||||||
|
|
||||||
use nulib\app;
|
|
||||||
use nulib\app\cli\Application;
|
|
||||||
use nulib\app\RunFile;
|
|
||||||
use nulib\ExitError;
|
|
||||||
use nulib\ext\yaml;
|
|
||||||
use nulib\os\path;
|
|
||||||
use nulib\os\proc\Cmd;
|
|
||||||
use nulib\os\sh;
|
|
||||||
use nulib\output\msg;
|
|
||||||
|
|
||||||
class BgLauncherApp extends Application {
|
|
||||||
const ACTION_INFOS = 0, ACTION_START = 1, ACTION_STOP = 2;
|
|
||||||
const ARGS = [
|
|
||||||
"purpose" => "lancer un script en tâche de fond",
|
|
||||||
"usage" => "ApplicationClass args...",
|
|
||||||
|
|
||||||
"sections" => [
|
|
||||||
parent::VERBOSITY_SECTION,
|
|
||||||
],
|
|
||||||
|
|
||||||
["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS,
|
|
||||||
"help" => "Afficher des informations sur la tâche",
|
|
||||||
],
|
|
||||||
["-s", "--start", "name" => "action", "value" => self::ACTION_START,
|
|
||||||
"help" => "Démarrer la tâche",
|
|
||||||
],
|
|
||||||
["-k", "--stop", "name" => "action", "value" => self::ACTION_STOP,
|
|
||||||
"help" => "Arrêter la tâche",
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
protected int $action = self::ACTION_START;
|
|
||||||
|
|
||||||
protected ?array $args = null;
|
|
||||||
|
|
||||||
static function show_infos(RunFile $runfile, ?int $level=null): void {
|
|
||||||
msg::print($runfile->getDesc(), $level);
|
|
||||||
msg::print(yaml::with(["data" => $runfile->read()]), ($level ?? 0) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
$args = $this->args;
|
|
||||||
|
|
||||||
$appClass = $args[0] ?? null;
|
|
||||||
if ($appClass === null) {
|
|
||||||
self::die("Vous devez spécifier la classe de l'application");
|
|
||||||
}
|
|
||||||
$appClass = $args[0] = str_replace("/", "\\", $appClass);
|
|
||||||
if (!class_exists($appClass)) {
|
|
||||||
self::die("$appClass: classe non trouvée");
|
|
||||||
}
|
|
||||||
|
|
||||||
$useRunfile = constant("$appClass::USE_RUNFILE");
|
|
||||||
if (!$useRunfile) {
|
|
||||||
self::die("Cette application ne supporte le lancement en tâche de fond");
|
|
||||||
}
|
|
||||||
|
|
||||||
$runfile = app::with($appClass)->getRunfile();
|
|
||||||
switch ($this->action) {
|
|
||||||
case self::ACTION_START:
|
|
||||||
$argc = count($args);
|
|
||||||
$appClass::_manage_runfile($argc, $args, $runfile);
|
|
||||||
if ($runfile->warnIfLocked()) self::exit(app::EC_LOCKED);
|
|
||||||
array_splice($args, 0, 0, [
|
|
||||||
PHP_BINARY,
|
|
||||||
path::abspath(NULIB_APP_app_launcher),
|
|
||||||
]);
|
|
||||||
app::params_putenv();
|
|
||||||
self::_start($args, $runfile);
|
|
||||||
break;
|
|
||||||
case self::ACTION_STOP:
|
|
||||||
self::_stop($runfile);
|
|
||||||
self::show_infos($runfile, -1);
|
|
||||||
break;
|
|
||||||
case self::ACTION_INFOS:
|
|
||||||
self::show_infos($runfile);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function _start(array $args, Runfile $runfile): void {
|
|
||||||
$pid = pcntl_fork();
|
|
||||||
if ($pid == -1) {
|
|
||||||
# parent, impossible de forker
|
|
||||||
throw new ExitError(app::EC_FORK_PARENT, "Unable to fork");
|
|
||||||
} elseif (!$pid) {
|
|
||||||
# child, fork ok
|
|
||||||
$runfile->wfPrepare($pid);
|
|
||||||
$outfile = $runfile->getOutfile() ?? "/tmp/NULIB_APP_app_console.out";
|
|
||||||
$exitcode = app::EC_FORK_CHILD;
|
|
||||||
try {
|
|
||||||
# rediriger STDIN, STDOUT et STDERR
|
|
||||||
fclose(fopen($outfile, "wb")); // vider le fichier
|
|
||||||
fclose(STDIN); $in = fopen("/dev/null", "rb");
|
|
||||||
fclose(STDOUT); $out = fopen($outfile, "ab");
|
|
||||||
fclose(STDERR); $err = fopen($outfile, "ab");
|
|
||||||
# puis lancer la commande
|
|
||||||
$cmd = new Cmd($args);
|
|
||||||
$cmd->addSource("/g/init.env");
|
|
||||||
$cmd->addRedir("both", $outfile, true);
|
|
||||||
$cmd->fork_exec($exitcode, false);
|
|
||||||
sh::_waitpid(-$pid, $exitcode);
|
|
||||||
} finally {
|
|
||||||
$runfile->wfReaped($exitcode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function _stop(Runfile $runfile): bool {
|
|
||||||
$data = $runfile->read();
|
|
||||||
$pid = $runfile->_getCid($data);
|
|
||||||
msg::action("stop $pid");
|
|
||||||
if ($runfile->wfKill($reason)) {
|
|
||||||
msg::asuccess();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
msg::afailure($reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\tools;
|
|
||||||
|
|
||||||
use nulib\app;
|
|
||||||
use nulib\app\cli\Application;
|
|
||||||
use nulib\output\msg;
|
|
||||||
use nulib\php\time\DateTime;
|
|
||||||
use nulib\text\words;
|
|
||||||
|
|
||||||
class SteamTrainApp extends Application {
|
|
||||||
const PROJDIR = __DIR__.'/../..';
|
|
||||||
const TITLE = "Train à vapeur";
|
|
||||||
const USE_LOGFILE = true;
|
|
||||||
const USE_RUNFILE = true;
|
|
||||||
const USE_RUNLOCK = true;
|
|
||||||
|
|
||||||
const ARGS = [
|
|
||||||
"purpose" => self::TITLE,
|
|
||||||
"description" => <<<EOT
|
|
||||||
Cette application peut être utilisée pour tester le lancement des tâches de fond
|
|
||||||
EOT,
|
|
||||||
|
|
||||||
["-c", "--count", "args" => 1,
|
|
||||||
"help" => "spécifier le nombre d'étapes",
|
|
||||||
],
|
|
||||||
["-f", "--force-enabled", "value" => true,
|
|
||||||
"help" => "lancer la commande même si les tâches planifiées sont désactivées",
|
|
||||||
],
|
|
||||||
["-n", "--no-install-signal-handler", "value" => false,
|
|
||||||
"help" => "ne pas installer le gestionnaire de signaux",
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $count = 100;
|
|
||||||
|
|
||||||
protected bool $forceEnabled = false;
|
|
||||||
|
|
||||||
protected bool $installSignalHandler = true;
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
app::check_bgapplication_enabled($this->forceEnabled);
|
|
||||||
if ($this->installSignalHandler) app::install_signal_handler();
|
|
||||||
$count = intval($this->count);
|
|
||||||
msg::info("Starting train for ".words::q($count, "step#s"));
|
|
||||||
app::action("Running train...", $count);
|
|
||||||
for ($i = 1; $i <= $count; $i++) {
|
|
||||||
msg::print("Tchou-tchou! x $i");
|
|
||||||
app::step();
|
|
||||||
sleep(1);
|
|
||||||
}
|
|
||||||
msg::info("Stopping train at ".new DateTime());
|
|
||||||
}
|
|
||||||
}
|
|
@ -276,7 +276,7 @@ class txt {
|
|||||||
* supprimer les diacritiques de la chaine $text
|
* supprimer les diacritiques de la chaine $text
|
||||||
*
|
*
|
||||||
* la translitération se fait avec les règles de la locale spécifiée.
|
* la translitération se fait avec les règles de la locale spécifiée.
|
||||||
* NB: la translitération ne fonctionne pas si LC_CTYPE == C ou POISX
|
* NB: la translitération ne fonctionne pas si LC_CTYPE == C ou POSIX
|
||||||
*/
|
*/
|
||||||
static final function remove_diacritics(?string $text, string $locale="fr_FR.UTF-8"): ?string {
|
static final function remove_diacritics(?string $text, string $locale="fr_FR.UTF-8"): ?string {
|
||||||
if ($text === null) return null;
|
if ($text === null) return null;
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
|
||||||
source "$(dirname -- "$0")/../../load.sh" || exit 1
|
|
||||||
|
|
||||||
declare -A DESTDIRS=(
|
|
||||||
[template-_bg_launcher.php]=sbin
|
|
||||||
[template-.launcher.php]=_cli
|
|
||||||
[template-_wrapper.sh]=_cli
|
|
||||||
)
|
|
||||||
declare -A MODES=(
|
|
||||||
[template-_bg_launcher.php]=+x
|
|
||||||
[template-.launcher.php]=
|
|
||||||
[template-_wrapper.sh]=+x
|
|
||||||
)
|
|
||||||
|
|
||||||
projdir=
|
|
||||||
args=(
|
|
||||||
"copier les templates dans le projet en cours"
|
|
||||||
#"usage"
|
|
||||||
-d:,--projdir: .
|
|
||||||
)
|
|
||||||
parse_args "$@"; set -- "${args[@]}"
|
|
||||||
|
|
||||||
if [ -n "$projdir" ]; then
|
|
||||||
cd "$projdir" || die
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -f composer.json ] || die "$(basename "$(dirname "$(pwd)")"): n'est pas un projet composer"
|
|
||||||
|
|
||||||
setx -a templates=ls_files "$MYDIR" "template-*"
|
|
||||||
for template in "${templates[@]}"; do
|
|
||||||
destdir="${DESTDIRS[$template]}"
|
|
||||||
[ -n "$destdir" ] || die "$template: la destination n'est pas configurée"
|
|
||||||
mode="${MODES[$template]}"
|
|
||||||
destname="${template#template-}"
|
|
||||||
|
|
||||||
tail -n+4 "$MYDIR/$template" >"$destdir/$destname"
|
|
||||||
[ -n "$mode" ] && chmod "$mode" "$destdir/$destname"
|
|
||||||
done
|
|
2
php/tests/.gitignore
vendored
Normal file
2
php/tests/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.db*
|
||||||
|
*.cache
|
@ -23,4 +23,72 @@ class argsTest extends TestCase {
|
|||||||
|
|
||||||
self::assertSame(["x", "1", "2", "3", "y"], args::from_array(["x", [1, 2, 3], "y"]));
|
self::assertSame(["x", "1", "2", "3", "y"], args::from_array(["x", [1, 2, 3], "y"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testBuild_query() {
|
||||||
|
self::assertSame(null, args::build_query(null));
|
||||||
|
self::assertSame(null, args::build_query([]));
|
||||||
|
|
||||||
|
self::assertSame(["a" => true], args::build_query(["a"]));
|
||||||
|
self::assertSame(["a" => true], args::build_query(["+a"]));
|
||||||
|
self::assertSame(["a" => false], args::build_query(["-a"]));
|
||||||
|
self::assertSame(["a" => false], args::build_query(["~a"]));
|
||||||
|
self::assertSame(["x" => "a"], args::build_query(["x=a"]));
|
||||||
|
self::assertSame(["x" => 0], args::build_query(["x:int=0"]));
|
||||||
|
self::assertSame(["x" => 42], args::build_query(["x:int=42"]));
|
||||||
|
self::assertSame(["x" => false], args::build_query(["x:bool=0"]));
|
||||||
|
self::assertSame(["x" => true], args::build_query(["x:bool=42"]));
|
||||||
|
self::assertSame(["x" => ["a", "b"]], args::build_query(["x:array=a,b"]));
|
||||||
|
self::assertSame(["x" => [0, 42]], args::build_query(["x:array:int=0,42"]));
|
||||||
|
self::assertSame(["x" => [0, 42]], args::build_query(["x:int:array=0,42"]));
|
||||||
|
|
||||||
|
self::assertSame(["x" => "a", "y" => "b"], args::build_query(["x=a", "y=b"]));
|
||||||
|
self::assertSame(["x" => ["a", "b"]], args::build_query(["x=a", "x=b"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testBuild_method_args() {
|
||||||
|
self::assertSame([], args::build_method_args(null));
|
||||||
|
self::assertSame([], args::build_method_args([]));
|
||||||
|
|
||||||
|
self::assertSame(["a"], args::build_method_args(["a"]));
|
||||||
|
self::assertSame(["a", "b"], args::build_method_args(["a", "b"]));
|
||||||
|
|
||||||
|
self::assertSame([0], args::build_method_args(["int:0"]));
|
||||||
|
self::assertSame([42], args::build_method_args(["int:42"]));
|
||||||
|
# pour le moment, pas de tint
|
||||||
|
self::assertSame([0], args::build_method_args(["int:"]));
|
||||||
|
self::assertSame([0], args::build_method_args(["int:truc"]));
|
||||||
|
|
||||||
|
self::assertSame([false], args::build_method_args(["bool:0"]));
|
||||||
|
self::assertSame([true], args::build_method_args(["bool:42"]));
|
||||||
|
self::assertSame([false], args::build_method_args(["bool:"]));
|
||||||
|
self::assertSame([true], args::build_method_args(["bool:truc"]));
|
||||||
|
# pour le moment, pas de tbool
|
||||||
|
self::assertSame([true], args::build_method_args(["bool:false"]));
|
||||||
|
self::assertSame([true], args::build_method_args(["bool:true"]));
|
||||||
|
|
||||||
|
self::assertSame([["a", "b"]], args::build_method_args(["array:a,b"]));
|
||||||
|
self::assertSame([["x" => "a", "y" => "b"]], args::build_method_args(["array:x:a,y:b"]));
|
||||||
|
# pour le moment, pas de tint
|
||||||
|
self::assertSame([[0, 42, 0, 0]], args::build_method_args(["array:int:0,42,,truc"]));
|
||||||
|
self::assertSame([["x" => 0, "y" => 42]], args::build_method_args(["array:int:x:0,y:42"]));
|
||||||
|
# pour le moment, pas de tbool
|
||||||
|
self::assertSame([[false, true, false, true, true, true]], args::build_method_args(["array:bool:0,42,,truc,false,true"]));
|
||||||
|
self::assertSame([["x" => false, "y" => true]], args::build_method_args(["array:bool:x:0,y:42"]));
|
||||||
|
|
||||||
|
self::assertSame([["a" => true]], args::build_method_args(["+a"]));
|
||||||
|
self::assertSame([["a" => false]], args::build_method_args(["-a"]));
|
||||||
|
self::assertSame([["a" => false]], args::build_method_args(["~a"]));
|
||||||
|
self::assertSame([["x" => "a"]], args::build_method_args(["x=a"]));
|
||||||
|
self::assertSame([["x" => 0]], args::build_method_args(["x:int=0"]));
|
||||||
|
self::assertSame([["x" => 42]], args::build_method_args(["x:int=42"]));
|
||||||
|
self::assertSame([["x" => false]], args::build_method_args(["x:bool=0"]));
|
||||||
|
self::assertSame([["x" => true]], args::build_method_args(["x:bool=42"]));
|
||||||
|
self::assertSame([["x" => ["a", "b"]]], args::build_method_args(["x:array=a,b"]));
|
||||||
|
self::assertSame([["x" => [0, 42]]], args::build_method_args(["x:array:int=0,42"]));
|
||||||
|
self::assertSame([["x" => [0, 42]]], args::build_method_args(["x:int:array=0,42"]));
|
||||||
|
|
||||||
|
self::assertSame([["x" => "a", "y" => "b"], "a", "b"], args::build_method_args(["x=a", "a", "y=b", "b"]));
|
||||||
|
|
||||||
|
self::assertSame([["x" => ["a", "b"]]], args::build_method_args(["x=a", "x=b"]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1
php/tests/db/sqlite/.gitignore
vendored
1
php/tests/db/sqlite/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/capacitor.db*
|
|
@ -5,6 +5,7 @@ use nulib\db\Capacitor;
|
|||||||
use nulib\db\sqlite\impl\MyChannel;
|
use nulib\db\sqlite\impl\MyChannel;
|
||||||
use nulib\db\sqlite\impl\MyChannelV2;
|
use nulib\db\sqlite\impl\MyChannelV2;
|
||||||
use nulib\db\sqlite\impl\MyChannelV3;
|
use nulib\db\sqlite\impl\MyChannelV3;
|
||||||
|
use nulib\db\sqlite\impl\MyIndexChannel;
|
||||||
use nulib\output\msg;
|
use nulib\output\msg;
|
||||||
use nulib\output\std\StdMessenger;
|
use nulib\output\std\StdMessenger;
|
||||||
use nulib\php\time\DateTime;
|
use nulib\php\time\DateTime;
|
||||||
@ -69,6 +70,42 @@ alter table my add column date_mod datetime;
|
|||||||
-- infos
|
-- infos
|
||||||
alter table my add column age integer;
|
alter table my add column age integer;
|
||||||
|
|
||||||
|
EOT;
|
||||||
|
self::assertSame($expected, $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMigrationIndex() {
|
||||||
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
|
$data = [
|
||||||
|
["un", "premier", "first"],
|
||||||
|
["deux", "deuxieme", "second"],
|
||||||
|
];
|
||||||
|
|
||||||
|
new Capacitor($storage, $channel = new MyIndexChannel());
|
||||||
|
$channel->reset(true);
|
||||||
|
$channel->chargeAll($data);
|
||||||
|
|
||||||
|
$sql = $channel->getCapacitor()->getCreateSql();
|
||||||
|
$class = MyIndexChannel::class;
|
||||||
|
$expected = <<<EOT
|
||||||
|
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||||
|
-- autogénéré à partir de $class
|
||||||
|
|
||||||
|
-- 0init
|
||||||
|
create table if not exists my_index (
|
||||||
|
name varchar not null primary key
|
||||||
|
, first varchar
|
||||||
|
, second varchar
|
||||||
|
, item__ mediumtext
|
||||||
|
, item__sum_ varchar(40)
|
||||||
|
, created_ datetime
|
||||||
|
, modified_ datetime
|
||||||
|
);
|
||||||
|
|
||||||
|
-- index
|
||||||
|
create index my_index_first on my_index(first);
|
||||||
|
create index my_index_second on my_index(second);
|
||||||
|
|
||||||
EOT;
|
EOT;
|
||||||
self::assertSame($expected, $sql);
|
self::assertSame($expected, $sql);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,12 @@ use nulib\db\Capacitor;
|
|||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
|
|
||||||
class SqliteStorageTest extends TestCase {
|
class SqliteStorageTest extends TestCase {
|
||||||
|
static function Txx(...$values): void {
|
||||||
|
foreach ($values as $value) {
|
||||||
|
var_export($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _testChargeStrings(SqliteStorage $storage, ?string $channel) {
|
function _testChargeStrings(SqliteStorage $storage, ?string $channel) {
|
||||||
$storage->reset($channel);
|
$storage->reset($channel);
|
||||||
$storage->charge($channel, "first");
|
$storage->charge($channel, "first");
|
||||||
@ -67,7 +73,8 @@ class SqliteStorageTest extends TestCase {
|
|||||||
$capacitor->charge(["name" => "third", "age" => 15]);
|
$capacitor->charge(["name" => "third", "age" => 15]);
|
||||||
$capacitor->charge(["name" => "fourth", "age" => 20]);
|
$capacitor->charge(["name" => "fourth", "age" => 20]);
|
||||||
|
|
||||||
$setDone = function ($item, $row, $suffix=null) {
|
$setDone = function ($row, $suffix=null) {
|
||||||
|
$item = $row["item"];
|
||||||
$updates = ["done" => 1];
|
$updates = ["done" => 1];
|
||||||
if ($suffix !== null) {
|
if ($suffix !== null) {
|
||||||
$item["name"] .= $suffix;
|
$item["name"] .= $suffix;
|
||||||
@ -76,9 +83,9 @@ class SqliteStorageTest extends TestCase {
|
|||||||
return $updates;
|
return $updates;
|
||||||
};
|
};
|
||||||
$capacitor->each(["age" => [">", 10]], $setDone, ["++"]);
|
$capacitor->each(["age" => [">", 10]], $setDone, ["++"]);
|
||||||
$capacitor->each(["done" => 0], $setDone, null);
|
$capacitor->each(["done" => 0], $setDone);
|
||||||
|
|
||||||
Txx(cl::all($capacitor->discharge(false)));
|
self::Txx(cl::all($capacitor->discharge(false)));
|
||||||
$capacitor->close();
|
$capacitor->close();
|
||||||
self::assertTrue(true);
|
self::assertTrue(true);
|
||||||
}
|
}
|
||||||
@ -133,16 +140,16 @@ class SqliteStorageTest extends TestCase {
|
|||||||
$capacitor->charge(["a" => null, "b" => null]);
|
$capacitor->charge(["a" => null, "b" => null]);
|
||||||
$capacitor->charge(["a" => "first", "b" => "second"]);
|
$capacitor->charge(["a" => "first", "b" => "second"]);
|
||||||
|
|
||||||
Txx("=== all");
|
self::Txx("=== all");
|
||||||
/** @var Sqlite $sqlite */
|
/** @var Sqlite $sqlite */
|
||||||
$sqlite = $capacitor->getStorage()->db();
|
$sqlite = $capacitor->getStorage()->db();
|
||||||
Txx(cl::all($sqlite->all([
|
self::Txx(cl::all($sqlite->all([
|
||||||
"select",
|
"select",
|
||||||
"from" => $capacitor->getChannel()->getTableName(),
|
"from" => $capacitor->getChannel()->getTableName(),
|
||||||
])));
|
])));
|
||||||
Txx("=== each");
|
self::Txx("=== each");
|
||||||
$capacitor->each(null, function ($item, $values) {
|
$capacitor->each(null, function ($row) {
|
||||||
Txx($values);
|
self::Txx($row);
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->close();
|
$capacitor->close();
|
||||||
@ -170,99 +177,100 @@ class SqliteStorageTest extends TestCase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->reset();
|
$capacitor->reset();
|
||||||
$capacitor->charge(["name" => "first", "age" => 5], function($item, ?array $values, ?array $pvalues) {
|
$capacitor->charge(["name" => "first", "age" => 5], function($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(5, $item["age"]);
|
self::assertSame(5, $item["age"]);
|
||||||
self::assertnotnull($values);
|
self::assertnotnull($row);
|
||||||
self::assertSame(["name", "age", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
self::assertSame(["name", "age", "item", "item__sum_", "created_", "modified_"], array_keys($row));
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first",
|
"name" => "first",
|
||||||
"age" => 5,
|
"age" => 5,
|
||||||
"item" => $item,
|
"item" => $item,
|
||||||
], cl::select($values, ["name", "age", "item"]));
|
], cl::select($row, ["name", "age", "item"]));
|
||||||
self::assertNull($pvalues);
|
self::assertNull($prow);
|
||||||
});
|
});
|
||||||
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $values, ?array $pvalues) {
|
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(10, $item["age"]);
|
self::assertSame(10, $item["age"]);
|
||||||
self::assertnotnull($values);
|
self::assertnotnull($row);
|
||||||
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($row));
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first",
|
"name" => "first",
|
||||||
"age" => 10,
|
"age" => 10,
|
||||||
"done" => 0,
|
"done" => 0,
|
||||||
"notes" => null,
|
"notes" => null,
|
||||||
"item" => $item,
|
"item" => $item,
|
||||||
], cl::select($values, ["name", "age", "done", "notes", "item"]));
|
], cl::select($row, ["name", "age", "done", "notes", "item"]));
|
||||||
self::assertNotNull($pvalues);
|
self::assertNotNull($prow);
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first",
|
"name" => "first",
|
||||||
"age" => 5,
|
"age" => 5,
|
||||||
"done" => 0,
|
"done" => 0,
|
||||||
"notes" => null,
|
"notes" => null,
|
||||||
"item" => ["name" => "first", "age" => 5],
|
"item" => ["name" => "first", "age" => 5],
|
||||||
], cl::select($pvalues, ["name", "age", "done", "notes", "item"]));
|
], cl::select($prow, ["name", "age", "done", "notes", "item"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->each(null, function($item, ?array $values) {
|
$capacitor->each(null, function(array $row) {
|
||||||
|
$item = $row["item"];
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(10, $item["age"]);
|
self::assertSame(10, $item["age"]);
|
||||||
self::assertnotnull($values);
|
self::assertnotnull($row);
|
||||||
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($row));
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first",
|
"name" => "first",
|
||||||
"age" => 10,
|
"age" => 10,
|
||||||
"done" => 0,
|
"done" => 0,
|
||||||
"notes" => null,
|
"notes" => null,
|
||||||
"item" => $item,
|
"item" => $item,
|
||||||
], cl::select($values, ["name", "age", "done", "notes", "item"]));
|
], cl::select($row, ["name", "age", "done", "notes", "item"]));
|
||||||
return [
|
return [
|
||||||
"done" => 1,
|
"done" => 1,
|
||||||
"notes" => "modified",
|
"notes" => "modified",
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $values, ?array $pvalues) {
|
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(10, $item["age"]);
|
self::assertSame(10, $item["age"]);
|
||||||
self::assertnotnull($values);
|
self::assertnotnull($row);
|
||||||
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($row));
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first",
|
"name" => "first",
|
||||||
"age" => 10,
|
"age" => 10,
|
||||||
"done" => 1,
|
"done" => 1,
|
||||||
"notes" => "modified",
|
"notes" => "modified",
|
||||||
"item" => $item,
|
"item" => $item,
|
||||||
], cl::select($values, ["name", "age", "done", "notes", "item"]));
|
], cl::select($row, ["name", "age", "done", "notes", "item"]));
|
||||||
self::assertNotNull($pvalues);
|
self::assertNotNull($prow);
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first",
|
"name" => "first",
|
||||||
"age" => 10,
|
"age" => 10,
|
||||||
"done" => 1,
|
"done" => 1,
|
||||||
"notes" => "modified",
|
"notes" => "modified",
|
||||||
"item" => $item,
|
"item" => $item,
|
||||||
], cl::select($pvalues, ["name", "age", "done", "notes", "item"]));
|
], cl::select($prow, ["name", "age", "done", "notes", "item"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->charge(["name" => "first", "age" => 20], function($item, ?array $values, ?array $pvalues) {
|
$capacitor->charge(["name" => "first", "age" => 20], function($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(20, $item["age"]);
|
self::assertSame(20, $item["age"]);
|
||||||
self::assertnotnull($values);
|
self::assertnotnull($row);
|
||||||
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values));
|
self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($row));
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first",
|
"name" => "first",
|
||||||
"age" => 20,
|
"age" => 20,
|
||||||
"done" => 1,
|
"done" => 1,
|
||||||
"notes" => "modified",
|
"notes" => "modified",
|
||||||
"item" => $item,
|
"item" => $item,
|
||||||
], cl::select($values, ["name", "age", "done", "notes", "item"]));
|
], cl::select($row, ["name", "age", "done", "notes", "item"]));
|
||||||
self::assertNotNull($pvalues);
|
self::assertNotNull($prow);
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first",
|
"name" => "first",
|
||||||
"age" => 10,
|
"age" => 10,
|
||||||
"done" => 1,
|
"done" => 1,
|
||||||
"notes" => "modified",
|
"notes" => "modified",
|
||||||
"item" => ["name" => "first", "age" => 10],
|
"item" => ["name" => "first", "age" => 10],
|
||||||
], cl::select($pvalues, ["name", "age", "done", "notes", "item"]));
|
], cl::select($prow, ["name", "age", "done", "notes", "item"]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,55 +295,55 @@ class SqliteStorageTest extends TestCase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->reset();
|
$capacitor->reset();
|
||||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 5], function ($item, ?array $values, ?array $pvalues) {
|
$nbModified = $capacitor->charge(["name" => "first", "age" => 5], function ($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 5,
|
"name" => "first", "age" => 5,
|
||||||
"item" => $item,
|
"item" => $item,
|
||||||
], cl::select($values, ["name", "age", "item"]));
|
], cl::select($row, ["name", "age", "item"]));
|
||||||
return ["item" => null];
|
return ["item" => null];
|
||||||
});
|
});
|
||||||
self::assertSame(1, $nbModified);
|
self::assertSame(1, $nbModified);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
# nb: on met des sleep() pour que la date de modification soit systématiquement différente
|
# nb: on met des sleep() pour que la date de modification soit systématiquement différente
|
||||||
|
|
||||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $values, ?array $pvalues) {
|
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 10,
|
"name" => "first", "age" => 10,
|
||||||
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
||||||
], cl::select($values, ["name", "age", "item", "item__sum_"]));
|
], cl::select($row, ["name", "age", "item", "item__sum_"]));
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 5,
|
"name" => "first", "age" => 5,
|
||||||
"item" => null, "item__sum_" => null,
|
"item" => null, "item__sum_" => null,
|
||||||
], cl::select($pvalues, ["name", "age", "item", "item__sum_"]));
|
], cl::select($prow, ["name", "age", "item", "item__sum_"]));
|
||||||
return ["item" => null];
|
return ["item" => null];
|
||||||
});
|
});
|
||||||
self::assertSame(1, $nbModified);
|
self::assertSame(1, $nbModified);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
|
||||||
# pas de modification ici
|
# pas de modification ici
|
||||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $values, ?array $pvalues) {
|
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 10,
|
"name" => "first", "age" => 10,
|
||||||
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
||||||
], cl::select($values, ["name", "age", "item", "item__sum_"]));
|
], cl::select($row, ["name", "age", "item", "item__sum_"]));
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 10,
|
"name" => "first", "age" => 10,
|
||||||
"item" => null, "item__sum_" => null,
|
"item" => null, "item__sum_" => null,
|
||||||
], cl::select($pvalues, ["name", "age", "item", "item__sum_"]));
|
], cl::select($prow, ["name", "age", "item", "item__sum_"]));
|
||||||
return ["item" => null];
|
return ["item" => null];
|
||||||
});
|
});
|
||||||
self::assertSame(0, $nbModified);
|
self::assertSame(0, $nbModified);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
|
||||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 20], function ($item, ?array $values, ?array $pvalues) {
|
$nbModified = $capacitor->charge(["name" => "first", "age" => 20], function ($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 20,
|
"name" => "first", "age" => 20,
|
||||||
"item" => $item, "item__sum_" => "001b91982b4e0883b75428c0eb28573a5dc5f7a5",
|
"item" => $item, "item__sum_" => "001b91982b4e0883b75428c0eb28573a5dc5f7a5",
|
||||||
], cl::select($values, ["name", "age", "item", "item__sum_"]));
|
], cl::select($row, ["name", "age", "item", "item__sum_"]));
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 10,
|
"name" => "first", "age" => 10,
|
||||||
"item" => null, "item__sum_" => null,
|
"item" => null, "item__sum_" => null,
|
||||||
], cl::select($pvalues, ["name", "age", "item", "item__sum_"]));
|
], cl::select($prow, ["name", "age", "item", "item__sum_"]));
|
||||||
return ["item" => null];
|
return ["item" => null];
|
||||||
});
|
});
|
||||||
self::assertSame(1, $nbModified);
|
self::assertSame(1, $nbModified);
|
||||||
|
@ -3,6 +3,7 @@ namespace nulib\db\sqlite;
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use nulib\tests\TestCase;
|
use nulib\tests\TestCase;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
class SqliteTest extends TestCase {
|
class SqliteTest extends TestCase {
|
||||||
const CREATE_PERSON = "create table person(nom varchar, prenom varchar, age integer)";
|
const CREATE_PERSON = "create table person(nom varchar, prenom varchar, age integer)";
|
||||||
@ -12,8 +13,8 @@ class SqliteTest extends TestCase {
|
|||||||
function testMigration() {
|
function testMigration() {
|
||||||
$sqlite = new Sqlite(":memory:", [
|
$sqlite = new Sqlite(":memory:", [
|
||||||
"migration" => [
|
"migration" => [
|
||||||
self::CREATE_PERSON,
|
"create" => self::CREATE_PERSON,
|
||||||
self::INSERT_JEPHTE,
|
"insert" => self::INSERT_JEPHTE,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
self::assertSame("clain", $sqlite->get("select nom, age from person"));
|
self::assertSame("clain", $sqlite->get("select nom, age from person"));
|
||||||
@ -30,15 +31,15 @@ class SqliteTest extends TestCase {
|
|||||||
], $sqlite->get("select nom, age from person where nom = 'payet'", null, true));
|
], $sqlite->get("select nom, age from person where nom = 'payet'", null, true));
|
||||||
|
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
["key" => "0", "value" => self::CREATE_PERSON, "done" => 1],
|
["channel" => "", "name" => "create", "done" => 1],
|
||||||
["key" => "1", "value" => self::INSERT_JEPHTE, "done" => 1],
|
["channel" => "", "name" => "insert", "done" => 1],
|
||||||
], iterator_to_array($sqlite->all("select key, value, done from _migration")));
|
], iterator_to_array($sqlite->all("select channel, name, done from _migration")));
|
||||||
}
|
}
|
||||||
|
|
||||||
function testException() {
|
function testException() {
|
||||||
$sqlite = new Sqlite(":memory:");
|
$sqlite = new Sqlite(":memory:");
|
||||||
self::assertException(Exception::class, [$sqlite, "exec"], "prout");
|
self::assertException(Exception::class, [$sqlite, "exec"], "prout");
|
||||||
self::assertException(SqliteException::class, [$sqlite, "exec"], ["prout"]);
|
self::assertException(ValueException::class, [$sqlite, "exec"], ["prout"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertInserted(Sqlite $sqlite, array $row, array $query): void {
|
protected function assertInserted(Sqlite $sqlite, array $row, array $query): void {
|
||||||
@ -141,6 +142,10 @@ class SqliteTest extends TestCase {
|
|||||||
|
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
["count" => 2],
|
["count" => 2],
|
||||||
], iterator_to_array($sqlite->all(["select count(name) as count from user", "group by" => ["amount"], "having" => ["count(name) = 2"]])));
|
], iterator_to_array($sqlite->all([
|
||||||
|
"select count(name) as count from user",
|
||||||
|
"group by" => ["amount"],
|
||||||
|
"having" => ["count(name) = 2"],
|
||||||
|
])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
php/tests/db/sqlite/impl/MyIndexChannel.php
Normal file
29
php/tests/db/sqlite/impl/MyIndexChannel.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\sqlite\impl;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\db\CapacitorChannel;
|
||||||
|
|
||||||
|
class MyIndexChannel extends CapacitorChannel {
|
||||||
|
const NAME = "my_index";
|
||||||
|
const TABLE_NAME = "my_index";
|
||||||
|
const COLUMN_DEFINITIONS = [
|
||||||
|
"name" => "varchar not null primary key",
|
||||||
|
"first" => "varchar",
|
||||||
|
"second" => "varchar",
|
||||||
|
];
|
||||||
|
const MIGRATION = [
|
||||||
|
"index" => [
|
||||||
|
"create index my_index_first on my_index(first)",
|
||||||
|
"create index my_index_second on my_index(second)",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
function getItemValues($item): ?array {
|
||||||
|
return cl::select($item, [
|
||||||
|
"name" => 0,
|
||||||
|
"first" => 1,
|
||||||
|
"second" => 2,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ class DateTest extends TestCase {
|
|||||||
|
|
||||||
function testClone() {
|
function testClone() {
|
||||||
$date = self::dt("now");
|
$date = self::dt("now");
|
||||||
$clone = Date::clone($date);
|
$clone = $date->clone();
|
||||||
self::assertInstanceOf(DateTime::class, $clone);
|
self::assertInstanceOf(DateTime::class, $clone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,13 +32,17 @@ class DateTimeTest extends TestCase {
|
|||||||
|
|
||||||
function testDateTimeZ() {
|
function testDateTimeZ() {
|
||||||
$date = new DateTime("20240405T091523Z");
|
$date = new DateTime("20240405T091523Z");
|
||||||
|
self::assertSame("20240405T131523", $date->YmdHMS);
|
||||||
|
self::assertSame("20240405T131523+04:00", $date->YmdHMSZ);
|
||||||
|
# comme on spécifie la timezone, la valeur Z est ignorée
|
||||||
|
$date = new DateTime("20240405T091523Z", new DateTimeZone("Indian/Reunion"));
|
||||||
self::assertSame("20240405T091523", $date->YmdHMS);
|
self::assertSame("20240405T091523", $date->YmdHMS);
|
||||||
self::assertSame("20240405T091523Z", $date->YmdHMSZ);
|
self::assertSame("20240405T091523+04:00", $date->YmdHMSZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testClone() {
|
function testClone() {
|
||||||
$date = self::dt("now");
|
$date = self::dt("now");
|
||||||
$clone = DateTime::clone($date);
|
$clone = $date->clone();
|
||||||
self::assertInstanceOf(DateTime::class, $clone);
|
self::assertInstanceOf(DateTime::class, $clone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +55,7 @@ class DateTimeTest extends TestCase {
|
|||||||
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("20240405")));
|
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("20240405")));
|
||||||
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("240405")));
|
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("240405")));
|
||||||
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("20240405T091523")));
|
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("20240405T091523")));
|
||||||
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("20240405T091523Z")));
|
self::assertSame("05/04/2024 13:15:23", strval(new DateTime("20240405T091523Z")));
|
||||||
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("5/4/2024 9:15:23")));
|
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("5/4/2024 9:15:23")));
|
||||||
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("5/4/2024 9.15.23")));
|
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("5/4/2024 9.15.23")));
|
||||||
self::assertSame("05/04/2024 09:15:00", strval(new DateTime("5/4/2024 9:15")));
|
self::assertSame("05/04/2024 09:15:00", strval(new DateTime("5/4/2024 9:15")));
|
||||||
|
@ -74,7 +74,7 @@ function build_check_env() {
|
|||||||
if template_copy_missing "$PROJDIR/$file"; then
|
if template_copy_missing "$PROJDIR/$file"; then
|
||||||
updated=1
|
updated=1
|
||||||
setx name=basename -- "$file"
|
setx name=basename -- "$file"
|
||||||
name="${name#.}"; name="${name%.}"
|
name="${name#.}"; name="${name%.*}"
|
||||||
setx file=dirname -- "$file"
|
setx file=dirname -- "$file"
|
||||||
file="$file/$name"
|
file="$file/$name"
|
||||||
updatedfiles+=("$file")
|
updatedfiles+=("$file")
|
||||||
@ -86,7 +86,7 @@ function build_check_env() {
|
|||||||
if template_copy_missing "$file"; then
|
if template_copy_missing "$file"; then
|
||||||
updated=1
|
updated=1
|
||||||
setx name=basename -- "$file"
|
setx name=basename -- "$file"
|
||||||
name="${name#.}"; name="${name%.}"
|
name="${name#.}"; name="${name%.*}"
|
||||||
setx file=dirname -- "$file"
|
setx file=dirname -- "$file"
|
||||||
file="$file/$name"
|
file="$file/$name"
|
||||||
updatedfiles+=("${file#$PROJDIR/}")
|
updatedfiles+=("${file#$PROJDIR/}")
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
# TODO Faire une copie de ce script dans un répertoire de l'application web
|
# TODO Faire une copie de ce script dans un répertoire de l'application web
|
||||||
# (dans le répertoire _cli/ par défaut) et modifier les paramètres si nécessaire
|
# (dans le répertoire cli_config/ par défaut) et modifier les paramètres si nécessaire
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
<?php
|
<?php
|
||||||
require __DIR__ . '/../vendor/autoload.php';
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
# Lancer une application en ligne de commande
|
# Lancer une application en ligne de commande
|
||||||
|
|
||||||
const NULIB_APP_app_params = [
|
const NULIB_APP_app_params = [
|
||||||
"projdir" => __DIR__ . '/..',
|
"projdir" => __DIR__.'/..',
|
||||||
"appcode" => \app\config\bootstrap::APPCODE,
|
"appcode" => \app\config\bootstrap::APPCODE,
|
||||||
];
|
];
|
||||||
require __DIR__.'/../vendor/nulib/base/php/src/app/cli/include-launcher.php';
|
require __DIR__.'/../vendor/nulib/base/php/src/app/cli/include-launcher.php';
|
@ -6,10 +6,10 @@ require __DIR__.'/../vendor/autoload.php';
|
|||||||
# Lancer une application en tâche de fond
|
# Lancer une application en tâche de fond
|
||||||
|
|
||||||
use nulib\app;
|
use nulib\app;
|
||||||
use nulib\tools\BgLauncherApp;
|
use nulib\cli\BgLauncherApp;
|
||||||
|
|
||||||
# chemin vers le lanceur PHP
|
# chemin vers le lanceur PHP
|
||||||
const NULIB_APP_app_launcher = __DIR__.'/../_cli/.launcher.php';
|
const NULIB_APP_app_launcher = __DIR__.'/../@@CLI@@/.launcher.php';
|
||||||
|
|
||||||
app::init([
|
app::init([
|
||||||
"projdir" => __DIR__.'/..',
|
"projdir" => __DIR__.'/..',
|
@ -1,5 +1,5 @@
|
|||||||
# TODO Faire une copie de ce script dans un répertoire de l'application web
|
# TODO Faire une copie de ce script dans un répertoire de l'application web
|
||||||
# (dans le répertoire _cli/ par défaut) et modifier les paramétres si nécessaire
|
# (dans le répertoire cli_config/ par défaut) et modifier les paramétres si nécessaire
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
#!/bin/bash
|
#!/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
|
||||||
@ -57,7 +57,7 @@ Vérification des liens..."
|
|||||||
for i in *.php*; do
|
for i in *.php*; do
|
||||||
[ -f "$i" ] || continue
|
[ -f "$i" ] || continue
|
||||||
name="bin/${i%.*}.php"
|
name="bin/${i%.*}.php"
|
||||||
dest="../_cli/_wrapper.sh"
|
dest="../@@CLI@@/_wrapper.sh"
|
||||||
link="../bin/${i%.*}.php"
|
link="../bin/${i%.*}.php"
|
||||||
if [ -L "$link" ]; then
|
if [ -L "$link" ]; then
|
||||||
echo "* $name OK"
|
echo "* $name OK"
|
@ -467,9 +467,11 @@ OPTIONS
|
|||||||
|
|
||||||
function host_docker_run() {
|
function host_docker_run() {
|
||||||
# lancer une commande avec docker
|
# lancer une commande avec docker
|
||||||
if [ "$1" == composer ]; then
|
case "$1" in
|
||||||
: # pas d'analyse d'argument pour composer
|
ci|cu|composer)
|
||||||
else
|
: # pas d'analyse d'argument pour les commandes composer
|
||||||
|
;;
|
||||||
|
*)
|
||||||
SOPTS=+w:
|
SOPTS=+w:
|
||||||
LOPTS=help,chdir:,no-use-rslave
|
LOPTS=help,chdir:,no-use-rslave
|
||||||
args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args"
|
args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args"
|
||||||
@ -513,7 +515,7 @@ OPTIONS
|
|||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
fi
|
esac
|
||||||
|
|
||||||
args=(
|
args=(
|
||||||
run -it --rm
|
run -it --rm
|
||||||
@ -535,26 +537,34 @@ OPTIONS
|
|||||||
|
|
||||||
# monter le répertoire qui contient $PROJDIR
|
# monter le répertoire qui contient $PROJDIR
|
||||||
mount_composer=
|
mount_composer=
|
||||||
mount_runphp=1
|
mount_standalone=1
|
||||||
|
mount_mount=1
|
||||||
if [ -z "$PROJDIR" -o "${PROJDIR#$HOME/}" != "$PROJDIR" -o "$PROJDIR" == "$HOME" ]; then
|
if [ -z "$PROJDIR" -o "${PROJDIR#$HOME/}" != "$PROJDIR" -o "$PROJDIR" == "$HOME" ]; then
|
||||||
# bind mount $HOME
|
# bind mount $HOME
|
||||||
args+=(-v "$HOME:$HOME${UseRslave:+:rslave}")
|
args+=(-v "$HOME:$HOME${UseRslave:+:rslave}")
|
||||||
[ -n "$RUNPHP_STANDALONE" ] &&
|
if [ -n "$RUNPHP_STANDALONE" -a "${RUNPHP_STANDALONE#$HOME/}" != "$RUNPHP_STANDALONE" ]; then
|
||||||
[ "${RUNPHP_STANDALONE#$HOME/}" != "$RUNPHP_STANDALONE" ] &&
|
mount_standalone=
|
||||||
mount_runphp=
|
fi
|
||||||
|
if [ -n "$RUNPHP_MOUNT" -a "${RUNPHP_MOUNT#$HOME/}" != "$RUNPHP_MOUNT" ]; then
|
||||||
|
mount_mount=
|
||||||
|
fi
|
||||||
elif [ -n "$PROJDIR" ]; then
|
elif [ -n "$PROJDIR" ]; then
|
||||||
# bind mount uniquement le répertoire du projet
|
# bind mount uniquement le répertoire du projet
|
||||||
args+=(-v "$PROJDIR:$PROJDIR${UseRslave:+:rslave}")
|
args+=(-v "$PROJDIR:$PROJDIR${UseRslave:+:rslave}")
|
||||||
mount_composer=1
|
mount_composer=1
|
||||||
[ "$RUNPHP_STANDALONE" == "$PROJDIR" ] && mount_runphp=
|
[ "$RUNPHP_STANDALONE" == "$PROJDIR" ] && mount_standalone=
|
||||||
|
[ "$RUNPHP_MOUNT" == "$PROJDIR" ] && mount_mount=
|
||||||
fi
|
fi
|
||||||
if [ -n "$mount_composer" -a -d "$HOME/.composer" ]; then
|
if [ -n "$mount_composer" -a -d "$HOME/.composer" ]; then
|
||||||
# monter la configuration de composer
|
# monter la configuration de composer
|
||||||
args+=(-v "$HOME/.composer:$HOME/.composer")
|
args+=(-v "$HOME/.composer:$HOME/.composer")
|
||||||
fi
|
fi
|
||||||
if [ -n "$RUNPHP_STANDALONE" -a -n "$mount_runphp" ]; then
|
if [ -n "$RUNPHP_STANDALONE" -a -n "$mount_standalone" ]; then
|
||||||
args+=(-v "$RUNPHP_STANDALONE:$RUNPHP_STANDALONE")
|
args+=(-v "$RUNPHP_STANDALONE:$RUNPHP_STANDALONE")
|
||||||
fi
|
fi
|
||||||
|
if [ -n "$RUNPHP_MOUNT" -a -n "$mount_mount" ]; then
|
||||||
|
args+=(-v "$RUNPHP_MOUNT:$RUNPHP_MOUNT")
|
||||||
|
fi
|
||||||
args+=(-w "$(pwd)")
|
args+=(-w "$(pwd)")
|
||||||
|
|
||||||
# lancer avec l'utilisateur courant
|
# lancer avec l'utilisateur courant
|
||||||
@ -589,9 +599,22 @@ function container_exec() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$1" == composer ]; then
|
# pour les commandes suivantes, pas d'analyse d'argument
|
||||||
: # pas d'analyse d'argument pour composer
|
case "$1" in
|
||||||
else
|
ci)
|
||||||
|
eecho "== installing composer dependencies"
|
||||||
|
shift
|
||||||
|
composer i "$@"
|
||||||
|
;;
|
||||||
|
cu)
|
||||||
|
eecho "== upgrading composer dependencies"
|
||||||
|
shift
|
||||||
|
composer u "$@"
|
||||||
|
;;
|
||||||
|
composer)
|
||||||
|
"$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
SOPTS=+w:
|
SOPTS=+w:
|
||||||
LOPTS=chdir:
|
LOPTS=chdir:
|
||||||
args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args"
|
args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args"
|
||||||
@ -606,26 +629,14 @@ function container_exec() {
|
|||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $# -eq 0 ]; then
|
[ $# -gt 0 ] || die "no command specified"
|
||||||
die "no command specified"
|
|
||||||
elif [ "$1" == ci ]; then
|
|
||||||
eecho "== installing composer dependencies"
|
|
||||||
shift
|
|
||||||
composer i "$@"
|
|
||||||
elif [ "$1" == cu ]; then
|
|
||||||
eecho "== upgrading composer dependencies"
|
|
||||||
shift
|
|
||||||
composer u "$@"
|
|
||||||
elif [ "$1" == composer ]; then
|
|
||||||
"$@"
|
|
||||||
else
|
|
||||||
if [ -n "$chdir" ]; then
|
if [ -n "$chdir" ]; then
|
||||||
cd "$chdir" || exit 1
|
cd "$chdir" || exit 1
|
||||||
fi
|
fi
|
||||||
exec "$@"
|
exec "$@"
|
||||||
fi
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -40,11 +40,31 @@ p == 1 {
|
|||||||
ac_clean "$conf0"
|
ac_clean "$conf0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare -A PHPWRAPPER_DESTDIRS=(
|
||||||
|
[_bg_launcher.php]=@@SBIN@@
|
||||||
|
[.launcher.php]=@@CLI@@
|
||||||
|
[_wrapper.sh]=@@CLI@@
|
||||||
|
)
|
||||||
|
declare -A PHPWRAPPER_MODES=(
|
||||||
|
[_bg_launcher.php]=+x
|
||||||
|
[.launcher.php]=
|
||||||
|
[_wrapper.sh]=+x
|
||||||
|
)
|
||||||
|
|
||||||
projdir=
|
projdir=
|
||||||
|
install_phpwrappers=auto
|
||||||
args=(
|
args=(
|
||||||
"Mettre à jour le script runphp"
|
"Mettre à jour le script runphp"
|
||||||
"[path/to/runphp]"
|
"[path/to/runphp]"
|
||||||
-d:,--projdir:PROJDIR . "Copier les fichiers pour un projet de l'université de la Réunion"
|
-d:,--projdir:PROJDIR . "\
|
||||||
|
Copier les fichiers pour un projet de l'université de la Réunion:
|
||||||
|
- BUILDENV0 et BUILDENV sont fixés à ..env.dist et .env
|
||||||
|
- les fichiers ..env.dist et .runphp.conf sont créés le cas échéant
|
||||||
|
- le script build est mis à jour
|
||||||
|
- les wrappers PHP pour la gestion des tâches de fond sont mis à jour le cas
|
||||||
|
échéant"
|
||||||
|
-p,--phpwrappers-install install_phpwrappers=1 "forcer l'installation des wrappers PHP"
|
||||||
|
--np,--no-phpwrappers-install install_phpwrappers= "ne pas installer les wrappers PHP"
|
||||||
)
|
)
|
||||||
parse_args "$@"; set -- "${args[@]}"
|
parse_args "$@"; set -- "${args[@]}"
|
||||||
|
|
||||||
@ -93,6 +113,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# (Re)construire le fichier destination
|
# (Re)construire le fichier destination
|
||||||
|
estep "$(relpath "$runphp")"
|
||||||
(
|
(
|
||||||
cat "$preamble"
|
cat "$preamble"
|
||||||
echo
|
echo
|
||||||
@ -102,24 +123,81 @@ fi
|
|||||||
) >"$runphp"
|
) >"$runphp"
|
||||||
[ -x "$runphp" ] || chmod +x "$runphp"
|
[ -x "$runphp" ] || chmod +x "$runphp"
|
||||||
|
|
||||||
|
eval "$(
|
||||||
|
vars=(PROJDIR COMPOSERDIR COMPOSERPHAR VENDORDIR BUILDENV0 BUILDENV BUILD_FLAVOUR DIST IMAGENAME)
|
||||||
|
arrays=(BUILD_IMAGES DISTFILES TEMPLATEFILES VARFILES)
|
||||||
|
for var in "${vars[@]}"; do eval "$var="; done
|
||||||
|
for array in "${arrays[@]}"; do eval "$array=()"; done
|
||||||
|
source "$runphp"
|
||||||
|
for var in "${vars[@]}"; do echo_setv2 "$var"; done
|
||||||
|
for array in "${arrays[@]}"; do echo_seta2 "$array"; done
|
||||||
|
)"
|
||||||
|
|
||||||
|
estep "$(relpath "$rundir/Dockerfile.runphp")"
|
||||||
rsync -lpt "$MYDIR/Dockerfile.runphp" "$rundir/"
|
rsync -lpt "$MYDIR/Dockerfile.runphp" "$rundir/"
|
||||||
|
|
||||||
if [ -n "$projdir" ]; then
|
if [ -n "$projdir" ]; then
|
||||||
if testdiff "$rundir/build" "$MYDIR/build"; then
|
if testdiff "$rundir/build" "$MYDIR/build"; then
|
||||||
|
estep "$(relpath "$rundir/build")"
|
||||||
cp "$MYDIR/build" "$rundir/build"
|
cp "$MYDIR/build" "$rundir/build"
|
||||||
chmod +x "$rundir/build"
|
chmod +x "$rundir/build"
|
||||||
fi
|
fi
|
||||||
if [ ! -f "$projdir/..env.dist" ]; then
|
if [ ! -f "$projdir/..env.dist" ]; then
|
||||||
|
estep "$(relpath "$projdir/..env.dist")"
|
||||||
sed <"$MYDIR/dot-build.env.dist" >"$projdir/..env.dist" '
|
sed <"$MYDIR/dot-build.env.dist" >"$projdir/..env.dist" '
|
||||||
/^IMAGENAME=/s/=.*\//='"$(basename -- "$projdir")"'\//
|
/^IMAGENAME=/s/=.*\//='"$(basename -- "$projdir")"'\//
|
||||||
'
|
'
|
||||||
initial_config=1
|
initial_config=1
|
||||||
fi
|
fi
|
||||||
if [ ! -f "$projdir/.runphp.conf" ]; then
|
if [ ! -f "$projdir/.runphp.conf" ]; then
|
||||||
|
estep "$(relpath "$projdir/.runphp.conf")"
|
||||||
sed <"$MYDIR/dot-runphp.conf" >"$projdir/.runphp.conf" '
|
sed <"$MYDIR/dot-runphp.conf" >"$projdir/.runphp.conf" '
|
||||||
/^RUNPHP=/s/=.*/=sbin\/runphp/
|
/^RUNPHP=/s/=.*/=sbin\/runphp/
|
||||||
'
|
'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
sbin_path=sbin
|
||||||
|
cli_path=cli_config
|
||||||
|
if [ "$install_phpwrappers" == auto ]; then
|
||||||
|
if [ ! -f "$PROJDIR/$COMPOSERDIR/composer.json" ]; then
|
||||||
|
# ce doit être un projet PHP
|
||||||
|
install_phpwrappers=
|
||||||
|
elif [ -d "$projdir/cli_config" ]; then
|
||||||
|
install_phpwrappers=1
|
||||||
|
sbin_path=sbin
|
||||||
|
cli_path=cli_config
|
||||||
|
elif [ -d "$projdir/_cli" ]; then
|
||||||
|
install_phpwrappers=1
|
||||||
|
sbin_path=sbin
|
||||||
|
cli_path=_cli
|
||||||
|
else
|
||||||
|
install_phpwrappers=
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$install_phpwrappers" ]; then
|
||||||
|
setx -a phpwrappers=ls_files "$MYDIR" "phpwrapper-*"
|
||||||
|
for phpwrapper in "${phpwrappers[@]}"; do
|
||||||
|
destname="${phpwrapper#phpwrapper-}"
|
||||||
|
destdir="${PHPWRAPPER_DESTDIRS[$destname]}"
|
||||||
|
[ -n "$destdir" ] || die "$phpwrapper: la destination n'est pas configurée"
|
||||||
|
mode="${PHPWRAPPER_MODES[$destname]}"
|
||||||
|
|
||||||
|
case "$destdir" in
|
||||||
|
@@SBIN@@) destdir="$PROJDIR/$sbin_path";;
|
||||||
|
@@CLI@@) destdir="$PROJDIR/$cli_path";;
|
||||||
|
*) destdir="$PROJDIR/$destdir";;
|
||||||
|
esac
|
||||||
|
|
||||||
|
estep "$(relpath "$destdir/$destname")"
|
||||||
|
mkdir -p "$destdir"
|
||||||
|
tail -n+4 "$MYDIR/$phpwrapper" | sed "
|
||||||
|
s|/@@SBIN@@/|/$sbin_path/|
|
||||||
|
s|/@@CLI@@/|/$cli_path/|
|
||||||
|
" >"$destdir/$destname"
|
||||||
|
[ -n "$mode" ] && chmod "$mode" "$destdir/$destname"
|
||||||
|
done
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ -n "$initial_config" ]
|
[ -n "$initial_config" ]
|
||||||
|
21
wip/pman.md
21
wip/pman.md
@ -6,9 +6,22 @@ outil pour gérer les projets PHP
|
|||||||
projets dépendants du projet courant
|
projets dépendants du projet courant
|
||||||
* pver: gestion des versions.
|
* pver: gestion des versions.
|
||||||
calculer la prochaine version en respectant semver
|
calculer la prochaine version en respectant semver
|
||||||
* pmer: gérer les branches de features et hotfixes.
|
|
||||||
* prel: faire une release.
|
## scripts de gestion de projet
|
||||||
ces outils peuvent agir sur les projets dépendants: faire une release sur un
|
|
||||||
projet downstream, ou synchroniser la version depuis un projet upstream
|
définir précisément le rôle des scripts
|
||||||
|
* pdist: créer la branche DIST, basculer dessus, merger MAIN dans DIST
|
||||||
|
* pmain: initialiser la branche MAIN (si nouveau dépôt), basculer dessus, merger DEVELOP dans MAIN
|
||||||
|
(s'occupe aussi de la configuration pman.conf)
|
||||||
|
* pdev: créer la branche DEVELOP, basculer dessus
|
||||||
|
* pwip: créer une branche WIP, basculer dessus si unique (ou laisser le choix), merger WIP dans DEVELOP
|
||||||
|
* PEUT-ETRE: pfix: créer une branche HOTFIX, basculer dessus si unique (ou laisser le choix), merger HOTFIX dans MAIN
|
||||||
|
* prel: faire une release de DEVELOP dans MAIN. à terme, support des branches de hotfix
|
||||||
|
* il s'agit d'une spécialisation de pmain et/ou pfix pour la gestion des releases
|
||||||
|
* à terme, gestion en cascade des projets dépendants: release sur un projet downstream, ou synchroniser la version depuis un projet upstream
|
||||||
|
|
||||||
|
il faudra supprimer
|
||||||
|
* pman: fonctionnalités réparties dans les autres scripts spécialisés
|
||||||
|
* pmer: fonctionnalités réperties dans les autres scripts spécialisés
|
||||||
|
|
||||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
Loading…
x
Reference in New Issue
Block a user