Skip to content

Commit

Permalink
Make inject_funs() more self-contained
Browse files Browse the repository at this point in the history
* Move to its own file
* Add a test
* Make it invisibly return previous values
* Polish examples
  • Loading branch information
hadley committed Jun 18, 2024
1 parent b2a2689 commit 2d498ef
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 40 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ Config/Needs/website: tidyverse/tidytemplate
Config/testthat/edition: 3
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
RoxygenNote: 7.3.1
37 changes: 1 addition & 36 deletions R/eval.R
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,7 @@ evaluate_call <- function(call,
timing_fn <- function(x) {x; NULL};
}

if (length(funs <- .env$inject_funs)) {
funs_names <- names(funs)
funs_new <- !vapply(funs_names, exists, logical(1), envir, inherits = FALSE)
funs_names <- funs_names[funs_new]
funs <- funs[funs_new]
on.exit(rm(list = funs_names, envir = envir), add = TRUE)
for (i in seq_along(funs_names)) assign(funs_names[i], funs[[i]], envir)
}
local_inject_funs(envir)

user_handlers <- output_handler$calling_handlers

Expand Down Expand Up @@ -304,34 +297,6 @@ eval_with_user_handlers <- function(expr, envir, enclos, calling_handlers) {
eval(call)
}

#' Inject functions into the environment of `evaluate()`
#'
#' Create functions in the environment specified in the `envir` argument of
#' [evaluate()]. This can be helpful if you want to substitute certain
#' functions when evaluating the code. To make sure it does not wipe out
#' existing functions in the environment, only functions that do not exist in
#' the environment are injected.
#' @param ... Named arguments of functions. If empty, previously injected
#' functions will be emptied.
#' @note For expert use only. Do not use it unless you clearly understand it.
#' @keywords internal
#' @examples library(evaluate)
#' # normally you cannot capture the output of system
#' evaluate("system('R --version')")
#'
#' # replace the system() function
#' inject_funs(system = function(...) cat(base::system(..., intern = TRUE), sep = '\n'))
#'
#' evaluate("system('R --version')")
#'
#' inject_funs() # empty previously injected functions
#' @export
inject_funs <- function(...) {
funs <- list(...)
funs <- funs[names(funs) != '']
.env$inject_funs <- Filter(is.function, funs)
}

format_warning <- function(x) {
if (inherits(x, "rlang_warning")) {
format(x)
Expand Down
54 changes: 54 additions & 0 deletions R/inject-funs.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#' Inject functions into the environment of `evaluate()`
#'
#' Create functions in the environment specified in the `envir` argument of
#' [evaluate()]. This can be helpful if you want to substitute certain
#' functions when evaluating the code. To make sure it does not wipe out
#' existing functions in the environment, only functions that do not exist in
#' the environment are injected.
#' @param ... Named arguments of functions. If empty, previously injected
#' functions will be emptied.
#' @note For expert use only. Do not use it unless you clearly understand it.
#' @keywords internal
#' @return Invisibly returns previous values.
#' @examples library(evaluate)
#' # normally you cannot capture the output of system
#' evaluate("system('R --version')")
#'
#' # replace the system() function
#' old <- inject_funs(system = function(...) {
#' cat(base::system(..., intern = TRUE), sep = '\n')
#' })
#'
#' evaluate("system('R --version')")
#'
#' # restore previously injected functions
#' inject_funs(old)
#' @export
inject_funs <- function(...) {
funs <- list(...)
funs <- funs[names(funs) != '']
old <- .env$inject_funs
.env$inject_funs <- Filter(is.function, funs)

invisible(old)
}

local_inject_funs <- function(envir, frame = parent.frame()) {
funs <- .env$inject_funs
if (length(funs) == 0) {
return()
}

funs_names <- names(funs)
funs_new <- !vapply(funs_names, exists, logical(1), envir, inherits = FALSE)
funs_names <- funs_names[funs_new]
funs <- funs[funs_new]

defer(rm(list = funs_names, envir = envir), frame = frame)

for (i in seq_along(funs_names)) {
assign(funs_names[i], funs[[i]], envir)
}

invisible()
}
4 changes: 4 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
defer <- function(expr, frame = parent.frame(), after = FALSE) {
thunk <- as.call(list(function() expr))
do.call(on.exit, list(thunk, TRUE, after), envir = frame)
}
12 changes: 9 additions & 3 deletions man/inject_funs.Rd

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

7 changes: 7 additions & 0 deletions tests/testthat/test-inject-funs.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
test_that("can inject functons into evaluation context", {
old <- inject_funs(f = function() 1)
defer(inject_funs(old))

ev <- evaluate("f()")
expect_equal(ev[[2]], "[1] 1\n")
})

0 comments on commit 2d498ef

Please sign in to comment.