[Console] refactored the progress bar to allow placeholder to be extensible
This commit is contained in:
parent
4e76aa3fbc
commit
2a78a09f1b
@ -59,4 +59,31 @@ abstract class Helper implements HelperInterface
|
||||
|
||||
return mb_strlen($string, $encoding);
|
||||
}
|
||||
|
||||
public static function formatTime($secs)
|
||||
{
|
||||
static $timeFormats = array(
|
||||
array(0, '< 1 sec'),
|
||||
array(2, '1 sec'),
|
||||
array(59, 'secs', 1),
|
||||
array(60, '1 min'),
|
||||
array(3600, 'mins', 60),
|
||||
array(5400, '1 hr'),
|
||||
array(86400, 'hrs', 3600),
|
||||
array(129600, '1 day'),
|
||||
array(604800, 'days', 86400),
|
||||
);
|
||||
|
||||
foreach ($timeFormats as $format) {
|
||||
if ($secs >= $format[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (2 == count($format)) {
|
||||
return $format[1];
|
||||
}
|
||||
|
||||
return ceil($secs / $format[2]).' '.$format[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,48 +44,12 @@ class ProgressBar
|
||||
private $step;
|
||||
private $max;
|
||||
private $startTime;
|
||||
private $stepWidth;
|
||||
private $percent;
|
||||
private $lastMessagesLength;
|
||||
private $barCharOriginal;
|
||||
|
||||
/**
|
||||
* List of formatting variables
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $defaultFormatVars = array(
|
||||
'current',
|
||||
'max',
|
||||
'bar',
|
||||
'percent',
|
||||
'elapsed',
|
||||
);
|
||||
|
||||
/**
|
||||
* Available formatting variables
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $formatVars;
|
||||
|
||||
/**
|
||||
* Various time formats
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $timeFormats = array(
|
||||
array(0, '???'),
|
||||
array(2, '1 sec'),
|
||||
array(59, 'secs', 1),
|
||||
array(60, '1 min'),
|
||||
array(3600, 'mins', 60),
|
||||
array(5400, '1 hr'),
|
||||
array(86400, 'hrs', 3600),
|
||||
array(129600, '1 day'),
|
||||
array(604800, 'days', 86400),
|
||||
);
|
||||
|
||||
private $stepWidth;
|
||||
private $percent;
|
||||
static private $formatters;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -99,6 +63,77 @@ class ProgressBar
|
||||
$this->output = $output->isDecorated() ? $output : new NullOutput();
|
||||
$this->max = (int) $max;
|
||||
$this->stepWidth = $this->max > 0 ? Helper::strlen($this->max) : 4;
|
||||
|
||||
if (!self::$formatters) {
|
||||
self::$formatters = self::initPlaceholderFormatters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a placeholder formatter for a given name.
|
||||
*
|
||||
* This method also allow you to override an existing placeholder.
|
||||
*
|
||||
* @param string $name The placeholder name (including the delimiter char like %)
|
||||
* @param callable $callable A PHP callable
|
||||
*/
|
||||
public static function setPlaceholderFormatter($name, $callable)
|
||||
{
|
||||
if (!self::$formatters) {
|
||||
self::$formatters = self::initPlaceholderFormatters();
|
||||
}
|
||||
|
||||
self::$formatters[$name] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress bar start time.
|
||||
*
|
||||
* @return int The progress bar start time
|
||||
*/
|
||||
public function getStartTime()
|
||||
{
|
||||
return $this->startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress bar maximal steps.
|
||||
*
|
||||
* @return int The progress bar max steps
|
||||
*/
|
||||
public function getMaxSteps()
|
||||
{
|
||||
return $this->max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress bar step.
|
||||
*
|
||||
* @return int The progress bar step
|
||||
*/
|
||||
public function getStep()
|
||||
{
|
||||
return $this->step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress bar step width.
|
||||
*
|
||||
* @return int The progress bar step width
|
||||
*/
|
||||
public function getStepWidth()
|
||||
{
|
||||
return $this->stepWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current progress bar percent.
|
||||
*
|
||||
* @return int The current progress bar percent
|
||||
*/
|
||||
public function getProgressPercent()
|
||||
{
|
||||
return $this->percent;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,6 +146,16 @@ class ProgressBar
|
||||
$this->barWidth = (int) $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress bar width.
|
||||
*
|
||||
* @return int The progress bar size
|
||||
*/
|
||||
public function getBarWidth()
|
||||
{
|
||||
return $this->barWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bar character.
|
||||
*
|
||||
@ -121,6 +166,16 @@ class ProgressBar
|
||||
$this->barChar = $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bar character.
|
||||
*
|
||||
* @return string A character
|
||||
*/
|
||||
public function getBarCharacter()
|
||||
{
|
||||
return $this->barChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the empty bar character.
|
||||
*
|
||||
@ -131,6 +186,16 @@ class ProgressBar
|
||||
$this->emptyBarChar = $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the empty bar character.
|
||||
*
|
||||
* @return string A character
|
||||
*/
|
||||
public function getEmptyBarCharacter()
|
||||
{
|
||||
return $this->emptyBarChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress bar character.
|
||||
*
|
||||
@ -141,6 +206,16 @@ class ProgressBar
|
||||
$this->progressChar = $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress bar character.
|
||||
*
|
||||
* @return string A character
|
||||
*/
|
||||
public function getProgressCharacter()
|
||||
{
|
||||
return $this->progressChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress bar format.
|
||||
*
|
||||
@ -176,13 +251,6 @@ class ProgressBar
|
||||
$this->format = $this->determineBestFormat();
|
||||
}
|
||||
|
||||
$this->formatVars = array();
|
||||
foreach ($this->defaultFormatVars as $var) {
|
||||
if (false !== strpos($this->format, "%{$var}%")) {
|
||||
$this->formatVars[$var] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->max) {
|
||||
$this->barCharOriginal = $this->barChar;
|
||||
$this->barChar = $this->emptyBarChar;
|
||||
@ -267,11 +335,11 @@ class ProgressBar
|
||||
throw new \LogicException('You must start the progress bar before calling display().');
|
||||
}
|
||||
|
||||
$message = $this->format;
|
||||
foreach ($this->generate() as $name => $value) {
|
||||
$message = str_replace("%{$name}%", $value, $message);
|
||||
}
|
||||
$this->overwrite($message);
|
||||
$regex = implode('|', array_keys(self::$formatters));
|
||||
$self = $this;
|
||||
$this->overwrite(preg_replace_callback("{($regex)}", function ($matches) use ($self) {
|
||||
return call_user_func(self::$formatters[$matches[1]], $self);
|
||||
}, $this->format));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -286,72 +354,6 @@ class ProgressBar
|
||||
$this->overwrite('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the array map of format variables to values.
|
||||
*
|
||||
* @return array Array of format vars and values
|
||||
*/
|
||||
private function generate()
|
||||
{
|
||||
$vars = array();
|
||||
|
||||
if (isset($this->formatVars['bar'])) {
|
||||
$completeBars = floor($this->max > 0 ? $this->percent * $this->barWidth : $this->step % $this->barWidth);
|
||||
$emptyBars = $this->barWidth - $completeBars - Helper::strlen($this->progressChar);
|
||||
$bar = str_repeat($this->barChar, $completeBars);
|
||||
if ($completeBars < $this->barWidth) {
|
||||
$bar .= $this->progressChar;
|
||||
$bar .= str_repeat($this->emptyBarChar, $emptyBars);
|
||||
}
|
||||
|
||||
$vars['bar'] = $bar;
|
||||
}
|
||||
|
||||
if (isset($this->formatVars['elapsed'])) {
|
||||
$elapsed = time() - $this->startTime;
|
||||
$vars['elapsed'] = str_pad($this->humaneTime($elapsed), 6, ' ', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
if (isset($this->formatVars['current'])) {
|
||||
$vars['current'] = str_pad($this->step, $this->stepWidth, ' ', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
if (isset($this->formatVars['max'])) {
|
||||
$vars['max'] = $this->max;
|
||||
}
|
||||
|
||||
if (isset($this->formatVars['percent'])) {
|
||||
$vars['percent'] = str_pad(floor($this->percent * 100), 3, ' ', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts seconds into human-readable format.
|
||||
*
|
||||
* @param integer $secs Number of seconds
|
||||
*
|
||||
* @return string Time in readable format
|
||||
*/
|
||||
private function humaneTime($secs)
|
||||
{
|
||||
$text = '';
|
||||
foreach ($this->timeFormats as $format) {
|
||||
if ($secs < $format[0]) {
|
||||
if (count($format) == 2) {
|
||||
$text = $format[1];
|
||||
break;
|
||||
} else {
|
||||
$text = ceil($secs / $format[2]).' '.$format[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites a previous message to the output.
|
||||
*
|
||||
@ -400,4 +402,32 @@ class ProgressBar
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
static private function initPlaceholderFormatters()
|
||||
{
|
||||
return array(
|
||||
'%bar%' => function (ProgressBar $bar) {
|
||||
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getStep() % $bar->getBarWidth());
|
||||
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlen($bar->getProgressCharacter());
|
||||
$display = str_repeat($bar->getBarCharacter(), $completeBars);
|
||||
if ($completeBars < $bar->getBarWidth()) {
|
||||
$display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
|
||||
}
|
||||
|
||||
return $display;
|
||||
},
|
||||
'%elapsed%' => function (ProgressBar $bar) {
|
||||
return str_pad(Helper::formatTime(time() - $bar->getStartTime()), 6, ' ', STR_PAD_LEFT);
|
||||
},
|
||||
'%current%' => function (ProgressBar $bar) {
|
||||
return str_pad($bar->getStep(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
|
||||
},
|
||||
'%max%' => function (ProgressBar $bar) {
|
||||
return $bar->getMaxSteps();
|
||||
},
|
||||
'%percent%' => function (ProgressBar $bar) {
|
||||
return str_pad(floor($bar->getProgressPercent() * 100), 3, ' ', STR_PAD_LEFT);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -298,6 +298,27 @@ class ProgressBarTest extends \PHPUnit_Framework_TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddingPlaceholderFormatter()
|
||||
{
|
||||
ProgressBar::setPlaceholderFormatter('%remaining_steps%', function (ProgressBar $bar) {
|
||||
return $bar->getMaxSteps() - $bar->getStep();
|
||||
});
|
||||
$bar = new ProgressBar($output = $this->getOutputStream(), 3);
|
||||
$bar->setFormat(' %remaining_steps% [%bar%]');
|
||||
|
||||
$bar->start();
|
||||
$bar->advance();
|
||||
$bar->finish();
|
||||
|
||||
rewind($output->getStream());
|
||||
$this->assertEquals(
|
||||
$this->generateOutput(' 3 [>---------------------------]').
|
||||
$this->generateOutput(' 2 [=========>------------------]').
|
||||
$this->generateOutput(' 0 [============================]'),
|
||||
stream_get_contents($output->getStream())
|
||||
);
|
||||
}
|
||||
|
||||
protected function getOutputStream($decorated = true)
|
||||
{
|
||||
return new StreamOutput(fopen('php://memory', 'r+', false), StreamOutput::VERBOSITY_NORMAL, $decorated);
|
||||
|
Reference in New Issue
Block a user