comparison vendor/symfony/console/Helper/ProgressBar.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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\ConsoleOutputInterface;
15 use Symfony\Component\Console\Output\OutputInterface;
16 use Symfony\Component\Console\Exception\LogicException;
17 use Symfony\Component\Console\Terminal;
18
19 /**
20 * The ProgressBar provides helpers to display progress output.
21 *
22 * @author Fabien Potencier <fabien@symfony.com>
23 * @author Chris Jones <leeked@gmail.com>
24 */
25 class ProgressBar
26 {
27 // options
28 private $barWidth = 28;
29 private $barChar;
30 private $emptyBarChar = '-';
31 private $progressChar = '>';
32 private $format;
33 private $internalFormat;
34 private $redrawFreq = 1;
35
36 /**
37 * @var OutputInterface
38 */
39 private $output;
40 private $step = 0;
41 private $max;
42 private $startTime;
43 private $stepWidth;
44 private $percent = 0.0;
45 private $formatLineCount;
46 private $messages = array();
47 private $overwrite = true;
48 private $terminal;
49 private $firstRun = true;
50
51 private static $formatters;
52 private static $formats;
53
54 /**
55 * @param OutputInterface $output An OutputInterface instance
56 * @param int $max Maximum steps (0 if unknown)
57 */
58 public function __construct(OutputInterface $output, $max = 0)
59 {
60 if ($output instanceof ConsoleOutputInterface) {
61 $output = $output->getErrorOutput();
62 }
63
64 $this->output = $output;
65 $this->setMaxSteps($max);
66 $this->terminal = new Terminal();
67
68 if (!$this->output->isDecorated()) {
69 // disable overwrite when output does not support ANSI codes.
70 $this->overwrite = false;
71
72 // set a reasonable redraw frequency so output isn't flooded
73 $this->setRedrawFrequency($max / 10);
74 }
75
76 $this->startTime = time();
77 }
78
79 /**
80 * Sets a placeholder formatter for a given name.
81 *
82 * This method also allow you to override an existing placeholder.
83 *
84 * @param string $name The placeholder name (including the delimiter char like %)
85 * @param callable $callable A PHP callable
86 */
87 public static function setPlaceholderFormatterDefinition($name, callable $callable)
88 {
89 if (!self::$formatters) {
90 self::$formatters = self::initPlaceholderFormatters();
91 }
92
93 self::$formatters[$name] = $callable;
94 }
95
96 /**
97 * Gets the placeholder formatter for a given name.
98 *
99 * @param string $name The placeholder name (including the delimiter char like %)
100 *
101 * @return callable|null A PHP callable
102 */
103 public static function getPlaceholderFormatterDefinition($name)
104 {
105 if (!self::$formatters) {
106 self::$formatters = self::initPlaceholderFormatters();
107 }
108
109 return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
110 }
111
112 /**
113 * Sets a format for a given name.
114 *
115 * This method also allow you to override an existing format.
116 *
117 * @param string $name The format name
118 * @param string $format A format string
119 */
120 public static function setFormatDefinition($name, $format)
121 {
122 if (!self::$formats) {
123 self::$formats = self::initFormats();
124 }
125
126 self::$formats[$name] = $format;
127 }
128
129 /**
130 * Gets the format for a given name.
131 *
132 * @param string $name The format name
133 *
134 * @return string|null A format string
135 */
136 public static function getFormatDefinition($name)
137 {
138 if (!self::$formats) {
139 self::$formats = self::initFormats();
140 }
141
142 return isset(self::$formats[$name]) ? self::$formats[$name] : null;
143 }
144
145 /**
146 * Associates a text with a named placeholder.
147 *
148 * The text is displayed when the progress bar is rendered but only
149 * when the corresponding placeholder is part of the custom format line
150 * (by wrapping the name with %).
151 *
152 * @param string $message The text to associate with the placeholder
153 * @param string $name The name of the placeholder
154 */
155 public function setMessage($message, $name = 'message')
156 {
157 $this->messages[$name] = $message;
158 }
159
160 public function getMessage($name = 'message')
161 {
162 return $this->messages[$name];
163 }
164
165 /**
166 * Gets the progress bar start time.
167 *
168 * @return int The progress bar start time
169 */
170 public function getStartTime()
171 {
172 return $this->startTime;
173 }
174
175 /**
176 * Gets the progress bar maximal steps.
177 *
178 * @return int The progress bar max steps
179 */
180 public function getMaxSteps()
181 {
182 return $this->max;
183 }
184
185 /**
186 * Gets the current step position.
187 *
188 * @return int The progress bar step
189 */
190 public function getProgress()
191 {
192 return $this->step;
193 }
194
195 /**
196 * Gets the progress bar step width.
197 *
198 * @return int The progress bar step width
199 */
200 private function getStepWidth()
201 {
202 return $this->stepWidth;
203 }
204
205 /**
206 * Gets the current progress bar percent.
207 *
208 * @return float The current progress bar percent
209 */
210 public function getProgressPercent()
211 {
212 return $this->percent;
213 }
214
215 /**
216 * Sets the progress bar width.
217 *
218 * @param int $size The progress bar size
219 */
220 public function setBarWidth($size)
221 {
222 $this->barWidth = max(1, (int) $size);
223 }
224
225 /**
226 * Gets the progress bar width.
227 *
228 * @return int The progress bar size
229 */
230 public function getBarWidth()
231 {
232 return $this->barWidth;
233 }
234
235 /**
236 * Sets the bar character.
237 *
238 * @param string $char A character
239 */
240 public function setBarCharacter($char)
241 {
242 $this->barChar = $char;
243 }
244
245 /**
246 * Gets the bar character.
247 *
248 * @return string A character
249 */
250 public function getBarCharacter()
251 {
252 if (null === $this->barChar) {
253 return $this->max ? '=' : $this->emptyBarChar;
254 }
255
256 return $this->barChar;
257 }
258
259 /**
260 * Sets the empty bar character.
261 *
262 * @param string $char A character
263 */
264 public function setEmptyBarCharacter($char)
265 {
266 $this->emptyBarChar = $char;
267 }
268
269 /**
270 * Gets the empty bar character.
271 *
272 * @return string A character
273 */
274 public function getEmptyBarCharacter()
275 {
276 return $this->emptyBarChar;
277 }
278
279 /**
280 * Sets the progress bar character.
281 *
282 * @param string $char A character
283 */
284 public function setProgressCharacter($char)
285 {
286 $this->progressChar = $char;
287 }
288
289 /**
290 * Gets the progress bar character.
291 *
292 * @return string A character
293 */
294 public function getProgressCharacter()
295 {
296 return $this->progressChar;
297 }
298
299 /**
300 * Sets the progress bar format.
301 *
302 * @param string $format The format
303 */
304 public function setFormat($format)
305 {
306 $this->format = null;
307 $this->internalFormat = $format;
308 }
309
310 /**
311 * Sets the redraw frequency.
312 *
313 * @param int|float $freq The frequency in steps
314 */
315 public function setRedrawFrequency($freq)
316 {
317 $this->redrawFreq = max((int) $freq, 1);
318 }
319
320 /**
321 * Starts the progress output.
322 *
323 * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
324 */
325 public function start($max = null)
326 {
327 $this->startTime = time();
328 $this->step = 0;
329 $this->percent = 0.0;
330
331 if (null !== $max) {
332 $this->setMaxSteps($max);
333 }
334
335 $this->display();
336 }
337
338 /**
339 * Advances the progress output X steps.
340 *
341 * @param int $step Number of steps to advance
342 */
343 public function advance($step = 1)
344 {
345 $this->setProgress($this->step + $step);
346 }
347
348 /**
349 * Sets whether to overwrite the progressbar, false for new line.
350 *
351 * @param bool $overwrite
352 */
353 public function setOverwrite($overwrite)
354 {
355 $this->overwrite = (bool) $overwrite;
356 }
357
358 /**
359 * Sets the current progress.
360 *
361 * @param int $step The current progress
362 */
363 public function setProgress($step)
364 {
365 $step = (int) $step;
366
367 if ($this->max && $step > $this->max) {
368 $this->max = $step;
369 } elseif ($step < 0) {
370 $step = 0;
371 }
372
373 $prevPeriod = (int) ($this->step / $this->redrawFreq);
374 $currPeriod = (int) ($step / $this->redrawFreq);
375 $this->step = $step;
376 $this->percent = $this->max ? (float) $this->step / $this->max : 0;
377 if ($prevPeriod !== $currPeriod || $this->max === $step) {
378 $this->display();
379 }
380 }
381
382 /**
383 * Finishes the progress output.
384 */
385 public function finish()
386 {
387 if (!$this->max) {
388 $this->max = $this->step;
389 }
390
391 if ($this->step === $this->max && !$this->overwrite) {
392 // prevent double 100% output
393 return;
394 }
395
396 $this->setProgress($this->max);
397 }
398
399 /**
400 * Outputs the current progress string.
401 */
402 public function display()
403 {
404 if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
405 return;
406 }
407
408 if (null === $this->format) {
409 $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
410 }
411
412 $this->overwrite($this->buildLine());
413 }
414
415 /**
416 * Removes the progress bar from the current line.
417 *
418 * This is useful if you wish to write some output
419 * while a progress bar is running.
420 * Call display() to show the progress bar again.
421 */
422 public function clear()
423 {
424 if (!$this->overwrite) {
425 return;
426 }
427
428 if (null === $this->format) {
429 $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
430 }
431
432 $this->overwrite('');
433 }
434
435 /**
436 * Sets the progress bar format.
437 *
438 * @param string $format The format
439 */
440 private function setRealFormat($format)
441 {
442 // try to use the _nomax variant if available
443 if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
444 $this->format = self::getFormatDefinition($format.'_nomax');
445 } elseif (null !== self::getFormatDefinition($format)) {
446 $this->format = self::getFormatDefinition($format);
447 } else {
448 $this->format = $format;
449 }
450
451 $this->formatLineCount = substr_count($this->format, "\n");
452 }
453
454 /**
455 * Sets the progress bar maximal steps.
456 *
457 * @param int $max The progress bar max steps
458 */
459 private function setMaxSteps($max)
460 {
461 $this->max = max(0, (int) $max);
462 $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4;
463 }
464
465 /**
466 * Overwrites a previous message to the output.
467 *
468 * @param string $message The message
469 */
470 private function overwrite($message)
471 {
472 if ($this->overwrite) {
473 if (!$this->firstRun) {
474 // Move the cursor to the beginning of the line
475 $this->output->write("\x0D");
476
477 // Erase the line
478 $this->output->write("\x1B[2K");
479
480 // Erase previous lines
481 if ($this->formatLineCount > 0) {
482 $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
483 }
484 }
485 } elseif ($this->step > 0) {
486 $this->output->writeln('');
487 }
488
489 $this->firstRun = false;
490
491 $this->output->write($message);
492 }
493
494 private function determineBestFormat()
495 {
496 switch ($this->output->getVerbosity()) {
497 // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
498 case OutputInterface::VERBOSITY_VERBOSE:
499 return $this->max ? 'verbose' : 'verbose_nomax';
500 case OutputInterface::VERBOSITY_VERY_VERBOSE:
501 return $this->max ? 'very_verbose' : 'very_verbose_nomax';
502 case OutputInterface::VERBOSITY_DEBUG:
503 return $this->max ? 'debug' : 'debug_nomax';
504 default:
505 return $this->max ? 'normal' : 'normal_nomax';
506 }
507 }
508
509 private static function initPlaceholderFormatters()
510 {
511 return array(
512 'bar' => function (ProgressBar $bar, OutputInterface $output) {
513 $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
514 $display = str_repeat($bar->getBarCharacter(), $completeBars);
515 if ($completeBars < $bar->getBarWidth()) {
516 $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
517 $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
518 }
519
520 return $display;
521 },
522 'elapsed' => function (ProgressBar $bar) {
523 return Helper::formatTime(time() - $bar->getStartTime());
524 },
525 'remaining' => function (ProgressBar $bar) {
526 if (!$bar->getMaxSteps()) {
527 throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
528 }
529
530 if (!$bar->getProgress()) {
531 $remaining = 0;
532 } else {
533 $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress()));
534 }
535
536 return Helper::formatTime($remaining);
537 },
538 'estimated' => function (ProgressBar $bar) {
539 if (!$bar->getMaxSteps()) {
540 throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
541 }
542
543 if (!$bar->getProgress()) {
544 $estimated = 0;
545 } else {
546 $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps());
547 }
548
549 return Helper::formatTime($estimated);
550 },
551 'memory' => function (ProgressBar $bar) {
552 return Helper::formatMemory(memory_get_usage(true));
553 },
554 'current' => function (ProgressBar $bar) {
555 return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
556 },
557 'max' => function (ProgressBar $bar) {
558 return $bar->getMaxSteps();
559 },
560 'percent' => function (ProgressBar $bar) {
561 return floor($bar->getProgressPercent() * 100);
562 },
563 );
564 }
565
566 private static function initFormats()
567 {
568 return array(
569 'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
570 'normal_nomax' => ' %current% [%bar%]',
571
572 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
573 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
574
575 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
576 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
577
578 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
579 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
580 );
581 }
582
583 /**
584 * @return string
585 */
586 private function buildLine()
587 {
588 $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
589 $callback = function ($matches) {
590 if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
591 $text = call_user_func($formatter, $this, $this->output);
592 } elseif (isset($this->messages[$matches[1]])) {
593 $text = $this->messages[$matches[1]];
594 } else {
595 return $matches[0];
596 }
597
598 if (isset($matches[2])) {
599 $text = sprintf('%'.$matches[2], $text);
600 }
601
602 return $text;
603 };
604 $line = preg_replace_callback($regex, $callback, $this->format);
605
606 // gets string length for each sub line with multiline format
607 $linesLength = array_map(function ($subLine) {
608 return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r"));
609 }, explode("\n", $line));
610
611 $linesWidth = max($linesLength);
612
613 $terminalWidth = $this->terminal->getWidth();
614 if ($linesWidth <= $terminalWidth) {
615 return $line;
616 }
617
618 $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);
619
620 return preg_replace_callback($regex, $callback, $this->format);
621 }
622 }