PluginProbe
Depicter — Popup & Slider Builder / trunk
Depicter — Popup & Slider Builder vtrunk
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / vendor / symfony / console / Helper / ProgressBar.php

ProgressBar.php in Depicter — Popup & Slider Builder trunk, at vendor/symfony/console/Helper/ProgressBar.php

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