From 70c82ae504b3e1ca1f81475bb8683f5d67bab32c Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 19 Jun 2025 11:24:18 +0400 Subject: [PATCH 01/13] ajout jsondiff --- bin/jsondiff | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 bin/jsondiff diff --git a/bin/jsondiff b/bin/jsondiff new file mode 100755 index 0000000..a0df237 --- /dev/null +++ b/bin/jsondiff @@ -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") From 6bcd8d4cf6464a7971df7102c1c67c1e856f9fa3 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 19 Jun 2025 17:02:56 +0400 Subject: [PATCH 02/13] ajout getCount() --- php/src/file/tab/AbstractBuilder.php | 4 ++++ php/src/file/tab/IBuilder.php | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/php/src/file/tab/AbstractBuilder.php b/php/src/file/tab/AbstractBuilder.php index 3ec767d..65e301f 100644 --- a/php/src/file/tab/AbstractBuilder.php +++ b/php/src/file/tab/AbstractBuilder.php @@ -118,6 +118,10 @@ abstract class AbstractBuilder extends TempStream implements IBuilder { if ($unsetRows) $this->rows = null; } + function getCount(): int { + return $this->index; + } + abstract protected function _sendContentType(): void; protected bool $sentHeaders = false; diff --git a/php/src/file/tab/IBuilder.php b/php/src/file/tab/IBuilder.php index 9cc794e..536b4a6 100644 --- a/php/src/file/tab/IBuilder.php +++ b/php/src/file/tab/IBuilder.php @@ -8,6 +8,11 @@ interface IBuilder extends \nulib\file\IReader { 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; + function sendHeaders(): void; function sendFile(?iterable $rows=null): int; From 0945a86763016ef9f347f160c97ac800c792091d Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 19 Jun 2025 17:42:18 +0400 Subject: [PATCH 03/13] maj doc --- php/src/file/tab/AbstractBuilder.php | 28 ++++++++++++++-------------- php/src/file/tab/IBuilder.php | 13 +++++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/php/src/file/tab/AbstractBuilder.php b/php/src/file/tab/AbstractBuilder.php index 65e301f..45c1cd9 100644 --- a/php/src/file/tab/AbstractBuilder.php +++ b/php/src/file/tab/AbstractBuilder.php @@ -122,20 +122,6 @@ abstract class AbstractBuilder extends TempStream implements IBuilder { return $this->index; } - 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)); - } - $this->sentHeaders = true; - } - protected function _build(?iterable $rows=null): void { $this->writeAll($rows); $this->writeHeaders(); @@ -158,6 +144,20 @@ abstract class AbstractBuilder extends TempStream implements IBuilder { 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)); + } + $this->sentHeaders = true; + } + function sendFile(?iterable $rows=null): int { if (!$this->built) { $this->_build($rows); diff --git a/php/src/file/tab/IBuilder.php b/php/src/file/tab/IBuilder.php index 536b4a6..9817ac0 100644 --- a/php/src/file/tab/IBuilder.php +++ b/php/src/file/tab/IBuilder.php @@ -1,6 +1,8 @@ Date: Fri, 20 Jun 2025 10:41:52 +0400 Subject: [PATCH 04/13] modifs.mineures sans commentaires --- bin/_merge82 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/_merge82 b/bin/_merge82 index e372fb8..7ccedb2 100755 --- a/bin/_merge82 +++ b/bin/_merge82 @@ -1,4 +1,12 @@ #!/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 -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"} "$@" From 5dc2d3d01959c8ae13befe6f5484598f5de0faca Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 23 Jun 2025 18:20:40 +0400 Subject: [PATCH 05/13] support content-length --- php/src/file/csv/CsvBuilder.php | 2 +- php/src/file/tab/AbstractBuilder.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/php/src/file/csv/CsvBuilder.php b/php/src/file/csv/CsvBuilder.php index 24899b9..ef30d2c 100644 --- a/php/src/file/csv/CsvBuilder.php +++ b/php/src/file/csv/CsvBuilder.php @@ -26,7 +26,7 @@ class CsvBuilder extends AbstractBuilder { } protected function _checkOk(): bool { - $size = $this->ftell(); + $this->size = $size = $this->ftell(); if ($size === 0) return false; $this->rewind(); return true; diff --git a/php/src/file/tab/AbstractBuilder.php b/php/src/file/tab/AbstractBuilder.php index 45c1cd9..4514442 100644 --- a/php/src/file/tab/AbstractBuilder.php +++ b/php/src/file/tab/AbstractBuilder.php @@ -131,6 +131,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder { protected bool $built = false, $closed = false; + protected ?int $size = null; + function build(?iterable $rows=null, bool $close=true): bool { $ok = true; if (!$this->built) { @@ -155,6 +157,10 @@ abstract class AbstractBuilder extends TempStream implements IBuilder { if ($output !== null) { http::download_as(path::filename($output)); } + $size = $this->size; + if ($size !== null) { + header("Content-Length: $size"); + } $this->sentHeaders = true; } From f34694e12dbd99719a2fdda997e1c78e0111c461 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 23 Jun 2025 22:40:26 +0400 Subject: [PATCH 06/13] ajout de chargeAll() --- php/src/db/Capacitor.php | 10 ++++++++++ php/src/db/CapacitorChannel.php | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/php/src/db/Capacitor.php b/php/src/db/Capacitor.php index 4b3eb89..05404d0 100644 --- a/php/src/db/Capacitor.php +++ b/php/src/db/Capacitor.php @@ -143,6 +143,16 @@ class Capacitor implements ITransactor { 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) { + foreach ($items as $item) { + $count += $this->charge($item, $func, $args); + } + } + return $count; + } + function discharge(bool $reset=true): Traversable { return $this->storage->_discharge($this->channel, $reset); } diff --git a/php/src/db/CapacitorChannel.php b/php/src/db/CapacitorChannel.php index 8e2d089..ad384ca 100644 --- a/php/src/db/CapacitorChannel.php +++ b/php/src/db/CapacitorChannel.php @@ -444,6 +444,10 @@ class CapacitorChannel implements ITransactor { 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 { return $this->capacitor->discharge($reset); } From 2c7020f44d07fb67f209225ac9feca173b421675 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 24 Jun 2025 02:18:18 +0400 Subject: [PATCH 07/13] filtre de base --- php/src/db/Capacitor.php | 5 ++++- php/src/db/CapacitorChannel.php | 28 +++++++++++++++++++++++++++- php/src/db/CapacitorStorage.php | 2 +- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/php/src/db/Capacitor.php b/php/src/db/Capacitor.php index 05404d0..8c62b42 100644 --- a/php/src/db/Capacitor.php +++ b/php/src/db/Capacitor.php @@ -146,8 +146,11 @@ class Capacitor implements ITransactor { 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, $args); + $count += $this->charge($item, $func); } } return $count; diff --git a/php/src/db/CapacitorChannel.php b/php/src/db/CapacitorChannel.php index ad384ca..fdb908c 100644 --- a/php/src/db/CapacitorChannel.php +++ b/php/src/db/CapacitorChannel.php @@ -444,7 +444,7 @@ class CapacitorChannel implements ITransactor { return $this->capacitor->charge($item, $func, $args, $row); } - function chargeAll(iterable $items, $func=null, ?array $args=null): int { + function chargeAll(?iterable $items, $func=null, ?array $args=null): int { return $this->capacitor->chargeAll($items, $func, $args); } @@ -452,23 +452,49 @@ class CapacitorChannel implements ITransactor { 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 { + $this->verifixFilter($filter); return $this->capacitor->count($filter); } function one($filter, ?array $mergeQuery=null): ?array { + $this->verifixFilter($filter); return $this->capacitor->one($filter, $mergeQuery); } function all($filter, ?array $mergeQuery=null): Traversable { + $this->verifixFilter($filter); return $this->capacitor->all($filter, $mergeQuery); } 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); } function delete($filter, $func=null, ?array $args=null): int { + $this->verifixFilter($filter); return $this->capacitor->delete($filter, $func, $args); } diff --git a/php/src/db/CapacitorStorage.php b/php/src/db/CapacitorStorage.php index 1d8e785..c948691 100644 --- a/php/src/db/CapacitorStorage.php +++ b/php/src/db/CapacitorStorage.php @@ -434,7 +434,7 @@ abstract class CapacitorStorage { } if ($func !== null) { - $updates = func::with($func) + $updates = func::with($func, $args) ->prependArgs([$item, $row, $prow]) ->bind($channel) ->invoke(); From b71e879823777683aa1802ab2041e6c405069e04 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 24 Jun 2025 04:13:09 +0400 Subject: [PATCH 08/13] =?UTF-8?q?la=20datetime=20est=20dans=20le=20timezon?= =?UTF-8?q?e=20par=20d=C3=A9faut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/src/db/CapacitorStorage.php | 2 +- php/src/db/pgsql/PgsqlStorage.php | 2 +- php/src/php/time/DateTime.php | 49 ++++++++++++++++++++++++++----- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/php/src/db/CapacitorStorage.php b/php/src/db/CapacitorStorage.php index c948691..cedf8b2 100644 --- a/php/src/db/CapacitorStorage.php +++ b/php/src/db/CapacitorStorage.php @@ -45,7 +45,7 @@ abstract class CapacitorStorage { const GENLIC_DEFINITION = "varchar(80)"; const GENLIB_DEFINITION = "varchar(255)"; const GENTEXT_DEFINITION = "mediumtext"; - const GENBOOL_DEFINITION = "integer(1)"; + const GENBOOL_DEFINITION = "integer(1) default 0"; const GENUUID_DEFINITION = "varchar(36)"; protected static function gencol($def): string { diff --git a/php/src/db/pgsql/PgsqlStorage.php b/php/src/db/pgsql/PgsqlStorage.php index 649b3eb..6d0f8a3 100644 --- a/php/src/db/pgsql/PgsqlStorage.php +++ b/php/src/db/pgsql/PgsqlStorage.php @@ -11,7 +11,7 @@ class PgsqlStorage extends CapacitorStorage { const SERTS_DEFINITION = "timestamp"; const GENSERIAL_DEFINITION = "serial primary key"; const GENTEXT_DEFINITION = "text"; - const GENBOOL_DEFINITION = "boolean"; + const GENBOOL_DEFINITION = "boolean default false"; const GENUUID_DEFINITION = "uuid"; function __construct($pgsql) { diff --git a/php/src/php/time/DateTime.php b/php/src/php/time/DateTime.php index 1fcd632..816b6bf 100644 --- a/php/src/php/time/DateTime.php +++ b/php/src/php/time/DateTime.php @@ -4,6 +4,7 @@ namespace nulib\php\time; use DateTimeInterface; use DateTimeZone; use InvalidArgumentException; +use nulib\str; /** * Class DateTime: une date et une heure @@ -32,7 +33,7 @@ class DateTime extends \DateTime { const DMY_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))?$/'; const YMD_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})$/'; const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/'; - const YMDHISZ_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})[tT](\d{2})(\d{2})(\d{2})?([zZ])?$/'; + 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) { @@ -206,16 +207,34 @@ class DateTime extends \DateTime { 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"; if ($datetime instanceof \DateTimeInterface) { - if ($timezone === null) $timezone = $datetime->getTimezone(); + $timezone ??= $datetime->getTimezone(); parent::__construct(); $this->setTimestamp($datetime->getTimestamp()); $this->setTimezone($timezone); + } elseif (is_int($datetime)) { parent::__construct("now", $timezone); $this->setTimestamp($datetime); + } elseif (is_string($datetime)) { $Y = $H = $Z = null; if (preg_match(self::DMY_PATTERN, $datetime, $ms)) { @@ -253,22 +272,30 @@ class DateTime extends \DateTime { if ($Y !== null) { if ($H === null) $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d); else $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H, $M, $S); - if ($Z !== null) $timezone = new DateTimeZone("UTC"); + if ($Z !== null) $timezone = new DateTimeZone(self::fix_z($Z)); } parent::__construct($datetime, $timezone); + } 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) { $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d); } else { $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H ?? 0, $M ?? 0, $S ?? 0); } - if ($tz === "Z" || $tz === "z") $tz = "UTC"; - $timezone = $tz !== null? new DateTimeZone($tz): null; + if ($Z !== null) $timezone = new DateTimeZone(self::fix_z($Z)); parent::__construct($datetime, $timezone); + } else { throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface"); } + + if ($forceLocalTimezone) { + $setTimezone ??= new DateTimeZone(date_default_timezone_get()); + $this->setTimezone($setTimezone); + } } function diff($target, $absolute=false): DateInterval { @@ -276,7 +303,13 @@ class DateTime extends \DateTime { } 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); } /** From 4f17d19609d9e5084618c2b989c60352ce6a2af9 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 24 Jun 2025 10:38:10 +0400 Subject: [PATCH 09/13] =?UTF-8?q?support=20pr=C3=A9fixe=20pour=20les=20mig?= =?UTF-8?q?rations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/src/db/CapacitorChannel.php | 14 ++++++-- php/src/db/CapacitorStorage.php | 2 +- php/src/db/IDatabase.php | 6 ++++ php/src/db/mysql/Mysql.php | 2 ++ php/src/db/mysql/MysqlStorage.php | 2 +- php/src/db/pdo/Pdo.php | 6 ++++ php/src/db/pgsql/Pgsql.php | 6 ++++ php/src/db/pgsql/PgsqlStorage.php | 2 +- php/src/db/sqlite/Sqlite.php | 6 ++++ php/src/db/sqlite/SqliteStorage.php | 2 +- php/src/php/time/Date.php | 2 +- php/src/php/time/DateTime.php | 10 ++++++ php/tests/db/sqlite/ChannelMigrationTest.php | 37 ++++++++++++++++++++ php/tests/db/sqlite/impl/MyIndexChannel.php | 29 +++++++++++++++ 14 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 php/tests/db/sqlite/impl/MyIndexChannel.php diff --git a/php/src/db/CapacitorChannel.php b/php/src/db/CapacitorChannel.php index fdb908c..7e8ff44 100644 --- a/php/src/db/CapacitorChannel.php +++ b/php/src/db/CapacitorChannel.php @@ -214,8 +214,18 @@ class CapacitorChannel implements ITransactor { protected ?array $migration; - function getMigration(): ?array { - return $this->migration; + function getMigration(?string $prefix=null): ?array { + 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; diff --git a/php/src/db/CapacitorStorage.php b/php/src/db/CapacitorStorage.php index cedf8b2..232d935 100644 --- a/php/src/db/CapacitorStorage.php +++ b/php/src/db/CapacitorStorage.php @@ -122,7 +122,7 @@ abstract class CapacitorStorage { } protected function getMigration(CapacitorChannel $channel): ?array { - return $channel->getMigration(); + return $channel->getMigration($this->db()->getPrefix()); } /** sérialiser les valeurs qui doivent l'être dans $row */ diff --git a/php/src/db/IDatabase.php b/php/src/db/IDatabase.php index 1383c9a..8e04bab 100644 --- a/php/src/db/IDatabase.php +++ b/php/src/db/IDatabase.php @@ -2,6 +2,12 @@ namespace nulib\db; 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 */ function getSql($query, ?array $params=null): string; diff --git a/php/src/db/mysql/Mysql.php b/php/src/db/mysql/Mysql.php index 431497e..3031583 100644 --- a/php/src/db/mysql/Mysql.php +++ b/php/src/db/mysql/Mysql.php @@ -4,6 +4,8 @@ namespace nulib\db\mysql; use nulib\db\pdo\Pdo; class Mysql extends Pdo { + const PREFIX = "mysql"; + function getDbname(): ?string { $url = $this->dbconn["name"] ?? null; if ($url !== null && preg_match('/^mysql(?::|.*;)dbname=([^;]+)/i', $url, $ms)) { diff --git a/php/src/db/mysql/MysqlStorage.php b/php/src/db/mysql/MysqlStorage.php index 41a7c65..be1382c 100644 --- a/php/src/db/mysql/MysqlStorage.php +++ b/php/src/db/mysql/MysqlStorage.php @@ -39,7 +39,7 @@ class MysqlStorage extends CapacitorStorage { function _getMigration(CapacitorChannel $channel): _mysqlMigration { $migrations = cl::merge([ "0init" => [$this->_createSql($channel)], - ], $channel->getMigration()); + ], $channel->getMigration($this->db->getPrefix())); return new _mysqlMigration($migrations, $channel->getName()); } diff --git a/php/src/db/pdo/Pdo.php b/php/src/db/pdo/Pdo.php index f34ff69..094b784 100644 --- a/php/src/db/pdo/Pdo.php +++ b/php/src/db/pdo/Pdo.php @@ -12,6 +12,12 @@ use nulib\ValueException; class Pdo implements IDatabase { use Tvalues; + const PREFIX = null; + + function getPrefix(): ?string { + return static::PREFIX; + } + static function with($pdo, ?array $params=null): self { if ($pdo instanceof static) { return $pdo; diff --git a/php/src/db/pgsql/Pgsql.php b/php/src/db/pgsql/Pgsql.php index f765666..963d425 100644 --- a/php/src/db/pgsql/Pgsql.php +++ b/php/src/db/pgsql/Pgsql.php @@ -12,6 +12,12 @@ use nulib\ValueException; class Pgsql implements IDatabase { use Tvalues; + const PREFIX = "pgsql"; + + function getPrefix(): ?string { + return self::PREFIX; + } + static function with($pgsql, ?array $params=null): self { if ($pgsql instanceof static) { return $pgsql; diff --git a/php/src/db/pgsql/PgsqlStorage.php b/php/src/db/pgsql/PgsqlStorage.php index 6d0f8a3..6861154 100644 --- a/php/src/db/pgsql/PgsqlStorage.php +++ b/php/src/db/pgsql/PgsqlStorage.php @@ -44,7 +44,7 @@ class PgsqlStorage extends CapacitorStorage { function _getMigration(CapacitorChannel $channel): _pgsqlMigration { $migrations = cl::merge([ "0init" => [$this->_createSql($channel)], - ], $channel->getMigration()); + ], $channel->getMigration($this->db->getPrefix())); return new _pgsqlMigration($migrations, $channel->getName()); } diff --git a/php/src/db/sqlite/Sqlite.php b/php/src/db/sqlite/Sqlite.php index ae4ea99..443d995 100644 --- a/php/src/db/sqlite/Sqlite.php +++ b/php/src/db/sqlite/Sqlite.php @@ -19,6 +19,12 @@ use SQLite3Stmt; class Sqlite implements IDatabase { use Tvalues; + const PREFIX = "sqlite"; + + function getPrefix(): ?string { + return self::PREFIX; + } + static function with($sqlite, ?array $params=null): self { if ($sqlite instanceof static) { return $sqlite; diff --git a/php/src/db/sqlite/SqliteStorage.php b/php/src/db/sqlite/SqliteStorage.php index e9423b9..f67b678 100644 --- a/php/src/db/sqlite/SqliteStorage.php +++ b/php/src/db/sqlite/SqliteStorage.php @@ -34,7 +34,7 @@ class SqliteStorage extends CapacitorStorage { function _getMigration(CapacitorChannel $channel): _sqliteMigration { $migrations = cl::merge([ "0init" => [$this->_createSql($channel)], - ], $channel->getMigration()); + ], $channel->getMigration($this->db->getPrefix())); return new _sqliteMigration($migrations, $channel->getName()); } diff --git a/php/src/php/time/Date.php b/php/src/php/time/Date.php index 3556ed4..c681e68 100644 --- a/php/src/php/time/Date.php +++ b/php/src/php/time/Date.php @@ -15,6 +15,6 @@ class Date extends DateTime { } function format($format=self::DEFAULT_FORMAT): string { - return \DateTime::format($format); + return parent::format($format); } } diff --git a/php/src/php/time/DateTime.php b/php/src/php/time/DateTime.php index 816b6bf..f819fe2 100644 --- a/php/src/php/time/DateTime.php +++ b/php/src/php/time/DateTime.php @@ -30,6 +30,16 @@ class DateTime extends \DateTime { else return new static($datetime); } + static function withn($datetime): ?self { + if ($datetime === null) return null; + elseif ($datetime instanceof static) return $datetime; + else return new static($datetime); + } + + static function ensure(&$datetime): void { + $datetime = static::withn($datetime); + } + const DMY_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))?$/'; const YMD_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})$/'; const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/'; diff --git a/php/tests/db/sqlite/ChannelMigrationTest.php b/php/tests/db/sqlite/ChannelMigrationTest.php index 1946179..fa48e7c 100644 --- a/php/tests/db/sqlite/ChannelMigrationTest.php +++ b/php/tests/db/sqlite/ChannelMigrationTest.php @@ -5,6 +5,7 @@ use nulib\db\Capacitor; use nulib\db\sqlite\impl\MyChannel; use nulib\db\sqlite\impl\MyChannelV2; use nulib\db\sqlite\impl\MyChannelV3; +use nulib\db\sqlite\impl\MyIndexChannel; use nulib\output\msg; use nulib\output\std\StdMessenger; use nulib\php\time\DateTime; @@ -69,6 +70,42 @@ alter table my add column date_mod datetime; -- infos 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 = << "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, + ]); + } +} From 933b6cec3b3d166cd06e15245a5dc0e36a57f7ec Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 24 Jun 2025 15:47:45 +0400 Subject: [PATCH 10/13] modifs.mineures sans commentaires --- composer.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 23b756f..d7a5d80 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2d630ab5ff0ffe6139447cf93c362ed7", + "content-hash": "4569957a35f86d8a4964d01c7358935c", "packages": [ { "name": "symfony/deprecation-contracts", @@ -361,16 +361,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { @@ -413,9 +413,9 @@ ], "support": { "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", From 599d646372415dc83e19fc93b4f1bcd3724400c0 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 25 Jun 2025 06:02:38 +0400 Subject: [PATCH 11/13] pas d'analyse d'argument pour ci, cu, composer --- runphp/runphp | 68 ++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/runphp/runphp b/runphp/runphp index 07d0e8d..b227b17 100755 --- a/runphp/runphp +++ b/runphp/runphp @@ -467,9 +467,11 @@ OPTIONS function host_docker_run() { # lancer une commande avec docker - if [ "$1" == composer ]; then - : # pas d'analyse d'argument pour composer - else + case "$1" in + ci|cu|composer) + : # pas d'analyse d'argument pour les commandes composer + ;; + *) SOPTS=+w: LOPTS=help,chdir:,no-use-rslave args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" @@ -513,7 +515,7 @@ OPTIONS esac shift done - fi + esac args=( run -it --rm @@ -589,43 +591,43 @@ function container_exec() { fi fi - if [ "$1" == composer ]; then - : # pas d'analyse d'argument pour composer - else - SOPTS=+w: - LOPTS=chdir: - args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" - - chdir= - action= - while [ $# -gt 0 ]; do - case "$1" in - --) shift; break;; - -w|--chdir) shift; chdir="$1";; - *) die "$1: option non configurée";; - esac - shift - done - fi - - if [ $# -eq 0 ]; then - die "no command specified" - elif [ "$1" == ci ]; then + # pour les commandes suivantes, pas d'analyse d'argument + case "$1" in + ci) eecho "== installing composer dependencies" shift composer i "$@" - elif [ "$1" == cu ]; then + ;; + cu) eecho "== upgrading composer dependencies" shift composer u "$@" - elif [ "$1" == composer ]; then + ;; + composer) "$@" - else - if [ -n "$chdir" ]; then - cd "$chdir" || exit 1 - fi - exec "$@" + ;; + esac + + SOPTS=+w: + LOPTS=chdir: + args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" + + chdir= + action= + while [ $# -gt 0 ]; do + case "$1" in + --) shift; break;; + -w|--chdir) shift; chdir="$1";; + *) die "$1: option non configurée";; + esac + shift + done + + [ $# -gt 0 ] || die "no command specified" + if [ -n "$chdir" ]; then + cd "$chdir" || exit 1 fi + exec "$@" } ################################################################################ From 3e642b84bc58ddf558a5974872add649a8e7f48a Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 25 Jun 2025 06:39:40 +0400 Subject: [PATCH 12/13] runphp fait aussi wrapper --- bin/._pman-composer_local_deps.php | 14 ++++++++++++++ bin/._pman-composer_select_profile.php | 22 ++++++++++++++++++++++ bin/_pman-composer_local_deps.php | 15 +-------------- bin/_pman-composer_select_profile.php | 23 +---------------------- bin/runphp | 21 ++++++++++++++++----- runphp/runphp | 20 ++++++++++++++------ 6 files changed, 68 insertions(+), 47 deletions(-) create mode 100755 bin/._pman-composer_local_deps.php create mode 100755 bin/._pman-composer_select_profile.php mode change 100755 => 120000 bin/_pman-composer_local_deps.php mode change 100755 => 120000 bin/_pman-composer_select_profile.php diff --git a/bin/._pman-composer_local_deps.php b/bin/._pman-composer_local_deps.php new file mode 100755 index 0000000..92aeda8 --- /dev/null +++ b/bin/._pman-composer_local_deps.php @@ -0,0 +1,14 @@ +#!/usr/bin/php +getLocalDeps(); +foreach ($deps as $dep => $path) { + echo "$path\n"; +} diff --git a/bin/._pman-composer_select_profile.php b/bin/._pman-composer_select_profile.php new file mode 100755 index 0000000..75cb8d9 --- /dev/null +++ b/bin/._pman-composer_select_profile.php @@ -0,0 +1,22 @@ +#!/usr/bin/php +selectProfile($profile, $config); +if (getenv("PMAN_COMPOSER_DEBUG")) { + $composer->print(); +} else { + $composer->write(); +} diff --git a/bin/_pman-composer_local_deps.php b/bin/_pman-composer_local_deps.php deleted file mode 100755 index 92aeda8..0000000 --- a/bin/_pman-composer_local_deps.php +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/php -getLocalDeps(); -foreach ($deps as $dep => $path) { - echo "$path\n"; -} diff --git a/bin/_pman-composer_local_deps.php b/bin/_pman-composer_local_deps.php new file mode 120000 index 0000000..42fbf67 --- /dev/null +++ b/bin/_pman-composer_local_deps.php @@ -0,0 +1 @@ +runphp \ No newline at end of file diff --git a/bin/_pman-composer_select_profile.php b/bin/_pman-composer_select_profile.php deleted file mode 100755 index 75cb8d9..0000000 --- a/bin/_pman-composer_select_profile.php +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/php -selectProfile($profile, $config); -if (getenv("PMAN_COMPOSER_DEBUG")) { - $composer->print(); -} else { - $composer->write(); -} diff --git a/bin/_pman-composer_select_profile.php b/bin/_pman-composer_select_profile.php new file mode 120000 index 0000000..42fbf67 --- /dev/null +++ b/bin/_pman-composer_select_profile.php @@ -0,0 +1 @@ +runphp \ No newline at end of file diff --git a/bin/runphp b/bin/runphp index 81a4f81..3e59b61 100755 --- a/bin/runphp +++ b/bin/runphp @@ -20,14 +20,25 @@ while true; do cd .. done -if [ -z "$PROJDIR" ]; then - # s'il n'y a pas de projet, --bs est l'action par défaut - [ $# -gt 0 ] || set -- --bs --ue -elif [ "$MYNAME" == composer ]; then +export RUNPHP_MOUNT= +if [ "$MYNAME" == composer ]; then 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 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 fi diff --git a/runphp/runphp b/runphp/runphp index b227b17..74c682a 100755 --- a/runphp/runphp +++ b/runphp/runphp @@ -537,26 +537,34 @@ OPTIONS # monter le répertoire qui contient $PROJDIR mount_composer= - mount_runphp=1 + mount_standalone=1 + mount_mount=1 if [ -z "$PROJDIR" -o "${PROJDIR#$HOME/}" != "$PROJDIR" -o "$PROJDIR" == "$HOME" ]; then # bind mount $HOME args+=(-v "$HOME:$HOME${UseRslave:+:rslave}") - [ -n "$RUNPHP_STANDALONE" ] && - [ "${RUNPHP_STANDALONE#$HOME/}" != "$RUNPHP_STANDALONE" ] && - mount_runphp= + if [ -n "$RUNPHP_STANDALONE" -a "${RUNPHP_STANDALONE#$HOME/}" != "$RUNPHP_STANDALONE" ]; then + mount_standalone= + fi + if [ -n "$RUNPHP_MOUNT" -a "${RUNPHP_MOUNT#$HOME/}" != "$RUNPHP_MOUNT" ]; then + mount_mount= + fi elif [ -n "$PROJDIR" ]; then # bind mount uniquement le répertoire du projet args+=(-v "$PROJDIR:$PROJDIR${UseRslave:+:rslave}") mount_composer=1 - [ "$RUNPHP_STANDALONE" == "$PROJDIR" ] && mount_runphp= + [ "$RUNPHP_STANDALONE" == "$PROJDIR" ] && mount_standalone= + [ "$RUNPHP_MOUNT" == "$PROJDIR" ] && mount_mount= fi if [ -n "$mount_composer" -a -d "$HOME/.composer" ]; then # monter la configuration de composer args+=(-v "$HOME/.composer:$HOME/.composer") 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") fi + if [ -n "$RUNPHP_MOUNT" -a -n "$mount_mount" ]; then + args+=(-v "$RUNPHP_MOUNT:$RUNPHP_MOUNT") + fi args+=(-w "$(pwd)") # lancer avec l'utilisateur courant From 12fe3a65a54d8cff893902188b5c047527a06180 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 25 Jun 2025 08:43:58 +0400 Subject: [PATCH 13/13] bug --- runphp/runphp | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/runphp/runphp b/runphp/runphp index 74c682a..30f3056 100755 --- a/runphp/runphp +++ b/runphp/runphp @@ -614,28 +614,29 @@ function container_exec() { composer) "$@" ;; + *) + SOPTS=+w: + LOPTS=chdir: + args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" + + chdir= + action= + while [ $# -gt 0 ]; do + case "$1" in + --) shift; break;; + -w|--chdir) shift; chdir="$1";; + *) die "$1: option non configurée";; + esac + shift + done + + [ $# -gt 0 ] || die "no command specified" + if [ -n "$chdir" ]; then + cd "$chdir" || exit 1 + fi + exec "$@" + ;; esac - - SOPTS=+w: - LOPTS=chdir: - args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" - - chdir= - action= - while [ $# -gt 0 ]; do - case "$1" in - --) shift; break;; - -w|--chdir) shift; chdir="$1";; - *) die "$1: option non configurée";; - esac - shift - done - - [ $# -gt 0 ] || die "no command specified" - if [ -n "$chdir" ]; then - cd "$chdir" || exit 1 - fi - exec "$@" } ################################################################################