322 lines
10 KiB
PHP
322 lines
10 KiB
PHP
|
<?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);
|
||
|
}
|
||
|
}
|