Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[2.3] [Console] TableHelper

  • Loading branch information...
commit 8de7813cab9e9d134bebf01a2bed44100aac1d39 1 parent e8b7f0f
@umpirsky umpirsky authored fabpot committed
View
2  src/Symfony/Component/Console/Application.php
@@ -27,6 +27,7 @@
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Helper\ProgressHelper;
+use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleForExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
@@ -1013,6 +1014,7 @@ protected function getDefaultHelperSet()
new FormatterHelper(),
new DialogHelper(),
new ProgressHelper(),
+ new TableHelper(),
));
}
View
1  src/Symfony/Component/Console/CHANGELOG.md
@@ -4,6 +4,7 @@ CHANGELOG
2.3.0
-----
+ * added Table Helper for tabular data rendering
* added support for events in `Application`
* added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()`
* added a way to set the progress bar progress via the `setCurrent` method
View
20 src/Symfony/Component/Console/Helper/FormatterHelper.php
@@ -71,26 +71,6 @@ public function formatBlock($messages, $style, $large = false)
}
/**
- * Returns the length of a string, using mb_strlen if it is available.
- *
- * @param string $string The string to check its length
- *
- * @return integer The length of the string
- */
- private function strlen($string)
- {
- if (!function_exists('mb_strlen')) {
- return strlen($string);
- }
-
- if (false === $encoding = mb_detect_encoding($string)) {
- return strlen($string);
- }
-
- return mb_strlen($string, $encoding);
- }
-
- /**
* {@inheritDoc}
*/
public function getName()
View
20 src/Symfony/Component/Console/Helper/Helper.php
@@ -39,4 +39,24 @@ public function getHelperSet()
{
return $this->helperSet;
}
+
+ /**
+ * Returns the length of a string, using mb_strlen if it is available.
+ *
+ * @param string $string The string to check its length
+ *
+ * @return integer The length of the string
+ */
+ protected function strlen($string)
+ {
+ if (!function_exists('mb_strlen')) {
+ return strlen($string);
+ }
+
+ if (false === $encoding = mb_detect_encoding($string)) {
+ return strlen($string);
+ }
+
+ return mb_strlen($string, $encoding);
+ }
}
View
29 src/Symfony/Component/Console/Helper/ProgressHelper.php
@@ -14,7 +14,7 @@
use Symfony\Component\Console\Output\OutputInterface;
/**
- * The Progress class providers helpers to display progress output.
+ * The Progress class provides helpers to display progress output.
*
* @author Chris Jones <leeked@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
@@ -320,7 +320,7 @@ private function initialize()
}
if ($this->max > 0) {
- $this->widths['max'] = $this->getLength($this->max);
+ $this->widths['max'] = $this->strlen($this->max);
$this->widths['current'] = $this->widths['max'];
} else {
$this->barCharOriginal = $this->barChar;
@@ -356,7 +356,7 @@ private function generate($finish = false)
}
}
- $emptyBars = $this->barWidth - $completeBars - $this->getLength($this->progressChar);
+ $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar);
$bar = str_repeat($this->barChar, $completeBars);
if ($completeBars < $this->barWidth) {
$bar .= $this->progressChar;
@@ -419,7 +419,7 @@ private function humaneTime($secs)
*/
private function overwrite(OutputInterface $output, $message)
{
- $length = $this->getLength($message);
+ $length = $this->strlen($message);
// append whitespace to match the last line's length
if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) {
@@ -430,26 +430,7 @@ private function overwrite(OutputInterface $output, $message)
$output->write("\x0D");
$output->write($message);
- $this->lastMessagesLength = $this->getLength($message);
- }
-
- /**
- * Wrapper arround strlen: uses multi-byte function if available
- *
- * @param string $string
- * @return integer
- */
- private function getLength($string)
- {
- if (!function_exists('mb_strlen')) {
- return strlen($string);
- }
-
- if (false === $encoding = mb_detect_encoding($string)) {
- return strlen($string);
- }
-
- return mb_strlen($string, $encoding);
+ $this->lastMessagesLength = $this->strlen($message);
}
/**
View
453 src/Symfony/Component/Console/Helper/TableHelper.php
@@ -0,0 +1,453 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Output\OutputInterface;
+use InvalidArgumentException;
+
+/**
+ * Provides helpers to display table output.
+ *
+ * @author Саша Стаменковић <umpirsky@gmail.com>
+ */
+class TableHelper extends Helper
+{
+ const LAYOUT_DEFAULT = 0;
+ const LAYOUT_BORDERLESS = 1;
+
+ /**
+ * Table headers.
+ *
+ * @var array
+ */
+ private $headers = array();
+
+ /**
+ * Table rows.
+ *
+ * @var array
+ */
+ private $rows = array();
+
+ // Rendering options
+ private $paddingChar;
+ private $horizontalBorderChar;
+ private $verticalBorderChar;
+ private $crossingChar;
+ private $cellHeaderFormat;
+ private $cellRowFormat;
+ private $borderFormat;
+ private $padType;
+
+ /**
+ * Column widths cache.
+ *
+ * @var array
+ */
+ private $columnWidths = array();
+
+ /**
+ * Number of columns cache.
+ *
+ * @var array
+ */
+ private $numberOfColumns;
+
+ /**
+ * @var OutputInterface
+ */
+ private $output;
+
+ public function __construct()
+ {
+ $this->setLayout(self::LAYOUT_DEFAULT);
+ }
+
+ /**
+ * Sets table layout type.
+ *
+ * @param int $layout self::LAYOUT_*
+ *
+ * @return TableHelper
+ */
+ public function setLayout($layout)
+ {
+ switch ($layout) {
+ case self::LAYOUT_BORDERLESS:
+ $this
+ ->setPaddingChar(' ')
+ ->setHorizontalBorderChar('=')
+ ->setVerticalBorderChar(' ')
+ ->setCrossingChar(' ')
+ ->setCellHeaderFormat('<info>%s</info>')
+ ->setCellRowFormat('<comment>%s</comment>')
+ ->setBorderFormat('%s')
+ ->setPadType(STR_PAD_RIGHT)
+ ;
+ break;
+
+ case self::LAYOUT_DEFAULT:
+ $this
+ ->setPaddingChar(' ')
+ ->setHorizontalBorderChar('-')
+ ->setVerticalBorderChar('|')
+ ->setCrossingChar('+')
+ ->setCellHeaderFormat('<info>%s</info>')
+ ->setCellRowFormat('<comment>%s</comment>')
+ ->setBorderFormat('%s')
+ ->setPadType(STR_PAD_RIGHT)
+ ;
+ break;
+
+ default:
+ throw new InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout));
+ break;
+ };
+
+ return $this;
+ }
+
+ public function setHeaders(array $headers)
+ {
+ $this->headers = array_values($headers);
+
+ return $this;
+ }
+
+ public function setRows(array $rows)
+ {
+ $this->rows = array();
+
+ return $this->addRows($rows);
+ }
+
+ public function addRows(array $rows)
+ {
+ foreach ($rows as $row) {
+ $this->addRow($row);
+ }
+
+ return $this;
+ }
+
+ public function addRow(array $row)
+ {
+ $this->rows[] = array_values($row);
+
+ return $this;
+ }
+
+ public function setRow($column, array $row)
+ {
+ $this->rows[$column] = $row;
+
+ return $this;
+ }
+
+ /**
+ * Sets padding character, used for cell padding.
+ *
+ * @param string $paddingChar
+ *
+ * @return TableHelper
+ */
+ public function setPaddingChar($paddingChar)
+ {
+ $this->paddingChar = $paddingChar;
+
+ return $this;
+ }
+
+ /**
+ * Sets horizontal border character.
+ *
+ * @param string $horizontalBorderChar
+ *
+ * @return TableHelper
+ */
+ public function setHorizontalBorderChar($horizontalBorderChar)
+ {
+ $this->horizontalBorderChar = $horizontalBorderChar;
+
+ return $this;
+ }
+
+ /**
+ * Sets vertical border character.
+ *
+ * @param string $verticalBorderChar
+ *
+ * @return TableHelper
+ */
+ public function setVerticalBorderChar($verticalBorderChar)
+ {
+ $this->verticalBorderChar = $verticalBorderChar;
+
+ return $this;
+ }
+
+ /**
+ * Sets crossing character.
+ *
+ * @param string $crossingChar
+ *
+ * @return TableHelper
+ */
+ public function setCrossingChar($crossingChar)
+ {
+ $this->crossingChar = $crossingChar;
+
+ return $this;
+ }
+
+ /**
+ * Sets header cell format.
+ *
+ * @param string $cellHeaderFormat
+ *
+ * @return TableHelper
+ */
+ public function setCellHeaderFormat($cellHeaderFormat)
+ {
+ $this->cellHeaderFormat = $cellHeaderFormat;
+
+ return $this;
+ }
+
+ /**
+ * Sets row cell format.
+ *
+ * @param string $cellRowFormat
+ *
+ * @return TableHelper
+ */
+ public function setCellRowFormat($cellRowFormat)
+ {
+ $this->cellRowFormat = $cellRowFormat;
+
+ return $this;
+ }
+
+ /**
+ * Sets table border format.
+ *
+ * @param string $borderFormat
+ *
+ * @return TableHelper
+ */
+ public function setBorderFormat($borderFormat)
+ {
+ $this->borderFormat = $borderFormat;
+
+ return $this;
+ }
+
+ /**
+ * Sets cell padding type.
+ *
+ * @param integer $padType STR_PAD_*
+ *
+ * @return TableHelper
+ */
+ public function setPadType($padType)
+ {
+ $this->padType = $padType;
+
+ 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 |
+ * +---------------+-----------------------+------------------+
+ *
+ * @param OutputInterface $output
+ */
+ public function render(OutputInterface $output)
+ {
+ $this->output = $output;
+
+ $this->renderRowSeparator();
+ $this->renderRow($this->headers, $this->cellHeaderFormat);
+ if (!empty($this->headers)) {
+ $this->renderRowSeparator();
+ }
+ foreach ($this->rows as $row) {
+ $this->renderRow($row, $this->cellRowFormat);
+ }
+ if (!empty($this->rows)) {
+ $this->renderRowSeparator();
+ }
+
+ $this->cleanup();
+ }
+
+ /**
+ * Renders horizontal header separator.
+ *
+ * Example: +-----+-----------+-------+
+ */
+ private function renderRowSeparator()
+ {
+ if (0 === $count = $this->getNumberOfColumns()) {
+ return;
+ }
+
+ $markup = $this->crossingChar;
+ for ($column = 0; $column < $count; $column++) {
+ $markup .= str_repeat($this->horizontalBorderChar, $this->getColumnWidth($column))
+ .$this->crossingChar
+ ;
+ }
+
+ $this->output->writeln(sprintf($this->borderFormat, $markup));
+ }
+
+ /**
+ * Renders vertical column separator.
+ */
+ private function renderColumnSeparator()
+ {
+ $this->output->write(sprintf($this->borderFormat, $this->verticalBorderChar));
+ }
+
+ /**
+ * Renders table row.
+ *
+ * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ *
+ * @param array $row
+ * @param string $cellFormat
+ */
+ private function renderRow(array $row, $cellFormat)
+ {
+ if (empty($row)) {
+ return;
+ }
+
+ $this->renderColumnSeparator();
+ for ($column = 0, $count = $this->getNumberOfColumns(); $column < $count; $column++) {
+ $this->renderCell($row, $column, $cellFormat);
+ $this->renderColumnSeparator();
+ }
+ $this->output->writeln('');
+ }
+
+ /**
+ * Renders table cell with padding.
+ *
+ * @param array $row
+ * @param integer $column
+ * @param string $cellFormat
+ */
+ private function renderCell(array $row, $column, $cellFormat)
+ {
+ $cell = isset($row[$column]) ? $row[$column] : '';
+
+ $this->output->write(sprintf(
+ $cellFormat,
+ str_pad(
+ $this->paddingChar.$cell.$this->paddingChar,
+ $this->getColumnWidth($column),
+ $this->paddingChar,
+ $this->padType
+ )
+ ));
+ }
+
+ /**
+ * Gets number of columns for this table.
+ *
+ * @return int
+ */
+ private function getNumberOfColumns()
+ {
+ if (null !== $this->numberOfColumns) {
+ return $this->numberOfColumns;
+ }
+
+ $columns = array(0);
+ $columns[] = count($this->headers);
+ foreach ($this->rows as $row) {
+ $columns[] = count($row);
+ }
+
+ return $this->numberOfColumns = max($columns);
+ }
+
+ /**
+ * Gets column width.
+ *
+ * @param integer $column
+ *
+ * @return int
+ */
+ private function getColumnWidth($column)
+ {
+ if (isset($this->columnWidths[$column])) {
+ return $this->columnWidths[$column];
+ }
+
+ $lengths = array(0);
+ $lengths[] = $this->getCellWidth($this->headers, $column);
+ foreach ($this->rows as $row) {
+ $lengths[] = $this->getCellWidth($row, $column);
+ }
+
+ return $this->columnWidths[$column] = max($lengths) + 2;
+ }
+
+ /**
+ * Gets cell width.
+ *
+ * @param array $row
+ * @param integer $column
+ *
+ * @return int
+ */
+ private function getCellWidth(array $row, $column)
+ {
+ if ($column < 0) {
+ return 0;
+ }
+
+ if (isset($row[$column])) {
+ return $this->strlen($row[$column]);
+ }
+
+ return $this->getCellWidth($row, $column - 1);
+ }
+
+ /**
+ * Called after rendering to cleanup cache data.
+ */
+ private function cleanup()
+ {
+ $this->columnWidths = array();
+ $this->numberOfColumns = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName()
+ {
+ return 'table';
+ }
+}
View
188 src/Symfony/Component/Console/Tests/Helper/TableHelperTest.php
@@ -0,0 +1,188 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Tests\Helper;
+
+use Symfony\Component\Console\Helper\TableHelper;
+use Symfony\Component\Console\Output\StreamOutput;
+
+class TableHelperTest extends \PHPUnit_Framework_TestCase
+{
+ protected $stream;
+
+ protected function setUp()
+ {
+ $this->stream = fopen('php://memory', 'r+');
+ }
+
+ protected function tearDown()
+ {
+ fclose($this->stream);
+ $this->stream = null;
+ }
+
+ /**
+ * @dataProvider testRenderProvider
+ */
+ public function testRender($headers, $rows, $layout, $expected)
+ {
+ $table = new TableHelper();
+ $table
+ ->setHeaders($headers)
+ ->setRows($rows)
+ ->setLayout($layout)
+ ;
+ $table->render($output = $this->getOutputStream());
+
+ $this->assertEquals($expected, $this->getOutputContent($output));
+ }
+
+ /**
+ * @dataProvider testRenderProvider
+ */
+ public function testRenderAddRows($headers, $rows, $layout, $expected)
+ {
+ $table = new TableHelper();
+ $table
+ ->setHeaders($headers)
+ ->addRows($rows)
+ ->setLayout($layout)
+ ;
+ $table->render($output = $this->getOutputStream());
+
+ $this->assertEquals($expected, $this->getOutputContent($output));
+ }
+
+ /**
+ * @dataProvider testRenderProvider
+ */
+ public function testRenderAddRowsOneByOne($headers, $rows, $layout, $expected)
+ {
+ $table = new TableHelper();
+ $table
+ ->setHeaders($headers)
+ ->setLayout($layout)
+ ;
+ foreach ($rows as $row) {
+ $table->addRow($row);
+ }
+ $table->render($output = $this->getOutputStream());
+
+ $this->assertEquals($expected, $this->getOutputContent($output));
+ }
+
+ public function testRenderProvider()
+ {
+ return array(
+ array(
+ array('ISBN', 'Title', 'Author'),
+ 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'),
+ ),
+ TableHelper::LAYOUT_DEFAULT,
+<<<TABLE
++---------------+--------------------------+------------------+
+| 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 |
++---------------+--------------------------+------------------+
+
+TABLE
+ ),
+ array(
+ array('ISBN', 'Title', 'Author'),
+ 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'),
+ ),
+ TableHelper::LAYOUT_BORDERLESS,
+ " =============== ========================== ================== \n ISBN Title Author \n =============== ========================== ================== \n 99921-58-10-7 Divine Comedy Dante Alighieri \n 9971-5-0210-0 A Tale of Two Cities Charles Dickens \n 960-425-059-0 The Lord of the Rings J. R. R. Tolkien \n 80-902734-1-6 And Then There Were None Agatha Christie \n =============== ========================== ================== \n"
+ ),
+ array(
+ array('ISBN', 'Title'),
+ array(
+ array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
+ array('9971-5-0210-0'),
+ 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'),
+ ),
+ TableHelper::LAYOUT_DEFAULT,
+<<<TABLE
++---------------+--------------------------+------------------+
+| ISBN | Title | |
++---------------+--------------------------+------------------+
+| 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+| 9971-5-0210-0 | | |
+| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+| 80-902734-1-6 | And Then There Were None | Agatha Christie |
++---------------+--------------------------+------------------+
+
+TABLE
+ ),
+ array(
+ array(),
+ array(
+ array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
+ array('9971-5-0210-0'),
+ 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'),
+ ),
+ TableHelper::LAYOUT_DEFAULT,
+<<<TABLE
++---------------+--------------------------+------------------+
+| 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+| 9971-5-0210-0 | | |
+| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+| 80-902734-1-6 | And Then There Were None | Agatha Christie |
++---------------+--------------------------+------------------+
+
+TABLE
+ ),
+ array(
+ array('ISBN', 'Title'),
+ array(),
+ TableHelper::LAYOUT_DEFAULT,
+<<<TABLE
++------+-------+
+| ISBN | Title |
++------+-------+
+
+TABLE
+ ),
+ array(
+ array(),
+ array(),
+ TableHelper::LAYOUT_DEFAULT,
+ '',
+ ),
+ );
+ }
+
+ protected function getOutputStream()
+ {
+ return new StreamOutput($this->stream);
+ }
+
+ protected function getOutputContent(StreamOutput $output)
+ {
+ rewind($output->getStream());
+
+ return stream_get_contents($output->getStream());
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.