cli 3.1.0

This is a companion discussion topic for the original entry at https://www.tidyverse.org/blog/2021/11/cli-3-1-0


We’re very chuffed to announce the release of cli 3.1.0. cli helps you create a consistent and convenient command line interface.

You can install it from CRAN with:

install.packages("cli")

This release of cli comes with an important feature for end users: the ability to select or define their preferred palettes. The selected palette is respected by every package that relies on either cli or the crayon package. We also show some other improvements in this post, these are mainly aimed at developers.

You can see a full list of changes in the release notes.

Color palettes

Built-in palettes

library(cli)

This release of cli adds support for ANSI color customization. Now the 16 foreground colors, created via the col_*() functions, and the 16 background colors, created via the bg_*() functions, can be customized with the cli.palette option.

You can set cli.palette to one of the built-in cli palettes, or you can create your own palette. See all built-in palettes at the cli homepage. You can also look at the ansi_palettes object, which is a data frame of RGB colors, with one row for each palette. To look at a single palette, run ansi_palette_show(). It shows the current palette by default:

ansi_palette_show()

To use a built-in palette, set cli.palette to the palette name. To make this permanent, put this setting into your .Rprofile:

options(cli.palette = "dichro")
ansi_palette_show()

To set the default palette again, set cli.palette to NULL:

options(cli.palette = NULL)

Custom palettes

To create a custom palette, set the cli.palette option to a named list where the names are the same as the column names in ansi_palettes. Colors can be specified with RGB color strings of the #rrggbb form or R color names (see the output of grDevices::colors()). For example:

options(cli.palette = list(
  black    = "#010101", red        = "#de382b",
  green    = "#39b54a", yellow     = "#ffc706",
  blue     = "#006fb8", magenta    = "#762671",
  cyan     = "#2cb5e9", white      = "#cccccc",
  br_black = "#808080", br_red     = "#ff0000", 
  br_green = "#00ff00", br_yellow  = "#ffff00", 
  br_blue  = "#0000ff", br_magenta = "#ff00ff", 
  br_cyan  = "#00ffff", br_white   = "#ffffff"
))

Color interpolation

For color palettes your terminal or IDE needs to support at least 256 ANSI colors. On terminals with true color ANSI support cli will use the exact colors, as specified in the cli.palette option. On consoles with 256 ANSI colors, e.g. the RStudio console, cli will interpolate the specified colors to the closest ANSI-256 color. This means that the actual output will probably look slightly different from the specified RGB colors on these displays.

What about the crayon package?

crayon is an older package than cli, with a smaller scope: adding ANSI colors to your display. More than 300 packages use crayon, so to make sure that cli palettes are respected in these packages as well, we added palette support to the latest release of crayon. Specifying the cli.palette option changes the colors in cli and in crayon as well, the same way.

This said, cli does have some additional features compared to crayon, e.g. the support of bright colors. Our focus will be on improving the cli package in the future, and crayon will only receive important bug fixes. If you already use both cli and crayon, then it might make sense to completely switch to cli.

Palettes in terminals

Many modern terminal emulators, e.g. iTerm on macOS, already allow the customization of ANSI colors, and some also support themes with custom ANSI palettes. If you already use this method to customize ANSI colors, then you don’t need to set the cli.palette option. If you use both terminals and RStudio then you can set it only in RStudio:

if (Sys.getenv("RSTUDIO")=="1") options(cli.palette = "dichro")

Other improvements

Bright ANSI colors

cli now has a new set of functions to create the bright version of the 8 base ANSI colors. The col_br_*() functions set the foreground and the bg_br_*() functions set the background colors of strings:

cli::col_blue("This is blue.")
cli::col_br_blue("This is bright blue.")

True color ANSI

cli now supports true color ANSI consoles better. Now custom styles made with make_ansi_style() will not interpolate the specified color on these displays:

orange <- make_ansi_style("#eb6123")
orange("This will be halloween orange.")

Unicode graphemes

cli’s ansi_*() functions and the new utf8_*() functions now handle Unicode graphemes properly. For example ansi_nchar() and utf8_nchar() count graphemes by default, and ansi_substr() and utf8_substr() will break the input strings at grapheme boundaries.

Consider this Unicode grapheme: 👷🏽‍♀️ (female construction worker, medium skin tone). It consists of five Unicode code points:

  • \U{1f477}, construction worker,
  • \U{1f3fd}, emoji modifier Fitzpatrick type-4, for the skin tone,
  • \u200d, zero width joiner,
  • \u2640, female sign,
  • \ufe0f, variation selector-16, to specify that the preceding character should be displayed as an emoji.

cli functions handle this grapheme properly:

wrk <- "👷🏽‍♀️"
as.hexmode(utf8ToInt(wrk))
#> [1] "1f477" "1f3fd" "0200d" "02640" "0fe0f"
# graphemes by default
utf8_nchar(wrk)
#> [1] 1
# code points
utf8_nchar(wrk, type = "codepoints")
#> [1] 5
# correct display width
utf8_nchar(wrk, type = "width")
#> [1] 2

Syntax highlight R code

The new code_highlight() function parses and syntax highlights R code using ANSI colors and styles. You can use deparse() to highlight the code of an existing function:

writeLines(code_highlight(deparse(cli::hash_emoji)))

Human readable hash functions

Sometimes it is convenient to create a short hash of a string, that is easy to compare to other hashes. The new hash_emoji() function creates a very short emoji hash of a string. The new hash_animal() function uses a short expression with one or more adjectives and an animal name:

txt <- "Hash this string please!"
hash_emoji(txt)$hash
#> [1] "👨‍👩‍👦‍👦🍘😽"
hash_emoji(txt)$text
#> [1] "family: man, woman, boy, boy, rice cracker, kissing cat"
hash_animal(txt)$hash
#> [1] "deadsmooth anaemic bighorn"

If you are using the new version of the sessioninfo package, then you already see an emoji hash on top of the sessioninfo::session_info() output. This makes trivial to decide if session_info() outputs are the same or not, without comparing them line by line.

Acknowledgements

A big thanks to all 76 contributors who filed issues and contributed code to this and past cli releases:

@aedobbyn, @AkhilGNair, @AlbertRapp, @assignUser, @batpigandme, @brodieG, @bwiernik, @cderv, @cfhammill, @cjyetman, @ColinFay, @combiz, @cpsievert, @danielvartan, @datafj, @DavisVaughan, @dchiu911, @dfalbel, @dgkf, @elinw, @flying-sheep, @fmichonneau, @fmmattioni, @gaborcsardi, @gavinsimpson, @GjjvdBurg, @gregleleu, @GregorDeCillia, @gwd999, @hadley, @IndrajeetPatil, @jennybc, @jimhester, @jonkeane, @jonocarroll, @juniperlsimonis, @krlmlr, @lazappi, @leeper, @lionel-, @llrs, @lorenzwalthert, @MarkEdmondson1234, @markwsac, @mattfidler, @matthiaskaeding, @mgirlich, @MilesMcBain, @MislavSag, @mjsteinbaugh, @MLopez-Ibanez, @mrcaseb, @ms609, @nfancy, @nick-komick, @overmar, @pat-s, @paul-sheridan, @QuLogic, @ramiromagno, @rrodrigueznt, @rundel, @salim-b, @sgibb, @ShixiangWang, @sthibaul, @tentacles-from-outer-space, @thothal, @topepo, @torfason, @trestletech, @tzakharko, @wngrtn, @x1o, @yutannihilation, and @zachary-foster.