diff --git a/README.md b/README.md index eee5634..a4372c8 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,11 @@ git checkout dev82 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" + git checkout dev74 + git cherry-pick "$commit" pp -a ~~~ diff --git a/bash/src/pman.sh b/bash/src/pman.sh index 4a2d928..094b269 100644 --- a/bash/src/pman.sh +++ b/bash/src/pman.sh @@ -426,7 +426,7 @@ EOF $(qvals echo "$(awk <"$changelog" ' BEGIN { p = 0 } p == 0 && $0 == "" { p = 1; next } -p == 1 { gsub(/\$/, "\\$", $0); print } +p == 1 { print } ')") >CHANGES.md git add CHANGES.md EOF diff --git a/php/src/A.php b/php/src/A.php index 253592b..75f0284 100644 --- a/php/src/A.php +++ b/php/src/A.php @@ -177,6 +177,14 @@ class A { return $pvalue; } + static final function shift(?array &$dest, int $count=1, $default=null) { + if ($dest === null) return null; + $values = array_slice($dest, 0, $count); + $dest = array_slice($dest, $count); + if ($values === []) return $default; + else return $count == 1? $values[0]: $values; + } + static final function pop(&$dest, $key, $default=null) { if ($dest === null) return $default; self::ensure_narray($dest); diff --git a/php/src/cv.php b/php/src/cv.php index 3bd8773..fd61a8e 100644 --- a/php/src/cv.php +++ b/php/src/cv.php @@ -86,6 +86,12 @@ class cv { $b = $tmp; } + /** cloner une valeur */ + static final function clone($value) { + if (is_object($value)) $value = clone $value; + return $value; + } + ############################################################################# /** mettre à jour $dest avec $value si $cond($value) est vrai */ @@ -197,19 +203,29 @@ class cv { } /** - * retourner [$bool, $string, $array] initialisés chacun en fonction du type + * retourner [$bool, $scalar, $array] initialisés chacun en fonction du type * de $value. * * @throws ValueException si $value n'est d'aucun de ces types */ static final function check_bsa($value, ?string $prefix=null, bool $throw_exception=true): array { $bool = is_bool($value)? $value : null; - $string = is_string($value)? $value : null; + $scalar = !is_bool($value) && is_scalar($value)? $value : null; $array = is_array($value)? $value : null; - if ($bool === null && $string === null && $array === null && $throw_exception) { + if ($bool === null && $scalar === null && $array === null && $throw_exception) { throw ValueException::invalid_kind($value, "value", $prefix); } else { - return [$bool, $string, $array]; + return [$bool, $scalar, $array]; + } + } + + static final function subclass_of($value, $class): bool { + if (is_string($value)) { + return $value === $class || is_subclass_of($value, $class); + } elseif (is_object($value)) { + return $value instanceof $class; + } else { + return false; } } diff --git a/php/src/db/Capacitor.php b/php/src/db/Capacitor.php index 0fee8b5..5551f1b 100644 --- a/php/src/db/Capacitor.php +++ b/php/src/db/Capacitor.php @@ -1,6 +1,7 @@ storage->_delete($this->channel, $filter, $func, $args); } + function dbUpdate(array $update) { + return $this->storage->db()->exec(cl::merge([ + "update", + "table" => $this->getTableName(), + ], $update)); + } + function close(): void { $this->storage->close(); } diff --git a/php/src/db/CapacitorChannel.php b/php/src/db/CapacitorChannel.php index c7265bb..4349e4b 100644 --- a/php/src/db/CapacitorChannel.php +++ b/php/src/db/CapacitorChannel.php @@ -13,7 +13,9 @@ class CapacitorChannel implements ITransactor { const TABLE_NAME = null; - const COLUMN_DEFINITIONS = null; + protected function COLUMN_DEFINITIONS(): ?array { + return static::COLUMN_DEFINITIONS; + } const COLUMN_DEFINITIONS = null; const PRIMARY_KEYS = null; @@ -28,19 +30,17 @@ class CapacitorChannel implements ITransactor { static function verifix_name(?string &$name, ?string &$tableName=null): void { if ($name !== null) { $name = strtolower($name); - if ($tableName === null) { - $tableName = str_replace("-", "_", $name) . "_channel"; - } + $tableName ??= str_replace("-", "_", $name) . "_channel"; } else { $name = static::class; if ($name === self::class) { $name = "default"; - if ($tableName === null) $tableName = "default_channel"; + $tableName ??= "default_channel"; } else { $name = preg_replace('/^.*\\\\/', "", $name); $name = preg_replace('/Channel$/', "", $name); $name = lcfirst($name); - if ($tableName === null) $tableName = str::camel2us($name); + $tableName ??= str::camel2us($name); $name = strtolower($name); } } @@ -63,7 +63,7 @@ class CapacitorChannel implements ITransactor { $this->useCache = static::USE_CACHE; $this->setup = false; $this->created = false; - $columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS); + $columnDefinitions = $this->COLUMN_DEFINITIONS(); $primaryKeys = cl::withn(static::PRIMARY_KEYS); $migration = cl::withn(static::MIGRATION); $lastMkey = 1; @@ -482,6 +482,10 @@ class CapacitorChannel implements ITransactor { return $this->capacitor->delete($filter, $func, $args); } + function dbUpdate(array $update) { + return $this->capacitor->dbUpdate($update); + } + function close(): void { $this->capacitor->close(); } diff --git a/php/src/db/CapacitorStorage.php b/php/src/db/CapacitorStorage.php index ba00c07..9d4cdb1 100644 --- a/php/src/db/CapacitorStorage.php +++ b/php/src/db/CapacitorStorage.php @@ -3,7 +3,6 @@ namespace nulib\db; use nulib\cl; use nulib\db\_private\_migration; -use nulib\db\cache\cache; use nulib\php\func; use nulib\ValueException; use Traversable; diff --git a/php/src/db/cache/CacheChannel.php b/php/src/db/cache/CacheChannel.php deleted file mode 100644 index b1f8619..0000000 --- a/php/src/db/cache/CacheChannel.php +++ /dev/null @@ -1,116 +0,0 @@ - "varchar(64) not null", - "id" => "varchar(64) not null", - "date_start" => "datetime", - "duration_" => "text", - "primary key (group_id, id)", - ]; - - static function get_cache_ids($id): array { - if (is_array($id)) { - $keys = array_keys($id); - if (array_key_exists("group_id", $id)) $groupIdKey = "group_id"; - else $groupIdKey = $keys[1] ?? null; - $groupId = $id[$groupIdKey] ?? ""; - if (array_key_exists("id", $id)) $idKey = "id"; - else $idKey = $keys[0] ?? null; - $id = $id[$idKey] ?? ""; - } else { - $groupId = ""; - } - if (preg_match('/^(.*\\\\)?([^\\\\]+)$/', $groupId, $ms)) { - # si le groupe est une classe, faire un hash du package pour limiter la - # longueur du groupe - [$package, $groupId] = [$ms[1], $ms[2]]; - $package = substr(md5($package), 0, 4); - $groupId = "${groupId}_$package"; - } - return ["group_id" => $groupId, "id" => $id]; - } - - function __construct(?string $duration=null, ?string $name=null) { - parent::__construct($name); - $this->duration = $duration ?? static::DURATION; - $this->includes = static::INCLUDES; - $this->excludes = static::EXCLUDES; - } - - protected string $duration; - - protected ?array $includes; - - protected ?array $excludes; - - function getItemValues($item): ?array { - return cl::merge(self::get_cache_ids($item), [ - "item" => null, - ]); - } - - function onCreate($item, array $values, ?array $alwaysNull, ?string $duration=null): ?array { - $now = new DateTime(); - $duration ??= $this->duration; - return [ - "date_start" => $now, - "duration" => new Delay($duration, $now), - ]; - } - - function onUpdate($item, array $values, array $pvalues, ?string $duration=null): ?array { - $now = new DateTime(); - $duration ??= $this->duration; - return [ - "date_start" => $now, - "duration" => new Delay($duration, $now), - ]; - } - - function shouldUpdate($id, bool $noCache=false): bool { - if ($noCache) return true; - - $cacheIds = self::get_cache_ids($id); - $groupId = $cacheIds["group_id"]; - if ($groupId) { - $includes = $this->includes; - $shouldInclude = $includes !== null && in_array($groupId, $includes); - $excludes = $this->excludes; - $shouldExclude = $excludes !== null && in_array($groupId, $excludes); - if (!$shouldInclude || $shouldExclude) return true; - } - - $found = false; - $expired = false; - $this->each($cacheIds, - function($item, $values) use (&$found, &$expired) { - $found = true; - $expired = $values["duration"]->isElapsed(); - }); - return !$found || $expired; - } - - function setCached($id, ?string $duration=null): void { - $cacheIds = self::get_cache_ids($id); - $this->charge($cacheIds, null, [$duration]); - } - - function resetCached($id) { - $cacheIds = self::get_cache_ids($id); - $this->delete($cacheIds); - } -} diff --git a/php/src/db/cache/RowsChannel.php b/php/src/db/cache/RowsChannel.php deleted file mode 100644 index a3f7055..0000000 --- a/php/src/db/cache/RowsChannel.php +++ /dev/null @@ -1,51 +0,0 @@ - "varchar(128) primary key not null", - "all_values" => "mediumtext", - ]; - - function __construct($id, callable $builder, ?string $duration=null) { - $this->cacheIds = $cacheIds = CacheChannel::get_cache_ids($id); - $this->builder = Closure::fromCallable($builder); - $this->duration = $duration; - $name = "{$cacheIds["group_id"]}-{$cacheIds["id"]}"; - parent::__construct($name); - } - - protected array $cacheIds; - - protected Closure $builder; - - protected ?string $duration = null; - - function getItemValues($item): ?array { - $key = array_keys($item)[0]; - $row = $item[$key]; - return [ - "key" => $key, - "item" => $row, - "all_values" => implode(" ", cl::filter_n(cl::with($row))), - ]; - } - - function getIterator(): Traversable { - $cm = cache::get(); - if ($cm->shouldUpdate($this->cacheIds)) { - $this->capacitor->reset(); - foreach (($this->builder)() as $key => $row) { - $this->charge([$key => $row]); - } - $cm->setCached($this->cacheIds, $this->duration); - } - return $this->discharge(false); - } -} diff --git a/php/src/db/cache/cache.php b/php/src/db/cache/cache.php deleted file mode 100644 index 401fb19..0000000 --- a/php/src/db/cache/cache.php +++ /dev/null @@ -1,37 +0,0 @@ - true, + # XXX désactiver les connexions persistantes par défaut + # pour réactiver par défaut, il faudrait vérifier la connexion à chaque fois + # qu'elle est ouverte avec un "select 1". en effet, l'expérience jusqu'ici + # est que la première connexion après un long timeout échoue + "persistent" => false, "force_new" => false, "serial_support" => true, ]; diff --git a/php/src/file/IReader.php b/php/src/file/IReader.php index 36a351b..fa96f7d 100644 --- a/php/src/file/IReader.php +++ b/php/src/file/IReader.php @@ -40,4 +40,6 @@ interface IReader extends _IFile { function unserialize(?array $options=null, bool $close=true, bool $alreadyLocked=false); function copyTo(IWriter $dest, bool $closeWriter=false, bool $closeReader=true): void; + + function setCsvFlavour(?string $flavour): void; } diff --git a/php/src/file/Stream.php b/php/src/file/Stream.php index 65216ee..c1d7496 100644 --- a/php/src/file/Stream.php +++ b/php/src/file/Stream.php @@ -299,8 +299,8 @@ class Stream extends AbstractIterator implements IReader, IWriter { /** retourner le contenu du fichier sous forme de chaine */ function getContents(bool $close=true, bool $alreadyLocked=false): string { - $useLocking = $this->useLocking; - if ($useLocking && !$alreadyLocked) $this->lock(LOCK_SH); + $useLocking = $this->useLocking && !$alreadyLocked; + if ($useLocking) $this->lock(LOCK_SH); try { return IOException::ensure_valid(stream_get_contents($this->fd), $this->throwOnError); } finally { @@ -380,7 +380,9 @@ class Stream extends AbstractIterator implements IReader, IWriter { /** @throws IOException */ function ftruncate(int $size=0, bool $rewind=true): self { $fd = $this->getResource(); - IOException::ensure_valid(ftruncate($fd, $size), $this->throwOnError); + $r = ftruncate($fd, $size); + $this->stat = null; + IOException::ensure_valid($r, $this->throwOnError); if ($rewind) rewind($fd); return $this; } @@ -390,6 +392,7 @@ class Stream extends AbstractIterator implements IReader, IWriter { $fd = $this->getResource(); if ($length === null) $r = fwrite($fd, $data); else $r = fwrite($fd, $data, $length); + $this->stat = null; return IOException::ensure_valid($r, $this->throwOnError); } @@ -403,9 +406,13 @@ class Stream extends AbstractIterator implements IReader, IWriter { $line[] = strval($col); } $line = implode($sep, $line); - IOException::ensure_valid(fwrite($fd, "$line\n"), $this->throwOnError); + $r = fwrite($fd, "$line\n"); + $this->stat = null; + IOException::ensure_valid($r, $this->throwOnError); } else { - IOException::ensure_valid(fputcsv($fd, $row, $params[0], $params[1], $params[2]), $this->throwOnError); + $r = fputcsv($fd, $row, $params[0], $params[1], $params[2]); + $this->stat = null; + IOException::ensure_valid($r, $this->throwOnError); } } @@ -471,8 +478,8 @@ class Stream extends AbstractIterator implements IReader, IWriter { } function putContents(string $contents, bool $close=true, bool $alreadyLocked=false): void { - $useLocking = $this->useLocking; - if ($useLocking && !$alreadyLocked) $this->lock(LOCK_EX); + $useLocking = $this->useLocking && !$alreadyLocked; + if ($useLocking) $this->lock(LOCK_EX); try { $this->fwrite($contents); } finally { diff --git a/php/src/file/csv/CsvReader.php b/php/src/file/csv/CsvReader.php index d923976..317fda0 100644 --- a/php/src/file/csv/CsvReader.php +++ b/php/src/file/csv/CsvReader.php @@ -2,7 +2,9 @@ namespace nulib\file\csv; use nulib\file; +use nulib\file\_IFile; use nulib\file\FileReader; +use nulib\file\IReader; use nulib\file\tab\AbstractReader; use nulib\file\tab\TAbstractReader; @@ -20,7 +22,12 @@ class CsvReader extends AbstractReader { protected ?string $inputEncoding; function getIterator() { - $reader = new FileReader(file::fix_dash($this->input)); + $input = $this->input; + if ($input instanceof IReader) { + $reader = $input; + } else { + $reader = new FileReader(file::fix_dash($input)); + } $inputEncoding = $this->inputEncoding; if ($inputEncoding !== null) { $reader->appendFilter("convert.iconv.$inputEncoding.utf-8"); diff --git a/php/src/php/func.php b/php/src/php/func.php index f2d2883..fcec77b 100644 --- a/php/src/php/func.php +++ b/php/src/php/func.php @@ -478,11 +478,17 @@ class func { } static function with($func, ?array $args=null, bool $strict=true): self { + if ($func instanceof self) return $func; $func = self::_with($func, $args, $strict, $reason); if ($func !== null) return $func; throw self::not_a_callable($func, $reason); } + static function withn($func, ?array $args=null, bool $strict=true): ?self { + if ($func === null) return null; + else return self::with($func, $args, $strict); + } + static function ensure($func, ?array $args=null, bool $strict=true): self { $func = self::with($func, $args, $strict); if (!$func->isBound()) { diff --git a/php/src/php/time/DateInterval.php b/php/src/php/time/DateInterval.php index c9ca935..210e6ba 100644 --- a/php/src/php/time/DateInterval.php +++ b/php/src/php/time/DateInterval.php @@ -29,7 +29,7 @@ class DateInterval extends \DateInterval { } function __construct($duration) { - if (is_int($duration)) $duration = "PT${duration}S"; + if (is_numeric($duration)) $duration = "PT${duration}S"; if ($duration instanceof \DateInterval) { $this->y = $duration->y; $this->m = $duration->m; diff --git a/php/src/php/time/Delay.php b/php/src/php/time/Delay.php index 14a5307..4afcf37 100644 --- a/php/src/php/time/Delay.php +++ b/php/src/php/time/Delay.php @@ -3,6 +3,7 @@ namespace nulib\php\time; use DateTimeInterface; use InvalidArgumentException; +use nulib\ValueException; /** * Class Delay: une durée jusqu'à un moment destination. le moment destination @@ -115,6 +116,10 @@ class Delay { $this->repr = $repr; } + function __clone() { + $this->dest = clone $this->dest; + } + function __serialize(): array { return [$this->dest, $this->repr]; } @@ -130,7 +135,7 @@ class Delay { } function addDuration($duration) { - if (is_int($duration) && $duration < 0) { + if (is_numeric($duration) && $duration < 0) { $this->dest->sub(DateInterval::with(-$duration)); } else { $this->dest->add(DateInterval::with($duration)); @@ -138,7 +143,7 @@ class Delay { } function subDuration($duration) { - if (is_int($duration) && $duration < 0) { + if (is_numeric($duration) && $duration < 0) { $this->dest->add(DateInterval::with(-$duration)); } else { $this->dest->sub(DateInterval::with($duration));