feature #26933 [Console] Add title table (maidmaid)
This PR was merged into the 4.2-dev branch.
Discussion
----------
[Console] Add title table
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | /
| License | MIT
| Doc PR | /
Why not just add a title to console tables? Inspired by the famous file manager [Midnight Commander](https://en.wikipedia.org/wiki/Midnight_Commander).
![screenshot from 2018-04-15 20-16-26](https://user-images.githubusercontent.com/4578773/38777361-eb6aaa72-40e9-11e8-8ae4-055fe80272c3.png)
Commits
-------
6bf9eeb14e
Add title table
This commit is contained in:
commit
df26feaa22
@ -34,6 +34,9 @@ class Table
|
||||
private const BORDER_OUTSIDE = 0;
|
||||
private const BORDER_INSIDE = 1;
|
||||
|
||||
private $headerTitle;
|
||||
private $footerTitle;
|
||||
|
||||
/**
|
||||
* Table headers.
|
||||
*/
|
||||
@ -290,6 +293,20 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders table to output.
|
||||
*
|
||||
@ -331,15 +348,17 @@ class Table
|
||||
}
|
||||
|
||||
if ($isHeader || $isFirstRow) {
|
||||
$this->renderRowSeparator($isFirstRow ? self::SEPARATOR_TOP_BOTTOM : self::SEPARATOR_TOP);
|
||||
if ($isFirstRow) {
|
||||
$this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM);
|
||||
$isFirstRow = false;
|
||||
} else {
|
||||
$this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat());
|
||||
}
|
||||
}
|
||||
|
||||
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
|
||||
}
|
||||
$this->renderRowSeparator(self::SEPARATOR_BOTTOM);
|
||||
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
|
||||
|
||||
$this->cleanup();
|
||||
$this->rendered = true;
|
||||
@ -350,7 +369,7 @@ class Table
|
||||
*
|
||||
* Example: <code>+-----+-----------+-------+</code>
|
||||
*/
|
||||
private function renderRowSeparator(int $type = self::SEPARATOR_MID)
|
||||
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
|
||||
{
|
||||
if (0 === $count = $this->numberOfColumns) {
|
||||
return;
|
||||
@ -378,6 +397,23 @@ class Table
|
||||
$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 = ($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));
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,8 @@ class TableStyle
|
||||
private $crossingTopLeftBottomChar = '+';
|
||||
private $crossingTopMidBottomChar = '+';
|
||||
private $crossingTopRightBottomChar = '+';
|
||||
private $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
|
||||
private $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
|
||||
private $cellHeaderFormat = '<info>%s</info>';
|
||||
private $cellRowFormat = '%s';
|
||||
private $cellRowContentFormat = ' %s ';
|
||||
@ -429,4 +431,28 @@ class TableStyle
|
||||
{
|
||||
return $this->padType;
|
||||
}
|
||||
|
||||
public function getHeaderTitleFormat(): string
|
||||
{
|
||||
return $this->headerTitleFormat;
|
||||
}
|
||||
|
||||
public function setHeaderTitleFormat(string $format): self
|
||||
{
|
||||
$this->headerTitleFormat = $format;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFooterTitleFormat(): string
|
||||
{
|
||||
return $this->footerTitleFormat;
|
||||
}
|
||||
|
||||
public function setFooterTitleFormat(string $format): self
|
||||
{
|
||||
$this->footerTitleFormat = $format;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -974,6 +974,82 @@ TABLE;
|
||||
Table::getStyleDefinition('absent');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider renderSetTitle
|
||||
*/
|
||||
public function testSetTitle($headerTitle, $footerTitle, $style, $expected)
|
||||
{
|
||||
(new Table($output = $this->getOutputStream()))
|
||||
->setHeaderTitle($headerTitle)
|
||||
->setFooterTitle($footerTitle)
|
||||
->setHeaders(array('ISBN', 'Title', 'Author'))
|
||||
->setRows(array(
|
||||
array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
|
||||
array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
|
||||
array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
|
||||
array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
|
||||
))
|
||||
->setStyle($style)
|
||||
->render()
|
||||
;
|
||||
|
||||
$this->assertEquals($expected, $this->getOutputContent($output));
|
||||
}
|
||||
|
||||
public function renderSetTitle()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'Books',
|
||||
'Page 1/2',
|
||||
'default',
|
||||
<<<'TABLE'
|
||||
+---------------+----------- Books --------+------------------+
|
||||
| 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 |
|
||||
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
|
||||
+---------------+--------- Page 1/2 -------+------------------+
|
||||
|
||||
TABLE
|
||||
),
|
||||
array(
|
||||
'Books',
|
||||
'Page 1/2',
|
||||
'box',
|
||||
<<<'TABLE'
|
||||
┌───────────────┬─────────── Books ────────┬──────────────────┐
|
||||
│ 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 │
|
||||
│ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
|
||||
└───────────────┴───────── Page 1/2 ───────┴──────────────────┘
|
||||
|
||||
TABLE
|
||||
),
|
||||
array(
|
||||
'Boooooooooooooooooooooooooooooooooooooooooooooooooooooooks',
|
||||
'Page 1/999999999999999999999999999999999999999999999999999',
|
||||
'default',
|
||||
<<<'TABLE'
|
||||
+- Booooooooooooooooooooooooooooooooooooooooooooooooooooo... -+
|
||||
| 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 |
|
||||
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
|
||||
+- Page 1/99999999999999999999999999999999999999999999999... -+
|
||||
|
||||
TABLE
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getOutputStream($decorated = false)
|
||||
{
|
||||
return new StreamOutput($this->stream, StreamOutput::VERBOSITY_NORMAL, $decorated);
|
||||
|
Reference in New Issue
Block a user