Skip to content

In a hidden chunk here, I’m “exporting” some unexported internal helpers, so that I can use them and talk about them. For similar reasons, I attach glue above, so that certain glue functions work here, without explicitly namespacing them.

Block styles

The block styles exist to produce bulleted output with a specific symbol, using a specific color.

> f <- function() {                                                     
+   ui_todo("ui_todo(): red bullet")                                    
+   ui_done("ui_done(): green check")                                   
+   ui_oops("ui_oops(): red x")                                         
+   ui_info("ui_info(): yellow i")                                      
+   ui_line("ui_line(): (no symbol)")                                   
+ }                                                                     
> f()                                                                   
 ui_todo(): red bullet                                                 
 ui_done(): green check                                                
 ui_oops(): red x                                                      
 ui_info(): yellow i                                                   
ui_line(): (no symbol)                                                  

Another important feature is that all of this output can be turned off package-wide via the usethis.quiet option.

> withr::with_options(                                                  
+   list(usethis.quiet = TRUE),                                         
+   ui_info("You won't see this message.")                              
+ )                                                                     
> withr::with_options(                                                  
+   list(usethis.quiet = FALSE), # this is the default                  
+   ui_info("But you will see this one.")                               
+ )                                                                     
 But you will see this one.                                            

These styles are very close to what can be done with cli::cli_bullets() and the way it responds to the names of its input text.

> cli::cli_bullets(c(                                                   
+         "noindent",                                                   
+   " " = "indent",                                                     
+   "*" = "bullet",                                                     
+   ">" = "arrow",                                                      
+   "v" = "success",                                                    
+   "x" = "danger",                                                     
+   "!" = "warning",                                                    
+   "i" = "info"                                                        
+ ))                                                                    
noindent                                                                
  indent                                                                
 bullet                                                                
→ arrow                                                                 
 success                                                               
 danger                                                                
! warning                                                               
 info                                                                  

A direct translation would look something like this:

Legacy ui_*() cli_bullets() shortcode tweaks needed
ui_todo() * blue (default) -> red
ui_done() v perfect match
ui_oops() x perfect match
ui_info() i blue (default) -> yellow
ui_line() (unnamed) sort of a perfect match? although sometimes ui_line() is used just to get a blank line

The overall conversion plan is to switch to a new function, ui_bullets(), which is a wrapper around cli::cli_bullets(), that adds a few features:

  • Early exit, without emitting messages, if the usethis.quiet option is TRUE.
  • A usethis theme, that changes the color of certain bullet styles and adds a new style for todo’s.
> ui_bullets(c(                                                         
+   "v" = "A great success!",                                           
+   "_" = "Something you need to do.",                                  
+   "x" = "Bad news.",                                                  
+   "i" = "The more you know.",                                         
+   " " = "I'm just here for the indentation.",                         
+   "No indentation at all. Not used much in usethis."                  
+ ))                                                                    
 A great success!                                                      
 Something you need to do.                                             
 Bad news.                                                             
 The more you know.                                                    
  I'm just here for the indentation.                                    
No indentation at all. Not used much in usethis.                        

Summary of what I’ve done for todo’s:

  • Introduce a new bullet shortcode for a todo. Proposal: _ (instead of *), which seems to be the best single ascii character that evokes a place to check something off.
  • Use cli::symbol$checkbox_off as the symbol (instead of a generic bullet). I guess it will continue to be red.

