Compare commits

...

26 Commits

Author SHA1 Message Date
8b7b318acd changement majeur dans Capacitor 2025-06-17 13:47:59 +04:00
9cf7ac908f possibilité de skip les premières lignes 2025-06-16 23:15:02 +04:00
a44da62b94 ajout genbool 2025-06-16 17:56:20 +04:00
04e7dab54e ajout string_reader 2025-06-16 17:56:16 +04:00
d704ce8c07 possibilité de changer items dans getItemValues 2025-06-12 15:54:20 +04:00
a82143a41b modifs.mineures sans commentaires 2025-06-12 08:08:51 +04:00
0d47b2a757 modifs.mineures sans commentaires 2025-06-05 15:38:06 +04:00
8fcf865bf0 maj projet 2025-06-05 14:56:10 +04:00
0467934a82 bug 2025-06-05 14:16:51 +04:00
5f4cdd2a76 bug 2025-06-05 12:08:52 +04:00
2f17d85121 ajout uuid 2025-06-05 11:56:38 +04:00
5ecc9ac3d7 ajout colonnes génériques 2025-06-04 19:35:23 +04:00
88436296e7 maj projet 2025-06-04 19:35:14 +04:00
8cab7b6064 modifs.mineures sans commentaires 2025-06-04 17:28:44 +04:00
9676be2dd6 inclure les template phpwrappers 2025-06-04 17:27:25 +04:00
c8f7f7cbaa maj doc 2025-06-04 16:28:04 +04:00
7b118682db maj doc 2025-06-03 10:24:32 +04:00
add9cfde3b <pman>Init changelog & version 0.6.1p82 2025-06-03 10:23:29 +04:00
2c63bcc1b3 <pman>Intégration de la branche rel74-0.6.1 2025-06-03 10:21:19 +04:00
745e0f2bec <pman>Init changelog & version 0.6.1p74 2025-06-03 10:21:19 +04:00
51ce95e177 maj doc 2025-06-03 10:19:32 +04:00
278ffc7db1 modifs.mineures sans commentaires 2025-06-03 06:25:52 +04:00
be6b7371e5 ajout file::try_ext 2025-06-03 06:06:09 +04:00
341a9ba05a élargir la plage de remplacement 2025-06-02 06:22:07 +04:00
a319baec05 cosmetic 2025-05-30 22:40:31 +04:00
87b0396f99 <pman>Init changelog & version 0.6.0p82 2025-05-30 22:38:31 +04:00
29 changed files with 479 additions and 483 deletions

1
.idea/nulib-base.iml generated
View File

@ -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
View 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
View File

@ -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
View 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
View File

@ -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

View File

@ -1,3 +1,13 @@
## Release 0.6.1p82 du 03/06/2025-10:22
## Release 0.6.1p74 du 03/06/2025-10:21
* `51ce95e` maj doc
* `be6b737` ajout file::try_ext
* `341a9ba` élargir la plage de remplacement
## Release 0.6.0p82 du 30/05/2025-22:37
## Release 0.6.0p74 du 30/05/2025-22:35 ## Release 0.6.0p74 du 30/05/2025-22:35
29/05/2025 Renommer le répertoire de nulib à nulib-base et le package de nulib/php à nulib/base 29/05/2025 Renommer le répertoire de nulib à nulib-base et le package de nulib/php à nulib/base

View File

@ -4,17 +4,28 @@
## Release ## Release
Exemple: release de la version 0.6.0 Exemple: release de la version 0.6.0
Ouvrir deux consoles
* console "74" avec le checkout de la version php7.4
* console "82" avec le checkout de la version php8.2
~~~sh ~~~sh
## dans les deux consoles
version=0.6.0 version=0.6.0
## branche dev74 ## console 74
git checkout dev74 git checkout dev74
prel -v$version prel -v$version
_merge82 _merge82
## branche dev82 ## console 82
pu
git checkout dev82 git checkout dev82
prel -C prel -C
@ -22,10 +33,21 @@ prel -C
commit="$(git log --grep="Init changelog . version ${version}p82" --format=%H)" && commit="$(git log --grep="Init changelog . version ${version}p82" --format=%H)" &&
echo "commit=$commit" echo "commit=$commit"
## console 74
# reprendre la valeur affichée par la précédente commande
commit=XXX
pu
git checkout dev74 git checkout dev74
git cherry-pick "$commit" git cherry-pick "$commit"
pp -a pp -a
## console 82
pu
~~~ ~~~
-*- 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

View File

@ -1 +1 @@
0.6.0 0.6.1

View File

