Nothing
# ---
# repo: r-lib/rlang
# file: standalone-cli.R
# last-updated: 2023-10-06
# license: https://unlicense.org
# ---
#
# Provides a minimal shim API to format message elements consistently
# with cli in packages that can't depend on it. If available, cli is
# used to format the elements. Otherwise a fallback format is used.
#
# ## Changelog
#
# 2023-10-06:
#
# * Speedup in `.rlang_cli_compat()`.
#
# 2022-09-23:
#
# * `format_` functions now use `cli::format_inline()` instead of
# `cli::format_message()`, resulting in simpler ANSI codes.
#
# * Added `format_run()` and `format_href()`.
#
#
# 2022-08-16:
#
# * Added `has_ansi()`. This checks that cli is installed and that
# `cli::num_ansi_colors()` is greater than 1.
#
# * `col_` and `style_` functions now consistently return bare strings.
#
#
# 2022-05-23:
#
# * Added compat for `style_hyperlink()`.
#
#
# 2022-02-23:
#
# * Bullet formatting now ignores unknown bullet names, consistently
# with cli. This increases resiliency against hard-to-detect errors
# and improves forward compatibility.
#
#
# 2022-02-22:
#
# * `format_error()` and variants now call cli even when ANSI colours
# are disabled.
#
# * The fallback formatting for `.emph` and `.strong` no longer
# surrounds in `_` or `*` characters. This is consistent with cli
# formatting.
#
#
# 2021-07-06:
#
# * Added missing `col_`, `bg_`, and `style_` functions.
#
#
# 2021-05-18:
#
# * Added `symbol_` and corresponding `ansi_` functions to create
# unicode symbols if possible. The `ansi_` variants apply default
# colours to these symbols if possible.
#
# * Added `style_` functions to apply ANSI styling (colours, slant, weight).
#
# * Added `format_error()` and variants to format messages with
# cli (including bullets).
#
# * Added `cli_escape()` to escape glue and cli syntax.
#
# * `mark_` functions now produce `{.cli input}` tags to be formatted
# with one of the message formatter (such as `format_error()`). They
# all have a `format_` variant that formats eagerly. Eager
# formatting is easier to work with but might produce incorrect
# styling in very specific cases involving sophisticated cli themes.
#
#
# 2021-05-11:
#
# * Initial version.
#
# nocov start
#' Create unicode symbols
#'
#' The `symbol_` functions generate Unicode symbols if cli is
#' installed and Unicode is enabled. The corresponding `ansi_`
#' functions apply default ANSI colours to these symbols if possible.
#'
#' @noRd
symbol_info <- function() if (.rlang_cli_has_cli()) cli::symbol$info else "i"
symbol_cross <- function() if (.rlang_cli_has_cli()) cli::symbol$cross else "x"
symbol_tick <- function() if (.rlang_cli_has_cli()) cli::symbol$tick else "v"
symbol_bullet <- function() if (.rlang_cli_has_cli()) cli::symbol$bullet else "*"
symbol_arrow <- function() if (.rlang_cli_has_cli()) cli::symbol$arrow_right else ">"
symbol_alert <- function() "!"
ansi_info <- function() col_blue(symbol_info())
ansi_cross <- function() col_red(symbol_cross())
ansi_tick <- function() col_green(symbol_tick())
ansi_bullet <- function() col_cyan(symbol_bullet())
ansi_arrow <- function() symbol_arrow()
ansi_alert <- function() col_yellow(symbol_alert())
#' Apply ANSI styling
#'
#' The `col_`, `bg_`, and `style_` functions style their inputs using
#' the relevant ANSI escapes if cli is installed and ANSI colours are
#' enabled.
#'
#' @param x A string.
#'
#' @noRd
col_black <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_black(x)) else x
col_blue <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_blue(x)) else x
col_cyan <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_cyan(x)) else x
col_green <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_green(x)) else x
col_magenta <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_magenta(x)) else x
col_red <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_red(x)) else x
col_white <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_white(x)) else x
col_yellow <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_yellow(x)) else x
col_grey <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_grey(x)) else x
col_silver <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_silver(x)) else x
col_none <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::col_none(x)) else x
bg_black <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::bg_black(x)) else x
bg_blue <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::bg_blue(x)) else x
bg_cyan <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::bg_cyan(x)) else x
bg_green <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::bg_green(x)) else x
bg_magenta <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::bg_magenta(x)) else x
bg_red <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::bg_red(x)) else x
bg_white <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::bg_white(x)) else x
bg_yellow <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::bg_yellow(x)) else x
bg_none <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::bg_none(x)) else x
style_dim <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_dim(x)) else x
style_blurred <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_blurred(x)) else x
style_bold <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_bold(x)) else x
<- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::(x)) else x
style_inverse <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_inverse(x)) else x
style_italic <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_italic(x)) else x
style_strikethrough <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_strikethrough(x)) else x
style_underline <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_underline(x)) else x
style_no_dim <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_no_dim(x)) else x
style_no_blurred <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_no_blurred(x)) else x
style_no_bold <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_no_bold(x)) else x
<- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::(x)) else x
style_no_inverse <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_no_inverse(x)) else x
style_no_italic <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_no_italic(x)) else x
style_no_strikethrough <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_no_strikethrough(x)) else x
style_no_underline <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_no_underline(x)) else x
style_reset <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_reset(x)) else x
style_no_colour <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_no_color(x)) else x
style_no_bg_colour <- function(x) if (.rlang_cli_has_cli()) .rlang_cli_unstructure(cli::style_no_bg_color(x)) else x
CLI_SUPPORT_HYPERLINK <- "2.2.0"
CLI_SUPPORT_HYPERLINK_PARAMS <- "3.1.1"
style_hyperlink <- function(text, url, params = NULL) {
if (is.null(params)) {
if (.rlang_cli_has_cli(CLI_SUPPORT_HYPERLINK)) {
cli::style_hyperlink(text, url)
} else {
text
}
} else {
if (.rlang_cli_has_cli(CLI_SUPPORT_HYPERLINK_PARAMS)) {
cli::style_hyperlink(text, url, params = params)
} else {
text
}
}
}
#' Apply inline styling
#'
#' @description
#' This set of `mark_` and `format_` functions create consistent
#' inline styling, using cli if available or an ASCII fallback style
#' otherwise.
#'
#' * The `mark_` functions wrap the input with mark up tags when cli
#' is available. For instance, `"foo"` is transformed to `{.span
#' {\"foo\"}}`. These marked up strings must eventually be formatted
#' using a formatter such as `format_error()` to be styled
#' appropriately.
#'
#' * The `format_` functions are easier to work with because they
#' format the style eagerly. However they produce slightly incorrect
#' style in corner cases because the formatting doesn't take into
#' account the message type. In principle, cli themes can create
#' different stylings depending on the message type.
#'
#' @param x A string.
#'
#' @noRd
mark_emph <- function(x) .rlang_cli_style_inline(x, "emph", "_%s_")
mark_strong <- function(x) .rlang_cli_style_inline(x, "strong", "*%s*")
mark_code <- function(x) .rlang_cli_style_inline(x, "code", "`%s`")
mark_q <- function(x) .rlang_cli_style_inline(x, "q", NULL)
mark_pkg <- function(x) .rlang_cli_style_inline(x, "pkg", NULL)
mark_fn <- function(x) .rlang_cli_style_inline(x, "fn", "`%s()`")
mark_arg <- function(x) .rlang_cli_style_inline(x, "arg", "`%s`")
mark_kbd <- function(x) .rlang_cli_style_inline(x, "kbd", "[%s]")
mark_key <- function(x) .rlang_cli_style_inline(x, "key", "[%s]")
mark_file <- function(x) .rlang_cli_style_inline(x, "file", NULL)
mark_path <- function(x) .rlang_cli_style_inline(x, "path", NULL)
mark_email <- function(x) .rlang_cli_style_inline(x, "email", NULL)
mark_url <- function(x) .rlang_cli_style_inline(x, "url", "<%s>")
mark_var <- function(x) .rlang_cli_style_inline(x, "var", "`%s`")
mark_envvar <- function(x) .rlang_cli_style_inline(x, "envvar", "`%s`")
mark_field <- function(x) .rlang_cli_style_inline(x, "field", NULL)
mark_cls <- function(x) {
fallback <- function(x) sprintf("<%s>", paste0(x, collapse = "/"))
.rlang_cli_style_inline(x, "cls", fallback)
}
format_emph <- function(x) .rlang_cli_format_inline(x, "emph", "%s")
format_strong <- function(x) .rlang_cli_format_inline(x, "strong", "%s")
format_code <- function(x) .rlang_cli_format_inline(x, "code", "`%s`")
format_q <- function(x) .rlang_cli_format_inline(x, "q", NULL)
format_pkg <- function(x) .rlang_cli_format_inline(x, "pkg", NULL)
format_fn <- function(x) .rlang_cli_format_inline(x, "fn", "`%s()`")
format_arg <- function(x) .rlang_cli_format_inline(x, "arg", "`%s`")
format_kbd <- function(x) .rlang_cli_format_inline(x, "kbd", "[%s]")
format_key <- function(x) .rlang_cli_format_inline(x, "key", "[%s]")
format_file <- function(x) .rlang_cli_format_inline(x, "file", NULL)
format_path <- function(x) .rlang_cli_format_inline(x, "path", NULL)
format_email <- function(x) .rlang_cli_format_inline(x, "email", NULL)
format_url <- function(x) .rlang_cli_format_inline(x, "url", "<%s>")
format_var <- function(x) .rlang_cli_format_inline(x, "var", "`%s`")
format_envvar <- function(x) .rlang_cli_format_inline(x, "envvar", "`%s`")
format_field <- function(x) .rlang_cli_format_inline(x, "field", NULL)
format_href <- function(x, target = NULL) .rlang_cli_format_inline_link(x, target, "href", "<%s>")
format_run <- function(x, target = NULL) .rlang_cli_format_inline_link(x, target, "run", "`%s`")
format_error_arg_highlight <- function(x, quote = TRUE) {
if (is_true(peek_option("rlang:::trace_test_highlight"))) {
return(paste0("<<ARG ", x, ">>"))
}
out <- if (quote) format_arg(x) else x
style_bold(cli::col_br_magenta(out))
}
format_error_call_highlight <- function(x, quote = TRUE) {
if (is_true(peek_option("rlang:::trace_test_highlight"))) {
return(paste0("<<CALL ", x, ">>"))
}
out <- if (quote) format_code(x) else x
style_bold(cli::col_br_blue(out))
}
format_cls <- function(x) {
fallback <- function(x) sprintf("<%s>", paste0(x, collapse = "/"))
.rlang_cli_format_inline(x, "cls", fallback)
}
.rlang_cli_style_inline <- function(x, span, fallback = "`%s`") {
if (.rlang_cli_has_cli()) {
paste0("{.", span, " {\"", encodeString(x), "\"}}")
} else if (is.null(fallback)) {
x
} else if (is.function(fallback)) {
fallback(x)
} else {
sprintf(fallback, x)
}
}
.rlang_cli_format_inline <- function(x, span, fallback = "`%s`") {
if (.rlang_cli_has_cli()) {
cli::format_inline(paste0("{.", span, " {x}}"))
} else {
.rlang_cli_style_inline(x, span, fallback = fallback)
}
}
.rlang_cli_format_inline_link <- function(x, target, span, fallback = "`%s`") {
if (.rlang_cli_has_cli()) {
if (is_null(target)) {
cli::format_inline(paste0("{.", span, " {x}}"))
} else {
cli::format_inline(paste0("{.", span, " [{x}]({target})}"))
}
} else {
.rlang_cli_style_inline(x, span, fallback = fallback)
}
}
#' Format messages
#'
#' @description
#'
#' These format functions use cli if available to format condition
#' messages. This includes structural formatting:
#'
#' - Styling as a function of the message type (error, warning,
#' message).
#' - Bullets formatting (info, alert, ...).
#' - Indented width wrapping.
#'
#' This also applies inline formatting in combination with the
#' `style_` prefixed functions.
#'
#' The input should not contain any `"{foo}"` glue syntax. If you are
#' assembling a message from multiple pieces, use `cli_escape()` on
#' user or external inputs that might contain curly braces.
#'
#' @param x A character vector of lines. Names define bullet types.
#'
#' @noRd
format_error <- function(x) {
.rlang_cli_format(x, cli::format_error)
}
#' @rdname format_error
#' @noRd
format_warning <- function(x) {
.rlang_cli_format(x, cli::format_warning)
}
#' @rdname format_error
#' @noRd
format_message <- function(x) {
.rlang_cli_format(x, cli::format_message)
}
.rlang_cli_format <- function(x, cli_format) {
if (.rlang_cli_has_cli()) {
out <- cli_format(x, .envir = emptyenv())
.rlang_cli_str_restore(out, unname(x))
} else {
.rlang_cli_format_fallback(x)
}
}
.rlang_cli_format_fallback <- function(x) {
if (!length(x)) {
return(unname(x))
}
nms <- names(x)
if (is_null(nms)) {
nms <- rep_len("", length(x))
}
abort <- .rlang_cli_compat("abort")
bullets <- local({
unicode_opt <- getOption("cli.condition_unicode_bullets")
if (identical(unicode_opt, FALSE)) {
old <- options(cli.unicode = FALSE)
on.exit(options(old))
}
# For consistency with `cli::format_error()` and for resiliency
# against hard-to-detect errors (see #1364), unknown names are
# silently ignored. This also makes it easier to add new bullet
# names in the future with forward-compatibility.
ifelse(nms == "i", ansi_info(),
ifelse(nms == "x", ansi_cross(),
ifelse(nms == "v", ansi_tick(),
ifelse(nms == "*", ansi_bullet(),
ifelse(nms == "!", ansi_alert(),
ifelse(nms == ">", ansi_arrow(),
ifelse(nms == "", "",
ifelse(nms == " ", " ",
""))))))))
})
bullets <-
ifelse(bullets == "", "", paste0(bullets, " "))
out <- paste0(bullets, x, collapse = "\n")
.rlang_cli_str_restore(out, unname(x))
}
.rlang_cli_str_restore <- function(x, to) {
out <- to
out <- out[1]
out[[1]] <- x
# Restore attributes only if unclassed. It is assumed the `[` and
# `[[` methods deal with attributes in case of classed objects.
# Preserving attributes matters for the assertthat package for
# instance.
if (!is.object(to)) {
attrib <- attributes(to)
attrib$names <- NULL
attrib$dim <- NULL
attrib$dimnames <- NULL
attrib <- c(attributes(out), attrib)
attributes(out) <- attrib
}
out
}
has_ansi <- function() {
.rlang_cli_has_cli() && cli::num_ansi_colors() > 1
}
.rlang_cli_has_cli <- local({
cache <- new.env()
function(version = "3.0.0") {
out <- cache[[version]]
if (is.null(out)) {
out <- cache[[version]] <<-
requireNamespace("cli", quietly = TRUE) &&
utils::packageVersion("cli") >= version
}
out
}
})
#' Escape cli and glue syntax
#'
#' This doubles all `{` and `}` characters to prevent them from being
#' interpreted as syntax for glue interpolation or cli styling.
#'
#' @param x A character vector.
#'
#' @noRd
cli_escape <- function(x) {
if (.rlang_cli_has_cli()) {
gsub("\\}", "}}", gsub("\\{", "{{", x))
} else {
x
}
}
.rlang_cli_compat <- function(fn, try_rlang = TRUE) {
# Compats that behave the same independently of rlang's presence
out <- switch(
fn,
is_installed = return(function(pkg) requireNamespace(pkg, quietly = TRUE))
)
# Only use rlang if it is fully loaded (#1482)
if (try_rlang &&
requireNamespace("rlang", quietly = TRUE) &&
environmentIsLocked(asNamespace("rlang"))) {
switch(
fn,
is_interactive = return(rlang::is_interactive)
)
ns <- asNamespace("rlang")
# Make sure rlang knows about "x" and "i" bullets.
# Pull from namespace rather than via `utils::packageVersion()`
# to avoid slowdown (#1657)
if (ns[[".__NAMESPACE__."]][["spec"]][["version"]] >= "0.4.2") {
switch(
fn,
abort = return(rlang::abort),
warn = return((rlang::warn)),
inform = return(rlang::inform)
)
}
}
# Fall back to base compats
is_interactive_compat <- function() {
opt <- getOption("rlang_interactive")
if (!is.null(opt)) {
opt
} else {
interactive()
}
}
format_msg <- function(x) paste(x, collapse = "\n")
switch(
fn,
is_interactive = return(is_interactive_compat),
abort = return(function(msg) stop(format_msg(msg), call. = FALSE)),
warn = return(function(msg) warning(format_msg(msg), call. = FALSE)),
inform = return(function(msg) message(format_msg(msg)))
)
stop(sprintf("Internal error in rlang shims: Unknown function `%s()`.", fn))
}
.rlang_cli_unstructure <- function(x) {
attributes(x) <- NULL
x
}
# nocov end
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.