Browse Source

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()
Mark Story 11 years ago
parent
commit
352136ca34
2 changed files with 265 additions and 0 deletions
  1. 131 0
      src/Shell/Helper/ProgressHelper.php
  2. 134 0
      tests/TestCase/Shell/Helper/ProgressHelperTest.php

+ 131 - 0
src/Shell/Helper/ProgressHelper.php

@@ -0,0 +1,131 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
+ * @since         3.1.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Shell\Helper;
+
+use Cake\Console\Helper;
+use RuntimeException;
+
+/**
+ * Create a progress bar using a supplied callback.
+ */
+class ProgressHelper extends Helper
+{
+    /**
+     * 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[0])) {
+            throw new RuntimeException('Callback option must be a callable.');
+        }
+        $this->init($args);
+
+        $callback = $args['callback'];
+        while ($this->_progress < $this->_total) {
+            $callback($this);
+            $this->draw();
+        }
+    }
+
+    /**
+     * 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 = max(0, $this->_progress + $num);
+    }
+
+    /**
+     * Render the progress bar based on the current state.
+     *
+     * @return void
+     */
+    public function draw()
+    {
+        $numberLen = strlen(' 100%');
+        $complete = ($this->_progress / $this->_total);
+        $barLen = floor(($this->_width - $numberLen) * ($this->_progress / $this->_total));
+        $bar = '';
+        if ($barLen > 1) {
+            $bar = str_repeat('=', $barLen - 1) . '>';
+        }
+
+        $pad = $this->_width - $numberLen - $barLen;
+        if ($pad > 0) {
+            $bar .= str_repeat(' ', $pad);
+        }
+        $percent = ($complete * 100) . '%';
+        $bar .= str_pad($percent, $numberLen, ' ', STR_PAD_LEFT);
+
+        $this->_io->overwrite($bar, 0);
+    }
+
+}

+ 134 - 0
tests/TestCase/Shell/Helper/ProgressHelperTest.php

@@ -0,0 +1,134 @@
+<?php
+/**
+ * CakePHP :  Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP Project
+ * @since         3.1.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Shell\Helper;
+
+use Cake\Console\ConsoleIo;
+use Cake\Shell\Helper\ProgressHelper;
+use Cake\TestSuite\Stub\ConsoleOutput;
+use Cake\TestSuite\TestCase;
+
+/**
+ * ProgressHelper test.
+ */
+class ProgressHelperTest extends TestCase
+{
+
+    /**
+     * setUp method
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        parent::setUp();
+
+        $this->stub = new ConsoleOutput();
+        $this->io = new ConsoleIo($this->stub);
+        $this->helper = new ProgressHelper($this->io);
+    }
+
+    /**
+     * Test that a callback is required.
+     *
+     * @expectedException \RuntimeException
+     */
+    public function testOutputFailure()
+    {
+        $this->helper->output(['not a callback']);
+    }
+
+    /**
+     * Test a callback that never reaches 100 fails.
+     *
+     * @return void
+     */
+    public function testOutputSuccess()
+    {
+        $this->helper->output([function ($progress) {
+            $progress->increment(20);
+        }]);
+        $expected = [
+            '',
+            '==============>                                                              20%',
+            '',
+            '=============================>                                               40%',
+            '',
+            '============================================>                                60%',
+            '',
+            '===========================================================>                 80%',
+            '',
+            '==========================================================================> 100%',
+        ];
+        $this->assertEquals($expected, $this->stub->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->stub->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->stub->messages());
+    }
+}