Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add progress helper
  • Loading branch information
Gareth Ellis committed Dec 1, 2015
1 parent e580ad8 commit 788c57d
Show file tree
Hide file tree
Showing 4 changed files with 446 additions and 0 deletions.
113 changes: 113 additions & 0 deletions lib/Cake/Console/Helper/ProgressHelper.php
@@ -0,0 +1,113 @@
<?php
App::uses("ShellHelper", "Console/Helper");

/**
* Create a progress bar using a supplied callback.
*/
class ProgressHelper extends ShellHelper
{
/**
* The current progress.
*
* @var int
*/
protected $_progress = 0;

/**
* The total number of 'items' to progress through.
*
* @var int
*/
protected $_total = 0;

/**
* The width of the bar.
*
* @var int
*/
protected $_width = 0;

/**
* Output a progress bar.
*
* Takes a number of options to customize the behavior:
*
* - `total` The total number of items in the progress bar. Defaults
* to 100.
* - `width` The width of the progress bar. Defaults to 80.
* - `callback` The callback that will be called in a loop to advance the progress bar.
*
* @param array $args The arguments/options to use when outputing the progress bar.
* @return void
*/
public function output($args)
{
$args += ['callback' => null];
if (isset($args[0])) {
$args['callback'] = $args[0];
}
if (!$args['callback'] || !is_callable($args['callback'])) {
throw new RuntimeException('Callback option must be a callable.');
}
$this->init($args);
$callback = $args['callback'];
while ($this->_progress < $this->_total) {
$callback($this);
$this->draw();
}
$this->_consoleOutput->write('');
}

/**
* Initialize the progress bar for use.
*
* - `total` The total number of items in the progress bar. Defaults
* to 100.
* - `width` The width of the progress bar. Defaults to 80.
*
* @param array $args The initialization data.
* @return void
*/
public function init(array $args = [])
{
$args += ['total' => 100, 'width' => 80];
$this->_progress = 0;
$this->_width = $args['width'];
$this->_total = $args['total'];
}


/**
* Increment the progress bar.
*
* @param int $num The amount of progress to advance by.
* @return void
*/
public function increment($num = 1)
{
$this->_progress = min(max(0, $this->_progress + $num), $this->_total);
}

/**
* Render the progress bar based on the current state.
*
* @return void
*/
public function draw()
{
$numberLen = strlen(' 100%');
$complete = round($this->_progress / $this->_total, 2);
$barLen = ($this->_width - $numberLen) * ($this->_progress / $this->_total);
$bar = '';
if ($barLen > 1) {
$bar = str_repeat('=', $barLen - 1) . '>';
}
$pad = ceil($this->_width - $numberLen - $barLen);
if ($pad > 0) {
$bar .= str_repeat(' ', $pad);
}
$percent = ($complete * 100) . '%';
$bar .= str_pad($percent, $numberLen, ' ', STR_PAD_LEFT);
$this->_consoleOutput->overwrite($bar, 0);
}
}
37 changes: 37 additions & 0 deletions lib/Cake/Console/Helper/ShellHelper.php
@@ -0,0 +1,37 @@
<?php

abstract class ShellHelper
{
/**
* Default config for this helper.
*
* @var array
*/
protected $_defaultConfig = [];

/**
* ConsoleOutput instance.
*
* @var ConsoleOutput
*/
protected $_consoleOutput;

/**
* Constructor.
*
* @param ConsoleOutput $consoleOutput The ConsoleOutput instance to use.
* @param array $config The settings for this helper.
*/
public function __construct(ConsoleOutput $consoleOutput)
{
$this->_consoleOutput = $consoleOutput;
}

/**
* This method should output content using `$this->_consoleOutput`.
*
* @param array $args The arguments for the helper.
* @return void
*/
abstract public function output($args);
}
217 changes: 217 additions & 0 deletions lib/Cake/Test/Case/Console/Helper/ProgressHelperTest.php
@@ -0,0 +1,217 @@
<?php
App::uses("ProgressHelper", "Console/Helper");
App::uses("ConsoleOutputStub", "TestSuite/Stub");

