|
1 | 1 | # THIS FILE IS NAMED aaa_preface.R SO IT IS LOADED BEFORE ALL OTHER FILES |
2 | 2 | # DO NOT CHANGE ITS NAME. IT MUST BE THE FIRST ONE ALPHABETICALLY. |
3 | 3 |
|
| 4 | +#' @noRd |
| 5 | +#' @keywords internal |
| 6 | + |
| 7 | +safe_list_ns <- local({ |
| 8 | + # Defines a safe-list that: |
| 9 | + # - Fails when trying to access a non-existant element by name, either by $, [] and [[]] |
| 10 | + # - Disables partial matching when using $ |
| 11 | + # - Supports numerical indexing as usual |
| 12 | + # |
| 13 | + # R does not fail when accesing non-existant elements by name, it returns NULL. |
| 14 | + # If we want to be defensive and forbiding access to non-existant elements, we should do that on an element by |
| 15 | + # element basis (!is.null, name %in names(), ...) |
| 16 | + # With this type we avoid including these checks and also forgetting to add a check when accessing a |
| 17 | + # new element. |
| 18 | + # |
| 19 | + # The intention is to use it in specific scopes where this conditions hold true. |
| 20 | + # |
| 21 | + # It exports elements individually and includes a `define_safe_list` that exports all individual elements |
| 22 | + # with the correct names for the operators to work |
| 23 | + |
| 24 | + #' @keywords internal |
| 25 | + safe_list <- function(...) { |
| 26 | + result <- list(...) |
| 27 | + class(result) <- c("safe_list", class(result)) |
| 28 | + return(result) |
| 29 | + } |
| 30 | + |
| 31 | + #' @keywords internal |
| 32 | + `[[.safe_list` <- function(x, name) { |
| 33 | + if (is.character(name) && !name %in% names(x)) { |
| 34 | + stop(sprintf("Element '%s' not found in safe_list.", name), call. = FALSE) |
| 35 | + } |
| 36 | + NextMethod("[[") |
| 37 | + } |
| 38 | + |
| 39 | + #' @keywords internal |
| 40 | + `$.safe_list` <- `[[.safe_list` |
| 41 | + |
| 42 | + #' @keywords internal |
| 43 | + `[.safe_list` <- function(x, i) { |
| 44 | + if (is.character(i) && length(setdiff(i, names(x))) > 0) { |
| 45 | + stop(sprintf("Elements '%s' not found in safe_list", paste(missing_elements, collapse = ", ")), call. = FALSE) |
| 46 | + } |
| 47 | + x <- NextMethod("[") |
| 48 | + as_safe_list(x) |
| 49 | + } |
| 50 | + |
| 51 | + as_safe_list <- function(x) { |
| 52 | + if (!is.list(x)) stop("x must be a list") |
| 53 | + class(x) <- c("safe_list", class(x)) |
| 54 | + x |
| 55 | + } |
| 56 | + |
| 57 | + is_safe_list <- function(x) isTRUE(is.list(x) && inherits(x, "safe_list")) |
| 58 | + |
| 59 | + test <- function() { |
| 60 | + assert <- function(expr, msg) if (!isTRUE(expr)) stop(msg) |
| 61 | + x <- safe_list(aa = 0, bb = 1) |
| 62 | + |
| 63 | + assert(is.list(x) && inherits(x, "safe_list"), "Classes are correctly set") |
| 64 | + |
| 65 | + assert(x[["aa"]] == 0, "Present element 'aa' can be accessed via [[]]") |
| 66 | + assert(x[["bb"]] == 1, "Present element 'bb' can be accessed via [[]]") |
| 67 | + |
| 68 | + err <- try(x[["c"]], silent = TRUE) |
| 69 | + assert(inherits(err, "try-error"), "Not present element 'c' cannot be accessed via [[]]") |
| 70 | + assert( |
| 71 | + attr(err, "condition")[["message"]] == "Element 'c' not found in safe_list.", |
| 72 | + "Not present element 'c' cannot be accessed via [[]] and throws the correct error" |
| 73 | + ) |
| 74 | + |
| 75 | + assert(x$aa == 0, "Present element 'aa' can be accessed via $") |
| 76 | + assert(x$bb == 1, "Present element 'bb' can be accessed via $") |
| 77 | + |
| 78 | + err <- try(x$c, silent = TRUE) |
| 79 | + assert(inherits(err, "try-error"), "Not present element 'c' cannot be accessed via $") |
| 80 | + assert( |
| 81 | + attr(err, "condition")[["message"]] == "Element 'c' not found in safe_list.", |
| 82 | + "Not present element 'c' cannot be accessed via $ and throws the correct error" |
| 83 | + ) |
| 84 | + |
| 85 | + err <- try(x$b, silent = TRUE) |
| 86 | + assert(inherits(err, "try-error"), "Partial matching is not possible via $") |
| 87 | + |
| 88 | + assert(isTRUE(is_safe_list(x)), "safe_list return TRUE when passed a safe_list") |
| 89 | + assert(isFALSE(is_safe_list(list())), "safe_list return FALSE when passed a regular list") |
| 90 | + |
| 91 | + assert(identical(x[c("aa")], safe_list(aa = 0)), "[] returns a subset safe_list") |
| 92 | + |
| 93 | + assert(x[[1]] == 0, "[[]] allows numerical indexing") |
| 94 | + assert(identical(x[1], safe_list(aa = 0)), "[] allows numerical indexing") |
| 95 | + |
| 96 | + assert(is_safe_list(as_safe_list(list(aa = 0))), "as_safe_list returns a safe_list") |
| 97 | + |
| 98 | + TRUE |
| 99 | + } |
| 100 | + |
| 101 | + individual_list <- list( |
| 102 | + safe_list = safe_list, |
| 103 | + "$.safe_list" = `$.safe_list`, |
| 104 | + "[[.safe_list" = `[[.safe_list`, |
| 105 | + "[.safe_list" = `[.safe_list`, |
| 106 | + as_safe_list = as_safe_list, |
| 107 | + is_safe_list = is_safe_list |
| 108 | + ) |
| 109 | + |
| 110 | + c( |
| 111 | + individual_list, |
| 112 | + define_safe_list = function(env = parent.frame()) { |
| 113 | + list2env(individual_list, env) |
| 114 | + invisible(NULL) |
| 115 | + }, |
| 116 | + test = test |
| 117 | + ) |
| 118 | +}) |
| 119 | + |
| 120 | +#' @keywords internal |
| 121 | +safe_list <- safe_list_ns[["safe_list"]] |
| 122 | +#' @keywords internal |
| 123 | +`$.safe_list` <- safe_list_ns[["$.safe_list"]] |
| 124 | +#' @keywords internal |
| 125 | +`[[.safe_list` <- safe_list_ns[["[[.safe_list"]] |
| 126 | +#' @keywords internal |
| 127 | +`[.safe_list` <- safe_list_ns[["[.safe_list"]] |
| 128 | +#' @keywords internal |
| 129 | +as_safe_list <- safe_list_ns[["as_safe_list"]] |
| 130 | +#' @keywords internal |
| 131 | +is_safe_list <- safe_list_ns[["is_safe_list"]] |
| 132 | + |
4 | 133 | #' Build a collection of named constants |
5 | 134 | #' |
6 | 135 | #' @param ... Named parameters to be collected as constants |
@@ -50,18 +179,14 @@ pack_of_constants <- function(...) { |
50 | 179 | #' This function differs from the base list extraction method in that it avoids partial matching of keys and throws |
51 | 180 | #' an error if the looked-for constant is not contained within the pack. |
52 | 181 | #' @keywords internal |
53 | | -#' @export |
54 | 182 | `$.pack_of_constants` <- function(pack, name) { |
55 | 183 | checkmate::assert_true(name %in% names(pack), .var.name = paste0(deparse(substitute(pack)), "$", name)) |
56 | 184 | NextMethod() |
57 | 185 | } |
58 | 186 |
|
59 | | -# This exports are recent requirement for devtools check https://github.com/r-lib/roxygen2/issues/1592#issue-2121199122 |
60 | 187 | #' @keywords internal |
61 | | -#' @export |
62 | 188 | `[[.pack_of_constants` <- `$.pack_of_constants` |
63 | 189 |
|
64 | | -#' @export |
65 | 190 | #' @keywords internal |
66 | 191 | `[.pack_of_constants` <- function(pack, name) { |
67 | 192 | stop("Invalid pack_of_constants method") |
|
0 commit comments