322 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			322 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nur\cli;
 | |
| 
 | |
| use nur\A;
 | |
| use nur\b\ExitError;
 | |
| 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 ExitError(
 | |
|         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);
 | |
|   }
 | |
| }
 |