diff --git a/composer.json b/composer.json index 6dad9dd..c1fba1c 100644 --- a/composer.json +++ b/composer.json @@ -26,12 +26,16 @@ "minimum-stability": "stable", "require": { "php": "^7.2 || ^7.3 || ^7.4 || ^8.0 || ^8.1", - "psr/log": "^1 || ^2 || ^3" + "psr/log": "^1 || ^2 || ^3", + "symfony/polyfill-mbstring": "^1.0" }, "config": { "optimize-autoloader": true, "sort-packages": true }, + "suggest": { + "ext-mbstring": "Use C/C++ extension instead of polyfill will be more efficient" + }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^1.1", diff --git a/demo/table-printer-demo.php b/demo/table-printer-demo.php new file mode 100644 index 0000000..f43dac5 --- /dev/null +++ b/demo/table-printer-demo.php @@ -0,0 +1,19 @@ + '我是一端比较牛逼的终端打印工具', + 'php_version' => PHP_VERSION, + 'php_version_id' => PHP_VERSION_ID, + 'info' => '并且支持中文!!', + 'path2' => __DIR__, + 'long' => '我可以' . str_repeat('很长', 50), +]; + +(new \ZM\Logger\TablePrinter($data)) + ->setValueColor('random') + ->setRowOverflowHide() + ->printAll(); diff --git a/src/ZM/Logger/ConsolePrettyPrinter.php b/src/ZM/Logger/ConsolePrettyPrinter.php deleted file mode 100644 index 2d7b7bb..0000000 --- a/src/ZM/Logger/ConsolePrettyPrinter.php +++ /dev/null @@ -1,53 +0,0 @@ -params = $params; - $this->head = $head === '' ? str_pad('', 65, '=') : $head; - $this->foot = $foot === '' ? str_pad('', 65, '=') : $foot; - } - - public static function createFromArray(array $params): ConsolePrettyPrinter - { - return new static($params); - } - - public function printAll(): void - { - $this->printHead(); - $this->printBody(); - $this->printFoot(); - } - - public function printHead() - { - // TODO: 写头 - } - - public function printBody() - { - // TODO: 写主体 - } - - public function printFoot() - { - // TODO: 写尾 - } -} diff --git a/src/ZM/Logger/TablePrinter.php b/src/ZM/Logger/TablePrinter.php new file mode 100644 index 0000000..38963a7 --- /dev/null +++ b/src/ZM/Logger/TablePrinter.php @@ -0,0 +1,205 @@ +params = $params; + $this->head = $head; + $this->foot = $foot; + $this->setBorderWidth($max_border_length); + } + + public function setValueColor(string $color): TablePrinter + { + if ($color === 'random') { + $random_list = [ + 'red', 'green', 'blue', 'yellow', 'magenta', 'gray', + 'bright_red', 'bright_yellow', 'bright_green', 'bright_blue', 'bright_magenta', 'bright_cyan', + ]; + $random = mt_rand(0, count($random_list) - 1); + $this->value_color = $random_list[$random]; + } else { + $this->value_color = $color; + } + return $this; + } + + public function setRowOverflowHide(bool $hide = true): TablePrinter + { + $this->row_overflow_hide = $hide; + return $this; + } + + public function printAll(): void + { + $this->printHead(); + $this->printBody(); + $this->printFoot(); + } + + public function printHead() + { + echo $this->head . PHP_EOL; + } + + public function printFoot() + { + echo $this->foot . PHP_EOL; + } + + public function printBody() + { + $line_data = []; + $current_line = 0; + foreach ($this->params as $k => $v) { + $k = (string) $k; + $v = (string) $v; + $k_len = mb_strwidth($k); // 获取 key 的长度 + $v_len = mb_strwidth($v); // 获取 value 的长度 + $len = $k_len + 2 + $v_len; // 计算需要的长度 + $valid_width = $this->border_width - 2; // 获取可用的长度 + if ($k_len + 5 > $this->border_width - 2) { // 这个参数的 key 过长了,没有你这么用的!! + continue; + } + while (true) { + if (!isset($line_data[$current_line])) { // 如果行是空的,先尝试放入一个参数 + if ($len > $valid_width) { // 需要的长度超出一行,直接另起折行 + if ($this->row_overflow_hide) { // 如果开启了超出部分隐藏,则直接砍掉后面的东西,变成三个或四个点 + [$partial_v, $partial_v_len] = $this->getPartialValue($v, $valid_width - $k_len - 2); + + // 写入到数据中 + $line_data[$current_line] = [ + 'used' => $valid_width - $k_len - 2 - $partial_v_len, + 'can_put_second' => false, + 'lines' => $k . ': ' . + ConsoleColor::apply([$this->value_color], $partial_v . str_pad('', $valid_width - $k_len - 2 - $partial_v_len, '.')), + ]; + ++$current_line; + // 下一个参数 + } else { // 没开隐藏就要拐弯输出 + $line_data[$current_line] = [ + 'used' => $k_len + 2, + 'can_put_second' => false, + 'lines' => $k . ': ', + ]; + do { + [$partial_v, $partial_v_len, $next_offset] = $this->getPartialValue($v, $valid_width - $line_data[$current_line]['used'], 0); + $v = mb_substr($v, $next_offset); + $line_data[$current_line]['lines'] .= ConsoleColor::apply([$this->value_color], $partial_v); + $line_data[$current_line]['used'] += $partial_v_len; + ++$current_line; + $line_data[$current_line] = [ + 'used' => 0, + 'can_put_second' => false, + 'lines' => '', + ]; + } while ($v !== ''); + if ($line_data[$current_line]['used'] === 0) { + unset($line_data[$current_line]); + } + // 下一个参数 + } + break; + } + // 没超出一行,直接写 + $line_data[$current_line] = [ + 'used' => $len, + 'can_put_second' => ($valid_width >= 57 && intval(floor($valid_width / 2)) - 2 + ($valid_width % 2) > $len), + 'lines' => $k . ': ' . ConsoleColor::apply([$this->value_color], $v), + ]; + break; + } + // 如果当前行不是空的,就要看看是否能放下这个参数 + if ($line_data[$current_line]['can_put_second'] && ($k_len + $v_len + 2 <= floor($valid_width / 2) - 2)) { // 如果可以放下,就放下 + // 首先把前面的参数补充到中间的分隔符 + $line_data[$current_line]['lines'] .= str_pad('', intval(floor($valid_width / 2)) - 2 + ($valid_width % 2) - $line_data[$current_line]['used']); + // 然后输出分隔符 + $line_data[$current_line]['lines'] .= ' | '; + // 最后输出第二列的参数 + $line_data[$current_line]['lines'] .= $k . ': ' . ConsoleColor::apply([$this->value_color], $v); + ++$current_line; + break; + } // 放不下,直接下一轮继续 + ++$current_line; + } // 这层是 while(true) + } + foreach ($line_data as $line) { + echo ' ' . $line['lines'] . PHP_EOL; + } + } + + public function setBorderWidth(int $border_width): TablePrinter + { + if ($border_width <= 0) { + $this->border_width = $this->fetchTerminalSize(); + } else { + $terminal_size = $this->fetchTerminalSize(); + $this->border_width = $border_width < $terminal_size ? $border_width : $terminal_size; + } + $this->head = str_pad($this->head, $this->border_width, '='); + $this->foot = str_pad($this->foot, $this->border_width, '='); + return $this; + } + + public function getConsoleLength(string $v) + { + return mb_strwidth($v); + } + + private function fetchTerminalSize(): int + { + if (STDIN === false) { + return 79; + } + $size = exec('stty size 2>/dev/null'); + if (empty($size)) { + return 79; + } + return (int) explode(' ', trim($size))[1]; + } + + private function getPartialValue($v, int $valid_width, int $remain = 3): array + { + $virtual_v_offset = 0; + do { // 依次填入字符,直到空下了小于等于4长度的距离 + ++$virtual_v_offset; + $virtual_v = mb_substr($v, 0, $virtual_v_offset); + $used_len = mb_strwidth($virtual_v); + } while ($valid_width - $used_len > $remain && mb_strlen($virtual_v) < mb_strlen($v)); + return [$virtual_v, $used_len, $virtual_v_offset]; + } +}