R/env.R

Defines functions extract_match parse_dot_line ignore_empty_lines ignore_comments load_env_file load_env ignore_env format_env_docs ignore_env_config set_env

set_env <- function(path, targz, desc, envir = parent.frame()) {
  pkg <- desc$get("Package")
  ign <- as_flag(desc$get("Config/rcmdcheck/ignore-inconsequential-notes"))
  if (ign) ignore_env(envir = envir)
  load_env(path, targz, pkg, envir = envir)
}

ignore_env_config <- function() {
  data_literal(
    "docs"         | "envvar"                                    | "value",
    # ---------------------------------------------------------------------
    "report large package sizes"
                   | "_R_CHECK_PKG_SIZES_"                       | FALSE,
    "check cross-references in Rd files"
                   | "_R_CHECK_RD_XREFS_"                        | FALSE,
    "NOTE if package requires GNU make"
                   | "_R_CHECK_CRAN_INCOMING_NOTE_GNU_MAKE_"     | FALSE,
    "report marked non-ASCII strings in datasets"
                   | "_R_CHECK_PACKAGE_DATASETS_SUPPRESS_NOTES_" | TRUE
  )
}

format_env_docs <- function() {
  envs <- ignore_env_config()
  paste0(
    "* ", envs$docs, " (`", envs$envvar, " = ", envs$value, "`)",
    collapse = ",\n"
  )
}

ignore_env <- function(to_ignore = NULL, envir = parent.frame()) {
  if (is.null(to_ignore)) {
    conf <- ignore_env_config()
    to_ignore <- structure(conf$value, names = conf$envvar)
  }
  withr::local_envvar(to_ignore, .local_envir = envir)
}

load_env <- function(path, targz, package, envir = parent.frame()) {
  should_load <- as_flag(Sys.getenv("RCMDCHECK_LOAD_CHECK_ENV"), TRUE)
  if (!should_load) return()

  env <- NULL
  if (file.info(path)$isdir) {
    env_path <- file.path(path, "tools", "check.env")
  } else {
    dir.create(tmp <- tempfile())
    on.exit(unlink(tmp, recursive = TRUE), add = TRUE)
    utils::untar(
      targz,
      file.path(package, "tools", "check.env"),
      exdir = tmp, tar = "internal"
    )
    env_path <- file.path(tmp, package, "tools", "check.env")
  }

  if (file.exists(env_path)) {
    load_env_file(env_path, envir = envir)
  }
}

load_env_file <- function(path, envir = parent.frame()) {
  env <- readLines(path, warn = FALSE)
  env <- ignore_comments(env)
  env <- ignore_empty_lines(env)
  if (length(env) == 0) return(invisible())

  env <- lapply(env, parse_dot_line)
  envc <- structure(
    vapply(env, "[[", character(1), "value"),
    names = vapply(env, "[[", character(1), "key")
  )
  withr::local_envvar(envc, .local_envir = envir)
}

ignore_comments <- function(lines) {
  grep("^#", lines, invert = TRUE, value = TRUE)
}

ignore_empty_lines <- function(lines) {
  grep("^\\s*$", lines, invert = TRUE, value = TRUE)
}

line_regex <- paste0(
  "^\\s*",                  # leading whitespace
  "(?<export>export\\s+)?", # export, if given
  "(?<key>[^=]+)",          # variable name
  "=",                      # equals sign
  "(?<q>['\"]?)",           # quote if present
  "(?<value>.*)",           # value
  "\\g{q}",                 # the same quote again
  "\\s*",                   # trailing whitespace
  "$"                       # end of line
)

parse_dot_line <- function(line) {
  match <- regexpr(line_regex, line, perl = TRUE)
  if (match == -1) {
    stop("Cannot parse check.env: ", substr(line, 1, 40), call. = FALSE)
  }
  as.list(extract_match(line, match)[c("key", "value")])
}

extract_match <- function(line, match) {
  tmp <- mapply(
    attr(match, "capture.start"),
    attr(match, "capture.length"),
    FUN = function(start, length) {
      tmp <- substr(line, start, start + length - 1)
    }
  )
  names(tmp) <- attr(match, "capture.names")
  tmp
}
r-lib/rcmdcheck documentation built on Nov. 9, 2023, 10:08 a.m.