nur-sery/nur_src/cli/Console.php

322 lines
10 KiB
PHP
Raw Normal View History

2023-12-03 22:10:18 +04:00
<?php
namespace nur\cli;
use nur\A;
use nur\b\ExitException;
use nur\b\io\IWriter;
use nur\b\params\Tparametrable;
use nur\b\ui\AbstractMessenger;
use nur\b\UserException;
use nur\data\types\md_utils;
use nur\data\types\Metadata;
use nur\writer;
/**
* Class Console: affichage de messages sur la console
*/
class Console extends AbstractMessenger {
use Tparametrable;
function __construct(?array $params=null) {
self::set_parametrable_params_defaults($params, [
"output" => null,
], false);
parent::__construct($params);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"output" => [null, null, "destination des messages affichés"],
"color" => ["?bool", null, "la sortie dans la destination se fait-elle en couleur?"],
];
/** @var IWriter */
protected $ppOutput;
/** @var bool */
protected $ppColor;
/** @var IWriter */
protected $output;
/** @var ?bool */
protected $color;
protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void {
parent::afterSetParametrableParams($modifiedKeys, $md);
if (self::was_parametrable_param_modified($modifiedKeys, "color")) {
$this->color = $this->ppColor;
}
if (self::was_parametrable_param_modified($modifiedKeys, "output")) {
$output = $this->ppOutput;
if ($output === null) $output = STDERR;
$this->output = $output = writer::with($output);
if (!self::was_parametrable_param_modified($modifiedKeys, "color")) {
$this->color = $output->isatty();
}
}
}
const KEY_EXIT = "exit";
const MESSAGE_OPTIONS_SCHEMA = [
self::KEY_EXIT => [null, null, "faut-il arrêter le script après avoir affiché le message?",
# la valeur peut être numérique auquel cas c'est le code de retour
# sinon ce doit être un booléan
],
];
/** @var Metadata */
private static $message_md;
protected static function message_md(): Metadata {
return md_utils::ensure_md(self::$message_md
, array_merge(self::MESSAGE_SCHEMA, self::MESSAGE_OPTIONS_SCHEMA));
}
protected function processMsgOptions(array $options): void {
$exit = $options[self::KEY_EXIT];
if ($exit !== null && $exit !== false) {
throw new ExitException(
is_int($exit)? $exit: 1,
is_string($exit)? $exit: null,
);
}
}
const TYPE_PREFIXES = [
# les clés doivent être ordonnées de la plus grande à la plus petite
# la 4ème valeur indique s'il faut garder le préfixe s'il y a un result
self::LEVEL_CRITICAL => [
self::TYPE_ERROR => ["CRITICAL!", "<color @r>E!", "</color>", true],
self::TYPE_WARNING => ["ATTENTION!", "<color @y>W!", "</color>", true],
self::TYPE_DEBUG => ["IMPORTANT!", "<color @g>N!", "</color>", true],
],
self::LEVEL_MAJOR => [
self::TYPE_ERROR => ["ERROR:", "<color @r>E</color><color r>", "</color>", true],
self::TYPE_WARNING => ["WARN:", "<color @y>W</color><color y>", "</color>", true],
self::TYPE_INFO => ["INFO:", "<color @b>I</color>", "", false],
self::TYPE_DEBUG => ["DEBUG:", "<color @w>D</color><color w>", "</color>", true],
],
self::LEVEL_NORMAL => [
self::TYPE_ERROR => ["E", "<color r>", "</color>", true],
self::TYPE_WARNING => ["W", "<color y>", "</color>", true],
self::TYPE_INFO => ["", "", "", false],
self::TYPE_DEBUG => ["D", "<color w>", "</color>", true],
],
self::LEVEL_MINOR => [
self::TYPE_ERROR => ["e", "<color -r>", "</color>", true],
self::TYPE_WARNING => ["w", "<color -y>", "</color>", true],
self::TYPE_INFO => ["i", "<color -b>", "</color>", false],
self::TYPE_DEBUG => ["d", "<color -w>", "</color>", true],
],
];
const RESULT_PREFIXES = [
self::RESULT_FAILURE => ["(FAILURE)", "<color r>✘</color>"],
self::RESULT_SUCCESS => ["(SUCCESS)", "<color @g>✔</color>"],
self::RESULT_NEUTRAL => ["*", "<color @w>.</color>"],
self::RESULT_NONE => [null, null],
];
const COLORS = [
"reset" => "0",
"bold" => "1",
"faint" => "2",
"underlined" => "4",
"reverse" => "7",
"normal" => "22",
"black" => "30",
"red" => "31",
"green" => "32",
"yellow" => "33",
"blue" => "34",
"magenta" => "35",
"cyan" => "36",
"white" => "37",
"default" => "39",
"black-bg" => "40",
"red-bg" => "41",
"green-bg" => "42",
"yellow-bg" => "43",
"blue-bg" => "44",
"magenta-bg" => "45",
"cyan-bg" => "46",
"white-bg" => "47",
"default-bg" => "49",
];
const COLOR_MAP = [
"z" => "reset",
"o" => "black",
"r" => "red",
"g" => "green",
"y" => "yellow",
"b" => "blue",
"m" => "magenta",
"c" => "cyan",
"w" => "white",
"O" => "black_bg",
"R" => "red_bg",
"G" => "green_bg",
"Y" => "yellow_bg",
"B" => "blue_bg",
"M" => "magenta_bg",
"C" => "cyan_bg",
"W" => "white_bg",
"@" => "bold",
"-" => "faint",
"_" => "underlined",
"~" => "reverse",
"n" => "normal",
];
private static function replace_colors(array $ms): string {
$colors = [];
foreach (preg_split('/\s+/', $ms[1]) as $color) {
while ($color && !A::has(self::COLORS, $color)) {
$alias = substr($color, 0, 1);
$colors[] = self::COLOR_MAP[$alias];
$color = substr($color, 1);
}
if ($color) $colors[] = $color;
}
$text = "\x1B[";
$first = true;
foreach ($colors as $color) {
if (!$color) continue;
if ($first) $first = false;
else $text .= ";";
$text .= self::COLORS[$color];
}
$text .= "m";
return $text;
}
protected function filterTags(string $text): string {
# couleur au début
$text = preg_replace_callback('/<color([^>]*)>/', [self::class, "replace_colors"], $text);
# reset à la fin
$text = preg_replace('/<\/color>/', "\x1B[0m", $text);
return parent::filterTags($text);
}
protected function filterColors(string $text): string {
return preg_replace('/\x1B\[.*?m/', "", $text);
}
protected function printStartSection($title, ?int $msgType, ?int $msgLevel): void {
$datetime = $this->ppAddDate? self::date()." ": "\n";
$title = "$datetime>>>> $title <<<<";
$this->wnl($this->output, $this->color, "", $title);
}
protected function printEndSection(): void {
}
protected function getResultPrefix(int $result, bool $color): ?string {
return self::RESULT_PREFIXES[$result][$color ? 1 : 0];
}
protected function getTypePrefixSuffix(int $type, int $level, bool $color, bool $haveResultPrefix): array {
$typePrefixSuffix = false;
foreach (self::TYPE_PREFIXES as $prefixLevel => $prefixes) {
if ($level >= $prefixLevel) {
foreach ($prefixes as $prefixType => $prefixValue) {
if ($type >= $prefixType) {
$typePrefixSuffix = $prefixValue;
break;
}
}
}
if ($typePrefixSuffix !== false) break;
}
if ($typePrefixSuffix === false) return [null, null];
elseif ($haveResultPrefix && !$typePrefixSuffix[3]) return [null, null];
$prefix = $typePrefixSuffix[$color ? 1 : 0];
$suffix = $color? $typePrefixSuffix[2]: "";
return [$prefix, $suffix];
}
protected function printStartGroup(array $group, ?array $groups): void {
[
"indent" => $indent,
"prefix" => $groupPrefix,
"count" => $count,
"type" => $type,
"level" => $level,
] = $group;
$count = $this->getGroupCount($group);
if ($count > 1) {
$groupIndent = $this->getGroupIndent($group, $indent - 1);
if ($this->ppAddDate) $groupIndent = self::date()." $groupIndent";
if ($type === null) $type = self::TYPE_INFO + self::RESULT_NEUTRAL;
if ($level === null) $level = self::LEVEL_ALWAYS;
$color = $this->color;
$resultPrefix = $this->getResultPrefix($type & self::RESULT_MASK, $color);
[$typePrefix, $typeSuffix] = $this->getTypePrefixSuffix($type & self::TYPE_MASK, $level, $color, $resultPrefix !== null);
$this->wnl($this->output, $color, " ", $groupIndent.$typePrefix.$resultPrefix, $groupPrefix, $typeSuffix);
}
}
protected function printEndGroup(array $group): void {
}
protected function getUserMsg($msg): array {
return [$msg];
}
protected function getTechMsg($msg): array {
return [$msg];
}
protected function getExceptionMsg($exception, $user, $tech, bool $haveTechMsgOrSummary): array {
$msg = [null, UserException::get_traceback($exception)];
if (!$haveTechMsgOrSummary || ($exception !== $user && $exception !== $tech)) {
$msg[0] = UserException::get_summary($exception);
}
return $msg;
}
protected function _printMsg(
IWriter $output, bool $color,
?string $groupIndent, ?string $groupPrefix,
bool $showUser, $userMsg,
bool $showTech, $techMsg,
bool $showException, $exceptionMsg,
int $type, int $level
): void {
$result = $type & self::RESULT_MASK;
$type = $type & self::TYPE_MASK;
$resultPrefix = $this->getResultPrefix($result, $color);
[$typePrefix, $typeSuffix] = $this->getTypePrefixSuffix($type, $level, $color, $resultPrefix !== null);
$prefix = $groupIndent.$typePrefix.$resultPrefix;
if ($showUser) $this->wnl($output, $color, " ", $prefix, $groupPrefix, $userMsg, $typeSuffix);
if ($showTech) $this->wnl($output, $color, " ", $prefix, "TECH:", $techMsg, $typeSuffix);
if ($showException) {
[$summary, $traceback] = $exceptionMsg;
$this->wnl($output, $color, " ", $prefix, "TRACEBACK:", $summary, $typeSuffix);
$this->wnl($output, false, "", $traceback);
}
}
protected function printMsg(
?array $groups,
bool $printUser, $userMsg,
bool $printTech, $techMsg,
bool $printException, $exceptionMsg,
int $type, int $level, array $options
): void {
$group = A::last($groups);
if ($group === false) {
# groupe neutralisé
return;
}
$groupIndent = $this->getGroupIndent($group);
$groupPrefix = $this->getGroupPrefix($group);
if ($this->ppAddDate) $groupIndent = self::date()." $groupIndent";
$this->_printMsg($this->output, $this->color
, $groupIndent, $groupPrefix
, $printUser, $userMsg
, $printTech, $techMsg
, $printException, $exceptionMsg
, $type, $level);
}
}