feature #32742 [Console] Added support for definition list and horizontal table (lyrixx)

This PR was merged into the 4.4 branch.

Discussion
----------

[Console] Added support for definition list and horizontal table

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

I need that in a projet where I want to display some data horizontally.

Usage:
```php
<?php

use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Style\SymfonyStyle;

require __DIR__.'/vendor/autoload.php';

$io = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput());

$io->table(['a', 'b', 'c'], [[1, 2, 3], [4, 5, 6]]);

$io->table(['a', 'b', 'c'], [[1, 2, 3], [4, 5, 6]], true);

$io->definitionList(
    ['foo' => 'bar'],
    new TableSeparator(),
    'this is a title',
    new TableSeparator(),
    ['foo2' => 'bar2']
);
```

![image](https://user-images.githubusercontent.com/408368/63788677-2df43580-c8f6-11e9-9ce6-b7abcecf7f24.png)

Commits
-------

66028fe19f [Console] Added support for definition list
This commit is contained in:
Fabien Potencier 2019-09-08 08:47:16 +02:00
commit 8f84347ec3
8 changed files with 210 additions and 7 deletions

View File

@ -8,6 +8,7 @@ CHANGELOG
* added method `preventRedrawFasterThan()` and `forceRedrawSlowerThan()` on `ProgressBar`
* `Application` implements `ResetInterface`
* marked all dispatched event classes as `@final`
* added support for displaying table horizontally
4.3.0
-----

View File

@ -48,6 +48,7 @@ class Table
* Table rows.
*/
private $rows = [];
private $horizontal = false;
/**
* Column widths cache.
@ -322,6 +323,13 @@ class Table
return $this;
}
public function setHorizontal(bool $horizontal = true): self
{
$this->horizontal = $horizontal;
return $this;
}
/**
* Renders table to output.
*
@ -337,14 +345,35 @@ class Table
*/
public function render()
{
$rows = array_merge($this->headers, [$divider = new TableSeparator()], $this->rows);
$divider = new TableSeparator();
if ($this->horizontal) {
$rows = [];
foreach ($this->headers[0] ?? [] as $i => $header) {
$rows[$i] = [$header];
foreach ($this->rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
if (isset($row[$i])) {
$rows[$i][] = $row[$i];
} elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) {
// Noop, there is a "title"
} else {
$rows[$i][] = null;
}
}
}
} else {
$rows = array_merge($this->headers, [$divider], $this->rows);
}
$this->calculateNumberOfColumns($rows);
$rows = $this->buildTableRows($rows);
$this->calculateColumnsWidth($rows);
$isHeader = true;
$isFirstRow = false;
$isHeader = !$this->horizontal;
$isFirstRow = $this->horizontal;
foreach ($rows as $row) {
if ($divider === $row) {
$isHeader = false;
@ -369,8 +398,11 @@ class Table
$this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat());
}
}
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
if ($this->horizontal) {
$this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
} else {
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
}
}
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
@ -450,13 +482,17 @@ class Table
*
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*/
private function renderRow(array $row, string $cellFormat)
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
{
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
$columns = $this->getRowColumns($row);
$last = \count($columns) - 1;
foreach ($columns as $i => $column) {
$rowContent .= $this->renderCell($row, $column, $cellFormat);
if ($firstCellFormat && 0 === $i) {
$rowContent .= $this->renderCell($row, $column, $firstCellFormat);
} else {
$rowContent .= $this->renderCell($row, $column, $cellFormat);
}
$rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
}
$this->output->writeln($rowContent);

View File

@ -11,12 +11,15 @@
namespace Symfony\Component\Console\Style;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
@ -190,6 +193,69 @@ class SymfonyStyle extends OutputStyle
$this->newLine();
}
/**
* Formats a horizontal table.
*/
public function horizontalTable(array $headers, array $rows)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->setHorizontal(true);
$table->render();
$this->newLine();
}
/**
* Formats a list of key/value horizontally.
*
* Each row can be one of:
* * 'A title'
* * ['key' => 'value']
* * new TableSeparator()
*
* @param string|array|TableSeparator ...$list
*/
public function definitionList(...$list)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$headers = [];
$row = [];
foreach ($list as $value) {
if ($value instanceof TableSeparator) {
$headers[] = $value;
$row[] = $value;
continue;
}
if (\is_string($value)) {
$headers[] = new TableCell($value, ['colspan' => 2]);
$row[] = null;
continue;
}
if (!\is_array($value)) {
throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.');
}
$headers[] = key($value);
$row[] = current($value);
}
$table->setHeaders($headers);
$table->setRows([$row]);
$table->setHorizontal();
$table->setStyle($style);
$table->render();
$this->newLine();
}
/**
* {@inheritdoc}
*/

View File

@ -0,0 +1,18 @@
<?php
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
return function (InputInterface $input, OutputInterface $output) {
$output = new SymfonyStyle($input, $output);
$output->definitionList(
['foo' => 'bar'],
new TableSeparator(),
'this is a title',
new TableSeparator(),
['foo2' => 'bar2']
);
};

View File

@ -0,0 +1,12 @@
<?php
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
//Ensure formatting tables when using multiple headers with TableCell
return function (InputInterface $input, OutputInterface $output) {
$output = new SymfonyStyle($input, $output);
$output->horizontalTable(['a', 'b', 'c', 'd'], [[1, 2, 3], [4, 5], [7, 8, 9]]);
};

View File

@ -0,0 +1,8 @@
---------- ---------
foo bar
---------- ---------
this is a title
---------- ---------
foo2 bar2
---------- ---------

View File

@ -0,0 +1,7 @@
--- --- --- ---
a 1 4 7
b 2 5 8
c 3 9
d
--- --- --- ---

View File

@ -1125,6 +1125,61 @@ TABLE;
$this->assertSame($expected, $this->getOutputContent($output));
}
public function provideRenderHorizontalTests()
{
$headers = ['foo', 'bar', 'baz'];
$rows = [['one', 'two', 'tree'], ['1', '2', '3']];
$expected = <<<EOTXT
+-----+------+---+
| foo | one | 1 |
| bar | two | 2 |
| baz | tree | 3 |
+-----+------+---+
EOTXT;
yield [$headers, $rows, $expected];
$headers = ['foo', 'bar', 'baz'];
$rows = [['one', 'two'], ['1']];
$expected = <<<EOTXT
+-----+-----+---+
| foo | one | 1 |
| bar | two | |
| baz | | |
+-----+-----+---+
EOTXT;
yield [$headers, $rows, $expected];
$headers = ['foo', 'bar', 'baz'];
$rows = [['one', 'two', 'tree'], new TableSeparator(), ['1', '2', '3']];
$expected = <<<EOTXT
+-----+------+---+
| foo | one | 1 |
| bar | two | 2 |
| baz | tree | 3 |
+-----+------+---+
EOTXT;
yield [$headers, $rows, $expected];
}
/**
* @dataProvider provideRenderHorizontalTests
*/
public function testRenderHorizontal(array $headers, array $rows, string $expected)
{
$table = new Table($output = $this->getOutputStream());
$table
->setHeaders($headers)
->setRows($rows)
->setHorizontal()
;
$table->render();
$this->assertEquals($expected, $this->getOutputContent($output));
}
protected function getOutputStream($decorated = false)
{
return new StreamOutput($this->stream, StreamOutput::VERBOSITY_NORMAL, $decorated);