From 22e9e917af60673fe6a315b194ecb64fb4f2757a Mon Sep 17 00:00:00 2001 From: AdamB Date: Mon, 3 Feb 2014 12:45:19 +0100 Subject: [PATCH] [Console] Added support for labels to ProgressHelper --- src/Symfony/Component/Console/CHANGELOG.md | 2 + .../Console/Helper/ProgressHelper.php | 110 +++++++++++++++--- .../Tests/Helper/ProgressHelperTest.php | 80 +++++++++++-- 3 files changed, 169 insertions(+), 23 deletions(-) diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index 19d03ca3039ea..910198dba88ad 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -1,6 +1,8 @@ CHANGELOG ========= +* added support for labels to ProgressHelper + 2.5.0 ----- diff --git a/src/Symfony/Component/Console/Helper/ProgressHelper.php b/src/Symfony/Component/Console/Helper/ProgressHelper.php index d9502eb814e4a..ebeed81a825f5 100644 --- a/src/Symfony/Component/Console/Helper/ProgressHelper.php +++ b/src/Symfony/Component/Console/Helper/ProgressHelper.php @@ -29,6 +29,12 @@ class ProgressHelper extends Helper const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]'; const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%'; + const LABEL_TOP = 1; + const LABEL_BOTTOM = 2; + const LABEL_LEFT = STR_PAD_LEFT; + const LABEL_CENTER = STR_PAD_BOTH; + const LABEL_RIGHT = STR_PAD_RIGHT; + // options private $barWidth = 28; private $barChar = '='; @@ -37,6 +43,18 @@ class ProgressHelper extends Helper private $format = null; private $redrawFreq = 1; + // Label options + private $label = ''; + private $labelPosition = self::LABEL_BOTTOM; + private $labelAlignment = self::LABEL_CENTER; + + /** + * Whether or not to show the label based on verbosity level. + * + * @var boolean + */ + private $showLabel = true; + private $lastMessagesLength; private $barCharOriginal; @@ -115,6 +133,13 @@ class ProgressHelper extends Helper array(604800, 'days', 86400), ); + /** + * Whether or not overwrite() has yet been called. + * + * @var boolean + */ + private $haveWritten = false; + /** * Sets the progress bar width. * @@ -175,6 +200,30 @@ public function setRedrawFrequency($freq) $this->redrawFreq = (int) $freq; } + /** + * Add a label to the progress bar. + * + * @param string $label The label to add + * @param int $position Where to show the label + * @param int $align How to align the label + */ + public function setLabel($label, $position = self::LABEL_BOTTOM, $align = self::LABEL_CENTER) + { + $this->label = $label; + $this->labelPosition = $position; + $this->labelAlignment = $align; + } + + /** + * Update current label. + * + * @param string $label Label's text + */ + public function updateLabel($label) + { + $this->label = $label; + } + /** * Starts the progress output. * @@ -199,6 +248,7 @@ public function start(OutputInterface $output, $max = null) if ($this->max > 0) { $this->format = self::FORMAT_QUIET; } + $this->showLabel = false; break; case OutputInterface::VERBOSITY_VERBOSE: case OutputInterface::VERBOSITY_VERY_VERBOSE: @@ -284,9 +334,31 @@ public function display($finish = false) foreach ($this->generate($finish) as $name => $value) { $message = str_replace("%{$name}%", $value, $message); } - $this->overwrite($this->output, $message); + + $length = $this->strlen($message); + + // append whitespace to match the last line's length + if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) { + $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + } + + $this->lastMessagesLength = $this->strlen($message); + + $lines[] = $message; + + if (true === $this->showLabel && !empty($this->label)) { + $label = str_pad($this->label, $this->lastMessagesLength, "\x20", $this->labelAlignment); + if (self::LABEL_TOP === $this->labelPosition) { + array_unshift($lines, $label); + } else { + $lines[] = $label; + } + } + + $this->overwrite($this->output, $lines); } + /** * Removes the progress bar from the current line. * @@ -296,7 +368,12 @@ public function display($finish = false) */ public function clear() { - $this->overwrite($this->output, ''); + $emptyLine = str_repeat("\x20", $this->lastMessagesLength); + $lines = array($emptyLine); + if (true === $this->showLabel && !empty($this->label)) { + $lines[] = $emptyLine; + } + $this->overwrite($this->output, $lines); } /** @@ -427,22 +504,27 @@ private function humaneTime($secs) * Overwrites a previous message to the output. * * @param OutputInterface $output An Output instance - * @param string $message The message + * @param array $lines Lines to output */ - private function overwrite(OutputInterface $output, $message) + private function overwrite(OutputInterface $output, array $lines) { - $length = $this->strlen($message); - - // append whitespace to match the last line's length - if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) { - $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + $numberOfLines = count($lines); + $glue = $numberOfLines > 1 ? PHP_EOL : ''; + if (true === $this->haveWritten) { + // carriage return + $output->write("\x0D"); + + // Move the cursor up if we are outputting more than one line + if ($numberOfLines > 1) { + // We move up number of lines - 1 because \r removes a line + $line = $numberOfLines - 1; + $output->write("\033[{$line}A"); + } + } else { + $this->haveWritten = true; } - // carriage return - $output->write("\x0D"); - $output->write($message); - - $this->lastMessagesLength = $this->strlen($message); + $output->write(join($glue, $lines)); } /** diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php index 88adf69c56c83..f86f0c52fe2f2 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php @@ -175,7 +175,7 @@ public function testClear() rewind($output->getStream()); $this->assertEquals( - $this->generateOutput(' 25/50 [==============>-------------] 50%') . $this->generateOutput(''), + $this->generateOutput(' 25/50 [==============>-------------] 50%') . $this->generateOutput(str_repeat("\x20", 42)), stream_get_contents($output->getStream()) ); } @@ -202,23 +202,85 @@ public function testNonDecoratedOutput() $this->assertEquals('', stream_get_contents($output->getStream())); } + public function testLabelDefault() + { + $progress = new ProgressHelper(); + $progress->setLabel('label'); + $progress->start($output = $this->getOutputStream(), 10); + $progress->display(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 0/10 [>---------------------------] 0%'.PHP_EOL.' label '), stream_get_contents($output->getStream())); + } + + public function testLabelTop() + { + $progress = new ProgressHelper(); + $progress->setLabel('label', ProgressHelper::LABEL_TOP); + $progress->start($output = $this->getOutputStream(), 10); + $progress->display(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' label '.PHP_EOL.' 0/10 [>---------------------------] 0%'), stream_get_contents($output->getStream())); + } + + public function testLabelAlignLeft() + { + $progress = new ProgressHelper(); + $progress->setLabel('label', ProgressHelper::LABEL_BOTTOM, ProgressHelper::LABEL_LEFT); + $progress->start($output = $this->getOutputStream(), 10); + $progress->display(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 0/10 [>---------------------------] 0%'.PHP_EOL.' label'), stream_get_contents($output->getStream())); + } + + public function testLabelAlignRight() + { + $progress = new ProgressHelper(); + $progress->setLabel('label', ProgressHelper::LABEL_BOTTOM, ProgressHelper::LABEL_RIGHT); + $progress->start($output = $this->getOutputStream(), 10); + + $progress->display(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 0/10 [>---------------------------] 0%'.PHP_EOL.'label '), stream_get_contents($output->getStream())); + } + + public function testLabelUpdate() + { + $progress = new ProgressHelper(); + $progress->setLabel('label'); + $progress->start($output = $this->getOutputStream(), 10); + $progress->display(); + $progress->updateLabel('label 1'); + $progress->advance(); + + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 0/10 [>---------------------------] 0%'.PHP_EOL.' label ').$this->generateOutput(' 1/10 [==>-------------------------] 10%'.PHP_EOL.' label 1 '), stream_get_contents($output->getStream())); + } + protected function getOutputStream($decorated = true) { return new StreamOutput(fopen('php://memory', 'r+', false), StreamOutput::VERBOSITY_NORMAL, $decorated); } - protected $lastMessagesLength; + protected $haveWritten = false; protected function generateOutput($expected) { - $expectedout = $expected; - - if ($this->lastMessagesLength !== null) { - $expectedout = str_pad($expected, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + $expectedout = ''; + if (true === $this->haveWritten) { + $expectedout .= "\x0D"; + if ($count = substr_count($expected, PHP_EOL)) { + $expectedout .= "\033[{$count}A"; + } + } else { + $this->haveWritten = true; } + $expectedout .= $expected; - $this->lastMessagesLength = strlen($expectedout); - - return "\x0D".$expectedout; + return $expectedout; } }