Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
33a10a37c2 | |||
f7e692df0c | |||
0c40769619 | |||
a120143794 | |||
3754ed7454 | |||
fea5cb8b87 | |||
5dd9ebbf65 | |||
5862d3583a | |||
ea68906b8a | |||
309375c297 | |||
d3950f5519 |
14
.idea/php.xml
generated
14
.idea/php.xml
generated
@ -21,20 +21,16 @@
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/phpss" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
|
||||
<path value="$PROJECT_DIR$/vendor/ezyang/htmlpurifier" />
|
||||
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
|
||||
<path value="$PROJECT_DIR$/vendor/myclabs/php-enum" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
|
||||
<path value="$PROJECT_DIR$/vendor/maennchen/zipstream-php" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/markbaker/complex" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
|
||||
@ -44,7 +40,6 @@
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
|
||||
@ -64,6 +59,15 @@
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
|
||||
<path value="$PROJECT_DIR$/vendor/composer" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
|
||||
<path value="$PROJECT_DIR$/vendor/ezyang/htmlpurifier" />
|
||||
<path value="$PROJECT_DIR$/vendor/myclabs/php-enum" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/php" />
|
||||
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
|
||||
</include_path>
|
||||
|
@ -1,3 +1,5 @@
|
||||
## Release 0.5.0p82 du 30/04/2025-05:35
|
||||
|
||||
## Release 0.5.0p74 du 30/04/2025-05:30
|
||||
|
||||
* `2d73f4d` documenter showmorePlugin
|
||||
|
48
README.md
Normal file
48
README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# nur-ture
|
||||
|
||||
|
||||
## Release
|
||||
|
||||
Exemple: release de la version 0.6.0
|
||||
|
||||
Avant de faire une release majeure sur nur/ture, faire d'abord la release
|
||||
majeure correspondante sur
|
||||
* nulib/php
|
||||
* nulib/spout
|
||||
* nulib/phpss
|
||||
|
||||
~~~sh
|
||||
version=0.6.0
|
||||
|
||||
major="${version%.*}.0"
|
||||
|
||||
## branche dev74
|
||||
git checkout dev74
|
||||
|
||||
sed -ri "\
|
||||
/nulib\/.*:/s/[0-9]+.[0-9]+.0p74/${major}p74/
|
||||
" .composer.pman.yml
|
||||
pci "maj projet"
|
||||
|
||||
prel -v$version
|
||||
|
||||
# en cas de conflit, sélectionner HEAD
|
||||
_merge82
|
||||
|
||||
## branche dev82
|
||||
git checkout dev82
|
||||
|
||||
sed -ri "\
|
||||
/nulib\/.*:/s/[0-9]+.[0-9]+.0p82/${major}p82/
|
||||
" .composer.pman.yml
|
||||
pci "maj projet"
|
||||
|
||||
prel -C
|
||||
|
||||
commit="$(git log --grep="Init changelog . version ${version}p82" --format=%H)"
|
||||
git checkout dev74
|
||||
git cherry-pick "$commit"
|
||||
pp -a
|
||||
~~~
|
||||
|
||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
@ -3,6 +3,18 @@
|
||||
"type": "library",
|
||||
"description": "espace de maturation pour les librairies",
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "../nulib"
|
||||
},
|
||||
{
|
||||
"type": "path",
|
||||
"url": "../nulib-spout"
|
||||
},
|
||||
{
|
||||
"type": "path",
|
||||
"url": "../nulib-phpss"
|
||||
},
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://repos.univ-reunion.fr/composer"
|
||||
@ -18,9 +30,9 @@
|
||||
"php": "^7.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"nulib/php": "^0.5.0p74",
|
||||
"nulib/spout": "^0.5.0p74",
|
||||
"nulib/phpss": "^0.5.0p74",
|
||||
"nulib/php": "^7.4-dev",
|
||||
"nulib/spout": "^7.4-dev",
|
||||
"nulib/phpss": "^7.4-dev",
|
||||
"nulib/tests": "^7.4",
|
||||
"ext-posix": "*",
|
||||
"ext-pcntl": "*",
|
||||
|
54
composer.lock
generated
54
composer.lock
generated
@ -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": "b4bd340f94d33a320d66b249b1c21edb",
|
||||
"content-hash": "0b1e015d12aecf1cdfbdc6a702d83d25",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
@ -585,11 +585,11 @@
|
||||
},
|
||||
{
|
||||
"name": "nulib/php",
|
||||
"version": "0.5.0p74",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.univ-reunion.fr/sda-php/nulib.git",
|
||||
"reference": "4037bf20424eb48708e5fdf9fc8e10f2ef71d134"
|
||||
"version": "dev-dev74",
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../nulib",
|
||||
"reference": "a371a68ee2d388d6970eec8ccf83b51cbc8e4e37"
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
@ -629,18 +629,20 @@
|
||||
}
|
||||
],
|
||||
"description": "fonctions et classes essentielles",
|
||||
"time": "2025-04-30T00:32:10+00:00"
|
||||
"transport-options": {
|
||||
"relative": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nulib/phpss",
|
||||
"version": "0.5.0p74",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.univ-reunion.fr/sda-php/nulib-phpss.git",
|
||||
"reference": "26b4bfddf5646f9313d419e568cd930efb9353eb"
|
||||
"version": "dev-dev74",
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../nulib-phpss",
|
||||
"reference": "e68672917d2c51d5cf559835a71d49b66facd081"
|
||||
},
|
||||
"require": {
|
||||
"nulib/php": "^0.5.0p74",
|
||||
"nulib/php": "^7.4-dev",
|
||||
"php": "^7.4",
|
||||
"phpoffice/phpspreadsheet": "^1.0"
|
||||
},
|
||||
@ -671,15 +673,17 @@
|
||||
}
|
||||
],
|
||||
"description": "wrapper pour phpoffice/phpspreadsheet",
|
||||
"time": "2025-04-30T00:46:31+00:00"
|
||||
"transport-options": {
|
||||
"relative": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nulib/spout",
|
||||
"version": "0.5.0p74",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.univ-reunion.fr/sda-php/nulib-spout.git",
|
||||
"reference": "e650e27abe571553424524633deada32747d33a6"
|
||||
"version": "dev-dev74",
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../nulib-spout",
|
||||
"reference": "853f747bad42718f58aa0c1760f4d2536a165d3d"
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
@ -687,7 +691,7 @@
|
||||
"ext-libxml": "*",
|
||||
"ext-xmlreader": "*",
|
||||
"ext-zip": "*",
|
||||
"nulib/php": "^0.5.0p74",
|
||||
"nulib/php": "^7.4-dev",
|
||||
"php": "^7.4"
|
||||
},
|
||||
"replace": {
|
||||
@ -725,7 +729,9 @@
|
||||
}
|
||||
],
|
||||
"description": "wrapper pour openspout/openspout",
|
||||
"time": "2025-04-30T00:40:11+00:00"
|
||||
"transport-options": {
|
||||
"relative": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nulib/tests",
|
||||
@ -2939,7 +2945,11 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {
|
||||
"nulib/php": 20,
|
||||
"nulib/spout": 20,
|
||||
"nulib/phpss": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
@ -63,21 +63,14 @@ Application::run(new class extends Application {
|
||||
$storage = new SqliteStorage($dbfile);
|
||||
$db = $storage->db();
|
||||
|
||||
$haveChannels = $storage->tableExists("_channels");
|
||||
|
||||
$name = $this->name;
|
||||
$channelClass = $this->channelClass;
|
||||
$tableName = $this->tableName;
|
||||
if ($name !== null) {
|
||||
$row = null;
|
||||
if ($haveChannels) {
|
||||
$row = $db->one([
|
||||
"select from _channels",
|
||||
"where" => ["name" => $name],
|
||||
]);
|
||||
if (!$storage->channelExists($name, $row)) {
|
||||
self::die("$name: nom de canal de données introuvable");
|
||||
}
|
||||
if ($row === null) self::die("$name: nom de canal de données introuvable");
|
||||
if ($row["class"] !== "class@anonymous") $channelClass = $row["class"];
|
||||
if ($row["class_name"] !== "class@anonymous") $channelClass = $row["class_name"];
|
||||
else $tableName = $row["table_name"];
|
||||
}
|
||||
if ($channelClass !== null) {
|
||||
@ -92,17 +85,12 @@ Application::run(new class extends Application {
|
||||
};
|
||||
} else {
|
||||
$found = false;
|
||||
if ($haveChannels) {
|
||||
$rows = $db->all([
|
||||
"select from _channels",
|
||||
]);
|
||||
foreach ($rows as $row) {
|
||||
msg::print($row["name"]);
|
||||
$found = true;
|
||||
}
|
||||
foreach ($storage->getChannels() as $row) {
|
||||
msg::print($row["name"]);
|
||||
$found = true;
|
||||
}
|
||||
if (!$found) self::die("Vous devez spécifier le canal de données");
|
||||
else self::exit();
|
||||
if ($found) self::exit();
|
||||
self::die("Vous devez spécifier le canal de données");
|
||||
}
|
||||
$capacitor = new Capacitor($storage, $channel);
|
||||
|
||||
|
@ -34,6 +34,12 @@
|
||||
.left-gap { margin-left: 1em;}
|
||||
.right-gap { margin-right: 1em;}
|
||||
|
||||
table.table-sticky tr th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* si un navbar-form contient des btn-sm ou des btn-xs, utiliser les classes ci-dessous. */
|
||||
.navbar-form-sm { margin-top: 10px; margin-bottom: 10px; }
|
||||
.navbar-form-xs { margin-top: 14px; margin-bottom: 14px; }
|
||||
|
@ -633,14 +633,15 @@ class CTable extends ComponentPrintable implements IParametrable {
|
||||
/** @var string|int clé de la colonne courante */
|
||||
protected $col;
|
||||
|
||||
function colTd($value): array {
|
||||
function colTd($value): ?array {
|
||||
$vs = $this->col($value);
|
||||
if ($this->colCtx !== null) {
|
||||
$vs = func::_call($this->colCtx, [$vs, $value, $this->col, $this->index, $this->row, $this->rawRow]);
|
||||
if ($vs === false) return null;
|
||||
} else {
|
||||
$result = A::get($this->results, $this->col);
|
||||
$valid = $result === null || $result["valid"];
|
||||
$vs= [
|
||||
$vs = [
|
||||
"class" => ["danger" => !$valid],
|
||||
$vs,
|
||||
];
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace nulib\php\access;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
@ -118,85 +119,18 @@ class KeyAccess extends AbstractAccess {
|
||||
}
|
||||
|
||||
function ensureAssoc(array $keys, ?array $params=null): void {
|
||||
$dest =& $this->dest;
|
||||
$prefix = $params["key_prefix"] ?? null;
|
||||
$suffix = $params["key_suffix"] ?? null;
|
||||
$index = 0;
|
||||
foreach ($keys as $key) {
|
||||
if ($prefix !== null || $suffix !== null) {
|
||||
$destKey = "$prefix$key$suffix";
|
||||
} else {
|
||||
# préserver les clés numériques
|
||||
$destKey = $key;
|
||||
}
|
||||
if ($dest !== null && array_key_exists($destKey, $dest)) continue;
|
||||
while (in_array($index, $keys, true)) {
|
||||
$index++;
|
||||
}
|
||||
if ($dest !== null && array_key_exists($index, $dest)) {
|
||||
$dest[$destKey] = $dest[$index];
|
||||
unset($dest[$index]);
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
A::ensure_assoc($this->dest, $keys, $params);
|
||||
}
|
||||
|
||||
function ensureKeys(array $defaults, ?array $missings, ?array $params=null): void {
|
||||
$dest =& $this->dest;
|
||||
$keys = array_keys($defaults);
|
||||
$prefix = $params["key_prefix"] ?? null;
|
||||
$suffix = $params["key_suffix"] ?? null;
|
||||
foreach ($keys as $key) {
|
||||
$destKey = "$prefix$key$suffix";
|
||||
$haveMissing = $missings !== null && array_key_exists($key, $missings);
|
||||
if ($dest === null || !array_key_exists($destKey, $dest)) {
|
||||
$dest[$destKey] = $defaults[$key];
|
||||
} elseif ($haveMissing && $dest[$destKey] === $missings[$key]) {
|
||||
$dest[$destKey] = $defaults[$key];
|
||||
}
|
||||
}
|
||||
A::ensure_keys($this->dest, $defaults, $missings, $params);
|
||||
}
|
||||
|
||||
function deleteMissings(array $missings, ?array $params=null): void {
|
||||
$dest =& $this->dest;
|
||||
$prefix = $params["key_prefix"] ?? null;
|
||||
$suffix = $params["key_suffix"] ?? null;
|
||||
foreach ($missings as $key => $missing) {
|
||||
$destKey = "$prefix$key$suffix";
|
||||
if (array_key_exists($destKey, $dest) && $dest[$destKey] === $missing) {
|
||||
unset($dest[$destKey]);
|
||||
}
|
||||
}
|
||||
A::delete_missings($this->dest, $missings, $params);
|
||||
}
|
||||
|
||||
function ensureOrder(array $keys, ?array $params=null): void {
|
||||
$dest =& $this->dest;
|
||||
if ($dest === null) return;
|
||||
|
||||
$prefix = $params["key_prefix"] ?? null;
|
||||
$suffix = $params["key_suffix"] ?? null;
|
||||
if ($prefix !== null || $suffix !== null) {
|
||||
foreach ($keys as &$key) {
|
||||
$key = "$prefix$key$suffix";
|
||||
}; unset($key);
|
||||
}
|
||||
|
||||
$destKeys = array_keys($dest);
|
||||
$keyCount = count($keys);
|
||||
if (array_slice($destKeys, 0, $keyCount) === $keys) {
|
||||
# si le tableau a déjà les bonnes clés dans le bon ordre, rien à faire
|
||||
return;
|
||||
}
|
||||
|
||||
$ordered = [];
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $dest)) {
|
||||
$ordered[$key] = $dest[$key];
|
||||
unset($dest[$key]);
|
||||
}
|
||||
}
|
||||
$preserveKeys = $params["preserve_keys"] ?? false;
|
||||
if ($preserveKeys) $dest = cl::merge2($ordered, $dest);
|
||||
else $dest = array_merge($ordered, $dest);
|
||||
A::ensure_order($this->dest, $keys, $params);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ use Traversable;
|
||||
/**
|
||||
* Class Cursor: parcours des lignes itérable
|
||||
*
|
||||
* XXX si on spécifie $cols ou $colsFunc, il y a une possibilité que les clés de
|
||||
* $row ne soient pas dans le bon ordre, ou que les clés de $cols ne soient pas
|
||||
* présentes dans $row. ajouter les paramètres ensure_keys et order_keys
|
||||
*
|
||||
* @property-read array|null $value alias pour $row
|
||||
* @property-read iterable|null $rows la source des lignes
|
||||
*/
|
||||
@ -18,80 +22,14 @@ class Cursor implements Iterator {
|
||||
const PARAMS_SCHEMA = [
|
||||
"rows" => ["?iterable"],
|
||||
"rows_func" => ["?callable"],
|
||||
"cols" => ["?array"],
|
||||
"cols_func" => ["?callable"],
|
||||
"map" => ["?array"],
|
||||
"map_func" => ["?callable"],
|
||||
"filter" => ["?array"],
|
||||
"filter_func" => ["?callable"],
|
||||
"map" => ["?array"],
|
||||
"map_func" => ["?callable"],
|
||||
"cols" => ["?array"],
|
||||
"cols_func" => ["?callable"],
|
||||
];
|
||||
|
||||
/**
|
||||
* mapper le tableau source $row selon les règles suivantes illustrées dans
|
||||
* l'exemple suivant:
|
||||
* si
|
||||
* $map = ["a", "b" => "x", "c" => function() { return "y"; }, "d" => null]
|
||||
* alors retourner le tableau
|
||||
* ["a" => $row["a"], "b" => $row["x"], "c" => "y", "d" => null]
|
||||
*/
|
||||
protected static function map_row(array $row, ?array $map): array {
|
||||
if ($map === null) return $row;
|
||||
$index = 0;
|
||||
$mapped = [];
|
||||
foreach ($map as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
if ($value === null) $mapped[] = null;
|
||||
else $mapped[$value] = cl::get($row, $value);
|
||||
} elseif (is_callable($value)) {
|
||||
$func = func::with($value);
|
||||
$value = cl::get($row, $key);
|
||||
$mapped[$key] = $func->invoke([$value, $key, $row]);
|
||||
} else {
|
||||
if ($value === null) $mapped[$key] = null;
|
||||
else $mapped[$key] = cl::get($row, $value);
|
||||
}
|
||||
}
|
||||
return $mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* tester si $row satisfait les conditions de $filter
|
||||
* - $filter est un scalaire, le transformer en [$filter]
|
||||
* - sinon $filter doit être un tableau de scalaires
|
||||
*
|
||||
* les règles des conditions sont les suivantes:
|
||||
* - une valeur séquentielle $key est équivalente à la valeur associative
|
||||
* $key => true
|
||||
* - une valeur associative $key => bool indique que la clé correspondante ne
|
||||
* doit pas (resp. doit) exister selon que bool vaut false (resp. true)
|
||||
* - une valeur associative $key => $value indique que la clé correspondante
|
||||
* doit exiter avec la valeur spécifiée
|
||||
*/
|
||||
protected static function filter_row(array $row, $filter): bool {
|
||||
if ($filter === null) return false;
|
||||
if (!is_array($filter)) $filter = [$filter];
|
||||
if (!$filter) return false;
|
||||
|
||||
$index = 0;
|
||||
foreach ($filter as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
if (!array_key_exists($value, $row)) return false;
|
||||
} elseif (is_bool($value)) {
|
||||
if ($value) {
|
||||
if (!array_key_exists($key, $row)) return false;
|
||||
} else {
|
||||
if (array_key_exists($key, $row)) return false;
|
||||
}
|
||||
} else {
|
||||
if (!array_key_exists($key, $row)) return false;
|
||||
if ($row[$key] !== $value) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function __construct(?iterable $rows=null, ?array $params=null) {
|
||||
if ($rows !== null) $params["rows"] = $rows;
|
||||
|
||||
@ -103,7 +41,7 @@ class Cursor implements Iterator {
|
||||
$rowsGenerator = $rowsFunc;
|
||||
$rowsFunc = null;
|
||||
} else {
|
||||
$rowsFunc = func::with($rowsFunc, [$rows]);
|
||||
$rowsFunc = func::with($rowsFunc, [$rows, $this]);
|
||||
}
|
||||
} elseif ($rows instanceof Traversable) {
|
||||
$rowsGenerator = $rows;
|
||||
@ -115,61 +53,95 @@ class Cursor implements Iterator {
|
||||
$this->rowsGenerator = $rowsGenerator;
|
||||
$this->rowsFunc = $rowsFunc;
|
||||
|
||||
$this->cols = $params["cols"] ?? null;
|
||||
$colsFunc = $params["cols_func"] ?? null;
|
||||
if ($colsFunc !== null) $colsFunc = func::with($colsFunc);
|
||||
$this->colsFunc = $colsFunc;
|
||||
$filter = $params["filter"] ?? null;
|
||||
$filterFunc = $params["filter_func"] ?? null;
|
||||
if ($filterFunc !== null) $this->setFilterFunc($filterFunc);
|
||||
elseif ($filter !== null) $this->setFilter($filter);
|
||||
|
||||
$map = $params["map"] ?? null;
|
||||
$mapFunc = $params["map_func"] ?? null;
|
||||
if ($mapFunc !== null) {
|
||||
$mapFunc = func::with($mapFunc);
|
||||
} elseif ($map !== null) {
|
||||
$mapFunc = func::with(function(array $row) use ($map) {
|
||||
return self::map_row($row, $map);
|
||||
});
|
||||
}
|
||||
$this->mapFunc = $mapFunc;
|
||||
if ($mapFunc !== null) $this->setMapFunc($mapFunc);
|
||||
elseif ($map !== null) $this->setMap($map);
|
||||
|
||||
$filter = $params["filter"] ?? null;
|
||||
$filterFunc = $params["filter_func"] ?? null;
|
||||
if ($filterFunc !== null) {
|
||||
$filterFunc = func::with($filterFunc);
|
||||
} elseif ($filter !== null) {
|
||||
$filterFunc = func::with(function(array $row) use ($filter) {
|
||||
return self::filter_row($row, $filter);
|
||||
});
|
||||
}
|
||||
$this->filterFunc = $filterFunc;
|
||||
$this->cols = $params["cols"] ?? null;
|
||||
$this->setColsFunc($params["cols_func"] ?? null);
|
||||
}
|
||||
|
||||
/** un générateur de lignes */
|
||||
private ?Traversable $rowsGenerator;
|
||||
|
||||
/** une fonction de signature <code>function(Cursor): ?iterable</code> */
|
||||
/** une fonction de signature <code>function(mixed $rows, Cursor): ?iterable</code> */
|
||||
private ?func $rowsFunc;
|
||||
|
||||
/** une fonction de signature <code>function(Cursor): ?array</code> */
|
||||
private ?func $colsFunc;
|
||||
/** une fonction de signature <code>function(?array $row, Cursor): bool</code> */
|
||||
private ?func $filterFunc = null;
|
||||
|
||||
/** une fonction de signature <code>function(Cursor): ?array</code> */
|
||||
private ?func $mapFunc;
|
||||
function setFilter(array $filter): self {
|
||||
$this->filterFunc = func::with(function(?array $row) use ($filter) {
|
||||
return cl::filter($row, $filter);
|
||||
});
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** une fonction de signature <code>function(Cursor): bool</code> */
|
||||
private ?func $filterFunc;
|
||||
function setFilterFunc(?callable $func): self {
|
||||
if ($func === null) $this->filterFunc = null;
|
||||
else $this->filterFunc = func::with($func)->bind($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** une fonction de signature <code>function(?array $row, Cursor): ?array</code> */
|
||||
private ?func $mapFunc = null;
|
||||
|
||||
function setMap(array $map): self {
|
||||
$this->mapFunc = func::with(function(?array $row) use ($map) {
|
||||
return cl::map($row, $map);
|
||||
});
|
||||
return $this;
|
||||
}
|
||||
|
||||
function setMapFunc(?callable $func): self {
|
||||
if ($func === null) $this->mapFunc = null;
|
||||
else $this->mapFunc = func::with($func)->bind($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** une fonction de signature <code>function(?array $row, Cursor): ?array</code> */
|
||||
private ?func $colsFunc = null;
|
||||
|
||||
function setColsFunc(?callable $func): self {
|
||||
$this->cols = null;
|
||||
if ($func === null) $this->colsFunc = null;
|
||||
else $this->colsFunc = func::with($func)->bind($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @var iterable|null source des éléments */
|
||||
protected ?iterable $rows;
|
||||
|
||||
/** @var array|null listes des colonnes de chaque enregistrement */
|
||||
public ?array $cols;
|
||||
|
||||
/**
|
||||
* @var int index de l'enregistrement (en ne comptant pas les éléments filtrés)
|
||||
*/
|
||||
public int $index;
|
||||
|
||||
/**
|
||||
* @var int index original de l'enregistrement (en tenant compte des éléments
|
||||
* filtrés)
|
||||
*/
|
||||
public int $origIndex;
|
||||
|
||||
/** @var string|int clé de l'enregistrement */
|
||||
public $key;
|
||||
|
||||
/** @var mixed élément original récupéré depuis la source */
|
||||
public $raw;
|
||||
|
||||
/**
|
||||
* @var array|null enregistrement après conversion en tableau et application
|
||||
* du mapping
|
||||
*/
|
||||
public ?array $row;
|
||||
|
||||
function __get($name) {
|
||||
@ -179,18 +151,22 @@ class Cursor implements Iterator {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cols(): ?array {
|
||||
return $this->row !== null? array_keys($this->row): null;
|
||||
protected function convertToRow($raw): ?array {
|
||||
return cl::withn($raw);
|
||||
}
|
||||
|
||||
protected function filter(): bool {
|
||||
protected function filterFunc(?array $row): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function map(): ?array {
|
||||
protected function mapFunc(?array $row): ?array {
|
||||
return $this->row;
|
||||
}
|
||||
|
||||
protected function colsFunc(?array $row): ?array {
|
||||
return $this->row !== null? array_keys($this->row): null;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# Iterator
|
||||
|
||||
@ -212,26 +188,30 @@ class Cursor implements Iterator {
|
||||
}
|
||||
|
||||
function valid(): bool {
|
||||
$cols = $this->colsFunc;
|
||||
$filter = $this->filterFunc;
|
||||
$map = $this->mapFunc;
|
||||
$colsFunc = $this->colsFunc;
|
||||
$filterFunc = $this->filterFunc;
|
||||
$mapFunc = $this->mapFunc;
|
||||
while ($valid = iter::valid($this->rows)) {
|
||||
$this->raw = iter::current($this->rows, $this->key);
|
||||
# conversion en enregistrement
|
||||
$this->key ??= $this->origIndex;
|
||||
$this->row = cl::withn($this->raw);
|
||||
if ($filter === null) $filtered = $this->filter();
|
||||
else $filtered = $filter->invoke([$this]);
|
||||
if (!$filtered) {
|
||||
if ($map === null) $this->row = $this->map();
|
||||
else $this->row = $map->invoke([$this]);
|
||||
if ($this->cols === null) {
|
||||
if ($cols === null) $this->cols = $this->cols();
|
||||
else $this->cols = $cols->invoke([$this]);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
$this->row = $this->convertToRow($this->raw);
|
||||
# filtrage
|
||||
if ($filterFunc === null) $filtered = $this->filterFunc($this->row);
|
||||
else $filtered = $filterFunc->invoke([$this->row, $this]);
|
||||
if ($filtered) {
|
||||
iter::next($this->rows);
|
||||
$this->origIndex++;
|
||||
} else {
|
||||
# l'enregistrement n'as pas été filtré: faire le mapping
|
||||
if ($mapFunc === null) $this->row = $this->mapFunc($this->row);
|
||||
else $this->row = $mapFunc->invoke([$this->row, $this]);
|
||||
# calculer la liste des colonnes le cas échéant
|
||||
if ($this->cols === null) {
|
||||
if ($colsFunc === null) $this->cols = $this->colsFunc($this->row);
|
||||
else $this->cols = $colsFunc->invoke([$this->row, $this]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$valid) {
|
||||
|
@ -127,7 +127,6 @@ abstract class Schema implements ArrayAccess {
|
||||
}
|
||||
}
|
||||
# type
|
||||
$types = [];
|
||||
$deftype = $definition["type"];
|
||||
$nullable = $definition["nullable"] ?? false;
|
||||
if ($deftype === null) {
|
||||
@ -138,7 +137,17 @@ abstract class Schema implements ArrayAccess {
|
||||
if (!is_string($deftype)) throw SchemaException::invalid_type($deftype);
|
||||
$deftype = explode("|", $deftype);
|
||||
}
|
||||
foreach ($deftype as $type) {
|
||||
$types = [];
|
||||
$unionTypes = [];
|
||||
$index = 0;
|
||||
foreach ($deftype as $key => $type) {
|
||||
$args = null;
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
} else {
|
||||
$args = $type;
|
||||
$type = $key;
|
||||
}
|
||||
if ($type !== null) $type = trim($type);
|
||||
if ($type === null || $type === "null") {
|
||||
$nullable = true;
|
||||
@ -151,10 +160,18 @@ abstract class Schema implements ArrayAccess {
|
||||
}
|
||||
if ($type === "") throw SchemaException::invalid_type($type);
|
||||
$type = cl::get(ref_types::ALIASES, $type, $type);
|
||||
$types = array_merge($types, explode("|", $type));
|
||||
if ($args === null) {
|
||||
$unionTypes = array_merge($unionTypes, explode("|", $type));
|
||||
} else {
|
||||
$types = array_merge($types, [$type => $args]);
|
||||
}
|
||||
}
|
||||
if (!$types && !$unionTypes) throw SchemaException::invalid_schema("scalar: type is required");
|
||||
foreach ($unionTypes as $type) {
|
||||
if (!array_key_exists($type, $types)) {
|
||||
$types[] = $type;
|
||||
}
|
||||
}
|
||||
if (!$types) throw SchemaException::invalid_schema("scalar: type is required");
|
||||
$types = array_keys(array_fill_keys($types, true));
|
||||
}
|
||||
$definition["type"] = $types;
|
||||
$definition["nullable"] = $nullable;
|
||||
@ -224,7 +241,7 @@ abstract class Schema implements ArrayAccess {
|
||||
$types = $definition["type"];
|
||||
$nullable = $definition["nullable"];
|
||||
# s'il n'y a qu'une seul type, l'instancier tout de suite
|
||||
if (is_array($types) && count($types) == 1 && $types[0] !== null) {
|
||||
if (is_array($types) && count($types) == 1 && cl::first($types) !== null) {
|
||||
foreach ($types as $key => $name) {
|
||||
if ($key === 0) {
|
||||
$args = null;
|
||||
|
@ -5,19 +5,45 @@ use nulib\cl;
|
||||
use nulib\schema\_scalar\ScalarSchema;
|
||||
use nulib\schema\Result;
|
||||
use nulib\schema\Schema;
|
||||
use nulib\str;
|
||||
|
||||
class tarray extends _tstring {
|
||||
const NAME = "array";
|
||||
|
||||
const SPLIT_PATTERN = '/\s+/';
|
||||
const FORMAT = " ";
|
||||
const TRIM = true;
|
||||
const NORM_NL = true;
|
||||
|
||||
const SEP_MAP = [
|
||||
"space" => "spaces",
|
||||
"lines" => "line",
|
||||
];
|
||||
const STD_SEPS = [
|
||||
"spaces" => [" ", '/\s+/', true],
|
||||
"line" => ["\n", "\n", false],
|
||||
];
|
||||
|
||||
const DEFAULT_SEP = self::STD_SEPS["spaces"];
|
||||
const DEFAULT_PARSE_SEP = self::DEFAULT_SEP[1];
|
||||
const DEFAULT_FORMAT_SEP = self::DEFAULT_SEP[0];
|
||||
|
||||
public static function get_params_from_definition(?array $definition): ?array {
|
||||
$params = parent::get_params_from_definition($definition);
|
||||
$splitPattern = $definition["split_pattern"] ?? null;
|
||||
if ($splitPattern !== null) $params["split_pattern"] = $splitPattern;
|
||||
$format = $definition["format"] ?? null;
|
||||
if ($format !== null) $params["format"] = $format;
|
||||
$sep = $definition["sep"] ?? null;
|
||||
if ($sep !== null) {
|
||||
if (!is_array($sep)) {
|
||||
$sep = cl::get(self::SEP_MAP, $sep, $sep);
|
||||
$sep = self::STD_SEPS[$sep] ?? [$sep, $sep, false];
|
||||
}
|
||||
$params["parse_sep"] = $sep[1] ?? $sep[0];
|
||||
$params["format_sep"] = $sep[0];
|
||||
$params["trim"] ??= $sep[2] ?? static::TRIM;
|
||||
$params["norm_nl"] ??= $sep[3] ?? static::NORM_NL;
|
||||
} else {
|
||||
$parseSep = $definition["parse_sep"] ?? null;
|
||||
if ($parseSep !== null) $params["parse_sep"] = $parseSep;
|
||||
$formatSep = $definition["format_sep"] ?? null;
|
||||
if ($formatSep !== null) $params["format_sep"] = $formatSep;
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
@ -43,8 +69,9 @@ class tarray extends _tstring {
|
||||
}
|
||||
|
||||
function parse(string $value) {
|
||||
$pattern = $this->params["split_pattern"] ?? static::SPLIT_PATTERN;
|
||||
return preg_split($pattern, $value);
|
||||
$sep = $this->params["parse_sep"] ?? static::DEFAULT_PARSE_SEP;
|
||||
if ($sep !== false) $value = str::split($sep, $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,7 +90,7 @@ class tarray extends _tstring {
|
||||
|
||||
function format($value, $format=null): string {
|
||||
if ($value === null) return "";
|
||||
$format ??= $this->params["format"] ?? static::FORMAT;
|
||||
$format ??= $this->params["format"] ?? static::DEFAULT_FORMAT_SEP;
|
||||
return implode($format, $value);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class CursorTest extends TestCase {
|
||||
function test_map_row() {
|
||||
$cursor = new class extends Cursor {
|
||||
function mapRow(array $row, ?array $map): array {
|
||||
return self::map_row($row, $map);
|
||||
return cl::map($row, $map);
|
||||
}
|
||||
};
|
||||
$row = ["a" => 1, "b" => 2, "c" => 3, "x" => 99];
|
||||
@ -32,7 +32,7 @@ class CursorTest extends TestCase {
|
||||
function test_filter_row() {
|
||||
$cursor = new class extends Cursor {
|
||||
function filterRow(array $row, $filter): bool {
|
||||
return self::filter_row($row, $filter);
|
||||
return cl::filter($row, $filter);
|
||||
}
|
||||
};
|
||||
$row = ["a" => 1, "b" => 2, "c" => 3, "x" => 99];
|
||||
|
56
tests/schema/types/arrayTest.php
Normal file
56
tests/schema/types/arrayTest.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace nulib\schema\types;
|
||||
|
||||
use nulib\schema\Schema;
|
||||
use nur\t\TestCase;
|
||||
|
||||
class arrayTest extends TestCase {
|
||||
function testSchema() {
|
||||
$value = " first second ";
|
||||
Schema::nw($value, null, "array");
|
||||
self::assertSame(["first", "second"], $value);
|
||||
|
||||
$value = " first second ";
|
||||
Schema::nw($value, null, [
|
||||
"array", "sep" => "spaces",
|
||||
]);
|
||||
self::assertSame(["first", "second"], $value);
|
||||
|
||||
$value = " first second ";
|
||||
Schema::nw($value, null, [
|
||||
"array",
|
||||
"sep" => "spaces", "trim" => false,
|
||||
]);
|
||||
self::assertSame(["", "first", "second", ""], $value);
|
||||
|
||||
$value = " first second ";
|
||||
Schema::nw($value, null, [
|
||||
"array",
|
||||
"sep" => "line",
|
||||
]);
|
||||
self::assertSame([" first second "], $value);
|
||||
|
||||
$value = " first second ";
|
||||
Schema::nw($value, null, [
|
||||
"array",
|
||||
"sep" => "line", "trim" => true,
|
||||
]);
|
||||
self::assertSame(["first second"], $value);
|
||||
}
|
||||
|
||||
function testxxx() {
|
||||
$value = " first second ";
|
||||
Schema::nw($value, null, [
|
||||
"array", "sep" => "spaces",
|
||||
]);
|
||||
self::assertSame(["first", "second"], $value);
|
||||
|
||||
# équivalent à...
|
||||
$value = " first second ";
|
||||
Schema::nw($value, null, [
|
||||
"type" => ["array" => [["sep" => "spaces"]]],
|
||||
"" => ["scalar"],
|
||||
]);
|
||||
self::assertSame(["first", "second"], $value);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user