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:
commit
8f84347ec3
@ -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
|
||||
-----
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
*/
|
||||
|
@ -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']
|
||||
);
|
||||
};
|
@ -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]]);
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
---------- ---------
|
||||
foo bar
|
||||
---------- ---------
|
||||
this is a title
|
||||
---------- ---------
|
||||
foo2 bar2
|
||||
---------- ---------
|
||||
|
@ -0,0 +1,7 @@
|
||||
--- --- --- ---
|
||||
a 1 4 7
|
||||
b 2 5 8
|
||||
c 3 9
|
||||
d
|
||||
--- --- --- ---
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user