R/utils.R

Defines functions vec_paste0 browser named new_vctrs_bytes find_unit print.vctrs_bytes format.vctrs_bytes fast_c import_from vec_common_suffix last df_has_base_subset s3_method_specific s3_get_method s3_find_method ns_methods try_catch_impl try_catch_hnd try_catch_callback data.frame unstructure glue_data_bullets new_opts obj_type result_get result compact has_unique_names inline_list all_equal set_partition cat_line has_inner_names outer_names vec_coerce_bare ones indent str_dup

str_dup <- function(x, times) {
  paste0(rep(x, times = times), collapse = "")
}

indent <- function(x, n) {
  pad <- str_dup(" ", n)
  map_chr(x, gsub, pattern = "(\n+)", replacement = paste0("\\1", pad))
}

ones <- function(...) {
  array(1, dim = c(...))
}

vec_coerce_bare <- function(x, type) {
  # FIXME! Unexported wrapper around Rf_coerceVector()
  coerce <- env_get(ns_env("rlang"), "vec_coerce")
  coerce(x, type)
}


# Matches the semantics of c() - based on experimenting with the output
# of c(), not reading the source code.
outer_names <- function(names, outer, n) {
  .Call(ffi_outer_names, names, outer, vec_cast(n, int()))
}

has_inner_names <- function(x) {
  !all(map_lgl(map(x, vec_names), is.null))
}

cat_line <- function(...) {
  cat(paste0(..., "\n", collapse = ""))
}


set_partition <- function(x, y) {
  list(
    both = intersect(x, y),
    only_x = setdiff(x, y),
    only_y = setdiff(y, x)
  )
}

all_equal <- function(x) all(x == x[[1]])

inline_list <- function(title, x, width = getOption("width"), quote = "") {
  label_width <- width - nchar(title)
  x <- glue::glue_collapse(
    encodeString(x, quote = quote),
    sep = ", ",
    width = label_width
  )
  paste0(title, x)
}

has_unique_names <- function(x) {
  nms <- names(x)

  if (length(nms) != length(x)) {
    return(FALSE)
  }

  if (any(is.na(nms) | nms == "")) {
    return(FALSE)
  }

  !anyDuplicated(nms)
}

compact <- function(x) {
  is_null <- map_lgl(x, is.null)
  x[!is_null]
}

paste_line <- function (...) {
  paste(chr(...), collapse = "\n")
}

# Experimental
result <- function(ok = NULL, err = NULL) {
  structure(
    list(ok = ok, err = err),
    class = "rlang_result"
  )
}
result_get <- function(x) {
  if (!is_null(x$err)) {
    cnd_signal(x$err)
  }
  x$ok
}

obj_type <- function(x) {
  if (vec_is(x)) {
    vec_ptype_full(x)
  } else if (is.object(x)) {
    paste(class(x), collapse = "/")
  } else if (is_function(x)) {
    "function"
  } else {
    typeof(x)
  }
}

new_opts <- function(x, opts, subclass = NULL, arg = NULL) {
  if (!all(x %in% opts)) {
    if (is_null(arg)) {
      arg <- "Argument"
    } else {
      arg <- glue::glue("`{arg}`")
    }
    opts <- encodeString(opts, quote = "\"")
    opts <- glue::glue_collapse(opts, sep = ", ", last = " or ")
    abort(glue::glue("{arg} must be one of {opts}."))
  }

  structure(
    set_names(opts %in% x, opts),
    class = c(subclass, "vctrs_opts")
  )
}

glue_data_bullets <- function(.data, ..., .env = caller_env()) {
  glue_data <- function(...) glue::glue_data(.data, ..., .envir = .env)
  format_error_bullets(map_chr(chr(...), glue_data))
}

unstructure <- function(x) {
  attributes(x) <- NULL
  x
}

# We almost never want `stringsAsFactors = TRUE`, and `FALSE` became
# the default in R 4.0.0. This wrapper ensures that our tests are compliant
# with versions of R before and after this change. Keeping it in `utils.R`
# rather than as a testthat helper ensures that it is sourced before any other
# testthat helpers.
data.frame <- function(..., stringsAsFactors = NULL) {
  stringsAsFactors <- stringsAsFactors %||% FALSE
  base::data.frame(..., stringsAsFactors = stringsAsFactors)
}

try_catch_callback <- function(data, cnd) {
  .Call(vctrs_try_catch_callback, data, cnd)
}

try_catch_hnd <- function(data) {
  function(cnd) {
    try_catch_callback(data, cnd)
  }
}
try_catch_impl <- function(data, ...) {
  tryCatch(
    try_catch_callback(data, NULL),
    ...
  )
}

ns_methods <- function(name) {
  ns_env(name)$.__S3MethodsTable__.
}

