Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add param sep2 #569

Merged
merged 7 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Authors@R: c(
person("Gábor", "Csárdi", , "[email protected]", role = c("aut", "cre")),
person("Hadley", "Wickham", role = "ctb"),
person("Kirill", "Müller", role = "ctb"),
person("Salim", "Brüggemann", , "[email protected]", role = "ctb",
comment = c(ORCID = "0000-0002-5329-5987")),
person("RStudio", role = c("cph", "fnd"))
)
Description: A suite of tools to build attractive command line interfaces
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
* `ansi_collapse(x, trunc = 1, style = "head")` now indeed show one element
if `length(x) == 2`, as documented (@salim-b, #572).

* `ansi_collapse()` gains a `sep2` argument to specify a seperate separator
for length-two inputs. It defaults to `" and "` which, in conjunction with
the other defaults, produces a collapsed string that fully adheres to the
[serial comma](https://en.wikipedia.org/wiki/Serial_comma) rules.
(@salim-b, #569)

* `ansi_string()` is now an exported function (@multimeric, #573).

# cli 3.6.1
Expand Down
77 changes: 45 additions & 32 deletions R/glue.R
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ drop_null <- function(x) {
#' @description
#' Features:
#'
#' - custom separator,
#' - custom last separator: `last` argument,
#' - custom separator (`sep`),
#' - custom separator for length-two input (`sep2`),
#' - custom last separator (`last`),
#' - adds ellipsis to truncated strings,
#' - uses Unicode ellipsis character on UTF-8 console,
#' - can collapse "from both ends", with `style = "both-ends"`,
Expand All @@ -76,9 +77,12 @@ drop_null <- function(x) {
#'
#' @param x Character vector, or an object with an `as.character()` method
#' to collapse.
#' @param sep Character string, separator.
#' @param sep Separator. A character string.
#' @param sep2 Separator for the special case that `x` contains only two
#' elements. A character string.
#' @param last Last separator, if there is no truncation. E.g. use
#' `", and "` for the Oxford comma.
#' `", and "` for the [serial
#' comma](https://en.wikipedia.org/wiki/Serial_comma). A character string.
#' @param trunc Maximum number of elements to show. For `style = "head"`
#' at least `trunc = 1` is used. For `style = "both-ends"` at least
#' `trunc = 5` is used, even if a smaller number is specified.
Expand All @@ -94,10 +98,10 @@ drop_null <- function(x) {
#' and skips elements in the middle if needed.
#' * `head`: shows the beginning of the vector, and skips elements at the
#' end, if needed.
#' @return Character scalar. It is `NA_character_` if any elements in the
#' vector are `NA`.
#' @return Character scalar. It is `NA_character_` if any elements in `x`
#' are `NA`.
#'
#' @seealso `glue_collapse` in the glue package inspired this function
#' @seealso `glue_collapse` in the glue package inspired this function.
#' @export
#' @examples
#' ansi_collapse(letters)
Expand All @@ -108,21 +112,26 @@ drop_null <- function(x) {
#' # head style
#' ansi_collapse(letters, trunc = 5, style = "head")

ansi_collapse <- function(x, sep = ", ", last = ", and ", trunc = Inf,
width = Inf, ellipsis = symbol$ellipsis,
ansi_collapse <- function(x, sep = ", ", sep2 = " and ", last = ", and ",
trunc = Inf, width = Inf, ellipsis = symbol$ellipsis,
style = c("both-ends", "head")) {

style <- match.arg(style)
switch(
style,
"both-ends" = collapse_both_ends(x, sep, last, trunc, width, ellipsis),
"head" = collapse_head(x, sep, last, trunc, width, ellipsis)
"both-ends" = collapse_both_ends(
x, sep, sep2, last, trunc, width, ellipsis
),
"head" = collapse_head(x, sep, sep2, last, trunc, width, ellipsis)
)
}

collapse_head_notrim <- function(x, trunc, sep, last, ellipsis) {
collapse_head_notrim <- function(x, trunc, sep, sep2, last, ellipsis) {

lnx <- length(x)

if (lnx == 1L) return(x)
if (lnx == 2L) return(paste0(x, collapse = sep2))
if (lnx <= trunc) {
# no truncation
return(paste0(
Expand All @@ -140,8 +149,7 @@ collapse_head_notrim <- function(x, trunc, sep, last, ellipsis) {
}
}

collapse_head <- function(x, sep = ", ", last = ", and ", trunc = Inf,
width = Inf, ellipsis = symbol$ellipsis) {
collapse_head <- function(x, sep, sep2, last, trunc, width, ellipsis) {

trunc <- max(trunc, 1L)
x <- as.character(x)
Expand All @@ -156,7 +164,7 @@ collapse_head <- function(x, sep = ", ", last = ", and ", trunc = Inf,

# easier case, no width trimming
if (width == Inf) {
return(collapse_head_notrim(x, trunc, sep, last, ellipsis))
return(collapse_head_notrim(x, trunc, sep, sep2, last, ellipsis))
}

# complex case, with width wrapping
Expand All @@ -165,23 +173,29 @@ collapse_head <- function(x, sep = ", ", last = ", and ", trunc = Inf,
if (tcd) x <- x[1:trunc]

# then we calculate the width w/o trimming
wx <- ansi_nchar(x)
wsep <- ansi_nchar(sep, "width")
wlst <- ansi_nchar(last, "width")
well <- ansi_nchar(ellipsis, "width")
wx <- ansi_nchar(x)
wsep <- ansi_nchar(sep, "width")
wsep2 <- ansi_nchar(sep2, "width")
wlast <- ansi_nchar(last, "width")
well <- ansi_nchar(ellipsis, "width")
if (!tcd) {
# x[1]
# x[1], and x[2]
# x[1] and x[2]
# x[1], x[2], and x[3]
nsep <- if (lnx >= 2) lnx - 2L else 0L
nlst <- if (lnx >= 2) 1L else 0L
wtot <- sum(wx) + nsep * wsep + nlst * wlst
nsep <- if (lnx > 2L) lnx - 2L else 0L
nsep2 <- if (lnx == 2L) 1L else 0L
nlast <- if (lnx > 2L) 1L else 0L
wtot <- sum(wx) + nsep * wsep + nsep2 * wsep2 + nlast * wlast
if (wtot <= width) {
return(paste0(
paste(x[1:(lnx - 1L)], collapse = sep),
last,
x[lnx]
))
if (lnx == 2L) {
return(paste0(x, collapse = sep2))
} else {
return(paste0(
paste(x[1:(lnx - 1L)], collapse = sep),
last,
x[lnx]
))
}
}

} else {
Expand Down Expand Up @@ -226,8 +240,7 @@ collapse_head <- function(x, sep = ", ", last = ", and ", trunc = Inf,
))
}

collapse_both_ends <- function(x, sep = ", ", last = ", and ", trunc = Inf,
width = Inf, ellipsis = symbol$ellipsis) {
collapse_both_ends <- function(x, sep, sep2, last, trunc, width, ellipsis) {

if (width != Inf) {
warning(format_warning(c(
Expand All @@ -240,8 +253,8 @@ collapse_both_ends <- function(x, sep = ", ", last = ", and ", trunc = Inf,
# we always list at least 5 elements
trunc <- max(trunc, 5L)
trunc <- min(trunc, length(x))
if (length(x) <= 5 || length(x) <= trunc) {
return(collapse_head(x, sep, last, trunc = trunc, width, ellipsis))
if (length(x) <= 5L || length(x) <= trunc) {
return(collapse_head(x, sep, sep2, last, trunc = trunc, width, ellipsis))
}

# we have at least six elements in the vector
Expand Down
10 changes: 4 additions & 6 deletions R/inline.R
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,16 @@ inline_generic <- function(app, x, style) {

inline_collapse <- function(x, style = list()) {
sep <- style[["vec-sep"]] %||% style[["vec_sep"]] %||% ", "
if (length(x) >= 3) {
last <- style[["vec-last"]] %||% style[["vec_last"]] %||% ", and "
} else {
last <- style[["vec-sep2"]] %||% style[["vec_sep2"]] %||% style[["vec-last"]] %||%
style[["vec_last"]] %||% " and "
}
sep2 <- style[["vec-sep2"]] %||% style[["vec_sep2"]] %||% " and "
last <- style[["vec-last"]] %||% style[["vec_last"]] %||% ", and "

trunc <- style[["vec-trunc"]] %||% style[["vec_trunc"]] %||% 20L
col_style <- style[["vec-trunc-style"]] %||% "both-ends"

ansi_collapse(
x,
sep = sep,
sep2 = sep2,
last = last,
trunc = trunc,
style = col_style
Expand Down
19 changes: 12 additions & 7 deletions man/ansi_collapse.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/testthat/_snaps/collapsing.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
Code
cli_text("{v(2,1)}")
Message
1, ...
1 and 2
Code
cli_text("{v(3,1)}")
Message
Expand Down
7 changes: 7 additions & 0 deletions tests/testthat/test-collapsing.R
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,10 @@ test_that("ansi_collapse produces consistent truncation results", {
expect_equal(ansi_collapse(1:2, trunc = 1, style = "head"),
ansi_collapse(1:2, trunc = 0, style = "head"))
})

test_that("ansi_collapse uses `sep2` for length-two inputs", {
expect_equal(ansi_collapse(1:2),
"1 and 2")
expect_equal(ansi_collapse(1:2, trunc = 2, style = "head"),
"1 and 2")
})
Loading