Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2a78a09

Browse files
committed
[Console] refactored the progress bar to allow placeholder to be extensible
1 parent 4e76aa3 commit 2a78a09

File tree

3 files changed

+191
-113
lines changed

3 files changed

+191
-113
lines changed

src/Symfony/Component/Console/Helper/Helper.php

+27
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,31 @@ public static function strlen($string)
5959

6060
return mb_strlen($string, $encoding);
6161
}
62+
63+
public static function formatTime($secs)
64+
{
65+
static $timeFormats = array(
66+
array(0, '< 1 sec'),
67+
array(2, '1 sec'),
68+
array(59, 'secs', 1),
69+
array(60, '1 min'),
70+
array(3600, 'mins', 60),
71+
array(5400, '1 hr'),
72+
array(86400, 'hrs', 3600),
73+
array(129600, '1 day'),
74+
array(604800, 'days', 86400),
75+
);
76+
77+
foreach ($timeFormats as $format) {
78+
if ($secs >= $format[0]) {
79+
continue;
80+
}
81+
82+
if (2 == count($format)) {
83+
return $format[1];
84+
}
85+
86+
return ceil($secs / $format[2]).' '.$format[1];
87+
}
88+
}
6289
}

src/Symfony/Component/Console/Helper/ProgressBar.php

+143-113
Original file line numberDiff line numberDiff line change
@@ -44,61 +44,96 @@ class ProgressBar
4444
private $step;
4545
private $max;
4646
private $startTime;
47+
private $stepWidth;
48+
private $percent;
4749
private $lastMessagesLength;
4850
private $barCharOriginal;
4951

52+
static private $formatters;
53+
5054
/**
51-
* List of formatting variables
55+
* Constructor.
5256
*
53-
* @var array
57+
* @param OutputInterface $output An OutputInterface instance
58+
* @param integer $max Maximum steps (0 if unknown)
5459
*/
55-
private $defaultFormatVars = array(
56-
'current',
57-
'max',
58-
'bar',
59-
'percent',
60-
'elapsed',
61-
);
60+
public function __construct(OutputInterface $output, $max = 0)
61+
{
62+
// Disabling output when it does not support ANSI codes as it would result in a broken display anyway.
63+
$this->output = $output->isDecorated() ? $output : new NullOutput();
64+
$this->max = (int) $max;
65+
$this->stepWidth = $this->max > 0 ? Helper::strlen($this->max) : 4;
66+
67+
if (!self::$formatters) {
68+
self::$formatters = self::initPlaceholderFormatters();
69+
}
70+
}
6271

6372
/**
64-
* Available formatting variables
73+
* Sets a placeholder formatter for a given name.
74+
*
75+
* This method also allow you to override an existing placeholder.
6576
*
66-
* @var array
77+
* @param string $name The placeholder name (including the delimiter char like %)
78+
* @param callable $callable A PHP callable
6779
*/
68-
private $formatVars;
80+
public static function setPlaceholderFormatter($name, $callable)
81+
{
82+
if (!self::$formatters) {
83+
self::$formatters = self::initPlaceholderFormatters();
84+
}
85+
86+
self::$formatters[$name] = $callable;
87+
}
6988

7089
/**
71-
* Various time formats
90+
* Gets the progress bar start time.
7291
*
73-
* @var array
92+
* @return int The progress bar start time
7493
*/
75-
private $timeFormats = array(
76-
array(0, '???'),
77-
array(2, '1 sec'),
78-
array(59, 'secs', 1),
79-
array(60, '1 min'),
80-
array(3600, 'mins', 60),
81-
array(5400, '1 hr'),
82-
array(86400, 'hrs', 3600),
83-
array(129600, '1 day'),
84-
array(604800, 'days', 86400),
85-
);
94+
public function getStartTime()
95+
{
96+
return $this->startTime;
97+
}
8698

87-
private $stepWidth;
88-
private $percent;
99+
/**
100+
* Gets the progress bar maximal steps.
101+
*
102+
* @return int The progress bar max steps
103+
*/
104+
public function getMaxSteps()
105+
{
106+
return $this->max;
107+
}
89108

