zhamao-logger/src/ZM/Logger/TablePrinter.php

254 lines
9.0 KiB
PHP
Raw Normal View History

2022-05-13 02:58:20 +08:00
<?php
declare(strict_types=1);
namespace ZM\Logger;
/**
* 输出漂亮的表格参数形式,支持自适应终端大小
*
* Class ConsolePrettyPrinter
*/
class TablePrinter
{
/**
* @var array 参数列表
*/
protected $params;
/**
* @var string 顶部格式
*/
protected $head;
/**
* @var string 底部格式
*/
protected $foot;
2022-05-13 03:09:38 +08:00
/**
* @var int 边界宽度
*/
2022-05-13 02:58:20 +08:00
protected $border_width;
2022-05-13 03:09:38 +08:00
/**
* @var string 值颜色
*/
2022-05-13 02:58:20 +08:00
protected $value_color = 'green';
/**
2022-05-13 03:09:38 +08:00
* @var bool 是否超出宽度自动省略
2022-05-13 02:58:20 +08:00
*/
protected $row_overflow_hide = false;
2022-05-13 17:18:26 +08:00
protected $terminal_size;
2022-05-13 03:16:54 +08:00
public function __construct(array $params, string $head = '=', string $foot = '=', int $max_border_length = 79)
2022-05-13 02:58:20 +08:00
{
$this->params = $params;
$this->head = $head;
$this->foot = $foot;
$this->setBorderWidth($max_border_length);
}
2022-05-13 03:09:38 +08:00
/**
* 设置值的显示颜色
*
* @param string $color 颜色
* @return $this 返回当前对象
*/
2022-05-13 02:58:20 +08:00
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;
}
2022-05-13 03:09:38 +08:00
/**
* 设置是否超出宽度自动省略
*
* @param bool $hide 是否超出宽度自动省略
* @return $this 返回当前对象
*/
2022-05-13 02:58:20 +08:00
public function setRowOverflowHide(bool $hide = true): TablePrinter
{
$this->row_overflow_hide = $hide;
return $this;
}
2022-05-13 03:09:38 +08:00
/**
* 打印表格
*/
2022-05-13 02:58:20 +08:00
public function printAll(): void
{
$this->printHead();
$this->printBody();
$this->printFoot();
}
2022-05-13 03:09:38 +08:00
/**
* 打印表格头
*/
public function printHead(): void
2022-05-13 02:58:20 +08:00
{
echo $this->head . PHP_EOL;
}
2022-05-13 03:09:38 +08:00
/**
* 打印表格尾
*/
public function printFoot(): void
2022-05-13 02:58:20 +08:00
{
echo $this->foot . PHP_EOL;
}
2022-05-13 03:09:38 +08:00
/**
* 打印表格体
*/
2022-05-13 02:58:20 +08:00
public function printBody()
{
$line_data = [];
$current_line = 0;
foreach ($this->params as $k => $v) {
$k = (string) $k;
$v = (string) $v;
2022-05-13 03:09:38 +08:00
$k_len = mb_strwidth($k); // 获取 key 的宽度
$v_len = mb_strwidth($v); // 获取 value 的宽度
$len = $k_len + 2 + $v_len; // 计算需要的宽度
$valid_width = $this->border_width - 2; // 获取可用的宽度
2022-05-13 02:58:20 +08:00
if ($k_len + 5 > $this->border_width - 2) { // 这个参数的 key 过长了,没有你这么用的!!
continue;
}
while (true) {
if (!isset($line_data[$current_line])) { // 如果行是空的,先尝试放入一个参数
2022-05-13 03:09:38 +08:00
if ($len > $valid_width) { // 需要的宽度超出一行,直接另起折行
2022-05-13 02:58:20 +08:00
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;
}
}
2022-05-13 03:09:38 +08:00
/**
* 设置边界最大宽度不调用本函数的话默认为79
*
* @param int $border_width 边界最大宽度
* @return $this 返回当前对象
*/
2022-05-13 02:58:20 +08:00
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;
}
2022-05-13 03:16:54 +08:00
$this->head = str_pad('', $this->border_width, $this->head[0]);
$this->foot = str_pad('', $this->border_width, $this->foot[0]);
2022-05-13 02:58:20 +08:00
return $this;
}
2022-05-13 03:09:38 +08:00
/**
* 获取终端大小
*
* @return int 终端大小
*/
2022-05-13 17:18:26 +08:00
public function fetchTerminalSize(): int
2022-05-13 02:58:20 +08:00
{
2022-05-13 17:18:26 +08:00
if (!isset($this->terminal_size)) {
if (STDIN === false) {
return $this->terminal_size = 79;
}
$size = exec('stty size 2>/dev/null');
if (empty($size)) {
return $this->terminal_size = 79;
}
return $this->terminal_size = (int) explode(' ', trim($size))[1];
2022-05-13 02:58:20 +08:00
}
2022-05-13 17:18:26 +08:00
return $this->terminal_size;
2022-05-13 02:58:20 +08:00
}
2022-05-13 03:09:38 +08:00
/**
* 获取字符串的截断部分、占用宽度、截断偏移量
* @param string $v 字符串
* @param int $valid_width 有效宽度
* @param int $remain 需要预留的宽度
* @return array 返回截断部分、占用宽度、截断偏移量
*/
private function getPartialValue(string $v, int $valid_width, int $remain = 3): array
2022-05-13 02:58:20 +08:00
{
$virtual_v_offset = 0;
2022-05-13 03:09:38 +08:00
do { // 依次填入字符直到空下了小于等于4宽度的距离
2022-05-13 02:58:20 +08:00
++$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];
}
}