composer update
This commit is contained in:
+145
-47
@@ -66,6 +66,8 @@ class Table
|
||||
|
||||
/**
|
||||
* Sets a style definition.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setStyleDefinition(string $name, TableStyle $style)
|
||||
{
|
||||
@@ -81,7 +83,7 @@ class Table
|
||||
{
|
||||
self::$styles ??= self::initStyles();
|
||||
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,7 +168,7 @@ class Table
|
||||
public function setColumnMaxWidth(int $columnIndex, int $width): static
|
||||
{
|
||||
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_debug_type($this->output->getFormatter())));
|
||||
throw new \LogicException(\sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
|
||||
}
|
||||
|
||||
$this->columnMaxWidths[$columnIndex] = $width;
|
||||
@@ -235,7 +237,7 @@ class Table
|
||||
public function appendRow(TableSeparator|array $row): static
|
||||
{
|
||||
if (!$this->output instanceof ConsoleSectionOutput) {
|
||||
throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
|
||||
throw new RuntimeException(\sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
|
||||
}
|
||||
|
||||
if ($this->rendered) {
|
||||
@@ -310,6 +312,8 @@ class Table
|
||||
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
|
||||
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
|
||||
* +---------------+-----------------------+------------------+
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
@@ -360,14 +364,28 @@ class Table
|
||||
$maxRows = max(\count($headers), \count($row));
|
||||
for ($i = 0; $i < $maxRows; ++$i) {
|
||||
$cell = (string) ($row[$i] ?? '');
|
||||
if ($headers && !$containsColspan) {
|
||||
$rows[] = [sprintf(
|
||||
'<comment>%s</>: %s',
|
||||
str_pad($headers[$i] ?? '', $maxHeaderLength, ' ', \STR_PAD_LEFT),
|
||||
$cell
|
||||
)];
|
||||
} elseif ('' !== $cell) {
|
||||
$rows[] = [$cell];
|
||||
|
||||
$eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
|
||||
$parts = explode($eol, $cell);
|
||||
foreach ($parts as $idx => $part) {
|
||||
if ($headers && !$containsColspan) {
|
||||
if (0 === $idx) {
|
||||
$rows[] = [\sprintf(
|
||||
'<comment>%s%s</>: %s',
|
||||
str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))),
|
||||
$headers[$i] ?? '',
|
||||
$part
|
||||
)];
|
||||
} else {
|
||||
$rows[] = [\sprintf(
|
||||
'%s %s',
|
||||
str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT),
|
||||
$part
|
||||
)];
|
||||
}
|
||||
} elseif ('' !== $cell) {
|
||||
$rows[] = [$part];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,6 +401,8 @@ class Table
|
||||
$isHeader = !$horizontal;
|
||||
$isFirstRow = $horizontal;
|
||||
$hasTitle = (bool) $this->headerTitle;
|
||||
$previousRow = null;
|
||||
$pendingSeparators = 0;
|
||||
|
||||
foreach ($rowGroups as $rowGroup) {
|
||||
$isHeaderSeparatorRendered = false;
|
||||
@@ -396,7 +416,8 @@ class Table
|
||||
}
|
||||
|
||||
if ($row instanceof TableSeparator) {
|
||||
$this->renderRowSeparator();
|
||||
// defer rendering until the row below is known, to align crossings with it
|
||||
++$pendingSeparators;
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -405,11 +426,17 @@ class Table
|
||||
continue;
|
||||
}
|
||||
|
||||
for (; $pendingSeparators > 0; --$pendingSeparators) {
|
||||
$this->renderRowSeparator(self::SEPARATOR_MID, null, null, $previousRow, $row);
|
||||
}
|
||||
|
||||
if ($isHeader && !$isHeaderSeparatorRendered) {
|
||||
$this->renderRowSeparator(
|
||||
$isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
|
||||
self::SEPARATOR_TOP,
|
||||
$hasTitle ? $this->headerTitle : null,
|
||||
$hasTitle ? $this->style->getHeaderTitleFormat() : null
|
||||
$hasTitle ? $this->style->getHeaderTitleFormat() : null,
|
||||
null,
|
||||
$row
|
||||
);
|
||||
$hasTitle = false;
|
||||
$isHeaderSeparatorRendered = true;
|
||||
@@ -417,9 +444,11 @@ class Table
|
||||
|
||||
if ($isFirstRow) {
|
||||
$this->renderRowSeparator(
|
||||
$isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
|
||||
$horizontal || !array_filter($this->headers) ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
|
||||
$hasTitle ? $this->headerTitle : null,
|
||||
$hasTitle ? $this->style->getHeaderTitleFormat() : null
|
||||
$hasTitle ? $this->style->getHeaderTitleFormat() : null,
|
||||
$previousRow,
|
||||
$row
|
||||
);
|
||||
$isFirstRow = false;
|
||||
$hasTitle = false;
|
||||
@@ -435,9 +464,16 @@ class Table
|
||||
} else {
|
||||
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
|
||||
}
|
||||
|
||||
$previousRow = $row;
|
||||
}
|
||||
}
|
||||
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
|
||||
|
||||
for (; $pendingSeparators > 0; --$pendingSeparators) {
|
||||
$this->renderRowSeparator(self::SEPARATOR_MID, null, null, $previousRow);
|
||||
}
|
||||
|
||||
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat(), $previousRow);
|
||||
|
||||
$this->cleanup();
|
||||
$this->rendered = true;
|
||||
@@ -450,7 +486,7 @@ class Table
|
||||
*
|
||||
* +-----+-----------+-------+
|
||||
*/
|
||||
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
|
||||
private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null, ?array $rowAbove = null, ?array $rowBelow = null): void
|
||||
{
|
||||
if (!$count = $this->numberOfColumns) {
|
||||
return;
|
||||
@@ -472,19 +508,42 @@ class Table
|
||||
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
|
||||
}
|
||||
|
||||
// only draw a crossing where a vertical border meets the separator; a cell
|
||||
// spanning several columns has no border on the columns it covers
|
||||
$hasLineAbove = self::SEPARATOR_TOP !== $type;
|
||||
$hasLineBelow = self::SEPARATOR_BOTTOM !== $type;
|
||||
$columnsAbove = null !== $rowAbove ? $this->getRowColumns($rowAbove) : null;
|
||||
$columnsBelow = null !== $rowBelow ? $this->getRowColumns($rowBelow) : null;
|
||||
|
||||
$markup = $leftChar;
|
||||
for ($column = 0; $column < $count; ++$column) {
|
||||
$markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
|
||||
$markup .= $column === $count - 1 ? $rightChar : $midChar;
|
||||
if ($column === $count - 1) {
|
||||
$markup .= $rightChar;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$borderAbove = $hasLineAbove && (null === $columnsAbove || \in_array($column + 1, $columnsAbove, true));
|
||||
$borderBelow = $hasLineBelow && (null === $columnsBelow || \in_array($column + 1, $columnsBelow, true));
|
||||
// the one-sided junctions reuse the top/bottom crossing chars; for the "box-double"
|
||||
// style these are double-horizontal (╤/╧) while an inner separator is single (─), as
|
||||
// that style has no single-line ┬/┴ to fall back to
|
||||
$markup .= match (true) {
|
||||
$borderAbove && $borderBelow => $midChar,
|
||||
$borderBelow => $crossings[2],
|
||||
$borderAbove => $crossings[6],
|
||||
default => $horizontal,
|
||||
};
|
||||
}
|
||||
|
||||
if (null !== $title) {
|
||||
$titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)));
|
||||
$titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = \sprintf($titleFormat, $title)));
|
||||
$markupLength = Helper::width($markup);
|
||||
if ($titleLength > $limit = $markupLength - 4) {
|
||||
$titleLength = $limit;
|
||||
$formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, '')));
|
||||
$formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
|
||||
$formatLength = Helper::width(Helper::removeDecoration($formatter, \sprintf($titleFormat, '')));
|
||||
$formattedTitle = \sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
|
||||
}
|
||||
|
||||
$titleStart = intdiv($markupLength - $titleLength, 2);
|
||||
@@ -495,7 +554,7 @@ class Table
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
|
||||
$this->output->writeln(\sprintf($this->style->getBorderFormat(), $markup));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -505,7 +564,7 @@ class Table
|
||||
{
|
||||
$borders = $this->style->getBorderChars();
|
||||
|
||||
return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
|
||||
return \sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -515,11 +574,11 @@ class Table
|
||||
*
|
||||
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
|
||||
*/
|
||||
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
|
||||
private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void
|
||||
{
|
||||
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
|
||||
$columns = $this->getRowColumns($row);
|
||||
$last = \count($columns) - 1;
|
||||
$last = array_key_last($columns);
|
||||
foreach ($columns as $i => $column) {
|
||||
if ($firstCellFormat && 0 === $i) {
|
||||
$rowContent .= $this->renderCell($row, $column, $firstCellFormat);
|
||||
@@ -546,18 +605,15 @@ 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) - Helper::width($cell) - substr_count($cell, "\0");
|
||||
$style = $this->getColumnStyle($column);
|
||||
|
||||
if ($cell instanceof TableSeparator) {
|
||||
return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
|
||||
return \sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
|
||||
}
|
||||
|
||||
$width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell));
|
||||
$content = sprintf($style->getCellRowContentFormat(), $cell);
|
||||
$content = \sprintf($style->getCellRowContentFormat(), $cell);
|
||||
|
||||
$padType = $style->getPadType();
|
||||
if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
|
||||
@@ -582,13 +638,13 @@ class Table
|
||||
$padType = $cell->getStyle()->getPadByAlign();
|
||||
}
|
||||
|
||||
return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
|
||||
return \sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate number of columns for this table.
|
||||
*/
|
||||
private function calculateNumberOfColumns(array $rows)
|
||||
private function calculateNumberOfColumns(array $rows): void
|
||||
{
|
||||
$columns = [0];
|
||||
foreach ($rows as $row) {
|
||||
@@ -614,15 +670,56 @@ class Table
|
||||
foreach ($rows[$rowKey] as $column => $cell) {
|
||||
$colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
|
||||
|
||||
if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) {
|
||||
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
|
||||
$minWrappedWidth = 0;
|
||||
$widthApplied = [];
|
||||
$lengthColumnBorder = $this->getColumnSeparatorWidth() + Helper::width($this->style->getCellRowContentFormat()) - 2;
|
||||
for ($i = $column; $i < ($column + $colspan); ++$i) {
|
||||
if (isset($this->columnMaxWidths[$i])) {
|
||||
$minWrappedWidth += $this->columnMaxWidths[$i];
|
||||
$widthApplied[] = ['type' => 'max', 'column' => $i];
|
||||
} elseif (($this->columnWidths[$i] ?? 0) > 0 && $colspan > 1) {
|
||||
$minWrappedWidth += $this->columnWidths[$i];
|
||||
$widthApplied[] = ['type' => 'min', 'column' => $i];
|
||||
}
|
||||
}
|
||||
if (1 === \count($widthApplied)) {
|
||||
if ($colspan > 1) {
|
||||
$minWrappedWidth *= $colspan; // previous logic
|
||||
}
|
||||
} elseif (\count($widthApplied) > 1) {
|
||||
$minWrappedWidth += (\count($widthApplied) - 1) * $lengthColumnBorder;
|
||||
}
|
||||
|
||||
$cellWidth = Helper::width(Helper::removeDecoration($formatter, $cell));
|
||||
if ($minWrappedWidth && $cellWidth > $minWrappedWidth) {
|
||||
$cell = $formatter->formatAndWrap($cell, $minWrappedWidth);
|
||||
}
|
||||
// update minimal columnWidths for spanned columns
|
||||
if ($colspan > 1 && $minWrappedWidth > 0) {
|
||||
$columnsMinWidthProcessed = [];
|
||||
$cellWidth = min($cellWidth, $minWrappedWidth);
|
||||
foreach ($widthApplied as $item) {
|
||||
if ('max' === $item['type'] && $cellWidth >= $this->columnMaxWidths[$item['column']]) {
|
||||
$minWidthColumn = $this->columnMaxWidths[$item['column']];
|
||||
$this->columnWidths[$item['column']] = $minWidthColumn;
|
||||
$columnsMinWidthProcessed[$item['column']] = true;
|
||||
$cellWidth -= $minWidthColumn + $lengthColumnBorder;
|
||||
}
|
||||
}
|
||||
for ($i = $column; $i < ($column + $colspan); ++$i) {
|
||||
if (isset($columnsMinWidthProcessed[$i])) {
|
||||
continue;
|
||||
}
|
||||
$this->columnWidths[$i] = $cellWidth + $lengthColumnBorder;
|
||||
}
|
||||
}
|
||||
if (!str_contains($cell ?? '', "\n")) {
|
||||
continue;
|
||||
}
|
||||
$escaped = implode("\n", array_map(OutputFormatter::escapeTrailingBackslash(...), explode("\n", $cell)));
|
||||
$eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n";
|
||||
$escaped = implode($eol, array_map(OutputFormatter::escapeTrailingBackslash(...), explode($eol, $cell)));
|
||||
$cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
|
||||
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default></>\n", $cell));
|
||||
$lines = explode($eol, str_replace($eol, '<fg=default;bg=default></>'.$eol, $cell));
|
||||
foreach ($lines as $lineKey => $line) {
|
||||
if ($colspan > 1) {
|
||||
$line = new TableCell($line, ['colspan' => $colspan]);
|
||||
@@ -678,14 +775,15 @@ class Table
|
||||
$unmergedRows = [];
|
||||
foreach ($rows[$line] as $column => $cell) {
|
||||
if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) {
|
||||
throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
|
||||
throw new InvalidArgumentException(\sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
|
||||
}
|
||||
if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
|
||||
$nbLines = $cell->getRowspan() - 1;
|
||||
$lines = [$cell];
|
||||
if (str_contains($cell, "\n")) {
|
||||
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
|
||||
$nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
|
||||
$eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
|
||||
$lines = explode($eol, str_replace($eol, '<fg=default;bg=default>'.$eol.'</>', $cell));
|
||||
$nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines;
|
||||
|
||||
$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
|
||||
unset($lines[0]);
|
||||
@@ -727,7 +825,7 @@ class Table
|
||||
/**
|
||||
* fill cells for a row that contains colspan > 1.
|
||||
*/
|
||||
private function fillCells(iterable $row)
|
||||
private function fillCells(iterable $row): iterable
|
||||
{
|
||||
$newRow = [];
|
||||
|
||||
@@ -789,7 +887,7 @@ class Table
|
||||
/**
|
||||
* Calculates columns widths.
|
||||
*/
|
||||
private function calculateColumnsWidth(iterable $groups)
|
||||
private function calculateColumnsWidth(iterable $groups): void
|
||||
{
|
||||
for ($column = 0; $column < $this->numberOfColumns; ++$column) {
|
||||
$lengths = [];
|
||||
@@ -804,7 +902,7 @@ class Table
|
||||
$textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
|
||||
$textLength = Helper::width($textContent);
|
||||
if ($textLength > 0) {
|
||||
$contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
|
||||
$contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan()));
|
||||
foreach ($contentColumns as $position => $content) {
|
||||
$row[$i + $position] = $content;
|
||||
}
|
||||
@@ -822,7 +920,7 @@ class Table
|
||||
|
||||
private function getColumnSeparatorWidth(): int
|
||||
{
|
||||
return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
|
||||
return Helper::width(\sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
|
||||
}
|
||||
|
||||
private function getCellWidth(array $row, int $column): int
|
||||
@@ -843,7 +941,7 @@ class Table
|
||||
/**
|
||||
* Called after rendering to cleanup cache data.
|
||||
*/
|
||||
private function cleanup()
|
||||
private function cleanup(): void
|
||||
{
|
||||
$this->effectiveColumnWidths = [];
|
||||
unset($this->numberOfColumns);
|
||||
@@ -905,6 +1003,6 @@ class Table
|
||||
return $name;
|
||||
}
|
||||
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user