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);
 | 
						|
  }
 | 
						|
}
 |