/**
* ProgressHelper test.
* @property ConsoleOutputStub $consoleOutput
* @property ProgressHelper $helper
*/
class ProgressHelperTest extends CakeTestCase
{

/**
* setUp method
*
* @return void
*/
public function setUp()
{
parent::setUp();

$this->consoleOutput = new ConsoleOutputStub();
$this->helper = new ProgressHelper($this->consoleOutput);
}

/**
* Test that a callback is required.*
* @expectedException \RuntimeException
*/
public function testOutputFailure()
{
$this->helper->output(['not a callback']);
}

/**
* Test that the callback is invoked until 100 is reached.
*
* @return void
*/
public function testOutputSuccess()
{
$this->helper->output([function ($progress) {
$progress->increment(20);
}]);
$expected = [
'',
'==============> 20%',
'',
'=============================> 40%',
'',
'============================================> 60%',
'',
'===========================================================> 80%',
'',
'==========================================================================> 100%',
'',
];
$this->assertEquals($expected, $this->consoleOutput->messages());
}

/**
* Test output with options
*
* @return void
*/
public function testOutputSuccessOptions()
{
$this->helper->output([
'total' => 10,
'width' => 20,
'callback' => function ($progress) {
$progress->increment(2);
}
]);
$expected = [
'',
'==> 20%',
'',
'=====> 40%',
'',
'========> 60%',
'',
'===========> 80%',
'',
'==============> 100%',
'',
];
$this->assertEquals($expected, $this->consoleOutput->messages());
}

/**
* Test using the helper manually.
*
* @return void
*/
public function testIncrementAndRender()
{
$this->helper->init();
$this->helper->increment(20);
$this->helper->draw();
$this->helper->increment(40);
$this->helper->draw();
$this->helper->increment(40);
$this->helper->draw();
$expected = [
'',
'==============> 20%',
'',
'============================================> 60%',
'',
'==========================================================================> 100%',
];
$this->assertEquals($expected, $this->consoleOutput->messages());
}

/**
* Test negative numbers
*
* @return void
*/
public function testIncrementWithNegatives()
{
$this->helper->init();
$this->helper->increment(40);
$this->helper->draw();
$this->helper->increment(-60);
$this->helper->draw();
$this->helper->increment(80);
$this->helper->draw();
$expected = [
'',
'=============================> 40%',
'',
' 0%',
'',
'===========================================================> 80%',
];
$this->assertEquals($expected, $this->consoleOutput->messages());
}

/**
* Test increment and draw with options
*
* @return void
*/
public function testIncrementWithOptions()
{
$this->helper->init([
'total' => 10,
'width' => 20,
]);
$this->helper->increment(4);
$this->helper->draw();
$this->helper->increment(4);
$this->helper->draw();
$this->helper->increment(4);
$this->helper->draw();

$expected = [
'',
'=====> 40%',
'',
'===========> 80%',
'',
'==============> 100%',
];
$this->assertEquals($expected, $this->consoleOutput->messages());
}

/**
* Test increment and draw with value that makes the pad
* be a float
*
* @return void
*/
public function testIncrementFloatPad()
{
$this->helper->init([
'total' => 50
]);
$this->helper->increment(7);
$this->helper->draw();
$this->helper->increment(7);
$this->helper->draw();
$this->helper->increment(7);
$this->helper->draw();
$this->helper->increment(7);
$this->helper->draw();
$this->helper->increment(7);
$this->helper->draw();
$this->helper->increment(3);
$this->helper->draw();
$this->helper->increment(4);
$this->helper->draw();
$this->helper->increment(8);
$this->helper->draw();
$expected = [
'',
'=========> 14%',
'',
'====================> 28%',
'',
'==============================> 42%',
'',
'=========================================> 56%',
'',
'===================================================> 70%',
'',
'========================================================> 76%',
'',
'==============================================================> 84%',
'',
'==========================================================================> 100%',
];
$this->assertEquals($expected, $this->consoleOutput->messages());
}
}

0 comments on commit 788c57d

Please sign in to comment.