yii2

Форк
1
436 строк · 13.2 Кб
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7

8
namespace yii\console\widgets;
9

10
use Yii;
11
use yii\base\Widget;
12
use yii\helpers\ArrayHelper;
13
use yii\helpers\Console;
14

15
/**
16
 * Table class displays a table in console.
17
 *
18
 * For example,
19
 *
20
 * ```php
21
 * $table = new Table();
22
 *
23
 * echo $table
24
 *     ->setHeaders(['test1', 'test2', 'test3'])
25
 *     ->setRows([
26
 *         ['col1', 'col2', 'col3'],
27
 *         ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
28
 *     ])
29
 *     ->run();
30
 * ```
31
 *
32
 * or
33
 *
34
 * ```php
35
 * echo Table::widget([
36
 *     'headers' => ['test1', 'test2', 'test3'],
37
 *     'rows' => [
38
 *         ['col1', 'col2', 'col3'],
39
 *         ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
40
 *     ],
41
 * ]);
42
 *
43
 * @property-write string $listPrefix List prefix.
44
 * @property-write int $screenWidth Screen width.
45
 *
46
 * @author Daniel Gomez Pan <pana_1990@hotmail.com>
47
 * @since 2.0.13
48
 */
49
class Table extends Widget
50
{
51
    const DEFAULT_CONSOLE_SCREEN_WIDTH = 120;
52
    const CONSOLE_SCROLLBAR_OFFSET = 3;
53
    const CHAR_TOP = 'top';
54
    const CHAR_TOP_MID = 'top-mid';
55
    const CHAR_TOP_LEFT = 'top-left';
56
    const CHAR_TOP_RIGHT = 'top-right';
57
    const CHAR_BOTTOM = 'bottom';
58
    const CHAR_BOTTOM_MID = 'bottom-mid';
59
    const CHAR_BOTTOM_LEFT = 'bottom-left';
60
    const CHAR_BOTTOM_RIGHT = 'bottom-right';
61
    const CHAR_LEFT = 'left';
62
    const CHAR_LEFT_MID = 'left-mid';
63
    const CHAR_MID = 'mid';
64
    const CHAR_MID_MID = 'mid-mid';
65
    const CHAR_RIGHT = 'right';
66
    const CHAR_RIGHT_MID = 'right-mid';
67
    const CHAR_MIDDLE = 'middle';
68

69
    /**
70
     * @var array table headers
71
     * @since 2.0.19
72
     */
73
    protected $headers = [];
74
    /**
75
     * @var array table rows
76
     * @since 2.0.19
77
     */
78
    protected $rows = [];
79
    /**
80
     * @var array table chars
81
     * @since 2.0.19
82
     */
83
    protected $chars = [
84
        self::CHAR_TOP => '═',
85
        self::CHAR_TOP_MID => '╤',
86
        self::CHAR_TOP_LEFT => '╔',
87
        self::CHAR_TOP_RIGHT => '╗',
88
        self::CHAR_BOTTOM => '═',
89
        self::CHAR_BOTTOM_MID => '╧',
90
        self::CHAR_BOTTOM_LEFT => '╚',
91
        self::CHAR_BOTTOM_RIGHT => '╝',
92
        self::CHAR_LEFT => '║',
93
        self::CHAR_LEFT_MID => '╟',
94
        self::CHAR_MID => '─',
95
        self::CHAR_MID_MID => '┼',
96
        self::CHAR_RIGHT => '║',
97
        self::CHAR_RIGHT_MID => '╢',
98
        self::CHAR_MIDDLE => '│',
99
    ];
100
    /**
101
     * @var array table column widths
102
     * @since 2.0.19
103
     */
104
    protected $columnWidths = [];
105
    /**
106
     * @var int screen width
107
     * @since 2.0.19
108
     */
109
    protected $screenWidth;
110
    /**
111
     * @var string list prefix
112
     * @since 2.0.19
113
     */
114
    protected $listPrefix = '• ';
115

116

117
    /**
118
     * Set table headers.
119
     *
120
     * @param array $headers table headers
121
     * @return $this
122
     */
123
    public function setHeaders(array $headers)
124
    {
125
        $this->headers = array_values($headers);
126
        return $this;
127
    }
128

129
    /**
130
     * Set table rows.
131
     *
132
     * @param array $rows table rows
133
     * @return $this
134
     */
135
    public function setRows(array $rows)
136
    {
137
        $this->rows = array_map(function($row) {
138
            return array_map(function($value) {
139
                return empty($value) && !is_numeric($value)
140
                    ? ' '
141
                    :  (is_array($value)
142
                        ? array_values($value)
143
                        : $value);
144
            }, array_values($row));
145
        }, $rows);
146
        return $this;
147
    }
148

149
    /**
150
     * Set table chars.
151
     *
152
     * @param array $chars table chars
153
     * @return $this
154
     */
155
    public function setChars(array $chars)
156
    {
157
        $this->chars = $chars;
158
        return $this;
159
    }
160

161
    /**
162
     * Set screen width.
163
     *
164
     * @param int $width screen width
165
     * @return $this
166
     */
167
    public function setScreenWidth($width)
168
    {
169
        $this->screenWidth = $width;
170
        return $this;
171
    }
172

173
    /**
174
     * Set list prefix.
175
     *
176
     * @param string $listPrefix list prefix
177
     * @return $this
178
     */
179
    public function setListPrefix($listPrefix)
180
    {
181
        $this->listPrefix = $listPrefix;
182
        return $this;
183
    }
184

185
    /**
186
     * @return string the rendered table
187
     */
188
    public function run()
189
    {
190
        $this->calculateRowsSize();
191
        $headerCount = count($this->headers);
192

193
        $buffer = $this->renderSeparator(
194
            $this->chars[self::CHAR_TOP_LEFT],
195
            $this->chars[self::CHAR_TOP_MID],
196
            $this->chars[self::CHAR_TOP],
197
            $this->chars[self::CHAR_TOP_RIGHT]
198
        );
199
        // Header
200
        if ($headerCount > 0) {
201
            $buffer .= $this->renderRow($this->headers,
202
                $this->chars[self::CHAR_LEFT],
203
                $this->chars[self::CHAR_MIDDLE],
204
                $this->chars[self::CHAR_RIGHT]
205
            );
206
        }
207

208
        // Content
209
        foreach ($this->rows as $i => $row) {
210
            if ($i > 0 || $headerCount > 0) {
211
                $buffer .= $this->renderSeparator(
212
                    $this->chars[self::CHAR_LEFT_MID],
213
                    $this->chars[self::CHAR_MID_MID],
214
                    $this->chars[self::CHAR_MID],
215
                    $this->chars[self::CHAR_RIGHT_MID]
216
                );
217
            }
218
            $buffer .= $this->renderRow($row,
219
                $this->chars[self::CHAR_LEFT],
220
                $this->chars[self::CHAR_MIDDLE],
221
                $this->chars[self::CHAR_RIGHT]);
222
        }
223

224
        $buffer .= $this->renderSeparator(
225
            $this->chars[self::CHAR_BOTTOM_LEFT],
226
            $this->chars[self::CHAR_BOTTOM_MID],
227
            $this->chars[self::CHAR_BOTTOM],
228
            $this->chars[self::CHAR_BOTTOM_RIGHT]
229
        );
230

231
        return $buffer;
232
    }
233

234
    /**
235
     * Renders a row of data into a string.
236
     *
237
     * @param array $row row of data
238
     * @param string $spanLeft character for left border
239
     * @param string $spanMiddle character for middle border
240
     * @param string $spanRight character for right border
241
     * @return string
242
     * @see \yii\console\widgets\Table::render()
243
     */
244
    protected function renderRow(array $row, $spanLeft, $spanMiddle, $spanRight)
245
    {
246
        $size = $this->columnWidths;
247

248
        $buffer = '';
249
        $arrayPointer = [];
250
        $renderedChunkTexts = [];
251
        for ($i = 0, ($max = $this->calculateRowHeight($row)) ?: $max = 1; $i < $max; $i++) {
252
            $buffer .= $spanLeft . ' ';
253
            foreach ($size as $index => $cellSize) {
254
                $cell = isset($row[$index]) ? $row[$index] : null;
255
                $prefix = '';
256
                if ($index !== 0) {
257
                    $buffer .= $spanMiddle . ' ';
258
                }
259

260
                $arrayFromMultilineString = false;
261
                if (is_string($cell)) {
262
                    $cellLines = explode(PHP_EOL, $cell);
263
                    if (count($cellLines) > 1) {
264
                        $cell = $cellLines;
265
                        $arrayFromMultilineString = true;
266
                    }
267
                }
268

269
                if (is_array($cell)) {
270
                    if (empty($renderedChunkTexts[$index])) {
271
                        $renderedChunkTexts[$index] = '';
272
                        $start = 0;
273
                        $prefix = $arrayFromMultilineString ? '' : $this->listPrefix;
274
                        if (!isset($arrayPointer[$index])) {
275
                            $arrayPointer[$index] = 0;
276
                        }
277
                    } else {
278
                        $start = mb_strwidth($renderedChunkTexts[$index], Yii::$app->charset);
279
                    }
280
                    $chunk = Console::ansiColorizedSubstr(
281
                        $cell[$arrayPointer[$index]],
282
                        $start,
283
                        $cellSize - 2 - Console::ansiStrwidth($prefix)
284
                    );
285
                    $renderedChunkTexts[$index] .= Console::stripAnsiFormat($chunk);
286
                    $fullChunkText = Console::stripAnsiFormat($cell[$arrayPointer[$index]]);
287
                    if (isset($cell[$arrayPointer[$index] + 1]) && $renderedChunkTexts[$index] === $fullChunkText) {
288
                        $arrayPointer[$index]++;
289
                        $renderedChunkTexts[$index] = '';
290
                    }
291
                } else {
292
                    $chunk = Console::ansiColorizedSubstr($cell, ($cellSize * $i) - ($i * 2), $cellSize - 2);
293
                }
294
                $chunk = $prefix . $chunk;
295
                $repeat = $cellSize - Console::ansiStrwidth($chunk) - 1;
296
                $buffer .= $chunk;
297
                if ($repeat >= 0) {
298
                    $buffer .= str_repeat(' ', $repeat);
299
                }
300
            }
301
            $buffer .= "$spanRight\n";
302
        }
303

304
        return $buffer;
305
    }
306

307
    /**
308
     * Renders separator.
309
     *
310
     * @param string $spanLeft character for left border
311
     * @param string $spanMid character for middle border
312
     * @param string $spanMidMid character for middle-middle border
313
     * @param string $spanRight character for right border
314
     * @return string the generated separator row
315
     * @see \yii\console\widgets\Table::render()
316
     */
317
    protected function renderSeparator($spanLeft, $spanMid, $spanMidMid, $spanRight)
318
    {
319
        $separator = $spanLeft;
320
        foreach ($this->columnWidths as $index => $rowSize) {
321
            if ($index !== 0) {
322
                $separator .= $spanMid;
323
            }
324
            $separator .= str_repeat($spanMidMid, $rowSize);
325
        }
326
        $separator .= $spanRight . "\n";
327
        return $separator;
328
    }
329

330
    /**
331
     * Calculate the size of rows to draw anchor of columns in console.
332
     *
333
     * @see \yii\console\widgets\Table::render()
334
     */
335
    protected function calculateRowsSize()
336
    {
337
        $this->columnWidths = $columns = [];
338
        $totalWidth = 0;
339
        $screenWidth = $this->getScreenWidth() - self::CONSOLE_SCROLLBAR_OFFSET;
340

341
        $headerCount = count($this->headers);
342
        if (empty($this->rows)) {
343
            $rowColCount = 0;
344
        } else {
345
            $rowColCount = max(array_map('count', $this->rows));
346
        }
347
        $count = max($headerCount, $rowColCount);
348
        for ($i = 0; $i < $count; $i++) {
349
            $columns[] = ArrayHelper::getColumn($this->rows, $i);
350
            if ($i < $headerCount) {
351
                $columns[$i][] = $this->headers[$i];
352
            }
353
        }
354

355
        foreach ($columns as $column) {
356
            $columnWidth = max(array_map(function ($val) {
357
                if (is_array($val)) {
358
                    return max(array_map('yii\helpers\Console::ansiStrwidth', $val)) + Console::ansiStrwidth($this->listPrefix);
359
                }
360
                if (is_string($val)) {
361
                    return max(array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val)));
362
                }
363
                return Console::ansiStrwidth($val);
364
            }, $column)) + 2;
365
            $this->columnWidths[] = $columnWidth;
366
            $totalWidth += $columnWidth;
367
        }
