It'd be nice to have an API function to display the progress of a loop or iterator, for instance, if you are looping over an array of items and performing some operation. I've attached a sample drush script so you can see what I mean.

If you see value in this, I can work up a patch. I really wish there was a way to get this working when in backend context, but I can't seem to get the \r's to make it through. To get around that, I have the progress indicator simply log a percentage every 10 seconds when in backend context. I'm not familiar enough with where that might be getting stripped out in the backend functions, so if someone knows where that would be, let me know.

https://gist.github.com/pounard/3c1679e32eca87ecf7cf

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

q0rban’s picture

Here's a screen shot of what it looks like in action.

q0rban’s picture

Simplified the progress bar function.


/**
 * Prints out a progress bar directly to STDOUT. Do not call this function
 * directly, rather call drush_print_progress().
 */
function _drush_print_progress($ratio) {
  $percentage = floor($ratio * 100) . '%';
  $columns = drush_get_context('DRUSH_COLUMNS', 80);
  // Subtract 8 characters for the percentage, brackets, spaces and arrow.
  $progress_columns = $columns - 8;
  // If ratio is 1 (complete), the > becomes a = to make a full bar.
  $arrow = ($ratio < 1) ? '>' : '=';
  // Print a new line if ratio is 1 (complete). Otherwise, use a CR.
  $line_ending = ($ratio < 1) ? "\r" : "\n";

  // Determine the current length of the progress string.
  $current_length = floor($ratio * $progress_columns);
  $progress_string = str_pad('', $current_length, '=');

  $output  = '[';
  $output .= $progress_string . $arrow;
  $output .= str_pad('', $progress_columns - $current_length);
  $output .= ']';
  $output .= str_pad('', 5 - strlen($percentage)) . $percentage;
  $output .= $line_ending;

  print $output;
}
moshe weitzman’s picture

Looks handy to me. Would it be possible to allow the caller to return a line of status text. The canonical example for me is migrate-import which wants to report status like Processed 3 (3 created, 0 updated, 0 failed, 0 ignored) in 0.1 sec (2467/min) . I'm not sure that migrate-import would use this feature (it is nice to be able to scroll back and see if your rate is increasing/decreasing) but it is a nice feature i think.

q0rban’s picture

Interesting project here:

http://pear.php.net/manual/en/package.console.console-progressbar.php#ex...

This is much more feature rich, but also a little more complex to set up and run.

moshe weitzman’s picture

Reusing PEAR package makes sense to me.

greg.1.anderson’s picture

Version: » 8.x-6.x-dev
Status: Active » Closed (won't fix)
Issue tags: +Needs migration

This issue was marked closed (won't fix) because Drush has moved to Github.

If this feature is still desired, you may copy it to our Github project. For best results, create a Pull Request that has been updated for the master branch. Post a link here to the PR, and please also change the status of this issue to closed (duplicate).

Please ask support questions on Drupal Answers.

greg.1.anderson’s picture

Issue summary: View changes

Fix CR.

pounard’s picture

Issue summary: View changes

@q0rban There is a fundamental problem with your code, you don't let the user explicitly tell that the progress ended: problem behind this is that when you have a very high count items to process, the ratio ends up to 1 quite easily even though it's unfinished and it is error prone. What happens then is that until the progress is unfinished and the ratio keeps rounded to one, you add a new line per print and end up with multiple progress bars being displayed.

Did this code and using it successfully:

<?php
/**
 * Code took from
 *   https://www.drupal.org/node/1744410
 * Then fixed.
 */

/**
 * Create and return a new progress bar.
 *
 * Usage as follow:
 * @code
 *   $total = 1000;
 *   $bar = drush_create_progress();
 *   for ($i = 0; $i < $total; ++$i) {
 *     do_something_with($i);
 *     $bar->update($i / $total);
 *     // Or alternatively you could use:
 *     $bar->setProgress($total, $i);
 *   }
 *   $bar->end();
 * @endcode
 *
 * @return \DrushProgressBar
 *   Progress bar instance.
 */
function drush_create_progress() {
  return new DrushProgressBar();
}

/**
 * Single progress bar instance.
 */
class DrushProgressBar
{
    /**
     * @var boolean
     */
    private $doDisplay = true;

    public function __construct($onlyVerbose = false)
    {
        $verbose = drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_DEBUG');

        // If the function is being called in quiet mode, or we're in
        // verbose/debug mode, simply return without printing anything.
        if (drush_get_context('DRUSH_QUIET') || (!$verbose && $onlyVerbose) || drush_get_context('DRUSH_BACKEND')) {
            $this->doDisplay = false;
        }
    }

    /**
     * Update the progress bar
     *
     * @param float $ratio
     */
    public function update($ratio)
    {
        if ($this->doDisplay) {
            $this->display($ratio);
        } else {
            $this->log($ratio);
        }
    }

    /**
     * Update with value
     *
     * @param int $total
     * @param int $limit
     */
    public function setProgress($total, $limit)
    {
        $this->update($limit / $total);
    }

    /**
     * Terminate the progess bar
     */
    public function end()
    {
        $this->update(1);

        if ($this->doDisplay) {
            print "\n";
        }
    }

    /**
     * Convert given ratio as percentage rounded value
     *
     * @param float $ratio
     */
    protected function getValue($ratio)
    {
        return min([
            max([
                0,
                round($ratio * 100),
            ]),
            100,
        ]);
    }

    /**
     * Display current ration in progress bar
     *
     * @param float $ratio
     */
    protected function display($ratio)
    {
        $percentage = $this->getValue($ratio) . '%';
        $columns = drush_get_context('DRUSH_COLUMNS', 80);
        // Subtract 8 characters for the percentage, brackets, spaces and arrow.
        $progress_columns = $columns - 8;
        // If ratio is 1 (complete), the > becomes a = to make a full bar.
        $arrow = ($ratio < 1) ? '>' : '=';
        // Print a new line if ratio is 1 (complete). Otherwise, use a CR.
        $line_ending = "\r";

        // Determine the current length of the progress string.
        $current_length = floor($ratio * $progress_columns);
        $progress_string = str_pad('', $current_length, '=');

        $output  = '[';
        $output .= $progress_string . $arrow;
        $output .= str_pad('', $progress_columns - $current_length);
        $output .= ']';
        $output .= str_pad('', 5 - strlen($percentage)) . $percentage;
        $output .= $line_ending;

        print $output;
    }

    /**
     * Log current ratio
     *
     * @param float $ratio
     */
    protected function log($ratio)
    {
        drush_log(dt('@percent completed.', ['@percent' => $this->getValue($ratio) . '%']), 'status');
    }
}

Hope it might help someone.

q0rban’s picture

Thanks for sharing, @pounard! I think we should use the PEAR package. Also, we should move this to GitHub if there is still interest. :)

greg.1.anderson’s picture

Yes, this should move to GitHub if there is any desire to have it reviewed and included. Is that project available via Composer? We don't use Pear for dependencies any more.

pounard’s picture

Issue summary: View changes

Just an update, works fine here, take into account elasped, eta, provide a few new options, adapts the bar depending on the info displayed, and provide an OOP interface, and finally provide a strict ->stop() method to tell the progress bar it ended instead of trying to guess it with ratio (ratio makes it bugguy a soon as your float value is approximated to 1 by the VM).

https://gist.github.com/pounard/3c1679e32eca87ecf7cf