90109
/**
91-
* Constructor.
110+
* Gets the progress bar step.
92111
*
93-
* @param OutputInterface $output An OutputInterface instance
94-
* @param integer $max Maximum steps (0 if unknown)
112+
* @return int The progress bar step
95113
*/
96-
public function __construct(OutputInterface $output, $max = 0)
114+
public function getStep()
97115
{
98-
// Disabling output when it does not support ANSI codes as it would result in a broken display anyway.
99-
$this->output = $output->isDecorated() ? $output : new NullOutput();
100-
$this->max = (int) $max;
101-
$this->stepWidth = $this->max > 0 ? Helper::strlen($this->max) : 4;
116+
return $this->step;
117+
}
118+
119+
/**
120+
* Gets the progress bar step width.
121+
*
122+
* @return int The progress bar step width
123+
*/
124+
public function getStepWidth()
125+
{
126+
return $this->stepWidth;
127+
}
128+
129+
/**
130+
* Gets the current progress bar percent.
131+
*
132+
* @return int The current progress bar percent
133+
*/
134+
public function getProgressPercent()
135+
{
136+
return $this->percent;
102137
}
103138

104139
/**
@@ -111,6 +146,16 @@ public function setBarWidth($size)
111146
$this->barWidth = (int) $size;
112147
}
113148

149+
/**
150+
* Gets the progress bar width.
151+
*
152+
* @return int The progress bar size
153+
*/
154+
public function getBarWidth()
155+
{
156+
return $this->barWidth;
157+
}
158+
114159
/**
115160
* Sets the bar character.
116161
*
@@ -121,6 +166,16 @@ public function setBarCharacter($char)
121166
$this->barChar = $char;
122167
}
123168

169+
/**
170+
* Gets the bar character.
171+
*
172+
* @return string A character
173+
*/
174+
public function getBarCharacter()
175+
{
176+
return $this->barChar;
177+
}
178+
124179
/**
125180
* Sets the empty bar character.
126181
*
@@ -131,6 +186,16 @@ public function setEmptyBarCharacter($char)
131186
$this->emptyBarChar = $char;
132187
}
133188

189+
/**
190+
* Gets the empty bar character.
191+
*
192+
* @return string A character
193+
*/
194+
public function getEmptyBarCharacter()
195+
{
196+
return $this->emptyBarChar;
197+
}
198+
134199
/**
135200
* Sets the progress bar character.
136201
*
@@ -141,6 +206,16 @@ public function setProgressCharacter($char)
141206
$this->progressChar = $char;
142207
}
143208

209+
/**
210+
* Gets the progress bar character.
211+
*
212+
* @return string A character
213+
*/
214+
public function getProgressCharacter()
215+
{
216+
return $this->progressChar;
217+
}
218+
144219
/**
145220
* Sets the progress bar format.
146221
*
@@ -176,13 +251,6 @@ public function start()
176251
$this->format = $this->determineBestFormat();
177252
}
178253

179-
$this->formatVars = array();
180-
foreach ($this->defaultFormatVars as $var) {
181-
if (false !== strpos($this->format, "%{$var}%")) {
182-
$this->formatVars[$var] = true;
183-
}
184-
}
185-
186254
if (!$this->max) {
187255
$this->barCharOriginal = $this->barChar;
188256
$this->barChar = $this->emptyBarChar;
@@ -267,11 +335,11 @@ public function display()
267335
throw new \LogicException('You must start the progress bar before calling display().');
268336
}
269337

270-
$message = $this->format;
271-
foreach ($this->generate() as $name => $value) {
272-
$message = str_replace("%{$name}%", $value, $message);
273-
}
274-
$this->overwrite($message);
338+
$regex = implode('|', array_keys(self::$formatters));
339+
$self = $this;
340+
$this->overwrite(preg_replace_callback("{($regex)}", function ($matches) use ($self) {
341+
return call_user_func(self::$formatters[$matches[1]], $self);
342+
}, $this->format));
275343
}
276344

277345
/**
@@ -286,72 +354,6 @@ public function clear()
286354
$this->overwrite('');
287355
}
288356

289-
/**
290-
* Generates the array map of format variables to values.
291-
*
292-
* @return array Array of format vars and values
293-
*/
294-
private function generate()
295-
{
296-
$vars = array();
297-
298-
if (isset($this->formatVars['bar'])) {
299-
$completeBars = floor($this->max > 0 ? $this->percent * $this->barWidth : $this->step % $this->barWidth);
300-
$emptyBars = $this->barWidth - $completeBars - Helper::strlen($this->progressChar);
301-
$bar = str_repeat($this->barChar, $completeBars);
302-
if ($completeBars < $this->barWidth) {
303-
$bar .= $this->progressChar;
304-
$bar .= str_repeat($this->emptyBarChar, $emptyBars);
305-
}
306-
307-
$vars['bar'] = $bar;
308-
}
309-
310-
if (isset($this->formatVars['elapsed'])) {
311-
$elapsed = time() - $this->startTime;
312-
$vars['elapsed'] = str_pad($this->humaneTime($elapsed), 6, ' ', STR_PAD_LEFT);
313-
}
314-
315-
if (isset($this->formatVars['current'])) {
316-
$vars['current'] = str_pad($this->step, $this->stepWidth, ' ', STR_PAD_LEFT);
317-
}
318-
319-
if (isset($this->formatVars['max'])) {
320-
$vars['max'] = $this->max;
321-
}
322-
323-
if (isset($this->formatVars['percent'])) {
324-
$vars['percent'] = str_pad(floor($this->percent * 100), 3, ' ', STR_PAD_LEFT);
325-
}
326-
327-
return $vars;
328-
}
329-
330-
/**
331-
* Converts seconds into human-readable format.
332-
*
333-
* @param integer $secs Number of seconds
334-
*
335-
* @return string Time in readable format
336-
*/
337-
private function humaneTime($secs)
338-
{
339-
$text = '';
340-
foreach ($this->timeFormats as $format) {
341-
if ($secs < $format[0]) {
342-
if (count($format) == 2) {
343-
$text = $format[1];
344-
break;
345-
} else {
346-
$text = ceil($secs / $format[2]).' '.$format[1];
347-
break;
348-
}
349-
}
350-
}
351-
352-
return $text;
353-
}
354-
355357
/**
356358
* Overwrites a previous message to the output.
357359
*
@@ -400,4 +402,32 @@ private function determineBestFormat()
400402

401403
return $format;
402404
}
405+
406+
static private function initPlaceholderFormatters()
407+
{
408+
return array(
409+
'%bar%' => function (ProgressBar $bar) {
410+
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getStep() % $bar->getBarWidth());
411+
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlen($bar->getProgressCharacter());
412+
$display = str_repeat($bar->getBarCharacter(), $completeBars);
413+
if ($completeBars < $bar->getBarWidth()) {
414+
$display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
415+
}
416+
417+
return $display;
418+
},
419+
'%elapsed%' => function (ProgressBar $bar) {
420+
return str_pad(Helper::formatTime(time() - $bar->getStartTime()), 6, ' ', STR_PAD_LEFT);
421+
},
422+
'%current%' => function (ProgressBar $bar) {
423+
return str_pad($bar->getStep(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
424+
},
425+
'%max%' => function (ProgressBar $bar) {
426+
return $bar->getMaxSteps();
427+
},
428+
'%percent%' => function (ProgressBar $bar) {
429+
return str_pad(floor($bar->getProgressPercent() * 100), 3, ' ', STR_PAD_LEFT);
430+
},
431+
);
432+
}
403433
}

src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php

+21
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,27 @@ public function testParallelBars()
298298
);
299299
}
300300

301+
public function testAddingPlaceholderFormatter()
302+
{
303+
ProgressBar::setPlaceholderFormatter('%remaining_steps%', function (ProgressBar $bar) {
304+
return $bar->getMaxSteps() - $bar->getStep();
305+
});
306+
$bar = new ProgressBar($output = $this->getOutputStream(), 3);
307+
$bar->setFormat(' %remaining_steps% [%bar%]');
308+
309+
$bar->start();
310+
$bar->advance();
311+
$bar->finish();
312+
313+
rewind($output->getStream());
314+
$this->assertEquals(
315+
$this->generateOutput(' 3 [>---------------------------]').
316+
$this->generateOutput(' 2 [=========>------------------]').
317+
$this->generateOutput(' 0 [============================]'),
318+
stream_get_contents($output->getStream())
319+
);
320+
}
321+
301322
protected function getOutputStream($decorated = true)
302323
{
303324
return new StreamOutput(fopen('php://memory', 'r+', false), StreamOutput::VERBOSITY_NORMAL, $decorated);

0 commit comments

Comments
 (0)