diff --git a/.lintr b/.lintr index 34473d27..3377b646 100644 --- a/.lintr +++ b/.lintr @@ -1,5 +1,4 @@ linters: linters_with_defaults( line_length_linter = line_length_linter(120), - cyclocomp_linter = NULL, object_usage_linter = NULL ) diff --git a/DESCRIPTION b/DESCRIPTION index a98b06fb..41167182 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -29,6 +29,7 @@ Depends: Imports: checkmate (>= 2.1.0), cli (>= 3.4.0), + evaluate (>= 1.0.0), grDevices, lifecycle (>= 0.2.0), rlang (>= 1.1.0), @@ -64,6 +65,7 @@ Collate: 'qenv-get_code.R' 'qenv-get_env.R' 'qenv-get_messages.r' + 'qenv-get_outputs.R' 'qenv-get_var.R' 'qenv-get_warnings.R' 'qenv-join.R' diff --git a/NAMESPACE b/NAMESPACE index a57ca1d2..1ab986cb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,6 +17,7 @@ export(eval_code) export(get_code) export(get_env) export(get_messages) +export(get_outputs) export(get_var) export(get_warnings) export(join) diff --git a/NEWS.md b/NEWS.md index 0ac60053..cc3f8e91 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,11 +1,16 @@ # teal.code 0.6.1.9003 +### Enhancements + +* Introduced `get_outputs` function to fetch objects which have been printed or plotted in the `qenv` code. + ### Bug fixes * Fix a problem detecting co-occurrences when expression has multiple lines. ### Miscellaneous +* `eval_code` uses `evaluate::evaluate` and stores returned outputs in the code's attribute. * Refactor `eval_code` method signature to allow for more flexibility when extending the `eval_code`/`within` functions. # teal.code 0.6.1 diff --git a/R/qenv-class.R b/R/qenv-class.R index b9114692..55208285 100644 --- a/R/qenv-class.R +++ b/R/qenv-class.R @@ -31,11 +31,12 @@ setMethod( "initialize", "qenv", function(.Object, .xData, code = list(), ...) { # nolint: object_name. + parent <- parent.env(.GlobalEnv) new_xdata <- if (rlang::is_missing(.xData)) { - new.env(parent = parent.env(.GlobalEnv)) + new.env(parent = parent) } else { checkmate::assert_environment(.xData) - rlang::env_clone(.xData, parent = parent.env(.GlobalEnv)) + rlang::env_clone(.xData, parent = parent) } lockEnvironment(new_xdata, bindings = TRUE) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 878f5eca..cb37d14a 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -44,60 +44,57 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co if (identical(trimws(code), "") || length(code) == 0) { return(object) } + code <- paste(split_code(code), collapse = "\n") + + object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(object@.xData)) parsed_code <- parse(text = code, keep.source = TRUE) - object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv)) - if (length(parsed_code) == 0) { - # empty code, or just comments - attr(code, "dependency") <- extract_dependency(parsed_code) # in case comment contains @linksto tag - object@code <- c(object@code, stats::setNames(list(code), sample.int(.Machine$integer.max, size = 1))) - return(object) - } - code_split <- split_code(paste(code, collapse = "\n")) - for (i in seq_along(code_split)) { - current_code <- code_split[[i]] - current_call <- parse(text = current_code, keep.source = TRUE) - # Using withCallingHandlers to capture warnings and messages. - # Using tryCatch to capture the error and abort further evaluation. - x <- withCallingHandlers( - tryCatch( - { - eval(current_call, envir = object@.xData) - if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { - # needed to make sure that @.xData is always a sibling of .GlobalEnv - # could be changed when any new package is added to search path (through library or require call) - parent.env(object@.xData) <- parent.env(.GlobalEnv) - } - NULL - }, - error = function(e) { + + old <- evaluate::inject_funs( + library = function(...) { + x <- library(...) + if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { + parent.env(object@.xData) <- parent.env(.GlobalEnv) + } + invisible(x) + } + ) + out <- evaluate::evaluate( + code, + envir = object@.xData, + stop_on_error = 1, + output_handler = evaluate::new_output_handler(value = identity) + ) + out <- evaluate::trim_intermediate_plots(out) + + evaluate::inject_funs(old) # remove library() override + + new_code <- list() + for (this in out) { + if (inherits(this, "source")) { + this_code <- gsub("\n$", "", this$src) + attr(this_code, "dependency") <- extract_dependency(parse(text = this_code, keep.source = TRUE)) + new_code <- c(new_code, stats::setNames(list(this_code), sample.int(.Machine$integer.max, size = 1))) + } else { + last_code <- new_code[[length(new_code)]] + if (inherits(this, "error")) { + return( errorCondition( message = sprintf( "%s \n when evaluating qenv code:\n%s", - cli::ansi_strip(conditionMessage(e)), - current_code + cli::ansi_strip(conditionMessage(this)), + last_code ), class = c("qenv.error", "try-error", "simpleError"), - trace = unlist(c(object@code, list(current_code))) + trace = unlist(c(object@code, list(new_code))) ) - } - ), - warning = function(w) { - attr(current_code, "warning") <<- cli::ansi_strip(sprintf("> %s\n", conditionMessage(w))) - invokeRestart("muffleWarning") - }, - message = function(m) { - attr(current_code, "message") <<- cli::ansi_strip(sprintf("> %s", conditionMessage(m))) - invokeRestart("muffleMessage") + ) } - ) - - if (!is.null(x)) { - return(x) + attr(last_code, "outputs") <- c(attr(last_code, "outputs"), list(this)) + new_code[[length(new_code)]] <- last_code } - attr(current_code, "dependency") <- extract_dependency(current_call) - object@code <- c(object@code, stats::setNames(list(current_code), sample.int(.Machine$integer.max, size = 1))) } + object@code <- c(object@code, new_code) lockEnvironment(object@.xData, bindings = TRUE) object } diff --git a/R/qenv-get_outputs.R b/R/qenv-get_outputs.R new file mode 100644 index 00000000..2aa62616 --- /dev/null +++ b/R/qenv-get_outputs.R @@ -0,0 +1,30 @@ +#' Get outputs +#' +#' @description +#' `eval_code` evaluates code silently so plots and prints don't show up in the console or graphic devices. +#' If one wants to use an output outside of the `qenv` (e.g. use a graph in `renderPlot`) then use `get_outputs`. +#' @param object (`qenv`) +#' @return list of outputs generated in a `qenv`` +#' @examples +#' q <- eval_code( +#' qenv(), +#' quote({ +#' a <- 1 +#' print("I'm an output") +#' plot(1) +#' }) +#' ) +#' get_outputs(q) +#' +#' @aliases get_outputs,qenv-method +#' +#' @export +setGeneric("get_outputs", function(object) standardGeneric("get_outputs")) + +setMethod("get_outputs", signature = "qenv", function(object) { + Reduce( + function(x, y) c(x, attr(y, "outputs")), + init = list(), + x = object@code + ) +}) diff --git a/R/utils-get_code_dependency.R b/R/utils-get_code_dependency.R index 7f555e4c..8596ae42 100644 --- a/R/utils-get_code_dependency.R +++ b/R/utils-get_code_dependency.R @@ -521,6 +521,9 @@ get_call_breaks <- function(code) { } )) call_breaks <- call_breaks[-nrow(call_breaks), , drop = FALSE] # breaks in between needed only + if (nrow(call_breaks) == 0L) { + call_breaks <- matrix(numeric(0), ncol = 2) + } colnames(call_breaks) <- c("line", "col") call_breaks } diff --git a/R/utils.R b/R/utils.R index 0028c708..bec2b9e4 100644 --- a/R/utils.R +++ b/R/utils.R @@ -57,12 +57,25 @@ lang2calls <- function(x) { #' Obtain warnings or messages from code slot #' #' @param object (`qenv`) -#' @param what (`"warning"` or `"message"`) +#' @param what (`warning` or `message`) #' @return `character(1)` containing combined message or `NULL` when no warnings/messages #' @keywords internal get_warn_message_util <- function(object, what) { checkmate::matchArg(what, choices = c("warning", "message")) - messages <- lapply(object@code, "attr", what) + messages <- lapply( + object@code, + function(x) { + unlist(lapply( + attr(x, "outputs"), + function(el) { + if (inherits(el, what)) { + sprintf("> %s", conditionMessage(el)) + } + } + )) + } + ) + idx_warn <- which(sapply(messages, function(x) !is.null(x) && !identical(x, ""))) if (!any(idx_warn)) { return(NULL) @@ -74,7 +87,7 @@ get_warn_message_util <- function(object, what) { warn = messages, expr = code, function(warn, expr) { - sprintf("%swhen running code:\n%s", warn, expr) + sprintf("%s\nwhen running code:\n%s", trimws(warn), trimws(expr)) } ) diff --git a/_pkgdown.yml b/_pkgdown.yml index e4a8c973..6be321a6 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -31,6 +31,7 @@ reference: - eval_code - get_code - get_env + - get_outputs - get_var - get_messages - get_warnings diff --git a/man/get_outputs.Rd b/man/get_outputs.Rd new file mode 100644 index 00000000..e838cecb --- /dev/null +++ b/man/get_outputs.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-get_outputs.R +\name{get_outputs} +\alias{get_outputs} +\alias{get_outputs,qenv-method} +\title{Get outputs} +\usage{ +get_outputs(object) +} +\arguments{ +\item{object}{(\code{qenv})} +} +\value{ +list of outputs generated in a `qenv`` +} +\description{ +\code{eval_code} evaluates code silently so plots and prints don't show up in the console or graphic devices. +If one wants to use an output outside of the \code{qenv} (e.g. use a graph in \code{renderPlot}) then use \code{get_outputs}. +} +\examples{ +q <- eval_code( + qenv(), + quote({ + a <- 1 + print("I'm an output") + plot(1) + }) +) +get_outputs(q) + +} diff --git a/man/get_warn_message_util.Rd b/man/get_warn_message_util.Rd index 18a54dfc..e6bd06ca 100644 --- a/man/get_warn_message_util.Rd +++ b/man/get_warn_message_util.Rd @@ -9,7 +9,7 @@ get_warn_message_util(object, what) \arguments{ \item{object}{(\code{qenv})} -\item{what}{(\code{"warning"} or \code{"message"})} +\item{what}{(\code{warning} or \code{message})} } \value{ \code{character(1)} containing combined message or \code{NULL} when no warnings/messages diff --git a/tests/testthat/test-get_outputs.R b/tests/testthat/test-get_outputs.R new file mode 100644 index 00000000..a1039556 --- /dev/null +++ b/tests/testthat/test-get_outputs.R @@ -0,0 +1,114 @@ +testthat::describe("get_output", { + testthat::it("returns an empty list if nothing is printed", { + q <- qenv() + q1 <- eval_code(q, expression(a <- 1L, b <- 2L)) + testthat::expect_identical(get_outputs(q1), list()) + }) + + testthat::it("implicitly printed objects are returned asis in a list and are identical to ones in the environment", { + q <- qenv() + q1 <- eval_code( + q, + expression( + a <- 1L, a, + b <- structure(list(aa = list(aaa = "aaa")), class = "class_to_break"), b + ) + ) + testthat::expect_identical(get_outputs(q1), unname(as.list(q1))) + testthat::expect_true(rlang::is_reference(get_outputs(q1)[[1]], q1$a)) + testthat::expect_true(rlang::is_reference(get_outputs(q1)[[2]], q1$b)) + }) + + # it cannot have a package prefix here until upstream bug in testthat is solved + it("implicitly printed S4 object is returned asis in a list and identical to the one in the environment", { + methods::setClass("NewS4Class", slots = list(value = "numeric")) + withr::defer(removeClass("NewS4Class")) + q <- qenv() + q1 <- eval_code( + q, + expression( + new_obj <- methods::new("NewS4Class", value = 42), + new_obj + ) + ) + testthat::expect_identical(get_outputs(q1), unname(as.list(q1))) + testthat::expect_true(rlang::is_reference(get_outputs(q1)[[1]], q1$new_obj)) + testthat::expect_s4_class(get_outputs(q1)[[1]], "NewS4Class") + }) + + testthat::it("implicitly printed list is returned asis even if its print is overridden", { + q <- qenv() + q1 <- eval_code( + q, + expression( + print.test_class <- function(x, ...) { + print("test_print") + invisible(NULL) + }, + b <- structure(list("test"), class = "test_class"), + b + ) + ) + testthat::expect_identical(get_outputs(q1), list(q1$b)) + }) + + testthat::it("explicitly printed objects are returned as console-output-string in a list", { + q <- qenv() + q1 <- eval_code(q, expression(a <- 1L, print(a), b <- 2L, print(b))) + testthat::expect_identical(get_outputs(q1), list("[1] 1\n", "[1] 2\n")) + }) + + testthat::it("explicitly printed object uses newly registered print method and returned as console-output-string", { + q <- qenv() + q1 <- eval_code( + q, + expression( + print.test_class <- function(x, ...) { + print("test_print") + invisible(NULL) + }, + b <- structure(list("test"), class = "test_class"), + print(b) + ) + ) + testthat::expect_identical(get_outputs(q1), list("[1] \"test_print\"\n")) + }) + + testthat::it("printed plots are returned as recordedplot in a list (1)", { + q <- qenv() + q1 <- eval_code(q, expression(a <- 1L, plot(a))) + testthat::expect_true(inherits(get_outputs(q1)[[1]], "recordedplot")) + }) + + testthat::it("printed plots are returned as recordedplot in a list (2)", { + q <- qenv() + q1 <- eval_code(q, expression(a <- seq_len(10L), hist(a))) + testthat::expect_true(inherits(get_outputs(q1)[[1]], "recordedplot")) + }) + + testthat::it("warnings are returned asis in a list", { + q <- qenv() + q1 <- eval_code(q, expression(warning("test"))) + expected <- simpleWarning("test") + expected["call"] <- NULL + testthat::expect_identical(get_outputs(q1), list(expected)) + }) + + testthat::it("messages are returned asis in a list", { + q <- qenv() + q1 <- eval_code(q, expression(message("test"))) + expected <- simpleMessage("test\n", call = quote(message("test"))) + testthat::expect_identical(get_outputs(q1), list(expected)) + }) + + testthat::it("prints inside for are bundled together", { + q <- within(qenv(), for (i in 1:3) print(i)) + testthat::expect_identical(get_outputs(q)[[1]], "[1] 1\n[1] 2\n[1] 3\n") + }) + + testthat::it("intermediate plots are not kept", { + q <- qenv() + q1 <- eval_code(q, expression(plot(1:10), title("A title"))) + testthat::expect_length(get_outputs(q1), 1) + }) +}) diff --git a/tests/testthat/test-qenv_concat.R b/tests/testthat/test-qenv_concat.R index 7feb0200..32761f88 100644 --- a/tests/testthat/test-qenv_concat.R +++ b/tests/testthat/test-qenv_concat.R @@ -50,11 +50,20 @@ testthat::test_that("Concatenate two independent qenvs with warnings results in q12 <- concat(q1, q2) - testthat::expect_equal( - unlist(lapply(q12@code, attr, "warning"), use.names = FALSE), - c( - "> This is warning 1\n", - "> This is warning 2\n" + testthat::expect_identical( + get_warnings(q12), + paste( + "~~~ Warnings ~~~", + "\n> This is warning 1", + "when running code:", + "warning('This is warning 1')", + "\n> This is warning 2", + "when running code:", + "warning('This is warning 2')", + "\n~~~ Trace ~~~\n", + "warning('This is warning 1')", + "warning('This is warning 2')", + sep = "\n" ) ) }) @@ -65,11 +74,20 @@ testthat::test_that("Concatenate two independent qenvs with messages results in q12 <- concat(q1, q2) - testthat::expect_equal( - unlist(lapply(q12@code, attr, "message"), use.names = FALSE), - c( - "> This is message 1\n", - "> This is message 2\n" + testthat::expect_identical( + get_messages(q12), + paste( + "~~~ Messages ~~~", + "\n> This is message 1", + "when running code:", + "message('This is message 1')", + "\n> This is message 2", + "when running code:", + "message('This is message 2')", + "\n~~~ Trace ~~~\n", + "message('This is message 1')", + "message('This is message 2')", + sep = "\n" ) ) }) diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R index b16ddbdf..01b11353 100644 --- a/tests/testthat/test-qenv_eval_code.R +++ b/tests/testthat/test-qenv_eval_code.R @@ -136,44 +136,12 @@ testthat::test_that("comments fall into proper calls", { testthat::expect_identical(get_code(q), code) }) -testthat::test_that("comments alone are pasted to the next/following call element", { - code <- c("x <- 5", "# comment", "y <- 6") - q <- eval_code(qenv(), code) - testthat::expect_identical( - as.character(q@code)[2], - paste(code[2:3], collapse = "\n") - ) - testthat::expect_identical( - get_code(q), - paste(code, collapse = "\n") - ) -}) - -testthat::test_that("comments at the end of src are added to the previous call element", { - code <- c("x <- 5", "# comment") - q <- eval_code(qenv(), code) - testthat::expect_identical( - as.character(q@code), - paste(code[1:2], collapse = "\n") - ) - testthat::expect_identical( - get_code(q), - paste(code, collapse = "\n") - ) -}) - testthat::test_that("comments from the same line are associated with it's call", { code <- c("x <- 5", " y <- 4 # comment", "z <- 5") q <- eval_code(qenv(), code) testthat::expect_identical(as.character(q@code)[2], code[2]) }) -testthat::test_that("alone comments at the end of the source are considered as continuation of the last call", { - code <- c("x <- 5\n", "y <- 10\n# comment") - q <- eval_code(eval_code(qenv(), code[1]), code[2]) - testthat::expect_identical(as.character(q@code)[2], code[2]) -}) - testthat::test_that("comments passed alone to eval_code that contain @linksto tag have detected dependency", { code <- c("x <- 5", "# comment @linksto x") q <- eval_code(eval_code(qenv(), code[1]), code[2]) @@ -186,3 +154,26 @@ testthat::test_that("comments passed alone to eval_code that contain @linksto ta "x" ) }) + +testthat::test_that("object printed (explicitly) is stored as string in the 'outputs' attribute of a code element", { + q <- eval_code(qenv(), "print('whatever')") + testthat::expect_identical(attr(q@code[[1]], "outputs")[[1]], '[1] "whatever"\n') +}) + +testthat::test_that("object printed (implicitly) is stored asis in the 'outputs' attribute of a code element", { + q <- eval_code(qenv(), "head(letters)") + testthat::expect_identical(attr(q@code[[1]], "outputs")[[1]], head(letters)) +}) + +testthat::test_that("plot output is stored as recordedplot in the 'outputs' attribute of a code element", { + q <- eval_code(qenv(), "plot(1)") + testthat::expect_s3_class(attr(q@code[[1]], "outputs")[[1]], "recordedplot") +}) + +testthat::test_that("plot cannot modified previous plots when calls are seperate", { + q <- qenv() + q1 <- eval_code(q, expression(plot(1:10))) + + q2 <- eval_code(q1, expression(title("A title"))) + testthat::expect_s3_class(q2, "qenv.error") +}) diff --git a/tests/testthat/test-qenv_get_code.R b/tests/testthat/test-qenv_get_code.R index 825d9769..a5dc2975 100644 --- a/tests/testthat/test-qenv_get_code.R +++ b/tests/testthat/test-qenv_get_code.R @@ -581,7 +581,7 @@ testthat::test_that("detects occurrence of a function definition when a formal i }) testthat::test_that("detects occurrence of a function definition with a @linksto usage", { - code <- c( + code <- trimws(c( " foo <- function() { env <- parent.frame() @@ -589,7 +589,7 @@ testthat::test_that("detects occurrence of a function definition with a @linksto }", "foo() # @linksto x", "y <- x" - ) + )) q <- eval_code(qenv(), code) testthat::expect_identical( get_code(q, names = "x"), @@ -601,7 +601,7 @@ testthat::test_that("detects occurrence of a function definition with a @linksto # for loop -------------------------------------------------------------------------------------------------------- testthat::test_that("objects in for loop are extracted if passed as one character", { - code <- " + code <- trimws(" some_other_dataset <- mtcars original_dataset <- iris[, 1:4] count <- 1 @@ -610,11 +610,11 @@ testthat::test_that("objects in for loop are extracted if passed as one characte count <- count + 1 } output <- rlang::list2(x = original_dataset) - " + ") q <- eval_code(qenv(), code) testthat::expect_identical( get_code(q, names = "output"), - gsub("\n some_other_dataset <- mtcars\n", "", code, fixed = TRUE) + gsub("some_other_dataset <- mtcars\n", "", code, fixed = TRUE) ) }) diff --git a/tests/testthat/test-qenv_join.R b/tests/testthat/test-qenv_join.R index a234fced..ccb23284 100644 --- a/tests/testthat/test-qenv_join.R +++ b/tests/testthat/test-qenv_join.R @@ -130,11 +130,20 @@ testthat::test_that("Joining two independent qenvs with warnings results in obje q <- c(q1, q2) - testthat::expect_equal( - vapply(q@code, attr, which = "warning", character(1L), USE.NAMES = FALSE), - c( - "> This is warning 1\n", - "> This is warning 2\n" + testthat::expect_identical( + get_warnings(q), + paste( + "~~~ Warnings ~~~", + "\n> This is warning 1", + "when running code:", + "warning('This is warning 1')", + "\n> This is warning 2", + "when running code:", + "warning('This is warning 2')", + "\n~~~ Trace ~~~\n", + "warning('This is warning 1')", + "warning('This is warning 2')", + sep = "\n" ) ) }) @@ -145,11 +154,20 @@ testthat::test_that("Joining two independent qenvs with messages results in obje q <- c(q1, q2) - testthat::expect_equal( - vapply(q@code, attr, which = "message", character(1L), USE.NAMES = FALSE), - c( - "> This is message 1\n", - "> This is message 2\n" + testthat::expect_identical( + get_messages(q), + paste( + "~~~ Messages ~~~", + "\n> This is message 1", + "when running code:", + "message('This is message 1')", + "\n> This is message 2", + "when running code:", + "message('This is message 2')", + "\n~~~ Trace ~~~\n", + "message('This is message 1')", + "message('This is message 2')", + sep = "\n" ) ) }) diff --git a/vignettes/qenv.Rmd b/vignettes/qenv.Rmd index 4859e32c..0a16b9fd 100644 --- a/vignettes/qenv.Rmd +++ b/vignettes/qenv.Rmd @@ -14,6 +14,7 @@ A `qenv` inherits from the `environment` class, behaves like an environment, and - It inherits from the environment and methods such as `$`, `get`, `ls`, `as.list()` work out of the box. - `qenv` is a locked environment, and data modification is only possible through the `eval_code` and `within` functions. +- It stores printed and plotted outputs (see `get_outputs`). - It stores metadata about the code used to create the data (see `get_code`). - It supports slicing by `[`. - It is immutable which means that each code evaluation does not modify the original `qenv` environment directly. @@ -56,14 +57,17 @@ The same result can be achieved with the `within` method. ```{r} q2 <- within(my_qenv, y <- x * 2) q2 <- within(q2, z <- y * 2) +q2 <- within(q2, plot(z)) print(q2) ``` -To extract objects from a `qenv`, use `[[`; this is particularly useful for displaying them in a `shiny` app. You can retrieve the code used to generate the `qenv` using the `get_code()` function. +To extract specific object from a `qenv`'s environment, use `[[`. To extract an output of a `print` or `plot` functions, use `get_outputs()` to get a `list()` of outputs captured by `qenv`. These functions are particularly useful for displaying them in a `shiny` app. You can retrieve the code used to generate the `qenv` using the `get_code()` function. ```{r} print(q2[["y"]]) +print(get_outputs(q2)[[1]]) + cat(get_code(q2)) ``` @@ -126,7 +130,8 @@ The feasibility of joining `qenv` objects hinges on the contents of the environm ### Warnings and messages in `qenv` objects -In cases where warnings or messages arise while evaluating code within a `qenv` environment, these are captured and stored within the `qenv` object. Access these messages and warnings using below +In cases where warnings or messages arise while evaluating code within a `qenv` environment, these are captured and stored within the `qenv` object. Access these messages and warnings using `get_messages()` and `get_warnings()` functions as shown below. + ```{r} q_message <- eval_code(qenv(), quote(message("this is a message"))) @@ -163,13 +168,13 @@ server <- function(input, output, session) { # create a qenv containing the reproducible output output_q <- reactive({ req(input$option) - eval_code( + within( data_q, - bquote(p <- hist(iris_data[, .(input$option)])) + p <- hist(iris_data[, .(input$option)]) ) }) - # display output + # display plot output output$plot <- renderPlot(output_q()[["p"]]) # display code output$rcode <- renderText(get_code(output_q()))