début StdLogMessenger

This commit is contained in:
Jephté Clain 2025-10-08 16:42:37 +04:00
parent 8974cf09a1
commit 903014e997
6 changed files with 621 additions and 339 deletions

View File

@ -0,0 +1,5 @@
<?php
namespace nulib\output\std;
class JsonLogMessenger implements _IMessenger {
}

View File

@ -0,0 +1,259 @@
<?php
namespace nulib\output\std;
use Exception;
use nulib\cl;
use nulib\output\IMessenger;
class StdLogMessenger implements _IMessenger {
use _TMessenger;
function __construct(?array $params=null) {
$output = $params["output"] ?? null;
$color = $params["color"] ?? null;
$indent = $params["indent"] ?? static::INDENT;
$defaultLevel = $params["default_level"] ?? null;
$defaultLevel = self::verifix_level($defaultLevel ?? self::NORMAL);
$debug = boolval($params["debug"] ?? null);
$minLevel = $params["min_level"] ?? null;
if ($debug) $minLevel ??= self::DEBUG;
$minLevel ??= $params["verbosity"] ?? null; # alias
$minLevel = self::verifix_level($minLevel ?? self::NORMAL, self::NONE);
$addDate = boolval($params["add_date"] ?? null);
$dateFormat = cl::get($params, "date_format", static::DATE_FORMAT);
$id = $params["id"] ?? null;
$this->out = new StdOutput($output ?? STDERR, [
"color" => $color,
"indent" => $indent,
]);
$this->defaultLevel = $defaultLevel;
$this->minLevel = $minLevel;
$this->addDate = $addDate;
$this->dateFormat = $dateFormat;
$this->id = $id;
}
function resetParams(?array $params=null): void {
$output = $params["output"] ?? null;
$color = $params["color"] ?? null;
$indent = $params["indent"] ?? null;
$defaultLevel = $params["default_level"] ?? null;
if ($defaultLevel !== null) $defaultLevel = self::verifix_level($defaultLevel);
$debug = $params["debug"] ?? null;
$minLevel = $params["min_level"] ?? null;
if ($debug !== null) $minLevel ??= self::DEBUG;
$minLevel ??= $params["verbosity"] ?? null; # alias
if ($minLevel !== null) $minLevel = self::verifix_level($minLevel, self::NONE);
$addDate = $params["add_date"] ?? null;
$dateFormat = $params["date_format"] ?? null;
$id = $params["id"] ?? null;
$this->out->resetParams([
"output" => $output,
"color" => $color,
"indent" => $indent,
]);
if ($defaultLevel !== null) $this->defaultLevel = $defaultLevel;
if ($minLevel !== null) $this->minLevel = $minLevel;
if ($addDate !== null) $this->addDate = boolval($addDate);
if ($dateFormat !== null) $this->dateFormat = $dateFormat;
if ($id !== null) $this->id = $id;
}
function clone(?array $params=null): IMessenger {
$clone = clone $this;
if ($params !== null) $clone->resetParams($params);
return $clone;
}
/** @var StdOutput la sortie standard */
protected StdOutput $out;
/** @var int level par défaut dans lequel les messages sont affichés */
protected int $defaultLevel;
/** @var int level minimum que doivent avoir les messages pour être affichés */
protected int $minLevel;
/** @var bool faut-il ajouter la date à chaque ligne? */
protected bool $addDate;
/** @var string format de la date */
protected string $dateFormat;
/** @var ?string identifiant de ce messenger, à ajouter à chaque ligne */
protected ?string $id;
function section($content, ?callable $func=null, ?int $level=null): void {
$this->_endSection();
if (!$this->checkLevel($level)) return;
$this->_printTitle(
$this->getLinePrefix(), $level,
"section", $content,
0, $this->out);
if ($func !== null) {
try {
$func($this);
} finally {
$this->_endSection();
}
}
}
function _endSection(): void {
$this->end(true);
}
protected int $titleLevel = 0;
function _getTitleMark(): int {
return $this->titleLevel;
}
function title($content, ?callable $func=null, ?int $level=null): void {
if (!$this->checkLevel($level)) return;
$until = $this->_getTitleMark();
$this->_printTitle(
$this->getLinePrefix(), $level,
"title", $content,
$this->titleLevel++, $this->out);
if ($func !== null) {
try {
$func($this);
} finally {
$this->_endTitle($until);
}
}
}
function desc($content, ?int $level=null): void {
$this->_printGeneric(
$this->getLinePrefix(), $level,
"desc", $content,
$this->titleLevel - 1, $this->out);
}
function _endTitle(?int $until=null): void {
$until ??= $this->_getTitleMark();
$this->titleLevel = $until;
}
protected int $actionLevel = 0;
protected array $actionLevels = [];
function _getActionMark(): int {
return $this->actionLevel;
}
function action($content, ?callable $func=null, ?int $level=null): void {
$this->checkLevel($level);
$until = $this->_getActionMark();
$this->_printAction(
$this->getLinePrefix(), $level,
true, $content,
false, null, null,
$this->actionLevel++, $this->out);
if ($func !== null) {
try {
$result = $func($this);
if ($this->_getActionMark() > $until) {
$this->aresult($result);
}
} catch (Exception $e) {
$this->afailure($e);
throw $e;
} finally {
$this->_endAction($until);
}
}
}
function step($content, ?int $level=null): void {
$this->_printGenericOrException($level, "step", $content, $this->getIndentLevel(), $this->out);
}
function asuccess($content=null, ?int $overrideLevel=null): void {
if ($this->actionLevel == 0) $this->action(null);
$this->_printAction(
$this->getLinePrefix(), $level,
false, null,
true, true, $content,
$this->actionLevel, $this->out);
$this->_endAction();
}
function afailure($content=null, ?int $overrideLevel=null): void {
if ($this->actionLevel == 0) $this->action(null);
$this->_printAction(
$this->getLinePrefix(), $level,
false, null,
true, false, $content,
$this->actionLevel, $this->out);
$this->_endAction();
}
function adone($content=null, ?int $overrideLevel=null): void {
if ($this->actionLevel == 0) $this->action(null);
$this->_printAction(
$this->getLinePrefix(), $level,
false, null,
true, null, $content,
$this->actionLevel, $this->out);
$this->_endAction();
}
function aresult($result=null, ?int $overrideLevel=null): void {
if ($this->actionLevel == 0) $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 {
$until ??= $this->_getActionMark();
$this->actionLevel = $until;
}
function print($content, ?int $level=null): void {
$this->_printGenericOrException($level, "print", $content, $this->getIndentLevel(), $this->out);
}
function info($content, ?int $level=null): void {
$this->_printGenericOrException($level, "info", $content, $this->getIndentLevel(), $this->err);
}
function note($content, ?int $level=null): void {
$this->_printGenericOrException($level, "note", $content, $this->getIndentLevel(), $this->err);
}
function warning($content, ?int $level=null): void {
$this->_printGenericOrException($level, "warning", $content, $this->getIndentLevel(), $this->err);
}
function error($content, ?int $level=null): void {
$this->_printGenericOrException($level, "error", $content, $this->getIndentLevel(), $this->err);
}
function end(bool $all=false): void {
if ($all) {
while ($this->actionLevel > 0) $this->adone();
while ($this->titleLevel > 0) $this->_endTitle();
$this->_endSection();
} elseif ($this->actionLevel > 0) {
$this->_endAction();
} elseif ($this->titleLevel > 0) {
$this->_endTitle();
}
}
}

View File

@ -10,83 +10,7 @@ use nulib\output\IMessenger;
use Throwable;
class StdMessenger implements _IMessenger {
const INDENT = " ";
const DATE_FORMAT = 'Y-m-d\TH:i:s.u';
const VALID_LEVELS = [self::DEBUG, self::MINOR, self::NORMAL, self::MAJOR, self::NONE];
const LEVEL_MAP = [
"debug" => self::DEBUG,
"minor" => self::MINOR, "verbose" => self::MINOR,
"normal" => self::NORMAL,
"major" => self::MAJOR, "quiet" => self::MAJOR,
"none" => self::NONE, "silent" => self::NONE,
];
protected static function verifix_level($level, int $max_level=self::MAX_LEVEL): int {
if (!in_array($level, self::VALID_LEVELS, true)) {
$level = cl::get(self::LEVEL_MAP, $level, $level);
}
if (!in_array($level, self::VALID_LEVELS, true)) {
throw new Exception("$level: invalid level");
}
if ($level > $max_level) {
throw new Exception("$level: level not allowed here");
}
return $level;
}
const GENERIC_PREFIXES = [
self::MAJOR => [
"section" => [true, "SECTION!", "===", "<color @b>=", "=</color>", "==="],
"title" => [false, "TITLE!", null, "<color @b>T", "</color>", "==="],
"desc" => ["DESC!", "<color @b>></color>", ""],
"error" => ["CRIT.ERROR!", "<color @r>E!", "</color>"],
"warning" => ["CRIT.WARNING!", "<color @y>W!", "</color>"],
"note" => ["ATTENTION!", "<color @g>N!", "</color>"],
"info" => ["IMPORTANT!", "<color @b>N!", "</color>"],
"step" => ["*", "<color @w>.</color>", ""],
"print" => [null, null, null],
],
self::NORMAL => [
"section" => [true, "SECTION:", "---", "<color @b>-", "-</color>", "---"],
"title" => [false, "TITLE:", null, "<color @b>T</color><color b>", "</color>", "---"],
"desc" => ["DESC:", "<color @b>></color>", ""],
"error" => ["ERROR:", "<color @r>E</color><color r>", "</color>"],
"warning" => ["WARNING:", "<color @y>W</color><color y>", "</color>"],
"note" => ["NOTE:", "<color @g>N</color>", ""],
"info" => ["INFO:", "<color @b>I</color>", ""],
"step" => ["*", "<color @w>.</color>", ""],
"print" => [null, null, null],
],
self::MINOR => [
"section" => [true, "section", null, "<color @w>>>", "<<</color>", null],
"title" => [false, "title", null, "<color b>t", "</color>", null],
"desc" => ["desc", "<color b>></color>", ""],
"error" => ["error", "<color r>E</color><color -r>", "</color>"],
"warning" => ["warning", "<color y>W</color><color -y>", "</color>"],
"note" => ["note", "<color g>N</color>", ""],
"info" => ["info", "<color b>I</color><color w>", "</color>"],
"step" => ["*", "<color w>.</color>", ""],
"print" => [null, null, null],
],
self::DEBUG => [
"section" => [true, "section", null, "<color @w>>>", "<<</color>", null],
"title" => [false, "title", null, "<color b>t", "</color>", null],
"desc" => ["desc", "<color b>></color>", ""],
"error" => ["debugE", "<color r>e</color><color -r>", "</color>"],
"warning" => ["debugW", "<color y>w</color><color -y>", "</color>"],
"note" => ["debugN", "<color b>i</color>", ""],
"info" => ["debug", "<color @w>D</color><color w>", "</color>"],
"step" => ["*", "<color w>.</color>", ""],
"print" => [null, null, null],
],
];
const RESULT_PREFIXES = [
"failure" => ["(FAILURE)", "<color r>✘</color>"],
"success" => ["(SUCCESS)", "<color @g>✔</color>"],
"done" => [null, null],
];
use _TMessenger;
function __construct(?array $params=null) {
$output = cl::get($params, "output");
@ -200,255 +124,6 @@ class StdMessenger implements _IMessenger {
/** @var ?string identifiant de ce messenger, à ajouter à chaque ligne */
protected $id;
protected function getLinePrefix(): ?string {
$linePrefix = null;
if ($this->addDate) {
$date = date_create()->format($this->dateFormat);
$linePrefix .= "$date ";
}
if ($this->id !== null) {
$linePrefix .= "$this->id ";
}
return $linePrefix;
}
protected function decrLevel(int $level, int $amount=-1): int {
$level += $amount;
if ($level < self::MIN_LEVEL) $level = self::MIN_LEVEL;
return $level;
}
protected function checkLevel(?int &$level): bool {
if ($level === null) $level = $this->defaultLevel;
elseif ($level < 0) $level = $this->decrLevel($this->defaultLevel, $level);
return $level >= $this->minLevel;
}
protected function getIndentLevel(bool $withActions=true): int {
$indentLevel = count($this->titles) - 1;
if ($indentLevel < 0) $indentLevel = 0;
if ($withActions) {
foreach ($this->actions as $action) {
if ($action["level"] < $this->minLevel) continue;
$indentLevel++;
}
}
return $indentLevel;
}
protected function _printTitle(
?string $linePrefix, int $level,
string $type, $content,
int $indentLevel, StdOutput $out
): void {
$prefixes = self::GENERIC_PREFIXES[$level][$type];
if ($prefixes[0]) $out->print();
$content = cl::with($content);
if ($out->isColor()) {
$before = $prefixes[2];
$prefix = $prefixes[3];
$prefix2 = $prefix !== null? "$prefix ": null;
$suffix = $prefixes[4];
$suffix2 = $suffix !== null? " $suffix": null;
$after = $prefixes[5];
$lines = $out->getLines(false, ...$content);
$maxlen = 0;
foreach ($lines as &$content) {
$line = $out->filterColors($content);
$len = mb_strlen($line);
if ($len > $maxlen) $maxlen = $len;
$content = [$content, $len];
}; unset($content);
if ($before !== null) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, substr($before, 1), str_repeat($before[0], $maxlen), $suffix);
}
foreach ($lines as [$content, $len]) {
if ($linePrefix !== null) $out->write($linePrefix);
$padding = $len < $maxlen? str_repeat(" ", $maxlen - $len): null;
$out->iprint($indentLevel, $prefix2, $content, $padding, $suffix2);
}
if ($after !== null) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, substr($after, 1), str_repeat($after[0], $maxlen), $suffix);
}
} else {
$prefix = $prefixes[1];
if ($prefix !== null) $prefix .= " ";
$prefix2 = str_repeat(" ", mb_strlen($prefix));
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, $content);
$prefix = $prefix2;
}
}
}
protected function _printAction(
?string $linePrefix, int $level,
bool $printContent, $content,
bool $printResult, ?bool $rsuccess, $rcontent,
int $indentLevel, StdOutput $out
): void {
$color = $out->isColor();
if ($rsuccess === true) $type = "success";
elseif ($rsuccess === false) $type = "failure";
else $type = "done";
$rprefixes = self::RESULT_PREFIXES[$type];
if ($color) {
$rprefix = $rprefixes[1];
$rprefix2 = null;
if ($rprefix !== null) {
$rprefix .= " ";
$rprefix2 = $out->filterColors($out->filterContent($rprefix));
$rprefix2 = str_repeat(" ", mb_strlen($rprefix2));
}
} else {
$rprefix = $rprefixes[0];
if ($rprefix !== null) $rprefix .= " ";
$rprefix2 = str_repeat(" ", mb_strlen($rprefix));
}
if ($printContent && $printResult) {
A::ensure_array($content);
if ($rcontent) {
$content[] = ": ";
$content[] = $rcontent;
}
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $rprefix, $content);
$rprefix = $rprefix2;
}
} elseif ($printContent) {
$prefixes = self::GENERIC_PREFIXES[$level]["step"];
if ($color) {
$prefix = $prefixes[1];
if ($prefix !== null) $prefix .= " ";
$prefix2 = $out->filterColors($out->filterContent($prefix));
$prefix2 = str_repeat(" ", mb_strlen($prefix2));
$suffix = $prefixes[2];
} else {
$prefix = $prefixes[0];
if ($prefix !== null) $prefix .= " ";
$prefix2 = str_repeat(" ", mb_strlen($prefix));
$suffix = null;
}
A::ensure_array($content);
$content[] = ":";
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, $content, $suffix);
$prefix = $prefix2;
}
} elseif ($printResult) {
if (!$rcontent) {
if ($type === "success") $rcontent = $color? "succès": "";
elseif ($type === "failure") $rcontent = $color? "échec": "";
elseif ($type === "done") $rcontent = "fait";
}
$rprefix = " $rprefix";
$rprefix2 = " $rprefix2";
$lines = $out->getLines(false, $rcontent);
foreach ($lines as $rcontent) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $rprefix, $rcontent);
$rprefix = $rprefix2;
}
}
}
protected function _printGeneric(
?string $linePrefix, int $level,
string $type, $content,
int $indentLevel, StdOutput $out
): void {
$prefixes = self::GENERIC_PREFIXES[$level][$type];
$content = cl::with($content);
if ($out->isColor()) {
$prefix = $prefixes[1];
$prefix2 = null;
if ($prefix !== null) {
$prefix .= " ";
$prefix2 = $out->filterColors($out->filterContent($prefix));
$prefix2 = str_repeat(" ", mb_strlen($prefix2));
}
$suffix = $prefixes[2];
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, $content, $suffix);
$prefix = $prefix2;
}
} else {
$prefix = $prefixes[0];
if ($prefix !== null) $prefix .= " ";
$prefix2 = str_repeat(" ", mb_strlen($prefix));
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, $content);
$prefix = $prefix2;
}
}
}
protected function _printGenericOrException(
?int $level,
string $type, $content,
int $indentLevel, StdOutput $out
): void {
$linePrefix = $this->getLinePrefix();
# si $content contient des exceptions, les afficher avec un level moindre
$exceptions = null;
if (is_array($content)) {
$valueContent = null;
foreach ($content as $value) {
if ($value instanceof Throwable || $value instanceof ExceptionShadow) {
$exceptions[] = $value;
} else {
$valueContent[] = $value;
}
}
if ($valueContent === null) $content = null;
elseif (count($valueContent) == 1) $content = $valueContent[0];
else $content = $valueContent;
} elseif ($content instanceof Throwable || $content instanceof ExceptionShadow) {
$exceptions[] = $content;
$content = null;
}
$printActions = true;
$showContent = $this->checkLevel($level);
if ($content !== null && $showContent) {
$this->printActions(); $printActions = false;
$this->_printGeneric($linePrefix, $level, $type, $content, $indentLevel, $out);
}
if ($exceptions !== null) {
$level1 = $this->decrLevel($level);
$showTraceback = $this->checkLevel($level1);
foreach ($exceptions as $exception) {
# tout d'abord message
$message = exceptions::get_message($exception);
if ($showContent) {
if ($printActions) { $this->printActions(); $printActions = false; }
$this->_printGeneric($linePrefix, $level, $type, $message, $indentLevel, $out);
}
# puis summary et traceback
if ($showTraceback) {
if ($printActions) { $this->printActions(); $printActions = false; }
$summary = exceptions::get_summary($exception, false);
$this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out);
$traceback = exceptions::get_traceback($exception);
$this->_printGeneric($linePrefix, $level1, $type, $traceback, $indentLevel, $out);
}
}
}
}
/** @var bool est-on dans une section? */
protected $inSection;