368

369
        if ($totalWidth > $screenWidth) {
370
            $minWidth = 3;
371
            $fixWidths = [];
372
            $relativeWidth = $screenWidth / $totalWidth;
373
            foreach ($this->columnWidths as $j => $width) {
374
                $scaledWidth = (int) ($width * $relativeWidth);
375
                if ($scaledWidth < $minWidth) {
376
                    $fixWidths[$j] = 3;
377
                }
378
            }
379

380
            $totalFixWidth = array_sum($fixWidths);
381
            $relativeWidth = ($screenWidth - $totalFixWidth) / ($totalWidth - $totalFixWidth);
382
            foreach ($this->columnWidths as $j => $width) {
383
                if (!array_key_exists($j, $fixWidths)) {
384
                    $this->columnWidths[$j] = (int) ($width * $relativeWidth);
385
                }
386
            }
387
        }
388
    }
389

390
    /**
391
     * Calculate the height of a row.
392
     *
393
     * @param array $row
394
     * @return int maximum row per cell
395
     * @see \yii\console\widgets\Table::render()
396
     */
397
    protected function calculateRowHeight($row)
398
    {
399
        $rowsPerCell = array_map(function ($size, $columnWidth) {
400
            if (is_array($columnWidth)) {
401
                $rows = 0;
402
                foreach ($columnWidth as $width) {
403
                    $rows +=  $size == 2 ? 0 : ceil($width / ($size - 2));
404
                }
405
                return $rows;
406
            }
407
            return $size == 2 || $columnWidth == 0 ? 0 : ceil($columnWidth / ($size - 2));
408
        }, $this->columnWidths, array_map(function ($val) {
409
            if (is_array($val)) {
410
                return array_map('yii\helpers\Console::ansiStrwidth', $val);
411
            }
412
            if (is_string($val)) {
413
                return array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val));
414
            }
415
            return Console::ansiStrwidth($val);
416
        }, $row));
417
        return max($rowsPerCell);
418
    }
419

420
    /**
421
     * Getting screen width.
422
     * If it is not able to determine screen width, default value `123` will be set.
423
     *
424
     * @return int screen width
425
     */
426
    protected function getScreenWidth()
427
    {
428
        if (!$this->screenWidth) {
429
            $size = Console::getScreenSize();
430
            $this->screenWidth = isset($size[0])
431
                ? $size[0]
432
                : self::DEFAULT_CONSOLE_SCREEN_WIDTH + self::CONSOLE_SCROLLBAR_OFFSET;
433
        }
434
        return $this->screenWidth;
435
    }
436
}
437

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.