@ -15,7 +15,7 @@
} }
}, },
"replace": { "replace": {
"nulib/php": "<0.6.0" "nulib/php": "*"
}, },
"require": { "require": {
"symfony/yaml": "^5.0", "symfony/yaml": "^5.0",
@ -44,8 +44,6 @@
"config": { "config": {
"vendor-dir": "php/vendor" "vendor-dir": "php/vendor"
}, },
"scripts": {
},
"authors": [ "authors": [
{ {
"name": "Jephte Clain", "name": "Jephte Clain",

View File

@ -138,9 +138,9 @@ 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 discharge(bool $reset=true): Traversable { function discharge(bool $reset=true): Traversable {

View File

@ -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.
*/ */
@ -255,6 +237,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 +263,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 +295,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 +314,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 +337,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,7 +351,7 @@ 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;
} }
@ -373,8 +359,8 @@ 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::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
* *
@ -382,7 +368,7 @@ 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 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 +377,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];
@ -454,8 +440,8 @@ 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 discharge(bool $reset=true): Traversable { function discharge(bool $reset=true): Traversable {

View File

@ -1,6 +1,7 @@
<?php <?php
namespace nulib\db; namespace nulib\db;
use nulib\A;
use nulib\cl; use nulib\cl;
use nulib\db\_private\_migration; use nulib\db\_private\_migration;
use nulib\php\func; use nulib\php\func;
@ -32,21 +33,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)";
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 +104,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,7 +115,7 @@ 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);
@ -102,9 +125,35 @@ abstract class CapacitorStorage {
return $channel->getMigration(); return $channel->getMigration();
} }
/** 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 +161,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 +238,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 +355,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 +366,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 +395,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)
->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 +444,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,25 +464,23 @@ 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; $pvalue = $praw[$col] ?? null;
if ($value !== ($pvalue)) { if ($value !== $pvalue) $updates[$col] = $value;
$updates[$col] = $value;
}
} }
if (count($updates) == 1 && array_key_first($updates) == "modified_") { if (count($updates) == 1 && array_key_first($updates) == "modified_") {
# si l'unique modification porte sur la date de modification, alors # si l'unique modification porte sur la date de modification, alors
@ -467,19 +508,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 +592,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,10 +655,10 @@ 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 (is_array($updates) && $updates) {
if (!array_key_exists("modified_", $updates)) { if (!array_key_exists("modified_", $updates)) {
$updates["modified_"] = date("Y-m-d H:i:s"); $updates["modified_"] = date("Y-m-d H:i:s");
@ -689,10 +722,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",

View File

@ -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",

View File

@ -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([

View File

@ -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";
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);

View File

@ -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,
@ -40,22 +38,6 @@ class SqliteStorage extends CapacitorStorage {
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";

View File

@ -7,6 +7,7 @@ use nulib\file\MemoryStream;
use nulib\file\SharedFile; use nulib\file\SharedFile;
use nulib\file\TempStream; use nulib\file\TempStream;
use nulib\file\TmpfileWriter; use nulib\file\TmpfileWriter;
use nulib\os\path;
/** /**
* Class file: outils pour gérer les fichiers * Class file: outils pour gérer les fichiers
@ -17,6 +18,34 @@ class file {
return $file; return $file;
} }
/**
* si le fichier $file existe, le retourner.
* sinon, essayer avec l'une des extensions spécifiées, jusqu'à ce qu'un
* fichier soit trouvé.
* si aucun fichier n'est trouvé, retourner null
*
* par exemple, si le fichier "file.yaml" n'existe pas et que le fichier
* "file.yml" existe, alors <code>file::try("file.yaml", ".yml")</code>
* retourne "file.yml"
*
* par défaut, $replace_ext est initialisé à l'extension de $file. utiliser []
* pour désactiver cette fonctionnalité
*
* @param string|array $exts
* @param string|array $replace_ext
*/
static function try_ext(string $file, $exts=null, $replace_ext=null): ?string {
if (file_exists($file)) return $file;
if ($exts !== null) {
$replace_ext ??= path::ext($file);
foreach (cl::with($exts) as $ext) {
$tmpfile = path::ensure_ext($file, $ext, $replace_ext);
if (file_exists($tmpfile)) return $tmpfile;
}
}
return null;
}
static function reader($input, ?callable $func=null): FileReader { static function reader($input, ?callable $func=null): FileReader {
$file = new FileReader(self::fix_dash($input)); $file = new FileReader(self::fix_dash($input));
if ($func !== null) { if ($func !== null) {
@ -88,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;
}
} }

View File

@ -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));

View File

@ -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;
}
}
}

View File

@ -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());
}
}

View File

@ -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

View File

@ -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);

View File

@ -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"],
])));
} }
} }

View File

@ -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/}")

View File

@ -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';

View File

@ -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__.'/..',

View File

@ -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"

View File

@ -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" ]

View File

@ -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