Advanced cli progress bars
Gábor Csárdi
2024-06-23
Source:vignettes/progress-advanced.Rmd
progress-advanced.Rmd
Overhead
cli progress bars do have an overhead that may or may not be significant for your use case. In the R API, if you have a tight loop, then you should not update the progress bar too often.
Minimizing overhead
To minimize progress bar overhead, cli_progress_update()
uses an internal timer and only update the progress bar on the screen if
the timer is due.
In C code, you can refer to the timer directly to avoid an update.
The CLI_SHOULD_TICK
macro evaluates to one if the timer is
due and an update is needed, otherwise to zero.
CLI_SHOULD_TICK
only works if you already created a cli
progress bar from C, or you called
cli_progress_init_timer()
. The latter initializes the cli
timer without creating a progress bar. (If the timer is not initialized,
then CLI_SHOULD_TICK
evaluates to zero.)
Non-interactive R sessions
cli output is different if the terminal or platform is not
dynamic, i.e. if it does not support the \r
character to move the cursor to the beginning of the line without
starting a new line. This often happens when R is running
non-interactively and the standard error is redirected to a file. cli
uses the cli::is_dynamic_tty()
function to determine if the
output supports \r
.
On a non-dynamic terminal, cli simply prints progress updates as new lines to the screen. Frequently updating the progress in this fashion would produce a lot of output, so on non-dynamic terminals cli falls back to a slower timer update interval.
By default the cli timer signals every 3000 milliseconds in an R session without a dynamic terminal, instead of 200 milliseconds.
Progress bars in scripts
You can use progress bars in R scripts, just like you use them in R packages.
In an R script you might create a progress bar from the global
environment, instead from within a function call. The global environment
also has a current progress bar, so when you create a progress
bar from the global environment, the previous one from the same
environment is terminated. However, there is not function to return
from, so the last progress bar of the script will be only terminated
when the script terminates, or when you explicitly terminate it using
cli_progress_done()
.
Customization
cli progress bars can be customized by the developer and the end user, by setting options, providing function arguments and regular cli themes.
Some aspects can only be customized by the developer, and some others can only be customized by the end user. Others can be customized by both, with the end user’s setting taking precedence.
Developer customization
About progress bar types
Each progress bar type has a default display (format string), which can be configured by the end user. The current progress bar types are, with their default display, with known and unknown number of total progress units.
iterator
Typically for loops and mapping functions. It shows a bar by default, if the total number of iterations is known.
Custom format strings (by the developer)
The developer can specify a custom format string for a progress bar.
For custom
progress bars, this is compulsory. Format
strings may use glue templating, cli pluralization and cli theming. They
can also use a number of built-in cli progress variables, see ‘Progress
variables’ below.
f <- function() {
cli_progress_bar(
total = 20000,
format = "Step {step} | {pb_bar} {pb_percent}"
)
step <- 1
for (i in 1:10000) {
Sys.sleep(2/10000)
cli_progress_update(set = i)
}
step <- 2
for (i in 10001:20000) {
Sys.sleep(2/10000)
cli_progress_update(set = i)
}
}
f()
For custom
progress bars cli always uses the specified
format string. For other types, the end user might customize the format
string, see below.
End user customization
Quick loops
The cli.progress_show_after
(default is two seconds)
option is the number seconds to wait before showing a progress bar.
Custom bars
The end user can customize how a progress bar will look, by setting one or more of the following options:
cli.progress_bar_style
cli.progress_bar_style_unicode
cli.progress_bar_style_ascii
On UTF-8 displays cli.progress_bar_style_unicode
is
used, if set. Otherwise cli.progress_bar_style
is used. On
non UTF-8 displays cli.progress_bar_style_ascii
is used, if
set. Otherwise cli.progress_bar_style
is used.
These options can be set to a built-in progress bar style name:
names(cli_progress_styles())
#> [1] "classic" "squares" "dot" "fillsquares" "bar"
options(cli.progress_bar_style = "fillsquares")
f <- function() lapply(cli_progress_along(letters), function(l) Sys.sleep(0.2))
x <- f()
Alternatively, they can be set to a list with entries
complete
, incomplete
and current
,
to specify the characters (or strings) for the parts of the progress
bar:
options(cli.progress_bar_style = list(
complete = cli::col_yellow("\u2605"),
incomplete = cli::col_grey("\u00b7")
))
f <- function() lapply(cli_progress_along(letters), function(l) Sys.sleep(0.2))
x <- f()
Custom spinners
Options to customize cli spinners:
cli.spinner
cli.spinner_unicode
cli.spinner_ascii
On UTF-8 displays cli.spinner_unicode
is used, if set,
otherwise cli.spinner
. On ASCII displays
cli.spinner_ascii
is used, if set, otherwise
cli.spinner
.
Use list_spinners()
to list all spinners and
demo_spinners()
to take a peek at them.
options(cli.spinner = "moon")
f <- function() {
cli_progress_bar(format = strrep("{cli::pb_spin} ", 20), clear = TRUE)
for (i in 1:100) {
Sys.sleep(5/100)
cli_progress_update()
}
}
f()
Custom format strings
The end user may use a number of global options to customize how the built-in progress bar types are displayed on the screen:
-
cli.progress_format_iterator
is used foriterator
progress bars. -
cli.progress_format_iterator_nototal
is used foriterator
progress bars with an unknown number of total units. -
cli.progress_format_tasks
is used fortasks
progress bars. -
cli.progress_format_tasks_nototal
is used fortasks
progress bars with an unknown number of total units. -
cli.progress_format_download
is used fordownload
progress bars. -
cli.progress_format_download_nototal
is used fordownload
progress bars with an unknown number of total units.
Progress variables
Custom format strings may use progress variables in glue interpolated
expressions, to refer to the state of the progress bar. See
?"progress-variables"
in the manual for the list of
supported variables.
If you refer to a progress variable from a package, you need need to
import it or qualify the reference with cli::
. When you set
a custom format string as an end user option, we suggest that you always
use the qualified form, in case the cli package is not attached. For
example, to set a minimal display for downloads you might write
options(cli.progress_format_download =
paste0(
"{cli::col_cyan('\u2B07')} {cli::pb_spin} ",
"{cli::pb_name}[{cli::pb_current_bytes}/{cli::pb_total_bytes}]"
)
)
to get
You can use your own expressions and functions on progress bar
tokens. E.g. to show the current number of steps with letters instead of
numbers, use letters[pb_current]
:
f <- function() {
cli_progress_bar(
total = 26,
format = "{pb_spin} This is step {.emph {letters[pb_current]}}. {pb_spin}"
)
for (i in 1:26) {
Sys.sleep(3/26)
cli_progress_update()
}
}
f()
Clearing or keeping terminated progress bars
By default terminated progress bars are removed from the screen. The
end user can set the cli.progress_clear
option to
FALSE
to override the default. In addition, the developer
can also change the default, using the clear
parameter of
cli_progress_bar()
. If both the option and the parameter
are set, the parameter is used.
The C API
To use the C cli progress API in your package, you need to add cli to
LinkingTo
and Imports
:
LinkingTo: cli
Imports: cli
In the C files you want to use the API from include
cli/progress.h
:
Now you are ready to call cli functions. The C API is similar to the traditional R API:
-
cli_progress_bar()
creates a progress bar. -
cli_progress_update()
updates a progress bar. -
cli_progress_done()
terminates it.
A complete example:
SEXP progress_test1() {
int i;
SEXP bar = PROTECT(cli_progress_bar(1000, NULL));
for (i = 0; i < 1000; i++) {
cli_progress_sleep(0, 4 * 1000 * 1000);
if (CLI_SHOULD_TICK) cli_progress_set(bar, i);
}
cli_progress_done(bar);
UNPROTECT(1);
return Rf_ScalarInteger(i);
}
C API reference
CLI_SHOULD_TICK
A macro that evaluates to (int) 1 if a cli progress bar update is
due, and to (int) 0 otherwise. If the timer hasn’t been initialized in
this compilation unit yet, then it is always 0. To initialize the timer,
call cli_progress_init_timer()
or create a progress bar
with cli_progress_bar()
.
cli_progress_add()
Add a number of progress units to the progress bar. It will also trigger an update if an update is due.
-
bar
: progress bar object. -
inc
: progress increment.
cli_progress_bar()
Create a new progress bar object. The returned progress bar object
must be PROTECT()
-ed.
-
total
: Total number of progress units. UseNA_REAL
if it is not known. -
config
: R named list object of additional parameters. May beNULL
(the CNULL~) or
R_NilValue(the R
NULL`) for the defaults.
config
may contain the following entries:
-
name
: progress bar name. -
status
: (initial) progress bar status. -
type
: progress bar type. -
total
: total number of progress units. -
show_after
: show the progress bar after the specified number of seconds. This overrides the globalshow_after
option. -
format
: format string, must be specified for custom progress bars. -
format_done
: format string for successful termination. -
format_failed
: format string for unsuccessful termination. -
clear
: whether to remove the progress bar from the screen after termination. -
auto_terminate
: whether to terminate the progress bar when the number of current units equals the number of total progress units.
cli_progress_set()
Set the progress bar to the specified number of progress units.
-
bar
: progress bar object. -
set
: number of current progress progress units.
cli_progress_set_clear()
Set whether to remove the progress bar from the screen. You can call
this any time before cli_progress_done()
is called.
-
bar
: progress bar object. -
clear
: whether to remove the progress bar from the screen, zero or one.
cli_progress_set_format()
Set a custom format string for the progress bar. This call does not
try to update the progress bar. If you want to request an update, call
cli_progress_add()
, cli_progress_set()
or
cli_progress_update()
.
-
bar
: progress bar object. -
format
: format string. -
...
: values to substitute intoformat
.
format
and ...
are passed to
vsnprintf()
to create a format string.
Format strings may contain glue substitutions, referring to progress variables, pluralization, and cli styling.
cli_progress_set_name()
Set the name of the progress bar.
-
bar
; progress bar object. -
name
: progress bar name.
cli_progress_set_status()
Set the status of the progress bar.
-
bar
: progress bar object. -
status
: progress bar status.
cli_progress_set_type()
Set the progress bar type. Call this function right after creating
the progress bar with cli_progress_bar()
. Otherwise the
behavior is undefined.
-
bar
: progress bar object. -
type
: progress bar type. Possible progress bar types:iterator
,tasks
,download
andcustom
.
cli_progress_update()
Update the progress bar. Unlike the simpler
cli_progress_add()
and cli_progress_set()
function, it can force an update if force
is set to 1.
-
bar
: progress bar object. -
set
: the number of current progress units. It is ignored if negative. -
inc
: increment to add to the current number of progress units. It is ignored ifset
is not negative. -
force
: whether to force an update, even if no update is due.
To force an update without changing the current number of progress
units, supply set = -1
, inc = 0
and
force = 1
.