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: /**
38: * The current progress.
39: *
40: * @var int
41: */
42: protected $_progress = 0;
43:
44: /**
45: * The total number of 'items' to progress through.
46: *
47: * @var int
48: */
49: protected $_total = 0;
50:
51: /**
52: * The width of the bar.
53: *
54: * @var int
55: */
56: protected $_width = 0;
57:
58: /**
59: * Output a progress bar.
60: *
61: * Takes a number of options to customize the behavior:
62: *
63: * - `total` The total number of items in the progress bar. Defaults
64: * to 100.
65: * - `width` The width of the progress bar. Defaults to 80.
66: * - `callback` The callback that will be called in a loop to advance the progress bar.
67: *
68: * @param array $args The arguments/options to use when outputing the progress bar.
69: * @return void
70: */
71: public function output($args)
72: {
73: $args += ['callback' => null];
74: if (isset($args[0])) {
75: $args['callback'] = $args[0];
76: }
77: if (!$args['callback'] || !is_callable($args['callback'])) {
78: throw new RuntimeException('Callback option must be a callable.');
79: }
80: $this->init($args);
81:
82: $callback = $args['callback'];
83:
84: $this->_io->out('', 0);
85: while ($this->_progress < $this->_total) {
86: $callback($this);
87: $this->draw();
88: }
89: $this->_io->out('');
90: }
91:
92: /**
93: * Initialize the progress bar for use.
94: *
95: * - `total` The total number of items in the progress bar. Defaults
96: * to 100.
97: * - `width` The width of the progress bar. Defaults to 80.
98: *
99: * @param array $args The initialization data.
100: * @return $this
101: */
102: public function init(array $args = [])
103: {
104: $args += ['total' => 100, 'width' => 80];
105: $this->_progress = 0;
106: $this->_width = $args['width'];
107: $this->_total = $args['total'];
108:
109: return $this;
110: }
111:
112: /**
113: * Increment the progress bar.
114: *
115: * @param int $num The amount of progress to advance by.
116: * @return $this
117: */
118: public function increment($num = 1)
119: {
120: $this->_progress = min(max(0, $this->_progress + $num), $this->_total);
121:
122: return $this;
123: }
124:
125: /**
126: * Render the progress bar based on the current state.
127: *
128: * @return $this
129: */
130: public function draw()
131: {
132: $numberLen = strlen(' 100%');
133: $complete = round($this->_progress / $this->_total, 2);
134: $barLen = ($this->_width - $numberLen) * ($this->_progress / $this->_total);
135: $bar = '';
136: if ($barLen > 1) {
137: $bar = str_repeat('=', $barLen - 1) . '>';
138: }
139:
140: $pad = ceil($this->_width - $numberLen - $barLen);
141: if ($pad > 0) {
142: $bar .= str_repeat(' ', $pad);
143: }
144: $percent = ($complete * 100) . '%';
145: $bar .= str_pad($percent, $numberLen, ' ', STR_PAD_LEFT);
146:
147: $this->_io->overwrite($bar, 0);
148:
149: return $this;
150: }
151: }
152: