<pman>Intégration de la branche dev74

This commit is contained in:
Jephté Clain 2025-08-20 10:37:59 +04:00
commit 467003a7e4
10 changed files with 388 additions and 43 deletions

16
TODO.md
View File

@ -1,5 +1,6 @@
# nulib
# nulib/bash
* [nulib/bash](bash/TODO.md)
* runners
* [ ] rnlphp -- lancer un programme php avec la bonne version (+docker le cas échéant)
* [ ] utilisable en shebang
@ -12,4 +13,17 @@
* [ ] rnlsh -- lancer un shell avec les librairies bash / lancer un script
* MYTRUEDIR, MYTRUENAME, MYTRUESELF -- résoudre les liens symboliques
# nulib/php
* [nulib](php/src/TODO.md)
* [nulib\app](php/src/app/TODO.md)
* [nulib\db](php/src/db/TODO.md)
* [nulib\os](php/src/os/TODO.md)
* [nulib\output](php/src/output/TODO.md)
* [nulib\php\time](php/src/php/time/TODO.md)
vrac:
* PID dans les logs
* build --ci
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

16
composer.lock generated
View File

@ -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": "4569957a35f86d8a4964d01c7358935c",
"content-hash": "71744d15224f445d1aeefe16ec7d1099",
"packages": [
{
"name": "symfony/deprecation-contracts",
@ -301,16 +301,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.13.1",
"version": "1.13.3",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
"shasum": ""
},
"require": {
@ -349,7 +349,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
},
"funding": [
{
@ -357,7 +357,7 @@
"type": "tidelift"
}
],
"time": "2025-04-29T12:36:36+00:00"
"time": "2025-07-05T12:25:42+00:00"
},
{
"name": "nikic/php-parser",
@ -2027,6 +2027,8 @@
"php": "^7.4"
},
"platform-dev": {
"ext-mbstring": "*",
"ext-iconv": "*",
"ext-posix": "*",
"ext-pcntl": "*",
"ext-curl": "*",

5
php/src/TODO.md Normal file
View File

@ -0,0 +1,5 @@
# nulib
* [ ] support de UserException pour ExceptionShadow: distinguer userMessage et techMessage
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

View File

@ -3,5 +3,7 @@
* [ ] ajouter des méthodes normalisées `app::get_cachedir()` et
`app::get_cachefile($name)` avec la valeur par défaut
`cachedir = $vardir/cache`
* [ ] `app::action()` et `app::step()` appellent automatiquement
`app::_dispatch_signals()`
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

View File

@ -2,6 +2,9 @@
namespace nulib\app;
use nulib\A;
use nulib\cl;
use nulib\cv;
use nulib\file;
use nulib\str;
class args {
@ -10,7 +13,10 @@ class args {
* - ["myArg" => $value] devient ["--my-arg", "$value"]
* - ["myOpt" => true] devient ["--my-opt"]
* - ["myOpt" => false] est omis
* - les autres valeurs sont prises telles quelles
* - les autres valeurs sont transformées en chaines puis ajoutée
*
* ainsi, ["myOpt" => "value", "myArg", "myBool" => true]
* devient ["--my-opt", "value", "myArg", "--my-bool"]
*/
static function from_array(?array $array): array {
$args = [];
@ -36,4 +42,205 @@ class args {
}
return $args;
}
private static function tint(string $value): int {
return intval($value);
}
private static function tbool(string $value): bool {
return boolval($value);
}
private static function tarray(string $value): ?array {
if ($value === "") return null;
$tmparray = explode(",", $value);
$array = null;
foreach ($tmparray as $tmpvalue) {
[$tmpkey, $tmpvalue] = str::split_pair($tmpvalue);
if ($tmpvalue === null) cv::swap($tmpkey, $tmpvalue);
if ($tmpkey === null) {
$array[] = $tmpvalue;
} else {
if (str::del_suffix($tmpkey, ":int")) {
$tmpvalue = self::tint($tmpvalue);
} elseif (str::del_suffix($tmpkey, ":bool")) {
$tmpvalue = self::tbool($tmpvalue);
}
$array[$tmpkey] = $tmpvalue;
}
}
return $array;
}
/**
* convertir une liste d'arguments en tableau qui est utilisable comme un
* filtre de base de données ou des données d'une requête REST. les arguments
* peuvent être de la forme:
* - "name=value"
* qui devient dans le tableau ["name" => "value"]
* - "+arg" ou "arg"
* qui devient dans le tableau ["arg" => true]
* - "-arg" ou "~arg"
* qui est stocké dans le tableau $query ["arg" => false]
*
* si $allow_file == true, les formes d'arguments suivantes sont reconnues
* aussi:
* - "name=@file" (1 argument) OU
* "name=@" "file" (2 arguments)
* qui deviennent ["name" => new FileReader(file)]
*/
static function build_query(?array $args, bool $allow_file=true): ?array {
$query = null;
$args ??= [];
$keys = array_keys($args);
$index = 0;
$count = count($keys);
while ($index < $count) {
$arg = $args[$keys[$index++]];
[$name, $value] = str::split_pair($arg, "=");
$checkType = true;
if ($value === null) {
if (str::del_prefix($name, "+")) {
$value = true;
} elseif (str::del_prefix($name, "-") || str::del_prefix($name, "~")) {
$value = false;
} else {
$value = true;
}
} elseif ($allow_file) {
if ($value === "@") {
$value = $args[$keys[$index++]];
$value = file::reader($value);
$checkType = false;
} elseif (substr($value, 0, 1) === "@") {
$value = substr($value, 1);
$value = file::reader($value);
$checkType = false;
}
}
if ($checkType) {
if (str::del_suffix($name, ":int")) {
if (str::del_suffix($name, ":array")) {
$value = array_map([self::class, "tint"], self::tarray($value));
} else {
$value = self::tint($value);
}
} elseif (str::del_suffix($name, ":bool")) {
if (str::del_suffix($name, ":array")) {
$value = array_map([self::class, "tbool"], self::tarray($value));
} else {
$value = self::tbool($value);
}
} elseif (str::del_suffix($name, ":array")) {
$value = self::tarray($value);
if (str::del_suffix($name, ":int")) {
$value = array_map([self::class, "tint"], $value);
} elseif (str::del_suffix($name, ":bool")) {
$value = array_map([self::class, "tbool"], $value);
}
}
}
if (cl::has($query, $name)) {
A::ensure_array($query[$name]);
$query[$name][] = $value;
} else {
$query[$name] = $value;
}
}
return $query;
}
/**
* convertir une liste d'arguments de façon qu'ils soient utilisables pour un
* appel de méthode. les arguments peuvent être de la forme:
* - "name=value"
* qui est stocké dans le tableau $query ["name" => "value"]
* il est possible de forcer le type de la valeur avec l'un des suffixes
* :int, :bool ou :array, e.g
* un entier: "name:int=42"
* un tableau de chaines: "name:array=a,b,c"
* un tableau d'entiers: "name:array:int=1,2,3"
* - "+arg"
* qui est stocké dans le tableau $query ["arg" => true]
* - "-arg" ou "~arg"
* qui est stocké dans le tableau $query ["arg" => false]
* - "array:sval,key:aval,..."
* qui devient l'argument ["sval", "key" => "aval", ...]
* il est possible de forcer le types des éléments avec le préfixe int: ou
* bool: e.g "array:int:1,2,3"
* - "int:value"
* qui devient l'argument intval("value")
* - "bool:value"
* qui devient l'argument boolval("value")
* - "value"
* qui devient l'argument "value"
*
* à la fin, la liste des arguments est retournée [$arguments...]
* si le tableau $query est renseigné, il est en premier dans la liste des
* arguments e.g [$query, $arguments...]
*/
static function build_method_args(?array $args): ?array {
$query = null;
$margs = [];
$args ??= [];
foreach ($args as $arg) {
[$name, $value] = str::split_pair($arg, "=");
if ($value === null) {
if (str::del_prefix($name, "+")) {
$value = true;
} elseif (str::del_prefix($name, "-") || str::del_prefix($name, "~")) {
$value = false;
} elseif (str::del_prefix($name, "int:")) {
$margs[] = self::tint($name);
continue;
} elseif (str::del_prefix($name, "bool:")) {
$margs[] = self::tbool($name);
continue;
} elseif (str::del_prefix($name, "array:")) {
if (str::del_prefix($name, "int:")) {
$map = [self::class, "tint"];
} elseif (str::del_prefix($name, "bool:")) {
$map = [self::class, "tbool"];
} else {
$map = null;
}
$value = self::tarray($name);
if ($map !== null) $value = array_map($map, $value);
$margs[] = $value;
continue;
} else {
$margs[] = $name;
continue;
}
}
if (str::del_suffix($name, ":int")) {
if (str::del_suffix($name, ":array")) {
$value = array_map([self::class, "tint"], self::tarray($value));
} else {
$value = self::tint($value);
}
} elseif (str::del_suffix($name, ":bool")) {
if (str::del_suffix($name, ":array")) {
$value = array_map([self::class, "tbool"], self::tarray($value));
} else {
$value = self::tbool($value);
}
} elseif (str::del_suffix($name, ":array")) {
$value = self::tarray($value);
if (str::del_suffix($name, ":int")) {
$value = array_map([self::class, "tint"], $value);
} elseif (str::del_suffix($name, ":bool")) {
$value = array_map([self::class, "tbool"], $value);
}
}
if (cl::has($query, $name)) {
A::ensure_array($query[$name]);
$query[$name][] = $value;
} else {
$query[$name] = $value;
}
}
if ($query !== null) array_unshift($margs, $query);
return $margs;
}
}

View File

@ -134,17 +134,6 @@ class Pdo implements IDatabase {
return $this;
}
const SQL_CHECK_LIVE = "select 1";
function ensure(): self {
try {
$this->_exec(static::SQL_CHECK_LIVE);
} catch (\PDOException $e) {
$this->open(true);
}
return $this;
}
function close(): void {
$this->db = null;
}
@ -159,6 +148,30 @@ class Pdo implements IDatabase {
return $this->db()->exec($query);
}
/** @return array|null */
function _query(string $query) {
$db = $this->db();
/** @var \PDOStatement $stmt */
$stmt = $db->query($query);
if ($stmt === false) return null;
try {
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
} finally {
$stmt->closeCursor();
}
}
const SQL_CHECK_LIVE = "select 1";
function ensure(): self {
try {
$this->_query(static::SQL_CHECK_LIVE);
} catch (\PDOException $e) {
$this->open(true);
}
return $this;
}
function exec($query, ?array $params=null) {
$db = $this->db();
$query = new _pdoQuery($query, $params);

View File

@ -173,17 +173,6 @@ class Pgsql implements IDatabase {
return $this;
}
const SQL_CHECK_LIVE = "select 1";
function ensure(): self {
try {
$this->_exec(static::SQL_CHECK_LIVE);
} catch (\PDOException $e) {
$this->open(true);
}
return $this;
}
function close(): self {
if ($this->db !== null) {
pg_close($this->db);
@ -204,6 +193,31 @@ class Pgsql implements IDatabase {
return true;
}
function _query(string $query): ?array {
$result = pg_query($this->db(), $query);
if ($result === false) return null;
try {
$rows = [];
while (($row = pg_fetch_assoc($result)) !== false) {
$rows[] = $row;
}
return $rows;
} finally {
pg_free_result($result);
}
}
const SQL_CHECK_LIVE = "select 1";
function ensure(): self {
try {
$this->_query(static::SQL_CHECK_LIVE);
} catch (\PDOException $e) {
$this->open(true);
}
return $this;
}
function getLastSerial() {
$db = $this->db();
$result = @pg_query($db, "select lastval()");

View File

@ -167,17 +167,6 @@ class Sqlite implements IDatabase {
return $this;
}
const SQL_CHECK_LIVE = "select 1";
function ensure(): self {
try {
$this->_exec(static::SQL_CHECK_LIVE);
} catch (\PDOException $e) {
$this->open(true);
}
return $this;
}
function close(): void {
if ($this->db !== null) {
$this->db->close();
@ -203,6 +192,31 @@ class Sqlite implements IDatabase {
return $this->db()->exec($query);
}
function _query(string $query): ?array {
$result = $this->db()->query($query);
if ($result === false) return null;
try {
$rows = [];
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
$rows[] = $row;
}
return $rows;
} finally {
$result->finalize();
}
}
const SQL_CHECK_LIVE = "select 1";
function ensure(): self {
try {
$this->_query(static::SQL_CHECK_LIVE);
} catch (\PDOException $e) {
$this->open(true);
}
return $this;
}
function exec($query, ?array $params=null) {
$db = $this->db();
$query = new _sqliteQuery($query, $params);

View File

@ -1,4 +1,4 @@
# TOOD
# nulib\output
* dans msg::action($m, function() {}), *bloquer* la marque pour empêcher d'aller
plus bas que prévu. comme ça s'il y a plusieurs success ou failure dans la
@ -32,4 +32,10 @@ pour l'UI
peut-être rajouter `ui` (ou `web`?) en plus de say, log, debuglog?
--> ou renommer `say` en `console`, et `ui` en `say`
* [ ] ajouter une option `Application::MSG_SIGNALS` qui fait que
* les méthodes `msg::eXXX()` appellent automatiquement `app::_dispatch_signals()`
* [ ] ajouter une option `Application::MSG_ACTIONS` qui fait que
* `msg::section()` et/ou `msg::title()` appellent automatiquement `app::action()`
* `msg::estep()` appelle automatiquement `app::step()`
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

View File

@ -23,4 +23,72 @@ class argsTest extends TestCase {
self::assertSame(["x", "1", "2", "3", "y"], args::from_array(["x", [1, 2, 3], "y"]));
}
function testBuild_query() {
self::assertSame(null, args::build_query(null));
self::assertSame(null, args::build_query([]));
self::assertSame(["a" => true], args::build_query(["a"]));
self::assertSame(["a" => true], args::build_query(["+a"]));
self::assertSame(["a" => false], args::build_query(["-a"]));
self::assertSame(["a" => false], args::build_query(["~a"]));
self::assertSame(["x" => "a"], args::build_query(["x=a"]));
self::assertSame(["x" => 0], args::build_query(["x:int=0"]));
self::assertSame(["x" => 42], args::build_query(["x:int=42"]));
self::assertSame(["x" => false], args::build_query(["x:bool=0"]));
self::assertSame(["x" => true], args::build_query(["x:bool=42"]));
self::assertSame(["x" => ["a", "b"]], args::build_query(["x:array=a,b"]));
self::assertSame(["x" => [0, 42]], args::build_query(["x:array:int=0,42"]));
self::assertSame(["x" => [0, 42]], args::build_query(["x:int:array=0,42"]));
self::assertSame(["x" => "a", "y" => "b"], args::build_query(["x=a", "y=b"]));
self::assertSame(["x" => ["a", "b"]], args::build_query(["x=a", "x=b"]));
}
function testBuild_method_args() {
self::assertSame([], args::build_method_args(null));
self::assertSame([], args::build_method_args([]));
self::assertSame(["a"], args::build_method_args(["a"]));
self::assertSame(["a", "b"], args::build_method_args(["a", "b"]));
self::assertSame([0], args::build_method_args(["int:0"]));
self::assertSame([42], args::build_method_args(["int:42"]));
# pour le moment, pas de tint
self::assertSame([0], args::build_method_args(["int:"]));
self::assertSame([0], args::build_method_args(["int:truc"]));
self::assertSame([false], args::build_method_args(["bool:0"]));
self::assertSame([true], args::build_method_args(["bool:42"]));
self::assertSame([false], args::build_method_args(["bool:"]));
self::assertSame([true], args::build_method_args(["bool:truc"]));
# pour le moment, pas de tbool
self::assertSame([true], args::build_method_args(["bool:false"]));
self::assertSame([true], args::build_method_args(["bool:true"]));
self::assertSame([["a", "b"]], args::build_method_args(["array:a,b"]));
self::assertSame([["x" => "a", "y" => "b"]], args::build_method_args(["array:x:a,y:b"]));
# pour le moment, pas de tint
self::assertSame([[0, 42, 0, 0]], args::build_method_args(["array:int:0,42,,truc"]));
self::assertSame([["x" => 0, "y" => 42]], args::build_method_args(["array:int:x:0,y:42"]));
# pour le moment, pas de tbool
self::assertSame([[false, true, false, true, true, true]], args::build_method_args(["array:bool:0,42,,truc,false,true"]));
self::assertSame([["x" => false, "y" => true]], args::build_method_args(["array:bool:x:0,y:42"]));
self::assertSame([["a" => true]], args::build_method_args(["+a"]));
self::assertSame([["a" => false]], args::build_method_args(["-a"]));
self::assertSame([["a" => false]], args::build_method_args(["~a"]));
self::assertSame([["x" => "a"]], args::build_method_args(["x=a"]));
self::assertSame([["x" => 0]], args::build_method_args(["x:int=0"]));
self::assertSame([["x" => 42]], args::build_method_args(["x:int=42"]));
self::assertSame([["x" => false]], args::build_method_args(["x:bool=0"]));
self::assertSame([["x" => true]], args::build_method_args(["x:bool=42"]));
self::assertSame([["x" => ["a", "b"]]], args::build_method_args(["x:array=a,b"]));
self::assertSame([["x" => [0, 42]]], args::build_method_args(["x:array:int=0,42"]));
self::assertSame([["x" => [0, 42]]], args::build_method_args(["x:int:array=0,42"]));
self::assertSame([["x" => "a", "y" => "b"], "a", "b"], args::build_method_args(["x=a", "a", "y=b", "b"]));
self::assertSame([["x" => ["a", "b"]]], args::build_method_args(["x=a", "x=b"]));
}
}