merged branch pkruithof/progress-helper-enhancements (PR #7300)

This PR was squashed before being merged into the master branch (closes #7300).

Discussion
----------

[Console] Progress helper enhancements

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | ~
| License       | MIT
| Doc PR        | ~

Two enhancements:

1. The progress bar clears the current line before writing the current progress. This can cause some flickering in the terminal. I've modified the write method to append whitespace to the line to be written, so it matches the previous line's length.
2. Added a `setCurrent` method to set the current progress. Rather than advancing by 1 or more steps, sometimes you want to just set the current state. For example if you are downloading a file, and a callback provides you with the current download status. A workaround for this could be to keep track of the previous event, calculate the difference, and advancing by the diff. But it's easier to just set the current progress.

Sidenotes:
* The [`overwrite`](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Console/Helper/ProgressHelper.php#L387) method copied documentation of the Output's [`write`](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Console/Output/Output.php#L139) method. The difference is, the overwrite method does not handle an array of messages. I've updated the documentation for this.
* The helper uses `strlen` to calculate line lengths. This could cause a problem when using multibyte strings. I'd change it to `mb_strlen`, but I'm not sure if the `mb_string` extension is required by Symfony.

Commits
-------

5ae76f0 [Console] Progress helper enhancements
This commit is contained in:
Fabien Potencier 2013-03-23 08:10:00 +01:00
commit 062cce0018
2 changed files with 143 additions and 16 deletions

View File

@ -177,8 +177,8 @@ class ProgressHelper extends Helper
/**
* Starts the progress output.
*
* @param OutputInterface $output An Output instance
* @param integer $max Maximum steps
* @param OutputInterface $output An Output instance
* @param integer $max Maximum steps
*/
public function start(OutputInterface $output, $max = null)
{
@ -236,6 +236,36 @@ class ProgressHelper extends Helper
}
}
/**
* Sets the current progress.
*
* @param integer $current The current progress
* @param Boolean $redraw Whether to redraw or not
*
* @throws \LogicException
*/
public function setCurrent($current, $redraw = false)
{
if (null === $this->startTime) {
throw new \LogicException('You must start the progress bar before calling setCurrent().');
}
$current = (int) $current;
if ($current < $this->current) {
throw new \LogicException('You can\'t regress the progress bar');
}
if ($this->current === 0) {
$redraw = true;
}
$this->current = $current;
if ($redraw || $this->current % $this->redrawFreq === 0) {
$this->display();
}
}
/**
* Outputs the current progress string.
*
@ -289,7 +319,7 @@ class ProgressHelper extends Helper
}
if ($this->max > 0) {
$this->widths['max'] = strlen($this->max);
$this->widths['max'] = $this->getLength($this->max);
$this->widths['current'] = $this->widths['max'];
} else {
$this->barCharOriginal = $this->barChar;
@ -325,7 +355,7 @@ class ProgressHelper extends Helper
}
}
$emptyBars = $this->barWidth - $completeBars - strlen($this->progressChar);
$emptyBars = $this->barWidth - $completeBars - $this->getLength($this->progressChar);
$bar = str_repeat($this->barChar, $completeBars);
if ($completeBars < $this->barWidth) {
$bar .= $this->progressChar;
@ -384,21 +414,41 @@ class ProgressHelper extends Helper
* Overwrites a previous message to the output.
*
* @param OutputInterface $output An Output instance
* @param string|array $messages The message as an array of lines or a single string
* @param string $messages The message
*/
private function overwrite(OutputInterface $output, $messages)
private function overwrite(OutputInterface $output, $message)
{
$length = $this->getLength($message);
// append whitespace to match the last line's length
if (($this->lastMessagesLength !== null) && ($this->lastMessagesLength > $length)) {
$message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
}
// carriage return
$output->write("\x0D");
if ($this->lastMessagesLength!==null) {
// clear the line with the length of the last message
$output->write(str_repeat("\x20", $this->lastMessagesLength));
// carriage return
$output->write("\x0D");
}
$output->write($messages);
$output->write($message);
$this->lastMessagesLength=strlen($messages);
$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);
}
/**

View File

@ -74,6 +74,83 @@ class ProgressHelperTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($this->generateOutput(' 0/50 [>---------------------------] 0%').$this->generateOutput(' 1/50 [>---------------------------] 2%').$this->generateOutput(' 2/50 [=>--------------------------] 4%'), stream_get_contents($output->getStream()));
}
public function testOverwriteWithShorterLine()
{
$progress = new ProgressHelper();
$progress->setFormat(' %current%/%max% [%bar%] %percent%%');
$progress->start($output = $this->getOutputStream(), 50);
$progress->display();
$progress->advance();
// set shorter format
$progress->setFormat(' %current%/%max% [%bar%]');
$progress->advance();
rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(' 0/50 [>---------------------------] 0%') .
$this->generateOutput(' 1/50 [>---------------------------] 2%') .
$this->generateOutput(' 2/50 [=>--------------------------] '),
stream_get_contents($output->getStream())
);
}
public function testSetCurrentProgress()
{
$progress = new ProgressHelper();
$progress->start($output = $this->getOutputStream(), 50);
$progress->display();
$progress->advance();
$progress->setCurrent(15);
$progress->setCurrent(25);
rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(' 0/50 [>---------------------------] 0%') .
$this->generateOutput(' 1/50 [>---------------------------] 2%') .
$this->generateOutput(' 15/50 [========>-------------------] 30%') .
$this->generateOutput(' 25/50 [==============>-------------] 50%'),
stream_get_contents($output->getStream())
);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage You must start the progress bar
*/
public function testSetCurrentBeforeStarting()
{
$progress = new ProgressHelper();
$progress->setCurrent(15);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage You can't regress the progress bar
*/
public function testRegressProgress()
{
$progress = new ProgressHelper();
$progress->start($output = $this->getOutputStream(), 50);
$progress->setCurrent(15);
$progress->setCurrent(10);
}
public function testMultiByteSupport()
{
if (!function_exists('mb_strlen') || (false === $encoding = mb_detect_encoding('■'))) {
$this->markTestSkipped('The mbstring extension is needed for multi-byte support');
}
$progress = new ProgressHelper();
$progress->start($output = $this->getOutputStream());
$progress->setBarCharacter('■');
$progress->advance(3);
rewind($output->getStream());
$this->assertEquals($this->generateOutput(' 3 [■■■>------------------------]'), stream_get_contents($output->getStream()));
}
protected function getOutputStream()
{
return new StreamOutput(fopen('php://memory', 'r+', false));
@ -86,10 +163,10 @@ class ProgressHelperTest extends \PHPUnit_Framework_TestCase
$expectedout = $expected;
if ($this->lastMessagesLength !== null) {
$expectedout = str_repeat("\x20", $this->lastMessagesLength)."\x0D".$expected;
$expectedout = str_pad($expected, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
}
$this->lastMessagesLength = strlen($expected);
$this->lastMessagesLength = strlen($expectedout);
return "\x0D".$expectedout;
}