[Console] Support max column width in Table
This commit is contained in:
parent
c3fd60df2c
commit
175f68fe51
@ -142,6 +142,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
|
||||
$offset = 0;
|
||||
$output = '';
|
||||
$tagRegex = '[a-z][a-z0-9,_=;-]*+';
|
||||
$currentLineLength = 0;
|
||||
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE);
|
||||
foreach ($matches[0] as $i => $match) {
|
||||
$pos = $match[1];
|
||||
@ -152,7 +153,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
|
||||
}
|
||||
|
||||
// add the text up to the next tag
|
||||
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width);
|
||||
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
|
||||
$offset = $pos + \strlen($text);
|
||||
|
||||
// opening tag?
|
||||
@ -166,7 +167,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
|
||||
// </>
|
||||
$this->styleStack->pop();
|
||||
} elseif (false === $style = $this->createStyleFromString(strtolower($tag))) {
|
||||
$output .= $this->applyCurrentStyle($text, $output, $width);
|
||||
$output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
|
||||
} elseif ($open) {
|
||||
$this->styleStack->push($style);
|
||||
} else {
|
||||
@ -174,7 +175,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface
|
||||
}
|
||||
}
|
||||
|
||||
$output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width);
|
||||
$output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
|
||||
|
||||
if (false !== strpos($output, "\0")) {
|
||||
return strtr($output, array("\0" => '\\', '\\<' => '<'));
|
||||
@ -231,24 +232,46 @@ class OutputFormatter implements WrappableOutputFormatterInterface
|
||||
/**
|
||||
* Applies current style from stack to text, if must be applied.
|
||||
*/
|
||||
private function applyCurrentStyle(string $text, string $current, int $width): string
|
||||
private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
|
||||
{
|
||||
if ('' === $text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($width) {
|
||||
if ('' !== $current) {
|
||||
$text = ltrim($text);
|
||||
}
|
||||
if (!$width) {
|
||||
return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
|
||||
}
|
||||
|
||||
$text = wordwrap($text, $width, "\n", true);
|
||||
if (!$currentLineLength && '' !== $current) {
|
||||
$text = ltrim($text);
|
||||
}
|
||||
|
||||
if ('' !== $current && "\n" !== substr($current, -1)) {
|
||||
$text = "\n".$text;
|
||||
if ($currentLineLength) {
|
||||
$prefix = substr($text, 0, $i = $width - $currentLineLength)."\n";
|
||||
$text = substr($text, $i);
|
||||
} else {
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
preg_match('~(\\n)$~', $text, $matches);
|
||||
$text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text);
|
||||
$text = rtrim($text, "\n").($matches[1] ?? '');
|
||||
|
||||
if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) {
|
||||
$text = "\n".$text;
|
||||
}
|
||||
|
||||
$lines = explode("\n", $text);
|
||||
if ($width === $currentLineLength = \strlen(end($lines))) {
|
||||
$currentLineLength = 0;
|
||||
}
|
||||
|
||||
if ($this->isDecorated()) {
|
||||
foreach ($lines as $i => $line) {
|
||||
$lines[$i] = $this->styleStack->getCurrent()->apply($line);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->isDecorated() && \strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
@ -80,6 +81,7 @@ class Table
|
||||
* @var array
|
||||
*/
|
||||
private $columnWidths = array();
|
||||
private $columnMaxWidths = array();
|
||||
|
||||
private static $styles;
|
||||
|
||||
@ -222,6 +224,25 @@ 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);
|
||||
@ -434,7 +455,6 @@ class Table
|
||||
* Example:
|
||||
*
|
||||
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
|
||||
*
|
||||
*/
|
||||
private function renderRow(array $row, string $cellFormat)
|
||||
{
|
||||
@ -498,12 +518,17 @@ class Table
|
||||
|
||||
private function buildTableRows($rows)
|
||||
{
|
||||
/** @var WrappableOutputFormatterInterface $formatter */
|
||||
$formatter = $this->output->getFormatter();
|
||||
$unmergedRows = array();
|
||||
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 (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) {
|
||||
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column]);
|
||||
}
|
||||
if (!strstr($cell, "\n")) {
|
||||
continue;
|
||||
}
|
||||
@ -711,8 +736,9 @@ class Table
|
||||
}
|
||||
|
||||
$columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;
|
||||
$cellWidth = max($cellWidth, $columnWidth);
|
||||
|
||||
return max($cellWidth, $columnWidth);
|
||||
return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -327,11 +327,19 @@ EOF
|
||||
{
|
||||
$formatter = new OutputFormatter(true);
|
||||
|
||||
$this->assertSame("pre\n\033[37;41mfoo\nbar\nbaz\033[39;49m\npos\nt", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 3));
|
||||
$this->assertSame("fo\no\e[37;41mb\e[39;49m\n\e[37;41mar\e[39;49m\nba\nz", $formatter->formatAndWrap('foo<error>bar</error> baz', 2));
|
||||
$this->assertSame("pr\ne \e[37;41m\e[39;49m\n\e[37;41mfo\e[39;49m\n\e[37;41mo \e[39;49m\n\e[37;41mba\e[39;49m\n\e[37;41mr \e[39;49m\n\e[37;41mba\e[39;49m\n\e[37;41mz\e[39;49m \npo\nst", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 2));
|
||||
$this->assertSame("pre\e[37;41m\e[39;49m\n\e[37;41mfoo\e[39;49m\n\e[37;41mbar\e[39;49m\n\e[37;41mbaz\e[39;49m\npos\nt", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 3));
|
||||
$this->assertSame("pre \e[37;41m\e[39;49m\n\e[37;41mfoo \e[39;49m\n\e[37;41mbar \e[39;49m\n\e[37;41mbaz\e[39;49m \npost", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 4));
|
||||
$this->assertSame("pre \e[37;41mf\e[39;49m\n\e[37;41moo ba\e[39;49m\n\e[37;41mr baz\e[39;49m\npost", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 5));
|
||||
|
||||
$formatter = new OutputFormatter();
|
||||
|
||||
$this->assertSame("fo\nob\nar\nba\nz", $formatter->formatAndWrap('foo<error>bar</error> baz', 2));
|
||||
$this->assertSame("pr\ne \nfo\no \nba\nr \nba\nz \npo\nst", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 2));
|
||||
$this->assertSame("pre\nfoo\nbar\nbaz\npos\nt", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 3));
|
||||
$this->assertSame("pre \nfoo \nbar \nbaz \npost", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 4));
|
||||
$this->assertSame("pre f\noo ba\nr baz\npost", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 5));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1050,6 +1050,33 @@ TABLE
|
||||
);
|
||||
}
|
||||
|
||||
public function testColumnMaxWidths()
|
||||
{
|
||||
$table = new Table($output = $this->getOutputStream());
|
||||
$table
|
||||
->setRows(array(
|
||||
array('Divine Comedy', 'A Tale of Two Cities', 'The Lord of the Rings', 'And Then There Were None'),
|
||||
))
|
||||
->setColumnMaxWidth(1, 5)
|
||||
->setColumnMaxWidth(2, 10)
|
||||
->setColumnMaxWidth(3, 15);
|
||||
|
||||
$table->render();
|
||||
|
||||
$expected =
|
||||
<<<TABLE
|
||||
+---------------+-------+------------+-----------------+
|
||||
| Divine Comedy | A Tal | The Lord o | And Then There |
|
||||
| | e of | f the Ring | Were None |
|
||||
| | Two C | s | |
|
||||
| | ities | | |
|
||||
+---------------+-------+------------+-----------------+
|
||||
|
||||
TABLE;
|
||||
|
||||
$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