Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make inject_funs() more self-contained #148

Merged
merged 4 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 1 addition & 37 deletions R/eval.R
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ evaluate <- function(input,
if (is.list(envir)) {
envir <- list2env(envir, parent = enclos %||% parent.frame())
}
local_inject_funs(envir)

if (new_device) {
# Ensure we have a graphics device available for recording, but choose
Expand Down Expand Up @@ -223,15 +224,6 @@ evaluate_top_level_expression <- function(exprs,
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)
}

user_handlers <- output_handler$calling_handlers

for (expr in exprs) {
Expand Down Expand Up @@ -284,34 +276,6 @@ eval_with_user_handlers <- function(expr, envir, 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)
}

new_evaluation <- function(x) {
# Needs explicit list for backwards compatibility
structure(x, class = c("evaluate_evaluation", "list"))
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()
}
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")
})
Loading