diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 894b1ad2a5d..dca3886227c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -10,6 +10,7 @@ Yii Framework 2 Change Log - Enh #19853: Added support for default value for `\yii\helpers\Console::select()` (rhertogh) - Bug #19868: Added whitespace sanitation for tests, due to updates in ICU 72 (schmunk42) - Enh #19884: Added support Enums in Query Builder (sk1t0n) +- Bug #19906: Fixed multiline strings in the `\yii\console\widgets\Table` widget (rhertogh) 2.0.48.1 May 24, 2023 diff --git a/framework/console/widgets/Table.php b/framework/console/widgets/Table.php index 4862e88b6be..658cf5fd55b 100644 --- a/framework/console/widgets/Table.php +++ b/framework/console/widgets/Table.php @@ -252,18 +252,32 @@ protected function renderRow(array $row, $spanLeft, $spanMiddle, $spanRight) if ($index !== 0) { $buffer .= $spanMiddle . ' '; } + + $arrayFromMultilineString = false; + if (is_string($cell)) { + $cellLines = explode(PHP_EOL, $cell); + if (count($cellLines) > 1) { + $cell = $cellLines; + $arrayFromMultilineString = true; + } + } + if (is_array($cell)) { if (empty($renderedChunkTexts[$index])) { $renderedChunkTexts[$index] = ''; $start = 0; - $prefix = $this->listPrefix; + $prefix = $arrayFromMultilineString ? '' : $this->listPrefix; if (!isset($arrayPointer[$index])) { $arrayPointer[$index] = 0; } } else { $start = mb_strwidth($renderedChunkTexts[$index], Yii::$app->charset); } - $chunk = Console::ansiColorizedSubstr($cell[$arrayPointer[$index]], $start, $cellSize - 4); + $chunk = Console::ansiColorizedSubstr( + $cell[$arrayPointer[$index]], + $start, + $cellSize - 2 - Console::ansiStrwidth($prefix) + ); $renderedChunkTexts[$index] .= Console::stripAnsiFormat($chunk); $fullChunkText = Console::stripAnsiFormat($cell[$arrayPointer[$index]]); if (isset($cell[$arrayPointer[$index] + 1]) && $renderedChunkTexts[$index] === $fullChunkText) { @@ -339,6 +353,9 @@ protected function calculateRowsSize() if (is_array($val)) { return max(array_map('yii\helpers\Console::ansiStrwidth', $val)) + Console::ansiStrwidth($this->listPrefix); } + if (is_string($val)) { + return max(array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val))); + } return Console::ansiStrwidth($val); }, $column)) + 2; $this->columnWidths[] = $columnWidth; @@ -388,6 +405,9 @@ protected function calculateRowHeight($row) if (is_array($val)) { return array_map('yii\helpers\Console::ansiStrwidth', $val); } + if (is_string($val)) { + return array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val)); + } return Console::ansiStrwidth($val); }, $row)); return max($rowsPerCell); diff --git a/tests/framework/console/widgets/TableTest.php b/tests/framework/console/widgets/TableTest.php index 95e7c60e462..b5bf6f2c6dc 100644 --- a/tests/framework/console/widgets/TableTest.php +++ b/tests/framework/console/widgets/TableTest.php @@ -58,6 +58,121 @@ public function testTable($headers, $rows) ║ testcontent21 │ testcontent22 │ testcontent23 ║ ╚═══════════════╧═══════════════╧═══════════════╝ +EXPECTED; + + $tableContent = $table + ->setHeaders($headers) + ->setRows($rows) + ->setScreenWidth(200) + ->run(); + $this->assertEqualsWithoutLE($expected, $tableContent); + } + + public function getMultiLineTableData() + { + return [ + [ + ['test1', 'test2', 'test3' . PHP_EOL . 'multiline'], + [ + ['test' . PHP_EOL . 'content1', 'testcontent2', 'test' . PHP_EOL . 'content3'], + [ + 'testcontent21', + 'testcontent22' . PHP_EOL + . 'loooooooooooooooooooooooooooooooooooong' . PHP_EOL + . 'content', + 'testcontent23' . PHP_EOL + . 'loooooooooooooooooooooooooooooooooooong content' + ], + ] + ], + [ + ['key1' => 'test1', 'key2' => 'test2', 'key3' => 'test3' . PHP_EOL . 'multiline'], + [ + [ + 'key1' => 'test' . PHP_EOL . 'content1', + 'key2' => 'testcontent2', + 'key3' => 'test' . PHP_EOL . 'content3' + ], + [ + 'key1' => 'testcontent21', + 'key2' => 'testcontent22' . PHP_EOL + . 'loooooooooooooooooooooooooooooooooooong' . PHP_EOL + . 'content', + 'key3' => 'testcontent23' . PHP_EOL + . 'loooooooooooooooooooooooooooooooooooong content' + ], + ] + ] + ]; + } + + /** + * @dataProvider getMultiLineTableData + */ + public function testMultiLineTable($headers, $rows) + { + $table = new Table(); + + $expected = <<<'EXPECTED' +╔═════════════╤═════════════════════════════════════╤═════════════════════════════════════════════╗ +║ test1 │ test2 │ test3 ║ +║ │ │ multiline ║ +╟─────────────┼─────────────────────────────────────┼─────────────────────────────────────────────╢ +║ test │ testcontent2 │ test ║ +║ content1 │ │ content3 ║ +╟─────────────┼─────────────────────────────────────┼─────────────────────────────────────────────╢ +║ testcontent │ testcontent22 │ testcontent23 ║ +║ 21 │ loooooooooooooooooooooooooooooooooo │ loooooooooooooooooooooooooooooooooooong con ║ +║ │ oong │ tent ║ +║ │ content │ ║ +╚═════════════╧═════════════════════════════════════╧═════════════════════════════════════════════╝ + +EXPECTED; + + $tableContent = $table + ->setHeaders($headers) + ->setRows($rows) + ->setScreenWidth(100) + ->run(); + $this->assertEqualsWithoutLE($expected, $tableContent); + } + + public function getNumericTableData() + { + return [ + [ + [1, 2, 3], + [ + [1, 1.2, -1.3], + [-2, 2.2, 2.3], + ] + ], + [ + ['key1' => 1, 'key2' => 2, 'key3' => 3], + [ + ['key1' => 1, 'key2' => 1.2, 'key3' => -1.3], + ['key1' => -2, 'key2' => 2.2, 'key3' => 2.3], + ] + ] + ]; + } + + /** + * @dataProvider getNumericTableData + */ + public function testNumericTable($headers, $rows) + { + $table = new Table(); + + $expected = <<<'EXPECTED' +╔════╤═════╤══════╗ +║ 1 │ 2 │ 3 ║ +╟────┼─────┼──────╢ +║ 1 │ 1.2 │ -1.3 ║ +╟────┼─────┼──────╢ +║ -2 │ 2.2 │ 2.3 ║ +╚════╧═════╧══════╝ + EXPECTED; $tableContent = $table @@ -141,6 +256,35 @@ public function testListPrefix() ); } + public function testLongerListPrefix() + { + $table = new Table(); + + $expected = <<<'EXPECTED' +╔═════════════════════════════════╤═════════════════════════════════╤═════════════════════════════╗ +║ test1 │ test2 │ test3 ║ +╟─────────────────────────────────┼─────────────────────────────────┼─────────────────────────────╢ +║ testcontent1 │ testcontent2 │ testcontent3 ║ +╟─────────────────────────────────┼─────────────────────────────────┼─────────────────────────────╢ +║ testcontent21 with looooooooooo │ testcontent22 with looooooooooo │ -- col1 with looooooooooooo ║ +║ ooooooooooooong content │ ooooooooooooong content │ ooooooooooong content ║ +║ │ │ -- col2 with long content ║ +╚═════════════════════════════════╧═════════════════════════════════╧═════════════════════════════╝ + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $table->setHeaders(['test1', 'test2', 'test3']) + ->setRows([ + ['testcontent1', 'testcontent2', 'testcontent3'], + [ + 'testcontent21 with loooooooooooooooooooooooong content', + 'testcontent22 with loooooooooooooooooooooooong content', + ['col1 with loooooooooooooooooooooooong content', 'col2 with long content'] + ], + ])->setScreenWidth(100)->setListPrefix('-- ')->run() + ); + } + public function testCustomChars() { $table = new Table();