406 lines
12 KiB
PHP
406 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* @link http://www.yiiframework.com/
|
|
* @copyright Copyright (c) 2008 Yii Software LLC
|
|
* @license http://www.yiiframework.com/license/
|
|
*/
|
|
|
|
namespace yii\console\widgets;
|
|
|
|
use Yii;
|
|
use yii\base\Widget;
|
|
use yii\helpers\ArrayHelper;
|
|
use yii\helpers\Console;
|
|
|
|
/**
|
|
* Table class displays a table in console.
|
|
*
|
|
* For example,
|
|
*
|
|
* ```php
|
|
* $table = new Table();
|
|
*
|
|
* echo $table
|
|
* ->setHeaders(['test1', 'test2', 'test3'])
|
|
* ->setRows([
|
|
* ['col1', 'col2', 'col3'],
|
|
* ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
|
|
* ])
|
|
* ->run();
|
|
* ```
|
|
*
|
|
* or
|
|
*
|
|
* ```php
|
|
* echo Table::widget([
|
|
* 'headers' => ['test1', 'test2', 'test3'],
|
|
* 'rows' => [
|
|
* ['col1', 'col2', 'col3'],
|
|
* ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
|
|
* ],
|
|
* ]);
|
|
*
|
|
* @property string $listPrefix List prefix. This property is write-only.
|
|
* @property int $screenWidth Screen width. This property is write-only.
|
|
*
|
|
* @author Daniel Gomez Pan <pana_1990@hotmail.com>
|
|
* @since 2.0.13
|
|
*/
|
|
class Table extends Widget
|
|
{
|
|
const DEFAULT_CONSOLE_SCREEN_WIDTH = 120;
|
|
const CONSOLE_SCROLLBAR_OFFSET = 3;
|
|
const CHAR_TOP = 'top';
|
|
const CHAR_TOP_MID = 'top-mid';
|
|
const CHAR_TOP_LEFT = 'top-left';
|
|
const CHAR_TOP_RIGHT = 'top-right';
|
|
const CHAR_BOTTOM = 'bottom';
|
|
const CHAR_BOTTOM_MID = 'bottom-mid';
|
|
const CHAR_BOTTOM_LEFT = 'bottom-left';
|
|
const CHAR_BOTTOM_RIGHT = 'bottom-right';
|
|
const CHAR_LEFT = 'left';
|
|
const CHAR_LEFT_MID = 'left-mid';
|
|
const CHAR_MID = 'mid';
|
|
const CHAR_MID_MID = 'mid-mid';
|
|
const CHAR_RIGHT = 'right';
|
|
const CHAR_RIGHT_MID = 'right-mid';
|
|
const CHAR_MIDDLE = 'middle';
|
|
|
|
/**
|
|
* @var array table headers
|
|
* @since 2.0.19
|
|
*/
|
|
protected $headers = [];
|
|
/**
|
|
* @var array table rows
|
|
* @since 2.0.19
|
|
*/
|
|
protected $rows = [];
|
|
/**
|
|
* @var array table chars
|
|
* @since 2.0.19
|
|
*/
|
|
protected $chars = [
|
|
self::CHAR_TOP => '═',
|
|
self::CHAR_TOP_MID => '╤',
|
|
self::CHAR_TOP_LEFT => '╔',
|
|
self::CHAR_TOP_RIGHT => '╗',
|
|
self::CHAR_BOTTOM => '═',
|
|
self::CHAR_BOTTOM_MID => '╧',
|
|
self::CHAR_BOTTOM_LEFT => '╚',
|
|
self::CHAR_BOTTOM_RIGHT => '╝',
|
|
self::CHAR_LEFT => '║',
|
|
self::CHAR_LEFT_MID => '╟',
|
|
self::CHAR_MID => '─',
|
|
self::CHAR_MID_MID => '┼',
|
|
self::CHAR_RIGHT => '║',
|
|
self::CHAR_RIGHT_MID => '╢',
|
|
self::CHAR_MIDDLE => '│',
|
|
];
|
|
/**
|
|
* @var array table column widths
|
|
* @since 2.0.19
|
|
*/
|
|
protected $columnWidths = [];
|
|
/**
|
|
* @var int screen width
|
|
* @since 2.0.19
|
|
*/
|
|
protected $screenWidth;
|
|
/**
|
|
* @var string list prefix
|
|
* @since 2.0.19
|
|
*/
|
|
protected $listPrefix = '• ';
|
|
|
|
|
|
/**
|
|
* Set table headers.
|
|
*
|
|
* @param array $headers table headers
|
|
* @return $this
|
|
*/
|
|
public function setHeaders(array $headers)
|
|
{
|
|
$this->headers = array_values($headers);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set table rows.
|
|
*
|
|
* @param array $rows table rows
|
|
* @return $this
|
|
*/
|
|
public function setRows(array $rows)
|
|
{
|
|
$this->rows = array_map(function($row) {
|
|
return array_map(function($value) {
|
|
return empty($value) && !is_numeric($value) ? ' ' : $value;
|
|
}, array_values($row));
|
|
}, $rows);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set table chars.
|
|
*
|
|
* @param array $chars table chars
|
|
* @return $this
|
|
*/
|
|
public function setChars(array $chars)
|
|
{
|
|
$this->chars = $chars;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set screen width.
|
|
*
|
|
* @param int $width screen width
|
|
* @return $this
|
|
*/
|
|
public function setScreenWidth($width)
|
|
{
|
|
$this->screenWidth = $width;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set list prefix.
|
|
*
|
|
* @param string $listPrefix list prefix
|
|
* @return $this
|
|
*/
|
|
public function setListPrefix($listPrefix)
|
|
{
|
|
$this->listPrefix = $listPrefix;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string the rendered table
|
|
*/
|
|
public function run()
|
|
{
|
|
$this->calculateRowsSize();
|
|
$headerCount = count($this->headers);
|
|
|
|
$buffer = $this->renderSeparator(
|
|
$this->chars[self::CHAR_TOP_LEFT],
|
|
$this->chars[self::CHAR_TOP_MID],
|
|
$this->chars[self::CHAR_TOP],
|
|
$this->chars[self::CHAR_TOP_RIGHT]
|
|
);
|
|
// Header
|
|
if ($headerCount > 0) {
|
|
$buffer .= $this->renderRow($this->headers,
|
|
$this->chars[self::CHAR_LEFT],
|
|
$this->chars[self::CHAR_MIDDLE],
|
|
$this->chars[self::CHAR_RIGHT]
|
|
);
|
|
}
|
|
|
|
// Content
|
|
foreach ($this->rows as $i => $row) {
|
|
if ($i > 0 || $headerCount > 0) {
|
|
$buffer .= $this->renderSeparator(
|
|
$this->chars[self::CHAR_LEFT_MID],
|
|
$this->chars[self::CHAR_MID_MID],
|
|
$this->chars[self::CHAR_MID],
|
|
$this->chars[self::CHAR_RIGHT_MID]
|
|
);
|
|
}
|
|
$buffer .= $this->renderRow($row,
|
|
$this->chars[self::CHAR_LEFT],
|
|
$this->chars[self::CHAR_MIDDLE],
|
|
$this->chars[self::CHAR_RIGHT]);
|
|
}
|
|
|
|
$buffer .= $this->renderSeparator(
|
|
$this->chars[self::CHAR_BOTTOM_LEFT],
|
|
$this->chars[self::CHAR_BOTTOM_MID],
|
|
$this->chars[self::CHAR_BOTTOM],
|
|
$this->chars[self::CHAR_BOTTOM_RIGHT]
|
|
);
|
|
|
|
return $buffer;
|
|
}
|
|
|
|
/**
|
|
* Renders a row of data into a string.
|
|
*
|
|
* @param array $row row of data
|
|
* @param string $spanLeft character for left border
|
|
* @param string $spanMiddle character for middle border
|
|
* @param string $spanRight character for right border
|
|
* @return string
|
|
* @see \yii\console\widgets\Table::render()
|
|
*/
|
|
protected function renderRow(array $row, $spanLeft, $spanMiddle, $spanRight)
|
|
{
|
|
$size = $this->columnWidths;
|
|
|
|
$buffer = '';
|
|
$arrayPointer = [];
|
|
$finalChunk = [];
|
|
$alreadyPrintedCells = [];
|
|
for ($i = 0, ($max = $this->calculateRowHeight($row)) ?: $max = 1; $i < $max; $i++) {
|
|
$buffer .= $spanLeft . ' ';
|
|
foreach ($size as $index => $cellSize) {
|
|
$cell = isset($row[$index]) ? $row[$index] : null;
|
|
$prefix = '';
|
|
$chunk = '';
|
|
if ($index !== 0) {
|
|
$buffer .= $spanMiddle . ' ';
|
|
}
|
|
if (is_array($cell)) {
|
|
if (empty($finalChunk[$index])) {
|
|
$finalChunk[$index] = '';
|
|
$prefix = $this->listPrefix;
|
|
if (!isset($arrayPointer[$index])) {
|
|
$arrayPointer[$index] = 0;
|
|
}
|
|
}
|
|
$chunk = $cell[$arrayPointer[$index]];
|
|
$finalChunk[$index] .= $chunk;
|
|
if (isset($cell[$arrayPointer[$index] + 1]) && $finalChunk[$index] === $cell[$arrayPointer[$index]]) {
|
|
$arrayPointer[$index]++;
|
|
$finalChunk[$index] = '';
|
|
}
|
|
} else {
|
|
if (!isset($alreadyPrintedCells[$index])) {
|
|
$chunk = $cell;
|
|
}
|
|
$alreadyPrintedCells[$index] = true;
|
|
}
|
|
$chunk = $prefix . $chunk;
|
|
$repeat = $cellSize - Console::ansiStrwidth($chunk) - 1;
|
|
$buffer .= $chunk;
|
|
if ($repeat >= 0) {
|
|
$buffer .= str_repeat(' ', $repeat);
|
|
}
|
|
}
|
|
$buffer .= "$spanRight\n";
|
|
}
|
|
|
|
return $buffer;
|
|
}
|
|
|
|
/**
|
|
* Renders separator.
|
|
*
|
|
* @param string $spanLeft character for left border
|
|
* @param string $spanMid character for middle border
|
|
* @param string $spanMidMid character for middle-middle border
|
|
* @param string $spanRight character for right border
|
|
* @return string the generated separator row
|
|
* @see \yii\console\widgets\Table::render()
|
|
*/
|
|
protected function renderSeparator($spanLeft, $spanMid, $spanMidMid, $spanRight)
|
|
{
|
|
$separator = $spanLeft;
|
|
foreach ($this->columnWidths as $index => $rowSize) {
|
|
if ($index !== 0) {
|
|
$separator .= $spanMid;
|
|
}
|
|
$separator .= str_repeat($spanMidMid, $rowSize);
|
|
}
|
|
$separator .= $spanRight . "\n";
|
|
return $separator;
|
|
}
|
|
|
|
/**
|
|
* Calculate the size of rows to draw anchor of columns in console.
|
|
*
|
|
* @see \yii\console\widgets\Table::render()
|
|
*/
|
|
protected function calculateRowsSize()
|
|
{
|
|
$this->columnWidths = $columns = [];
|
|
$totalWidth = 0;
|
|
$screenWidth = $this->getScreenWidth() - self::CONSOLE_SCROLLBAR_OFFSET;
|
|
|
|
$headerCount = count($this->headers);
|
|
if (empty($this->rows)) {
|
|
$rowColCount = 0;
|
|
} else {
|
|
$rowColCount = max(array_map('count', $this->rows));
|
|
}
|
|
$count = max($headerCount, $rowColCount);
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$columns[] = ArrayHelper::getColumn($this->rows, $i);
|
|
if ($i < $headerCount) {
|
|
$columns[$i][] = $this->headers[$i];
|
|
}
|
|
}
|
|
|
|
foreach ($columns as $column) {
|
|
$columnWidth = max(array_map(function ($val) {
|
|
if (is_array($val)) {
|
|
return max(array_map('yii\helpers\Console::ansiStrwidth', $val)) + Console::ansiStrwidth($this->listPrefix);
|
|
}
|
|
return Console::ansiStrwidth($val);
|
|
}, $column)) + 2;
|
|
$this->columnWidths[] = $columnWidth;
|
|
$totalWidth += $columnWidth;
|
|
}
|
|
|
|
$relativeWidth = $screenWidth / $totalWidth;
|
|
|
|
if ($totalWidth > $screenWidth) {
|
|
foreach ($this->columnWidths as $j => $width) {
|
|
$this->columnWidths[$j] = (int) ($width * $relativeWidth);
|
|
if ($j === count($this->columnWidths)) {
|
|
$this->columnWidths = $totalWidth;
|
|
}
|
|
$totalWidth -= $this->columnWidths[$j];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the height of a row.
|
|
*
|
|
* @param array $row
|
|
* @return int maximum row per cell
|
|
* @see \yii\console\widgets\Table::render()
|
|
*/
|
|
protected function calculateRowHeight($row)
|
|
{
|
|
$rowsPerCell = array_map(function ($size, $columnWidth) {
|
|
if (is_array($columnWidth)) {
|
|
$rows = 0;
|
|
foreach ($columnWidth as $width) {
|
|
$rows += $size == 2 ? 0 : ceil($width / ($size - 2));
|
|
}
|
|
return $rows;
|
|
}
|
|
return $size == 2 || $columnWidth == 0 ? 0 : ceil($columnWidth / ($size - 2));
|
|
}, $this->columnWidths, array_map(function ($val) {
|
|
if (is_array($val)) {
|
|
return array_map('yii\helpers\Console::ansiStrwidth', $val);
|
|
}
|
|
return Console::ansiStrwidth($val);
|
|
}, $row));
|
|
return max($rowsPerCell);
|
|
}
|
|
|
|
/**
|
|
* Getting screen width.
|
|
* If it is not able to determine screen width, default value `123` will be set.
|
|
*
|
|
* @return int screen width
|
|
*/
|
|
protected function getScreenWidth()
|
|
{
|
|
if (!$this->screenWidth) {
|
|
$size = Console::getScreenSize();
|
|
$this->screenWidth = isset($size[0])
|
|
? $size[0]
|
|
: self::DEFAULT_CONSOLE_SCREEN_WIDTH + self::CONSOLE_SCROLLBAR_OFFSET;
|
|
}
|
|
return $this->screenWidth;
|
|
}
|
|
}
|