8000 added progress indicator helper · symfony/symfony@abacef0 · GitHub
[go: up one dir, main page]

Skip to content

Commit abacef0

Browse files
committed
added progress indicator helper
1 parent dc4a7df commit abacef0

File tree

2 files changed

+391
-0
lines changed

2 files changed

+391
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Helper;
13+
14+
use Symfony\Component\Console\Output\OutputInterface;
15+
16+
/**
17+
* @author Kevin Bond <kevinbond@gmail.com>
18+
*/
19+
class ProgressIndicator
20+
{
21+
private $output;
22+
private $startTime;
23+
private $format;
24+
private $message = null;
25+
private $indicatorValues = array('-', '\\', '|', '/');
26+
private $indicatorCurrent = 0;
27+
private $lastMessagesLength = 0;
28+
29+
private static $formatters;
30+
private static $formats;
31+
32+
/**
33+
* @param OutputInterface $output
34+
*/
35+
public function __construct(OutputInterface $output)
36+
{
37+
$this->output = $output;
38+
$this->startTime = time();
39+
40+
$this->setFormat($this->determineBestFormat());
41+
}
42+
43+
/**
44+
* @param string $format
45+
*/
46+
public function setFormat($format)
47+
{
48+
$this->format = self::getFormatDefinition($format);
49+
}
50+
51+
/**
52+
* @param string|null $message
53+
*/
54+
public function setMessage($message)
55+
{
56+
$this->message = $message;
57+
58+
$this->display();
59+
}
60+
61+
/**
62+
* @return string|null
63+
*/
64+
public function getMessage()
65+
{
66+
return $this->message;
67+
}
68+
69+
/**
70+
* @param array $values
71+
*/
72+
public function setIndicatorValues(array $values)
73+
{
74+
$values = array_values($values);
75+
76+
if (empty($values)) {
77+
throw new \InvalidArgumentException('Must have at least 1 value.');
78+
}
79+
80+
$this->indicatorValues = $values;
81+
}
82+
83+
/**
84+
* Gets the progress bar start time.
85+
*
86+
* @return int The progress bar start time
87+
*/
88+
public function getStartTime()
89+
{
90+
return $this->startTime;
91+
}
92+
93+
/**
94+
* @return string
95+
*/
96+
public function getCurrentValue()
97+
{
98+
return $this->indicatorValues[$this->indicatorCurrent % count($this->indicatorValues)];
99+
}
100+
101+
public function start($message)
102+
{
103+
$this->message = $message;
104+
$this->startTime = time();
105+
$this->indicatorCurrent = 0;
106+
107+
$this->display();
108+
}
109+
110+
public function advance()
111+
{
112+
++$this->indicatorCurrent;
113+
114+
if ($this->output->isDecorated()) {
115+
$this->display();
116+
}
117+
}
118+
119+
public function finish($message)
120+
{
121+
$this->message = $message;
122+
$this->display();
123+
$this->output->writeln('');
124+
}
125+
126+
/**
127+
* Gets the format for a given name.
128+
*
129+
* @param string $name The format name
130+
*
131+
* @return string|null A format string
132+
*/
133+
public static function getFormatDefinition($name)
134+
{
135+
if (!self::$formats) {
136+
self::$formats = self::initFormats();
137+
}
138+
139+
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
140+
}
141+
142+
/**
143+
* Sets a placeholder formatter for a given name.
144+
*
145+
* This method also allow you to override an existing placeholder.
146+
*
147+
* @param string $name The placeholder name (including the delimiter char like %)
148+
* @param callable $callable A PHP callable
149+
*/
150+
public static function setPlaceholderFormatterDefinition($name, $callable)
151+
{
152+
if (!self::$formatters) {
153+
self::$formatters = self::initPlaceholderFormatters();
154+
}
155+
156+
self::$formatters[$name] = $callable;
157+
}
158+
159+
/**
160+
* Gets the placeholder formatter for a given name.
161+
*
162+
* @param string $name The placeholder name (including the delimiter char like %)
163+
*
164+
* @return callable|null A PHP callable
165+
*/
166+
public static function getPlaceholderFormatterDefinition($name)
167+
{
168+
if (!self::$formatters) {
169+
self::$formatters = self::initPlaceholderFormatters();
170+
}
171+
172+
return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
173+
}
174+
175+
private function display()
176+
{
177+
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
178+
return;
179+
}
180+
181+
$self = $this;
182+
183+
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) {
184+
if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
185+
return call_user_func($formatter, $self);
186+
}
187+
188+
return $matches[0];
189+
}, $this->format));
190+
}
191+
192+
private function determineBestFormat()
193+
{
194+
switch ($this->output->getVerbosity()) {
195+
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
196+
case OutputInterface::VERBOSITY_VERBOSE:
197+
return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
198+
case OutputInterface::VERBOSITY_VERY_VERBOSE:
199+
case OutputInterface::VERBOSITY_DEBUG:
200+
return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
201+
default:
202+
return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
203+
}
204+
}
205+
206+
/**
207+
* Overwrites a previous message to the output.
208+
*
209+
* @param string $message The message
210+
*/
211+
private function overwrite($message)
212+
{
213+
// append whitespace to match the line's length
214+
if (null !== $this->lastMessagesLength) {
215+
if ($this->lastMessagesLength > Helper::strlenWithoutDecoration($this->output->getFormatter(), $message)) {
216+
$message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
217+
}
218+
}
219+
220+
if ($this->output->isDecorated()) {
221+
$this->output->write("\x0D");
222+
$this->output->write($message);
223+
} else {
224+
$this->output->writeln($message);
225+
}
226+
227+
$this->lastMessagesLength = 0;
228+
229+
$len = Helper::strlenWithoutDecoration($this->output->getFormatter(), $message);
230+
231+
if ($len > $this->lastMessagesLength) {
232+
$this->lastMessagesLength = $len;
233+
}
234+
}
235+
236+
private static function initPlaceholderFormatters()
237+
{
238+
return array(
239+
'indicator' => function (ProgressIndicator $indicator) {
240+
return $indicator->getCurrentValue();
241+
},
242+
'message' => function (ProgressIndicator $indicator) {
243+
return $indicator->getMessage();
244+
},
245+
'elapsed' => function (ProgressIndicator $indicator) {
246+
return Helper::formatTime(time() - $indicator->getStartTime());
247+
},
248+
'memory' => function () {
249+
return Helper::formatMemory(memory_get_usage(true));
250+
},
251+
);
252+
}
253+
254+
private static function initFormats()
255+
{
256+
return array(
257+
'normal' => ' %indicator% %message%',
258+
'normal_no_ansi' => ' %message%',
259+
260+
'verbose' => ' %indicator% %message% (%elapsed:6s%)',
261+
'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
262+
263+
'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
264+
'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
265+
);
266+
}
267+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
namespace Symfony\Component\Console\Tests\Helper;
4+
5+
use Symfony\Component\Console\Helper\ProgressIndicator;
6+
use Symfony\Component\Console\Output\StreamOutput;
7+
8+
class ProgressIndicatorTest extends \PHPUnit_Framework_TestCase
9+
{
10+
public function testDefaultIndicator()
11+
{
12+
$bar = new ProgressIndicator($output = $this->getOutputStream());
13+
$bar->start('Starting...');
14+
$bar->advance();
15+
$bar->advance();
16+
$bar->advance();
17+
$bar->advance();
18+
$bar->advance();
19+
$bar->setMessage('Advancing...');
20+
$bar->advance();
21+
$bar->finish('Done...');
22+
23+
rewind($output->getStream());
24+
25+
$this->assertEquals(
26+
$this->generateOutput(' - Starting...').
27+
$this->generateOutput(' \\ Starting...').
28+
$this->generateOutput(' | Starting...').
29+
$this->generateOutput(' / Starting...').
30+
$this->generateOutput(' - Starting...').
31+
$this->generateOutput(' \\ Starting...').
32+
$this->generateOutput(' \\ Advancing...').
33+
$this->generateOutput(' | Advancing...').
34+
$this->generateOutput(' | Done... ').
35+
"\n",
36+
stream_get_contents($output->getStream())
37+
);
38+
}
39+
40+
public function testNonDecoratedOutput()
41+
{
42+
$bar = new ProgressIndicator($output = $this->getOutputStream(false));
43+
44+
$bar->start('Starting...');
45+
$bar->advance();
46+
$bar->advance();
47+
$bar->setMessage('Midway...');
48+
$bar->advance();
49+
$bar->advance();
50+
$bar->finish('Done...');
51+
52+
rewind($output->getStream());
53+
54+
$this->assertEquals(
55+
' Starting...'.PHP_EOL.
56+
' Midway... '.PHP_EOL.
57+
' Done... '.PHP_EOL.PHP_EOL,
58+
stream_get_contents($output->getStream())
59+
);
60+
}
61+
62+
public function testCustomIndicatorValues()
63+
{
64+
$bar = new ProgressIndicator($output = $this->getOutputStream());
65+
$bar->setIndicatorValues(array('a', 'b', 'c'));
66+
67+
$bar->start('Starting...');
68+
$bar->advance();
69+
$bar->advance();
70+
$bar->advance();
71+
72+
rewind($output->getStream());
73+
74+
$this->assertEquals(
75+
$this->generateOutput(' a Starting...').
76+
$this->generateOutput(' b Starting...').
77+
$this->generateOutput(' c Starting...').
78+
$this->generateOutput(' a Starting...'),
79+
stream_get_contents($output->getStream())
80+
);
81+
}
82+
83+
/**
84+
* @dataProvider provideFormat
85+
*/
86+
public function testFormats($format)
87+
{
88+
$bar = new ProgressIndicator($output = $this->getOutputStream());
89+
$bar->setFormat($format);
90+
$bar->start('Starting...');
91+
$bar->advance();
92+
93+
rewind($output->getStream());
94+
95+
$this->assertNotEmpty(stream_get_contents($output->getStream()));
96+
}
97+
98+
/**
99+
* Provides each defined format.
100+
*
101+
* @return array
102+
*/
103+
public function provideFormat()
104+
{
105+
return array(
106+
array('normal'),
107+
array('verbose'),
108+
array('very_verbose'),
109+
array('debug'),
110+
);
111+
}
112+
113+
protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL)
114+
{
115+
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated);
116+
}
117+
118+
protected function generateOutput($expected)
119+
{
120+
$count = substr_count($expected, "\n");
121+
122+
return "\x0D".($count ? sprintf("\033[%dA", $count) : '').$expected;
123+
}
124+
}

0 commit comments

Comments
 (0)
0