diff --git a/src/db/Capacitor.php b/src/db/Capacitor.php index 883d3a8..e38c851 100644 --- a/src/db/Capacitor.php +++ b/src/db/Capacitor.php @@ -6,9 +6,10 @@ namespace nur\sery\db; * instance de {@link CapacitorStorage} */ class Capacitor { - function __construct(CapacitorStorage $storage, CapacitorChannel $channel) { + function __construct(CapacitorStorage $storage, CapacitorChannel $channel, bool $ensureExists=true) { $this->storage = $storage; $this->channel = $channel; + if ($ensureExists) $this->ensureExists(); } /** @var CapacitorStorage */ @@ -21,14 +22,22 @@ class Capacitor { return $this->storage->_exists($this->channel); } + function ensureExists(): void { + $this->storage->_ensureExists($this->channel); + } + function reset(): void { $this->storage->_reset($this->channel); } - function charge($item, ?callable $func=null, ?array $args=null): bool { + function charge($item, ?callable $func=null, ?array $args=null): int { return $this->storage->_charge($this->channel, $item, $func, $args); } + function count($filter=null): int { + return $this->storage->_count($this->channel, $filter); + } + function discharge($filter=null, ?bool $reset=null): iterable { return $this->storage->_discharge($this->channel, $filter, $reset); } @@ -37,8 +46,8 @@ class Capacitor { return $this->storage->_get($this->channel, $filter); } - function each($filter, ?callable $func=null, ?array $args=null): void { - $this->storage->_each($this->channel, $filter, $func, $args); + function each($filter, ?callable $func=null, ?array $args=null): int { + return $this->storage->_each($this->channel, $filter, $func, $args); } function close(): void { diff --git a/src/db/CapacitorStorage.php b/src/db/CapacitorStorage.php index d9428b1..0e3bd39 100644 --- a/src/db/CapacitorStorage.php +++ b/src/db/CapacitorStorage.php @@ -15,6 +15,13 @@ abstract class CapacitorStorage { return $this->_exists($this->getChannel($channel)); } + abstract function _ensureExists(CapacitorChannel $channel): void; + + /** s'assurer que le canal spécifié existe */ + function ensureExists(?string $channel): void { + $this->_ensureExists($this->getChannel($channel)); + } + abstract function _reset(CapacitorChannel $channel): void; /** supprimer le canal spécifié */ @@ -22,7 +29,7 @@ abstract class CapacitorStorage { $this->_reset($this->getChannel($channel)); } - abstract function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): bool; + abstract function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int; /** * charger une valeur dans le canal @@ -33,13 +40,20 @@ abstract class CapacitorStorage { * Si la fonction retourne un tableau, il est utilisé pour modifier les valeurs * insérées/mises à jour * - * @return true si l'objet a été chargé ou mis à jour, false s'il existait + * @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait * déjà à l'identique dans le canal */ - function charge(?string $channel, $item, ?callable $func=null, ?array $args=null): bool { + function charge(?string $channel, $item, ?callable $func=null, ?array $args=null): int { return $this->_charge($this->getChannel($channel), $item, $func, $args); } + abstract function _count(CapacitorChannel $channel, $filter): int; + + /** indiquer le nombre d'éléments du canal spécifié */ + function count(?string $channel, $filter=null): int { + return $this->_count($this->getChannel($channel), $filter); + } + abstract function _discharge(CapacitorChannel $channel, $filter, ?bool $reset): iterable; /** décharger les données du canal spécifié */ @@ -58,7 +72,7 @@ abstract class CapacitorStorage { return $this->_get($this->getChannel($channel), $filter); } - abstract function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): void; + abstract function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int; /** * appeler une fonction pour chaque élément du canal spécifié. @@ -67,9 +81,11 @@ abstract class CapacitorStorage { * * $func est appelé avec la signature ($item, $row, ...$args). si la fonction * retourne un tableau, il est utilisé pour mettre à jour la ligne + * + * @return int le nombre de lignes parcourues */ - function each(?string $channel, $filter, ?callable $func=null, ?array $args=null): void { - $this->_each($this->getChannel($channel), $filter, $func, $args); + function each(?string $channel, $filter, ?callable $func=null, ?array $args=null): int { + return $this->_each($this->getChannel($channel), $filter, $func, $args); } abstract function close(): void; diff --git a/src/db/sqlite/SqliteCapacitor.php b/src/db/sqlite/SqliteCapacitor.php index cc03e96..8246397 100644 --- a/src/db/sqlite/SqliteCapacitor.php +++ b/src/db/sqlite/SqliteCapacitor.php @@ -68,6 +68,10 @@ class SqliteCapacitor extends CapacitorStorage { return $tableName !== null; } + function _ensureExists(CapacitorChannel $channel): void { + $this->_create($channel); + } + function _reset(CapacitorChannel $channel): void { $this->sqlite->exec([ "drop table if exists", @@ -76,7 +80,7 @@ class SqliteCapacitor extends CapacitorStorage { $channel->setCreated(false); } - function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): bool { + function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int { $this->_create($channel); $now = date("Y-m-d H:i:s"); $_item = serialize($item); @@ -129,7 +133,7 @@ class SqliteCapacitor extends CapacitorStorage { if ($insert === null) { # aucune modification - return false; + return 0; } elseif ($insert) { $this->sqlite->exec([ "insert", @@ -144,7 +148,16 @@ class SqliteCapacitor extends CapacitorStorage { "where" => ["_id" => $id], ]); } - return true; + return 1; + } + + function _count(CapacitorChannel $channel, $filter): int { + if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter]; + return $this->sqlite->get([ + "select count(*)", + "from" => $channel->getTableName(), + "where" => $filter, + ]); } function _discharge(CapacitorChannel $channel, $filter, ?bool $reset): iterable { @@ -174,13 +187,14 @@ class SqliteCapacitor extends CapacitorStorage { else return unserialize($row["_item"]); } - function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): void { + function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int { if ($func === null) $func = [$channel, "onEach"]; $onEach = func::_prepare($func); if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter]; $sqlite = $this->sqlite; $tableName = $channel->getTableName(); $commited = false; + $count = 0; $sqlite->beginTransaction(); try { $rows = $sqlite->all([ @@ -203,9 +217,11 @@ class SqliteCapacitor extends CapacitorStorage { "where" => ["_id" => $row["_id"]], ]); } + $count++; } $sqlite->commit(); $commited = true; + return $count; } finally { if (!$commited) $sqlite->rollback(); } diff --git a/src/output/IMessenger.php b/src/output/IMessenger.php index a82b026..572cfbb 100644 --- a/src/output/IMessenger.php +++ b/src/output/IMessenger.php @@ -70,6 +70,17 @@ interface IMessenger { */ function adone($content=null, ?int $overrideLevel=null): void; + /** + * terminer l'action courante avec le résultat "succès", "échec" ou "neutre" + * en fonction de la valeur de $result + * - si c'est un booléen, true vaut succès, false vaut échec + * - si c'est une exception, c'est un échec et le message est affiché + * - sinon, le résultat est neutre et le message est affiché s'il n'est pas null + * + * démarrer une action le cas échéant (et la terminer aussitôt) + */ + function aresult($result=null, ?int $overrideLevel=null): void; + /** afficher une donnée non structurée */ function print($content, ?int $level=null): void; diff --git a/src/output/TODO.md b/src/output/TODO.md index 3a4e60a..01e7113 100644 --- a/src/output/TODO.md +++ b/src/output/TODO.md @@ -4,8 +4,22 @@ rotation des logs * [ ] lors de la rotation, si l'ouverture du nouveau fichier échoue, continuer à écrire dans l'ancien fichier + * ou alors un moyen pour ré-ouvrir la sortie, afin de pouvoir indiquer à un + long running process qu'une rotation a eu lieu * [ ] dans `StdMessenger::resetParams()`, `[output]` peut être une instance de StdOutput pour mettre à jour $out ET $err, ou un tableau de deux éléments pour mettre à jour séparément $out et $err +* [ ] vérifier que la date affichée pour un TITLE est celle à laquelle l'appel + a été fait, même si le premier événement en dessous arrive bien plus tard +* [ ] pareil pour action: sauf si c'est une seule ligne, la date de action est + la date du premier appel, alors que la date de $result est celui du result si + c'est affiché sur une autre ligne +* réorganiser pour que msg:: attaque un proxy dans lequel est configuré un + ensemble standard de sorties: say, log, debuglog + * `--aD, --av, --aq, --asilent` pour les valeurs d'ajustement qui sont un + incrément à la valeur courante (+2, +1, -1, -2) + * `--yD, --yv, --yq, --ysilent, -D, -v, -q, --silent` pour say + * `--lD, --lv, --lq, --lsilent` pour log, `-L:, --L` l'active + * `--DD, --Dv, --Dq, --Dsilent` pour debuglog, `--DL:` l'active -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/src/output/_messenger.php b/src/output/_messenger.php index 3dfaf52..2fc26ca 100644 --- a/src/output/_messenger.php +++ b/src/output/_messenger.php @@ -62,6 +62,7 @@ abstract class _messenger { static function asuccess($content=null, ?int $override_level=null): void { static::get()->asuccess($content, $override_level); } static function afailure($content=null, ?int $override_level=null): void { static::get()->afailure($content, $override_level); } static function adone($content=null, ?int $override_level=null): void { static::get()->adone($content, $override_level); } + static function aresult($result=null, ?int $override_level=null): void { static::get()->aresult($result, $override_level); } static function print($content, ?int $level=null): void { static::get()->print($content, $level); } static function info($content, ?int $level=null): void { static::get()->info($content, $level); } static function note($content, ?int $level=null): void { static::get()->note($content, $level); } diff --git a/src/output/std/ProxyMessenger.php b/src/output/std/ProxyMessenger.php index 08c07ab..6b61a9c 100644 --- a/src/output/std/ProxyMessenger.php +++ b/src/output/std/ProxyMessenger.php @@ -85,12 +85,11 @@ class ProxyMessenger implements IMessenger { if ($useFunc && $func !== null) { try { $result = $func($this); - if ($result !== null) { - /** @var _IMessenger $msg */ - foreach ($this->msgs as $msg) { - if ($result === true) $msg->asuccess(); - elseif ($result === false) $msg->afailure(); - else $msg->adone($result); + /** @var _IMessenger $msg */ + $index = 0; + foreach ($this->msgs as $msg) { + if ($msg->_getActionMark() > $untils[$index++]) { + $msg->aresult($result); } } } catch (Exception $e) { @@ -112,6 +111,7 @@ class ProxyMessenger implements IMessenger { function asuccess($content=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->asuccess($content, $overrideLevel); } } function afailure($content=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->afailure($content, $overrideLevel); } } function adone($content=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->adone($content, $overrideLevel); } } + function aresult($result=null, ?int $overrideLevel=null): void { foreach ($this->msgs as $msg) { $msg->aresult($result, $overrideLevel); } } function print($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->print($content, $level); } } function info($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->info($content, $level); } } function note($content, ?int $level=null): void { foreach ($this->msgs as $msg) { $msg->note($content, $level); } } diff --git a/src/output/std/StdMessenger.php b/src/output/std/StdMessenger.php index 250439c..fd2ba12 100644 --- a/src/output/std/StdMessenger.php +++ b/src/output/std/StdMessenger.php @@ -580,10 +580,8 @@ class StdMessenger implements _IMessenger { if ($func !== null) { try { $result = $func($this); - if ($result !== null) { - if ($result === true) $this->asuccess(); - elseif ($result === false) $this->afailure(); - else $this->adone($result); + if ($this->_getActionMark() > $until) { + $this->aresult($result); } } catch (Exception $e) { $this->afailure($e); @@ -653,6 +651,14 @@ class StdMessenger implements _IMessenger { $this->printActions(true, $overrideLevel); } + function aresult($result=null, ?int $overrideLevel=null): void { + if (!$this->actions) $this->action(null); + if ($result === true) $this->asuccess(null, $overrideLevel); + elseif ($result === false) $this->afailure(null, $overrideLevel); + elseif ($result instanceof Exception) $this->afailure($result, $overrideLevel); + else $this->adone($result, $overrideLevel); + } + function _endAction(?int $until=null): void { if ($until === null) $until = $this->_getActionMark() - 1; while (count($this->actions) > $until) { diff --git a/src/text/Word.php b/src/text/Word.php new file mode 100644 index 0000000..f7506ac --- /dev/null +++ b/src/text/Word.php @@ -0,0 +1,212 @@ +fem = $fem; + $this->le = $le; + $this->du = $du; + $this->au = $au; + $this->w = $spec; + } + + /** + * retourner le mot sans article + * + * @param bool|int $amount nombre du nom, avec l'équivalence false===0 et + * true===2. à partir de 2, le mot est ecrit au pluriel + * @param bool|string $fem genre du nom avec lequel accorder les adjectifs, + * avec l'équivalence false==="M" et true==="F" + */ + function w($amount=1, bool $upper1=false, $fem=false): string { + if ($amount === true) $amount = 2; + elseif ($amount === false) $amount = 0; + $amount = abs($amount); + $w = $this->w; + # marque du nombre + if ($amount <= 1) { + $w = preg_replace('/#[sx]/', "", $w); + } else { + $w = preg_replace('/#([sx])/', "$1", $w); + } + # marque du genre + if ($fem === "f" || $fem === "F") $fem = true; + elseif ($fem === "m" || $fem === "M") $fem = false; + $repl = $fem? "$1": ""; + $w = preg_replace('/#([e])/', $repl, $w); + # mise en majuscule + if ($upper1) { + if (strpos($w, "^") === false) { + # uniquement la première lettre + $w = txt::upper1($w); + } else { + # toutes les lettres qui suivent les occurences de ^ + $w = preg_replace_callback('/\^([[:alpha:]])/u', function ($ms) { + return mb_strtoupper($ms[1]); + }, $w); + } + } + return $w; + } + + /** + * retourner le mot sans article avec la première lettre en majuscule. + * alias pour $this->w($amount, true, $fem) + * + * @param bool|int $amount + */ + function u($amount=1, $fem=false): string { + return $this->w($amount, true, $fem); + } + + /** + * retourner l'adjectif accordé avec le genre spécifié. + * alias pour $this->w($amount, false, $fem) + * + * @param bool|int $amount + */ + function a($fem=false, $amount=1): string { + return $this->w($amount, false, $fem); + } + + /** retourner le mot sans article et avec la quantité */ + function q(int $amount=1, $fem=false): string { + return $amount." ".$this->w($amount, $fem); + } + + /** retourner le mot sans article et avec la quantité $amount/$max */ + function r(int $amount, int $max, $fem=false): string { + return "$amount/$max ".$this->w($amount, $fem); + } + + /** retourner le mot avec l'article indéfini et la quantité */ + function un(int $amount=1, $fem=false): string { + $abs_amount = abs($amount); + if ($abs_amount == 0) { + $aucun = $this->fem? "aucune ": "aucun "; + return $aucun.$this->w($amount, $fem); + } elseif ($abs_amount == 1) { + $un = $this->fem? "une ": "un "; + return $un.$this->w($amount, $fem); + } else { + return "les $amount ".$this->w($amount, $fem); + } + } + + function le(int $amount=1, $fem=false): string { + $abs_amount = abs($amount); + if ($abs_amount == 0) { + $le = $this->fem? "la 0 ": "le 0 "; + return $le.$this->w($amount, $fem); + } elseif ($abs_amount == 1) { + return $this->le.$this->w($amount, $fem); + } else { + return "les $amount ".$this->w($amount, $fem); + } + } + + function du(int $amount=1, $fem=false): string { + $abs_amount = abs($amount); + if ($abs_amount == 0) { + $du = $this->fem? "de la 0 ": "du 0 "; + return $du.$this->w($amount, $fem); + } elseif ($abs_amount == 1) { + return $this->du.$this->w($amount, $fem); + } else { + return "des $amount ".$this->w($amount, $fem); + } + } + + function au(int $amount=1, $fem=false): string { + $abs_amount = abs($amount); + if ($abs_amount == 0) { + $au = $this->fem? "à la 0 ": "au 0 "; + return $au.$this->w($amount, $fem); + } elseif ($abs_amount == 1) { + return $this->au.$this->w($amount, $fem); + } else { + return "aux $amount ".$this->w($amount, $fem); + } + } +} diff --git a/src/text/words.php b/src/text/words.php new file mode 100644 index 0000000..44691b5 --- /dev/null +++ b/src/text/words.php @@ -0,0 +1,14 @@ +q($count); + } + + static function r(int $count, int $max, string $spec, bool $adjective=true): string { + $word = new Word($spec, $adjective); + return $word->r($count, $max); + } +} diff --git a/tbin/legacy-test-nested-actions.php b/tbin/legacy-test-nested-actions.php index fd8bbf7..9c69379 100755 --- a/tbin/legacy-test-nested-actions.php +++ b/tbin/legacy-test-nested-actions.php @@ -30,5 +30,51 @@ Application::run(new class extends Application { }); $msg->asuccess(); }); + + msg::title("auto result"); + msg::action("sans result 1", function ($msg) { + return true; + }); + msg::print("hello 1"); + + msg::action("sans result 2", function ($msg) { + return false; + }); + msg::print("hello 2"); + + msg::action("sans result 3", function ($msg) { + return "whatever"; + }); + msg::print("hello 3"); + + try { + msg::action("sans result 4", function ($msg) { + throw new Exception(); + }); + } catch (Exception $e) {} + msg::print("hello 4"); + + msg::action("avec result 1", function ($msg) { + $msg->asuccess(); + }); + msg::print("hello 1"); + + msg::action("avec result 2", function ($msg) { + $msg->afailure(); + }); + msg::print("hello 2"); + + msg::action("avec result 3", function ($msg) { + $msg->adone(); + }); + msg::print("hello 3"); + + try { + msg::action("avec result 4", function ($msg) { + $msg->aresult(new Exception()); + }); + } catch (Exception $e) {} + msg::print("hello 4"); + msg::end(); } });