In terms of the block styles, that just leaves ui_code_block(), which is pretty different. ui_code_block() is used to put some code on the screen and optionally place it on the clipboard. I have created a new function, ui_code_snippet() that is built around cli::code_block(). Main observations:

  • cli::code_block(language = "R") applies syntax highlighting and hyperlinks (e.g. to function help topics) to R code, which is cool. Therefore the language argument is also exposed in ui_code_snippet(), defaulting to "R". Use "" for anything that’s not R code:
      > ui_code_snippet("x <- 1 + 2")                                         
        x <- 1 + 2                                                            
      > ui_code_snippet("#include <blah.h>", language = "")                   
        #include <blah.h>                                                     
      
  • ui_code_snippet() takes a scalar glue-type template string or a vector of lines. Note that the two calls below produce the same output.
      > ui_code_snippet("                                                     
      +   options(                                                            
      +     warnPartialMatchArgs = TRUE,                                      
      +     warnPartialMatchDollar = TRUE,                                    
      +     warnPartialMatchAttr = TRUE                                       
      +   )")                                                                 
        options(                                                              
          warnPartialMatchArgs = TRUE,                                        
          warnPartialMatchDollar = TRUE,                                      
          warnPartialMatchAttr = TRUE                                         
        )                                                                     
      > # produces same result as                                             
      > ui_code_snippet(c(                                                    
      +   "options(",                                                         
      +   "  warnPartialMatchArgs = TRUE,",                                   
      +   "  warnPartialMatchDollar = TRUE,",                                 
      +   "  warnPartialMatchAttr = TRUE",                                    
      +   ")"))                                                               
        options(                                                              
          warnPartialMatchArgs = TRUE,                                        
          warnPartialMatchDollar = TRUE,                                      
          warnPartialMatchAttr = TRUE                                         
        )                                                                     
      
  • ui_code_snippet() does glue interpolation, by default, before calling cli::cli_code(), which means you have to think about your use of { and }. If you want literal { or }:
    • Use interpolate = FALSE, if you don’t need interpolation.
    • Do the usual glue thing and double them, i.e. {{ or }}.
    • If this becomes a real pain, open an issue/PR about adding .open and .close as arguments to ui_code_snippet().

Utility functions

The block style functions all route through some unexported utility functions.

is_quiet() just consults the usethis.quiet option and implements the default of FALSE.

is_quiet <- function() {
  isTRUE(getOption("usethis.quiet", default = FALSE))
}

ui_bullet() is an intermediate helper used by ui_todo(), ui_done(), ui_oops() and ui_info(). It does some hygiene related to indentation (using the indent() utility function), then calls ui_inform(). ui_line() and ui_code() both call ui_inform() directly.

ui_inform() is just a wrapper around rlang::inform() that is guarded by a call to is_quiet()

ui_inform <- function(...) {
  if (!is_quiet()) {
    inform(paste0(...))
  }
  invisible()
}

Other than is_quiet(), which will continue to play the same role, I anticipate that we no longer need these utilities (indent(), ui_bullet(), ui_inform()). Updates from the future:

  • indent() turns out to still be useful in ui_code_snippet(), so I’ve inlined it there, to avoid any reliance on definitions in ui-legacy.R.
  • ui_bullet() has been renamed to ui_legacy_bullet() for auto-completion happiness with the new ui_bullets().

Let’s cover ui_silence() while we’re here, which is exported. It’s just a withr::with_*() function for executing code with usethis.quiet = TRUE.

ui_silence <- function(code) {
  withr::with_options(list(usethis.quiet = TRUE), code)
}

Inline styles

Legacy functions

usethis has its own inline styles (mostly) for use inside functions like ui_todo():

> new_val <- "oxnard"                                                   
> x <- glue("{ui_field('name')} set to {ui_value(new_val)}")            
> dput(x)                                                               
structure("\033[32mname\033[39m set to \033[34m'oxnard'\033[39m", class 
= c("glue",                                                             
"character"))                                                           
> ui_done(x)                                                            
 name set to 'oxnard'                                                  

The inline styles enact some combination of:

  • Color, e.g. crayon::green(x)
  • Collapsing a collection of things to one thing, e.g. c("a", "b", "c") to “a, b, c”
  • Quoting, e.g. encodeString(x, quote = "'")

ui_path() is special because it potentially modifies the input before styling it. ui_path() first makes the path relative to a specific base (by default, the active project root) and, if the path is a directory, it also ensures there is a trailing /.

ui_unset() is a special purpose helper used when we need to report that something is unknown, not configured, nonexistent, etc.

> x <- glue("Your super secret password is {ui_unset()}.")              
> dput(x)                                                               
structure("Your super secret password is \033[90m<unset>\033[39m.", clas
s = c("glue",                                                           
"character"))                                                           
> ui_info(x)                                                            
 Your super secret password is <unset>.                                

cli replacements

In general, we can move towards cli’s native inline-markup: https://cli.r-lib.org/reference/inline-markup.html

Here’s the general conversion plan:

  • ui_field() becomes {.field blah}. In usethis_theme(), I tweak the .field style to apply single quotes if color is not available, which is what ui_field() has always done.

  • ui_value() becomes {.val value}.

  • ui_path() is connected to {.path path/to/thing}, but, as explained above, ui_path() also does more. Therefore, I abstracted the “path math” into an internal helper, ui_path_impl(), which is aliased to pth() for compactness. Here’s a typical conversion:

    # using legacy functions
    ui_done("Setting {ui_field('LazyData')} to \\
             {ui_value('true')} in {ui_path('DESCRIPTION')}")
    # using new cli-based ui
    ui_bullets(c(
      "v" = "Setting {.field LazyData} to {.val true} in {.path {pth('DESCRIPTION')}}."
    ))

    It would be nice to create a custom inline class, e.g. {.ui_path {some_path}}, which I have done in, e.g., googledrive. But it’s not easy to do this while still inheriting cli’s file: hyperlink behaviour, which is very desirable. So that leads to the somewhat clunky, verbose pattern above, but it gives a nice result.

  • ui_code() gets replaced by various inline styles, depending on what the actual goal is, such as:

    • {.code some_text}
    • {.arg some_argument}
    • {.cls some_class}
    • {.fun devtools::build_readme}
    • {.help some_function}
    • {.run usethis::usethis_function()}
    • {.topic some_topic}
    • {.var some_variable}
  • ui_unset() is replaced by ui_special(), which you’ll see more of below. Currently the intended grey color doesn’t show up when I render this document using solarized-dark and so far I can’t get to the bottom of that :( Why isn’t it the same grey as “[Copied to clipboard]” in ui_code_snippet(), which does work?

Conditions

I’m moving from ui_stop():

ui_stop <- function(x, .envir = parent.frame()) {
  x <- glue_collapse(x, "\n")
  x <- glue(x, .envir = .envir)

  cnd <- structure(
    class = c("usethis_error", "error", "condition"),
    list(message = x)
  )

  stop(cnd)
}

to ui_abort():

ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) {
  cli::cli_div(theme = usethis_theme())
  # bullet naming gymnastics, see below
  cli::cli_abort(
    message,
    class = c(class, "usethis_error"),
    .envir = .envir,
    ...
  )
}

The main point of ui_abort() is to use to cli_abort() (and to continue applying the "usethis_error" class).

I also use ui_abort() to apply different default bullet naming/styling. Starting with "x" and then defaulting to "i" seems to fit best with usethis’s existing errors.

> block_start = "# <<<"                                                 
> block_end = "# >>>"                                                   
> ui_abort(c(                                                           
+   "Invalid block specification.",                                     
+   "Must start with {.code {block_start}} and end with {.code {block_en
d}}."                                                                   
+ ))                                                                    
Error:                                                                  
 Invalid block specification.                                          
 Must start with `# <<<` and end with `# >>>`.                         
