1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP Project
12: * @since 3.1.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Shell\Helper;
16:
17: use Cake\Console\Helper;
18: use RuntimeException;
19:
20: /**
21: * Create a progress bar using a supplied callback.
22: *
23: * ## Usage
24: *
25: * The ProgressHelper can be accessed from shells using the helper() method
26: *
27: * ```
28: * $this->helper('Progress')->output(['callback' => function ($progress) {
29: * // Do work
30: * $progress->increment();
31: * });
32: * ```
33: */
34: class ProgressHelper extends Helper
35: {
36: /**
37: * The current progress.
38: *
39: * @var int|float
40: */
41: protected $_progress = 0;
42:
43: /**
44: * The total number of 'items' to progress through.
45: *
46: * @var int
47: */
48: protected $_total = 0;
49:
50: /**
51: * The width of the bar.
52: *
53: * @var int
54: */
55: protected $_width = 0;
56:
57: /**
58: * Output a progress bar.
59: *
60: * Takes a number of options to customize the behavior:
61: *
62: * - `total` The total number of items in the progress bar. Defaults
63: * to 100.
64: * - `width` The width of the progress bar. Defaults to 80.
65: * - `callback` The callback that will be called in a loop to advance the progress bar.
66: *
67: * @param array $args The arguments/options to use when outputing the progress bar.
68: * @return void
69: */
70: public function output($args)
71: {
72: $args += ['callback' => null];
73: if (isset($args[0])) {
74: $args['callback'] = $args[0];
75: }
76: if (!$args['callback'] || !is_callable($args['callback'])) {
77: throw new RuntimeException('Callback option must be a callable.');
78: }
79: $this->init($args);
80:
81: $callback = $args['callback'];
82:
83: $this->_io->out('', 0);
84: while ($this->_progress < $this->_total) {
85: $callback($this);
86: $this->draw();
87: }
88: $this->_io->out('');
89: }
90:
91: /**
92: * Initialize the progress bar for use.
93: *
94: * - `total` The total number of items in the progress bar. Defaults
95: * to 100.
96: * - `width` The width of the progress bar. Defaults to 80.
97: *
98: * @param array $args The initialization data.
99: * @return $this
100: */
101: public function init(array $args = [])
102: {
103: $args += ['total' => 100, 'width' => 80];
104: $this->_progress = 0;
105: $this->_width = $args['width'];
106: $this->_total = $args['total'];
107:
108: return $this;
109: }
110:
111: /**
112: * Increment the progress bar.
113: *
114: * @param int|float $num The amount of progress to advance by.
115: * @return $this
116: */
117: public function increment($num = 1)
118: {
119: $this->_progress = min(max(0, $this->_progress + $num), $this->_total);
120:
121: return $this;
122: }
123:
124: /**
125: * Render the progress bar based on the current state.
126: *
127: * @return $this
128: */
129: public function draw()
130: {
131: $numberLen = strlen(' 100%');
132: $complete = round($this->_progress / $this->_total, 2);
133: $barLen = ($this->_width - $numberLen) * ($this->_progress / $this->_total);
134: $bar = '';
135: if ($barLen > 1) {
136: $bar = str_repeat('=', $barLen - 1) . '>';
137: }
138:
139: $pad = ceil($this->_width - $numberLen - $barLen);
140: if ($pad > 0) {
141: $bar .= str_repeat(' ', $pad);
142: }
143: $percent = ($complete * 100) . '%';
144: $bar .= str_pad($percent, $numberLen, ' ', STR_PAD_LEFT);
145:
146: $this->_io->overwrite($bar, 0);
147:
148: return $this;
149: }
150: }
151: