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!", "===", "=", "=", "==="], "title" => [false, "TITLE!", null, "T", "", "==="], "desc" => ["DESC!", ">", ""], "error" => ["CRIT.ERROR!", "E!", ""], "warn" => ["CRIT.WARN!", "W!", ""], "note" => ["ATTENTION!", "N!", ""], "info" => ["IMPORTANT!", "N!", ""], "step" => ["*", ".", ""], "print" => [null, null, null], ], self::NORMAL => [ "section" => [true, "SECTION:", "---", "-", "-", "---"], "title" => [false, "TITLE:", null, "T", "", "---"], "desc" => ["DESC:", ">", ""], "error" => ["ERROR:", "E", ""], "warn" => ["WARN:", "W", ""], "note" => ["NOTE:", "N", ""], "info" => ["INFO:", "I", ""], "step" => ["*", ".", ""], "print" => [null, null, null], ], self::MINOR => [ "section" => [true, "section", null, ">>", "<<", null], "title" => [false, "title", null, "t", "", null], "desc" => ["desc", ">", ""], "error" => ["error", "E", ""], "warn" => ["warn", "W", ""], "note" => ["note", "N", ""], "info" => ["info", "I", ""], "step" => ["*", ".", ""], "print" => [null, null, null], ], self::DEBUG => [ "section" => [true, "section", null, ">>", "<<", null], "title" => [false, "title", null, "t", "", null], "desc" => ["desc", ">", ""], "error" => ["debugE", "e", ""], "warn" => ["debugW", "w", ""], "note" => ["debugN", "i", ""], "info" => ["debug", "D", ""], "step" => ["*", ".", ""], "print" => [null, null, null], ], ]; const RESULT_PREFIXES = [ "failure" => ["(FAILURE)", ""], "success" => ["(SUCCESS)", ""], "done" => [null, null], ]; function __construct(?array $params=null) { $output = cl::get($params, "output"); $color = cl::get($params, "color"); $indent = cl::get($params, "indent", static::INDENT); $defaultLevel = cl::get($params, "default_level"); if ($defaultLevel === null) $defaultLevel = self::NORMAL; $defaultLevel = self::verifix_level($defaultLevel); $debug = boolval(cl::get($params, "debug")); $minLevel = cl::get($params, "min_level"); if ($minLevel === null && $debug) $minLevel = self::DEBUG; if ($minLevel === null) $minLevel = cl::get($params, "verbosity"); # alias if ($minLevel === null) $minLevel = self::NORMAL; $minLevel = self::verifix_level($minLevel, self::NONE); $addDate = boolval(cl::get($params, "add_date")); $dateFormat = cl::get($params, "date_format", static::DATE_FORMAT); $id = cl::get($params, "id"); $params = [ "color" => $color, "indent" => $indent, ]; if ($output !== null) { $this->err = $this->out = new StdOutput($output, $params); } else { $this->out = new StdOutput(STDOUT, $params); $this->err = new StdOutput(STDERR, $params); } $this->defaultLevel = $defaultLevel; $this->minLevel = $minLevel; $this->addDate = $addDate; $this->dateFormat = $dateFormat; $this->id = $id; $this->inSection = false; $this->titles = []; $this->actions = []; } function resetParams(?array $params=null): void { $output = cl::get($params, "output"); $color = cl::get($params, "color"); $indent = cl::get($params, "indent"); $defaultLevel = cl::get($params, "default_level"); if ($defaultLevel !== null) $defaultLevel = self::verifix_level($defaultLevel); $debug = cl::get($params, "debug"); $minLevel = cl::get($params, "min_level"); if ($minLevel === null && $debug !== null) $minLevel = $debug? self::DEBUG: self::NORMAL; if ($minLevel === null) $minLevel = cl::get($params, "verbosity"); # alias if ($minLevel !== null) $minLevel = self::verifix_level($minLevel, self::NONE); $addDate = cl::get($params, "add_date"); $dateFormat = cl::get($params, "date_format"); $id = cl::get($params, "id"); $params = [ "output" => $output, "color" => $color, "indent" => $indent, ]; if ($this->out === $this->err) { $this->out->resetParams($params); } else { # NB: si initialement [output] était null, et qu'on spécifie une valeur # [output], alors les deux instances $out et $err sont mis à jour # séparément avec la même valeur de output # de plus, on ne peut plus revenir à la situation initiale avec une # destination différente pour $out et $err $this->out->resetParams($params); $this->err->resetParams($params); } 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); #XXX faut-il marquer la section et les titres du clone à "print" => false? # ou en faire des références au parent? # dans tous les cas, on considère qu'il n'y a pas d'actions en cours, et on # ne doit pas dépiler avec end() plus que l'état que l'on a eu lors du clone return $clone; } /** @var StdOutput la sortie standard */ protected $out; /** @var StdOutput la sortie d'erreur */ protected $err; /** @var int level par défaut dans lequel les messages sont affichés */ protected $defaultLevel; /** @var int level minimum que doivent avoir les messages pour être affichés */ protected $minLevel; /** @var bool faut-il ajouter la date à chaque ligne? */ protected $addDate; /** @var string format de la date */ protected $dateFormat; /** @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(); 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) { if ($rcontent) { cl::ensure_array($content); $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; } $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]; 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; } } $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 userMessage $userMessage = UserException::get_user_message($exception); if ($userMessage !== null && $showContent) { if ($printActions) { $this->printActions(); $printActions = false; } $this->_printGeneric($linePrefix, $level, $type, $userMessage, $indentLevel, $out); } # puis summary et traceback if ($showTraceback) { if ($printActions) { $this->printActions(); $printActions = false; } $summary = UserException::get_summary($exception); $traceback = UserException::get_traceback($exception); $this->_printGeneric($linePrefix, $level1, $type, $summary, $indentLevel, $out); $this->_printGeneric($linePrefix, $level1, $type, $traceback, $indentLevel, $out); } } } } /** @var bool est-on dans une section? */ protected $inSection; /** @var array section qui est en attente d'affichage */ protected $section; function section($content, ?callable $func=null, ?int $level=null): void { $this->_endSection(); $this->inSection = true; if (!$this->checkLevel($level)) return; $this->section = [ "line_prefix" => $this->getLinePrefix(), "level" => $level, "content" => $content, "print_content" => true, ]; if ($func !== null) { try { $func($this); } finally { $this->_endSection(); } } } protected function printSection() { $section =& $this->section; if ($section !== null && $section["print_content"]) { $this->_printTitle( $section["line_prefix"], $section["level"], "section", $section["content"], 0, $this->err); $section["print_content"] = false; } } function _endSection(): void { $this->inSection = false; $this->section = null; } /** @var array */ protected $titles; /** @var array */ protected $title; function _getTitleMark(): int { return count($this->titles); } function title($content, ?callable $func=null, ?int $level=null): void { if (!$this->checkLevel($level)) return; $until = $this->_getTitleMark(); $this->titles[] = [ "line_prefix" => $this->getLinePrefix(), "level" => $level, "content" => $content, "print_content" => true, "descs" => [], "print_descs" => false, ]; $this->title =& $this->titles[$until]; if ($func !== null) { try { $func($this); } finally { $this->_endTitle($until); } } } function desc($content, ?int $level=null): void { if (!$this->checkLevel($level)) return; $title =& $this->title; $title["descs"][] = [ "line_prefix" => $this->getLinePrefix(), "level" => $level, "content" => $content, ]; $title["print_descs"] = true; } protected function printTitles(): void { $this->printSection(); $err = $this->err; $indentLevel = 0; foreach ($this->titles as &$title) { if ($title["print_content"]) { $this->_printTitle( $title["line_prefix"], $title["level"], "title", $title["content"], $indentLevel, $err); $title["print_content"] = false; } if ($title["print_descs"]) { foreach ($title["descs"] as $desc) { $this->_printGeneric( $desc["line_prefix"], $desc["level"], "desc", $desc["content"], $indentLevel, $err); } $title["descs"] = []; $title["print_descs"] = false; } $indentLevel++; }; unset($title); } function _endTitle(?int $until=null): void { if ($until === null) $until = $this->_getTitleMark() - 1; while (count($this->titles) > $until) { array_pop($this->titles); } if ($this->titles) { $this->title =& $this->titles[count($this->titles) - 1]; } else { $this->titles = []; unset($this->title); } } /** @var array */ protected $actions; /** @var array */ protected $action; function _getActionMark(): int { return count($this->actions); } function action($content, ?callable $func=null, ?int $level=null): void { $this->checkLevel($level); $until = $this->_getActionMark(); $this->actions[] = [ "line_prefix" => $this->getLinePrefix(), "level" => $level, "content" => $content, "print_content" => true, "result_success" => null, "result_content" => null, ]; $this->action =& $this->actions[$until]; if ($func !== null) { try { $result = $func($this); if ($result !== null) { if ($result === true) $this->asuccess(); elseif ($result === false) $this->afailure(); else $this->adone($result); } } finally { $this->_endAction($until); } } } function printActions(bool $endAction=false): void { $this->printTitles(); $err = $this->err; $indentLevel = $this->getIndentLevel(false); $lastIndex = count($this->actions) - 1; $index = 0; foreach ($this->actions as &$action) { $mergeResult = $index++ == $lastIndex && $endAction; $linePrefix = $action["line_prefix"]; $level = $action["level"]; $content = $action["content"]; $printContent = $action["print_content"]; $rsuccess = $action["result_success"]; $rcontent = $action["result_content"]; if ($level < $this->minLevel) continue; if ($mergeResult) { $this->_printAction( $linePrefix, $level, $printContent, $content, true, $rsuccess, $rcontent, $indentLevel, $err); } elseif ($printContent) { $this->_printAction( $linePrefix, $level, $printContent, $content, false, $rsuccess, $rcontent, $indentLevel, $err); $action["print_content"] = false; } $indentLevel++; }; unset($action); if ($endAction) $this->_endAction(); } function step($content, ?int $level=null): void { $this->_printGenericOrException($level, "step", $content, $this->getIndentLevel(), $this->err); } function asuccess($content=null): void { if (!$this->actions) $this->action(null); $this->action["result_success"] = true; $this->action["result_content"] = $content; $this->printActions(true); } function afailure($content=null): void { if (!$this->actions) $this->action(null); $this->action["result_success"] = false; $this->action["result_content"] = $content; $this->printActions(true); } function adone($content=null): void { if (!$this->actions) $this->action(null); $this->action["result_success"] = null; $this->action["result_content"] = $content; $this->printActions(true); } function _endAction(?int $until=null): void { if ($until === null) $until = $this->_getActionMark() - 1; while (count($this->actions) > $until) { array_pop($this->actions); } if ($this->actions) { $this->action =& $this->actions[count($this->actions) - 1]; } else { $this->actions = []; unset($this->action); } } 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 warn($content, ?int $level=null): void { $this->_printGenericOrException($level, "warn", $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->actions) $this->adone(); while ($this->titles) $this->_endTitle(); $this->_endSection(); } elseif ($this->actions) { $this->_endAction(); } elseif ($this->titles) { $this->_endTitle(); } else { $this->_endSection(); } } }