knitr::opts_chunk$set( collapse = TRUE, comment = "", out.width = "100%", #asciicast_knitr_output = "html", asciicast_theme = "solarized-light", # tango, solarized-dark, solarized-light asciicast_cols = 72 ) asciicast::init_knitr_engine( #echo = TRUE, #echo_input = FALSE, startup = quote({ library(usethis) library(glue) options(cli.num_colors = 256) ui_code_snippet <- usethis:::ui_code_snippet usethis_theme <- usethis:::usethis_theme ui_inform <- usethis:::ui_inform kv_line <- usethis:::kv_line ui_special <- usethis:::ui_special ui_abort <- usethis:::ui_abort ui_bullets <- usethis:::ui_bullets set.seed(1) }) )
library(usethis) library(glue)
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.
#| include: false ui_code_snippet <- usethis:::ui_code_snippet usethis_theme <- usethis:::usethis_theme ui_inform <- usethis:::ui_inform kv_line <- usethis:::kv_line ui_special <- usethis:::ui_special ui_abort <- usethis:::ui_abort ui_bullets <- usethis:::ui_bullets
The block styles exist to produce bulleted output with a specific symbol, using a specific color.
#| collapse: false 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()
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.") )
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" ))
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:
usethis.quiet
option is TRUE
.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." ))
Summary of what I've done for todo's:
_
(instead of *
), which seems to be the best single ascii character that evokes a place to check something off.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:
{asciicast}
ui_code_snippet("x <- 1 + 2")
ui_code_snippet("#include <blah.h>", language = "")
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.
{asciicast}
ui_code_snippet("
options(
warnPartialMatchArgs = TRUE,
warnPartialMatchDollar = TRUE,
warnPartialMatchAttr = TRUE
)")
# produces same result as
ui_code_snippet(c(
"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 }
:interpolate = FALSE
, if you don't need interpolation.{{
or }}
..open
and .close
as arguments to ui_code_snippet()
.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
.
#| eval: 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()
#| eval: false 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
.
#| eval: false ui_silence <- function(code) { withr::with_options(list(usethis.quiet = TRUE), code) }
usethis has its own inline styles (mostly) for use inside functions like ui_todo()
:
ui_field()
ui_value()
ui_path()
ui_code()
ui_unset()
# why is this block truncated from the top in the rendered document? new_val <- "oxnard" x <- glue("{ui_field('name')} set to {ui_value(new_val)}") dput(x) ui_done(x)
The inline styles enact some combination of:
crayon::green(x)
c("a", "b", "c")
to "a, b, c"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.
# why is this block truncated from the top in the rendered document? x <- glue("Your super secret password is {ui_unset()}.") dput(x) ui_info(x)
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:
r
#| eval: false
# 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?I'm moving from ui_stop()
:
#| eval: false 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()
:
#| eval: false 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.
#| error: true # why is this block truncated from the top in the rendered document? block_start = "# <<<" block_end = "# >>>" ui_abort(c( "Invalid block specification.", "Must start with {.code {block_start}} and end with {.code {block_end}}." ))
Any bullets that are explicitly given are honored.
#| error: true ui_abort(c("v" = "It's weird to give a green check in an error, but whatever.")) ui_abort(c( "!" = "Things are not ideal.", ">" = "Look at me!" ))
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."))
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.
This is a small clump of functions that support sitrep-type output.
hd_line()
unexported and, apparently, unused! now removedkv_line()
unexported, so has new cli implementationui_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) host <- "github.com" kv_line_legacy("Personal access token for {ui_value(host)}", NULL)
Key features:
key
and value
, because you're much more likely to use interpolation and styling in key
than value
.value
is NULL
."*"
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:
# why is this block truncated from the top in the rendered document? noun <- "thingy" value <- "VALUE" kv_line("Let's reveal {.field {noun}}", "whatever") kv_line("URL for the {.val {remote}} remote", I("{.url {url}}")) kv_line("Personal access token for {.val {host}}", NULL) kv_line("Personal access token for {.val {host}}", ui_special("discovered"))
ui_special()
is the successor to ui_unset()
.
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.
#| eval: false 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.") }
r
#| eval: false
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: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."
. This extraneous white space is later rationalized by cli, which handles wrapping.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.ui_abort()
, where there's just one, unnamed bullet, but the call doesn't fit on one line.
r
#| eval: false
pr <- list(pr_number = 13)
ui_abort("
The repo or branch where PR #{pr$pr_number} originates seems to have been
deleted.")
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.