View File

@ -79,12 +79,12 @@ class StdOutput {
}
function resetParams(?array $params=null): void {
$output = cl::get($params, "output");
$output = $params["output"] ?? null;
$maskErrors = null;
$color = cl::get($params, "color");
$filterTags = cl::get($params, "filter_tags");
$indent = cl::get($params, "indent");
$flush = cl::get($params, "flush");
$color = $params["color"] ?? null;
$filterTags = $params["filter_tags"] ?? null;
$indent = $params["indent"] ?? null;
$flush = $params["flush"] ?? null;
if ($output instanceof Stream) $output = $output->getResource();
if ($output !== null) {
@ -105,14 +105,14 @@ class StdOutput {
else $message = "$output: open error";
throw new Exception($message);
}
if ($flush === null) $flush = true;
$flush ??= true;
} else {
$outf = $output;
}
$this->outf = $outf;
$this->maskErrors = $maskErrors;
if ($color === null) $color = stream_isatty($outf);
if ($flush === null) $flush = false;
$color ??= stream_isatty($outf);
$flush ??= false;
}
if ($color !== null) $this->color = boolval($color);
if ($filterTags !== null) $this->filterTags = boolval($filterTags);
@ -124,23 +124,23 @@ class StdOutput {
protected $outf;
/** @var bool faut-il masquer les erreurs d'écriture? */
protected $maskErrors;
protected bool $maskErrors;
/** @var bool faut-il autoriser la sortie en couleur? */
protected $color;
protected bool $color = false;
function isColor(): bool {
return $this->color;
}
/** @var bool faut-il enlever les tags dans la sortie? */
protected $filterTags;
protected bool $filterTags = true;
/** @var string indentation unitaire */
protected $indent;
protected string $indent = " ";
/** @var bool faut-il flush le fichier après l'écriture de chaque ligne */
protected $flush;
protected bool $flush = true;
function isatty(): bool {
return stream_isatty($this->outf);
@ -167,6 +167,7 @@ class StdOutput {
$text .= "m";
return $text;
}
function filterContent(string $text): string {
# couleur au début
$text = preg_replace_callback('/<color([^>]*)>/', [self::class, "replace_colors"], $text);
@ -178,6 +179,7 @@ class StdOutput {
}
return $text;
}
function filterColors(string $text): string {
return preg_replace('/\x1B\[.*?m/', "", $text);
}

View File

@ -7,6 +7,73 @@ use nulib\output\IMessenger;
* Interface _IMessenger: méthodes privées de IMessenger
*/
interface _IMessenger extends IMessenger {
const INDENT = " ";
const DATE_FORMAT = 'Y-m-d\TH:i:s.u';
const VALID_LEVELS = [self::DEBUG, self::MINOR, self::NORMAL, self::MAJOR, self::NONE];
const LEVEL_MAP = [
"debug" => self::DEBUG,
"minor" => self::MINOR, "verbose" => self::MINOR,
"normal" => self::NORMAL,
"major" => self::MAJOR, "quiet" => self::MAJOR,
"none" => self::NONE, "silent" => self::NONE,
];
const GENERIC_PREFIXES = [
self::MAJOR => [
"section" => [true, "SECTION!", "===", "<color @b>=", "=</color>", "==="],
"title" => [false, "TITLE!", null, "<color @b>T", "</color>", "==="],
"desc" => ["DESC!", "<color @b>></color>", ""],
"error" => ["CRIT.ERROR!", "<color @r>E!", "</color>"],
"warning" => ["CRIT.WARNING!", "<color @y>W!", "</color>"],
"note" => ["ATTENTION!", "<color @g>N!", "</color>"],
"info" => ["IMPORTANT!", "<color @b>N!", "</color>"],
"step" => ["*", "<color @w>.</color>", ""],
"print" => [null, null, null],
],
self::NORMAL => [
"section" => [true, "SECTION:", "---", "<color @b>-", "-</color>", "---"],
"title" => [false, "TITLE:", null, "<color @b>T</color><color b>", "</color>", "---"],
"desc" => ["DESC:", "<color @b>></color>", ""],
"error" => ["ERROR:", "<color @r>E</color><color r>", "</color>"],
"warning" => ["WARNING:", "<color @y>W</color><color y>", "</color>"],
"note" => ["NOTE:", "<color @g>N</color>", ""],
"info" => ["INFO:", "<color @b>I</color>", ""],
"step" => ["*", "<color @w>.</color>", ""],
"print" => [null, null, null],
],
self::MINOR => [
"section" => [true, "section", null, "<color @w>>>", "<<</color>", null],
"title" => [false, "title", null, "<color b>t", "</color>", null],
"desc" => ["desc", "<color b>></color>", ""],
"error" => ["error", "<color r>E</color><color -r>", "</color>"],
"warning" => ["warning", "<color y>W</color><color -y>", "</color>"],
"note" => ["note", "<color g>N</color>", ""],
"info" => ["info", "<color b>I</color><color w>", "</color>"],
"step" => ["*", "<color w>.</color>", ""],
"print" => [null, null, null],
],
self::DEBUG => [
"section" => [true, "section", null, "<color @w>>>", "<<</color>", null],
"title" => [false, "title", null, "<color b>t", "</color>", null],
"desc" => ["desc", "<color b>></color>", ""],
"error" => ["debugE", "<color r>e</color><color -r>", "</color>"],
"warning" => ["debugW", "<color y>w</color><color -y>", "</color>"],
"note" => ["debugN", "<color b>i</color>", ""],
"info" => ["debug", "<color @w>D</color><color w>", "</color>"],
"step" => ["*", "<color w>.</color>", ""],
"print" => [null, null, null],
],
];
const RESULT_PREFIXES = [
"failure" => ["(FAILURE)", "<color r>✘</color>"],
"success" => ["(SUCCESS)", "<color @g>✔</color>"],
"done" => [null, null],
];
function _endSection(): void;
function _getTitleMark(): int;

View File

@ -0,0 +1,274 @@
<?php
namespace nulib\output\std;
use Exception;
use nulib\A;
use nulib\cl;
use nulib\exceptions;
use nulib\ExceptionShadow;
use Throwable;
trait _TMessenger {
protected static function verifix_level($level, int $max_level=self::MAX_LEVEL): int {
if (!in_array($level, self::VALID_LEVELS, true)) {
$level = cl::get(self::LEVEL_MAP, $level, $level);
}
if (!in_array($level, self::VALID_LEVELS, true)) {
throw new Exception("$level: invalid level");
}
if ($level > $max_level) {
throw new Exception("$level: level not allowed here");
}
return $level;
}
protected function getLinePrefix(): ?string {
$linePrefix = null;
if ($this->addDate) {
$date = date_create()->format($this->dateFormat);
$linePrefix .= "$date ";
}
if ($this->id !== null) {
$linePrefix .= "$this->id ";
}
return $linePrefix;
}
protected function decrLevel(int $level, int $amount=-1): int {
$level += $amount;
if ($level < self::MIN_LEVEL) $level = self::MIN_LEVEL;
return $level;
}
protected function checkLevel(?int &$level): bool {
if ($level === null) $level = $this->defaultLevel;
elseif ($level < 0) $level = $this->decrLevel($this->defaultLevel, $level);
return $level >= $this->minLevel;
}
protected function getIndentLevel(bool $withActions=true): int {
$indentLevel = count($this->titles) - 1;
if ($indentLevel < 0) $indentLevel = 0;
if ($withActions) {
foreach ($this->actions as $action) {
if ($action["level"] < $this->minLevel) continue;
$indentLevel++;
}
}
return $indentLevel;
}
protected function _printTitle(
?string $linePrefix, int $level,
string $type, $content,
int $indentLevel, StdOutput $out
): void {
$prefixes = self::GENERIC_PREFIXES[$level][$type];
if ($prefixes[0]) $out->print();
$content = cl::with($content);
if ($out->isColor()) {
$before = $prefixes[2];
$prefix = $prefixes[3];
$prefix2 = $prefix !== null? "$prefix ": null;
$suffix = $prefixes[4];
$suffix2 = $suffix !== null? " $suffix": null;
$after = $prefixes[5];
$lines = $out->getLines(false, ...$content);
$maxlen = 0;
foreach ($lines as &$content) {
$line = $out->filterColors($content);
$len = mb_strlen($line);
if ($len > $maxlen) $maxlen = $len;
$content = [$content, $len];
}; unset($content);
if ($before !== null) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, substr($before, 1), str_repeat($before[0], $maxlen), $suffix);
}
foreach ($lines as [$content, $len]) {
if ($linePrefix !== null) $out->write($linePrefix);
$padding = $len < $maxlen? str_repeat(" ", $maxlen - $len): null;
$out->iprint($indentLevel, $prefix2, $content, $padding, $suffix2);
}
if ($after !== null) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, substr($after, 1), str_repeat($after[0], $maxlen), $suffix);
}
} else {
$prefix = $prefixes[1];
if ($prefix !== null) $prefix .= " ";
$prefix2 = str_repeat(" ", mb_strlen($prefix));
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, $content);
$prefix = $prefix2;
}
}
}
protected function _printAction(
?string $linePrefix, int $level,
bool $printContent, $content,
bool $printResult, ?bool $rsuccess, $rcontent,
int $indentLevel, StdOutput $out
): void {
$color = $out->isColor();
if ($rsuccess === true) $type = "success";
elseif ($rsuccess === false) $type = "failure";
else $type = "done";
$rprefixes = self::RESULT_PREFIXES[$type];
if ($color) {
$rprefix = $rprefixes[1];
$rprefix2 = null;
if ($rprefix !== null) {
$rprefix .= " ";
$rprefix2 = $out->filterColors($out->filterContent($rprefix));
$rprefix2 = str_repeat(" ", mb_strlen($rprefix2));
}
} else {
$rprefix = $rprefixes[0];
if ($rprefix !== null) $rprefix .= " ";
$rprefix2 = str_repeat(" ", mb_strlen($rprefix));
}
if ($printContent && $printResult) {
A::ensure_array($content);
if ($rcontent) {
$content[] = ": ";
$content[] = $rcontent;
}
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $rprefix, $content);
$rprefix = $rprefix2;
}
} elseif ($printContent) {
$prefixes = self::GENERIC_PREFIXES[$level]["step"];
if ($color) {
$prefix = $prefixes[1];
if ($prefix !== null) $prefix .= " ";
$prefix2 = $out->filterColors($out->filterContent($prefix));
$prefix2 = str_repeat(" ", mb_strlen($prefix2));
$suffix = $prefixes[2];
} else {
$prefix = $prefixes[0];
if ($prefix !== null) $prefix .= " ";
$prefix2 = str_repeat(" ", mb_strlen($prefix));
$suffix = null;
}
A::ensure_array($content);
$content[] = ":";
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, $content, $suffix);
$prefix = $prefix2;
}
} elseif ($printResult) {
if (!$rcontent) {
if ($type === "success") $rcontent = $color? "succès": "";
elseif ($type === "failure") $rcontent = $color? "échec": "";
elseif ($type === "done") $rcontent = "fait";
}
$rprefix = " $rprefix";
$rprefix2 = " $rprefix2";
$lines = $out->getLines(false, $rcontent);
foreach ($lines as $rcontent) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $rprefix, $rcontent);
$rprefix = $rprefix2;
}
}
}
protected function _printGeneric(
?string $linePrefix, int $level,
string $type, $content,
int $indentLevel, StdOutput $out
): void {
$prefixes = self::GENERIC_PREFIXES[$level][$type];
$content = cl::with($content);
if ($out->isColor()) {
$prefix = $prefixes[1];
$prefix2 = null;
if ($prefix !== null) {
$prefix .= " ";
$prefix2 = $out->filterColors($out->filterContent($prefix));
$prefix2 = str_repeat(" ", mb_strlen($prefix2));
}
$suffix = $prefixes[2];
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, $content, $suffix);
$prefix = $prefix2;
}
} else {
$prefix = $prefixes[0];
if ($prefix !== null) $prefix .= " ";
$prefix2 = str_repeat(" ", mb_strlen($prefix));
$lines = $out->getLines(false, ...$content);
foreach ($lines as $content) {
if ($linePrefix !== null) $out->write($linePrefix);
$out->iprint($indentLevel, $prefix, $content);
$prefix = $prefix2;
}
}
}
protected function _printGenericOrException(
?int $level,
string $type, $content,
int $indentLevel, StdOutput $out
): void {
$linePrefix = $this->getLinePrefix();
# si $content contient des exceptions, les afficher avec un level moindre
$exceptions = null;
if (is_array($content)) {
$valueContent = null;
foreach ($content as $value) {
if ($value instanceof Throwable || $value instanceof ExceptionShadow) {
$exceptions[] = $value;
} else {
$valueContent[] = $value;
}
}
if ($valueContent === null) $content = null;
elseif (count($valueContent) == 1) $content = $valueContent[0];
else $content = $valueContent;
} elseif ($content instanceof Throwable || $content instanceof ExceptionShadow) {
$exceptions[] = $content;
$content = null;
}
$printActions = true;
$showContent = $this->checkLevel($level);
if ($content !== null && $showContent) {
$this->printActions(); $printActions = false;
$this->_printGeneric($linePrefix, $level, $type, $content, $indentLevel, $out);
}
if ($exceptions !== null) {
$level1 = $this->decrLevel($level);
$showTraceback = $this->checkLevel($level1);
foreach ($exceptions as $exception) {
# tout d'abord message
$message = exceptions::get_message($exception);
if ($showContent) {
if ($printActions) { $this->printActions(); $printActions = false; }
$this->_printGeneric($linePrefix, $level, $type, $message, $indentLevel, $out);
}
# puis summary et traceback
if ($showTraceback) {
if ($printActions) { $this->printActions(); $printActions = false; }
$summary = exceptions::get_summary($exception, false);
$this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out);
$traceback = exceptions::get_traceback($exception);
$this->_printGeneric($linePrefix, $level1, $type, $traceback, $indentLevel, $out);
}
}
}
}
}