diff --git a/R/utils.R b/R/utils.R index a17abda2..9ead9fe2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -21,15 +21,16 @@ bullets_with_header <- function(header, x) { cli::cli_li(paste0("{.field ", names(x), "}: ", vals)) } -modify_list <- function(.x, ...) { +modify_list <- function(.x, ..., .compact = TRUE) { dots <- list2(...) if (length(dots) == 0) return(.x) if (!is_named(dots)) { abort("All components of ... must be named") } - .x[names(dots)] <- dots - out <- compact(.x) + + out <- replace2(.x, dots) %>% compact() + if (length(out) == 0) { names(out) <- NULL } @@ -37,6 +38,24 @@ modify_list <- function(.x, ...) { out } +# Replace elements in `x` with elements in `y`, preserving the original ordering +# where applicable, and preserving duplicates found in `y`. +replace2 <- function(x, y) { + + for (nm in unique(names(y))) { + + xi <- which(names(x) == nm) + yi <- which(names(y) == nm) + + x[xi[seq_along(xi) <= length(yi)]] <- y[yi[seq_along(yi) <= length(xi)]] + + x[xi[seq_along(xi) > length(yi)]] <- NULL + + x <- c(x, y[yi[seq_along(yi) > length(xi)]]) + } + + x +} sys_sleep <- function(seconds) { check_number(seconds, "`seconds`") diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 45c7d9bd..0f9bc23a 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -9,6 +9,11 @@ test_that("modify list adds, removes, and overrides", { expect_snapshot(modify_list(x, a = 1, 2), error = TRUE) }) +test_that("`modify_list()` preserves duplicates in `y`", { + x <- list(x = 1, y = 2) + expect_equal(modify_list(x, y = 3, y = 4), list(x = 1, y = 3, y = 4)) +}) + test_that("can check arg types", { expect_snapshot(error = TRUE, { check_string(1, "x")