diff --git a/lib/outputcomponents.php b/lib/outputcomponents.php index ff5428b9978..8ed6e1ec986 100644 --- a/lib/outputcomponents.php +++ b/lib/outputcomponents.php @@ -4916,6 +4916,14 @@ class progress_bar implements renderable, templatable { } } + /** + * 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. * @@ -4925,9 +4933,6 @@ class progress_bar implements renderable, templatable { global $OUTPUT; $this->time_start = microtime(true); - if (CLI_SCRIPT) { - return; // Temporary solution for cli scripts. - } flush(); echo $OUTPUT->render($this); @@ -4943,15 +4948,13 @@ class progress_bar implements renderable, templatable { * @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) { @@ -4965,16 +4968,15 @@ class progress_bar implements renderable, templatable { 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(); } diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index abfb60339dc..222eaf33b69 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -4776,6 +4776,22 @@ EOD; 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. * @@ -4800,6 +4816,12 @@ EOD; */ 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. * @@ -4844,6 +4866,99 @@ class core_renderer_cli extends core_renderer { 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 = ""; + $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 .= ''; + + if ($estimate) { + $estimate = "- $estimate"; + } + + $ascii .= ''; + $ascii .= ''; + $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. *