From 66028fe19f4949ffacea59736fde0775391434ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 25 Jul 2019 16:18:44 +0200 Subject: [PATCH] [Console] Added support for definition list --- src/Symfony/Component/Console/CHANGELOG.md | 1 + .../Component/Console/Helper/Table.php | 50 ++++++++++++-- .../Component/Console/Style/SymfonyStyle.php | 66 +++++++++++++++++++ .../Style/SymfonyStyle/command/command_18.php | 18 +++++ .../Style/SymfonyStyle/command/command_19.php | 12 ++++ .../Style/SymfonyStyle/output/output_18.txt | 8 +++ .../Style/SymfonyStyle/output/output_19.txt | 7 ++ .../Console/Tests/Helper/TableTest.php | 55 ++++++++++++++++ 8 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_18.php create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_19.php create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_18.txt create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_19.txt diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index 17c05c9e42..b9e837a8d1 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -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 ----- diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php index 5af631559d..80257aa8cc 100644 --- a/src/Symfony/Component/Console/Helper/Table.php +++ b/src/Symfony/Component/Console/Helper/Table.php @@ -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); diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index 962ba923f3..4a104ca3fc 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -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('%s'); + + $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('%s'); + + $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} */ diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_18.php b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_18.php new file mode 100644 index 0000000000..d4afa45cf3 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_18.php @@ -0,0 +1,18 @@ +definitionList( + ['foo' => 'bar'], + new TableSeparator(), + 'this is a title', + new TableSeparator(), + ['foo2' => 'bar2'] + ); +}; diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_19.php b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_19.php new file mode 100644 index 0000000000..e44b18b766 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_19.php @@ -0,0 +1,12 @@ +horizontalTable(['a', 'b', 'c', 'd'], [[1, 2, 3], [4, 5], [7, 8, 9]]); +}; diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_18.txt b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_18.txt new file mode 100644 index 0000000000..e836b26f38 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_18.txt @@ -0,0 +1,8 @@ + ---------- --------- + foo bar + ---------- --------- + this is a title + ---------- --------- + foo2 bar2 + ---------- --------- + diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_19.txt b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_19.txt new file mode 100644 index 0000000000..cc7d9c7e62 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_19.txt @@ -0,0 +1,7 @@ + --- --- --- --- + a 1 4 7 + b 2 5 8 + c 3 9 + d + --- --- --- --- + diff --git a/src/Symfony/Component/Console/Tests/Helper/TableTest.php b/src/Symfony/Component/Console/Tests/Helper/TableTest.php index a3c2d30b79..124309dd5b 100644 --- a/src/Symfony/Component/Console/Tests/Helper/TableTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/TableTest.php @@ -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 = <<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);