Skip to content

Commit

Permalink
MDL-70583 cli: Allow progress bars to be rendered in cli scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
brendanheywood committed Feb 2, 2021
1 parent 9dabd07 commit ded82b7
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 13 deletions.
28 changes: 15 additions & 13 deletions lib/outputcomponents.php
Original file line number Diff line number Diff line change
Expand Up @@ -4916,6 +4916,14 @@ public function __construct($htmlid = '', $width = 500, $autostart = false) {
}
}

/**
* Getter for ID
* @return string id
*/
public function get_id() : string {
return $this->html_id;
}

/**
* Create a new progress bar, this function will output html.
*
Expand All @@ -4925,9 +4933,6 @@ public function create() {
global $OUTPUT;

$this->time_start = microtime(true);
if (CLI_SCRIPT) {
return; // Temporary solution for cli scripts.
}

flush();
echo $OUTPUT->render($this);
Expand All @@ -4943,15 +4948,13 @@ public function create() {
* @throws coding_exception
*/
private function _update($percent, $msg) {
global $OUTPUT;

if (empty($this->time_start)) {
throw new coding_exception('You must call create() (or use the $autostart ' .
'argument to the constructor) before you try updating the progress bar.');
}

if (CLI_SCRIPT) {
return; // Temporary solution for cli scripts.
}

$estimate = $this->estimate($percent);

if ($estimate === null) {
Expand All @@ -4965,16 +4968,15 @@ private function _update($percent, $msg) {
return;
}

$estimatemsg = null;
if (is_numeric($estimate)) {
$estimatemsg = get_string('secondsleft', 'moodle', round($estimate, 2));
$estimatemsg = '';
if ($estimate != 0 && is_numeric($estimate)) {
$estimatemsg = format_time(round($estimate));
}

$this->percent = round($percent, 2);
$this->percent = $percent;
$this->lastupdate = microtime(true);

echo html_writer::script(js_writer::function_call('updateProgressBar',
array($this->html_id, $this->percent, $msg, $estimatemsg)));
echo $OUTPUT->render_progress_bar_update($this->html_id, sprintf("%.1f", $this->percent), $msg, $estimatemsg);
flush();
}

Expand Down
115 changes: 115 additions & 0 deletions lib/outputrenderers.php
Original file line number Diff line number Diff line change
Expand Up @@ -4776,6 +4776,22 @@ public function render_progress_bar(progress_bar $bar) {
return $this->render_from_template('core/progress_bar', $data);
}

/**
* Renders an update to a progress bar.
*
* Note: This does not cleanly map to a renderable class and should
* never be used directly.
*
* @param string $id
* @param float $percent
* @param string $msg Message
* @param string $estimate time remaining message
* @return string ascii fragment
*/
public function render_progress_bar_update(string $id, float $percent, string $msg, string $estimate) : string {
return html_writer::script(js_writer::function_call('updateProgressBar', [$id, $percent, $msg, $estimate]));
}

/**
* Renders element for a toggle-all checkbox.
*
Expand All @@ -4800,6 +4816,12 @@ public function render_checkbox_toggleall(\core\output\checkbox_toggleall $eleme
*/
class core_renderer_cli extends core_renderer {

/**
* @var array $progressmaximums stores the largest percentage for a progress bar.
* @return string ascii fragment
*/
private $progressmaximums = [];

/**
* Returns the page header.
*
Expand Down Expand Up @@ -4844,6 +4866,99 @@ public function check_result(core\check\result $result) {
return $this->render_check_result($result);
}

/**
* Renders a progress bar.
*
* Do not use $OUTPUT->render($bar), instead use progress_bar::create().
*
* @param progress_bar $bar The bar.
* @return string ascii fragment
*/
public function render_progress_bar(progress_bar $bar) {
global $CFG;

$size = 55; // The width of the progress bar in chars.
$ascii = "\n";

if (stream_isatty(STDOUT)) {
require_once($CFG->libdir.'/clilib.php');

$ascii .= "[" . str_repeat(' ', $size) . "] 0% \n";
return cli_ansi_format($ascii);
}

$this->progressmaximums[$bar->get_id()] = 0;
$ascii .= '[';
return $ascii;
}

/**
* Renders an update to a progress bar.
*
* Note: This does not cleanly map to a renderable class and should
* never be used directly.
*
* @param string $id
* @param float $percent
* @param string $msg Message
* @param string $estimate time remaining message
* @return string ascii fragment
*/
public function render_progress_bar_update(string $id, float $percent, string $msg, string $estimate) : string {
$size = 55; // The width of the progress bar in chars.
$ascii = '';

// If we are rendering to a terminal then we can safely use ansii codes
// to move the cursor and redraw the complete progress bar each time
// it is updated.
if (stream_isatty(STDOUT)) {
$colour = $percent == 100 ? 'green' : 'blue';

$done = $percent * $size * 0.01;
$whole = floor($done);
$bar = "<colour:$colour>";
$bar .= str_repeat('', $whole);

if ($whole < $size) {
// By using unicode chars for partial blocks we can have higher
// precision progress bar.
$fraction = floor(($done - $whole) * 8);
$bar .= core_text::substr(' ▏▎▍▌▋▊▉', $fraction, 1);

// Fill the rest of the empty bar.
$bar .= str_repeat(' ', $size - $whole - 1);
}

$bar .= '<colour:normal>';

if ($estimate) {
$estimate = "- $estimate";
}

$ascii .= '<cursor:up>';
$ascii .= '<cursor:up>';
$ascii .= sprintf("[$bar] %3.1f%% %-22s\n", $percent, $estimate);
$ascii .= sprintf("%-80s\n", $msg);
if ($percent == 100) {
$ascii .= "\n";
}
return cli_ansi_format($ascii);
}

// If we are not rendering to a tty, ie when piped to another command
// or on windows we need to progressively render the progress bar
// which can only ever go forwards.
$done = round($percent * $size * 0.01);
$delta = max(0, $done - $this->progressmaximums[$id]);
$this->progressmaximums[$id] += $delta;

$ascii .= str_repeat('#', $delta);
if ($percent >= 100) {
$ascii .= sprintf("] %3.1f%%\n$msg\n", $percent);
}
return $ascii;
}

/**
* Returns a template fragment representing a Heading.
*
Expand Down

0 comments on commit ded82b7

Please sign in to comment.