s3_find_method <- function(x, generic, ns = "base") {
  stopifnot(
    is_string(generic),
    is_string(ns)
  )
  table <- ns_methods(ns_env(ns))
  .Call(vctrs_s3_find_method, generic, x, table)
}
s3_get_method <- function(class, generic, ns = "base") {
  stopifnot(
    is_string(class),
    is_string(generic),
    is_string(ns)
  )
  table <- ns_methods(ns_env(ns))
  .Call(ffi_s3_get_method, generic, class, table)
}

s3_method_specific <- function(x,
                               generic,
                               ns = "base",
                               default = TRUE) {
  classes <- class(x)[[1]]

  if (default) {
    classes <- c(classes, "default")
  }

  for (class in classes) {
    method <- s3_get_method(class, generic, ns = ns)
    if (!is_null(method)) {
      return(method)
    }
  }

  cli::cli_abort("Can't find {.fn {generic}} method for {.cls {class}}.")
}

df_has_base_subset <- function(x) {
  method <- s3_find_method(x, "[", ns = "base")
  is_null(method) || identical(method, `[.data.frame`)
}

last <- function(x) {
  x[[length(x)]]
}

# Find the longest common suffix of two vectors
vec_common_suffix <- function(x, y) {
  common <- vec_cast_common(x = x, y = y)
  x <- common$x
  y <- common$y

  x_size <- vec_size(x)
  y_size <- vec_size(y)
  n <- min(x_size, y_size)

  if (!n) {
    return(vec_slice(x, int()))
  }

  # Truncate the start of the vectors so they have equal size
  if (x_size < y_size) {
    y <- vec_slice(y, seq2(y_size - x_size + 1, y_size))
  } else if (y_size < x_size) {
    x <- vec_slice(x, seq2(x_size - y_size + 1, x_size))
  }

  # Find locations of unequal elements. Elements after the last
  # location are the common suffix.
  common <- vec_equal(x, y)
  i <- which(!common)

  # Slice the suffix after the last unequal element
  if (length(i)) {
    vec_slice(x, seq2(max(i) + 1, n))
  } else {
    x
  }
}

import_from <- function(ns, names, env = caller_env()) {
  objs <- env_get_list(ns_env(ns), names)
  env_bind(env, !!!objs)
}

fast_c <- function(x, y) {
  .Call(vctrs_fast_c, x, y)
}

# Based on r-lib/bench (itself based on gaborcsardi/prettyunits)
#' @export
format.vctrs_bytes <- function(x, scientific = FALSE, digits = 3, drop0trailing = TRUE, ...) {
  nms <- names(x)

  bytes <- unclass(x)

  unit <- map_chr(x, find_unit, byte_units)
  res <- round(bytes / byte_units[unit], digits = digits)

  ## Zero bytes
  res[bytes == 0] <- 0
  unit[bytes == 0] <- "B"

  ## NA and NaN bytes
  res[is.na(bytes)] <- NA_real_
  res[is.nan(bytes)] <- NaN
  unit[is.na(bytes)] <- ""            # Includes NaN as well

  # Append an extra B to each unit
  large_units <- unit %in% names(byte_units)[-1]
  unit[large_units] <- paste0(unit[large_units], "B")

  res <- format(res, scientific = scientific, digits = digits, drop0trailing = drop0trailing, ...)

  stats::setNames(paste0(res, unit), nms)
}
#' @export
print.vctrs_bytes <- function(x, ...) {
  print(format(x, ...), quote = FALSE)
}

tolerance <- sqrt(.Machine$double.eps)
find_unit <- function(x, units) {
  if (is.na(x) || is.nan(x) || x <= 0 || is.infinite(x)) {
    return(NA_character_)
  }
  epsilon <- 1 - (x * (1 / units))
  names(utils::tail(n = 1, which(epsilon < tolerance)))
}

byte_units <- c(
  'B' = 1,
  'K' = 1024,
  'M' = 1024 ^ 2,
  'G' = 1024 ^ 3,
  'T' = 1024 ^ 4,
  'P' = 1024 ^ 5,
  'E' = 1024 ^ 6,
  'Z' = 1024 ^ 7,
  'Y' = 1024 ^ 8
)

new_vctrs_bytes <- function(x) {
  structure(x, class = c("vctrs_bytes", "numeric"))
}

named <- function(x) {
  if (is_null(names(x))) {
    names(x) <- names2(x)
  }
  x
}

browser <- function(...,
                    skipCalls = 0,
                    frame = parent.frame()) {
  if (!identical(stdout(), getConnection(1))) {
    sink(getConnection(1))
    withr::defer(sink(), envir = frame)
  }

  # Calling `browser()` on exit avoids RStudio displaying the
  # `browser2()` location. We still need one `n` to get to the
  # expected place. Ideally `skipCalls` would not skip but exit the
  # contexts.
  on.exit(base::browser(..., skipCalls = skipCalls + 1))
}

vec_paste0 <- function(...) {
  args <- vec_recycle_common(...)
  exec(paste0, !!!args)
}

Try the vctrs package in your browser

Any scripts or data that you put into this service are public.

vctrs documentation built on Oct. 13, 2023, 1:05 a.m.