Compare commits

...

11 Commits

14 changed files with 344 additions and 259 deletions

14
.idea/php.xml generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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);
}
}