1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\Console\Helper;
13:
14: use Symfony\Component\Console\Output\OutputInterface;
15:
16: 17: 18: 19: 20: 21:
22: class ProgressHelper extends Helper
23: {
24: const FORMAT_QUIET = ' %percent%%';
25: const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%';
26: const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%';
27: const FORMAT_QUIET_NOMAX = ' %current%';
28: const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]';
29: const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%';
30:
31:
32: private $barWidth = 28;
33: private $barChar = '=';
34: private $emptyBarChar = '-';
35: private $progressChar = '>';
36: private $format = null;
37: private $redrawFreq = 1;
38:
39: private $lastMessagesLength;
40: private $barCharOriginal;
41:
42: 43: 44:
45: private $output;
46:
47: 48: 49: 50: 51:
52: private $current;
53:
54: 55: 56: 57: 58:
59: private $max;
60:
61: 62: 63: 64: 65:
66: private $startTime;
67:
68: 69: 70: 71: 72:
73: private $defaultFormatVars = array(
74: 'current',
75: 'max',
76: 'bar',
77: 'percent',
78: 'elapsed',
79: );
80:
81: 82: 83: 84: 85:
86: private $formatVars;
87:
88: 89: 90: 91: 92:
93: private $widths = array(
94: 'current' => 4,
95: 'max' => 4,
96: 'percent' => 3,
97: 'elapsed' => 6,
98: );
99:
100: 101: 102: 103: 104:
105: private $timeFormats = array(
106: array(0, '???'),
107: array(2, '1 sec'),
108: array(59, 'secs', 1),
109: array(60, '1 min'),
110: array(3600, 'mins', 60),
111: array(5400, '1 hr'),
112: array(86400, 'hrs', 3600),
113: array(129600, '1 day'),
114: array(604800, 'days', 86400),
115: );
116:
117: 118: 119: 120: 121:
122: public function setBarWidth($size)
123: {
124: $this->barWidth = (int) $size;
125: }
126:
127: 128: 129: 130: 131:
132: public function setBarCharacter($char)
133: {
134: $this->barChar = $char;
135: }
136:
137: 138: 139: 140: 141:
142: public function setEmptyBarCharacter($char)
143: {
144: $this->emptyBarChar = $char;
145: }
146:
147: 148: 149: 150: 151:
152: public function setProgressCharacter($char)
153: {
154: $this->progressChar = $char;
155: }
156:
157: 158: 159: 160: 161:
162: public function setFormat($format)
163: {
164: $this->format = $format;
165: }
166:
167: 168: 169: 170: 171:
172: public function setRedrawFrequency($freq)
173: {
174: $this->redrawFreq = (int) $freq;
175: }
176:
177: 178: 179: 180: 181: 182:
183: public function start(OutputInterface $output, $max = null)
184: {
185: $this->startTime = time();
186: $this->current = 0;
187: $this->max = (int) $max;
188: $this->output = $output;
189:
190: if (null === $this->format) {
191: switch ($output->getVerbosity()) {
192: case OutputInterface::VERBOSITY_QUIET:
193: $this->format = self::FORMAT_QUIET_NOMAX;
194: if ($this->max > 0) {
195: $this->format = self::FORMAT_QUIET;
196: }
197: break;
198: case OutputInterface::VERBOSITY_VERBOSE:
199: $this->format = self::FORMAT_VERBOSE_NOMAX;
200: if ($this->max > 0) {
201: $this->format = self::FORMAT_VERBOSE;
202: }
203: break;
204: default:
205: $this->format = self::FORMAT_NORMAL_NOMAX;
206: if ($this->max > 0) {
207: $this->format = self::FORMAT_NORMAL;
208: }
209: break;
210: }
211: }
212:
213: $this->initialize();
214: }
215:
216: 217: 218: 219: 220: 221: 222: 223:
224: public function advance($step = 1, $redraw = false)
225: {
226: if (null === $this->startTime) {
227: throw new \LogicException('You must start the progress bar before calling advance().');
228: }
229:
230: if ($this->current === 0) {
231: $redraw = true;
232: }
233: $this->current += $step;
234: if ($redraw || $this->current % $this->redrawFreq === 0) {
235: $this->display();
236: }
237: }
238:
239: 240: 241: 242: 243: 244: 245:
246: public function display($finish = false)
247: {
248: if (null === $this->startTime) {
249: throw new \LogicException('You must start the progress bar before calling display().');
250: }
251:
252: $message = $this->format;
253: foreach ($this->generate($finish) as $name => $value) {
254: $message = str_replace("%{$name}%", $value, $message);
255: }
256: $this->overwrite($this->output, $message);
257: }
258:
259: 260: 261:
262: public function finish()
263: {
264: if (null === $this->startTime) {
265: throw new \LogicException('You must start the progress bar before calling finish().');
266: }
267:
268: if ($this->startTime !== null) {
269: if (!$this->max) {
270: $this->barChar = $this->barCharOriginal;
271: $this->display(true);
272: }
273: $this->startTime = null;
274: $this->output->writeln('');
275: $this->output = null;
276: }
277: }
278:
279: 280: 281:
282: private function initialize()
283: {
284: $this->formatVars = array();
285: foreach ($this->defaultFormatVars as $var) {
286: if (strpos($this->format, "%{$var}%") !== false) {
287: $this->formatVars[$var] = true;
288: }
289: }
290:
291: if ($this->max > 0) {
292: $this->widths['max'] = strlen($this->max);
293: $this->widths['current'] = $this->widths['max'];
294: } else {
295: $this->barCharOriginal = $this->barChar;
296: $this->barChar = $this->emptyBarChar;
297: }
298: }
299:
300: 301: 302: 303: 304: 305: 306:
307: private function generate($finish = false)
308: {
309: $vars = array();
310: $percent = 0;
311: if ($this->max > 0) {
312: $percent = (double) round($this->current / $this->max, 2);
313: }
314:
315: if (isset($this->formatVars['bar'])) {
316: $completeBars = 0;
317: $emptyBars = 0;
318: if ($this->max > 0) {
319: $completeBars = floor($percent * $this->barWidth);
320: } else {
321: if (!$finish) {
322: $completeBars = floor($this->current % $this->barWidth);
323: } else {
324: $completeBars = $this->barWidth;
325: }
326: }
327:
328: $emptyBars = $this->barWidth - $completeBars - strlen($this->progressChar);
329: $bar = str_repeat($this->barChar, $completeBars);
330: if ($completeBars < $this->barWidth) {
331: $bar .= $this->progressChar;
332: $bar .= str_repeat($this->emptyBarChar, $emptyBars);
333: }
334:
335: $vars['bar'] = $bar;
336: }
337:
338: if (isset($this->formatVars['elapsed'])) {
339: $elapsed = time() - $this->startTime;
340: $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT);
341: }
342:
343: if (isset($this->formatVars['current'])) {
344: $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT);
345: }
346:
347: if (isset($this->formatVars['max'])) {
348: $vars['max'] = $this->max;
349: }
350:
351: if (isset($this->formatVars['percent'])) {
352: $vars['percent'] = str_pad($percent * 100, $this->widths['percent'], ' ', STR_PAD_LEFT);
353: }
354:
355: return $vars;
356: }
357:
358: 359: 360: 361: 362: 363: 364:
365: private function humaneTime($secs)
366: {
367: $text = '';
368: foreach ($this->timeFormats as $format) {
369: if ($secs < $format[0]) {
370: if (count($format) == 2) {
371: $text = $format[1];
372: break;
373: } else {
374: $text = ceil($secs / $format[2]) . ' ' . $format[1];
375: break;
376: }
377: }
378: }
379:
380: return $text;
381: }
382:
383: 384: 385: 386: 387: 388:
389: private function overwrite(OutputInterface $output, $messages)
390: {
391:
392: $output->write("\x0D");
393: if ($this->lastMessagesLength!==null) {
394:
395: $output->write(str_repeat("\x20", $this->lastMessagesLength));
396:
397: $output->write("\x0D");
398: }
399: $output->write($messages);
400:
401: $this->lastMessagesLength=strlen($messages);
402: }
403:
404: 405: 406:
407: public function getName()
408: {
409: return 'progress';
410: }
411: }
412: