Skip to content

Commit 352136c

Browse files
committed
Implement a progress bar shell helper.
Implement a simple progress bar helper for use in shells. Having progress bars will make the i18n shell provide more feedback and can be used anywhere there is a long running shell task. The helper supports both a simple API through output() and a more low-level usage by using increment() and draw()
1 parent 589ff7b commit 352136c

File tree

2 files changed

+265
-0
lines changed

2 files changed

+265
-0
lines changed

src/Shell/Helper/ProgressHelper.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
/**
3+
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4+
* Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
11+
* @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
12+
* @since 3.1.0
13+
* @license http://www.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+
class ProgressHelper extends Helper
24+
{
25+
/**
26+
* The current progress.
27+
*
28+
* @var int
29+
*/
30+
protected $_progress = 0;
31+
32+
/**
33+
* The total number of 'items' to progress through.
34+
*
35+
* @var int
36+
*/
37+
protected $_total = 0;
38+
39+
/**
40+
* The width of the bar.
41+
*
42+
* @var int
43+
*/
44+
protected $_width = 0;
45+
46+
/**
47+
* Output a progress bar.
48+
*
49+
* Takes a number of options to customize the behavior:
50+
*
51+
* - `total` The total number of items in the progress bar. Defaults
52+
* to 100.
53+
* - `width` The width of the progress bar. Defaults to 80.
54+
* - `callback` The callback that will be called in a loop to advance the progress bar.
55+
*
56+
* @param array $args The arguments/options to use when outputing the progress bar.
57+
* @return void
58+
*/
59+
public function output($args)
60+
{
61+
$args += ['callback' => null];
62+
if (isset($args[0])) {
63+
$args['callback'] = $args[0];
64+
}
65+
if (!$args['callback'] || !is_callable($args[0])) {
66+
throw new RuntimeException('Callback option must be a callable.');
67+
}
68+
$this->init($args);
69+
70+
$callback = $args['callback'];
71+
while ($this->_progress < $this->_total) {
72+
$callback($this);
73+
$this->draw();
74+
}
75+
}
76+
77+
/**
78+
* Initialize the progress bar for use.
79+
*
80+
* - `total` The total number of items in the progress bar. Defaults
81+
* to 100.
82+
* - `width` The width of the progress bar. Defaults to 80.
83+
*
84+
* @param array $args The initialization data.
85+
* @return void
86+
*/
87+
public function init(array $args = [])
88+
{
89+
$args += ['total' => 100, 'width' => 80];
90+
$this->_progress = 0;
91+
$this->_width = $args['width'];
92+
$this->_total = $args['total'];
93+
}
94+
95+
/**
96+
* Increment the progress bar.
97+
*
98+
* @param int $num The amount of progress to advance by.
99+
* @return void
100+
*/
101+
public function increment($num = 1)
102+
{
103+
$this->_progress = max(0, $this->_progress + $num);
104+
}
105+
106+
/**
107+
* Render the progress bar based on the current state.
108+
*
109+
* @return void
110+
*/
111+
public function draw()
112+
{
113+
$numberLen = strlen(' 100%');
114+
$complete = ($this->_progress / $this->_total);
115+
$barLen = floor(($this->_width - $numberLen) * ($this->_progress / $this->_total));
116+
$bar = '';
117+
if ($barLen > 1) {
118+
$bar = str_repeat('=', $barLen - 1) . '>';
119+
}
120+
121+
$pad = $this->_width - $numberLen - $barLen;
122+
if ($pad > 0) {
123+
$bar .= str_repeat(' ', $pad);
124+
}
125+
$percent = ($complete * 100) . '%';
126+
$bar .= str_pad($percent, $numberLen, ' ', STR_PAD_LEFT);
127+
128+
$this->_io->overwrite($bar, 0);
129+
}
130+
131+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
/**
3+
* CakePHP : Rapid Development Framework (http://cakephp.org)
4+
* Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
11+
* @link http://cakephp.org CakePHP Project
12+
* @since 3.1.0
13+
* @license http://www.opensource.org/licenses/mit-license.php MIT License
14+
*/
15+
namespace Cake\Test\TestCase\Shell\Helper;
16+
17+
use Cake\Console\ConsoleIo;
18+
use Cake\Shell\Helper\ProgressHelper;
19+
use Cake\TestSuite\Stub\ConsoleOutput;
20+
use Cake\TestSuite\TestCase;
21+
22+
/**
23+
* ProgressHelper test.
24+
*/
25+
class ProgressHelperTest extends TestCase
26+
{
27+
28+
/**
29+
* setUp method
30+
*
31+
* @return void
32+
*/
33+
public function setUp()
34+
{
35+
parent::setUp();
36+
37+
$this->stub = new ConsoleOutput();
38+
$this->io = new ConsoleIo($this->stub);
39+
$this->helper = new ProgressHelper($this->io);
40+
}
41+
42+
/**
43+
* Test that a callback is required.
44+
*
45+
* @expectedException \RuntimeException
46+
*/
47+
public function testOutputFailure()
48+
{
49+
$this->helper->output(['not a callback']);
50+
}
51+
52+
/**
53+
* Test a callback that never reaches 100 fails.
54+
*
55+
* @return void
56+
*/
57+
public function testOutputSuccess()
58+
{
59+
$this->helper->output([function ($progress) {
60+
$progress->increment(20);
61+
}]);
62+
$expected = [
63+
'',
64+
'==============> 20%',
65+
'',
66+
'=============================> 40%',
67+
'',
68+
'============================================> 60%',
69+
'',
70+
'===========================================================> 80%',
71+
'',
72+
'==========================================================================> 100%',
73+
];
74+
$this->assertEquals($expected, $this->stub->messages());
75+
}
76+
77+
/**
78+
* Test using the helper manually.
79+
*
80+
* @return void
81+
*/
82+
public function testIncrementAndRender()
83+
{
84+
$this->helper->init();
85+
86+
$this->helper->increment(20);
87+
$this->helper->draw();
88+
89+
$this->helper->increment(40);
90+
$this->helper->draw();
91+
92+
$this->helper->increment(40);
93+
$this->helper->draw();
94+
95+
$expected = [
96+
'',
97+
'==============> 20%',
98+
'',
99+
'============================================> 60%',
100+
'',
101+
'==========================================================================> 100%',
102+
];
103+
$this->assertEquals($expected, $this->stub->messages());
104+
}
105+
106+
/**
107+
* Test negative numbers
108+
*
109+
* @return void
110+
*/
111+
public function testIncrementWithNegatives()
112+
{
113+
$this->helper->init();
114+
115+
$this->helper->increment(40);
116+
$this->helper->draw();
117+
118+
$this->helper->increment(-60);
119+
$this->helper->draw();
120+
121+
$this->helper->increment(80);
122+
$this->helper->draw();
123+
124+
$expected = [
125+
'',
126+
'=============================> 40%',
127+
'',
128+
' 0%',
129+
'',
130+
'===========================================================> 80%',
131+
];
132+
$this->assertEquals($expected, $this->stub->messages());
133+
}
134+
}

0 commit comments

Comments
 (0)