modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2024-06-23 15:07:50 +04:00
parent 18b05b7ed7
commit b82a74418d
11 changed files with 328 additions and 43 deletions

View File

@ -3,6 +3,7 @@ namespace nur;
use nur\b\ui\IContent;
use nur\b\ui\IPrintable;
use nur\sery\php\content\c;
/**
* Class co: affichage générique de données formatées avec {@link c}

View File

@ -4,6 +4,7 @@ namespace nur\ref;
use nur\data\types\Metadata;
use nur\md;
use nur\sery\os\file\file;
use nur\sery\php\content\c;
/**
* Class ref_type: référence des types utilisables dans les schémas
@ -102,7 +103,7 @@ class ref_type {
/** comme {@link KEY} mais nullable */
const NKEY = "?".self::KEY;
/** comme {@link CONTENT} mais nullable */
/** comme {@link c} mais nullable */
const NCONTENT = "?".self::CONTENT;
/** comme {@link \nur\sery\FILE} mais nullable */

58
src/php/content/README.md Normal file
View File

@ -0,0 +1,58 @@
# nulib\php\content
un contenu est une liste de valeurs, avec une syntaxe pour que certains éléments
soient dynamiquement calculés.
le contenu final est résolu selon les règles suivantes:
- Si le contenu n'est pas un tableau:
- une chaine est quotée avec `htmlspecialchars()`
- un scalaire ou une instance d'objet sont pris tels quels
- Sinon, le contenu doit être un tableau, séquentiel ou associatif, ça n'a pas
d'incidence
- les éléments scalaires ou instance d'objets sont pris tels quels
- les tableaux représentent un traitement dynamique: appel de fonction,
instanciation, etc.
Les syntaxes possibles sont:
`[[], $args...]`
: merge littéral: les valeurs $args... sont insérées dans le
flux du contenu sans modification. c'est la seule façon d'insérer un tableau
dans la liste des valeurs
`["class_or_function", $args...]`
`[["class_or_function"], $args...]`
: instantiation ou appel de fonction
`["->method", $args...]`
`[["->method"], $args...]`
`[[null, "method"], $args...]`
: appel de méthode sur l'objet contexte spécifié lors de la résolution du contenu
`[[$object, "method"], $args...]`
: appel de méthode sur l'objet spécifié
`[["class", "method"], $args...]`
: appel de méthode statique de la classe spécifiée
Le fait de transformer un contenu en une liste de valeurs statiques s'appelle
la résolution. la résolution se fait par rapport à un objet contexte, qui est
utilisé lors des appels de méthodes.
## Affichage d'un contenu
Deux interfaces sont utilisées pour modéliser un élément de contenu à afficher:
- IContent: objet capable de produire du contenu
- IPrintable: objet capable d'afficher un contenu
Tous les autres éléments de contenus sont transformés en string avant affichage.
Un système de formatters permet de définir des fonctions ou méthodes à utiliser
pour formatter des objets de certains types.
Lors de l'affichage du contenu, deux éléments contigûs $a et $b sont affichés
séparés par un espace sauf si:
- $a se termine par `[> ]` OU
- $b commence par `[< ]`
- $a et $b sont dans une section littérale e.g `[[], $a, $b]`
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

163
src/php/content/c.php Normal file
View File

@ -0,0 +1,163 @@
<?php
namespace nur\sery\php\content;
use nur\sery\cl;
use nur\sery\php\func;
/**
* Class c: classe outil pour gérer du contenu
*/
class c {
/** s'assure que $c est un contenu. quoter $c si c'est une chaine */
static final function q($content): iterable {
if (is_string($content)) return [htmlspecialchars($content)];
elseif (is_iterable($content)) return $content;
elseif ($content === null || $content === false) return [];
else return [$content];
}
const q = [self::class, "q"];
/** s'assure que $c est un contenu. ne jamais quoter la valeur */
static final function nq($content): iterable {
if (is_iterable($content)) return $content;
elseif ($content === null || $content === false) return [];
else return [$content];
}
const nq = [self::class, "nq"];
/** résoudre le contenu, et retourner la liste des valeurs */
static final function resolve($content, $object_or_class=null, bool $quote=true, ?array &$dest=null): array {
if ($dest === null) $dest = [];
$content = $quote? self::q($content): self::nq($content);
$index = 0;
foreach ($content as $key => $value) {
if ($key === $index) {
$index++;
$seq = true;
} else {
$seq = false;
}
if (is_array($value)) {
# contenu dynamique
if (count($value) == 0) continue;
$func = cl::first($value);
$args = array_slice($value, 1);
if ($func === []) {
# merge statique
$sindex = 0;
foreach ($args as $skey => $arg) {
if ($skey === $sindex) {
$sindex++;
if ($seq) {
$dest[] = $arg;
} else {
# la première sous-clé séquentielle est ajoutée avec la clé du
# merge statique
$dest[$key] = $arg;
$seq = true;
}
} else {
$dest[$skey] = $arg;
}
}
continue;
} else {
# chaque argument est aussi un contenu
foreach ($args as &$arg) {
$array = is_array($arg);
$arg = self::resolve($arg, $object_or_class, false);
if (!$array) $arg = $arg[0];
}; unset($arg);
if (func::is_static($func)) {
func::ensure_func($func, $object_or_class, $args);
$value = func::call($func, ...$args);
} elseif (func::is_class($func)) {
func::fix_class_args($func, $args);
$value = func::cons($func, ...$args);
} else {
func::ensure_func($func, $object_or_class, $args);
$value = func::call($func, ...$args);
}
}
}
if ($seq) $dest[] = $value;
else $dest[$key] = $value;
}
return $dest;
}
const resolve = [self::class, "resolve"];
private static final function wend(?string $value): bool {
return $value !== null && preg_match('/(\w|\w\.)$/', $value);
}
private static final function startw(?string $value): bool {
return $value !== null && preg_match('/^\w/', $value);
}
private static final function to_values($content, ?array &$values=null): void {
$pvalue = cl::last($values);
$wend = self::wend($pvalue);
foreach ($content as $value) {
if ($value === null || $value === false) {
continue;
} elseif ($value instanceof IContent) {
self::to_values($value->getContent(), $values);
continue;
} elseif ($value instanceof IPrintable) {
ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
$value->print();
$value = ob_get_clean();
} else {
$value = strval($value);
#XXX rendre paramétrable le formatage de $value
}
if ($value !== "") {
$startw = self::startw($value);
if ($wend && $startw) $values[] = " ";
$values[] = $value;
$wend = self::wend($value);
}
}
}
/** écrire le contenu sur la resource spécifiée, qui vaut STDOUT par défaut */
static final function write($content, $fd=null, bool $resolve=true): void {
if ($resolve) $content = self::resolve($content);
$wend = false;
foreach ($content as $value) {
if ($value === null || $value === false) {
continue;
} elseif ($value instanceof IPrintable) {
if ($fd === null) {
$value->print();
} else {
ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
$value->print();
fwrite($fd, ob_get_clean());
}
$wend = false;
continue;
} elseif ($value instanceof IContent) {
$values = [];
self::to_values($content, $values);
$value = implode("", $values);
} else {
$value = strval($value);
#XXX rendre paramétrable le formattage de $value
}
$startw = self::startw($value);
if (!$wend && !$startw) $value = " $value";
if ($fd === null) echo $value;
else fwrite($fd, $value);
$wend = self::wend($value);
}
}
/** retourner le contenu sous forme de chaine */
static final function to_string($content, bool $resolve=true): string {
if ($resolve) $content = self::resolve($content);
$values = [];
self::to_values($content, $values);
return implode("", $values);
}
}

View File

@ -4,7 +4,6 @@ namespace nur\sery\php;
use Closure;
use nur\sery\cl;
use nur\sery\ref\php\ref_func;
use nur\sery\schema\Schema;
use nur\sery\ValueException;
use ReflectionClass;
use ReflectionFunction;

View File

@ -0,0 +1,71 @@
# nulib\web\content
un contenu web est un type spécial de contenu permettant de gérer aussi les
attributs avec une syntaxe particulière.
les règles diffèrent légèrement:
- Si le contenu n'est pas un tableau:
- une chaine est quotée avec `htmlspecialchars()`
- un scalaire ou une instance d'objet sont pris tels quels
- Sinon, le contenu doit être un tableau, séquentiel ou associatif
- les éléments séquentiels scalaires ou instance d'objets sont pris tels quels
- les éléments associatifs sont des attributs, et sont fusionnés le cas échéant
- les éléments séquentiels de type tableau séqentiel sont du contenu dynamique
- les éléments séquentiels de type tableau associatif sont évalues
récursivement avec les mêmes règles (sauf la partie chaine quotée avec
`htmlspecialchars()`)
Par exemple, les deux contenus web suivants sont équivalents:
~~~php
# ce contenu:
$content1 = [
"before",
"class" => "first",
["class" => "second"],
["func", "arg"],
"after",
];
# donne le même résultat que:
$content2 = [
"class" => "first second",
"before",
func("arg"),
"after",
];
~~~
lors de la définition d'attributs,
- un tableau séquentiel est un appel de fonction
- un tableau associatif permet de faire des définitions conditionnelles
Par exemple, les deux contenus web suivants sont équivalents:
~~~php
# ce contenu:
$content1 = [
["class" => ["func", "arg"]],
];
# donne le même résultat que:
$content2 = [
"class" => func("arg"),
];
~~~
De même, les deux contenus web suivants sont équivalents:
~~~php
# si $is_primary && !$is_danger
# alors ce contenu:
$content1 = [
["class" => [
"btn",
"btn-primary" => $is_primary,
"btn-danger" => $is_danger,
"btn-default" => !$is_primary && !$is_danger,
]],
];
# donne le même résultat que:
$content2 = [
"class" => "btn btn-primary",
];
~~~
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