Run `rlang::last_trace()` to see where the error occurred.              

Any bullets that are explicitly given are honored.

atever."))                                                              
Error:                                                                  
 It's weird to give a green check in an error, but whatever.           
Run `rlang::last_trace()` to see where the error occurred.              
> ui_abort(c(                                                           
+   "!" = "Things are not ideal.",                                      
+   ">" = "Look at me!"                                                 
+ ))                                                                    
Error:                                                                  
! Things are not ideal.                                                 
→ Look at me!                                                           
Run `rlang::last_trace()` to see where the error occurred.              

rlang::abort() and cli::cli_abort() start with "!" by default, then use "*" and " ", respectively.

The legacy functions also include ui_warn(). It has very little usage and, instead of converting it, I’ve eliminated its use altogether in favor of a "!" bullet:

> ui_bullets(c("!" = "The guy she told you not to worry about."))       
! The guy she told you not to worry about.                              

Sidebar: Now that I’m looking at a lot of the new errors with ui_abort() I realize that usethis also needs to be passing the call argument along. I’m going to leave that for a future, separate effort.

Sitrep and format helpers

This is a small clump of functions that support sitrep-type output.

  • hd_line() unexported and, apparently, unused! now removed
  • kv_line() unexported, so has new cli implementation
  • ui_unset() exported and succeeded by ui_special()

