diff options
Diffstat (limited to 'vendor/symfony/console/Helper/Table.php')
-rw-r--r-- | vendor/symfony/console/Helper/Table.php | 570 |
1 files changed, 370 insertions, 200 deletions
diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php index 1434562..99496b1 100644 --- a/vendor/symfony/console/Helper/Table.php +++ b/vendor/symfony/console/Helper/Table.php @@ -11,8 +11,12 @@ namespace Symfony\Component\Console\Helper; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; /** * Provides helpers to display a table. @@ -21,34 +25,40 @@ use Symfony\Component\Console\Exception\InvalidArgumentException; * @author Саша Стаменковић <umpirsky@gmail.com> * @author Abdellatif Ait boudad <a.aitboudad@gmail.com> * @author Max Grigorian <maxakawizard@gmail.com> + * @author Dany Maillard <danymaillard93b@gmail.com> */ class Table { + private const SEPARATOR_TOP = 0; + private const SEPARATOR_TOP_BOTTOM = 1; + private const SEPARATOR_MID = 2; + private const SEPARATOR_BOTTOM = 3; + private const BORDER_OUTSIDE = 0; + private const BORDER_INSIDE = 1; + + private $headerTitle; + private $footerTitle; + /** * Table headers. - * - * @var array */ - private $headers = array(); + private $headers = []; /** * Table rows. - * - * @var array */ - private $rows = array(); + private $rows = []; + private $horizontal = false; /** * Column widths cache. - * - * @var array */ - private $effectiveColumnWidths = array(); + private $effectiveColumnWidths = []; /** * Number of columns cache. * - * @var array + * @var int */ private $numberOfColumns; @@ -65,17 +75,20 @@ class Table /** * @var array */ - private $columnStyles = array(); + private $columnStyles = []; /** * User set column widths. * * @var array */ - private $columnWidths = array(); + private $columnWidths = []; + private $columnMaxWidths = []; private static $styles; + private $rendered = false; + public function __construct(OutputInterface $output) { $this->output = $output; @@ -90,8 +103,7 @@ class Table /** * Sets a style definition. * - * @param string $name The style name - * @param TableStyle $style A TableStyle instance + * @param string $name The style name */ public static function setStyleDefinition($name, TableStyle $style) { @@ -107,7 +119,7 @@ class Table * * @param string $name The style name * - * @return TableStyle A TableStyle instance + * @return TableStyle */ public static function getStyleDefinition($name) { @@ -127,7 +139,7 @@ class Table * * @param TableStyle|string $name The style name or a TableStyle instance * - * @return Table + * @return $this */ public function setStyle($name) { @@ -152,11 +164,11 @@ class Table * @param int $columnIndex Column index * @param TableStyle|string $name The style name or a TableStyle instance * - * @return Table + * @return $this */ public function setColumnStyle($columnIndex, $name) { - $columnIndex = intval($columnIndex); + $columnIndex = (int) $columnIndex; $this->columnStyles[$columnIndex] = $this->resolveStyle($name); @@ -174,11 +186,7 @@ class Table */ public function getColumnStyle($columnIndex) { - if (isset($this->columnStyles[$columnIndex])) { - return $this->columnStyles[$columnIndex]; - } - - return $this->getStyle(); + return $this->columnStyles[$columnIndex] ?? $this->getStyle(); } /** @@ -187,11 +195,11 @@ class Table * @param int $columnIndex Column index * @param int $width Minimum column width in characters * - * @return Table + * @return $this */ public function setColumnWidth($columnIndex, $width) { - $this->columnWidths[intval($columnIndex)] = intval($width); + $this->columnWidths[(int) $columnIndex] = (int) $width; return $this; } @@ -199,13 +207,11 @@ class Table /** * Sets the minimum width of all columns. * - * @param array $widths - * - * @return Table + * @return $this */ public function setColumnWidths(array $widths) { - $this->columnWidths = array(); + $this->columnWidths = []; foreach ($widths as $index => $width) { $this->setColumnWidth($index, $width); } @@ -213,11 +219,30 @@ class Table return $this; } + /** + * Sets the maximum width of a column. + * + * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while + * formatted strings are preserved. + * + * @return $this + */ + public function setColumnMaxWidth(int $columnIndex, int $width): self + { + if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { + throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, \get_class($this->output->getFormatter()))); + } + + $this->columnMaxWidths[$columnIndex] = $width; + + return $this; + } + public function setHeaders(array $headers) { $headers = array_values($headers); - if (!empty($headers) && !is_array($headers[0])) { - $headers = array($headers); + if (!empty($headers) && !\is_array($headers[0])) { + $headers = [$headers]; } $this->headers = $headers; @@ -227,7 +252,7 @@ class Table public function setRows(array $rows) { - $this->rows = array(); + $this->rows = []; return $this->addRows($rows); } @@ -249,7 +274,7 @@ class Table return $this; } - if (!is_array($row)) { + if (!\is_array($row)) { throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); } @@ -258,6 +283,25 @@ class Table return $this; } + /** + * Adds a row to the table, and re-renders the table. + */ + public function appendRow($row): self + { + if (!$this->output instanceof ConsoleSectionOutput) { + throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); + } + + if ($this->rendered) { + $this->output->clear($this->calculateRowCount()); + } + + $this->addRow($row); + $this->render(); + + return $this; + } + public function setRow($column, array $row) { $this->rows[$column] = $row; @@ -265,65 +309,177 @@ class Table return $this; } + public function setHeaderTitle(?string $title): self + { + $this->headerTitle = $title; + + return $this; + } + + public function setFooterTitle(?string $title): self + { + $this->footerTitle = $title; + + return $this; + } + + public function setHorizontal(bool $horizontal = true): self + { + $this->horizontal = $horizontal; + + return $this; + } + /** * Renders table to output. * * Example: - * +---------------+-----------------------+------------------+ - * | ISBN | Title | Author | - * +---------------+-----------------------+------------------+ - * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | - * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | - * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | - * +---------------+-----------------------+------------------+ + * + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ */ public function render() { - $this->calculateNumberOfColumns(); - $rows = $this->buildTableRows($this->rows); - $headers = $this->buildTableRows($this->headers); - - $this->calculateColumnsWidth(array_merge($headers, $rows)); - - $this->renderRowSeparator(); - if (!empty($headers)) { - foreach ($headers as $header) { - $this->renderRow($header, $this->style->getCellHeaderFormat()); - $this->renderRowSeparator(); + $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); } - foreach ($rows as $row) { - if ($row instanceof TableSeparator) { - $this->renderRowSeparator(); - } else { - $this->renderRow($row, $this->style->getCellRowFormat()); + + $this->calculateNumberOfColumns($rows); + + $rowGroups = $this->buildTableRows($rows); + $this->calculateColumnsWidth($rowGroups); + + $isHeader = !$this->horizontal; + $isFirstRow = $this->horizontal; + $hasTitle = (bool) $this->headerTitle; + + foreach ($rowGroups as $rowGroup) { + $isHeaderSeparatorRendered = false; + + foreach ($rowGroup as $row) { + if ($divider === $row) { + $isHeader = false; + $isFirstRow = true; + + continue; + } + + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + + continue; + } + + if (!$row) { + continue; + } + + if ($isHeader && !$isHeaderSeparatorRendered) { + $this->renderRowSeparator( + $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $hasTitle = false; + $isHeaderSeparatorRendered = true; + } + + if ($isFirstRow) { + $this->renderRowSeparator( + $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $isFirstRow = false; + $hasTitle = false; + } + + if ($this->horizontal) { + $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat()); + } else { + $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); + } } } - if (!empty($rows)) { - $this->renderRowSeparator(); - } + $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); $this->cleanup(); + $this->rendered = true; } /** * Renders horizontal header separator. * - * Example: +-----+-----------+-------+ + * Example: + * + * +-----+-----------+-------+ */ - private function renderRowSeparator() + private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null) { if (0 === $count = $this->numberOfColumns) { return; } - if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { + $borders = $this->style->getBorderChars(); + if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) { return; } - $markup = $this->style->getCrossingChar(); + $crossings = $this->style->getCrossingChars(); + if (self::SEPARATOR_MID === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; + } elseif (self::SEPARATOR_TOP === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; + } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; + } else { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; + } + + $markup = $leftChar; for ($column = 0; $column < $count; ++$column) { - $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar(); + $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]); + $markup .= $column === $count - 1 ? $rightChar : $midChar; + } + + if (null !== $title) { + $titleLength = Helper::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)); + $markupLength = Helper::strlen($markup); + if ($titleLength > $limit = $markupLength - 4) { + $titleLength = $limit; + $formatLength = Helper::strlenWithoutDecoration($formatter, sprintf($titleFormat, '')); + $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...'); + } + + $titleStart = intdiv($markupLength - $titleLength, 2); + if (false === mb_detect_encoding($markup, null, true)) { + $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength); + } else { + $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength); + } } $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); @@ -332,43 +488,42 @@ class Table /** * Renders vertical column separator. */ - private function renderColumnSeparator() + private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string { - return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()); + $borders = $this->style->getBorderChars(); + + return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]); } /** * Renders table row. * - * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * Example: * - * @param array $row - * @param string $cellFormat + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | */ - private function renderRow(array $row, $cellFormat) - { - if (empty($row)) { - return; - } - - $rowContent = $this->renderColumnSeparator(); - foreach ($this->getRowColumns($row) as $column) { - $rowContent .= $this->renderCell($row, $column, $cellFormat); - $rowContent .= $this->renderColumnSeparator(); + 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) { + 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); } /** * Renders table cell with padding. - * - * @param array $row - * @param int $column - * @param string $cellFormat */ - private function renderCell(array $row, $column, $cellFormat) + private function renderCell(array $row, int $column, string $cellFormat): string { - $cell = isset($row[$column]) ? $row[$column] : ''; + $cell = $row[$column] ?? ''; $width = $this->effectiveColumnWidths[$column]; if ($cell instanceof TableCell && $cell->getColspan() > 1) { // add the width of the following columns(numbers of colspan). @@ -379,13 +534,13 @@ class Table // str_pad won't work properly with multi-byte strings, we need to fix the padding if (false !== $encoding = mb_detect_encoding($cell, null, true)) { - $width += strlen($cell) - mb_strwidth($cell, $encoding); + $width += \strlen($cell) - mb_strwidth($cell, $encoding); } $style = $this->getColumnStyle($column); if ($cell instanceof TableSeparator) { - return sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width)); + return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width)); } $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); @@ -397,14 +552,10 @@ class Table /** * Calculate number of columns for this table. */ - private function calculateNumberOfColumns() + private function calculateNumberOfColumns(array $rows) { - if (null !== $this->numberOfColumns) { - return; - } - - $columns = array(0); - foreach (array_merge($this->headers, $this->rows) as $row) { + $columns = [0]; + foreach ($rows as $row) { if ($row instanceof TableSeparator) { continue; } @@ -415,80 +566,113 @@ class Table $this->numberOfColumns = max($columns); } - private function buildTableRows($rows) + private function buildTableRows(array $rows): TableRows { - $unmergedRows = array(); - for ($rowKey = 0; $rowKey < count($rows); ++$rowKey) { + /** @var WrappableOutputFormatterInterface $formatter */ + $formatter = $this->output->getFormatter(); + $unmergedRows = []; + for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { $rows = $this->fillNextRows($rows, $rowKey); // Remove any new line breaks and replace it with a new line foreach ($rows[$rowKey] as $column => $cell) { - if (!strstr($cell, "\n")) { + $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; + + if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) { + $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); + } + if (!strstr($cell ?? '', "\n")) { continue; } - $lines = explode("\n", $cell); + $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell))); + $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; + $lines = explode("\n", str_replace("\n", "<fg=default;bg=default></>\n", $cell)); foreach ($lines as $lineKey => $line) { - if ($cell instanceof TableCell) { - $line = new TableCell($line, array('colspan' => $cell->getColspan())); + if ($colspan > 1) { + $line = new TableCell($line, ['colspan' => $colspan]); } if (0 === $lineKey) { $rows[$rowKey][$column] = $line; } else { + if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) { + $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey); + } $unmergedRows[$rowKey][$lineKey][$column] = $line; } } } } - $tableRows = array(); - foreach ($rows as $rowKey => $row) { - $tableRows[] = $this->fillCells($row); - if (isset($unmergedRows[$rowKey])) { - $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); + return new TableRows(function () use ($rows, $unmergedRows): \Traversable { + foreach ($rows as $rowKey => $row) { + $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)]; + + if (isset($unmergedRows[$rowKey])) { + foreach ($unmergedRows[$rowKey] as $row) { + $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row); + } + } + yield $rowGroup; } + }); + } + + private function calculateRowCount(): int + { + $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows)))); + + if ($this->headers) { + ++$numberOfRows; // Add row for header separator } - return $tableRows; + if (\count($this->rows) > 0) { + ++$numberOfRows; // Add row for footer separator + } + + return $numberOfRows; } /** * fill rows that contains rowspan > 1. * - * @param array $rows - * @param int $line - * - * @return array + * @throws InvalidArgumentException */ - private function fillNextRows($rows, $line) + private function fillNextRows(array $rows, int $line): array { - $unmergedRows = array(); + $unmergedRows = []; foreach ($rows[$line] as $column => $cell) { + if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) { + throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', \gettype($cell))); + } if ($cell instanceof TableCell && $cell->getRowspan() > 1) { $nbLines = $cell->getRowspan() - 1; - $lines = array($cell); + $lines = [$cell]; if (strstr($cell, "\n")) { - $lines = explode("\n", $cell); - $nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; + $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell)); + $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; - $rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan())); + $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]); unset($lines[0]); } // create a two dimensional array (rowspan x colspan) - $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows); + $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows); foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { - $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; - $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan())); + $value = $lines[$unmergedRowKey - $line] ?? ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]); + if ($nbLines === $unmergedRowKey - $line) { + break; + } } } } foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { // we need to know if $unmergedRow will be merged or inserted into $rows - if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { foreach ($unmergedRow as $cellKey => $cell) { // insert cell into row at cellKey position - array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell)); + array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); } } else { $row = $this->copyRow($rows, $unmergedRowKey - 1); @@ -497,7 +681,7 @@ class Table $row[$column] = $unmergedRow[$column]; } } - array_splice($rows, $unmergedRowKey, 0, array($row)); + array_splice($rows, $unmergedRowKey, 0, [$row]); } } @@ -506,14 +690,11 @@ class Table /** * fill cells for a row that contains colspan > 1. - * - * @param array $row - * - * @return array */ - private function fillCells($row) + private function fillCells(iterable $row) { - $newRow = array(); + $newRow = []; + foreach ($row as $column => $cell) { $newRow[] = $cell; if ($cell instanceof TableCell && $cell->getColspan() > 1) { @@ -527,19 +708,13 @@ class Table return $newRow ?: $row; } - /** - * @param array $rows - * @param int $line - * - * @return array - */ - private function copyRow($rows, $line) + private function copyRow(array $rows, int $line): array { $row = $rows[$line]; foreach ($row as $cellKey => $cellValue) { $row[$cellKey] = ''; if ($cellValue instanceof TableCell) { - $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan())); + $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); } } @@ -548,14 +723,10 @@ class Table /** * Gets number of columns by row. - * - * @param array $row - * - * @return int */ - private function getNumberOfColumns(array $row) + private function getNumberOfColumns(array $row): int { - $columns = count($row); + $columns = \count($row); foreach ($row as $column) { $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; } @@ -565,12 +736,8 @@ class Table /** * Gets list of columns for the given row. - * - * @param array $row - * - * @return array */ - private function getRowColumns($row) + private function getRowColumns(array $row): array { $columns = range(0, $this->numberOfColumns - 1); foreach ($row as $cellKey => $cell) { @@ -585,56 +752,44 @@ class Table /** * Calculates columns widths. - * - * @param array $rows */ - private function calculateColumnsWidth($rows) + private function calculateColumnsWidth(iterable $groups) { for ($column = 0; $column < $this->numberOfColumns; ++$column) { - $lengths = array(); - foreach ($rows as $row) { - if ($row instanceof TableSeparator) { - continue; - } + $lengths = []; + foreach ($groups as $group) { + foreach ($group as $row) { + if ($row instanceof TableSeparator) { + continue; + } - foreach ($row as $i => $cell) { - if ($cell instanceof TableCell) { - $textLength = strlen($cell); - if ($textLength > 0) { - $contentColumns = str_split($cell, ceil($textLength / $cell->getColspan())); - foreach ($contentColumns as $position => $content) { - $row[$i + $position] = $content; + foreach ($row as $i => $cell) { + if ($cell instanceof TableCell) { + $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); + $textLength = Helper::strlen($textContent); + if ($textLength > 0) { + $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); + foreach ($contentColumns as $position => $content) { + $row[$i + $position] = $content; + } } } } - } - $lengths[] = $this->getCellWidth($row, $column); + $lengths[] = $this->getCellWidth($row, $column); + } } - $this->effectiveColumnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2; + $this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2; } } - /** - * Gets column width. - * - * @return int - */ - private function getColumnSeparatorWidth() + private function getColumnSeparatorWidth(): int { - return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); + return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3])); } - /** - * Gets cell width. - * - * @param array $row - * @param int $column - * - * @return int - */ - private function getCellWidth(array $row, $column) + private function getCellWidth(array $row, int $column): int { $cellWidth = 0; @@ -643,9 +798,10 @@ class Table $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); } - $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0; + $columnWidth = $this->columnWidths[$column] ?? 0; + $cellWidth = max($cellWidth, $columnWidth); - return max($cellWidth, $columnWidth); + return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; } /** @@ -653,44 +809,58 @@ class Table */ private function cleanup() { - $this->effectiveColumnWidths = array(); + $this->effectiveColumnWidths = []; $this->numberOfColumns = null; } - private static function initStyles() + private static function initStyles(): array { $borderless = new TableStyle(); $borderless - ->setHorizontalBorderChar('=') - ->setVerticalBorderChar(' ') - ->setCrossingChar(' ') + ->setHorizontalBorderChars('=') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') ; $compact = new TableStyle(); $compact - ->setHorizontalBorderChar('') - ->setVerticalBorderChar(' ') - ->setCrossingChar('') - ->setCellRowContentFormat('%s') + ->setHorizontalBorderChars('') + ->setVerticalBorderChars('') + ->setDefaultCrossingChar('') + ->setCellRowContentFormat('%s ') ; $styleGuide = new TableStyle(); $styleGuide - ->setHorizontalBorderChar('-') - ->setVerticalBorderChar(' ') - ->setCrossingChar(' ') + ->setHorizontalBorderChars('-') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') ->setCellHeaderFormat('%s') ; - return array( + $box = (new TableStyle()) + ->setHorizontalBorderChars('─') + ->setVerticalBorderChars('│') + ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├') + ; + + $boxDouble = (new TableStyle()) + ->setHorizontalBorderChars('═', '─') + ->setVerticalBorderChars('║', '│') + ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣') + ; + + return [ 'default' => new TableStyle(), 'borderless' => $borderless, 'compact' => $compact, 'symfony-style-guide' => $styleGuide, - ); + 'box' => $box, + 'box-double' => $boxDouble, + ]; } - private function resolveStyle($name) + private function resolveStyle($name): TableStyle { if ($name instanceof TableStyle) { return $name; |