View File

@ -0,0 +1,8 @@
<?php
namespace nur\sery\wip\web\content;
/**
* Class v: classe outil pour gérer du contenu pour le web
*/
class v {
}

View File

@ -5,27 +5,23 @@ use nur\sery\php\content\impl\html;
use PHPUnit\Framework\TestCase;
class contentTest extends TestCase {
function testFlattern() {
self::assertSame([], content::flatten(null));
self::assertSame([], content::flatten([null]));
self::assertSame([""], content::flatten(""));
self::assertSame([""], content::flatten([""]));
self::assertSame(["a", "b", "c", "d"], content::flatten(["a", ["b", ["c"], "d"]]));
}
function testResolve_static() {
self::assertSame([], content::resolve_static(null, null));
self::assertSame([], content::resolve_static([null], [null]));
self::assertSame([""], content::resolve_static("", ""));
self::assertSame([""], content::resolve_static([""], [""]));
self::assertSame(["&lt;quoted&gt;"], content::resolve_static("<quoted>", "<quoted>"));
self::assertSame(["<non-quoted>"], content::resolve_static(["<non-quoted>"], ["<non-quoted>"]));
function testTo_string() {
self::assertSame("", c::to_string(null));
self::assertSame("", c::to_string(false));
self::assertSame("pouet&lt;q/&gt;", c::to_string("pouet<q/>"));
self::assertSame("pouet<q/>", c::to_string(["pouet<q/>"]));
self::assertSame("hello world", c::to_string(["hello", "world"]));
self::assertSame("hello1 world", c::to_string(["hello1", "world"]));
self::assertSame("hello<world>", c::to_string(["hello", "<world>"]));
self::assertSame("<hello>world", c::to_string(["<hello>", "world"]));
self::assertSame("hello,world", c::to_string(["hello,", "world"]));
self::assertSame("hello&nbsp;world", c::to_string(["hello&nbsp;", "world"]));
self::assertSame("hello. world", c::to_string(["hello.", "world"]));
self::assertSame("hello.<world>", c::to_string(["hello.", "<world>"]));
self::assertSame(
"<h1>title&lt;q/&gt;</h1><p>hello<nq/><span>brave&lt;q/&gt;</span><span>world<nq/></span></p>",
content::to_string([
c::to_string([
[html::H1, "title<q/>"],
[html::P, [
"hello<nq/>",

View File

@ -1,12 +0,0 @@
<?php
namespace nur\sery\php\content\impl;
use nur\sery\php\content\IStaticContent;
class AStaticContent implements IStaticContent {
function getContent(): iterable {
return [
[html::P, "static content"],
];
}
}

View File

@ -1,22 +1,22 @@
<?php
namespace nur\sery\php\content\impl;
use nur\sery\php\content\content;
use nur\sery\php\content\IStaticContent;
use nur\sery\php\content\c;
use nur\sery\php\content\IContent;
class ATag implements IStaticContent {
function __construct(string $tag, $contents=null) {
class ATag implements IContent {
function __construct(string $tag, $content=null) {
$this->tag = $tag;
$this->contents = $contents;
$this->content = $content;
}
protected $tag;
protected $contents;
protected $content;
function getContent(): iterable {
return [
"<$this->tag>",
...content::quote($this->contents),
...c::q($this->content),
"</$this->tag>",
];
}

View File

@ -7,8 +7,8 @@ class html {
const P = [self::class, "p"];
const SPAN = [self::class, "span"];
static function h1($contents) { return new ATag("h1", $contents); }
static function div($contents) { return new ATag("div", $contents); }
static function p($contents) { return new ATag("p", $contents); }
static function span($contents) { return new ATag("span", $contents); }
static function h1($content) { return new ATag("h1", $content); }
static function div($content) { return new ATag("div", $content); }
static function p($content) { return new ATag("p", $content); }
static function span($content) { return new ATag("span", $content); }
}