kv_line() stands for “key-value line”. Here’s what it used to be:

> kv_line_legacy <- function(key, value, .envir = parent.frame()) {     
+   value <- if (is.null(value)) ui_unset() else ui_value(value)        
+   key <- glue(key, .envir = .envir)                                   
+   ui_inform(glue("{cli::symbol$bullet} {key}: {value}"))              
+ }                                                                     
>                                                                       
> url <- "https://github.com/r-lib/usethis.git"                         
> remote <- "origin"                                                    
> kv_line_legacy("URL for the {ui_value(remote)} remote", url)          
• URL for the 'origin' remote: 'https://github.com/r-lib/usethis.git'   
>                                                                       
> host <- "github.com"                                                  
> kv_line_legacy("Personal access token for {ui_value(host)}", NULL)    
• Personal access token for 'github.com': <unset>                       

Key features:

  • Interpolates data and applies inline style to the result. Works differently for key and value, because you’re much more likely to use interpolation and styling in key than value.
  • Has special handling when value is NULL.
  • Applies "*" bullet name/style to over all result.

I won’t show the updated source for kv_line() but here is some usage to show what it’s capable of:

> noun <- "thingy"                                                      
> value <- "VALUE"                                                      
> kv_line("Let's reveal {.field {noun}}", "whatever")                   
• Let's reveal thingy: "whatever"                                       
>                                                                       
> kv_line("URL for the {.val {remote}} remote", I("{.url {url}}"))      
• URL for the "origin" remote: <https://github.com/r-lib/usethis.git>   
>                                                                       
> kv_line("Personal access token for {.val {host}}", NULL)              
• Personal access token for "github.com": <unset>                       
>                                                                       
> kv_line("Personal access token for {.val {host}}", ui_special("discove
red"))                                                                  
• Personal access token for "github.com": <discovered>                  

ui_special() is the successor to ui_unset().

Questions

There’s currently no drop-in substitute for ui_yeah() and ui_nope() in cli. Related issues: https://github.com/r-lib/cli/issues/228, https://github.com/r-lib/cli/issues/488. Therefore, in the meantime, ui_yeah() and ui_nope() are not-quite-superseded for external users.

However, internally, I’ve switched to the unexported functions ui_yep() and ui_nah() that are lightly modified versions of ui_yeah() and ui_nope() that use cli for styling.

if (ui_nope("
      Current branch ({ui_value(actual)}) is not repo's default \\
      branch ({ui_value(default_branch)}).{details}")) {
        ui_abort("Cancelling. Not on desired branch.")
    }

Miscellaneous notes

  • I’ve been adding a period to the end of messages, as a general rule.

  • In terms of whitespace and indentation, I’ve settled on some conventions. The overall goal is to get the right user-facing output (obviously), while making it as easy as possible to predict what that’s going to look like when you’re writing the code.

    ui_bullets(c(
      "i" = "Downloading into {.path {pth(destdir)}}.",
      "_" = "Prefer a different location? Cancel, try again, and specify
             {.arg destdir}."
    ))
    ...
    ui_bullets(c("x" = "Things are very wrong."))

    Key points:

    • Put ui_bullets(c( on its own line, then all of the bullet items, followed by )) on its own line. Sometimes I make an exception for a bullet list with exactly one, short bullet item.

    • Use hard line breaks inside bullet text to comply with surrounding line length. In subsequent lines, use indentation to get you just past the opening ". This extraneous white space is later rationalized by cli, which handles wrapping.

    • Surround bullet names like x and i with quotes, even though you don’t have to, because it’s required for other names, such as ! or _ and it’s better to be consistent.

    • Here’s another style I like that applies to ui_abort(), where there’s just one, unnamed bullet, but the call doesn’t fit on one line.

      pr <- list(pr_number = 13)
      ui_abort("
        The repo or branch where PR #{pr$pr_number} originates seems to have been
        deleted.")